import { type KillTreeStrategy, type WindowsKillTreeResult, type ProcessTreeTerminationOutcome, type ReapProcessTreeOptions } from "./process-tree.js"; import { type ProcessGroupService } from "./process-group.js"; export interface SpawnOptions { cwd?: string; env?: Record; /** * Base env composition for the child: `mirror_native` (default) inherits the * parent env; `clean` starts from a minimal allowlist (agent env isolation). * `env` patches + scrub are applied on top either way. */ inheritEnv?: "mirror_native" | "clean"; input?: string; timeoutMs?: number; /** Signal sent when the consumer closes the stream before process exit. */ cancelSignal?: NodeJS.Signals; /** Test seams: prove the win32 kill-fallback without a Windows host (the * fsyncDirectoryHandle precedent). Production always uses the defaults. */ killTreeStrategy?: KillTreeStrategy; windowsKillTree?: (pid: number) => WindowsKillTreeResult; /** Hard-kill delay after cancelSignal when the child ignores cooperative stop. */ cancelKillDelayMs?: number; /** * Overall bound (ms) on the whole-tree death proof after a cancel. Past it the * generator returns and `onTerminationUnconfirmed` fires rather than hanging. * Defaults to `cancelKillDelayMs + 4000`. */ cancelDeadlineMs?: number; /** * Fail-closed disclosure (QA-027) for a consumer that broke the stream EARLY * (it is no longer iterating, so the typed `termination_unconfirmed` event * cannot be delivered): called once when a proven-alive descendant group * survives the bounded TERM->KILL escalation. An actively-iterating consumer * receives the typed `termination_unconfirmed` ProcEvent instead — the primary, * non-optional disclosure channel. */ onTerminationUnconfirmed?: (info: { rootPid: number; survivors: number[]; unresolved: Array<{ pgid: number; reason: string; }>; }) => void; /** * Injection seam for the whole-tree death proof (deterministic tests of the * termination_unconfirmed disclosure). Defaults to the real `reapProcessTree`. * Production callers never set this. */ reap?: (opts: ReapProcessTreeOptions) => Promise; /** * Injection seam for the identity-proven process-group service used by the * direct-group cancel belt (deterministic tests of unknown-capture / stale- * identity refusal). Defaults to the shared `defaultProcessGroupService`. * Production callers never set this. */ processGroups?: ProcessGroupService; /** Runtime abort signal for active daemon/orchestrator cancellation. */ abortSignal?: AbortSignal; /** * Keep stdin open after writing `input` (bidirectional protocols such as * Claude's stream-json control channel). The caller receives a writer via * `onSpawn` and OWNS closing it; the child usually exits on stdin EOF. */ keepStdinOpen?: boolean; /** Called once after spawn with a live stdin handle (see keepStdinOpen). */ onSpawn?: (io: ChildStdin) => void; } /** Minimal live stdin handle for bidirectional CLI protocols. */ export interface ChildStdin { /** Write one line/frame; errors are swallowed (exit carries the outcome). */ write(data: string): void; /** Close stdin (EOF) — the cooperative way to end a streaming session. */ end(): void; /** Settles once the child process closes or fails to spawn. Session handlers * race blocking protocol work against this instead of orphaning a wait after * the native process can no longer answer. */ closed: Promise; } export type ProcEvent = { type: "stdout"; line: string; } | { type: "stderr"; line: string; } | { type: "exit"; code: number | null; signal: NodeJS.Signals | null; } /** * QA-027 fail-closed death-proof disclosure: the process was cancelled and the * whole-tree reap could NOT confirm death — a descendant group is proven-alive * (`survivors`) or its leader identity was unreadable (`unresolved`). Emitted * once, AFTER the terminal `exit`, so an actively-iterating consumer terminalizes * over a typed unconfirmed-death fact instead of a silent clean cancel. */ | { type: "termination_unconfirmed"; rootPid: number; survivors: number[]; unresolved: Array<{ pgid: number; reason: string; }>; }; /** * Spawn a process and stream stdout/stderr lines as they arrive, ending with an * `exit` event. Throws (rejects the iterator) if the binary cannot be spawned * (e.g. ENOENT) so callers can detect an unavailable harness. */ export declare function spawnProcess(cmd: string, args: string[], opts?: SpawnOptions): AsyncGenerator; export interface CaptureResult { code: number | null; signal: NodeJS.Signals | null; stdout: string; stderr: string; } /** * Label which stream said what in a compact one-line detail (truncated probe * errors must stay attributable to stderr vs stdout). Null when both are empty. * * `transform` (e.g. a secret redactor) runs on each FULL stream BEFORE * truncation — truncating first could split a token and leave a partial * secret the redactor no longer recognizes. Each present stream then gets an * equal code-point budget (never splitting a surrogate pair), so one noisy * stream cannot evict the other from the detail. */ export declare function labelStreams(stderr: string, stdout: string, opts?: { maxLen?: number; transform?: (s: string) => string; }): string | null; /** Run a process to completion, capturing stdout/stderr. Throws on spawn error. */ export declare function runCapture(cmd: string, args: string[], opts?: SpawnOptions): Promise; /** * BYTE-FAITHFUL capture: `runCapture` rides readline, which splits * on lone `\r` too and rejoins with `\n` — destroying CR bytes in CRLF file * content and fabricating trailing newlines. Diff-carrying git output MUST * come through here, or `final/patch.diff` is corrupted at the source and * fails `git apply` downstream. Raw buffers, no line splitting, no * fabrication; the same spawn machinery (process group, abort, timeout). */ export declare function runCaptureRaw(cmd: string, args: string[], opts?: SpawnOptions): Promise; export interface OrphanExitOptions { /** Poll cadence for the parent-death check (default 5s, unref'd). */ intervalMs?: number; getppid?: () => number; exit?: (code: number) => void; /** Disclosed once, right before exiting (e.g. a stderr note). */ onOrphaned?: () => void; } /** * Orphaned-bridge watchdog (W3.5): a stdio bridge (mcp/acp serve) whose HOST * died without the pipe closing — grandchildren holding inherited fds, a * SIGKILLed host — reparents to pid 1 and would otherwise idle forever with * nobody on the other end. Polling ppid catches exactly that class; the * interval is unref'd so the watchdog never keeps a clean bridge alive. */ export declare function armOrphanExit(options?: OrphanExitOptions): { stop: () => void; }; //# sourceMappingURL=proc.d.ts.map