@essentialai/cogent-plugin 3.25.5 → 3.25.6
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cogent",
|
|
3
|
-
"version": "3.25.
|
|
3
|
+
"version": "3.25.6",
|
|
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
|
@@ -625,6 +625,20 @@ function hasJoinedInProcess() {
|
|
|
625
625
|
function markCloudAttemptInProcess() {
|
|
626
626
|
cloudAttemptInProcessFlag = true;
|
|
627
627
|
joinedInProcessFlag = false;
|
|
628
|
+
cloudAttemptGeneration++;
|
|
629
|
+
for (const fn of cloudAttemptListeners) {
|
|
630
|
+
try {
|
|
631
|
+
fn();
|
|
632
|
+
} catch {
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
function getCloudAttemptGeneration() {
|
|
637
|
+
return cloudAttemptGeneration;
|
|
638
|
+
}
|
|
639
|
+
function onCloudAttemptInProcess(fn) {
|
|
640
|
+
cloudAttemptListeners.add(fn);
|
|
641
|
+
return () => cloudAttemptListeners.delete(fn);
|
|
628
642
|
}
|
|
629
643
|
function hasCloudAttemptInProcess() {
|
|
630
644
|
return cloudAttemptInProcessFlag;
|
|
@@ -662,7 +676,7 @@ async function legacyGlobalCredentialsExist() {
|
|
|
662
676
|
return false;
|
|
663
677
|
}
|
|
664
678
|
}
|
|
665
|
-
var LEGACY_GLOBAL_CREDENTIAL_PATH, joinedInProcessFlag, cloudAttemptInProcessFlag;
|
|
679
|
+
var LEGACY_GLOBAL_CREDENTIAL_PATH, joinedInProcessFlag, cloudAttemptInProcessFlag, cloudAttemptGeneration, cloudAttemptListeners;
|
|
666
680
|
var init_credential_store = __esm({
|
|
667
681
|
"src/cloud/credential-store.ts"() {
|
|
668
682
|
"use strict";
|
|
@@ -672,6 +686,8 @@ var init_credential_store = __esm({
|
|
|
672
686
|
);
|
|
673
687
|
joinedInProcessFlag = false;
|
|
674
688
|
cloudAttemptInProcessFlag = false;
|
|
689
|
+
cloudAttemptGeneration = 0;
|
|
690
|
+
cloudAttemptListeners = /* @__PURE__ */ new Set();
|
|
675
691
|
}
|
|
676
692
|
});
|
|
677
693
|
|
|
@@ -26396,8 +26412,8 @@ var init_stdio2 = __esm({
|
|
|
26396
26412
|
// src/constants.ts
|
|
26397
26413
|
import { createRequire } from "node:module";
|
|
26398
26414
|
function resolveVersion() {
|
|
26399
|
-
if ("3.25.
|
|
26400
|
-
return "3.25.
|
|
26415
|
+
if ("3.25.6") {
|
|
26416
|
+
return "3.25.6";
|
|
26401
26417
|
}
|
|
26402
26418
|
try {
|
|
26403
26419
|
const require2 = createRequire(import.meta.url);
|
|
@@ -26787,11 +26803,14 @@ var init_errors4 = __esm({
|
|
|
26787
26803
|
BridgeErrorCode2["DIR_NOT_WRITABLE"] = "DIR_NOT_WRITABLE";
|
|
26788
26804
|
BridgeErrorCode2["NETWORK_TIMEOUT"] = "NETWORK_TIMEOUT";
|
|
26789
26805
|
BridgeErrorCode2["JOIN_NOT_CONFIRMED"] = "JOIN_NOT_CONFIRMED";
|
|
26806
|
+
BridgeErrorCode2["PEER_NOT_YOURS"] = "PEER_NOT_YOURS";
|
|
26790
26807
|
return BridgeErrorCode2;
|
|
26791
26808
|
})(BridgeErrorCode || {});
|
|
26792
26809
|
BridgeError = class extends Error {
|
|
26793
26810
|
code;
|
|
26794
26811
|
suggestion;
|
|
26812
|
+
/** Relay hint: ms until a refused peerId can be reclaimed (PEER_NOT_YOURS only). */
|
|
26813
|
+
retryAfterMs;
|
|
26795
26814
|
constructor(code, message, suggestion) {
|
|
26796
26815
|
const fullMessage = suggestion ? `${code}: ${message}. ${suggestion}` : `${code}: ${message}`;
|
|
26797
26816
|
super(fullMessage);
|
|
@@ -28278,12 +28297,16 @@ var init_http_client = __esm({
|
|
|
28278
28297
|
} catch {
|
|
28279
28298
|
errorBody = { message: resp.statusText };
|
|
28280
28299
|
}
|
|
28281
|
-
const code = errorBody.code && errorBody.code in BridgeErrorCode ? errorBody.code : "STARTUP_FAILED" /* STARTUP_FAILED */;
|
|
28282
|
-
|
|
28300
|
+
const code = /PEER_NOT_YOURS/.test(errorBody.message ?? "") ? "PEER_NOT_YOURS" /* PEER_NOT_YOURS */ : errorBody.code && errorBody.code in BridgeErrorCode ? errorBody.code : "STARTUP_FAILED" /* STARTUP_FAILED */;
|
|
28301
|
+
const err = new BridgeError(
|
|
28283
28302
|
code,
|
|
28284
28303
|
errorBody.message ?? `HTTP ${resp.status}`,
|
|
28285
28304
|
errorBody.suggestion
|
|
28286
28305
|
);
|
|
28306
|
+
if (typeof errorBody.retryAfterMs === "number" && Number.isFinite(errorBody.retryAfterMs)) {
|
|
28307
|
+
err.retryAfterMs = errorBody.retryAfterMs;
|
|
28308
|
+
}
|
|
28309
|
+
throw err;
|
|
28287
28310
|
}
|
|
28288
28311
|
};
|
|
28289
28312
|
}
|
|
@@ -35517,6 +35540,7 @@ __export(startup_exports, {
|
|
|
35517
35540
|
hasPendingLabelResolve: () => hasPendingLabelResolve,
|
|
35518
35541
|
isCloudMode: () => isCloudMode2,
|
|
35519
35542
|
presenceRegisterArgs: () => presenceRegisterArgs,
|
|
35543
|
+
presenceRetryPending: () => presenceRetryPending,
|
|
35520
35544
|
reinitCloudBackend: () => reinitCloudBackend,
|
|
35521
35545
|
resolvePendingLabel: () => resolvePendingLabel,
|
|
35522
35546
|
restorePeerPresence: () => restorePeerPresence,
|
|
@@ -35669,6 +35693,9 @@ function presenceRegisterArgs(p) {
|
|
|
35669
35693
|
// AUD-003 ownership proof — reclaims a peer owned by an earlier token
|
|
35670
35694
|
];
|
|
35671
35695
|
}
|
|
35696
|
+
function presenceRetryPending() {
|
|
35697
|
+
return presenceRetry !== null;
|
|
35698
|
+
}
|
|
35672
35699
|
function cancelPresenceRetry() {
|
|
35673
35700
|
if (presenceRetry) clearTimeout(presenceRetry);
|
|
35674
35701
|
presenceRetry = null;
|
|
@@ -35678,6 +35705,17 @@ function presenceRetryDelayMs(attempt) {
|
|
|
35678
35705
|
return Math.min((Number.isFinite(base) && base > 0 ? base : 3e4) * 2 ** attempt, 3e5);
|
|
35679
35706
|
}
|
|
35680
35707
|
async function restorePeerPresence(attempt = 0) {
|
|
35708
|
+
if (!presenceSupersedeBound) {
|
|
35709
|
+
presenceSupersedeBound = true;
|
|
35710
|
+
onCloudAttemptInProcess(() => cancelPresenceRetry());
|
|
35711
|
+
}
|
|
35712
|
+
const superseded = { state: "deferred", restored: false, detail: PRESENCE_SUPERSEDED };
|
|
35713
|
+
if (hasCloudAttemptInProcess()) {
|
|
35714
|
+
logPresence(superseded);
|
|
35715
|
+
return superseded;
|
|
35716
|
+
}
|
|
35717
|
+
const generation = getCloudAttemptGeneration();
|
|
35718
|
+
const stillCurrent = () => getCloudAttemptGeneration() === generation;
|
|
35681
35719
|
const outcome = await restorePresence({
|
|
35682
35720
|
// The endpoint being a relay URL is what makes presence meaningful. We deliberately do
|
|
35683
35721
|
// NOT gate on the active backend being the cloud one: at startup it is still the
|
|
@@ -35698,14 +35736,24 @@ async function restorePeerPresence(attempt = 0) {
|
|
|
35698
35736
|
loadCredentials: () => loadCredentials(),
|
|
35699
35737
|
cwd: () => process.cwd(),
|
|
35700
35738
|
goOnline: async ({ peerId, endpoint, sessionId, token, cwd, label, peerSecret }) => {
|
|
35739
|
+
if (!stillCurrent()) throw new Error(PRESENCE_SUPERSEDED);
|
|
35701
35740
|
await reinitCloudBackend(endpoint, sessionId, token);
|
|
35702
35741
|
const backend = getBackend();
|
|
35703
35742
|
await backend.registerPeer(...presenceRegisterArgs({ peerId, sessionId, cwd, label, peerSecret }));
|
|
35743
|
+
const slot = await loadCredentials();
|
|
35744
|
+
if (!stillCurrent() || slot?.sessionId !== sessionId || slot?.endpoint !== endpoint) {
|
|
35745
|
+
throw new Error(PRESENCE_SUPERSEDED);
|
|
35746
|
+
}
|
|
35704
35747
|
autoRelay.setLocalPeer(peerId, sessionId, cwd, "agent", label, sessionId);
|
|
35705
35748
|
if (cloudWsClient) cloudWsClient.setPeerId(peerId);
|
|
35706
35749
|
heartbeat.restart();
|
|
35750
|
+
markJoinedInProcess();
|
|
35707
35751
|
}
|
|
35708
35752
|
});
|
|
35753
|
+
if (!stillCurrent()) {
|
|
35754
|
+
logPresence(superseded);
|
|
35755
|
+
return superseded;
|
|
35756
|
+
}
|
|
35709
35757
|
setPresence(outcome);
|
|
35710
35758
|
logPresence(outcome);
|
|
35711
35759
|
if (outcome.state === "offline" && outcome.detail.startsWith("presence restore failed")) {
|
|
@@ -35958,7 +36006,7 @@ async function runPreflights() {
|
|
|
35958
36006
|
await preflightClaude(config2.COGENT_CLAUDE_PATH);
|
|
35959
36007
|
}
|
|
35960
36008
|
}
|
|
35961
|
-
var UUID_V4_RE, execFileAsync3, PERSIST_PATH, legacyWarnEmitted, cloudWsClient, cloudInbox, cloudHttpClient, cloudModeActive, inboxNotifier, notifierBoundTo, reRegistrationInProgress, presenceRetry, pendingLabelResolve;
|
|
36009
|
+
var UUID_V4_RE, execFileAsync3, PERSIST_PATH, legacyWarnEmitted, cloudWsClient, cloudInbox, cloudHttpClient, cloudModeActive, inboxNotifier, notifierBoundTo, reRegistrationInProgress, presenceRetry, presenceSupersedeBound, PRESENCE_SUPERSEDED, pendingLabelResolve;
|
|
35962
36010
|
var init_startup = __esm({
|
|
35963
36011
|
"src/startup.ts"() {
|
|
35964
36012
|
"use strict";
|
|
@@ -35992,6 +36040,8 @@ var init_startup = __esm({
|
|
|
35992
36040
|
notifierBoundTo = null;
|
|
35993
36041
|
reRegistrationInProgress = false;
|
|
35994
36042
|
presenceRetry = null;
|
|
36043
|
+
presenceSupersedeBound = false;
|
|
36044
|
+
PRESENCE_SUPERSEDED = "presence restore superseded: an explicit cogent_join_session/cogent_create_session ran in this process and owns the channel choice";
|
|
35995
36045
|
pendingLabelResolve = null;
|
|
35996
36046
|
}
|
|
35997
36047
|
});
|
|
@@ -36064,6 +36114,30 @@ function unresolvedSessionReason(r) {
|
|
|
36064
36114
|
if (r.sessionId) return null;
|
|
36065
36115
|
return r.candidateCount === 0 ? "no-candidates" : "ambiguous";
|
|
36066
36116
|
}
|
|
36117
|
+
function peerReclaimWaitMs() {
|
|
36118
|
+
const v = Number(process.env.COGENT_PEER_RECLAIM_WAIT_MS);
|
|
36119
|
+
return Number.isFinite(v) && v >= 0 ? v : 5e4;
|
|
36120
|
+
}
|
|
36121
|
+
async function retrySamePeerId(first, attempt, budgetMs = peerReclaimWaitMs(), sleep4 = (ms) => new Promise((r) => setTimeout(r, ms))) {
|
|
36122
|
+
let err = first;
|
|
36123
|
+
const start = Date.now();
|
|
36124
|
+
let backoff = 1e3;
|
|
36125
|
+
for (; ; ) {
|
|
36126
|
+
const remaining = budgetMs - (Date.now() - start);
|
|
36127
|
+
const retryAfter = err.retryAfterMs;
|
|
36128
|
+
if (retryAfter === void 0 || remaining <= 0 || retryAfter > remaining) throw err;
|
|
36129
|
+
const wait = Math.min(Math.max(retryAfter, backoff), remaining);
|
|
36130
|
+
logger.warn(`register_peer: peerId held by an earlier registration \u2014 retrying the SAME id in ${Math.round(wait / 1e3)}s`);
|
|
36131
|
+
await sleep4(wait);
|
|
36132
|
+
backoff = Math.min(backoff * 2, 15e3);
|
|
36133
|
+
try {
|
|
36134
|
+
return await attempt();
|
|
36135
|
+
} catch (next) {
|
|
36136
|
+
if (!(next instanceof BridgeError && next.code === "PEER_NOT_YOURS" /* PEER_NOT_YOURS */)) throw next;
|
|
36137
|
+
err = next;
|
|
36138
|
+
}
|
|
36139
|
+
}
|
|
36140
|
+
}
|
|
36067
36141
|
function registerRegisterPeerTool(server) {
|
|
36068
36142
|
server.registerTool(
|
|
36069
36143
|
"cogent_register_peer",
|
|
@@ -36125,6 +36199,17 @@ function registerRegisterPeerTool(server) {
|
|
|
36125
36199
|
}
|
|
36126
36200
|
}
|
|
36127
36201
|
if (!cloudSessionId) {
|
|
36202
|
+
const stored = await loadCredentials();
|
|
36203
|
+
if (stored?.sessionId) {
|
|
36204
|
+
const channel = stored.label ? `'${stored.label}' (${stored.sessionId})` : `'${stored.sessionId}'`;
|
|
36205
|
+
return errorResult(
|
|
36206
|
+
new BridgeError(
|
|
36207
|
+
"JOIN_NOT_CONFIRMED" /* JOIN_NOT_CONFIRMED */,
|
|
36208
|
+
`This working directory is already on channel ${channel} at ${stored.endpoint}, but this bridge is not online on it, so cogent_register_peer will NOT auto-create a new channel (that would silently move you off your channel while reporting success)`,
|
|
36209
|
+
`Call cogent_join_session for channel ${channel} (name + secret, + Org_ID for Team channels) and confirm it returns success, then retry cogent_register_peer with the SAME peerId. If that join reports the channel no longer exists, or you do not know its secret, ask the user which channel this agent belongs on and join it (cogent_join_session) or create it (cogent_create_session) explicitly, then register with the SAME peerId \u2014 a channel is never auto-created over a stored one`
|
|
36210
|
+
)
|
|
36211
|
+
);
|
|
36212
|
+
}
|
|
36128
36213
|
autoSecret = crypto10.randomBytes(16).toString("hex");
|
|
36129
36214
|
const resp = await fetch(
|
|
36130
36215
|
`${endpoint}/api/sessions`,
|
|
@@ -36260,6 +36345,24 @@ function registerRegisterPeerTool(server) {
|
|
|
36260
36345
|
logger.debug(`register_peer: ownership recovery failed: ${recoverErr.message}`);
|
|
36261
36346
|
}
|
|
36262
36347
|
}
|
|
36348
|
+
if (!recovered && regErr instanceof BridgeError && regErr.code === "PEER_NOT_YOURS" /* PEER_NOT_YOURS */) {
|
|
36349
|
+
recovered = await retrySamePeerId(
|
|
36350
|
+
regErr,
|
|
36351
|
+
() => backend.registerPeer(
|
|
36352
|
+
peerId,
|
|
36353
|
+
sessionId,
|
|
36354
|
+
cwd,
|
|
36355
|
+
label,
|
|
36356
|
+
CLIENT_VERSION,
|
|
36357
|
+
mode,
|
|
36358
|
+
channelSessionId,
|
|
36359
|
+
capabilities,
|
|
36360
|
+
workspaceId,
|
|
36361
|
+
threadId,
|
|
36362
|
+
storedPeerSecret
|
|
36363
|
+
)
|
|
36364
|
+
);
|
|
36365
|
+
}
|
|
36263
36366
|
if (!recovered) throw regErr;
|
|
36264
36367
|
peer = recovered;
|
|
36265
36368
|
}
|
package/commands/register.md
CHANGED
|
@@ -79,6 +79,8 @@ Call `cogent_register_peer` with:
|
|
|
79
79
|
- `label`: `CHANNEL_PEER_LABEL`, or derive from peerId (capitalize, replace hyphens with spaces)
|
|
80
80
|
- `mode`: `"agent"` (default — auto-replies to directed messages). Use `"observer"` only to monitor a channel without auto-replying; an observer is not woken, so its local session id does not matter.
|
|
81
81
|
|
|
82
|
+
> 🔴 **HARD RULE — never change your peerId.** Register under the EXACT peer name you were given — never a suffixed, "-recovery", "-2" or otherwise invented variant: your peers address you by that name, and a renamed peer silently stops receiving their messages. If `cogent_register_peer` fails with **`PEER_NOT_YOURS`**, the name is almost always held by your OWN earlier registration: retry the **SAME** `peerId` after the `retryAfterMs` / time the error states (or stop the other session using it). If it returns **`JOIN_NOT_CONFIRMED`**, re-join the channel it names — do not create a new one. Report the failure to the user rather than picking another name.
|
|
83
|
+
|
|
82
84
|
### Step 4: Confirm
|
|
83
85
|
|
|
84
86
|
Call `cogent_list_peers` to show who is online.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@essentialai/cogent-plugin",
|
|
3
|
-
"version": "3.25.
|
|
3
|
+
"version": "3.25.6",
|
|
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",
|
|
@@ -65,6 +65,8 @@ Call `cogent_register_peer` with:
|
|
|
65
65
|
- `cwd`: the absolute working directory path (output of `pwd`)
|
|
66
66
|
- `label`: the chosen label
|
|
67
67
|
|
|
68
|
+
> 🔴 **HARD RULE — never change your peerId.** Register under the EXACT peer name you were given — never a suffixed, "-recovery", "-2" or otherwise invented variant: your peers address you by that name, and a renamed peer silently stops receiving their messages. If `cogent_register_peer` fails with **`PEER_NOT_YOURS`**, the name is almost always held by your OWN earlier registration: retry the **SAME** `peerId` after the `retryAfterMs` / time the error states (or stop the other session using it). If it returns **`JOIN_NOT_CONFIRMED`**, re-join the channel it names — do not create a new one. Report the failure to the user rather than picking another name.
|
|
69
|
+
|
|
68
70
|
## Step 4: Confirm Registration
|
|
69
71
|
|
|
70
72
|
After successful registration, inform the user:
|