@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.5",
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",
@@ -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.5") {
26400
- return "3.25.5";
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
- throw new BridgeError(
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
  }
@@ -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.5",
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: