# Runner (spawn-engine child entry) ## Purpose `runner.ts` is the child-process entry point invoked by `SpawnEngine`. The parent process spawns it via `node:child_process.spawn`, writes a JSON `RunnerInput` envelope to its stdin (closing the pipe to signal end-of-input), and reads a JSON `RunnerOutput` envelope from its stdout. The runner runs inside a fresh OS process with a sanitised environment — that process boundary is the security guarantee, not anything the runner itself does. The runner exports `runRunner(input)` as a pure function so the parent can also test the evaluation pipeline without spawning a child. The `main()` entry point reads stdin, parses JSON, calls `runRunner`, and writes stdout — exposed for tests that want to drive the full I/O surface. ## Features - Pure `runRunner(input): RunnerOutput` for in-process testing. - `readStdinAll()` async stdin slurper. - `main()` orchestrator. - Auto-invokes `main()` when the file is launched directly (matched by filename ending in `runner.{js,ts,cjs,mjs}`). - `stringify(value)` JSON-safe fallback used by the captured console. ## Usage: Basic The runner is normally invoked by `SpawnEngine`; consumers do not call it directly. To test in isolation: ```ts import { runRunner } from "@suaveplan/sandbox/runner"; const out = runRunner({ code: "1 + 1", context: {}, modules: [], maxOutputSize: 1024, }); console.log(out.result); // 2 ``` ## Usage: Advanced Drive the full stdin/stdout pipeline against the runner file by spawning a real child: ```ts import { spawn } from "node:child_process"; const child = spawn(process.execPath, ["./dist/spawn-engine/runner.js"], { stdio: ["pipe", "pipe", "pipe"], }); child.stdin.write(JSON.stringify({ code: "1 + 1", context: {}, modules: [], maxOutputSize: 1024 })); child.stdin.end(); ``` ## API Reference - `RunnerInput` — `{ code, context, modules, maxOutputSize }`. - `RunnerOutput` — `{ success, result, error, logs, memoryUsed }`. - `runRunner(input: RunnerInput): RunnerOutput`. - `readStdinAll(): Promise`. - `main(): Promise`. - `stringify(value: unknown): string`. ## Implementation Notes The runner builds a function scope via `Reflect.construct(Function, [...keys, source])` — this is the controlled boundary the runner owns; the parent is the one that decides whether to spawn it. Strict mode is enforced via the prologue. The auto-invoke guard checks `process.argv[1]` against a filename regex so re-importing the runner from a test never triggers the main loop.