@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.
139 lines • 7.38 kB
JavaScript
/**
* CROSS-PROCESS WAKE EXCLUSION — one wake per message, however many bridges are listening.
*
* ── THE DEFECT (peer marvin-coder, 2026-09-05) ──────────────────────────────────────────
* Four commits with identical titles on main, plus another session's note: "the duplicate-work
* prediction came true — five identical commits". Mechanism, read out of the relay source rather
* than inferred: `ConnectionManager` holds `sessionId -> peerId -> PeerConnection[]` — an ARRAY —
* and `sendToPeer` iterates it, sending to EVERY connection. That array is deliberate (a reconnect
* keeps the old socket briefly), but it means N bridge processes registered as the SAME peer each
* receive the SAME message. Each consults its own in-memory queue — empty in each, because the
* queue is per-process — and each runs `claude --resume <the same threadId>` in the same cwd.
* N copies, same work, same files.
*
* The existing wake-inflight marker cannot help: rail B only WRITES it (auto-relay.ts:931) as a
* signal to rail C, and never reads it back to decide whether to wake.
*
* ── WHY A FILE, AND WHY `wx` ─────────────────────────────────────────────────────────────
* The competing writers are separate OS processes, so the exclusion must live outside all of them.
* `writeFileSync(..., { flag: "wx" })` is an atomic exclusive create: exactly one caller can win,
* with no read-modify-write window. That is the same primitive peer-registry already uses for
* local locks, including its stale-holder detection via `process.kill(pid, 0)`.
*
* Homedir-keyed, never COGENT_STATE_PATH: the competing processes may be launched with different
* env (the documented trap for COGENT_CHECK_ON_STOP), and an exclusion the other process cannot
* see is not an exclusion.
*
* ── PER MESSAGE, NOT PER CWD ─────────────────────────────────────────────────────────────
* A per-cwd lock would prevent more collisions, and would also LOSE messages: both processes
* receive every message, so the loser of a cwd-wide lock would silently drop a message it was the
* only one free to handle. Per-message dedups the actual defect (the same message delivered N
* times) while letting two processes legitimately split two different messages.
*
* ── FAILS OPEN, DELIBERATELY ─────────────────────────────────────────────────────────────
* If a claim cannot be written or read, we WAKE. Duplicated work is visible and recoverable; a
* silently dropped message is neither, and silence is the worst failure mode in this codebase.
* This trades the fix's guarantee for the product's guarantee, on purpose.
*/
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { getConfig } from "../config.js";
import { logger } from "../logger.js";
/** Hard ceiling on how long one claim may suppress other processes. Mirrors INFLIGHT_MAX_MS. */
export const CLAIM_MAX_MS = 1_800_000; // 30 min
/** Multiplier over COGENT_TIMEOUT_MS covering primary + rotated-session retry, as wake-inflight does. */
const CLAIM_TIMEOUT_FACTOR = 3;
/** MUST match wake-inflight.ts hashCwd / credential-store — one cwd, one key, everywhere. */
function hashCwd(cwd) {
return crypto.createHash("sha256").update(path.resolve(cwd)).digest("hex").slice(0, 16);
}
const safe = (s) => String(s).replace(/[^A-Za-z0-9._-]/g, "_");
/** `~/.cogent/wake-claims/<sha256(cwd)[..16]>.<peerId>.<messageId>.json` */
export function wakeClaimPath(peerId, messageId, cwd = process.cwd()) {
return path.join(os.homedir(), ".cogent", "wake-claims", `${hashCwd(cwd)}.${safe(peerId)}.${safe(messageId)}.json`);
}
function ttlMs() {
let timeout = 300_000;
try {
const c = getConfig()?.COGENT_TIMEOUT_MS;
if (typeof c === "number" && Number.isFinite(c) && c > 0)
timeout = c;
}
catch {
/* config not loaded — documented default */
}
return Math.min(timeout * CLAIM_TIMEOUT_FACTOR, CLAIM_MAX_MS);
}
/** Is the recorded holder still a live process? Unknown/odd values count as DEAD (steal). */
function holderAlive(pid) {
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0)
return false;
if (pid === process.pid)
return true;
try {
process.kill(pid, 0); // signal 0 = existence check only
return true;
}
catch (err) {
// EPERM means it EXISTS but belongs to another user — alive for our purposes.
return err?.code === "EPERM";
}
}
/**
* Try to become the single process that wakes for this message.
*
* Returns true when this process owns the wake (or when claiming was impossible — see FAILS OPEN).
* Returns false ONLY when another LIVE, UNEXPIRED process demonstrably holds it.
*/
export function claimWake(peerId, messageId, cwd = process.cwd()) {
const p = wakeClaimPath(peerId, messageId, cwd);
const now = Date.now();
const body = () => JSON.stringify({ pid: process.pid, startedAt: now, expiresAt: now + ttlMs() });
try {
fs.mkdirSync(path.dirname(p), { recursive: true });
try {
fs.writeFileSync(p, body(), { flag: "wx" }); // atomic: exactly one winner
return true;
}
catch (err) {
if (err?.code !== "EEXIST")
throw err;
}
// Someone holds it. Take it over ONLY if they are gone or their claim has expired — a claim
// that outlives its holder would mute this peer for good, which is worse than duplicating.
let held = null;
try {
held = JSON.parse(fs.readFileSync(p, "utf-8"));
}
catch {
held = null; // corrupt → treat as abandoned
}
const expired = !held || typeof held.expiresAt !== "number" || held.expiresAt <= now;
if (!held || expired || !holderAlive(held.pid)) {
fs.writeFileSync(p, body()); // steal
logger.debug(`wake-claim: took over an abandoned claim for ${messageId}`);
return true;
}
logger.info(`Auto-relay: SKIPPING wake for message ${messageId} — pid ${held.pid} in this cwd already has it. ` +
`Another bridge process is registered as "${peerId}"; without this both would resume the same ` +
`session and duplicate the work.`);
return false;
}
catch (err) {
// FAIL OPEN. Never lose a message to a broken lock.
logger.warn(`wake-claim: could not claim ${messageId} (${err.message}) — waking anyway`);
return true;
}
}
/** Release the claim. Best-effort; an orphan expires or is stolen when its holder dies. */
export function releaseWake(peerId, messageId, cwd = process.cwd()) {
try {
fs.unlinkSync(wakeClaimPath(peerId, messageId, cwd));
}
catch {
/* already gone, or never ours */
}
}
//# sourceMappingURL=wake-claim.js.map