@essentialai/cogent-plugin 3.25.7 → 3.25.9
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/.claude-plugin/plugin.json +1 -1
- package/bridge/cogent-bridge.mjs +181 -29
- package/hooks/check-on-stop.lib.mjs +21 -0
- package/hooks/check-on-stop.mjs +5 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cogent",
|
|
3
|
-
"version": "3.25.
|
|
3
|
+
"version": "3.25.9",
|
|
4
4
|
"description": "Inter-session communication bridge for Claude Code with Slack integration. Enables CC agents and Slack team members to communicate in real time.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Essential AI Solutions",
|
package/bridge/cogent-bridge.mjs
CHANGED
|
@@ -655,18 +655,80 @@ async function loadCredentials(credentialPath) {
|
|
|
655
655
|
async function saveCredentials(creds, credentialPath) {
|
|
656
656
|
const filePath = resolveCredentialPath(credentialPath);
|
|
657
657
|
await fs4.mkdir(path4.dirname(filePath), { recursive: true, mode: 448 });
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
{
|
|
662
|
-
|
|
663
|
-
|
|
658
|
+
let out = creds;
|
|
659
|
+
if (creds.peers === void 0) {
|
|
660
|
+
const prior = await loadCredentials(filePath);
|
|
661
|
+
if (prior?.peers) out = { ...creds, peers: prior.peers };
|
|
662
|
+
}
|
|
663
|
+
const tmp = `${filePath}.${process.pid}.${crypto.randomBytes(4).toString("hex")}.tmp`;
|
|
664
|
+
await fs4.writeFile(tmp, JSON.stringify(out, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
665
|
+
await fs4.chmod(tmp, 384);
|
|
666
|
+
await fs4.rename(tmp, filePath);
|
|
664
667
|
}
|
|
665
668
|
async function rememberIssuedPeerSecret(sessionId, peerId, peerSecret) {
|
|
666
669
|
const creds = await loadCredentials();
|
|
667
670
|
if (!creds || creds.sessionId !== sessionId) return;
|
|
668
|
-
|
|
669
|
-
|
|
671
|
+
const entry = creds.peers?.[peerId];
|
|
672
|
+
const entryCurrent = !entry || entry.sessionId !== sessionId || entry.peerSecret === peerSecret;
|
|
673
|
+
if (creds.peerId === peerId && creds.peerSecret === peerSecret && entryCurrent) return;
|
|
674
|
+
const peers = entry && entry.sessionId === sessionId ? { ...creds.peers, [peerId]: { ...entry, peerSecret, savedAt: (/* @__PURE__ */ new Date()).toISOString() } } : creds.peers;
|
|
675
|
+
const base = entry && entry.sessionId === sessionId ? { ...creds, ...entry } : creds;
|
|
676
|
+
await saveCredentials({ ...base, peerId, peerSecret, ...peers ? { peers } : {}, savedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
677
|
+
}
|
|
678
|
+
function setCurrentCloudConnection(c2) {
|
|
679
|
+
currentCloudConnection = c2;
|
|
680
|
+
}
|
|
681
|
+
function getCurrentCloudConnection() {
|
|
682
|
+
return currentCloudConnection;
|
|
683
|
+
}
|
|
684
|
+
function peerCredentials(folder, peerId) {
|
|
685
|
+
if (!folder) return null;
|
|
686
|
+
if (folder.peerId === peerId) {
|
|
687
|
+
const { peers: _p, ...top } = folder;
|
|
688
|
+
return top;
|
|
689
|
+
}
|
|
690
|
+
const e = folder.peers?.[peerId];
|
|
691
|
+
return e ? { ...e, peerId } : null;
|
|
692
|
+
}
|
|
693
|
+
function peerRecordsOf(folder) {
|
|
694
|
+
if (!folder?.peers) return [];
|
|
695
|
+
return Object.entries(folder.peers).map(([peerId, e]) => ({ ...e, peerId }));
|
|
696
|
+
}
|
|
697
|
+
async function loadPeerRecord(peerId, credentialPath) {
|
|
698
|
+
return peerCredentials(await loadCredentials(credentialPath), peerId);
|
|
699
|
+
}
|
|
700
|
+
async function loadPeerRecords(credentialPath) {
|
|
701
|
+
return peerRecordsOf(await loadCredentials(credentialPath));
|
|
702
|
+
}
|
|
703
|
+
async function savePeerRecord(rec, credentialPath) {
|
|
704
|
+
const folder = await loadCredentials(credentialPath);
|
|
705
|
+
if (!folder) return;
|
|
706
|
+
const { peerId, peers: _p, ...entry } = rec;
|
|
707
|
+
await saveCredentials({ ...folder, peers: { ...folder.peers ?? {}, [peerId]: entry } }, credentialPath);
|
|
708
|
+
}
|
|
709
|
+
async function deletePeerRecord(peerId, credentialPath) {
|
|
710
|
+
const folder = await loadCredentials(credentialPath);
|
|
711
|
+
if (!folder?.peers?.[peerId]) return;
|
|
712
|
+
const { [peerId]: _gone, ...rest } = folder.peers;
|
|
713
|
+
await saveCredentials({ ...folder, peers: rest }, credentialPath);
|
|
714
|
+
}
|
|
715
|
+
function chooseRestoreCredentials(folder, records, ownSession, platform) {
|
|
716
|
+
if (records.length === 0) return { creds: folder };
|
|
717
|
+
const top = folder?.peerId ? peerCredentials(folder, folder.peerId) : null;
|
|
718
|
+
const pick2 = (r) => peerCredentials(folder, r.peerId) ?? r;
|
|
719
|
+
if (ownSession) {
|
|
720
|
+
const mine = records.filter((r) => r.agentSession === ownSession);
|
|
721
|
+
if (mine.length > 0) return { creds: pick2(mine[0]) };
|
|
722
|
+
}
|
|
723
|
+
const sameKind = records.filter((r) => r.platform === platform);
|
|
724
|
+
if (sameKind.length === 1) return { creds: pick2(sameKind[0]) };
|
|
725
|
+
if (sameKind.length > 1 && top && sameKind.some((r) => r.peerId === top.peerId)) return { creds: top };
|
|
726
|
+
if (sameKind.length === 0 && top && !records.some((r) => r.peerId === top.peerId)) return { creds: top };
|
|
727
|
+
const names = records.map((r) => `'${r.peerId}' (${r.platform ?? "unknown agent"})`).join(", ");
|
|
728
|
+
return {
|
|
729
|
+
creds: null,
|
|
730
|
+
skip: sameKind.length === 0 ? `this folder's saved names belong to other agents \u2014 ${names}; this ${platform} agent has none, so it registers its own name explicitly` : `this folder has several saved ${platform} names and this bridge cannot tell which is its own \u2014 ${names}; the agent registers its name explicitly`
|
|
731
|
+
};
|
|
670
732
|
}
|
|
671
733
|
async function legacyGlobalCredentialsExist() {
|
|
672
734
|
try {
|
|
@@ -676,7 +738,7 @@ async function legacyGlobalCredentialsExist() {
|
|
|
676
738
|
return false;
|
|
677
739
|
}
|
|
678
740
|
}
|
|
679
|
-
var LEGACY_GLOBAL_CREDENTIAL_PATH, joinedInProcessFlag, cloudAttemptInProcessFlag, cloudAttemptGeneration, cloudAttemptListeners;
|
|
741
|
+
var LEGACY_GLOBAL_CREDENTIAL_PATH, joinedInProcessFlag, cloudAttemptInProcessFlag, cloudAttemptGeneration, cloudAttemptListeners, currentCloudConnection;
|
|
680
742
|
var init_credential_store = __esm({
|
|
681
743
|
"src/cloud/credential-store.ts"() {
|
|
682
744
|
"use strict";
|
|
@@ -688,6 +750,7 @@ var init_credential_store = __esm({
|
|
|
688
750
|
cloudAttemptInProcessFlag = false;
|
|
689
751
|
cloudAttemptGeneration = 0;
|
|
690
752
|
cloudAttemptListeners = /* @__PURE__ */ new Set();
|
|
753
|
+
currentCloudConnection = null;
|
|
691
754
|
}
|
|
692
755
|
});
|
|
693
756
|
|
|
@@ -26412,8 +26475,8 @@ var init_stdio2 = __esm({
|
|
|
26412
26475
|
// src/constants.ts
|
|
26413
26476
|
import { createRequire } from "node:module";
|
|
26414
26477
|
function resolveVersion() {
|
|
26415
|
-
if ("3.25.
|
|
26416
|
-
return "3.25.
|
|
26478
|
+
if ("3.25.9") {
|
|
26479
|
+
return "3.25.9";
|
|
26417
26480
|
}
|
|
26418
26481
|
try {
|
|
26419
26482
|
const require2 = createRequire(import.meta.url);
|
|
@@ -26692,6 +26755,11 @@ var init_config = __esm({
|
|
|
26692
26755
|
// launches via the daemon (see bin/cogent-codex) and sets this to `app-server`.
|
|
26693
26756
|
// Only affects COGENT_PLATFORM=codex; claude/gemini/mail rails never read it.
|
|
26694
26757
|
COGENT_CODEX_WAKE: import_zod2.z.enum(["auto", "exec-resume", "app-server"]).default("auto"),
|
|
26758
|
+
// COG-86: what a Claude wake may do. `read` (default) = Read, Glob and Grep only: the wake answers but cannot edit,
|
|
26759
|
+
// run commands or commit. A wake resumes the SAME session a human may have open in a terminal, and nothing can
|
|
26760
|
+
// tell reliably that the terminal is there; on 2026-10-15 a full-tool wake edited a live session's files, then
|
|
26761
|
+
// committed and pushed. `full` = every tool (the behaviour before 3.25.9), for an agent no human drives.
|
|
26762
|
+
COGENT_WAKE_TOOLS: import_zod2.z.enum(["read", "full"]).default("read"),
|
|
26695
26763
|
// Auto-recovery for a blocked Codex sandbox. On a host where the OS sandbox
|
|
26696
26764
|
// can't initialize (unprivileged user namespaces blocked — Ubuntu 24.04's
|
|
26697
26765
|
// apparmor_restrict_unprivileged_userns, containers, hardened kernels),
|
|
@@ -26833,6 +26901,7 @@ var init_errors4 = __esm({
|
|
|
26833
26901
|
BridgeErrorCode2["JOIN_NOT_CONFIRMED"] = "JOIN_NOT_CONFIRMED";
|
|
26834
26902
|
BridgeErrorCode2["CHANNEL_NOT_FOUND"] = "CHANNEL_NOT_FOUND";
|
|
26835
26903
|
BridgeErrorCode2["PEER_NOT_YOURS"] = "PEER_NOT_YOURS";
|
|
26904
|
+
BridgeErrorCode2["PEER_HELD_IN_FOLDER"] = "PEER_HELD_IN_FOLDER";
|
|
26836
26905
|
return BridgeErrorCode2;
|
|
26837
26906
|
})(BridgeErrorCode || {});
|
|
26838
26907
|
BridgeError = class extends Error {
|
|
@@ -27351,6 +27420,11 @@ async function execClaude(sessionId, message, cwd, timeoutMs) {
|
|
|
27351
27420
|
// as a raw JSON blob. Flag drift is guarded by claude-preflight.ts.
|
|
27352
27421
|
"--output-format",
|
|
27353
27422
|
"json",
|
|
27423
|
+
// COG-86: an ALLOW list, never a deny list. --disallowedTools Edit,Write,NotebookEdit,Bash was measured to leak
|
|
27424
|
+
// (the Monitor tool, loaded through ToolSearch, ran a shell and wrote a file) and would miss every tool a later
|
|
27425
|
+
// claude adds. --tools is variadic, so it must be followed by another option, never by the message. A claude
|
|
27426
|
+
// too old to know --tools exits "unknown option" — the wake fails, it never runs with every tool.
|
|
27427
|
+
...config2.COGENT_WAKE_TOOLS === "read" ? ["--tools", READ_ONLY_WAKE_TOOLS, "--append-system-prompt", READ_ONLY_WAKE_NOTICE] : [],
|
|
27354
27428
|
"-p",
|
|
27355
27429
|
truncated
|
|
27356
27430
|
];
|
|
@@ -27479,12 +27553,15 @@ function parseClaudeJsonReply(raw) {
|
|
|
27479
27553
|
const result = typeof env.result === "string" ? env.result : null;
|
|
27480
27554
|
return { result, isError: env.is_error === true || env.subtype !== "success" };
|
|
27481
27555
|
}
|
|
27556
|
+
var READ_ONLY_WAKE_TOOLS, READ_ONLY_WAKE_NOTICE;
|
|
27482
27557
|
var init_cc_cli = __esm({
|
|
27483
27558
|
"src/services/cc-cli.ts"() {
|
|
27484
27559
|
"use strict";
|
|
27485
27560
|
init_config();
|
|
27486
27561
|
init_agent_config();
|
|
27487
27562
|
init_agent_sessions();
|
|
27563
|
+
READ_ONLY_WAKE_TOOLS = "Read,Glob,Grep";
|
|
27564
|
+
READ_ONLY_WAKE_NOTICE = "This is an automatic Cogent wake. You can read files but cannot change them or run commands. If the message asks for changes, say what must change and that it has to be done in the agent's own session.";
|
|
27488
27565
|
}
|
|
27489
27566
|
});
|
|
27490
27567
|
|
|
@@ -33899,6 +33976,7 @@ function announceBridge(cwd, peerId, version2, endpoint, sessionId = agentSessio
|
|
|
33899
33976
|
cwd: path19.resolve(cwd),
|
|
33900
33977
|
parentPid: process.ppid,
|
|
33901
33978
|
...sessionId ? { sessionId } : {},
|
|
33979
|
+
platform: process.env.COGENT_PLATFORM || "cc",
|
|
33902
33980
|
// 🔴 A headless bridge never had a client to lose (review r7 FIX2-2, SEC2-2): when its launching
|
|
33903
33981
|
// shell exited, the changed parent read as "client gone" and any bridge that started took its name.
|
|
33904
33982
|
...startedWithoutClient() ? { noClient: true } : {}
|
|
@@ -35396,6 +35474,25 @@ function otherBridgesInFolder(entries, self, listProcs) {
|
|
|
35396
35474
|
(e) => e.peerId !== PROCESS_MARK && e.pid !== self.pid && !e.draining && holderRelation(e.sessionId, self.sessionId) !== "same-session" && !holderClientGone(e, procs ??= listProcs())
|
|
35397
35475
|
);
|
|
35398
35476
|
}
|
|
35477
|
+
function heldInFolder(others, peerId, ownSession) {
|
|
35478
|
+
return others.find((o) => o.peerId === peerId && !mayBeOwnPreSessionBridge(o, ownSession));
|
|
35479
|
+
}
|
|
35480
|
+
function mayBeOwnPreSessionBridge(holder, ownSession) {
|
|
35481
|
+
if (holder.sessionId || !ownSession) return false;
|
|
35482
|
+
const v = /^(\d+)\.(\d+)\.(\d+)$/.exec(String(holder.version ?? ""));
|
|
35483
|
+
if (!v) return false;
|
|
35484
|
+
const [maj, min, pat] = [Number(v[1]), Number(v[2]), Number(v[3])];
|
|
35485
|
+
return maj < 3 || maj === 3 && (min < 25 || min === 25 && pat < 7);
|
|
35486
|
+
}
|
|
35487
|
+
function heldNameAdvice(peerId, holder, own) {
|
|
35488
|
+
const otherSession = !!(holder?.sessionId && own?.sessionId && holder.sessionId !== own.sessionId);
|
|
35489
|
+
const otherPlatform = !!(holder?.platform && own?.platform && holder.platform !== own.platform);
|
|
35490
|
+
if (otherSession || otherPlatform) {
|
|
35491
|
+
const who = [holder?.platform && `a ${holder.platform} agent`, holder?.sessionId && `session ${holder.sessionId}`].filter(Boolean).join(", ");
|
|
35492
|
+
return `'${peerId}' is held by ANOTHER agent's live bridge in this folder (${who}). It keeps the name while that agent runs, so waiting will not free it. Do not take the name and do not rename: tell the user which agent holds '${peerId}' and ask them to have THAT agent run /cogent:register with ITS OWN peer name (which releases '${peerId}'); after that, run /cogent:register again with the SAME peer name '${peerId}'`;
|
|
35493
|
+
}
|
|
35494
|
+
return `If this agent was just restarted, its previous connection closes within ${HOLDER_LINGER_SECONDS} seconds: wait ${HOLDER_LINGER_SECONDS}+ seconds, then run /cogent:register again with the SAME peer name '${peerId}'. If '${peerId}' is still held after that, another live connection is using it \u2014 another agent, or an old window of this one still open. Do not take the name and do not rename: ask the user`;
|
|
35495
|
+
}
|
|
35399
35496
|
function anotherAgentSessionAmong(others, ownSession) {
|
|
35400
35497
|
return others.some((o) => holderRelation(o.sessionId, ownSession) === "other-session");
|
|
35401
35498
|
}
|
|
@@ -35411,7 +35508,7 @@ function otherBridgeNotice(others, peerId, verdict, ownSession) {
|
|
|
35411
35508
|
}
|
|
35412
35509
|
return `${who} is registered as '${peerId}' too. NOBODY answers for '${peerId}' in this channel now: the bridge that started last (pid ${verdict.owner?.pid ?? "unknown"}) is the one that wakes its agent, but in this channel the relay registration now belongs to this bridge, so the relay refuses that bridge's replies here and this bridge does not wake. It stays that way until that bridge is closed or its agent registers again. If that bridge is registered in another channel, it still answers there.`;
|
|
35413
35510
|
});
|
|
35414
|
-
return lines.join(" ") + (anotherAgentSessionAmong(others, ownSession) ? `
|
|
35511
|
+
return lines.join(" ") + (anotherAgentSessionAmong(others, ownSession) ? ` Each agent in this folder keeps its own saved name (COG-80): after a restart it comes back under that name, not the other agent's.` : "");
|
|
35415
35512
|
}
|
|
35416
35513
|
function autoPresenceEnabled(env = process.env) {
|
|
35417
35514
|
const v = env.COGENT_AUTO_PRESENCE;
|
|
@@ -35427,6 +35524,10 @@ async function restorePresence(deps) {
|
|
|
35427
35524
|
return { state: "online", restored: false, detail: "registered by this process" };
|
|
35428
35525
|
}
|
|
35429
35526
|
const creds = await deps.loadCredentials();
|
|
35527
|
+
const skip = creds ? void 0 : deps.restoreSkip?.();
|
|
35528
|
+
if (skip) {
|
|
35529
|
+
return { state: "deferred", restored: false, detail: `nothing restored: ${skip}` };
|
|
35530
|
+
}
|
|
35430
35531
|
if (!creds || !creds.endpoint || !creds.sessionId || !creds.token) {
|
|
35431
35532
|
return {
|
|
35432
35533
|
state: "deferred",
|
|
@@ -35461,11 +35562,11 @@ async function restorePresence(deps) {
|
|
|
35461
35562
|
const started = Number.isFinite(servedBy.startedAt) ? new Date(servedBy.startedAt).toISOString() : "unknown";
|
|
35462
35563
|
const recheck = holder === "same-session";
|
|
35463
35564
|
const every = deps.recheckSeconds ? ` (re-checked every ${deps.recheckSeconds}s)` : "";
|
|
35464
|
-
const take =
|
|
35565
|
+
const take = heldNameAdvice(peerId);
|
|
35465
35566
|
const why = {
|
|
35466
35567
|
"same-session": `That bridge serves this same agent session (${session})`,
|
|
35467
35568
|
"no-session": `Neither bridge records an agent session (codex, gemini), so it cannot be told whether that bridge is this agent's previous one or another agent's. ${take}`,
|
|
35468
|
-
"other-session": `That bridge serves another agent session (${servedBy.sessionId}; ours: ${session}).
|
|
35569
|
+
"other-session": `That bridge serves another agent session (${servedBy.sessionId}; ours: ${session}). ` + heldNameAdvice(peerId, { sessionId: servedBy.sessionId }, { sessionId: session }),
|
|
35469
35570
|
unknown: `Its agent session cannot be compared with this one (that bridge: ${servedBy.sessionId ?? "not recorded"}; this bridge: ${session ?? "not known"}): it may be this agent's own other bridge of an older version, or another agent's. ${take}`
|
|
35470
35571
|
};
|
|
35471
35572
|
return {
|
|
@@ -35545,7 +35646,7 @@ function logPresence(outcome) {
|
|
|
35545
35646
|
logger.debug(`Presence: ${outcome.state} \u2014 ${outcome.detail}.`);
|
|
35546
35647
|
}
|
|
35547
35648
|
}
|
|
35548
|
-
var ORPHAN_PARENT_RE, NPX_WRAPPER_RE, lastPresence;
|
|
35649
|
+
var ORPHAN_PARENT_RE, NPX_WRAPPER_RE, HOLDER_LINGER_SECONDS, lastPresence;
|
|
35549
35650
|
var init_peer_presence = __esm({
|
|
35550
35651
|
"src/services/peer-presence.ts"() {
|
|
35551
35652
|
"use strict";
|
|
@@ -35553,6 +35654,7 @@ var init_peer_presence = __esm({
|
|
|
35553
35654
|
init_wake_ownership();
|
|
35554
35655
|
ORPHAN_PARENT_RE = /^(init|launchd|systemd)$/;
|
|
35555
35656
|
NPX_WRAPPER_RE = /^(?:\S*\/)?(?:sh|bash|dash) -c cogent-bridge\b|^npm exec @essentialai\/cogent-bridge/;
|
|
35657
|
+
HOLDER_LINGER_SECONDS = 60;
|
|
35556
35658
|
lastPresence = null;
|
|
35557
35659
|
}
|
|
35558
35660
|
});
|
|
@@ -35774,7 +35876,9 @@ var init_claude_preflight = __esm({
|
|
|
35774
35876
|
{ flag: "--dangerously-skip-permissions", usedFor: "unattended auto-relay execution" },
|
|
35775
35877
|
{ flag: "--no-session-persistence", usedFor: "don't pollute the resumed session transcript" },
|
|
35776
35878
|
{ flag: "--strict-mcp-config", usedFor: "isolate MCP config of the resumed process" },
|
|
35777
|
-
{ flag: "--output-format", usedFor: "structured JSON reply envelope (Slice 3 capture)" }
|
|
35879
|
+
{ flag: "--output-format", usedFor: "structured JSON reply envelope (Slice 3 capture)" },
|
|
35880
|
+
{ flag: "--tools", usedFor: "read-only wake: Read, Glob and Grep only (COG-86)" },
|
|
35881
|
+
{ flag: "--append-system-prompt", usedFor: "tell a read-only wake it cannot change anything (COG-86)" }
|
|
35778
35882
|
];
|
|
35779
35883
|
}
|
|
35780
35884
|
});
|
|
@@ -36004,7 +36108,9 @@ async function restorePeerPresence(attempt = 0) {
|
|
|
36004
36108
|
},
|
|
36005
36109
|
agentSessionId: () => agentSessionId(),
|
|
36006
36110
|
startedByWakeTurn: () => insideCodexWakeTurn(process.pid, ps()),
|
|
36007
|
-
|
|
36111
|
+
// COG-80: the identity THIS agent saved, not whichever agent registered in this folder last.
|
|
36112
|
+
loadCredentials: () => restoreCandidate(),
|
|
36113
|
+
restoreSkip: () => lastRestoreSkip,
|
|
36008
36114
|
cwd: () => process.cwd(),
|
|
36009
36115
|
recheckSeconds: Math.round(standbyRecheckMs() / 1e3),
|
|
36010
36116
|
goOnline: async ({ peerId, endpoint, sessionId, token, cwd, label, peerSecret }) => {
|
|
@@ -36012,7 +36118,7 @@ async function restorePeerPresence(attempt = 0) {
|
|
|
36012
36118
|
await reinitCloudBackend(endpoint, sessionId, token);
|
|
36013
36119
|
const backend = getBackend();
|
|
36014
36120
|
await backend.registerPeer(...presenceRegisterArgs({ peerId, sessionId, cwd, label, peerSecret }));
|
|
36015
|
-
const slot = await
|
|
36121
|
+
const slot = await restoreCandidate();
|
|
36016
36122
|
if (!stillCurrent() || slot?.sessionId !== sessionId || slot?.endpoint !== endpoint) {
|
|
36017
36123
|
throw new Error(PRESENCE_SUPERSEDED);
|
|
36018
36124
|
}
|
|
@@ -36144,9 +36250,20 @@ async function resolvePendingLabel() {
|
|
|
36144
36250
|
logger.warn(`Post-connect label upgrade failed (non-fatal): ${err}`);
|
|
36145
36251
|
}
|
|
36146
36252
|
}
|
|
36253
|
+
async function restoreCandidate() {
|
|
36254
|
+
const choice = chooseRestoreCredentials(
|
|
36255
|
+
await loadCredentials(),
|
|
36256
|
+
await loadPeerRecords(),
|
|
36257
|
+
agentSessionId(),
|
|
36258
|
+
getConfig().COGENT_PLATFORM
|
|
36259
|
+
);
|
|
36260
|
+
lastRestoreSkip = choice.skip;
|
|
36261
|
+
return choice.creds;
|
|
36262
|
+
}
|
|
36147
36263
|
async function reinitCloudBackend(endpoint, sessionId, token) {
|
|
36148
36264
|
const config2 = getConfig();
|
|
36149
36265
|
cloudHttpClient = new HttpClient(endpoint, token);
|
|
36266
|
+
setCurrentCloudConnection({ endpoint, sessionId, token });
|
|
36150
36267
|
const newBackend = new HttpBackend(cloudHttpClient, sessionId);
|
|
36151
36268
|
resetBackend();
|
|
36152
36269
|
initBackend(newBackend);
|
|
@@ -36251,6 +36368,7 @@ async function runStartup() {
|
|
|
36251
36368
|
cloudInbox = new MessageInbox();
|
|
36252
36369
|
bindInboxNotifier();
|
|
36253
36370
|
cloudHttpClient = new HttpClient(effectiveEndpoint, effectiveToken);
|
|
36371
|
+
setCurrentCloudConnection({ endpoint: effectiveEndpoint, sessionId: effectiveSessionId, token: effectiveToken });
|
|
36254
36372
|
cloudWsClient = createCloudWsClient(
|
|
36255
36373
|
effectiveEndpoint,
|
|
36256
36374
|
effectiveSessionId,
|
|
@@ -36285,7 +36403,7 @@ async function runPreflights() {
|
|
|
36285
36403
|
await preflightClaude(config2.COGENT_CLAUDE_PATH);
|
|
36286
36404
|
}
|
|
36287
36405
|
}
|
|
36288
|
-
var UUID_V4_RE, execFileAsync3, PERSIST_PATH, legacyWarnEmitted, cloudWsClient, cloudInbox, cloudHttpClient, cloudModeActive, inboxNotifier, notifierBoundTo, reRegistrationInProgress, presenceRetry, ignoredHolders, presenceSupersedeBound, PRESENCE_SUPERSEDED, pendingLabelResolve;
|
|
36406
|
+
var UUID_V4_RE, execFileAsync3, PERSIST_PATH, legacyWarnEmitted, cloudWsClient, cloudInbox, cloudHttpClient, cloudModeActive, inboxNotifier, notifierBoundTo, reRegistrationInProgress, presenceRetry, ignoredHolders, presenceSupersedeBound, PRESENCE_SUPERSEDED, pendingLabelResolve, lastRestoreSkip;
|
|
36289
36407
|
var init_startup = __esm({
|
|
36290
36408
|
"src/startup.ts"() {
|
|
36291
36409
|
"use strict";
|
|
@@ -36469,6 +36587,24 @@ function registerRegisterPeerTool(server) {
|
|
|
36469
36587
|
)
|
|
36470
36588
|
);
|
|
36471
36589
|
}
|
|
36590
|
+
if (config2.COGENT_ENDPOINT) try {
|
|
36591
|
+
const own = agentSessionId();
|
|
36592
|
+
const seen = /* @__PURE__ */ new Set();
|
|
36593
|
+
const entries = [...listLiveBridgesInFolder(cwd), ...listLiveBridgesInFolder(process.cwd())].filter((e) => !seen.has(`${e.pid}:${e.peerId}`) && seen.add(`${e.pid}:${e.peerId}`));
|
|
36594
|
+
const holder = heldInFolder(otherBridgesInFolder(entries, { pid: process.pid, sessionId: own }, listProcesses), peerId, own);
|
|
36595
|
+
if (holder) {
|
|
36596
|
+
logger.info(`Register: refused '${peerId}' \u2014 held in this folder by bridge pid ${holder.pid}`);
|
|
36597
|
+
return errorResult(
|
|
36598
|
+
new BridgeError(
|
|
36599
|
+
"PEER_HELD_IN_FOLDER" /* PEER_HELD_IN_FOLDER */,
|
|
36600
|
+
`'${peerId}' is held by another live connection in this folder (bridge pid ${holder.pid}, version ${registryText(holder.version)}); this bridge has NOT registered`,
|
|
36601
|
+
heldNameAdvice(peerId, holder, { sessionId: own, platform: config2.COGENT_PLATFORM })
|
|
36602
|
+
)
|
|
36603
|
+
);
|
|
36604
|
+
}
|
|
36605
|
+
} catch (err) {
|
|
36606
|
+
logger.debug(`Register: could not check this folder for a holder of '${peerId}': ${err.message}`);
|
|
36607
|
+
}
|
|
36472
36608
|
const hasCloudEndpointConfig = config2.COGENT_ENDPOINT && isCloudEndpoint(config2.COGENT_ENDPOINT);
|
|
36473
36609
|
let sessionCreated = false;
|
|
36474
36610
|
let cloudSessionId;
|
|
@@ -36579,7 +36715,9 @@ function registerRegisterPeerTool(server) {
|
|
|
36579
36715
|
let storedPeerSecret = argPeerSecret;
|
|
36580
36716
|
if (storedPeerSecret === void 0 && isCloudMode()) {
|
|
36581
36717
|
try {
|
|
36582
|
-
|
|
36718
|
+
const own = await loadPeerRecord(peerId);
|
|
36719
|
+
const folder = own ? null : await loadCredentials();
|
|
36720
|
+
storedPeerSecret = own?.peerSecret ?? (!folder?.peerId || folder.peerId === peerId ? folder?.peerSecret : void 0);
|
|
36583
36721
|
} catch {
|
|
36584
36722
|
storedPeerSecret = void 0;
|
|
36585
36723
|
}
|
|
@@ -36604,7 +36742,8 @@ function registerRegisterPeerTool(server) {
|
|
|
36604
36742
|
let recovered;
|
|
36605
36743
|
if (/PEER_NOT_YOURS/.test(msg) && isCloudMode()) {
|
|
36606
36744
|
try {
|
|
36607
|
-
const
|
|
36745
|
+
const folderCreds = await loadCredentials();
|
|
36746
|
+
const creds = await loadPeerRecord(peerId) ?? (!folderCreds?.peerId || folderCreds.peerId === peerId ? folderCreds : null);
|
|
36608
36747
|
if (creds?.ownerToken && creds.ownerToken !== creds.token && creds.endpoint && creds.sessionId) {
|
|
36609
36748
|
logger.warn(
|
|
36610
36749
|
`register_peer: '${peerId}' is owned by an earlier token (its peerSecret was lost by a pre-3.23.5 re-join). Re-proving ownership with the stored owner token.`
|
|
@@ -36626,7 +36765,7 @@ function registerRegisterPeerTool(server) {
|
|
|
36626
36765
|
recovered = await res.json();
|
|
36627
36766
|
const secret = recovered?.peerSecret;
|
|
36628
36767
|
if (secret) {
|
|
36629
|
-
await
|
|
36768
|
+
await savePeerRecord({ ...creds, peerId, peerSecret: secret, savedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
36630
36769
|
logger.info(`register_peer: ownership reclaimed for '${peerId}'; a fresh peerSecret is stored.`);
|
|
36631
36770
|
}
|
|
36632
36771
|
}
|
|
@@ -36660,13 +36799,24 @@ function registerRegisterPeerTool(server) {
|
|
|
36660
36799
|
try {
|
|
36661
36800
|
const creds = await loadCredentials();
|
|
36662
36801
|
const issuedSecret = peer?.peerSecret;
|
|
36663
|
-
|
|
36664
|
-
|
|
36665
|
-
|
|
36666
|
-
|
|
36667
|
-
|
|
36802
|
+
const platform = getConfig().COGENT_PLATFORM;
|
|
36803
|
+
const agent = { agentSession: (platform === "cc" ? agentSessionId() : void 0) ?? sessionId, platform };
|
|
36804
|
+
if (creds) {
|
|
36805
|
+
const channel = getCurrentCloudConnection() ?? { endpoint: creds.endpoint, sessionId: creds.sessionId, token: creds.token };
|
|
36806
|
+
const mine = peerCredentials(creds, peerId);
|
|
36807
|
+
const sameChannel = !!mine && mine.sessionId === channel.sessionId && mine.endpoint === channel.endpoint;
|
|
36808
|
+
const secret = issuedSecret ?? (sameChannel ? mine.peerSecret : void 0);
|
|
36809
|
+
const ownerToken = sameChannel ? mine.ownerToken ?? (mine.token !== channel.token ? mine.token : void 0) : void 0;
|
|
36810
|
+
const label2 = creds.sessionId === channel.sessionId ? creds.label : sameChannel ? mine.label : void 0;
|
|
36811
|
+
const entry = {
|
|
36812
|
+
...channel,
|
|
36813
|
+
...label2 ? { label: label2 } : {},
|
|
36814
|
+
...agent,
|
|
36815
|
+
...secret ? { peerSecret: secret } : {},
|
|
36816
|
+
...ownerToken ? { ownerToken } : {},
|
|
36668
36817
|
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
36669
|
-
}
|
|
36818
|
+
};
|
|
36819
|
+
await saveCredentials({ ...entry, peerId, peers: { ...creds.peers ?? {}, [peerId]: entry } });
|
|
36670
36820
|
}
|
|
36671
36821
|
} catch (err) {
|
|
36672
36822
|
logger.debug(
|
|
@@ -37786,7 +37936,8 @@ function registerDeregisterPeerTool(server) {
|
|
|
37786
37936
|
const backend = getBackend();
|
|
37787
37937
|
let ownershipProof;
|
|
37788
37938
|
try {
|
|
37789
|
-
|
|
37939
|
+
const folder = await loadCredentials();
|
|
37940
|
+
ownershipProof = (await loadPeerRecord(peerId))?.peerSecret ?? (!folder?.peerId || folder.peerId === peerId ? folder?.peerSecret : void 0);
|
|
37790
37941
|
} catch {
|
|
37791
37942
|
}
|
|
37792
37943
|
const { removed, mailboxDeprovisioned } = await backend.deregisterPeer(peerId, ownershipProof);
|
|
@@ -37807,6 +37958,7 @@ function registerDeregisterPeerTool(server) {
|
|
|
37807
37958
|
await saveCredentials({ ...rest, savedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
37808
37959
|
logger.info("cleared stored peerId after deregister", { peerId });
|
|
37809
37960
|
}
|
|
37961
|
+
await deletePeerRecord(peerId);
|
|
37810
37962
|
} catch (err) {
|
|
37811
37963
|
logger.warn("failed to clear stored peerId after deregister", { error: err });
|
|
37812
37964
|
}
|
|
@@ -84,6 +84,27 @@ export function buildAgentSessionRecord(payload, nowIso) {
|
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Which saved identity this hook's agent is (COG-80). MUST follow src/cloud/credential-store.ts
|
|
89
|
+
* chooseRestoreCredentials + peerCredentials. `folder` is the folder file; its `peers` map holds one
|
|
90
|
+
* entry per registered peer. The top-level fields name the last-registered peer and are that peer's
|
|
91
|
+
* newest write. Returns null when the folder's names provably belong to other agents (stay silent
|
|
92
|
+
* rather than act as the other agent in the folder).
|
|
93
|
+
*/
|
|
94
|
+
export function chooseHookCredentials(folder, sessionId, platform) {
|
|
95
|
+
if (!folder) return null;
|
|
96
|
+
const records = Object.entries(folder.peers || {}).map(([peerId, e]) => ({ ...e, peerId }));
|
|
97
|
+
if (records.length === 0) return folder;
|
|
98
|
+
const resolve = (peerId) => (folder.peerId === peerId ? folder : { ...folder.peers[peerId], peerId });
|
|
99
|
+
const mine = sessionId ? records.filter((r) => r.agentSession === sessionId) : [];
|
|
100
|
+
if (mine.length > 0) return resolve(mine[0].peerId);
|
|
101
|
+
const sameKind = records.filter((r) => r.platform === platform);
|
|
102
|
+
if (sameKind.length === 1) return resolve(sameKind[0].peerId);
|
|
103
|
+
if (sameKind.length > 1 && folder.peerId && sameKind.some((r) => r.peerId === folder.peerId)) return folder;
|
|
104
|
+
if (sameKind.length === 0 && folder.peerId && !records.some((r) => r.peerId === folder.peerId)) return folder;
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
87
108
|
function isForMe(msg, me, scope) {
|
|
88
109
|
if (msg.toPeerId === me) return scope.directed;
|
|
89
110
|
if (msg.toPeerId === "*") {
|
package/hooks/check-on-stop.mjs
CHANGED
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
activeInFlightIds,
|
|
21
21
|
agentSessionRecordPath,
|
|
22
22
|
buildAgentSessionRecord,
|
|
23
|
+
chooseHookCredentials,
|
|
24
|
+
rootFromTranscriptPath,
|
|
23
25
|
} from "./check-on-stop.lib.mjs";
|
|
24
26
|
|
|
25
27
|
const HTTP_TIMEOUT_MS = 5000; // < the 8s hook timeout; only bites on a degraded relay
|
|
@@ -143,7 +145,9 @@ async function main() {
|
|
|
143
145
|
if (envOff(env.COGENT_CHECK_ON_STOP)) return process.exit(0); // opt-out
|
|
144
146
|
const cwd = input.cwd || process.cwd();
|
|
145
147
|
|
|
146
|
-
|
|
148
|
+
// COG-80: this agent's own record — the folder file names whichever agent registered last.
|
|
149
|
+
const creds = chooseHookCredentials(await readJson(credPath(cwd, env)), input.session_id,
|
|
150
|
+
rootFromTranscriptPath(input.transcript_path) ? "cc" : "codex");
|
|
147
151
|
if (!creds || !creds.endpoint || !creds.sessionId || !creds.token) {
|
|
148
152
|
return process.exit(0); // not registered in cloud mode → nothing to do
|
|
149
153
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@essentialai/cogent-plugin",
|
|
3
|
-
"version": "3.25.
|
|
3
|
+
"version": "3.25.9",
|
|
4
4
|
"description": "Cogent — Claude Code plugin (skills + slash-commands + MCP server) for the cross-agent comms fabric.",
|
|
5
5
|
"author": { "name": "Essential AI Solutions Ltd.", "url": "https://essentialai.uk" },
|
|
6
6
|
"homepage": "https://cogent.tools",
|