@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.
- package/cogent-bridge.mjs +221 -65
- 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.
|
|
26393
|
-
return "3.
|
|
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(
|
|
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
|
-
|
|
27741
|
-
|
|
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
|
-
|
|
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(
|
|
33594
|
-
reject(
|
|
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(
|
|
33680
|
+
this.deliveryAckWaiters.set(messageId, { peerId: fromPeerId, resolve, reject, timer });
|
|
33597
33681
|
});
|
|
33598
33682
|
}
|
|
33599
33683
|
/**
|
|
33600
|
-
*
|
|
33601
|
-
*
|
|
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(
|
|
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(
|
|
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
|
|
33618
|
-
|
|
33619
|
-
clearTimeout(
|
|
33620
|
-
this.deliveryAckWaiters.delete(
|
|
33621
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
35012
|
-
if (
|
|
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:
|
|
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:
|
|
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
|
-
...
|
|
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
|
-
|
|
35558
|
-
|
|
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
|
-
|
|
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.
|
|
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": {
|