import type { Database } from "../data/memory/schema.js"; import type { ThreadLifecycleService } from "./thread-lifecycle.service.js"; export declare const PENDING_TASKS_DIR: string; export declare const PROCESS_BASE_DIR: string; export declare const PROCESS_LOGS_DIR: string; export declare const THREAD_LOGS_DIR: string; export declare const PROCESS_PIDS_DIR: string; export interface SpawnedThread { pid: number; threadId: number; name: string; startedAt: number; createdAt: number; logFile: string; memorySourceThreadId?: number; threadType?: "worker" | "branch"; } interface PidFileEntry { threadId: number; pid: number; filePath: string; name?: string; threadType?: "worker" | "branch"; startedAt?: number; } export declare const spawnedThreads: SpawnedThread[]; export declare const markTaskConsumed: (threadId: number) => void; export declare const wasTaskConsumed: (threadId: number) => boolean; export declare const clearTaskConsumed: (threadId: number) => void; /** Delete a worker's pending-task mailbox (and any in-flight .processing temp) * so a retired worker can never be re-fed a late append. */ export declare function deletePendingTaskMailbox(threadId: number): void; /** * Liveness + identity in one question: is `pid` alive AND (when `spawnedAtMs` * is given) still the process we spawned? * * Two-step check, neither of which ever spawns a child process: * 1. `process.kill(pid, 0)` — a plain syscall. ESRCH (any non-EPERM error) is * a definitive "dead" and needs no broker (this also keeps dev mode * without a supervisor fully functional for the common case). * 2. Otherwise the answer is NOT trustworthy on Windows — kill(0) succeeds * for kernel-zombie PIDs held open by a lingering handle, and EPERM is * what a parent sees for its own terminated child. Ask the supervisor's * process broker, which answers from a native process snapshot and also * verifies creation-time identity (PID-reuse guard). * * Broker unreachable → "cannot confirm → treat alive", exactly the audited-safe * degradation the deleted tasklist/Get-Process breakers had. */ export declare function isProcessAlive(pid: number, spawnedAtMs?: number): Promise; /** * Thread-scoped liveness — the correct question for a broker-spawned worker. * * On Windows claude.exe/copilot.exe are LAUNCHERS: they fork the real agent into * the thread's Job Object under a NEW pid, then exit. ~20s later the launcher pid * we recorded at spawn is gone, so a pid-first `isProcessAlive(launcherPid)` * returns false and orphans the still-working child. The thread's JOB is the * source of truth; ask the broker by threadId: * - null (unreachable) → cannot confirm → treat ALIVE (never kill on unknown), * exactly the audited-safe degradation contract. * - known:false (404) → the broker has NEVER managed this thread (dev mode / * pre-broker process) → fall back to the pid check. * - known:true → use the job/registry verdict (kernel truth). * * Off Windows there is no launcher-fork indirection (the pid IS the real child), * so defer straight to the pid check. */ export declare function isThreadAlive(threadId: number, fallbackPid?: number, spawnedAtMs?: number): Promise; export declare function readPidFiles(): PidFileEntry[]; export declare function findAliveThread(threadId: number): Promise; export declare function getActiveThreadIds(): Promise; export declare function ensureDirs(): void; /** Thread transcripts (logs/threads/*.json) are append-only debug artifacts with * one file per thread per day. Nothing consumes them at runtime except the * segfault tail (which only ever reads the current day's file), so old ones can * be deleted freely. Without this they grow unbounded — they were the single * largest disk consumer (hundreds of MB across months). Bound retention to a * configurable window (default 14 days). Called from ensureDirs() (server * startup and each spawn); once retention is in effect the dir stays small so * the readdir/stat sweep is cheap. */ export declare function pruneOldThreadLogs(): void; /** * Check liveness of a thread directly via its PID file, bypassing the * spawnedThreads[] in-memory cache. Returns the live PID together with its * recorded spawn identity (startedAt) so callers can make identity-checked * kill/alive calls, or undefined if no PID file exists or the process is dead * (stale file is deleted). */ export declare function findAliveThreadViaPidFile(threadId: number): Promise<{ pid: number; startedAt?: number; } | undefined>; /** * Reconcile in-memory state (spawnedThreads[]) with the two authoritative * sources of truth at startup: * - SQLite thread_registry (intent / configuration) * - PID files on disk (OS evidence of running processes) * * Replaces the old restoreFromPidFiles() + cleanupStalePidFiles() pair. */ export declare function reconcileState(db: Database, threadLifecycle: ThreadLifecycleService): Promise; /** * Kill the process tree rooted at `pid` via the supervisor's broker (native * TerminateProcess over the whole tree, identity-checked when `spawnedAtMs` is * given, outcome recorded in the broker's on-disk registry). Never spawns a * child process and never blocks the event loop. * * Broker unreachable → degraded best-effort: one native `process.kill(pid)` * (root only — Node cannot enumerate a tree without shelling out, and that * machinery is gone by design) followed by a conservative liveness re-check. * A survived kill keeps the PID file so the keeper/cleanup policy loops retry * and escalate — same contract as before. */ export declare function killProcessTree(pid: number, threadId: number, caller?: string, spawnedAtMs?: number): Promise; /** * Kill a worker by its THREAD, not by the pid we recorded at spawn. * * For a launcher-forked worker the recorded pid is the launcher, which has long * since exited; `killProcessTree(launcherPid)` therefore routes to the broker's * KillByPid, hits its "AlreadyDead" fast-path (the launcher pid is gone) and * returns BEFORE the job-kill fallback — so the live forked child is never * touched. Routing by threadId (pid omitted) takes the broker's KillByThread * path instead: it terminates the whole Job Object (every live member), or, when * the job assign failed (Avecto downgrade), the registry-identity survivor the * broker adopted. This is the kill that actually reaches the real agent. * * Broker unreachable → degraded best-effort native kill of `fallbackPid` (self/ * server-guarded), and we NEVER remove the PID file or claim death: a later sweep * retries against fresh evidence. `brokerKillTree` also returns null for an * unknown/already-dead thread (HTTP 404); the degraded native kill of a dead pid * is a harmless no-op there, and the caller's own `isThreadAlive` gate — not this * function — decides teardown. */ export declare function killThreadTree(threadId: number, caller?: string, fallbackPid?: number, spawnedAtMs?: number): Promise; export {}; //# sourceMappingURL=process.service.d.ts.map