/** * When a stdio MCP process must stop: its client closed the pipe, or the * process that spawned it is gone. * * Imported by the relay, so it may use Node builtins only. * * A relay holds one connection on its daemon, and a daemon exits only when it * has had none for CHAT_RECALL_DAEMON_IDLE_SECS. So one relay that outlives its * session keeps a whole daemon — and every version-specific engine it loaded — * resident for as long as that relay runs. The pipe closing is the normal * signal. The parent check covers the case where the pipe never reports EOF, * because another process still holds its other end after the AI tool died. */ import type { Duplex, Readable, Writable } from 'node:stream'; /** How often the parent is checked. The timer is unref'd and costs one syscall. */ export declare const PARENT_POLL_MS = 5000; export interface ParentProbe { /** The current parent pid. `process.ppid` is read live on every access. */ ppid(): number; /** Whether a process with this pid exists. */ alive(pid: number): boolean; } /** `kill(pid, 0)` sends nothing and fails with ESRCH when the pid is gone. */ export declare function pidAlive(pid: number): boolean; export declare const processProbe: ParentProbe; /** * Whether the process that spawned us has gone. * * On Linux and macOS an orphan is re-parented, so its ppid changes (to 1, or to * a subreaper). Windows keeps the original ppid on an orphan, so the pid itself * is probed as well. */ export declare function parentGone(originalPpid: number, probe: ParentProbe): boolean; /** * Call `onGone` once, when the parent that spawned this process is gone. * Returns a function that stops the watch. * * A process that started with pid 1 as its parent was never spawned by a * session (it was started by init or a service manager), so there is no * parent to lose and no watch is set. */ export declare function watchParent(onGone: () => void, opts?: { probe?: ParentProbe; intervalMs?: number; }): () => void; /** * Call `onEnd` once, when the client's side of stdio is finished. * * 'end' is the clean EOF. 'close' without 'end' and 'error' are the same fact * reported by a pipe that broke: nothing more will ever arrive on it. */ export declare function onInputFinished(input: Readable, onEnd: () => void): void; /** * Move bytes both ways between the client's stdio and the daemon socket, and * call `exit` exactly once when the session is over. * * The session is over when the daemon closes the socket, when the client's * input finishes, or when the parent is gone. On input EOF the socket is * half-closed first, so a reply the daemon is still writing reaches the * client; the daemon's server closes its side on that EOF, which ends here as * the socket's 'close'. A daemon that does not close within `drainMs` is not * waited on any longer. */ export declare function bridge(input: Readable, output: Writable, sock: Duplex, exit: (code: number) => void, opts?: { drainMs?: number; probe?: ParentProbe; parentPollMs?: number; }): void; /** The error a client gets for a call that was running when its daemon went away. */ export declare const DAEMON_RESTART_ERROR = "chat-recall restarted while this call ran. Call the tool again."; /** * Like `bridge`, but the session survives its daemon. * * A daemon goes away when it is upgraded or killed. `bridge` then ended the * relay, and the AI tool showed the server as disconnected until a person * reconnected it by hand. Here the relay connects again (`reconnect` starts a * daemon when none answers), replays the client's `initialize` request and * `notifications/initialized`, drops the new daemon's reply to that replay, * and goes on. * * A request that had no reply when the daemon went away gets a JSON-RPC error * that says to call again. Sending it again could run a write twice. * * The session is over when the client's input finishes, when the parent is * gone, when output fails, or when no daemon can be reached again. */ export declare function resilientBridge(input: Readable, output: Writable, first: Duplex, reconnect: () => Promise, exit: (code: number) => void, opts?: { drainMs?: number; probe?: ParentProbe; parentPollMs?: number; }): void; //# sourceMappingURL=relay-lifecycle.d.ts.map