@essentialai/cogent-bridge 3.23.2 → 3.23.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +82 -2
- package/dist/services/auto-relay.d.ts +14 -0
- package/dist/services/auto-relay.d.ts.map +1 -1
- package/dist/services/auto-relay.js +113 -1
- package/dist/services/auto-relay.js.map +1 -1
- package/dist/services/session-wake-lock.d.ts +26 -0
- package/dist/services/session-wake-lock.d.ts.map +1 -0
- package/dist/services/session-wake-lock.js +198 -0
- package/dist/services/session-wake-lock.js.map +1 -0
- package/dist/services/wake-claim.d.ts +14 -0
- package/dist/services/wake-claim.d.ts.map +1 -0
- package/dist/services/wake-claim.js +139 -0
- package/dist/services/wake-claim.js.map +1 -0
- package/dist/services/wake-lock.d.ts +30 -0
- package/dist/services/wake-lock.d.ts.map +1 -0
- package/dist/services/wake-lock.js +257 -0
- package/dist/services/wake-lock.js.map +1 -0
- package/dist/services/wake-ownership.d.ts +48 -0
- package/dist/services/wake-ownership.d.ts.map +1 -0
- package/dist/services/wake-ownership.js +193 -0
- package/dist/services/wake-ownership.js.map +1 -0
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CROSS-PROCESS WAKE LOCK — never two agent turns running at once in ONE CHECKOUT.
|
|
3
|
+
*
|
|
4
|
+
* ── THE DEFECT THIS FIXES, AND HOW IT DIFFERS FROM wake-claim.ts ─────────────────────────
|
|
5
|
+
* `wake-claim.ts` excludes on the MESSAGE id: it stops one message, fanned by the relay to N
|
|
6
|
+
* sockets of the same peer, from being answered N times. That is a real defect and it is fixed.
|
|
7
|
+
*
|
|
8
|
+
* It cannot help here. Measured on a real user (peer `marvin-coder`, 2026-09-05): the SAME peer
|
|
9
|
+
* was registered on the FREE relay at plugin v3.20.0 AND on the TEAM relay at v3.23.1 — same cwd,
|
|
10
|
+
* same thread id, two live bridge processes. Reachable because credentials are keyed on cwd ALONE
|
|
11
|
+
* (`~/.cogent/credentials/<sha256(cwd)>.json`, credential-store.ts:64-71); the endpoint is not part
|
|
12
|
+
* of the key, so a second join overwrites the file while the first process keeps running from its
|
|
13
|
+
* in-memory token and live WebSocket. Nothing on disk then records that the first relay is in play,
|
|
14
|
+
* which is why nothing reaps it.
|
|
15
|
+
*
|
|
16
|
+
* Two relays mint two DIFFERENT message ids, so both per-message claims succeed and both processes
|
|
17
|
+
* resume the SAME session concurrently. `scripts/wake-dedup-matrix-test.mjs --topology two-relay`
|
|
18
|
+
* reproduces it offline against two local relays: two wakes, one session id, 4001ms of overlap.
|
|
19
|
+
*
|
|
20
|
+
* ── WHY THE KEY IS THE CWD, NOT THE SESSION ──────────────────────────────────────────────
|
|
21
|
+
* The first cut of this keyed on (cwd, sessionId). That is too narrow. `pinnedSessionId` is
|
|
22
|
+
* per-process in-memory state (auto-relay.ts:338) and `decideResolution` returns whatever pin it
|
|
23
|
+
* was handed whenever that pin still validates, so two bridge processes in one checkout can hold
|
|
24
|
+
* DIFFERENT pins — they would take different locks and overlap anyway.
|
|
25
|
+
*
|
|
26
|
+
* More importantly the damage is a property of the WORKTREE, not of the session: two `claude`
|
|
27
|
+
* processes editing one checkout corrupt it whether or not they resume the same session. So the
|
|
28
|
+
* key is the cwd. The session id is recorded in the lease for diagnostics only.
|
|
29
|
+
*
|
|
30
|
+
* `wake-claim.ts` explicitly rejected a per-cwd lock — "a per-cwd lock would ... LOSE messages,
|
|
31
|
+
* the loser would silently drop a message it was the only one free to handle". That objection is
|
|
32
|
+
* about DROP semantics and does not apply here: this lock WAITS. Same key, opposite conclusion.
|
|
33
|
+
*
|
|
34
|
+
* Blast radius is deliberately small: with a single bridge process the lock is never contended, so
|
|
35
|
+
* nothing about normal operation changes. Contention only arises in the topology that IS the bug.
|
|
36
|
+
*
|
|
37
|
+
* ── WHY IT WAITS INSTEAD OF DROPPING ─────────────────────────────────────────────────────
|
|
38
|
+
* Two relays carry two genuinely distinct questions; answering both is CORRECT. What is never
|
|
39
|
+
* correct is running both turns at once against one worktree. So a loser waits for the holder and
|
|
40
|
+
* then proceeds, which serialises the turns without losing a message. Dropping would trade a
|
|
41
|
+
* corruption bug for a silence bug, and silence is the worse failure mode in this codebase.
|
|
42
|
+
*
|
|
43
|
+
* ── FAILS OPEN, DELIBERATELY ─────────────────────────────────────────────────────────────
|
|
44
|
+
* Every uncertain path — unreadable lock dir, corrupt holder record, a holder that outlives the
|
|
45
|
+
* wait budget — resolves to "go ahead and wake". A lock that can mute an agent for good is worse
|
|
46
|
+
* than the overlap it prevents. That is the 3.21.5 lesson: a fix whose blast radius is "auto-wake
|
|
47
|
+
* stops working" is not a safe fix, however correct it looks.
|
|
48
|
+
*/
|
|
49
|
+
import crypto from "node:crypto";
|
|
50
|
+
import fs from "node:fs";
|
|
51
|
+
import os from "node:os";
|
|
52
|
+
import path from "node:path";
|
|
53
|
+
import { getConfig } from "../config.js";
|
|
54
|
+
import { logger } from "../logger.js";
|
|
55
|
+
/** Hard ceiling on how long one lease may exclude others. Mirrors wake-claim's CLAIM_MAX_MS. */
|
|
56
|
+
export const WAKE_LOCK_MAX_MS = 1_800_000; // 30 min
|
|
57
|
+
/** Multiplier over COGENT_TIMEOUT_MS covering primary + rotated-session retry, as wake-claim does. */
|
|
58
|
+
const LEASE_TIMEOUT_FACTOR = 3;
|
|
59
|
+
/** How often a waiter re-checks. Short enough to be invisible, long enough not to spin. */
|
|
60
|
+
const POLL_MS = 250;
|
|
61
|
+
/** MUST match wake-claim / wake-inflight / credential-store — one cwd, one key, everywhere. */
|
|
62
|
+
function hashCwd(cwd) {
|
|
63
|
+
return crypto.createHash("sha256").update(path.resolve(cwd)).digest("hex").slice(0, 16);
|
|
64
|
+
}
|
|
65
|
+
/** `~/.cogent/wake-locks/<sha256(cwd)[..16]>.json` — ONE checkout, ONE turn. */
|
|
66
|
+
export function wakeLockPath(cwd = process.cwd()) {
|
|
67
|
+
return path.join(os.homedir(), ".cogent", "wake-locks", `${hashCwd(cwd)}.json`);
|
|
68
|
+
}
|
|
69
|
+
function leaseTtlMs() {
|
|
70
|
+
let timeout = 300_000;
|
|
71
|
+
try {
|
|
72
|
+
const c = getConfig()?.COGENT_TIMEOUT_MS;
|
|
73
|
+
if (typeof c === "number" && Number.isFinite(c) && c > 0)
|
|
74
|
+
timeout = c;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
/* config not loaded — documented default */
|
|
78
|
+
}
|
|
79
|
+
return Math.min(timeout * LEASE_TIMEOUT_FACTOR, WAKE_LOCK_MAX_MS);
|
|
80
|
+
}
|
|
81
|
+
/** Default budget for waiting out another process's turn: one full turn. */
|
|
82
|
+
function defaultMaxWaitMs() {
|
|
83
|
+
try {
|
|
84
|
+
const c = getConfig()?.COGENT_TIMEOUT_MS;
|
|
85
|
+
if (typeof c === "number" && Number.isFinite(c) && c > 0)
|
|
86
|
+
return c;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
/* config not loaded */
|
|
90
|
+
}
|
|
91
|
+
return 300_000;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* How many nested acquisitions this process currently holds.
|
|
95
|
+
*
|
|
96
|
+
* 🔴 WHY RE-ENTRANCY IS NOT OPTIONAL. `_injectOnly` is called from handleIncoming (auto-relay.ts:811),
|
|
97
|
+
* OUTSIDE the `processing` lock, so it can run while a wake in THIS SAME process holds the lease.
|
|
98
|
+
* Without this counter the inner acquire would treat the self-pid record as stale, steal it, and
|
|
99
|
+
* then delete it on release — leaving the outer wake running with no lease and letting a second
|
|
100
|
+
* process in. That is the hole this lock exists to close, reopened one level down.
|
|
101
|
+
*/
|
|
102
|
+
let heldDepth = 0;
|
|
103
|
+
/**
|
|
104
|
+
* Is the recorded holder still a live process?
|
|
105
|
+
*
|
|
106
|
+
* A record bearing OUR pid counts as dead ONLY when heldDepth is 0 — i.e. we are not actually
|
|
107
|
+
* holding it, so the file is a leak from an earlier turn. While we do hold it, re-entrancy is
|
|
108
|
+
* handled before this is ever consulted.
|
|
109
|
+
*/
|
|
110
|
+
function holderAlive(pid) {
|
|
111
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0)
|
|
112
|
+
return false;
|
|
113
|
+
if (pid === process.pid)
|
|
114
|
+
return heldDepth > 0;
|
|
115
|
+
try {
|
|
116
|
+
process.kill(pid, 0); // signal 0 = existence check only
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
// EPERM means it EXISTS but belongs to another user — alive for our purposes.
|
|
121
|
+
return err?.code === "EPERM";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/** Read the current holder, or null when absent/corrupt (both mean "free"). */
|
|
125
|
+
function readHolder(p) {
|
|
126
|
+
try {
|
|
127
|
+
const held = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
128
|
+
if (!held || typeof held.expiresAt !== "number")
|
|
129
|
+
return null;
|
|
130
|
+
return held;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
137
|
+
/**
|
|
138
|
+
* Become the single process running an agent turn in `cwd`, waiting out any other holder.
|
|
139
|
+
*
|
|
140
|
+
* `sessionId` is recorded in the lease for diagnostics; it is NOT part of the key (see the header).
|
|
141
|
+
*
|
|
142
|
+
* Never returns `acquired: false` today — every path either takes the lease or fails open — but the
|
|
143
|
+
* field is explicit so a caller reads the intent rather than assuming it.
|
|
144
|
+
*/
|
|
145
|
+
export async function acquireWakeLock(cwd = process.cwd(), sessionId = "unknown", opts = {}) {
|
|
146
|
+
const p = wakeLockPath(cwd);
|
|
147
|
+
const maxWaitMs = opts.maxWaitMs ?? defaultMaxWaitMs();
|
|
148
|
+
const started = Date.now();
|
|
149
|
+
let contended = false;
|
|
150
|
+
// Re-entrant: this process already holds the checkout, so nothing may block and nothing may be
|
|
151
|
+
// written. Only the OUTERMOST release removes the file.
|
|
152
|
+
if (heldDepth > 0) {
|
|
153
|
+
heldDepth++;
|
|
154
|
+
let released = false;
|
|
155
|
+
return {
|
|
156
|
+
acquired: true,
|
|
157
|
+
waitedMs: 0,
|
|
158
|
+
contended: false,
|
|
159
|
+
release: () => {
|
|
160
|
+
if (released)
|
|
161
|
+
return;
|
|
162
|
+
released = true;
|
|
163
|
+
heldDepth--;
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const lease = () => {
|
|
168
|
+
heldDepth++;
|
|
169
|
+
let released = false;
|
|
170
|
+
return {
|
|
171
|
+
acquired: true,
|
|
172
|
+
waitedMs: Date.now() - started,
|
|
173
|
+
contended,
|
|
174
|
+
release: () => {
|
|
175
|
+
if (released)
|
|
176
|
+
return;
|
|
177
|
+
released = true;
|
|
178
|
+
heldDepth--;
|
|
179
|
+
if (heldDepth === 0)
|
|
180
|
+
releaseWakeLock(cwd);
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
/** Fail open: the caller wakes, and nothing is left behind that could mute the next turn. */
|
|
185
|
+
const failOpen = (why) => {
|
|
186
|
+
logger.warn(`wake-lock: ${why} — waking anyway (fail-open)`);
|
|
187
|
+
return { acquired: true, waitedMs: Date.now() - started, contended, release: () => { } };
|
|
188
|
+
};
|
|
189
|
+
try {
|
|
190
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
return failOpen(`cannot create lock dir (${err.message})`);
|
|
194
|
+
}
|
|
195
|
+
for (;;) {
|
|
196
|
+
const now = Date.now();
|
|
197
|
+
const body = JSON.stringify({
|
|
198
|
+
pid: process.pid,
|
|
199
|
+
sessionId,
|
|
200
|
+
startedAt: now,
|
|
201
|
+
expiresAt: now + leaseTtlMs(),
|
|
202
|
+
});
|
|
203
|
+
try {
|
|
204
|
+
fs.writeFileSync(p, body, { flag: "wx" }); // atomic: exactly one winner
|
|
205
|
+
if (contended) {
|
|
206
|
+
logger.info(`Auto-relay: waited ${Date.now() - started}ms for another bridge process to finish its ` +
|
|
207
|
+
`turn in this checkout before resuming — turns are serialised, not overlapping.`);
|
|
208
|
+
}
|
|
209
|
+
return lease();
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
if (err?.code !== "EEXIST") {
|
|
213
|
+
return failOpen(`cannot write lock (${err.message})`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Held. Take it over when the holder is gone, expired, or unreadable — a lease that outlives
|
|
217
|
+
// its holder would mute this agent until the writer-stamped expiry.
|
|
218
|
+
const held = readHolder(p);
|
|
219
|
+
if (!held || held.expiresAt <= now || !holderAlive(held.pid)) {
|
|
220
|
+
try {
|
|
221
|
+
fs.writeFileSync(p, body); // steal
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
return failOpen(`cannot steal abandoned lock (${err.message})`);
|
|
225
|
+
}
|
|
226
|
+
logger.debug(`wake-lock: took over an abandoned lease in ${cwd}`);
|
|
227
|
+
return lease();
|
|
228
|
+
}
|
|
229
|
+
if (!contended) {
|
|
230
|
+
contended = true;
|
|
231
|
+
logger.info(`Auto-relay: pid ${held.pid} is already running an agent turn in this checkout ` +
|
|
232
|
+
`(session ${held.sessionId}) — waiting for it rather than running a second resume over ` +
|
|
233
|
+
`the same worktree. Two bridge processes are live for this cwd; if that is unexpected, ` +
|
|
234
|
+
`check for a stale one left behind by a plugin update.`);
|
|
235
|
+
}
|
|
236
|
+
if (Date.now() - started >= maxWaitMs) {
|
|
237
|
+
// Budget spent and the holder is STILL alive. Proceeding risks the overlap this lock
|
|
238
|
+
// exists to prevent; refusing risks silence. Silence is worse — but say so loudly.
|
|
239
|
+
return failOpen(`pid ${held.pid} still holds this checkout after ${Date.now() - started}ms`);
|
|
240
|
+
}
|
|
241
|
+
await sleep(POLL_MS);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/** Test-only: reset the in-process nesting counter. Production code must never call this. */
|
|
245
|
+
export function __resetWakeLockDepthForTests() {
|
|
246
|
+
heldDepth = 0;
|
|
247
|
+
}
|
|
248
|
+
/** Release the lease. Best-effort; an orphan expires or is stolen when its holder dies. */
|
|
249
|
+
export function releaseWakeLock(cwd = process.cwd()) {
|
|
250
|
+
try {
|
|
251
|
+
fs.unlinkSync(wakeLockPath(cwd));
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
/* already gone, or never ours */
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=wake-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wake-lock.js","sourceRoot":"","sources":["../../src/services/wake-lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
interface BridgeEntry {
|
|
2
|
+
pid: number;
|
|
3
|
+
startedAt: number;
|
|
4
|
+
version: string;
|
|
5
|
+
peerId: string;
|
|
6
|
+
endpoint: string;
|
|
7
|
+
cwd: string;
|
|
8
|
+
}
|
|
9
|
+
/** `~/.cogent/bridges/<sha256(cwd)[..16]>.<peerId>/` — one directory per identity. */
|
|
10
|
+
export declare function bridgeDirFor(cwd: string, peerId: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Announce this process as a bridge serving `peerId` in `cwd`.
|
|
13
|
+
*
|
|
14
|
+
* Called once the peer is actually registered, so a bridge that never registered never competes
|
|
15
|
+
* for ownership. Best-effort: a failure here leaves us un-announced, which can only make us LOSE
|
|
16
|
+
* ownership to someone else, never wrongly take it.
|
|
17
|
+
*/
|
|
18
|
+
export declare function announceBridge(cwd: string, peerId: string, version: string, endpoint: string): void;
|
|
19
|
+
/** Remove this process's announcement. Called on graceful shutdown; a crash is handled by pruning. */
|
|
20
|
+
export declare function withdrawBridge(cwd: string, peerId: string): void;
|
|
21
|
+
/**
|
|
22
|
+
* Every LIVE bridge announced for this identity, newest process first.
|
|
23
|
+
*
|
|
24
|
+
* Prunes entries whose process is gone, so a machine that has been through a dozen plugin updates
|
|
25
|
+
* does not accumulate junk here forever.
|
|
26
|
+
*/
|
|
27
|
+
export declare function listLiveBridges(cwd: string, peerId: string): BridgeEntry[];
|
|
28
|
+
export interface OwnershipVerdict {
|
|
29
|
+
/** True when this process should do this peer's work. Every uncertain path returns true. */
|
|
30
|
+
isOwner: boolean;
|
|
31
|
+
/** The bridge that owns it instead, when we do not. */
|
|
32
|
+
owner?: {
|
|
33
|
+
pid: number;
|
|
34
|
+
version: string;
|
|
35
|
+
endpoint: string;
|
|
36
|
+
};
|
|
37
|
+
/** How many live bridges serve this identity, including us. */
|
|
38
|
+
liveCount: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Should THIS process wake for `peerId` in `cwd`?
|
|
42
|
+
*
|
|
43
|
+
* No when a strictly newer live bridge serves the same identity — that bridge is the one the user
|
|
44
|
+
* most recently started, and we are the leftover.
|
|
45
|
+
*/
|
|
46
|
+
export declare function checkWakeOwnership(cwd: string, peerId: string): OwnershipVerdict;
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=wake-ownership.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wake-ownership.d.ts","sourceRoot":"","sources":["../../src/services/wake-ownership.ts"],"names":[],"mappings":"AA0DA,UAAU,WAAW;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAQD,sFAAsF;AACtF,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEhE;AAeD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAgBnG;AAED,sGAAsG;AACtG,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAMhE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,EAAE,CAiC1E;AAED,MAAM,WAAW,gBAAgB;IAC/B,4FAA4F;IAC5F,OAAO,EAAE,OAAO,CAAC;IACjB,uDAAuD;IACvD,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,gBAAgB,CA0BhF"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WAKE OWNERSHIP — a superseded bridge process stops waking, by itself, with nobody running pkill.
|
|
3
|
+
*
|
|
4
|
+
* ── WHY (operator, 2026-09-05) ───────────────────────────────────────────────────────────
|
|
5
|
+
* "killed, but my users dont do that - so think how to have self healing mechanism"
|
|
6
|
+
*
|
|
7
|
+
* ── WHAT IS ACTUALLY ON A REAL MACHINE (measured here, 2026-09-05) ───────────────────────
|
|
8
|
+
* 37 live cogent-bridge processes, and NOT ONE of them is an orphan — every parent is alive:
|
|
9
|
+
*
|
|
10
|
+
* codex app-server (1 daemon, up 3.5 days) ......... 7 bridges, all at a stale 3.22.0
|
|
11
|
+
* claude --resume <one interactive session> ........ 2 bridges (plugin AND an npx @latest)
|
|
12
|
+
* 5 other interactive `claude --resume` sessions ... 1 each
|
|
13
|
+
*
|
|
14
|
+
* That kills the obvious fix. "Exit when stdin hits EOF" heals nothing here, because no bridge has
|
|
15
|
+
* lost its parent. They are not orphaned, they are REDUNDANT: a long-lived daemon spawns a bridge
|
|
16
|
+
* per MCP session and never reaps them, and one agent session can have cogent registered twice.
|
|
17
|
+
* A plugin update does not stop any of them, so old versions keep serving for days.
|
|
18
|
+
*
|
|
19
|
+
* ── THE SIGNAL: SAME peerId, NOT same cwd ────────────────────────────────────────────────
|
|
20
|
+
* Superseding on cwd alone would break a legitimate setup — two agents working one checkout (say
|
|
21
|
+
* `marvin-coder` and `marvin-mobile`) are two DIFFERENT peers and both must keep waking. What is
|
|
22
|
+
* never legitimate is the same peerId served by two bridges in one checkout: that is the ghost,
|
|
23
|
+
* and it is exactly what was measured on the real user (`marvin-coder` live on the FREE relay at
|
|
24
|
+
* v3.20.0 and on the TEAM relay at v3.23.1, same cwd, same thread).
|
|
25
|
+
*
|
|
26
|
+
* So ownership is keyed on (cwd, peerId). The wake LOCK (wake-lock.ts) stays keyed on cwd alone,
|
|
27
|
+
* because worktree corruption does not care which peer caused it. Two invariants, two keys:
|
|
28
|
+
* ownership → only ONE process does this peer's work (kills duplicate work)
|
|
29
|
+
* lock → only ONE turn runs in this checkout (kills concurrent corruption)
|
|
30
|
+
*
|
|
31
|
+
* ── NEWEST WINS, BY PROCESS START, SO IT CANNOT OSCILLATE ────────────────────────────────
|
|
32
|
+
* The owner is the live bridge with the latest PROCESS START time. Start time is fixed for the
|
|
33
|
+
* life of a process, so a superseded bridge can never win the title back — if ownership were keyed
|
|
34
|
+
* on registration time instead, a stood-down bridge that re-registered would steal it back and the
|
|
35
|
+
* two would ping-pong forever.
|
|
36
|
+
*
|
|
37
|
+
* ── ONE FILE PER PID, SO THERE IS NO WRITE RACE ──────────────────────────────────────────
|
|
38
|
+
* Each process writes ONLY its own `<pid>.json`; nobody rewrites a shared document, so concurrent
|
|
39
|
+
* registration cannot corrupt or lose an entry. Reading is a readdir plus a liveness check, and
|
|
40
|
+
* dead entries are pruned opportunistically by whoever notices them.
|
|
41
|
+
*
|
|
42
|
+
* ── FAILS OPEN ───────────────────────────────────────────────────────────────────────────
|
|
43
|
+
* Any unreadable/unwritable state resolves to "you are the owner, go ahead and wake". A
|
|
44
|
+
* self-healing mechanism that can silence a healthy agent is a worse bug than the one it heals —
|
|
45
|
+
* that is the 3.21.5 lesson, where a fix took auto-wake down for every default-config macOS user.
|
|
46
|
+
*/
|
|
47
|
+
import crypto from "node:crypto";
|
|
48
|
+
import fs from "node:fs";
|
|
49
|
+
import os from "node:os";
|
|
50
|
+
import path from "node:path";
|
|
51
|
+
import { logger } from "../logger.js";
|
|
52
|
+
/**
|
|
53
|
+
* When this process started. Captured at module load — as close to process start as we can get
|
|
54
|
+
* from inside — and never recomputed, which is what makes ownership stable and non-oscillating.
|
|
55
|
+
*/
|
|
56
|
+
const PROCESS_STARTED_AT = Date.now() - Math.floor(process.uptime() * 1000);
|
|
57
|
+
/** MUST match wake-lock / wake-claim / credential-store — one cwd, one key, everywhere. */
|
|
58
|
+
function hashCwd(cwd) {
|
|
59
|
+
return crypto.createHash("sha256").update(path.resolve(cwd)).digest("hex").slice(0, 16);
|
|
60
|
+
}
|
|
61
|
+
const safe = (s) => String(s).replace(/[^A-Za-z0-9._-]/g, "_");
|
|
62
|
+
/** `~/.cogent/bridges/<sha256(cwd)[..16]>.<peerId>/` — one directory per identity. */
|
|
63
|
+
export function bridgeDirFor(cwd, peerId) {
|
|
64
|
+
return path.join(os.homedir(), ".cogent", "bridges", `${hashCwd(cwd)}.${safe(peerId)}`);
|
|
65
|
+
}
|
|
66
|
+
/** Is that pid a live process? Unknown/odd values count as DEAD so their entries get pruned. */
|
|
67
|
+
function pidAlive(pid) {
|
|
68
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0)
|
|
69
|
+
return false;
|
|
70
|
+
if (pid === process.pid)
|
|
71
|
+
return true;
|
|
72
|
+
try {
|
|
73
|
+
process.kill(pid, 0); // signal 0 = existence check only
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
// EPERM means it EXISTS but belongs to another user — alive for our purposes.
|
|
78
|
+
return err?.code === "EPERM";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Announce this process as a bridge serving `peerId` in `cwd`.
|
|
83
|
+
*
|
|
84
|
+
* Called once the peer is actually registered, so a bridge that never registered never competes
|
|
85
|
+
* for ownership. Best-effort: a failure here leaves us un-announced, which can only make us LOSE
|
|
86
|
+
* ownership to someone else, never wrongly take it.
|
|
87
|
+
*/
|
|
88
|
+
export function announceBridge(cwd, peerId, version, endpoint) {
|
|
89
|
+
const dir = bridgeDirFor(cwd, peerId);
|
|
90
|
+
try {
|
|
91
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
92
|
+
const entry = {
|
|
93
|
+
pid: process.pid,
|
|
94
|
+
startedAt: PROCESS_STARTED_AT,
|
|
95
|
+
version,
|
|
96
|
+
peerId,
|
|
97
|
+
endpoint,
|
|
98
|
+
cwd: path.resolve(cwd),
|
|
99
|
+
};
|
|
100
|
+
fs.writeFileSync(path.join(dir, `${process.pid}.json`), JSON.stringify(entry));
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
logger.debug(`wake-ownership: could not announce this bridge (${err.message})`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Remove this process's announcement. Called on graceful shutdown; a crash is handled by pruning. */
|
|
107
|
+
export function withdrawBridge(cwd, peerId) {
|
|
108
|
+
try {
|
|
109
|
+
fs.unlinkSync(path.join(bridgeDirFor(cwd, peerId), `${process.pid}.json`));
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
/* already gone, or never announced */
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Every LIVE bridge announced for this identity, newest process first.
|
|
117
|
+
*
|
|
118
|
+
* Prunes entries whose process is gone, so a machine that has been through a dozen plugin updates
|
|
119
|
+
* does not accumulate junk here forever.
|
|
120
|
+
*/
|
|
121
|
+
export function listLiveBridges(cwd, peerId) {
|
|
122
|
+
const dir = bridgeDirFor(cwd, peerId);
|
|
123
|
+
let names;
|
|
124
|
+
try {
|
|
125
|
+
names = fs.readdirSync(dir);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return []; // no directory = nobody announced = no competition
|
|
129
|
+
}
|
|
130
|
+
const live = [];
|
|
131
|
+
for (const name of names) {
|
|
132
|
+
if (!name.endsWith(".json"))
|
|
133
|
+
continue;
|
|
134
|
+
const p = path.join(dir, name);
|
|
135
|
+
let entry = null;
|
|
136
|
+
try {
|
|
137
|
+
entry = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
entry = null;
|
|
141
|
+
}
|
|
142
|
+
if (!entry || !pidAlive(entry.pid) || typeof entry.startedAt !== "number") {
|
|
143
|
+
try {
|
|
144
|
+
fs.unlinkSync(p); // prune: the process is gone or the record is unusable
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
/* raced with another pruner */
|
|
148
|
+
}
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
live.push(entry);
|
|
152
|
+
}
|
|
153
|
+
// Newest process first; pid breaks a same-millisecond tie deterministically so every process
|
|
154
|
+
// computes the SAME owner. A non-deterministic tie-break would let two bridges each believe
|
|
155
|
+
// they own it — the exact condition this exists to prevent.
|
|
156
|
+
live.sort((a, b) => b.startedAt - a.startedAt || b.pid - a.pid);
|
|
157
|
+
return live;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Should THIS process wake for `peerId` in `cwd`?
|
|
161
|
+
*
|
|
162
|
+
* No when a strictly newer live bridge serves the same identity — that bridge is the one the user
|
|
163
|
+
* most recently started, and we are the leftover.
|
|
164
|
+
*/
|
|
165
|
+
export function checkWakeOwnership(cwd, peerId) {
|
|
166
|
+
let live;
|
|
167
|
+
try {
|
|
168
|
+
live = listLiveBridges(cwd, peerId);
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
logger.warn(`wake-ownership: could not read bridge registry (${err.message}) — assuming ownership`);
|
|
172
|
+
return { isOwner: true, liveCount: 1 };
|
|
173
|
+
}
|
|
174
|
+
// Nobody announced (including us — announcement is best-effort): assume ownership. Standing
|
|
175
|
+
// down on missing evidence would mute a healthy, solitary agent.
|
|
176
|
+
if (live.length === 0)
|
|
177
|
+
return { isOwner: true, liveCount: 0 };
|
|
178
|
+
const owner = live[0];
|
|
179
|
+
if (owner.pid === process.pid)
|
|
180
|
+
return { isOwner: true, liveCount: live.length };
|
|
181
|
+
// We are announced but are not newest -> superseded.
|
|
182
|
+
if (live.some((e) => e.pid === process.pid)) {
|
|
183
|
+
return {
|
|
184
|
+
isOwner: false,
|
|
185
|
+
owner: { pid: owner.pid, version: owner.version, endpoint: owner.endpoint },
|
|
186
|
+
liveCount: live.length,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
// Others are announced but we are not (our own announce failed). Do NOT stand down on the
|
|
190
|
+
// strength of a record we could not write: fail open.
|
|
191
|
+
return { isOwner: true, liveCount: live.length + 1 };
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=wake-ownership.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wake-ownership.js","sourceRoot":"","sources":["../../src/services/wake-ownership.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@essentialai/cogent-bridge",
|
|
3
|
-
"version": "3.23.
|
|
3
|
+
"version": "3.23.4",
|
|
4
4
|
"description": "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.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "io.github.eaisdevelopment/cogent",
|
|
4
4
|
"title": "Cogent Bridge from Essential AI Solutions (essentialai.uk)",
|
|
5
5
|
"description": "MCP bridge for inter-session communication between Claude Code instances",
|
|
6
|
-
"version": "3.23.
|
|
6
|
+
"version": "3.23.4",
|
|
7
7
|
"repository": {
|
|
8
8
|
"url": "https://github.com/eaisdevelopment/cogent",
|
|
9
9
|
"source": "github"
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "@essentialai/cogent-bridge",
|
|
15
|
-
"version": "3.23.
|
|
15
|
+
"version": "3.23.4",
|
|
16
16
|
"runtimeHint": "npx",
|
|
17
17
|
"transport": {
|
|
18
18
|
"type": "stdio"
|