Child-process launch/execution adapters for Node/Bun/Python, runtime-selection layer, entry resolvers, stream forwarding, lifecycle observation, and exit-code translation helpers used by outer startup.
- Strategy pattern:
selectExecutor(config)returns one ofnodeExecutor,bunExecutor, orpythonExecutorbased onselectRuntimeKind(engines)from@scramjet/symbols. - Uniform contract: each executor exposes a
RuntimeExecutorwith aspawn()method and returnsRuntimeProcessHandles. - 6-slot stdio layout: all runtimes use the same 6-slot layout:
- fd0: stdin (pipe)
- fd1: stdout (pipe)
- fd2: stderr (pipe)
- fd3: IPC (reserved — ensures Node creates IPC;
channel.unref()in Bun executor) - fd4: control (raw duplex byte pipe)
- fd5: monitoring (raw duplex byte pipe)
- Runtime-specific guardrails:
- Node (
node-process-executor.ts): absolute-path validation, optional tsx CJS loader (--requirewhen the resolved entry is TypeScript source). - Bun (
bun-process-executor.ts): usesprocess.env.BUN_BIN || "bun", stripsSEQUENCE_*/RUNNER_CONNECT_INFOfrom child env. - Python (
python-process-executor.ts): usesprocess.env.PYTHON_BIN || "python3", buildsPYTHONPATHvia candidate search, two spawn forms (production-m runner_pythonvs test override).
- Node (
- Entry resolvers:
runner-node-launcher.ts: resolves@scramjet/runner-nodeentry — searchespackage.jsonbin, dist, src with TypeScript source fallback; returnsResolvedRunnerNodeEntry { entry, needsTypeScriptSourceLoader }.runner-bun-launcher.ts: resolves@scramjet/runner-bunentry — searches dist, bin, src; returnsResolvedRunnerBunEntry { entry }.
- Non-destructive monitoring observation (
lifecycle-observer.ts): inspects a copy of the monitoring stream for terminal frames (SEQUENCE_COMPLETED/SEQUENCE_STOPPED) without consuming bytes from the stream. - Stream forwarding (
stream-forwarder.ts): pipes child stdout/stderr to host with{ end: false }; idempotentdetach()for teardown. - Exit translation (
exit-translation.ts):closeevent →TranslatedChildClosewithRunnerExitCode/RunnerMessageCodemapping + terminal frame writer.
Sub-modules:
select.ts: runtime kind -> executor mapping (bun → bunExecutor, python3 → pythonExecutor, default → nodeExecutor).node-process-executor.ts: Node child spawn + stdio contract.bun-process-executor.ts: Bun child spawn + env stripping + IPC fd unref.python-process-executor.ts: Python child spawn + PYTHONPATH resolution.runner-node-launcher.ts: resolvesrunner-noderuntime entry path.runner-bun-launcher.ts: resolvesrunner-bunruntime entry path.stream-forwarder.ts: raw passthrough of child stdout/stderr to host.lifecycle-observer.ts: non-destructive monitoring scan for terminal frames.exit-translation.ts:closeevent → exit code/message mapping + terminal frame writer.
start-runner passes engines and spawn options into selectExecutor. The chosen executor then:
- spawns child with fixed stdio layout (
[pipe,pipe,pipe,ipc,pipe,pipe]). - returns validated handles (
stdout,stderr,control,monitoring,child). - leaves channel framing untouched for outer runner to forward raw bytes.
Lifecycle helpers (lifecycle-observer, exit-translation) inspect the child monitoring stream for terminal markers and translate process close outcomes into canonical runner exit semantics.
Depends on @scramjet/types runtime interfaces (BootConfig, SpawnOptions, RuntimeProcessHandles, RuntimeExecutor), @scramjet/symbols runtime selection + message codes, Node child_process/stream modules, and @scramjet/runner-node/@scramjet/runner-bun entry resolvers.