* test(e2e): on the real WSL lane, background work never starts the stopped distro (E9 guard) The Windows real-WSL lane opens a project inside its own imported distro, terminates that distro, and runs a window of background work elsewhere (focus/blur/reveal, workspace switches, a linked issue/PR card, Settings panes, Tasks, account switches). Every child the main process starts is logged; no wsl.exe may run anything in the distro (listings are fine), and `wsl --list --running`, watched each second, must never show it, which also catches UNC touches. Opening a terminal in the WSL workspace then starts it. A participation check fails the lane if the guard skipped. * test(e2e): actually render each Settings pane in the WSL lane's E9 background window openSettingsTarget alone only set the navigation target, so no pane mounted and none of its reads (servers, accounts, agents, integrations, CLI status, the repo pane's orca.yaml check) ran. Open Settings, wait for each pane's section to mount, then close it. * test(e2e): harden the WSL lane's E9 guard per review - process-host logs every spawn to ORCA_E2E_SPAWN_LOG when set, so the guard sees worker-thread and inheriting-child spawns (e.g. the Codex rate-limit fetch), sync ones included. - The spawn baseline is taken before `wsl --terminate`, and the running-list watcher starts right after it, so work in the stopping window counts. - A wsl.exe launch counts as starting the distro unless it names another or only lists/terminates. - The watcher is async with a 5 s timeout and non-overlapping ticks; a failed read is retried and fails the guard after three, never reads as "stopped". The recorder throws if it cannot hook, and the spawn log must be non-empty. The doc names the watcher as the authoritative assertion. * test(e2e): bound the terminate grace, keep the spawn log out of shipped runs, tolerate torn lines - The watcher forgives running reads only within 3 s of `--terminate` and before its first not-running read, which the rounds now await; any running read after it fails the guard. - Main drops ORCA_E2E_SPAWN_LOG outside e2e mode (no ORCA_E2E_USER_DATA_DIR) before anything spawns, so no daemon, PTY or agent inherits it; the log holds full argv, tests only. - readSpawnLog skips a line torn by a concurrent append. * test(e2e): no blocking wsl.exe in the WSL lane's E9 guard, and a timeline for failures The first lane run was cancelled at the 30-minute job limit while the guard step had run 25 minutes: past three 5-minute test timeouts, which only a blocked worker survives. Every wsl.exe call (setup, terminate, cleanup) is now async with a timeout, so a hung one fails the test. The guard logs a timestamped timeline (each running-list read, terminate, rounds) for diagnosis, and the job limit goes to 50 minutes for the three added runs. * test(e2e): let the setup's own WSL terminal launch settle before the terminate; 10 s grace The lane run failed with "Ubuntu was still listed 3000 ms after --terminate" in all three runs. The timeline showed why: focusing the WSL workspace in setup is a user act that opens a WSL terminal, and its launch (codex trust probe, PTY, hook relay) landed about a second after the terminate and restarted the distro. Setup now waits for that terminal's PTY and for wsl.exe launches to go quiet for 3 s before terminating. The grace is two running-list reads (10 s). * test(e2e): interleave wsl.exe launches into the E9 guard's timeline The lane saw the distro boot about two seconds after the terminate with nothing naming the launcher; each running-list tick now also prints the wsl.exe launches logged since the last one. * test(e2e): time and stack every logged spawn and main's WSL UNC touches The E9 guard printed spawns at its poll time, up to a second late, and with no caller. Each record now carries when it happened and the caller's stack, and main's fs touches of \\wsl.localhost paths are logged too, so a boot with no wsl.exe launch before it can still be attributed. * test(e2e): keep spawn-log stacks and main's fs hooks strictly to e2e mode process-host now writes the spawn log, and captures caller stacks, only when ORCA_E2E_USER_DATA_DIR is set as well, so a stray log variable in a CLI or daemon environment does nothing. The main-process fs/spawn hook refuses to install outside an e2e-mode app, and a source scan pins that no shipped code imports or installs it. * test(e2e): guard a focused WSL workspace left idle through the stop Device runs saw a stopped distro restart with its bridge workspace focused and nothing touched: the stop's own fallout re-ran renderer reads as user acts. The new case keeps that workspace focused with a folder expanded in the Explorer and Source Control shown, terminates, and idles. Shared guard helpers move to tests/e2e/helpers/wsl-boot-guard.ts. * test(e2e): capture caller stacks deep enough to reach the IPC handler * test(e2e): give the focused-idle guard's timeout bump its reason
Orca process host
This Node package owns subprocess spawning, forked children, bounded output, cancellation, Windows argument handling, and process-tree termination. Desktop, the headless server, CLI, SSH relay, and daemon use the same implementation. It has no runtime npm dependencies, Electron imports, or native addons.
Import runProcess, runProcessSync, and spawnProcess from @orca/process-host. Import their contracts and default limits from @orca/process-host/process-spec. Other explicit subpaths expose forking, termination, Windows arguments, and bounded output. Application code must use public exports.
spawnProcess guarantees three stream pipes when stdio is omitted, is 'pipe', or starts with ['pipe', 'pipe', 'pipe']. Other specifications return a child with nullable streams. Use PipedProcessSpec and PipedChildProcess when a stream-owning interface requires pipes.
The application supplies policy through process-tree-kill-gate and diagnostics through spawn-observer. Register these before starting managed children. Each process must load one package instance so registration and execution share state. The production exports use one CommonJS output for both import and require callers. The orca-source condition supports tests, dependency analysis, and Node tooling that runs TypeScript through tsx. Source tooling runs in a separate process and must use that condition for every package import; mixing source and compiled output would split the registration state.
From the repository root:
pnpm build:packages
pnpm --filter @orca/process-host typecheck
ORCA_BACKGROUND_LAUNCH=1 pnpm --filter @orca/process-host test
Which copy a caller loads:
- Root app build, test, typecheck, and launch scripts build
distfirst. Editor types and root typechecks read its declarations. pnpm devrefreshesdistonce at launch. Restart it after package edits, or runpnpm dev --watchto rebuild the package and restart the app after package or main-process source changes.- Run
pnpm build:packagesafter package edits before invoking config scripts or Vitest directly; their bootstrap code can readdist. - Vitest and
tsx --conditions=orca-sourceloadsrcthrough theorca-sourcecondition. - Packaging ships
dist; supported build scripts and CI rebuild it first. The packaging guard checks missing exports/modules and removed-source leftovers, but does not certify source freshness. Build the package before invoking electron-builder directly.
The build needs no prior dist: tsx --conditions=orca-source starts its compiler through source. It serializes builds with a lock, compiles into staging, adds new files before replacing existing files, then removes obsolete output. Failed compilation preserves prior output; unchanged files are not rewritten. Publication is atomic per file, so a concurrent reader can still observe mixed build versions. Detected lock loss stops subsequent writes and preserves the new owner's lock; already published files remain. tsx and proper-lockfile are build-only dependencies.
The package tests cover execution, output bounds, cancellation, termination, and Windows command construction. Windows shim integration stays under src/shared/__tests__/process-host because those tests use the host's filesystem cleanup policy. Root CI runs both sets, and root pnpm typecheck type-checks the package tests through tsconfig.test.json; Windows integration still requires a Windows runner. Package artifact tests verify public exports and shared registration state from staged output. The engines floor is Node 18 because the relay bundles this package for remote hosts. Its compiler uses Node 18 module semantics and Node 18 types to reject newer builtin APIs; CI's Node 18 job runs config/scripts/smoke-process-host-node18.mjs against the staged public output. Repository build and test tools use the root manifest's newer Node requirement.