export type HostPlatform = "darwin" | "linux" | "win32" | string; /** macOS: IOPlatformUUID from `ioreg -rd1 -c IOPlatformExpertDevice`. */ export declare function parseDarwinMachineGuid(ioregStdout: string): string | null; /** Linux: /etc/machine-id is a single line of 32 hex chars. */ export declare function parseLinuxMachineId(raw: string): string | null; /** Windows: MachineGuid from `reg query HKLM\…\Cryptography /v MachineGuid`. */ export declare function parseWindowsMachineGuid(regStdout: string): string | null; /** Injectable command runner — returns stdout, or "" on any failure. */ export type CommandRunner = (cmd: string, args: string[]) => string; /** * Compute this host's stable OS machine GUID. Returns null on unsupported * platforms or extraction failure (caller then treats every agent as * cross-host → no PID probe → age-based fallback, the safe default). */ export declare function machineGuid(platform?: HostPlatform, run?: CommandRunner): string | null; /** * This relay's own machine GUID, computed once and memoized. `null` when it * can't be determined — callers MUST treat null as "can't host-scope" and skip * the PID probe. */ export declare function getOwnHostId(): string | null; /** Test-only: reset the memoized own-host-id so tests can re-derive it. */ export declare function _resetOwnHostIdForTests(value?: string | null): void; /** Injectable kill probe (process.kill semantics: throws on dead/forbidden). */ export type KillProbe = (pid: number, signal: 0) => void; /** * Is `pid` a live process on THIS host? Uses the signal-0 probe: * - success → alive * - EPERM (cross-user) → alive (process exists, just not ours) * - ESRCH / anything → dead * Mirrors the probe in src/instance.ts. Non-positive / non-integer pids → dead. */ export declare function isPidAlive(pid: number, kill?: KillProbe): boolean; export interface ProcEntry { pid: number; ppid: number; /** The process's start time, as an opaque stable token (PID-reuse guard). */ startedAt: string; /** The full command line (argv0 + args) — used for the runtime-script check + self-exclude. */ command: string; /** * The executable's `comm` (basename source), captured separately because on * macOS it can itself contain spaces (full exec path) and so can't share a * line with the greedy `command` field. Optional: hand-built test tables and * the parse-from-`command` path leave it unset → we fall back to argv[0]. */ comm?: string; } export interface AgentProcess { pid: number; startedAt: string; } /** * Default agent matcher — an EXACT-basename test, NOT a full-command-line regex. * The matcher is applied to a process's IDENTITY (the executable basename, or — * for a runtime-hosted CLI like `node …/claude` — the script basename), never * the raw `ps command=` string. This is load-bearing: a substring/argv match * against the full command false-hits any process whose PATH contains "claude" * or "codex" (e.g. a checkout under `…/Claude AI/…`), stamping a non-agent * ancestor as the agent — so presence would read a dead agent alive while that * ancestor lives. See processIdentityIsAgent for how identity is derived. * * Scope is honest: covers claude + codex out of the box. An unrecognized CLI * falls back to age-based presence (safe). Operators extend coverage via * RELAY_AGENT_PROCESS_PATTERN (an alternation of BASENAMES, e.g. "aider|goose"). */ export declare const DEFAULT_AGENT_PATTERN: RegExp; /** * Resolve the agent matcher, broadened by RELAY_AGENT_PROCESS_PATTERN when set * (an alternation of executable/script BASENAMES, e.g. "aider|goose|my-cli"). * Anchored to a whole basename (^…$) so it can never match a mid-path segment. * Invalid regexes are ignored (→ default only) so a bad env var can't crash the * startup walk. */ export declare function resolveAgentPattern(env?: Record): RegExp; /** * True iff a process is an agent CLI, judged by IDENTITY (basenames) not the * full command string. Order: (1) executable basename — `comm` when available, * else argv[0] — matched exactly; (2) if the executable is a known runtime, the * hosted SCRIPT basename matched exactly. Never regex-tests the raw command, so * a "claude"/"codex" substring in a directory path cannot false-match. Exported * for the adversarial regression tests. */ export declare function processIdentityIsAgent(command: string, comm: string | undefined, agentBasename?: RegExp): boolean; /** * Parse `ps -axo pid=,ppid=,lstart=,command=` output into a pid→entry map. * `lstart` is a fixed-width human date (the process start clock time) — stable * for the life of the process, so it serves as the reuse-guard token. Pure. */ export declare function parseProcessTable(psStdout: string): Map; /** * Walk the ancestry of `selfPid` (the relay stdio server) toward init and * return the first ancestor whose argv matches the agent binary — i.e. the * agent's own CLI process. Excludes any process whose argv matches * `selfExcludePattern` (the relay entrypoint) so we never bind to ourselves. * Returns null when no agent ancestor is found (caller → age-based fallback). */ export declare function findAgentProcess(selfPid: number, table: Map, agentPattern?: RegExp, selfExcludePattern?: RegExp): AgentProcess | null; /** * Identify the agent process that spawned THIS relay stdio server. One `ps` * at startup (zero-token, no loop). Returns null if no agent ancestor matched * → the agent simply gets no positive liveness signal (age-based fallback). */ export declare function detectAgentProcess(selfPid?: number, run?: CommandRunner, agentPattern?: RegExp): AgentProcess | null; /** Read a live PID's start-time token (reuse guard). Null if unreadable. */ export declare function processStartedAt(pid: number, run?: CommandRunner): string | null; /** * Is the recorded agent process still the SAME live process? Alive iff the PID * is live AND (when both start-times are readable) they match — a reused PID * (new process, different start-time) reads dead. * * EXPLICIT TRADEOFF (intentional): if the current start-time can't be read * (`ps` restricted/failed, or no token was recorded), we fall back to * PID-liveness ALONE. This deliberately errs toward NOT falsely closing a live * agent at the cost of a weaker reuse guard. The exposure is narrow: a reused * PID only false-reads-alive when (a) the original agent's row wasn't cleared * on close — close clears the anchor (HIGH #1) — AND (b) the OS recycled that * exact PID within the ~120s alive window AND (c) ps can't read the new * process's start-time. We accept this for the relay-side foundation; the * cross-host heartbeat follow-on removes the PID dependency entirely. */ export declare function isAgentProcessAlive(pid: number, expectedStartedAt: string | null | undefined, run?: CommandRunner, kill?: KillProbe): boolean; export type AnchorVerdict = "alive" | "dead" | "unverifiable"; /** * ADR-0012 (Fork B) — ANCHOR-ONLY liveness verdict. The SHARED canonical rule for * the dead-anchor diagnostic (hooks/check-relay.sh) AND the `relay release-binding` * gate: exactly the brief's "stored agent PID/start anchor + host_id, probed * same-host". The bash `relay_anchor_liveness` mirrors this 1:1 (pinned by the * conformance test) so the diagnostic and the remedy can never disagree. * * DELIBERATELY NOT `computeLivenessVerdict`: that OR's in an argv scan * (`agentProcessAdvertised`) which reads a resummon'd argv-advertised agent (e.g. * a codex process whose argv carries RELAY_AGENT_NAME) "alive" DESPITE a dead * anchor — masking the stale binding and deadlocking the remedy (the bash * anchor-only probe would say dead, computeLivenessVerdict would say alive). That * argv fallback is correct for the dashboard PRESENCE surface, wrong for a * wakeability/eligibility decision. This function reuses the SAME narrow-dead * primitive (`isAgentProcessAlive`) — no new rule — minus the argv fallback. * * - cross-host / no own-host GUID → "unverifiable" (never guess across the * federation boundary); * - no probe-able anchor (agent_pid absent / non-positive) → "unverifiable"; * - same-host + anchor → `isAgentProcessAlive` (pid gone or start-time MISMATCH * → dead; pid live + start matched/unreadable → alive). */ export declare function anchorLivenessVerdict(row: { host_id?: string | null; agent_pid?: number | null; agent_pid_start?: string | null; }, ownHostId?: string | null, run?: CommandRunner, kill?: KillProbe): AnchorVerdict; /** * Last-resort ALIVE probe: does a live process on THIS host advertise * `RELAY_AGENT_NAME=""` in its argv? (The launch path that fixed Codex * cold-start puts the name in the command line.) Implemented as a LITERAL * both-side-anchored substring search of `ps` command lines — NOT a `pgrep -f` * regex — so: * - the surrounding quotes anchor both sides: agent "foo" cannot match a live * "foobar" / "foo-x" process (the needle `RELAY_AGENT_NAME="foo"` is not a * substring of `RELAY_AGENT_NAME="foobar"`); * - a literal `includes` has ZERO regex/shell-injection surface (a name with a * `.` or other metachar can't widen the match). Names are allowlisted on * register; we re-validate here belt-and-suspenders and bail on a bad name. * Matches ARGV only (ps `command=`), never the environment, so this is exactly * the argv-advertised case. Cost is one `ps` — the caller gates it behind the * liveness probe cache and only reaches it when the pid probes miss. */ export declare function agentProcessAdvertised(name: string, run?: CommandRunner): boolean; //# sourceMappingURL=liveness.d.ts.map