import type { EventEmitter } from "events"; /** Mirrors crosspad_hil.errors.HilError.to_dict(): {code, message, hint, details}. */ export declare class HilError extends Error { code: string; hint?: string; details: Record; constructor(code: string, message: string, hint?: string, details?: Record); toJSON(): { code: string; message: string; hint?: string; details: Record; }; } /** Error codes minted on this side (the daemon's own codes pass through). */ export declare const DAEMON_DIED = "DAEMON_DIED"; export declare const DAEMON_PROTOCOL = "DAEMON_PROTOCOL"; export declare const TIMEOUT = "TIMEOUT"; export declare const CANCELLED = "CANCELLED"; export type HilEvent = { ev: string; } & Record; /** What the daemon process is holding, counted from inside it (crosspad_hil.serve). */ export interface DaemonStats { version: string; pid: number; uptime_s: number; ops_total: number; handles: Record; handles_total: number; threads: number; open_fds: number; alsa_seq_clients: number; } export interface ChildLike extends EventEmitter { stdin: NodeJS.WritableStream | null; stdout: NodeJS.ReadableStream | null; stderr: NodeJS.ReadableStream | null; pid?: number; kill(signal?: NodeJS.Signals | number): boolean; } export type SpawnFn = (cmd: string, args: string[], opts: { cwd?: string; }) => ChildLike; export interface HilDaemonOpts { python: string; cwd?: string; onEvent?: (ev: HilEvent) => void; /** Test seam: replaces child_process.spawn. */ spawnFn?: SpawnFn; /** Test seam: the idle clock. */ now?: () => number; /** Idle-sweep period, and the minimum idle before the daemon is probed. 0 disables the timer. */ recycleCheckMs?: number; /** Idle after which a daemon holding nothing is stopped. */ recycleIdleMs?: number; /** ALSA sequencer clients tolerated on an idle daemon before it is stopped. */ alsaSeqBudget?: number; } export interface RequestOpts { signal?: AbortSignal; timeoutMs?: number; /** Bookkeeping this side issued itself: does not count as activity. */ internal?: boolean; } /** ALSA's sequencer client table is ~64 for the whole machine, not per process. */ export declare const ALSA_SEQ_BUDGET = 24; /** What recycleTick() decided. Only the two `stopped-*` outcomes ended a daemon. */ export type RecycleOutcome = "stopped-idle" | "stopped-alsa" | "busy" | "in-use" | "fresh" | "no-daemon" | "unknown"; export declare class HilDaemon { private readonly opts; private proc; private pending; private nextId; private stdoutBuf; private stderrBuf; private stderrLines; private starting; private stopWaiters; private termTimer; private killTimer; private recycleTimer; private lastActivityAt; private readonly spawnFn; /** The last automatic stop, for the doctor to report. */ lastRecycle: { at: number; reason: "idle" | "alsa_seq"; stats: DaemonStats; } | null; constructor(opts: HilDaemonOpts); get alive(): boolean; private now; private get checkMs(); private get idleMs(); private get seqBudget(); /** Last `n` daemon stderr lines, newest last (from TraceSession.stderrTail). */ stderrTail(n?: number): string; /** Spawn ` -m crosspad_hil.serve` and wait for `serve.ping`. * Idempotent: a second call while starting/alive shares the same promise. */ start(): Promise; private doStart; /** serve.stats. Internal, so asking does not itself keep the daemon alive. */ stats(opts?: RequestOpts): Promise; /** Stop and start again. Daemon-side handles do not survive: an open console * or cdc session is gone and has to be reopened. */ restart(): Promise; /** Give an idle daemon's OS resources back. Never touches one that is working * or holding a handle; request() starts a fresh daemon on demand. */ recycleTick(): Promise; private startRecycleTimer; private stopRecycleTimer; /** Send one op and await its correlated reply. Restarts a dead daemon first. */ request(op: string, args: Record, opts?: RequestOpts): Promise; /** Graceful stop: serve.shutdown → SIGTERM (1500 ms) → SIGKILL (4500 ms). * Resolves once the process has exited. Idempotent. */ stop(): Promise; private settle; private lastStderr; /** Newline framing with a carried partial tail (TraceSession.ingest). */ private ingest; /** Bounded stderr ring (TraceSession.ingestStderr). */ private ingestStderr; private onExit; private clearKillTimers; } /** config `hil_python` → $CROSSPAD_HIL_PYTHON → tracer python → "python3". */ export declare function resolvedHilPython(): string; /** Subscribe to daemon events (console.fatal, task.progress, …). Returns an * unsubscribe function. Listeners survive daemon restarts. */ export declare function onHilEvent(cb: (ev: HilEvent) => void): () => void; /** Lazy per-process daemon. Not started until the first request(); a dead * daemon is restarted transparently by the next request(). */ export declare function getHilDaemon(): HilDaemon; /** @internal test-only */ export declare function _resetHilDaemonForTest(): void;