/** * Pausing a run: freeze the driver and every process under it where it stands, * and thaw them later (autopilot-pause-resume). * * `stop` ends a run at its next step and leaves its agents running. A pause * stops everything mid-step, and `resume` carries on from the same instruction. * It exists for closing the laptop: sleep cuts the network, an agent that * cannot reach its API exits, and the driver then parks every item whose agent * exited — on 2026-10-04 that parked five items in nine minutes. * * SIGSTOP and SIGCONT, so POSIX only. The tree is a process-table snapshot * walked by parent pid, not process groups: agents are detached into groups of * their own and their tools into further groups, so a group signal misses them. * * A pid is never trusted on liveness alone. Pids wrap within hours on a busy * machine and the state outlives a reboot, so a recorded pid can name a stranger. * Each process is matched on a key read when it was spawned or frozen — one * that survives clock steps, because a VM's clock is stepped forward on wake * from exactly the sleep a pause is for. Records written before keys were kept * fall back to the wall-clock spawn time. * * @module */ import { type AutopilotPaths } from './state.js'; export interface ProcessEntry { pid: number; ppid: number; /** Start time, epoch ms. On Linux it moves when the wall clock is stepped. */ started: number; /** * Identity that survives clock steps. On Linux ms since boot, from * `/proc//stat`; elsewhere the start time, which BSD keeps as a fixed * wall-clock stamp. */ key: number; stopped: boolean; } /** * Which system a pid belongs to. Linux has a boot id and a pid namespace; a * container shares its host's boot id but not its namespace. BSD/macOS has * neither, and needs none: its start stamps are unique on their own. */ export interface SystemId { boot: string | null; ns: string | null; } /** An identity read when a process was spawned: its key, and the system that read it. */ export interface Identity extends SystemId { key: number; } export interface FrozenProcess { pid: number; key: number; } export interface PauseRecord { paused_at: string; /** Linux's boot id, so a reboot is seen even if a key happens to match. */ boot?: string | null; /** Linux's pid namespace, so a container's record is told from its host's. */ ns?: string | null; /** In the order they were frozen: the driver first. */ processes: FrozenProcess[]; } export interface PauseDeps { platform: NodeJS.Platform; /** One snapshot of every process on the machine. */ processTable(): ProcessEntry[]; system(): SystemId; /** Send `sig`; false when the process is gone or not ours. */ signal(pid: number, sig: 'SIGSTOP' | 'SIGCONT'): boolean; now(): number; /** The pid doing the pausing. */ self: number; } /** * A root that was not frozen. `reused`: its pid now belongs to another process. * `unverified`: an older autopilot relaunched it without recording when, and it * is not under the driver either, so nothing tells it from a stranger. */ export interface SkippedRoot { pid: number; who: string; reason: 'reused' | 'unverified'; } export type PauseOutcome = { ok: true; frozen: number; skipped: SkippedRoot[]; } | { ok: false; reason: string; skipped?: SkippedRoot[]; }; export type ResumeOutcome = { ok: true; resumed: number; gone: number[]; pausedAt: string; } | { ok: false; reason: string; }; /** `roots` and everything under them, breadth first, each pid once. */ export declare function descendants(roots: number[], table: ProcessEntry[]): number[]; /** * `a` was read on a different system from `here`, one whose pids this one cannot * see: one side has a boot id and the other none (a Linux VM and its macOS * host), or the same kernel in another pid namespace (a container and its * Linux host). Different boot ids are not this — that is a reboot. */ export declare function elsewhere(a: SystemId, here: SystemId): boolean; export declare function loadPause(p: AutopilotPaths): PauseRecord | null; /** * The pause record while it still holds, else null: one of its processes is * still the same process, on the same boot, and still stopped. A record whose * processes all died or were continued by hand does not hold. * * Read-only. The record is read before the process table, so a pause writing * it concurrently is seen with its signals already sent — and only `pause` and * `resume` ever remove it, so a `status` racing a `pause` cannot lose the list * `resume` needs. */ export declare function livePause(p: AutopilotPaths, table: () => ProcessEntry[], system?: () => SystemId): PauseRecord | null; /** * Freeze the run's driver, every building item's agent, and everything under * them. * * With a live driver it holds the autopilot lock while it freezes, so it only * ever freezes the driver between passes: never mid-merge or mid-release, and * never holding a lock that would read as stale after an hour and be broken. * With no driver there is no pass to interrupt, and a lock left by a killed * pass must not stop the agents being frozen. The record is rewritten after * each round, so a pause that dies part-way still leaves `resume` the list of * what it froze. */ export declare function pauseRun(p: AutopilotPaths, deps: PauseDeps): PauseOutcome; /** * Thaw what `pauseRun` froze: leaves first and the driver last, so its next * pass finds its agents running. A pid that is no longer the process it froze * is not signalled. */ export declare function resumeRun(p: AutopilotPaths, deps: Pick): ResumeOutcome; /** The pause half of `status`. */ export declare function pauseStatusLines(record: PauseRecord | null): string[]; /** `ps` elapsed time, `[[dd-]hh:]mm:ss`, in seconds; NaN when it is not that shape. */ export declare function parseEtime(etime: string): number; /** Parse `ps -A -o pid= -o ppid= -o etime= -o stat=`. `now` is when it ran. */ export declare function parseProcessTable(stdout: string, now: number): ProcessEntry[]; /** * The fields of `/proc//stat` that matter, or null. The command name sits * in parentheses and may itself contain spaces and parentheses, so fields are * counted from the last `)`. */ export declare function parseProcStat(stat: string, ticksPerSecond: number): { ppid: number; sinceBootMs: number; stopped: boolean; } | null; export declare function processTable(): ProcessEntry[]; /** `pid`'s identity now, for matching later; null when it cannot be read. */ export declare function identify(pid: number): Identity | null; /** The system this process runs on. */ export declare function currentSystem(): SystemId; export declare function bootId(): string | null; export declare function realPauseDeps(): PauseDeps; //# sourceMappingURL=pause.d.ts.map