import { randomUUID } from "node:crypto";import { rmSync } from "node:fs";import { constants as osConstants } from "node:os";import { posix as posixPath } from "node:path";import type { NativeMountPluginDescriptor } from "./descriptors.js";import type { ConnectTerminalOptions, Kernel, KernelExecOptions, KernelExecResult, KernelSpawnOptions, ManagedProcess, OpenShellOptions, ProcessInfo, ShellHandle, VirtualFileSystem, VirtualStat,} from "./test-runtime.js";import type { AuthenticatedSession, CreatedVm, GuestFilesystemStat, SidecarProcess, SidecarMountDescriptor, SidecarProcessSnapshotEntry, SidecarResourceSnapshot, SidecarSignalHandlerRegistration, SidecarSocketStateEntry,} from "./sidecar-process.js"; export interface PlainMountConfig { path: string; driver: VirtualFileSystem; guestSource?: string; guestFstype?: string; readOnly?: boolean;} export interface NativeMountConfig { path: string; plugin: NativeMountPluginDescriptor; guestSource?: string; guestFstype?: string; readOnly?: boolean;} export function serializeMountConfigForSidecar( mount: PlainMountConfig | NativeMountConfig,): SidecarMountDescriptor { if ("driver" in mount) { return { guestPath: mount.path, guestSource: mount.guestSource ?? "js_bridge", guestFstype: mount.guestFstype ?? "js_bridge", readOnly: mount.readOnly ?? false, plugin: { id: "js_bridge", config: {}, }, }; } return { guestPath: mount.path, guestSource: mount.guestSource ?? mount.plugin.id, guestFstype: mount.guestFstype ?? mount.plugin.id, readOnly: mount.readOnly ?? false, plugin: { id: mount.plugin.id, config: mount.plugin.config ?? {}, }, };} const SYNTHETIC_PID_BASE = 1_000_000;const MISSING_EXIT_EVENT_GRACE_MS = 500;const PROTECTED_READ_ONLY_GUEST_ROOTS = ["/etc/secure-exec"] as const;const TRAILING_OUTPUT_DRAIN_INTERVAL_MS = 10;const TRAILING_OUTPUT_DRAIN_MAX_MS = 250;const TRAILING_OUTPUT_DRAIN_QUIET_TURNS = 2; async function drainTrailingProcessOutputTurn( delayMs = 0,): Promise<void> { // Native-sidecar `process_output` events can lag one macrotask behind the // terminal `process_exited` notification for very short-lived processes, and // under suite load the sidecar event pump can need a little extra time to // flush delayed output through its listener callbacks. await new Promise<void>((resolve) => { setTimeout(resolve, delayMs); });} const PREFERRED_SIGNAL_NAMES = [ "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGSTKFLT", "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU", "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO", "SIGPWR", "SIGSYS", "SIGEMT", "SIGINFO",] as const;const NON_TERMINATING_SIGNALS = new Set([ "0", "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGURG", "SIGWINCH",]);const LOCALLY_TERMINATING_SIGNALS = new Set(["SIGKILL", "SIGTERM"]);const NON_CANONICAL_SIGNAL_NAMES = new Set([ "SIGCLD", "SIGIOT", "SIGPOLL", "SIGUNUSED",]);const SIGNAL_NAME_BY_NUMBER = buildSignalNameByNumber();const DOUBLE_QUOTE_ESCAPABLE_CHARACTERS = new Set(['"', "\\", "$", "`"]);function appendDoubleQuotedEscape(current: string, character: string): string { if (DOUBLE_QUOTE_ESCAPABLE_CHARACTERS.has(character)) { return current + character; } if (character === "\n") { return current; } return `${current}\\${character}`;} function parseSimpleExecCommand(command: string): string[] | null { const tokens: string[] = []; let current = ""; let quote: "'" | '"' | null = null; let escaped = false; for (const character of command) { if (quote === null) { if (escaped) { current += character; escaped = false; continue; } if (character === "\\") { escaped = true; continue; } if (character === "'" || character === '"') { quote = character; continue; } if (/\s/.test(character)) { if (current) { tokens.push(current); current = ""; } continue; } if ("|&;<>()$`*?[]{}~!".includes(character)) { return null; } current += character; continue; } if (quote === "'") { if (character === "'") { quote = null; continue; } current += character; continue; } if (escaped) { current = appendDoubleQuotedEscape(current, character); escaped = false; continue; } if (character === "\\") { escaped = true; continue; } if (character === '"') { quote = null; continue; } if (character === "$" || character === "`") { return null; } current += character; } if (quote !== null || escaped) { return null; } if (current) { tokens.push(current); } if (tokens.length === 0) { return null; } if (tokens.some((token) => token.length === 0)) { return null; } return tokens;} function canUseDirectExec( driver: string | undefined, commandName: string | undefined,): boolean { return driver === "wasmvm" || (driver === "node" && commandName === "node");} function shellSingleQuote(value: string): string { if (value.length === 0) { return "''"; } return `'${value.replace(/'/g, `'\"'\"'`)}'`;} function buildSignalNameByNumber(): Map<number, string> { const signals = osConstants.signals as Record<string, number | undefined>; const names = new Map<number, string>(); for (const name of PREFERRED_SIGNAL_NAMES) { const value = signals[name]; if (typeof value === "number") { names.set(value, name); } } for (const [name, value] of Object.entries(signals)) { if ( typeof value === "number" && !NON_CANONICAL_SIGNAL_NAMES.has(name) && !names.has(value) ) { names.set(value, name); } } return names;} export function toSidecarSignalName(signal: number): string { return SIGNAL_NAME_BY_NUMBER.get(signal) ?? String(signal);} export interface LocalCompatMount { path: string; fs: VirtualFileSystem; readOnly: boolean;} interface KernelSocketSnapshot { processId: string; host?: string; port?: number; path?: string;} interface KernelSignalState { handlers: Map< number, { action: SidecarSignalHandlerRegistration["action"]; mask: Set<number>; flags: number; } >;} interface SocketLookupCacheEntry { value: KernelSocketSnapshot | null; pending: Promise<void> | null;} interface TrackedProcessEntry { pid: number; processId: string; command: string; args: string[]; driver: string; cwd: string; env: Record<string, string>; startTime: number; exitTime: number | null; hostPid: number | null; exitCode: number | null; started: boolean; startPromise: Promise<void>; waitPromise: Promise<number>; resolveWait: (exitCode: number) => void; rejectWait: (error: Error) => void; onStdout: Set<(data: Uint8Array) => void>; onStderr: Set<(data: Uint8Array) => void>; pendingStdin: Array<string | Uint8Array>; stdinFlushPromise: Promise<void> | null; pendingCloseStdin: boolean; pendingKillSignal: number | null; waitWithFallbackPromise: Promise<number> | null; hostExitObservedAt: number | null; outputGeneration: number; exitViaEvent: boolean;} interface NativeSidecarKernelProxyOptions { client: SidecarProcess; session: AuthenticatedSession; vm: CreatedVm; disposeClient?: boolean; env: Record<string, string>; cwd: string; defaultExecCwd?: string; localMounts: LocalCompatMount[]; commandGuestPaths: ReadonlyMap<string, string>; onWasmCommandResolved?: (command: string) => void; onDispose?: () => Promise<void>;} export class NativeSidecarKernelProxy { readonly env: Record<string, string>; readonly cwd: string; readonly commands: ReadonlyMap<string, string>; readonly vfs: VirtualFileSystem; readonly processes = new Map<number, ProcessInfo>(); private readonly defaultExecCwd: string | undefined; private readonly client: SidecarProcess; private readonly disposeClient: boolean; private readonly session: AuthenticatedSession; private readonly vm: CreatedVm; private readonly localMounts: LocalCompatMount[]; private readonly commandDrivers: Map<string, string>; private readonly onWasmCommandResolved: | ((command: string) => void) | undefined; private readonly onDispose: (() => Promise<void>) | undefined; private readonly trackedProcesses = new Map<number, TrackedProcessEntry>(); private readonly trackedProcessesById = new Map< string, TrackedProcessEntry >(); private readonly listenerLookups = new Map<string, SocketLookupCacheEntry>(); private readonly boundUdpLookups = new Map<string, SocketLookupCacheEntry>(); private readonly signalStates = new Map<number, KernelSignalState>(); private readonly signalRefreshes = new Map<number, Promise<void>>(); private sidecarProcessSnapshot: SidecarProcessSnapshotEntry[] = []; private processSnapshotRefresh: Promise<void> | null = null; private readonly observedProcessStartTimes = new Map<string, number>(); private readonly rootView: VirtualFileSystem; private zombieTimerCountValue = 0; private zombieTimerCountRefresh: Promise<void> | null = null; private disposed = false; private pumpError: Error | null = null; private nextSyntheticPid = SYNTHETIC_PID_BASE; private readonly eventPumpAbortController = new AbortController(); private readonly eventPump: Promise<void>; constructor(options: NativeSidecarKernelProxyOptions) { this.client = options.client; this.disposeClient = options.disposeClient ?? true; this.session = options.session; this.vm = options.vm; this.env = { ...options.env }; this.cwd = options.cwd; this.defaultExecCwd = options.defaultExecCwd; this.localMounts = [...options.localMounts].sort( (left, right) => right.path.length - left.path.length, ); this.commandDrivers = buildCommandMap(options.commandGuestPaths); this.onWasmCommandResolved = options.onWasmCommandResolved; this.onDispose = options.onDispose; this.commands = this.commandDrivers; this.vfs = this.createFilesystemView(true); this.rootView = this.createFilesystemView(false); this.eventPump = this.runEventPump(); void this.eventPump.catch(() => {}); } createRootView(): VirtualFileSystem { return this.rootView; } get zombieTimerCount(): number { if (!this.zombieTimerCountRefresh) { this.zombieTimerCountRefresh = this.refreshZombieTimerCount(); } return this.zombieTimerCountValue; } registerCommandGuestPaths( commandGuestPaths: ReadonlyMap<string, string>, ): void { for (const name of commandGuestPaths.keys()) { this.commandDrivers.set(name, "wasmvm"); } } async dispose(): Promise<void> { if (this.disposed) { return; } this.disposed = true; this.eventPumpAbortController.abort(); const liveProcesses = [...this.trackedProcesses.values()].filter( (entry) => entry.exitCode === null, ); await Promise.allSettled( liveProcesses.map((entry) => this.signalProcess(entry, 15)), ); await Promise.race([ this.client.disposeVm(this.session, this.vm), new Promise<void>((resolve) => setTimeout(resolve, 1000)), ]).catch(() => {}); for (const entry of liveProcesses) { if (entry.exitCode === null) { // The sidecar dispose path already performs TERM/KILL escalation for any // guest executions that are still live. Resolve local waiters eagerly so // VM teardown does not hang on killed ACP adapter processes that never // surface a terminal process_exited event back to the JS bridge. this.finishProcess(entry, 143); } } if (this.disposeClient) { await this.client.dispose().catch(() => {}); } await this.eventPump.catch(() => {}); await this.onDispose?.().catch(() => {}); } async exec( command: string, options?: KernelExecOptions, ): Promise<KernelExecResult> { if (!this.commands.has("sh")) { throw new Error( `native sidecar exec requires guest shell command 'sh': ${command}`, ); } const stdoutChunks: Uint8Array[] = []; const stderrChunks: Uint8Array[] = []; const effectiveCwd = options?.cwd ?? this.defaultExecCwd ?? this.cwd; const parsedCommand = parseSimpleExecCommand(command); const resolveExecPath = (targetPath: string) => targetPath.startsWith("/") ? posixPath.normalize(targetPath) : posixPath.normalize(posixPath.join(effectiveCwd, targetPath)); const runAndCapture = async ( proc: ManagedProcess, stdinOverride?: string | Uint8Array, readExitCode?: () => Promise<number>, ): Promise<KernelExecResult> => { if (stdinOverride !== undefined) { proc.writeStdin(stdinOverride); } else if (options?.stdin !== undefined) { proc.writeStdin(options.stdin); } // `kernel.exec()` is a non-interactive run-to-completion API: when the // caller does not opt into a streaming stdin handle, the guest process // should observe EOF after any provided input so commands like // `node -e ...` do not linger behind an inherited open stdin pipe. proc.closeStdin(); const waitPromise = proc.wait(); const shellExitCode = typeof options?.timeout === "number" ? await new Promise<number>((resolve) => { const timer = setTimeout(() => { proc.kill(9); void proc.wait().then(resolve); }, options.timeout); void waitPromise.then((code) => {Lines 1–500 of 2398. Load more
import { randomUUID } from "node:crypto"; import { rmSync } from "node:fs"; import { constants as osConstants } from "node:os"; import { posix as posixPath } from "node:path"; import type { NativeMountPluginDescriptor } from "./descriptors.js"; import type { ConnectTerminalOptions, Kernel, KernelExecOptions, KernelExecResult, KernelSpawnOptions, ManagedProcess, OpenShellOptions, ProcessInfo, ShellHandle, VirtualFileSystem, VirtualStat, } from "./test-runtime.js"; import type { AuthenticatedSession, CreatedVm, GuestFilesystemStat, SidecarProcess, SidecarMountDescriptor, SidecarProcessSnapshotEntry, SidecarResourceSnapshot, SidecarSignalHandlerRegistration, SidecarSocketStateEntry, } from "./sidecar-process.js"; export interface PlainMountConfig { path: string; driver: VirtualFileSystem; guestSource?: string; guestFstype?: string; readOnly?: boolean; } export interface NativeMountConfig { path: string; plugin: NativeMountPluginDescriptor; guestSource?: string; guestFstype?: string; readOnly?: boolean; } export function serializeMountConfigForSidecar( mount: PlainMountConfig | NativeMountConfig, ): SidecarMountDescriptor { if ("driver" in mount) { return { guestPath: mount.path, guestSource: mount.guestSource ?? "js_bridge", guestFstype: mount.guestFstype ?? "js_bridge", readOnly: mount.readOnly ?? false, plugin: { id: "js_bridge", config: {}, }, }; } return { guestPath: mount.path, guestSource: mount.guestSource ?? mount.plugin.id, guestFstype: mount.guestFstype ?? mount.plugin.id, readOnly: mount.readOnly ?? false, plugin: { id: mount.plugin.id, config: mount.plugin.config ?? {}, }, }; } const SYNTHETIC_PID_BASE = 1_000_000; const MISSING_EXIT_EVENT_GRACE_MS = 500; const PROTECTED_READ_ONLY_GUEST_ROOTS = ["/etc/secure-exec"] as const; const TRAILING_OUTPUT_DRAIN_INTERVAL_MS = 10; const TRAILING_OUTPUT_DRAIN_MAX_MS = 250; const TRAILING_OUTPUT_DRAIN_QUIET_TURNS = 2; async function drainTrailingProcessOutputTurn( delayMs = 0, ): Promise<void> { // Native-sidecar `process_output` events can lag one macrotask behind the // terminal `process_exited` notification for very short-lived processes, and // under suite load the sidecar event pump can need a little extra time to // flush delayed output through its listener callbacks. await new Promise<void>((resolve) => { setTimeout(resolve, delayMs); }); } const PREFERRED_SIGNAL_NAMES = [ "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGSTKFLT", "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU", "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO", "SIGPWR", "SIGSYS", "SIGEMT", "SIGINFO", ] as const; const NON_TERMINATING_SIGNALS = new Set([ "0", "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGURG", "SIGWINCH", ]); const LOCALLY_TERMINATING_SIGNALS = new Set(["SIGKILL", "SIGTERM"]); const NON_CANONICAL_SIGNAL_NAMES = new Set([ "SIGCLD", "SIGIOT", "SIGPOLL", "SIGUNUSED", ]); const SIGNAL_NAME_BY_NUMBER = buildSignalNameByNumber(); const DOUBLE_QUOTE_ESCAPABLE_CHARACTERS = new Set(['"', "\\", "$", "`"]); function appendDoubleQuotedEscape(current: string, character: string): string { if (DOUBLE_QUOTE_ESCAPABLE_CHARACTERS.has(character)) { return current + character; } if (character === "\n") { return current; } return `${current}\\${character}`; } function parseSimpleExecCommand(command: string): string[] | null { const tokens: string[] = []; let current = ""; let quote: "'" | '"' | null = null; let escaped = false; for (const character of command) { if (quote === null) { if (escaped) { current += character; escaped = false; continue; } if (character === "\\") { escaped = true; continue; } if (character === "'" || character === '"') { quote = character; continue; } if (/\s/.test(character)) { if (current) { tokens.push(current); current = ""; } continue; } if ("|&;<>()$`*?[]{}~!".includes(character)) { return null; } current += character; continue; } if (quote === "'") { if (character === "'") { quote = null; continue; } current += character; continue; } if (escaped) { current = appendDoubleQuotedEscape(current, character); escaped = false; continue; } if (character === "\\") { escaped = true; continue; } if (character === '"') { quote = null; continue; } if (character === "$" || character === "`") { return null; } current += character; } if (quote !== null || escaped) { return null; } if (current) { tokens.push(current); } if (tokens.length === 0) { return null; } if (tokens.some((token) => token.length === 0)) { return null; } return tokens; } function canUseDirectExec( driver: string | undefined, commandName: string | undefined, ): boolean { return driver === "wasmvm" || (driver === "node" && commandName === "node"); } function shellSingleQuote(value: string): string { if (value.length === 0) { return "''"; } return `'${value.replace(/'/g, `'\"'\"'`)}'`; } function buildSignalNameByNumber(): Map<number, string> { const signals = osConstants.signals as Record<string, number | undefined>; const names = new Map<number, string>(); for (const name of PREFERRED_SIGNAL_NAMES) { const value = signals[name]; if (typeof value === "number") { names.set(value, name); } } for (const [name, value] of Object.entries(signals)) { if ( typeof value === "number" && !NON_CANONICAL_SIGNAL_NAMES.has(name) && !names.has(value) ) { names.set(value, name); } } return names; } export function toSidecarSignalName(signal: number): string { return SIGNAL_NAME_BY_NUMBER.get(signal) ?? String(signal); } export interface LocalCompatMount { path: string; fs: VirtualFileSystem; readOnly: boolean; } interface KernelSocketSnapshot { processId: string; host?: string; port?: number; path?: string; } interface KernelSignalState { handlers: Map< number, { action: SidecarSignalHandlerRegistration["action"]; mask: Set<number>; flags: number; } >; } interface SocketLookupCacheEntry { value: KernelSocketSnapshot | null; pending: Promise<void> | null; } interface TrackedProcessEntry { pid: number; processId: string; command: string; args: string[]; driver: string; cwd: string; env: Record<string, string>; startTime: number; exitTime: number | null; hostPid: number | null; exitCode: number | null; started: boolean; startPromise: Promise<void>; waitPromise: Promise<number>; resolveWait: (exitCode: number) => void; rejectWait: (error: Error) => void; onStdout: Set<(data: Uint8Array) => void>; onStderr: Set<(data: Uint8Array) => void>; pendingStdin: Array<string | Uint8Array>; stdinFlushPromise: Promise<void> | null; pendingCloseStdin: boolean; pendingKillSignal: number | null; waitWithFallbackPromise: Promise<number> | null; hostExitObservedAt: number | null; outputGeneration: number; exitViaEvent: boolean; } interface NativeSidecarKernelProxyOptions { client: SidecarProcess; session: AuthenticatedSession; vm: CreatedVm; disposeClient?: boolean; env: Record<string, string>; cwd: string; defaultExecCwd?: string; localMounts: LocalCompatMount[]; commandGuestPaths: ReadonlyMap<string, string>; onWasmCommandResolved?: (command: string) => void; onDispose?: () => Promise<void>; } export class NativeSidecarKernelProxy { readonly env: Record<string, string>; readonly cwd: string; readonly commands: ReadonlyMap<string, string>; readonly vfs: VirtualFileSystem; readonly processes = new Map<number, ProcessInfo>(); private readonly defaultExecCwd: string | undefined; private readonly client: SidecarProcess; private readonly disposeClient: boolean; private readonly session: AuthenticatedSession; private readonly vm: CreatedVm; private readonly localMounts: LocalCompatMount[]; private readonly commandDrivers: Map<string, string>; private readonly onWasmCommandResolved: | ((command: string) => void) | undefined; private readonly onDispose: (() => Promise<void>) | undefined; private readonly trackedProcesses = new Map<number, TrackedProcessEntry>(); private readonly trackedProcessesById = new Map< string, TrackedProcessEntry >(); private readonly listenerLookups = new Map<string, SocketLookupCacheEntry>(); private readonly boundUdpLookups = new Map<string, SocketLookupCacheEntry>(); private readonly signalStates = new Map<number, KernelSignalState>(); private readonly signalRefreshes = new Map<number, Promise<void>>(); private sidecarProcessSnapshot: SidecarProcessSnapshotEntry[] = []; private processSnapshotRefresh: Promise<void> | null = null; private readonly observedProcessStartTimes = new Map<string, number>(); private readonly rootView: VirtualFileSystem; private zombieTimerCountValue = 0; private zombieTimerCountRefresh: Promise<void> | null = null; private disposed = false; private pumpError: Error | null = null; private nextSyntheticPid = SYNTHETIC_PID_BASE; private readonly eventPumpAbortController = new AbortController(); private readonly eventPump: Promise<void>; constructor(options: NativeSidecarKernelProxyOptions) { this.client = options.client; this.disposeClient = options.disposeClient ?? true; this.session = options.session; this.vm = options.vm; this.env = { ...options.env }; this.cwd = options.cwd; this.defaultExecCwd = options.defaultExecCwd; this.localMounts = [...options.localMounts].sort( (left, right) => right.path.length - left.path.length, ); this.commandDrivers = buildCommandMap(options.commandGuestPaths); this.onWasmCommandResolved = options.onWasmCommandResolved; this.onDispose = options.onDispose; this.commands = this.commandDrivers; this.vfs = this.createFilesystemView(true); this.rootView = this.createFilesystemView(false); this.eventPump = this.runEventPump(); void this.eventPump.catch(() => {}); } createRootView(): VirtualFileSystem { return this.rootView; } get zombieTimerCount(): number { if (!this.zombieTimerCountRefresh) { this.zombieTimerCountRefresh = this.refreshZombieTimerCount(); } return this.zombieTimerCountValue; } registerCommandGuestPaths( commandGuestPaths: ReadonlyMap<string, string>, ): void { for (const name of commandGuestPaths.keys()) { this.commandDrivers.set(name, "wasmvm"); } } async dispose(): Promise<void> { if (this.disposed) { return; } this.disposed = true; this.eventPumpAbortController.abort(); const liveProcesses = [...this.trackedProcesses.values()].filter( (entry) => entry.exitCode === null, ); await Promise.allSettled( liveProcesses.map((entry) => this.signalProcess(entry, 15)), ); await Promise.race([ this.client.disposeVm(this.session, this.vm), new Promise<void>((resolve) => setTimeout(resolve, 1000)), ]).catch(() => {}); for (const entry of liveProcesses) { if (entry.exitCode === null) { // The sidecar dispose path already performs TERM/KILL escalation for any // guest executions that are still live. Resolve local waiters eagerly so // VM teardown does not hang on killed ACP adapter processes that never // surface a terminal process_exited event back to the JS bridge. this.finishProcess(entry, 143); } } if (this.disposeClient) { await this.client.dispose().catch(() => {}); } await this.eventPump.catch(() => {}); await this.onDispose?.().catch(() => {}); } async exec( command: string, options?: KernelExecOptions, ): Promise<KernelExecResult> { if (!this.commands.has("sh")) { throw new Error( `native sidecar exec requires guest shell command 'sh': ${command}`, ); } const stdoutChunks: Uint8Array[] = []; const stderrChunks: Uint8Array[] = []; const effectiveCwd = options?.cwd ?? this.defaultExecCwd ?? this.cwd; const parsedCommand = parseSimpleExecCommand(command); const resolveExecPath = (targetPath: string) => targetPath.startsWith("/") ? posixPath.normalize(targetPath) : posixPath.normalize(posixPath.join(effectiveCwd, targetPath)); const runAndCapture = async ( proc: ManagedProcess, stdinOverride?: string | Uint8Array, readExitCode?: () => Promise<number>, ): Promise<KernelExecResult> => { if (stdinOverride !== undefined) { proc.writeStdin(stdinOverride); } else if (options?.stdin !== undefined) { proc.writeStdin(options.stdin); } // `kernel.exec()` is a non-interactive run-to-completion API: when the // caller does not opt into a streaming stdin handle, the guest process // should observe EOF after any provided input so commands like // `node -e ...` do not linger behind an inherited open stdin pipe. proc.closeStdin(); const waitPromise = proc.wait(); const shellExitCode = typeof options?.timeout === "number" ? await new Promise<number>((resolve) => { const timer = setTimeout(() => { proc.kill(9); void proc.wait().then(resolve); }, options.timeout); void waitPromise.then((code) => {