import type { ChildProcess } from "node:child_process"; import { EventEmitter } from "node:events"; export interface LifeguardProcess { pid: number; task_id: string; agent_type: string; process: ChildProcess; } /** * Monitors running subagent processes for heartbeats, hard timeouts, * and parent-exit cleanup. Emits "stalled" and "timeout" events when * processes are terminated. */ export declare class SubagentLifeguard extends EventEmitter { private processes; private lastHeartbeat; private timeouts; /** When each task started, used to compute the load-scaled hard timeout. */ private startedAt; /** Per-agent base hard timeout (before load scaling), captured at monitor(). */ private baseTimeoutMs; private checkInterval; /** Wall-clock time the heartbeat check last ran, to measure event-loop lag. */ private lastCheckAt; /** * Count of external in-process tasks (e.g. background MCP tools) running in the * parent alongside the monitored subagents. These don't show up in `processes` * but still saturate the parent's CPU/event loop, so they're folded into the * load multiplier. Updated by the pool via setExternalLoad(). */ private externalLoad; /** * Tasks already reaped (stalled/timeout kill sent) but whose `exit` has not * fired yet. The heartbeat check runs every 5s, so without this a stalled task * would re-emit "stalled" (and re-kill) on every tick until the process exits. */ private reaping; private disposed; private readonly cwd; private parentShutdownHandler?; constructor(cwd: string); /** * Set the count of external in-process tasks (background MCP tools) sharing the * parent's CPU/event loop. Folded into loadMultiplier() so concurrent MCP work * widens the heartbeat/timeout budgets just like extra monitored subagents do. * Negative values are clamped to 0. */ setExternalLoad(count: number): void; /** * Tolerance multiplier for the current load. 1 concurrent task → 1x; each extra * concurrent task adds LOAD_TOLERANCE_PER_PROCESS, capped at MAX_LOAD_MULTIPLIER. * Concurrency counts both monitored subagents and external load (background MCP * tools), since both contend for the same parent event loop. */ private loadMultiplier; /** * Begin monitoring a child process. The process must emit a * `{"ping":true}` JSON line on stdout every 30 seconds. */ monitor(task_id: string, agent_type: string, proc: ChildProcess): void; /** Record a heartbeat for a monitored task. */ recordHeartbeat(task_id: string): void; /** Get the last recorded heartbeat timestamp, or null. */ lastHeartbeatAt(task_id: string): number | null; /** True if the task is currently being monitored. */ isMonitoring(task_id: string): boolean; /** Kill all monitored processes and clean up. */ dispose(): void; private checkHeartbeats; private handleStalled; private handleTimeout; /** * Kill a monitored subagent and everything it spawned. The pool spawns * children detached on POSIX (each leads its own process group), so the * group/tree kill reaches nested grandchildren (the subagent's bash commands, * its own subagents) that a single-PID SIGKILL would orphan. A pid of 0 means * the spawn never produced a process — nothing to kill (and `kill(-0)` would * signal our own process group). */ private killTree; private untrack; private setupParentExitHandlers; private gracefulShutdown; private sweepOldAgents; private hasRunningPid; private rmrf; } //# sourceMappingURL=lifeguard.d.ts.map