@essentialai/cogent-bridge-bundled 3.21.8 → 3.22.0

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.
Files changed (2) hide show
  1. package/cogent-bridge.mjs +221 -65
  2. package/package.json +1 -1
package/cogent-bridge.mjs CHANGED
@@ -26389,8 +26389,8 @@ var init_stdio2 = __esm({
26389
26389
  // src/constants.ts
26390
26390
  import { createRequire } from "node:module";
26391
26391
  function resolveVersion() {
26392
- if ("3.21.8") {
26393
- return "3.21.8";
26392
+ if ("3.22.0") {
26393
+ return "3.22.0";
26394
26394
  }
26395
26395
  try {
26396
26396
  const require2 = createRequire(import.meta.url);
@@ -26499,8 +26499,26 @@ function isNewer(current, latest) {
26499
26499
  return false;
26500
26500
  }
26501
26501
  function setLastNudge(nudge) {
26502
+ if (nudge !== lastNudge) shownNudge = null;
26502
26503
  lastNudge = nudge;
26503
26504
  }
26505
+ function consumeNudgeOnce() {
26506
+ if (!lastNudge || lastNudge === shownNudge) return null;
26507
+ shownNudge = lastNudge;
26508
+ return lastNudge;
26509
+ }
26510
+ function startUpdateCheckLoop(current, opts = {}) {
26511
+ const { intervalMs = 6 * 60 * 60 * 1e3, ...checkOpts } = opts;
26512
+ const run = () => {
26513
+ void checkForUpdate(current, checkOpts).then((nudge) => {
26514
+ if (nudge) setLastNudge(nudge);
26515
+ });
26516
+ };
26517
+ run();
26518
+ const timer = setInterval(run, intervalMs);
26519
+ timer.unref?.();
26520
+ return () => clearInterval(timer);
26521
+ }
26504
26522
  function getLastNudge() {
26505
26523
  return lastNudge;
26506
26524
  }
@@ -26524,13 +26542,14 @@ async function checkForUpdate(current, opts = {}) {
26524
26542
  clearTimeout(timer);
26525
26543
  }
26526
26544
  }
26527
- var NPM_LATEST_URL, lastNudge;
26545
+ var NPM_LATEST_URL, lastNudge, shownNudge;
26528
26546
  var init_npm_update_check = __esm({
26529
26547
  "src/backend/npm-update-check.ts"() {
26530
26548
  "use strict";
26531
26549
  init_logger();
26532
26550
  NPM_LATEST_URL = "https://registry.npmjs.org/@essentialai/cogent-bridge/latest";
26533
26551
  lastNudge = null;
26552
+ shownNudge = null;
26534
26553
  }
26535
26554
  });
26536
26555
 
@@ -27728,8 +27747,13 @@ async function getRelayVersion(http, nowFn = Date.now) {
27728
27747
  if (cached2 !== null && now - cached2.fetchedAt < TTL_MS) {
27729
27748
  return cached2.version;
27730
27749
  }
27750
+ return (await _refresh(http, now))?.version ?? null;
27751
+ }
27752
+ async function _refresh(http, now) {
27731
27753
  try {
27732
- const resp = await http.get("/api/health");
27754
+ const resp = await http.get(
27755
+ "/api/health"
27756
+ );
27733
27757
  const version2 = resp?.version;
27734
27758
  if (typeof version2 !== "string" || version2.length === 0) {
27735
27759
  logger.debug(
@@ -27737,14 +27761,26 @@ async function getRelayVersion(http, nowFn = Date.now) {
27737
27761
  );
27738
27762
  return null;
27739
27763
  }
27740
- cached2 = { version: version2, fetchedAt: now };
27741
- return version2;
27764
+ const raw = resp?.capabilities;
27765
+ const capabilities = Array.isArray(raw) ? raw.filter((c2) => typeof c2 === "string") : [];
27766
+ cached2 = { version: version2, capabilities, fetchedAt: now };
27767
+ return cached2;
27742
27768
  } catch (err) {
27743
27769
  const msg = err instanceof Error ? err.message : String(err);
27744
27770
  logger.debug(`relay-version-cache: /api/health fetch failed: ${msg}`);
27745
27771
  return null;
27746
27772
  }
27747
27773
  }
27774
+ async function getRelayCapabilities(http, nowFn = Date.now) {
27775
+ const now = nowFn();
27776
+ if (cached2 !== null && now - cached2.fetchedAt < TTL_MS) {
27777
+ return cached2.capabilities;
27778
+ }
27779
+ return (await _refresh(http, now))?.capabilities ?? null;
27780
+ }
27781
+ async function relaySupports(http, capability, nowFn = Date.now) {
27782
+ return (await getRelayCapabilities(http, nowFn))?.includes(capability) ?? false;
27783
+ }
27748
27784
  var TTL_MS, cached2;
27749
27785
  var init_relay_version_cache = __esm({
27750
27786
  "src/backend/relay-version-cache.ts"() {
@@ -27760,6 +27796,8 @@ var MAILBOX_DEPROVISIONED_HEADER, PATHS, HttpBackend;
27760
27796
  var init_http_backend = __esm({
27761
27797
  "src/backend/http-backend.ts"() {
27762
27798
  "use strict";
27799
+ init_relay_version_cache();
27800
+ init_errors4();
27763
27801
  init_health_check();
27764
27802
  init_constants();
27765
27803
  init_config();
@@ -27787,7 +27825,7 @@ var init_http_backend = __esm({
27787
27825
  * The sessionId parameter from StorageBackend is the local CC sessionId;
27788
27826
  * in cloud mode we ignore it and use this.sessionId (the cloud session).
27789
27827
  */
27790
- async registerPeer(peerId, _sessionId, cwd, label, clientVersion, mode, _channelSessionId, capabilities, workspaceId, threadId) {
27828
+ async registerPeer(peerId, _sessionId, cwd, label, clientVersion, mode, _channelSessionId, capabilities, workspaceId, threadId, peerSecret) {
27791
27829
  const path20 = PATHS.peers.replace(":sessionId", this.sessionId);
27792
27830
  const body = {
27793
27831
  peerId,
@@ -27816,6 +27854,9 @@ var init_http_backend = __esm({
27816
27854
  body.role = roleCap.slice("role:".length);
27817
27855
  }
27818
27856
  }
27857
+ if (peerSecret !== void 0 && await relaySupports(this.http, "peer-ownership")) {
27858
+ body.peerSecret = peerSecret;
27859
+ }
27819
27860
  return this.http.post(path20, body);
27820
27861
  }
27821
27862
  /**
@@ -27833,8 +27874,11 @@ var init_http_backend = __esm({
27833
27874
  const headers = await this.http.delete(path20, {});
27834
27875
  const mailboxDeprovisioned = headers.get(MAILBOX_DEPROVISIONED_HEADER) === "true";
27835
27876
  return { removed: true, mailboxDeprovisioned };
27836
- } catch {
27837
- return { removed: false, mailboxDeprovisioned: false };
27877
+ } catch (err) {
27878
+ if (err instanceof BridgeError && err.code === "PEER_NOT_FOUND" /* PEER_NOT_FOUND */) {
27879
+ return { removed: false, mailboxDeprovisioned: false };
27880
+ }
27881
+ throw err;
27838
27882
  }
27839
27883
  }
27840
27884
  /**
@@ -27881,6 +27925,17 @@ var init_http_backend = __esm({
27881
27925
  if (record2.isRelayEcho === true) {
27882
27926
  body.isRelayEcho = true;
27883
27927
  }
27928
+ if (await relaySupports(this.http, "message-delivery-fields")) {
27929
+ if (record2.success === false) {
27930
+ body.success = false;
27931
+ }
27932
+ if (record2.error != null) {
27933
+ body.error = record2.error;
27934
+ }
27935
+ if (record2.durationMs != null) {
27936
+ body.durationMs = record2.durationMs;
27937
+ }
27938
+ }
27884
27939
  if (record2.attachments && record2.attachments.length > 0) {
27885
27940
  body.attachments = record2.attachments;
27886
27941
  }
@@ -33238,7 +33293,15 @@ function formatRelayMessage(fromLabel, fromPeerId, message, attachments) {
33238
33293
  ${message}${formatAttachmentsBlock(attachments)}
33239
33294
 
33240
33295
  ---
33241
- Respond directly to the message above. Your entire response is delivered automatically to whoever should receive it \u2014 a broadcast question (to the whole channel) is answered to everyone; a direct message, to the sender. You do NOT need to \u2014 and should NOT \u2014 call cogent_send_message or any bridge tool to deliver it; just answer normally.`;
33296
+ Respond directly to the message above. Your entire response is delivered automatically to whoever should receive it \u2014 a broadcast question (to the whole channel) is answered to everyone; a direct message, to the sender. You do NOT need to \u2014 and should NOT \u2014 call cogent_send_message or any bridge tool to deliver it; just answer normally.` + updateNoticeFooter();
33297
+ }
33298
+ function updateNoticeFooter() {
33299
+ const nudge = consumeNudgeOnce();
33300
+ if (!nudge) return "";
33301
+ return `
33302
+
33303
+ ---
33304
+ [Cogent FYI \u2014 not part of the message above, and not a task for you] ${nudge} You cannot apply this yourself; mention it to your operator when convenient, then carry on. Do not reply about it and do not call any cogent_* tool for it.`;
33242
33305
  }
33243
33306
  function formatInjectOnlyMessage(fromLabel, fromPeerId, message, attachments) {
33244
33307
  return `[Cogent Bridge: Response from ${fromLabel} (${fromPeerId})]
@@ -33323,7 +33386,7 @@ function truncate(s) {
33323
33386
  const flat = s.replace(/\s+/g, " ").trim();
33324
33387
  return flat.length > DIAGNOSTIC_MAX ? `${flat.slice(0, DIAGNOSTIC_MAX - 1)}\u2026` : flat;
33325
33388
  }
33326
- var RELAY_STATUS, STATUS_MARKER_RE, DIAGNOSTIC_MAX, AutoRelayService, autoRelay;
33389
+ var RELAY_STATUS, STATUS_MARKER_RE, DIAGNOSTIC_MAX, EARLY_ACK_TTL_MS, EARLY_ACK_MAX, AutoRelayService, autoRelay;
33327
33390
  var init_auto_relay = __esm({
33328
33391
  "src/services/auto-relay.ts"() {
33329
33392
  "use strict";
@@ -33334,6 +33397,7 @@ var init_auto_relay = __esm({
33334
33397
  init_codex_cli();
33335
33398
  init_wake_inflight();
33336
33399
  init_logger();
33400
+ init_npm_update_check();
33337
33401
  RELAY_STATUS = {
33338
33402
  QUEUED: "queued",
33339
33403
  PROCESSING: "processing",
@@ -33343,6 +33407,8 @@ var init_auto_relay = __esm({
33343
33407
  };
33344
33408
  STATUS_MARKER_RE = /^\[cogent:(queued|processing|delivered|needs-manual|failed)\]/;
33345
33409
  DIAGNOSTIC_MAX = 220;
33410
+ EARLY_ACK_TTL_MS = 12e4;
33411
+ EARLY_ACK_MAX = 256;
33346
33412
  AutoRelayService = class {
33347
33413
  localPeerId = null;
33348
33414
  localSessionId = null;
@@ -33389,7 +33455,24 @@ var init_auto_relay = __esm({
33389
33455
  // so the first send-message tool call returned literal "Superseded by
33390
33456
  // newer send_message call" instead of the actual peer response.
33391
33457
  responseWaiters = /* @__PURE__ */ new Map();
33458
+ /**
33459
+ * Pending delivery-ack waiters, keyed by MESSAGE ID.
33460
+ *
33461
+ * 🔴 AUD-002. This used to be keyed by PEER ID, so a peer could have only one outstanding
33462
+ * waiter and a second concurrent send displaced the first — resolving it, so the first send
33463
+ * reported "delivered" with no acknowledgment from anyone. Delivery acknowledgment is the one
33464
+ * claim this system must never fabricate.
33465
+ */
33392
33466
  deliveryAckWaiters = /* @__PURE__ */ new Map();
33467
+ /**
33468
+ * Acks that arrived before their waiter existed, keyed by message id.
33469
+ *
33470
+ * The recipient acknowledges over WS as soon as it receives the message, which can beat our own
33471
+ * POST response. Waiters used to be registered BEFORE the send to dodge that race, which is what
33472
+ * forced peer-keying. Buffering the early ack instead lets registration happen after the message
33473
+ * id is known, so correlation stays exact. Entries are consumed once and expire.
33474
+ */
33475
+ recentAcks = /* @__PURE__ */ new Map();
33393
33476
  sendDeliveryAckFn = null;
33394
33477
  /**
33395
33478
  * Set the local peer info. Called from register-peer tool after
@@ -33558,12 +33641,6 @@ var init_auto_relay = __esm({
33558
33641
  cancelResponseWaiter(fromPeerId) {
33559
33642
  this._cancelWaiter(fromPeerId);
33560
33643
  }
33561
- /**
33562
- * Cancel only the active delivery ack waiter for a peer.
33563
- */
33564
- cancelDeliveryAckWaiter(fromPeerId) {
33565
- this._cancelDeliveryAckWaiter(fromPeerId);
33566
- }
33567
33644
  /**
33568
33645
  * Cancel ALL pending response waiters for a peer by rejecting them with
33569
33646
  * a clear error. Used by clearExpectation()/cancelResponseWaiter() to
@@ -33586,39 +33663,73 @@ var init_auto_relay = __esm({
33586
33663
  * resolves when the target peer's MCP sends a delivery_ack frame, or
33587
33664
  * rejects on timeout (recipient likely offline).
33588
33665
  */
33589
- waitForDeliveryAck(fromPeerId, timeoutMs) {
33666
+ waitForDeliveryAck(fromPeerId, messageId, timeoutMs) {
33667
+ if (this.recentAcks.delete(messageId)) {
33668
+ logger.debug(`Auto-relay: delivery ack for ${messageId} was already buffered`);
33669
+ return Promise.resolve();
33670
+ }
33590
33671
  return new Promise((resolve, reject) => {
33591
- this._cancelDeliveryAckWaiter(fromPeerId);
33592
33672
  const timer = setTimeout(() => {
33593
- this.deliveryAckWaiters.delete(fromPeerId);
33594
- reject(new Error(`Delivery ack timeout from ${fromPeerId} after ${timeoutMs}ms`));
33673
+ this.deliveryAckWaiters.delete(messageId);
33674
+ reject(
33675
+ new Error(
33676
+ `Delivery ack timeout from ${fromPeerId} for ${messageId} after ${timeoutMs}ms`
33677
+ )
33678
+ );
33595
33679
  }, timeoutMs);
33596
- this.deliveryAckWaiters.set(fromPeerId, { resolve, timer });
33680
+ this.deliveryAckWaiters.set(messageId, { peerId: fromPeerId, resolve, reject, timer });
33597
33681
  });
33598
33682
  }
33599
33683
  /**
33600
- * Resolve a pending delivery ack waiter. Called by startup.ts onDeliveryAck
33601
- * callback when the server routes a delivery_ack frame to us.
33684
+ * Settle the waiter for exactly this message.
33685
+ *
33686
+ * 🔴 Correlation is by MESSAGE ID, and the acknowledging peer must match the peer we sent to.
33687
+ * The frame has carried `messageId` all along (src/cloud/ws-client.ts:52); startup.ts used to
33688
+ * drop it and resolve by peer, so any ack settled any outstanding send to that peer — and an ack
33689
+ * naming a peer the sender never messaged could settle a waiter outright (AUD-003).
33690
+ *
33691
+ * An ack with no matching waiter is BUFFERED rather than discarded: the recipient can legitimately
33692
+ * acknowledge before our POST response returns.
33602
33693
  */
33603
- resolveDeliveryAck(fromPeerId) {
33604
- const waiter = this.deliveryAckWaiters.get(fromPeerId);
33694
+ resolveDeliveryAck(fromPeerId, messageId) {
33695
+ const waiter = this.deliveryAckWaiters.get(messageId);
33605
33696
  if (waiter) {
33697
+ if (waiter.peerId !== fromPeerId) {
33698
+ logger.warn(
33699
+ `Auto-relay: IGNORED delivery ack for ${messageId} \u2014 claimed by '${fromPeerId}' but that message was sent to '${waiter.peerId}'`
33700
+ );
33701
+ return;
33702
+ }
33606
33703
  clearTimeout(waiter.timer);
33607
- this.deliveryAckWaiters.delete(fromPeerId);
33704
+ this.deliveryAckWaiters.delete(messageId);
33608
33705
  waiter.resolve();
33609
- logger.debug(`Auto-relay: delivery ack received from ${fromPeerId}`);
33706
+ logger.debug(`Auto-relay: delivery ack received from ${fromPeerId} for ${messageId}`);
33707
+ return;
33610
33708
  }
33709
+ this._bufferEarlyAck(messageId);
33710
+ }
33711
+ /** Remember an ack whose waiter does not exist yet, with bounded retention. */
33712
+ _bufferEarlyAck(messageId) {
33713
+ const now = Date.now();
33714
+ for (const [id, at] of this.recentAcks) {
33715
+ if (now - at > EARLY_ACK_TTL_MS) this.recentAcks.delete(id);
33716
+ }
33717
+ while (this.recentAcks.size >= EARLY_ACK_MAX) {
33718
+ const oldest = this.recentAcks.keys().next().value;
33719
+ if (oldest === void 0) break;
33720
+ this.recentAcks.delete(oldest);
33721
+ }
33722
+ this.recentAcks.set(messageId, now);
33723
+ logger.debug(`Auto-relay: buffered early delivery ack for ${messageId}`);
33611
33724
  }
33612
- /**
33613
- * Cancel a pending delivery ack waiter for a peer.
33614
- * Resolves the old promise so callers awaiting it don't hang forever.
33615
- */
33616
33725
  _cancelDeliveryAckWaiter(fromPeerId) {
33617
- const existing = this.deliveryAckWaiters.get(fromPeerId);
33618
- if (existing) {
33619
- clearTimeout(existing.timer);
33620
- this.deliveryAckWaiters.delete(fromPeerId);
33621
- existing.resolve();
33726
+ for (const [messageId, waiter] of this.deliveryAckWaiters) {
33727
+ if (waiter.peerId !== fromPeerId) continue;
33728
+ clearTimeout(waiter.timer);
33729
+ this.deliveryAckWaiters.delete(messageId);
33730
+ waiter.reject(
33731
+ new Error(`Delivery ack wait for ${messageId} cancelled (${fromPeerId})`)
33732
+ );
33622
33733
  }
33623
33734
  }
33624
33735
  /**
@@ -34121,15 +34232,18 @@ var init_auto_relay = __esm({
34121
34232
  for (const [, queue] of this.responseWaiters) {
34122
34233
  for (const waiter of queue) {
34123
34234
  clearTimeout(waiter.timer);
34124
- waiter.resolve("");
34235
+ waiter.reject(new Error("Auto-relay shut down while waiting for a response"));
34125
34236
  }
34126
34237
  }
34127
34238
  this.responseWaiters.clear();
34128
- for (const [, waiter] of this.deliveryAckWaiters) {
34239
+ for (const [messageId, waiter] of this.deliveryAckWaiters) {
34129
34240
  clearTimeout(waiter.timer);
34130
- waiter.resolve();
34241
+ waiter.reject(
34242
+ new Error(`Auto-relay shut down while waiting for delivery ack (${messageId})`)
34243
+ );
34131
34244
  }
34132
34245
  this.deliveryAckWaiters.clear();
34246
+ this.recentAcks.clear();
34133
34247
  this.queue = [];
34134
34248
  this.awarenessOnly.clear();
34135
34249
  this.localPeerId = null;
@@ -34552,7 +34666,7 @@ function createCloudWsClient(endpoint, sessionId, token, http, inbox, pollInterv
34552
34666
  logger.warn(`WebSocket error frame: ${err.code}: ${err.message}`);
34553
34667
  },
34554
34668
  onDeliveryAck: (payload) => {
34555
- autoRelay.resolveDeliveryAck(payload.peerId);
34669
+ autoRelay.resolveDeliveryAck(payload.peerId, payload.messageId);
34556
34670
  },
34557
34671
  onPeerEvicted: () => {
34558
34672
  triggerReRegistration().catch((err) => {
@@ -34844,6 +34958,10 @@ var init_mail_credential_store = __esm({
34844
34958
 
34845
34959
  // src/tools/register-peer.ts
34846
34960
  import crypto6 from "node:crypto";
34961
+ function unresolvedSessionReason(r) {
34962
+ if (r.sessionId) return null;
34963
+ return r.candidateCount === 0 ? "no-candidates" : "ambiguous";
34964
+ }
34847
34965
  function registerRegisterPeerTool(server) {
34848
34966
  server.registerTool(
34849
34967
  "cogent_register_peer",
@@ -34855,6 +34973,9 @@ function registerRegisterPeerTool(server) {
34855
34973
  sessionId: import_zod3.z.string().describe("Claude Code session ID (used with --resume)"),
34856
34974
  cwd: import_zod3.z.string().describe("Absolute path to the project working directory"),
34857
34975
  label: import_zod3.z.string().describe("Human-readable label, e.g. 'Cogent_Backend' or 'Cogent_Frontend'"),
34976
+ peerSecret: import_zod3.z.string().optional().describe(
34977
+ "AUD-003 ownership proof. Only needed to RECLAIM a peerId whose registration was made from a different connection \u2014 e.g. this agent re-joined the channel and was issued a new token. Normally omitted: the stored credential is replayed automatically."
34978
+ ),
34858
34979
  mode: import_zod3.z.enum(["observer", "agent"]).optional().default("agent").describe(
34859
34980
  'Peer participation mode. "agent" (default) auto-responds via execClaude on inbound directed messages. "observer" registers a passive watcher that receives WS frames and remains targetable but never spawns a subprocess. Useful for operators using their MCP client to monitor a channel without polluting transcript with phantom replies.'
34860
34981
  ),
@@ -34869,7 +34990,7 @@ function registerRegisterPeerTool(server) {
34869
34990
  openWorldHint: false
34870
34991
  }
34871
34992
  },
34872
- async ({ peerId, sessionId, cwd, label, mode, role }) => {
34993
+ async ({ peerId, sessionId, cwd, label, mode, role, peerSecret: argPeerSecret }) => {
34873
34994
  try {
34874
34995
  const config2 = getConfig();
34875
34996
  const hasExplicitCloudSession = !!config2.COGENT_SESSION_ID || process.env.COGENT_TRUST_PERSISTED_CREDENTIALS === "1";
@@ -34962,6 +35083,14 @@ function registerRegisterPeerTool(server) {
34962
35083
  }
34963
35084
  const workspaceId = deriveWorkspaceId(cwd);
34964
35085
  const threadId = sessionId;
35086
+ let storedPeerSecret = argPeerSecret;
35087
+ if (storedPeerSecret === void 0 && isCloudMode()) {
35088
+ try {
35089
+ storedPeerSecret = (await loadCredentials())?.peerSecret;
35090
+ } catch {
35091
+ storedPeerSecret = void 0;
35092
+ }
35093
+ }
34965
35094
  const peer = await backend.registerPeer(
34966
35095
  peerId,
34967
35096
  sessionId,
@@ -34972,15 +35101,18 @@ function registerRegisterPeerTool(server) {
34972
35101
  channelSessionId,
34973
35102
  capabilities,
34974
35103
  workspaceId,
34975
- threadId
35104
+ threadId,
35105
+ storedPeerSecret
34976
35106
  );
34977
35107
  if (isCloudMode()) {
34978
35108
  try {
34979
35109
  const creds = await loadCredentials();
34980
- if (creds && creds.peerId !== peerId) {
35110
+ const issuedSecret = peer?.peerSecret;
35111
+ if (creds && (creds.peerId !== peerId || issuedSecret && creds.peerSecret !== issuedSecret)) {
34981
35112
  await saveCredentials({
34982
35113
  ...creds,
34983
35114
  peerId,
35115
+ ...issuedSecret ? { peerSecret: issuedSecret } : {},
34984
35116
  savedAt: (/* @__PURE__ */ new Date()).toISOString()
34985
35117
  });
34986
35118
  }
@@ -35008,16 +35140,17 @@ function registerRegisterPeerTool(server) {
35008
35140
  logger.info(
35009
35141
  `Register: session resolved ${resolution.decision}/${resolution.confidence} (${resolution.candidateCount} candidate[s]) -> ${localSessionId}`
35010
35142
  );
35011
- const noLocalSession = resolution.candidateCount === 0;
35012
- if (noLocalSession) {
35143
+ const unresolved = unresolvedSessionReason(resolution);
35144
+ if (unresolved) {
35013
35145
  try {
35014
35146
  const base = getConfig().COGENT_PLATFORM === "cc" ? await transcriptBaseSummary() : "(codex: scans CODEX_HOME/sessions)";
35147
+ const cause = unresolved === "ambiguous" ? `${resolution.candidateCount} candidate sessions matched and NONE could be chosen, so the id you supplied was pinned UNCHECKED \u2014 if that is a channel UUID rather than a local session id, every wake will fail in ~2s` : `NO local ${getConfig().COGENT_PLATFORM} session was found for this cwd`;
35015
35148
  logger.warn(
35016
- `Register: NO local ${getConfig().COGENT_PLATFORM} session found for cwd \u2014 auto-wake WILL be refused. cwd=${cwd}; scanned ${(resolution.probedPaths ?? []).join(", ") || "(unknown)"}; base ${base}. If the base is missing or empty, this agent's config dir is elsewhere \u2014 set CLAUDE_CONFIG_DIR / CODEX_HOME so the BRIDGE process sees it too. If the base has project dirs but none match, the registered cwd is not where this agent runs.`
35149
+ `Register: ${cause} \u2014 auto-wake WILL be refused. cwd=${cwd}; scanned ${(resolution.probedPaths ?? []).join(", ") || "(unknown)"}; base ${base}. If the base is missing or empty, this agent's config dir is elsewhere \u2014 set CLAUDE_CONFIG_DIR / CODEX_HOME so the BRIDGE process sees it too. If the base has project dirs but none match, the registered cwd is not where this agent runs.`
35017
35150
  );
35018
35151
  } catch (err) {
35019
35152
  logger.warn(
35020
- `Register: NO local session found for cwd=${cwd} (auto-wake WILL be refused); could not describe the transcript base: ${err.message}`
35153
+ `Register: unresolved session (${unresolved}) for cwd=${cwd} (auto-wake WILL be refused); could not describe the transcript base: ${err.message}`
35021
35154
  );
35022
35155
  }
35023
35156
  }
@@ -35031,8 +35164,8 @@ function registerRegisterPeerTool(server) {
35031
35164
  confidence: resolution.confidence,
35032
35165
  candidateCount: resolution.candidateCount,
35033
35166
  // Only on the broken path — keeps the happy-path result terse.
35034
- ...noLocalSession ? {
35035
- warning: "No local session transcript found for this cwd \u2014 auto-wake will be REFUSED. Verify the cwd is where this agent actually runs and that CLAUDE_CONFIG_DIR / CODEX_HOME are visible to the bridge process.",
35167
+ ...unresolved ? {
35168
+ warning: (unresolved === "ambiguous" ? `${resolution.candidateCount} local session transcripts matched this cwd and none could be chosen, so the sessionId you supplied was pinned UNCHECKED \u2014 if it is a channel UUID rather than a local session id, auto-wake will be REFUSED. ` : "No local session transcript found for this cwd \u2014 auto-wake will be REFUSED. ") + "Verify the cwd is where this agent actually runs and that CLAUDE_CONFIG_DIR / CODEX_HOME are visible to the bridge process.",
35036
35169
  scannedPaths: resolution.probedPaths ?? []
35037
35170
  } : {}
35038
35171
  }
@@ -35248,8 +35381,9 @@ function registerSendMessageTool(server) {
35248
35381
  if (isCloud && success2) {
35249
35382
  const effectiveAckTimeout = Math.min(ACK_TIMEOUT_MS, config2.COGENT_TIMEOUT_MS);
35250
35383
  const fastPathMs = Math.min(config2.COGENT_FASTPATH_MS, config2.COGENT_TIMEOUT_MS);
35251
- const ackPromise = autoRelay.waitForDeliveryAck(toPeerId, effectiveAckTimeout);
35252
35384
  const responsePromise = autoRelay.waitForResponse(toPeerId, fastPathMs);
35385
+ responsePromise.catch(() => {
35386
+ });
35253
35387
  let outboundRecord;
35254
35388
  try {
35255
35389
  outboundRecord = await backend.recordMessage({
@@ -35265,6 +35399,11 @@ function registerSendMessageTool(server) {
35265
35399
  autoRelay.clearExpectation(toPeerId);
35266
35400
  throw recordErr;
35267
35401
  }
35402
+ const ackPromise = autoRelay.waitForDeliveryAck(
35403
+ toPeerId,
35404
+ outboundRecord.id,
35405
+ effectiveAckTimeout
35406
+ );
35268
35407
  let ackReceived = false;
35269
35408
  try {
35270
35409
  await ackPromise;
@@ -35445,6 +35584,7 @@ var init_message = __esm({
35445
35584
  timestamp: import_zod6.z.string().datetime().describe("ISO 8601 message timestamp"),
35446
35585
  durationMs: import_zod6.z.number().nullable().describe("Round-trip duration in ms"),
35447
35586
  success: import_zod6.z.boolean().describe("Whether delivery succeeded"),
35587
+ pending: import_zod6.z.boolean().optional().describe("Delivered+acked; the reply is still expected"),
35448
35588
  error: import_zod6.z.string().nullable().describe("Error message if delivery failed"),
35449
35589
  originPlatform: import_zod6.z.enum(["cc", "codex", "slack", "gchat", "web", "gemini", "whatsapp", "telegram", "discord"]).optional().describe("Platform that originated this message"),
35450
35590
  attachments: import_zod6.z.array(AttachmentSchema).optional().describe("Cogent Mail M2.5 \u2014 file references (byte transport = email)")
@@ -35534,15 +35674,31 @@ var init_errors5 = __esm({
35534
35674
  });
35535
35675
 
35536
35676
  // cogent/dist/schemas/api-requests.js
35537
- var import_zod10, CreateSessionRequestSchema, JoinSessionRequestSchema, ResolveSessionRequestSchema, RegisterPeerRequestSchema, DeregisterPeerRequestSchema, SendMessageRequestSchema, GetHistoryQuerySchema, PollQuerySchema;
35677
+ var import_zod10, FIELD_LIMITS, CreateSessionRequestSchema, JoinSessionRequestSchema, ResolveSessionRequestSchema, RegisterPeerRequestSchema, DeregisterPeerRequestSchema, SendMessageRequestSchema, GetHistoryQuerySchema, PollQuerySchema;
35538
35678
  var init_api_requests = __esm({
35539
35679
  "cogent/dist/schemas/api-requests.js"() {
35540
35680
  "use strict";
35541
35681
  import_zod10 = __toESM(require_zod(), 1);
35682
+ FIELD_LIMITS = {
35683
+ /** A shared channel secret. Long passphrases are fine; novels are not. */
35684
+ SECRET_MAX: 1024,
35685
+ /** Org_ID. */
35686
+ ORG_ID_MAX: 256,
35687
+ /** Peer identifier. */
35688
+ PEER_ID_MAX: 128,
35689
+ /** Human-readable peer label. */
35690
+ PEER_LABEL_MAX: 256,
35691
+ /** Working directory — comfortably above PATH_MAX (4096) on every platform we support. */
35692
+ CWD_MAX: 8192,
35693
+ /** A single message body. */
35694
+ MESSAGE_MAX: 1048576,
35695
+ /** One platform-identity value (e.g. a Slack user id). */
35696
+ PLATFORM_IDENTITY_VALUE_MAX: 512
35697
+ };
35542
35698
  CreateSessionRequestSchema = import_zod10.z.object({
35543
35699
  label: import_zod10.z.string().regex(/^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$/).optional().describe("Optional human-readable session label (3-32 lowercase alphanumeric + hyphens)"),
35544
- secret: import_zod10.z.string().min(8).describe("Shared secret for joining the session (min 8 chars)"),
35545
- orgId: import_zod10.z.string().min(1).optional().describe("Optional Org_ID -- gates the channel to Business Edition orgs (multi-tenant relay only)")
35700
+ secret: import_zod10.z.string().min(8).max(FIELD_LIMITS.SECRET_MAX).describe("Shared secret for joining the session (min 8 chars)"),
35701
+ orgId: import_zod10.z.string().min(1).max(FIELD_LIMITS.ORG_ID_MAX).optional().describe("Optional Org_ID -- gates the channel to Business Edition orgs (multi-tenant relay only)")
35546
35702
  }).strict();
35547
35703
  JoinSessionRequestSchema = import_zod10.z.object({
35548
35704
  secret: import_zod10.z.string().min(1).describe("Shared secret for the session"),
@@ -35553,13 +35709,14 @@ var init_api_requests = __esm({
35553
35709
  orgId: import_zod10.z.string().min(1).optional().describe("Optional Org_ID -- required to resolve an Org_ID-gated business channel")
35554
35710
  }).strict();
35555
35711
  RegisterPeerRequestSchema = import_zod10.z.object({
35556
- peerId: import_zod10.z.string().min(1).describe("Unique peer identifier"),
35557
- cwd: import_zod10.z.string().describe("Peer working directory path"),
35558
- label: import_zod10.z.string().describe("Human-readable peer label"),
35712
+ peerId: import_zod10.z.string().min(1).max(FIELD_LIMITS.PEER_ID_MAX).describe("Unique peer identifier"),
35713
+ peerSecret: import_zod10.z.string().min(1).max(256).optional().describe("AUD-003: proof of ownership when reclaiming a peer from a different token"),
35714
+ cwd: import_zod10.z.string().max(FIELD_LIMITS.CWD_MAX).describe("Peer working directory path"),
35715
+ label: import_zod10.z.string().max(FIELD_LIMITS.PEER_LABEL_MAX).describe("Human-readable peer label"),
35559
35716
  platform: import_zod10.z.enum(["cc", "codex", "slack", "gchat", "web", "gemini"]).optional().describe("Platform this peer belongs to"),
35560
35717
  type: import_zod10.z.enum(["agent", "human"]).optional().describe("Peer type"),
35561
35718
  transport: import_zod10.z.enum(["ws", "slack-bot", "gchat-bot", "websocket"]).optional().describe("Transport mechanism"),
35562
- platformIdentity: import_zod10.z.record(import_zod10.z.string(), import_zod10.z.string()).optional().describe("Platform-specific identity fields"),
35719
+ platformIdentity: import_zod10.z.record(import_zod10.z.string().max(64), import_zod10.z.string().max(FIELD_LIMITS.PLATFORM_IDENTITY_VALUE_MAX)).optional().describe("Platform-specific identity fields"),
35563
35720
  role: import_zod10.z.string().max(64).optional().describe("A2A role advertised by this peer (Agent Card)"),
35564
35721
  capabilities: import_zod10.z.array(import_zod10.z.string().max(64)).max(16).optional().describe("Capability strings \u2192 A2A Agent Card skills[]")
35565
35722
  }).strict();
@@ -35567,8 +35724,11 @@ var init_api_requests = __esm({
35567
35724
  SendMessageRequestSchema = import_zod10.z.object({
35568
35725
  fromPeerId: import_zod10.z.string().min(1).describe("Sender peer ID"),
35569
35726
  toPeerId: import_zod10.z.string().min(1).describe("Recipient peer ID (use '*' for broadcast)"),
35570
- message: import_zod10.z.string().min(1).describe("Message content"),
35571
- originPlatform: import_zod10.z.enum(["cc", "codex", "slack", "gchat", "web", "gemini"]).optional().describe("Platform originating this message")
35727
+ message: import_zod10.z.string().min(1).max(FIELD_LIMITS.MESSAGE_MAX).describe("Message content"),
35728
+ originPlatform: import_zod10.z.enum(["cc", "codex", "slack", "gchat", "web", "gemini"]).optional().describe("Platform originating this message"),
35729
+ success: import_zod10.z.boolean().optional().describe("Delivery outcome as reported by the sender"),
35730
+ error: import_zod10.z.string().nullable().optional().describe("Failure code as reported by the sender"),
35731
+ durationMs: import_zod10.z.number().int().nonnegative().nullable().optional().describe("Sender-measured duration")
35572
35732
  }).strict();
35573
35733
  GetHistoryQuerySchema = import_zod10.z.object({
35574
35734
  peerId: import_zod10.z.string().optional().describe("Filter messages by peer ID"),
@@ -37023,11 +37183,7 @@ async function main() {
37023
37183
  `Preflight error (non-fatal): ${err instanceof Error ? err.message : String(err)}`
37024
37184
  );
37025
37185
  });
37026
- void checkForUpdate(SERVER_VERSION).then((nudge) => {
37027
- setLastNudge(nudge);
37028
- if (nudge) process.stderr.write(`${nudge}
37029
- `);
37030
- });
37186
+ startUpdateCheckLoop(SERVER_VERSION);
37031
37187
  logger.info(
37032
37188
  "Server ready. Tools: cogent_register_peer, cogent_deregister_peer, cogent_send_message, cogent_list_peers, cogent_get_history, cogent_health_check, cogent_create_session, cogent_join_session"
37033
37189
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@essentialai/cogent-bridge-bundled",
3
- "version": "3.21.8",
3
+ "version": "3.22.0",
4
4
  "description": "Zero-dependency, pre-bundled Cogent MCP bridge — a single self-contained file for fast cold `npx` startup (used by the Codex plugin, whose MCP startup window is short). Functionally identical to @essentialai/cogent-bridge; carries no npm dependencies so `npx` skips dependency install.",
5
5
  "type": "module",
6
6
  "bin": {