/** * OS process supervision for the device Automation arm. * * Kept separate from automation.ts so spawn/heartbeat/resume policy does not * share a control-flow surface with POSIX group ownership and Windows tree * termination. */ import { type ChildProcess } from 'node:child_process'; export declare const CLI_SUPERVISOR_SOURCE: string; export declare function configureCliSupervisorCommand(command: string, prefix: readonly string[]): void; /** Entrypoint-side landing for the self-exec command above. */ export declare function runPackagedCliSupervisor(argv: string[]): void; export type TargetExitStage = 'target_exit' | 'target_spawn' | 'supervisor_spawn' | 'supervisor_exit'; export interface TargetExit { exitCode: number | null; signalCode: NodeJS.Signals | null; error: string | null; errorCode: string | null; stage: TargetExitStage; } interface SupervisedCli { supervisor: ChildProcess; targetExit: Promise; } /** Wait for the supervised CLI to exit, the timeout to lapse, or cancellation. */ export declare function waitForTargetExit(targetExit: Promise, timeoutMs: number, abortSignal?: AbortSignal): Promise<{ timedOut: boolean; aborted: boolean; target?: TargetExit; }>; /** Await the target metadata after process-tree termination. */ export declare function waitForTargetExitAfterTermination(targetExit: Promise, timeoutMs?: number): Promise; /** Wait a bounded interval for a signal sent to the child to take effect. */ declare function waitForSignalledExit(proc: ChildProcess, timeoutMs: number): Promise; type ProcessGroupOwner = Pick; /** * Signal a POSIX group only while its supervisor is a live ownership anchor. * Once the anchor has exited, its numeric pid/pgid may refer to an unrelated * future process group and must never be used as a negative-pid signal target. * Exported only so the PID-reuse safety invariant has a direct regression test. */ export declare function signalOwnedPosixProcessGroup(owner: ProcessGroupOwner, signal: NodeJS.Signals, sendSignal?: typeof process.kill): boolean; /** * Count the members of an owned POSIX process group other than the supervisor * itself. Used to tell "the command cleaned up after itself" apart from "the * command left background work that the group SIGKILL is about to destroy". * * The supervisor is spawned `detached`, so its pgid equals its pid; every * process the command started without deliberately leaving the group shares * that pgid. Anything still listed under it once the target has exited is a * descendant the caller backgrounded, so reaping it is a caller-visible event * rather than routine cleanup (#3629). * * POSIX exposes no syscall to list a group, and kill(-pgid, 0) cannot answer * this because the live supervisor is itself a member and always makes the * probe succeed -- so the group has to be enumerated from the process table. * On Linux that is read from `/proc`, because the images this daemon ships in * are slim ones that carry no `procps`: shelling out to `ps` there would fail * with ENOENT and silently report "nothing was reaped" on exactly the hosts * this fix is for. `ps` remains the reader everywhere else (macOS dev hosts). * * Zombies are not survivors. The daemon is PID 1 in the worker image with no * init in front of it, and libuv only reaps the children it spawned itself, so * a grandchild the command backgrounded and that exited before the command * returned is reparented to the daemon and stays a zombie -- still listed * under the supervisor's pgid -- for the life of the container. Counting it * would fail a run that left nothing running, and would later charge it to an * unrelated run when the kernel reuses the pid as a new supervisor's pgid. * * A failure is reported as "no survivors": this drives a report field, never * the cleanup itself, so an unreadable process table must never change what * gets killed. */ export declare function countOwnedGroupSurvivors(owner: ProcessGroupOwner, readProcessTable?: () => string): number; /** Ask the supervisor to release its non-reusable group identity and reap it. */ export declare function releaseSupervisor(proc: ChildProcess, timeoutMs?: number): Promise; /** Windows has no negative-pid process groups; taskkill supplies bounded tree cleanup. */ declare function taskkillWindowsTree(proc: ChildProcess, force: boolean): Promise; /** * Keep the supervisor alive until Windows has had its forced tree-kill chance. * Killing only the supervisor after a failed graceful `taskkill /T` would * orphan the real CLI and make its numeric tree root unusable. Exported, with * its collaborators injectable, only so that ordering is testable off Windows. */ export declare function terminateWindowsProcessTree(proc: ChildProcess, terminateTree?: typeof taskkillWindowsTree, waitForExit?: typeof waitForSignalledExit): Promise<'SIGTERM' | 'SIGKILL'>; /** * Stop the complete CLI process tree, escalating to SIGKILL if any POSIX group * member ignores SIGTERM. Returns the signal that actually ended it, which the * timeout branch reports as `exit_signal`. */ export declare function terminateChild(proc: ChildProcess): Promise<'SIGTERM' | 'SIGKILL'>; /** Spawn the real CLI beneath a persistent process-group ownership anchor. */ export declare function spawnSupervisedCli(binary: string, args: string[], env: NodeJS.ProcessEnv, options?: { stdin?: 'ignore' | 'pipe'; cwd?: string; }): SupervisedCli; export {}; //# sourceMappingURL=automation-process.d.ts.map