/** Env keys that must NEVER reach the daemon process. Restarting crtrd is * overwhelmingly done from inside an agent node's own bash tool (`crtr sys * daemon stop && start`), which inherits that node's FULL env — its identity * (`CRTR_NODE_ID`/`CRTR_KIND`/…, the `nodeEnv()` shape in `core/runtime/ * nodes.ts`), its front-door recursion-guard flag, and any pi-engine * resolution seam a prior test/dev session left exported. The daemon is a * singleton supervisor, never "a node" itself, so none of this belongs in its * env regardless of whether today's code happens to read it — and at least one * of these IS actively read: `host.ts`'s broker-engine resolution falls back to * `process.env['CRTR_BROKER_ENGINE']` verbatim (nodeEnv() never sets that key, * so nothing overrides it per child launch), so a daemon that inherits a * stale/dev override throws inside `headlessBrokerHost.launch()` on EVERY * relaunch it ever attempts, for its whole lifetime — the 2026-07-06 diagnosis * root cause behind 19 nodes killed with "failed to relaunch and is now dead". */ export declare const DAEMON_ENV_STRIP_KEYS: readonly string[]; /** A copy of `process.env` with every `DAEMON_ENV_STRIP_KEYS` entry removed. * Deliberate global config the user actually wants the daemon to see — * `CRTR_HOME`, `CRTR_SUBTREE`, `CRTR_LOG`, `CRTR_DEBUG`, etc. — passes through * untouched; only the node-identity/engine-poisoning surface is stripped. */ export declare function sanitizedDaemonEnv(): NodeJS.ProcessEnv; /** A crtrd declares its owning canvas in argv, so ownership is an exact match * on this process's canvas home. Untagged daemons retain their own * supervision and are never counted or killed here. */ export declare function daemonCommandDeclaresCanvasHome(command: string, home: string): boolean; /** Every crtrd process on this host that declares ownership of this process's * canvas home, by pid. The `daemon/crtrd` argv segment distinguishes daemons * from brokers. `ps` is best-effort: failures return no matches, and the * current process is always excluded. */ export declare function findDaemonPids(): number[]; /** SIGTERM (then SIGKILL after a short grace) every crtrd daemon process EXCEPT * `keepPid`. This is the backstop the pidfile alone cannot provide: a daemon * that survived a prior stop (ignored SIGTERM, or released its ownership lock * without exiting) keeps running a supervise loop — reviving brokers and * contending for canvas state — while never appearing in the pidfile again, so * `stop`'s single recorded-pid signal can never reach it. Returns the pids it * reaped. Best-effort throughout: a pid that dies between enumeration and signal * just no-ops. */ export declare function sweepStrayDaemons(keepPid: number | null): number[]; export interface SpawnDaemonResult { /** True when a new daemon process was spawned. */ started: boolean; /** PID of the newly spawned process, if started. */ pid?: number; /** PID of the already-running daemon, if it was already up. */ existing_pid?: number; } export declare const DAEMON_VERIFY_WINDOW_MS = 20000; export interface DaemonWaitDeps { readPidfile?: () => number | null; isPidAlive?: (pid: number) => boolean; sleepMs?: (ms: number) => Promise | void; now?: () => number; } export interface DaemonStartupDeps extends DaemonWaitDeps { childExited?: () => { code: number | null; signal: NodeJS.Signals | null; } | null; } /** Tiny bounded post-spawn guard: wait for the daemon pidfile and live pid to * appear before reporting success. This catches the "started:true but not yet * plausibly alive" race without turning startup into a retry loop. * * One deadline, owner-first polling. A signal or non-zero child exit fails * immediately. A clean code-0 exit is treated only as a possible ownership- * claim loser (never readiness on its own): polling continues on the SAME * deadline for a different live pidfile owner — the winner may publish its * pidfile deliberately late — and fails at that deadline if none appears. No * deadline extension and no retry spawn. * * Returns null when the spawned pid owns the pidfile; returns a different live * daemon pid when startup lost the race to an already-running daemon. */ export declare function verifyDaemonStartup(pid: number, timeoutMs?: number, deps?: DaemonStartupDeps): Promise; /** Wait until a signaled daemon has exited and relinquished its pidfile. This * makes a following start safe to claim the singleton rather than racing its * previous owner. */ export declare function waitForDaemonExit(pid: number, timeoutMs?: number, deps?: DaemonWaitDeps): Promise; /** Stop the RECORDED daemon: SIGTERM, and if it does not exit within the wait * window, escalate to SIGKILL exactly as `sweepStrayDaemons` does for a stray. * * The escalation is the point. `stop` used to send SIGTERM, wait, and throw on * timeout — before ever reaching the stray sweep — so the one daemon `stop` is * actually aimed at was the only crtrd process exempt from the SIGKILL backstop. * A daemon whose shutdown hung (or outran the window) then became permanently * unstoppable from the CLI: every retry re-sent a SIGTERM it was already * ignoring, and `start` kept refusing as "already running". * * A SIGKILL'd daemon runs no exit handler, so it leaves its pidfile behind * pointing at a dead pid. That is a stale record, not an owner: `readPidfile` * callers gate on `isPidAlive`, and the next `start` overwrites it. So this * waits on process death alone after a kill, never on pidfile release. * * Returns how the daemon went down, for a truthful report. */ export declare function stopDaemonProcess(pid: number, timeoutMs?: number, deps?: DaemonWaitDeps): Promise<'terminated' | 'killed'>; /** Spawn crtrd detached. Returns immediately; the child outlives this process. * * If the daemon is already running, returns {started:false, existing_pid}. * If spawning fails (e.g. missing dist — run `npm run build` first), throws. */ export declare function spawnDaemon(): Promise; /** Start the daemon if it is not already running. No-op if already up. * Silently swallows spawn errors (the canvas still works without the daemon; * nodes just won't be auto-revived). */ export declare function ensureDaemon(): void;