UNPKG

@essentialai/cogent-bridge

Version:

Cogent Bridge — cross-agent comms for Claude Code, OpenAI Codex and Slack. Codex users: install with `curl -fsSL https://cogent.tools/install.sh | sh`, then start with `cogent-codex` for real-time peer wake.

213 lines • 11.5 kB
import { type BridgeEntry, type OwnershipVerdict } from "./wake-ownership.js"; /** The states an agent can be in with respect to its channel. */ export type PresenceState = "local" | "deferred" | "offline" | "online" | "standby"; /** * What the bridge holding the name is to this one, from the two agent-session ids (Claude Code * passes one, codex and gemini pass none, and nothing before 3.25.7 recorded it): * same-session both known, equal → this agent's own other bridge * no-session neither side has one → this agent's previous bridge, or another agent (codex, gemini) * other-session both known, differ → another agent, proven * unknown only one side has one → this agent's own older bridge, or another agent * 🔴 An ABSENT id is never "different" (review r5 SEC-2): the plugin 3.25.6 + `npx @latest` pair of * ONE Claude session was told another agent session held its name. */ export type HolderRelation = "same-session" | "no-session" | "other-session" | "unknown"; export declare function holderRelation(holderSession: string | undefined, ownSession: string | undefined): HolderRelation; /** The live bridge that already serves this peer (from its ownership announcement). */ export interface ServingBridge { pid: number; version?: string; startedAt?: number; /** The agent session it announced (3.25.7+, Claude Code); absent = cannot be compared. */ sessionId?: string; } /** One process as `ps` reports it (structural subset of stale-bridge-reaper's ProcInfo). */ export interface PsProc { pid: number; ppid: number; argv: string; } /** * Is the MCP client of an announced bridge GONE? Then the bridge serves no agent — it is an orphan * left draining or (pre-3.25.7) running forever, like marvin's script-spawned bridges — and must not * keep a restarted agent out of its own channel. * * - 3.25.7+ announcements record `parentPid`: gone when the bridge's CURRENT parent differs from it * (an orphan is reparented; the kernel never hands a live child to another parent otherwise). * - older announcements carry none: gone when the parent is pid 1 or an init-like process. * 🔴 Not "ppid === 1" alone: a Linux desktop reparents orphans to its `systemd --user` * subreaper (measured on this dev box, 2026-10-13: an orphaned `sleep` got ppid 1615 = systemd). * 🔴 And not the immediate parent alone (review r3 R3-5): an npx-launched bridge keeps its `sh` * and `npm exec` wrappers when the client dies — the one reparented is npm. Walk up through them. * * Unknown (the holder is not in the process list, ps failed) ⇒ NOT gone: no proof ⇒ it still counts. * A holder that announced it started WITHOUT a client (headless) cannot lose one ⇒ never gone. */ export declare function holderClientGone(holder: { pid: number; parentPid?: number; noClient?: boolean; }, procs: PsProc[]): boolean; /** * Was this bridge started inside a `codex exec resume` wake turn? codex (unlike claude's * --strict-mcp-config, measured: no bridge starts in a claude wake turn) starts the configured MCP * servers for the resumed turn, so each codex wake spawns a bridge in the same checkout. It serves * no agent: the bridge that is waking it does. It must never register as the peer (review r3 R3-1) * and must not be reported as an agent locked out of its channel (review r2 NEW-4). */ export declare function insideCodexWakeTurn(selfPid: number, procs: PsProc[]): boolean; /** * The OTHER live bridges registered in this folder — what an explicit cogent_register_peer tells * its caller about (otherBridgeNotice). Not counted: this process, a bridge that announced this same * agent session (the agent's own second bridge), a draining bridge and one whose MCP client is gone * (they serve no agent), and the process marks. */ export declare function otherBridgesInFolder(entries: BridgeEntry[], self: { pid: number; sessionId?: string; }, /** Called at most once, and only when a candidate is left to check (one `ps`). */ listProcs: () => PsProc[]): BridgeEntry[]; /** * COG-85: how long a closed agent's old connection can still hold its name. MEASURED 2026-10-15 (codex * 0.162.1, Linux and macOS, probe MCP server): codex's app-server keeps a quit thread's MCP server * alive for 60.0 s, then SIGTERMs it. Plain `codex` uses that daemon too. Gemini and `codex --no-daemon` * end it at once. A name still held after this is held by another live connection. */ export declare const HOLDER_LINGER_SECONDS = 60; /** * The live bridge in this folder that holds `peerId`, from otherBridgesInFolder (so never this * process, this agent session's own bridge, a draining one, or one whose client is gone). */ export declare function heldInFolder(others: BridgeEntry[], peerId: string, ownSession?: string): BridgeEntry | undefined; /** * A bridge older than 3.25.7 records no agent session at all. Beside a bridge that knows its Claude * session it may be that session's OWN older bridge — the plugin's bridge next to an `npx @latest` one * (r5 SEC-2) — which lives as long as the session, so "wait 60 s" would never free the name (side-agent * review 2026-10-15). Not refused, as before COG-85. Only a plain x.y.z version below 3.25.7 qualifies: * an unparseable one is treated as current. */ export declare function mayBeOwnPreSessionBridge(holder: BridgeEntry, ownSession: string | undefined): boolean; /** * What an agent locked out of a held name is told to do (COG-85, operator 2026-10-15: retry in 60+ s). * 🔴 COG-80: "wait 60 s" is only true when the holder may be this agent's OWN previous connection. When * it is provably ANOTHER agent's live bridge (another session, or another platform), waiting frees * nothing — KLAIRE 2026-10-16: Codex was told to wait while Claude's live bridge held its name. */ export declare function heldNameAdvice(peerId: string, holder?: { sessionId?: string; platform?: string; }, own?: { sessionId?: string; platform?: string; }): string; /** True when one of them is PROVEN to be another agent session (both ids recorded, different). */ export declare function anotherAgentSessionAmong(others: BridgeEntry[], ownSession: string | undefined): boolean; /** * What the agent that just registered `peerId` is told about them, in plain words (operator, * 2026-10-13: "notify"). The registration is NOT refused: a lingering old window must not lock an * agent out of its own name. `verdict` is checkWakeOwnership AFTER this bridge announced: the * newest PROCESS wakes for a name, which is not always the one that registered last. * * 🔴 "Another agent session" is said only when both session ids are recorded and differ. A bridge * that recorded none is usually this agent's OWN — a restarted codex agent's previous thread, the * plugin 3.25.6 bridge beside `npx @latest` — and was told two agents shared its folder (r5 SEC-2). */ export declare function otherBridgeNotice(others: BridgeEntry[], peerId: string, verdict: OwnershipVerdict, ownSession?: string): string; /** What a presence evaluation concluded, and what it did about it. */ export type PresenceOutcome = { state: "local" | "deferred" | "online"; restored: false; detail: string; } | { state: "online"; restored: true; peerId: string; detail: string; } | { state: "standby"; restored: false; peerId: string; servedBy: ServingBridge; /** What servedBy is to this bridge, as far as the two agent-session ids can say. */ holder: HolderRelation; /** True when the standby is re-checked and restores by itself (rule b); false = final (rule c). */ recheck: boolean; detail: string; } | { state: "offline"; restored: false; detail: string; }; export interface PresenceDeps { /** True only when the ACTIVE backend really is the cloud one — not a configuration flag. */ isCloudConfigured: () => boolean; /** True when something already registered in THIS process; its state is authoritative. */ alreadyRegisteredInProcess: () => boolean; /** * Is a LIVE bridge already announcing this peer for this working directory? * * 🔴 TWO BRIDGES, ONE NAME. Credentials are one file per working directory, and every bridge * in the checkout reads it — one Claude Code session can run two (plugin + npx), and two agents * can share a checkout. Without this check the second bridge * re-registers the first one's peerId and supersession stands one of them down. Proven by * controlled A/B: with the restore unguarded, wake-dedup-matrix-test --topology two-peer could no * longer detect its own break (STAYED_GREEN, 0 overlapping resumes); with presence off it went * RED with 1. The gate was telling the truth. * * Returns that bridge, or null when nobody else serves the peer. */ peerAlreadyServed: (peerId: string) => ServingBridge | null; /** The agent session this bridge serves (wake-ownership agentSessionId); undefined = not known. */ agentSessionId?: () => string | undefined; /** True when this bridge runs inside a `codex exec resume` wake turn (insideCodexWakeTurn). */ startedByWakeTurn?: () => boolean; loadCredentials: () => Promise<{ endpoint?: string; sessionId?: string; token?: string; peerId?: string; /** AUD-003 ownership proof, issued once when this peer was created. */ peerSecret?: string; } | null>; /** * COG-80: why loadCredentials returned nothing although the folder has saved names (they belong to * other agents, or several candidates and none is provably this agent's). undefined = no such case. */ restoreSkip?: () => string | undefined; /** Go cloud-live and register, restoring the channel entry. Throws on failure. */ goOnline: (peer: { peerId: string; endpoint: string; sessionId: string; token: string; cwd: string; label: string; /** Replayed on register so a peer owned by an EARLIER token can be reclaimed. */ peerSecret?: string; }) => Promise<void>; cwd: () => string; /** How often a re-checked standby looks again, for the message only (startup.ts owns the timer). */ recheckSeconds?: number; env?: NodeJS.ProcessEnv; } /** * Automatic presence restore. ON by default — a default-off self-heal heals nobody. * Opt out with COGENT_AUTO_PRESENCE=false to observe the state without acting on it. */ export declare function autoPresenceEnabled(env?: NodeJS.ProcessEnv): boolean; /** * Determine this agent's presence and, when it is `offline` with a peer it owns, * bring it back `online`. * * NEVER THROWS. Runs fire-and-forget after the MCP transport connects — the * `initialize` handshake must never wait on I/O (scripts/startup-nonblocking-test.mjs). */ export declare function restorePresence(deps: PresenceDeps): Promise<PresenceOutcome>; /** Record the evaluated presence. Called by startup after each evaluation. */ export declare function setPresence(outcome: PresenceOutcome): void; /** The agent's own view of its channel presence. Null until first evaluated. */ export declare function getPresence(): PresenceOutcome | null; /** Report the outcome once, at a level that matches how much it matters. */ export declare function logPresence(outcome: PresenceOutcome): void; //# sourceMappingURL=peer-presence.d.ts.map