@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.
121 lines • 6.69 kB
TypeScript
/**
* The AGENT SESSION this bridge serves, when its MCP client says which one — else undefined.
*
* MEASURED 2026-10-13 (real CLIs, a probe MCP server): Claude Code 2.1.296 sets
* CLAUDE_CODE_SESSION_ID for every stdio MCP server it spawns (its changelog: since 2.1.154). Codex
* 0.162.0 and gemini 0.58.0 pass none. It is fixed at spawn (stale after `/clear`), so it is used
* ONLY to tell "my own other bridge" from "another agent session" in messages — never to choose
* credentials. COGENT_PLATFORM is checked because a harness-launched codex bridge can inherit the
* variable from a Claude Code shell (real codex strips the environment).
*/
export declare function agentSessionId(env?: NodeJS.ProcessEnv): string | undefined;
export interface BridgeEntry {
pid: number;
startedAt: number;
version: string;
peerId: string;
/** The CONFIGURED COGENT_ENDPOINT — not necessarily the relay served (an Org_ID join goes to Team). */
endpoint: string;
cwd: string;
/** This bridge's parent (its MCP client) when it announced. Absent before 3.25.7. */
parentPid?: number;
/** The agent session it serves (agentSessionId). Absent before 3.25.7 and for clients that pass none. */
sessionId?: string;
/** COGENT_PLATFORM of the agent it serves ("cc", "codex"). Absent before 3.25.9 (COG-80). */
platform?: string;
/** Its MCP client is gone and it is only finishing work in flight: it serves no agent (markBridgeDraining). */
draining?: boolean;
/** Started without an MCP client (startedWithoutClient): `parentPid` is a launcher, not a client. */
noClient?: boolean;
}
export declare function startedWithoutClient(): boolean;
/** `~/.cogent/bridges/<sha256(cwd)[..16]>.<peerId>/` — one directory per identity. */
export declare function bridgeDirFor(cwd: string, peerId: string): string;
/**
* The pseudo-peer every bridge is recorded under FROM PROCESS START, registered or not.
*
* 🔴 WHY. The reaper spares announced pids, and a bridge used to announce only once it registered —
* so a current bridge that was still waiting to register (a fresh session, a standby) looked like a
* pre-3.23.4 leftover and was SIGTERMed (marvin, 2026-10-13; review r4 problem 5). This mark makes a
* 3.25.7+ bridge recognisable whatever its argv and whether or not it ever registers.
* It is an ordinary registry directory on purpose: reapers of 3.23.6–3.25.6 list every
* `<cwdhash>.<name>` directory, so they spare a marked bridge too. Ownership and presence read one
* peer's directory only and never see it.
*/
export declare const PROCESS_MARK = ".process";
/**
* Does this peer name land in the process marks' directory? Compared as the FILESYSTEM compares it:
* after safe(), case folded — the default macOS volume is case-insensitive, and `.PROCESS` passed an
* exact string compare while sharing the directory.
*/
export declare function isReservedPeerName(peerId: string): boolean;
/**
* A value READ FROM A REGISTRY FILE, made fit to quote in a tool result or a log line. Any local
* process of this user can write those files, and what the bridge quotes from them is read by the
* agent (review r7 SEC2-4: a planted `sessionId` put a sentence into the register result). Keeps only
* what a peer id, a session id or a version is made of — no spaces, quotes or line breaks — and at
* most 64 characters.
*/
export declare function registryText(value: unknown): string;
/**
* The newer owner as named in a log line, built from its registry entry. The version goes through
* registryText(); the endpoint is a URL, so it is JSON-quoted (line breaks and quotes escaped) and capped
* rather than stripped. Review r8 R8-NEW-1: a planted newline in either field forged log lines.
*/
export declare function registryOwnerLabel(o: {
pid: unknown;
version: unknown;
endpoint: unknown;
}): string;
/**
* Announce this process as a bridge serving `peerId` in `cwd`.
*
* Called once the peer is actually registered, so a bridge that never registered never competes
* for ownership. Best-effort: a failure here leaves us un-announced, which can only make us LOSE
* ownership to someone else, never wrongly take it.
*/
export declare function announceBridge(cwd: string, peerId: string, version: string, endpoint: string, sessionId?: string | undefined): void;
/**
* This bridge's MCP client is gone and it is draining: say so in its OWN announcement.
*
* 🔴 NOT a withdrawal (review r3 R3-2). Round 3 removed the announcement here so the restarted
* agent's bridge would not stand by behind it — but an unannounced bridge is what the reaper
* terminates when it cannot read its version (a global install, a checkout, a fresh npx), and the
* restarted bridge killed this one 326 ms into a 12 s wake. Marked `draining` it stays announced
* (the reaper spares it, and it stands down for the newer bridge on its own) while presence ignores
* it (it serves no agent). Best-effort like the announce itself.
*/
export declare function markBridgeDraining(cwd: string, peerId: string): void;
/** Record this process as a running bridge for `cwd` — at start, before any registration. */
export declare function markBridgeProcess(cwd: string, version: string): void;
/** Remove this process's announcement. Called on graceful shutdown; a crash is handled by pruning. */
export declare function withdrawBridge(cwd: string, peerId: string): void;
/**
* Every LIVE bridge announced for this identity, newest process first.
*
* Prunes entries whose process is gone, so a machine that has been through a dozen plugin updates
* does not accumulate junk here forever.
*/
export declare function listLiveBridges(cwd: string, peerId: string): BridgeEntry[];
/** Every live bridge recorded for this folder under ANY name, the process marks included. */
export declare function listLiveBridgesInFolder(cwd: string): BridgeEntry[];
export interface OwnershipVerdict {
/** True when this process should do this peer's work. Every uncertain path returns true. */
isOwner: boolean;
/** The bridge that owns it instead, when we do not. */
owner?: {
pid: number;
version: string;
endpoint: string;
};
/** How many live bridges serve this identity, including us. */
liveCount: number;
}
/**
* Should THIS process wake for `peerId` in `cwd`?
*
* No when a strictly newer live bridge serves the same identity — that bridge is the one the user
* most recently started, and we are the leftover.
*/
export declare function checkWakeOwnership(cwd: string, peerId: string): OwnershipVerdict;
//# sourceMappingURL=wake-ownership.d.ts.map