@essentialai/cogent-plugin 3.25.4 → 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
|
}
|
|
@@ -35510,12 +35533,14 @@ var init_claude_preflight = __esm({
|
|
|
35510
35533
|
var startup_exports = {};
|
|
35511
35534
|
__export(startup_exports, {
|
|
35512
35535
|
autoRelay: () => autoRelay,
|
|
35536
|
+
cancelPresenceRetry: () => cancelPresenceRetry,
|
|
35513
35537
|
cloudHttpClient: () => cloudHttpClient,
|
|
35514
35538
|
cloudInbox: () => cloudInbox,
|
|
35515
35539
|
cloudWsClient: () => cloudWsClient,
|
|
35516
35540
|
hasPendingLabelResolve: () => hasPendingLabelResolve,
|
|
35517
35541
|
isCloudMode: () => isCloudMode2,
|
|
35518
35542
|
presenceRegisterArgs: () => presenceRegisterArgs,
|
|
35543
|
+
presenceRetryPending: () => presenceRetryPending,
|
|
35519
35544
|
reinitCloudBackend: () => reinitCloudBackend,
|
|
35520
35545
|
resolvePendingLabel: () => resolvePendingLabel,
|
|
35521
35546
|
restorePeerPresence: () => restorePeerPresence,
|
|
@@ -35668,7 +35693,29 @@ function presenceRegisterArgs(p) {
|
|
|
35668
35693
|
// AUD-003 ownership proof — reclaims a peer owned by an earlier token
|
|
35669
35694
|
];
|
|
35670
35695
|
}
|
|
35671
|
-
|
|
35696
|
+
function presenceRetryPending() {
|
|
35697
|
+
return presenceRetry !== null;
|
|
35698
|
+
}
|
|
35699
|
+
function cancelPresenceRetry() {
|
|
35700
|
+
if (presenceRetry) clearTimeout(presenceRetry);
|
|
35701
|
+
presenceRetry = null;
|
|
35702
|
+
}
|
|
35703
|
+
function presenceRetryDelayMs(attempt) {
|
|
35704
|
+
const base = Number(process.env.COGENT_PRESENCE_RETRY_MS);
|
|
35705
|
+
return Math.min((Number.isFinite(base) && base > 0 ? base : 3e4) * 2 ** attempt, 3e5);
|
|
35706
|
+
}
|
|
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;
|
|
35672
35719
|
const outcome = await restorePresence({
|
|
35673
35720
|
// The endpoint being a relay URL is what makes presence meaningful. We deliberately do
|
|
35674
35721
|
// NOT gate on the active backend being the cloud one: at startup it is still the
|
|
@@ -35689,16 +35736,37 @@ async function restorePeerPresence() {
|
|
|
35689
35736
|
loadCredentials: () => loadCredentials(),
|
|
35690
35737
|
cwd: () => process.cwd(),
|
|
35691
35738
|
goOnline: async ({ peerId, endpoint, sessionId, token, cwd, label, peerSecret }) => {
|
|
35739
|
+
if (!stillCurrent()) throw new Error(PRESENCE_SUPERSEDED);
|
|
35692
35740
|
await reinitCloudBackend(endpoint, sessionId, token);
|
|
35693
35741
|
const backend = getBackend();
|
|
35694
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
|
+
}
|
|
35695
35747
|
autoRelay.setLocalPeer(peerId, sessionId, cwd, "agent", label, sessionId);
|
|
35696
35748
|
if (cloudWsClient) cloudWsClient.setPeerId(peerId);
|
|
35697
35749
|
heartbeat.restart();
|
|
35750
|
+
markJoinedInProcess();
|
|
35698
35751
|
}
|
|
35699
35752
|
});
|
|
35753
|
+
if (!stillCurrent()) {
|
|
35754
|
+
logPresence(superseded);
|
|
35755
|
+
return superseded;
|
|
35756
|
+
}
|
|
35700
35757
|
setPresence(outcome);
|
|
35701
35758
|
logPresence(outcome);
|
|
35759
|
+
if (outcome.state === "offline" && outcome.detail.startsWith("presence restore failed")) {
|
|
35760
|
+
const delay = presenceRetryDelayMs(attempt);
|
|
35761
|
+
cancelPresenceRetry();
|
|
35762
|
+
presenceRetry = setTimeout(() => {
|
|
35763
|
+
presenceRetry = null;
|
|
35764
|
+
void restorePeerPresence(attempt + 1).catch(() => {
|
|
35765
|
+
});
|
|
35766
|
+
}, delay);
|
|
35767
|
+
presenceRetry.unref?.();
|
|
35768
|
+
logger.warn(`presence: restore failed \u2014 retrying in ${Math.round(delay / 1e3)}s`);
|
|
35769
|
+
}
|
|
35702
35770
|
return outcome;
|
|
35703
35771
|
}
|
|
35704
35772
|
function createCloudWsClient(endpoint, sessionId, token, http, inbox, pollIntervalMs) {
|
|
@@ -35938,7 +36006,7 @@ async function runPreflights() {
|
|
|
35938
36006
|
await preflightClaude(config2.COGENT_CLAUDE_PATH);
|
|
35939
36007
|
}
|
|
35940
36008
|
}
|
|
35941
|
-
var UUID_V4_RE, execFileAsync3, PERSIST_PATH, legacyWarnEmitted, cloudWsClient, cloudInbox, cloudHttpClient, cloudModeActive, inboxNotifier, notifierBoundTo, reRegistrationInProgress, pendingLabelResolve;
|
|
36009
|
+
var UUID_V4_RE, execFileAsync3, PERSIST_PATH, legacyWarnEmitted, cloudWsClient, cloudInbox, cloudHttpClient, cloudModeActive, inboxNotifier, notifierBoundTo, reRegistrationInProgress, presenceRetry, presenceSupersedeBound, PRESENCE_SUPERSEDED, pendingLabelResolve;
|
|
35942
36010
|
var init_startup = __esm({
|
|
35943
36011
|
"src/startup.ts"() {
|
|
35944
36012
|
"use strict";
|
|
@@ -35971,6 +36039,9 @@ var init_startup = __esm({
|
|
|
35971
36039
|
inboxNotifier = null;
|
|
35972
36040
|
notifierBoundTo = null;
|
|
35973
36041
|
reRegistrationInProgress = false;
|
|
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";
|
|
35974
36045
|
pendingLabelResolve = null;
|
|
35975
36046
|
}
|
|
35976
36047
|
});
|
|
@@ -36043,6 +36114,30 @@ function unresolvedSessionReason(r) {
|
|
|
36043
36114
|
if (r.sessionId) return null;
|
|
36044
36115
|
return r.candidateCount === 0 ? "no-candidates" : "ambiguous";
|
|
36045
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
|
+
}
|
|
36046
36141
|
function registerRegisterPeerTool(server) {
|
|
36047
36142
|
server.registerTool(
|
|
36048
36143
|
"cogent_register_peer",
|
|
@@ -36104,6 +36199,17 @@ function registerRegisterPeerTool(server) {
|
|
|
36104
36199
|
}
|
|
36105
36200
|
}
|
|
36106
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
|
+
}
|
|
36107
36213
|
autoSecret = crypto10.randomBytes(16).toString("hex");
|
|
36108
36214
|
const resp = await fetch(
|
|
36109
36215
|
`${endpoint}/api/sessions`,
|
|
@@ -36239,6 +36345,24 @@ function registerRegisterPeerTool(server) {
|
|
|
36239
36345
|
logger.debug(`register_peer: ownership recovery failed: ${recoverErr.message}`);
|
|
36240
36346
|
}
|
|
36241
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
|
+
}
|
|
36242
36366
|
if (!recovered) throw regErr;
|
|
36243
36367
|
peer = recovered;
|
|
36244
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:
|