5044 lines · 192.9 KB · TypeScript
RawDownload
import { guestEncodingBootstrapCode } from "./encoding.js";import { BROWSER_BUFFER_POLYFILL_CODE } from "./generated/buffer-polyfill.js";import { BROWSER_PATH_POLYFILL_CODE } from "./generated/path-polyfill.js";import { BROWSER_UTIL_POLYFILL_CODE } from "./generated/util-polyfill.js";import {	createInMemoryFileSystem,	InMemoryFileSystem,} from "./os-filesystem.js";import { PROCESS_SIGNAL_NUMBERS } from "./signals.js";import { BROWSER_WASI_POLYFILL_CODE } from "./wasi-polyfill.js"; export type StdioChannel = "stdout" | "stderr";export type TimingMitigation = "off" | "freeze";type BodyLike = unknown; export interface VirtualDirEntry {	name: string;	isDirectory: boolean;	isSymbolicLink?: boolean;} export interface VirtualStat {	mode: number;	size: number;	sizeExact?: bigint;	blocks: number;	dev: number;	rdev: number;	isDirectory: boolean;	isSymbolicLink: boolean;	atimeMs: number;	mtimeMs: number;	ctimeMs: number;	birthtimeMs: number;	ino: number;	inoExact?: bigint;	nlink: number;	nlinkExact?: bigint;	uid: number;	gid: number;} export interface VirtualFileSystem {	readFile(path: string): Promise<Uint8Array>;	readTextFile(path: string): Promise<string>;	readDir(path: string): Promise<string[]>;	readDirWithTypes(path: string): Promise<VirtualDirEntry[]>;	writeFile(path: string, content: string | Uint8Array): Promise<void>;	createDir(path: string): Promise<void>;	mkdir(path: string, options?: { recursive?: boolean }): Promise<void>;	exists(path: string): Promise<boolean>;	stat(path: string): Promise<VirtualStat>;	removeFile(path: string): Promise<void>;	removeDir(path: string): Promise<void>;	rename(oldPath: string, newPath: string): Promise<void>;	realpath(path: string): Promise<string>;	symlink(target: string, linkPath: string): Promise<void>;	readlink(path: string): Promise<string>;	lstat(path: string): Promise<VirtualStat>;	link(oldPath: string, newPath: string): Promise<void>;	chmod(path: string, mode: number): Promise<void>;	chown(path: string, uid: number, gid: number): Promise<void>;	utimes(path: string, atime: number, mtime: number): Promise<void>;	truncate(path: string, length: number): Promise<void>;	pread(path: string, offset: number, length: number): Promise<Uint8Array>;	pwrite(path: string, offset: number, data: Uint8Array): Promise<void>;} export type PermissionDecision =	| boolean	| { allowed: boolean; reason?: string }	| { allow: boolean; reason?: string };export type PermissionCheck<T = unknown> = (request: T) => PermissionDecision; export interface Permissions {	fs?: PermissionCheck<{ path: string; operation: string }>;	network?: PermissionCheck<{ url?: string; host?: string; port?: number }>;	childProcess?: PermissionCheck<{		command: string;		args: string[];		argv0?: string;		cwd?: string;		env?: Record<string, string>;	}>;	env?: PermissionCheck<{ name: string; value: string }>;} export const allowAllFs: PermissionCheck = () => true;export const allowAllNetwork: PermissionCheck = () => true;export const allowAllChildProcess: PermissionCheck = () => true;export const allowAllEnv: PermissionCheck = () => true;export const allowAll: Permissions = {	fs: allowAllFs,	network: allowAllNetwork,	childProcess: allowAllChildProcess,	env: allowAllEnv,}; export interface ExecOptions {	filePath?: string;	env?: Record<string, string>;	cwd?: string;	stdin?: string;	/**	 * Bind process stdio to a kernel PTY slave fd. When present, stdin/stdout/stderr	 * report isTTY=true, stdout/stderr write through `pty.write(slaveFd, ...)`, and	 * stdin is read from `pty.read(slaveFd, ...)`.	 */	stdioPty?: {		/**		 * Open a fresh kernel PTY for this execution before guest code runs. The		 * execution's stdio binds to the slave; `onOpen` receives the master fd so		 * the host terminal can drive it via `writePty`/`readPty`.		 */		open?: boolean;		/** Bind to an existing slave fd already owned by this execution. */		slaveFd?: number;		columns?: number;		rows?: number;		onOpen?: (pty: PtyOpenResult) => void;	};	cpuTimeLimitMs?: number;	timingMitigation?: TimingMitigation;	onStdio?: StdioHook;	/**	 * Run a PERSISTENT (service-style) program: instead of returning once `main`	 * finishes and microtasks drain, the executor keeps the worker event loop alive	 * for async I/O (stdin events, timers, stream pumps) until the program calls	 * `process.exit` (or a safety timeout). Needed for long-running stdio servers such	 * as an ACP agent that reads requests and replies asynchronously. Default false	 * (run-to-completion).	 */	persistent?: boolean;	/**	 * Stream stdin: the host feeds stdin incrementally (`writeStdin`) and ends it	 * explicitly (`endStdin`) rather than the one-shot `stdin` string that auto-ends.	 * Pairs with `persistent` to drive a long-running stdio program (e.g. an ACP agent)	 * as a proper external client. Use `onStart` to learn the execution id.	 */	streamingStdin?: boolean;	/** Called once with the execution id when the run starts (for writeStdin/endStdin). */	onStart?: (executionId: string) => void;} export interface ExecResult {	code: number;	exitCode?: number;	stdout?: string;	stderr?: string;	errorMessage?: string;} export interface PtyOpenResult {	masterFd: number;	slaveFd: number;	path?: string;	columns: number;	rows: number;} export interface RunResult<T = unknown> {	value?: T;	code: number;	errorMessage?: string;	exports?: T;} export interface OSConfig {	platform?: string;	arch?: string;	type?: string;	release?: string;	version?: string;	cpuCount?: number;	totalmem?: number;	freemem?: number;	hostname?: string;	homedir?: string;	tmpdir?: string;	machine?: string;	user?: string;	shell?: string;	uid?: number;	gid?: number;} export interface ProcessConfig {	cwd?: string;	env?: Record<string, string>;	argv?: string[];	platform?: string;	arch?: string;	version?: string;	pid?: number;	ppid?: number;	uid?: number;	gid?: number;	timingMitigation?: TimingMitigation;	frozenTimeMs?: number;} export type StdioEvent = { channel: StdioChannel; message: string };export type StdioHook = (event: StdioEvent) => void; export type CommandWaitResult =	| number	| {			exitCode: number | null;			signal: number | string | null;	  }; export interface CommandExecutor {	spawn(		command: string,		args: string[],		options?: {			argv0?: string;			cwd?: string;			env?: Record<string, string>;			onStdout?: (data: Uint8Array) => void;			onStderr?: (data: Uint8Array) => void;		},	): {		wait(): Promise<CommandWaitResult>;		writeStdin(data: Uint8Array | string): void;		closeStdin(): void;		kill(signal?: number): boolean | void;	};} export interface NetworkAdapter {	fetch(		url: string,		options?: {			method?: string;			headers?: Record<string, string>;			body?: BodyLike | null;		},	): Promise<{		ok: boolean;		status: number;		statusText: string;		headers: Record<string, string>;		body: string;		url: string;		redirected: boolean;	}>;	dnsLookup(hostname: string): Promise<{		address?: string;		family?: number;		error?: string;		code?: string;	}>;	httpRequest(		url: string,		options?: {			method?: string;			headers?: Record<string, string>;			body?: BodyLike | null;		},	): Promise<{		status: number;		statusText: string;		headers: Record<string, string>;		body: string;		url: string;	}>;} export interface SystemDriver {	filesystem?: VirtualFileSystem;	network?: NetworkAdapter;	commandExecutor?: CommandExecutor;	permissions?: Permissions;	runtime: {		process: ProcessConfig;		os: OSConfig;	};} export interface RuntimeDriverOptions {	system: SystemDriver;	runtime: {		process: ProcessConfig;		os: OSConfig;	};	memoryLimit?: number;	cpuTimeLimitMs?: number;	timingMitigation?: TimingMitigation;	onStdio?: StdioHook;	payloadLimits?: {		base64TransferBytes?: number;		jsonPayloadBytes?: number;	};} export interface NodeRuntimeDriver {	exec(code: string, options?: ExecOptions): Promise<ExecResult>;	run<T = unknown>(code: string, filePath?: string): Promise<RunResult<T>>;	dispose(): void;	terminate?(): Promise<void>;	/** Feed stdin to a running `streamingStdin` execution (id from ExecOptions.onStart). */	writeStdin?(executionId: string, data: string): void;	/** End stdin for a running `streamingStdin` execution. */	endStdin?(executionId: string): void;	/** Write bytes to a PTY fd owned by the execution. */	writePty?(		executionId: string,		fd: number,		data: string | Uint8Array,	): Promise<number>;	/** Read bytes from a PTY fd owned by the execution. */	readPty?(		executionId: string,		fd: number,		options?: { maxBytes?: number; timeoutMs?: number },	): Promise<Uint8Array | null>;	/** Resize a PTY owned by the execution. */	resizePty?(		executionId: string,		fd: number,		size: { columns: number; rows: number },	): Promise<void>;	/** Close a PTY fd owned by the execution. */	closePty?(executionId: string, fd: number): Promise<void>;} export interface NodeRuntimeDriverFactory {	createRuntimeDriver(options: RuntimeDriverOptions): NodeRuntimeDriver;} function normalizePath(inputPath: string): string {	if (!inputPath) return "/";	let normalized = inputPath.startsWith("/") ? inputPath : `/${inputPath}`;	normalized = normalized.replace(/\/+/g, "/");	if (normalized.length > 1 && normalized.endsWith("/")) {		normalized = normalized.slice(0, -1);	}	const parts = normalized.split("/");	const resolved: string[] = [];	for (const part of parts) {		if (!part || part === ".") continue;		if (part === "..") {			resolved.pop();			continue;		}		resolved.push(part);	}	return resolved.length === 0 ? "/" : `/${resolved.join("/")}`;} function dirname(inputPath: string): string {	const normalized = normalizePath(inputPath);	if (normalized === "/") return "/";	const parts = normalized.split("/").filter(Boolean);	return parts.length <= 1 ? "/" : `/${parts.slice(0, -1).join("/")}`;} export function permissionAllowed(	decision: PermissionDecision | undefined,): boolean {	if (decision === undefined) return true;	if (typeof decision === "boolean") return decision;	return "allowed" in decision ? decision.allowed : decision.allow;} export function filterEnv(	env: Record<string, string> | undefined,	permissions?: Permissions,): Record<string, string> {	const source = env ?? {};	if (!permissions?.env) return { ...source };	const output: Record<string, string> = {};	for (const [name, value] of Object.entries(source)) {		if (permissionAllowed(permissions.env({ name, value }))) {			output[name] = value;		}	}	return output;} export function createEnosysError(operation: string): Error {	const error = new Error(`ENOSYS: ${operation} is not supported`);	(error as { code?: string }).code = "ENOSYS";	return error;} export function createFsStub(): VirtualFileSystem {	return createInMemoryFileSystem();} export function createNetworkStub(): NetworkAdapter {	return {		async fetch() {			throw createEnosysError("network.fetch");		},		async dnsLookup() {			return { error: "DNS not supported", code: "ENOSYS" };		},		async httpRequest() {			throw createEnosysError("network.httpRequest");		},	};} export function createCommandExecutorStub(): CommandExecutor {	return {		spawn() {			throw createEnosysError("child_process.spawn");		},	};} export function wrapCommandExecutor(	adapter: CommandExecutor,	permissions?: Permissions,): CommandExecutor {	if (!permissions?.childProcess) return adapter;	const check = (		command: string,		args: string[],		options?: {			argv0?: string;			cwd?: string;			env?: Record<string, string>;		},	): void => {		if (			!permissionAllowed(				permissions.childProcess?.({					command,					args,					argv0: options?.argv0,					cwd: options?.cwd,					env: options?.env,				}),			)		) {			const error = new Error(				`EACCES: blocked child_process spawn '${command}'`,			);			(error as { code?: string }).code = "EACCES";			throw error;		}	};	return {		spawn(command, args, options) {			check(command, args, options);			return adapter.spawn(command, args, options);		},	};} export function wrapFileSystem(	filesystem: VirtualFileSystem,	permissions?: Permissions,): VirtualFileSystem {	if (!permissions?.fs) return filesystem;	const check = (path: string, operation: string): void => {		if (!permissionAllowed(permissions.fs?.({ path, operation }))) {			throw new Error(`EACCES: blocked ${operation} on '${path}'`);		}	};	return {		readFile(path) {			check(path, "readFile");			return filesystem.readFile(path);		},		readTextFile(path) {			check(path, "readTextFile");			return filesystem.readTextFile(path);		},		readDir(path) {			check(path, "readDir");			return filesystem.readDir(path);		},		readDirWithTypes(path) {			check(path, "readDirWithTypes");			return filesystem.readDirWithTypes(path);		},		writeFile(path, content) {			check(path, "writeFile");			return filesystem.writeFile(path, content);		},		createDir(path) {			check(path, "createDir");			return filesystem.createDir(path);		},		mkdir(path, options) {			check(path, "mkdir");			return filesystem.mkdir(path, options);		},		exists(path) {			check(path, "exists");			return filesystem.exists(path);		},		stat(path) {			check(path, "stat");			return filesystem.stat(path);		},

Lines 1–500 of 5044.