@essentialai/cogent-bridge-bundled 3.21.9 → 3.23.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 +547 -186
  2. package/package.json +1 -1
package/cogent-bridge.mjs CHANGED
@@ -1259,8 +1259,8 @@ var init_parseUtil = __esm({
1259
1259
  init_errors();
1260
1260
  init_en();
1261
1261
  makeIssue = (params) => {
1262
- const { data, path: path20, errorMaps, issueData } = params;
1263
- const fullPath = [...path20, ...issueData.path || []];
1262
+ const { data, path: path21, errorMaps, issueData } = params;
1263
+ const fullPath = [...path21, ...issueData.path || []];
1264
1264
  const fullIssue = {
1265
1265
  ...issueData,
1266
1266
  path: fullPath
@@ -1540,11 +1540,11 @@ var init_types = __esm({
1540
1540
  init_parseUtil();
1541
1541
  init_util();
1542
1542
  ParseInputLazyPath = class {
1543
- constructor(parent, value, path20, key) {
1543
+ constructor(parent, value, path21, key) {
1544
1544
  this._cachedPath = [];
1545
1545
  this.parent = parent;
1546
1546
  this.data = value;
1547
- this._path = path20;
1547
+ this._path = path21;
1548
1548
  this._key = key;
1549
1549
  }
1550
1550
  get path() {
@@ -4996,10 +4996,10 @@ function assignProp(target, prop, value) {
4996
4996
  configurable: true
4997
4997
  });
4998
4998
  }
4999
- function getElementAtPath(obj, path20) {
5000
- if (!path20)
4999
+ function getElementAtPath(obj, path21) {
5000
+ if (!path21)
5001
5001
  return obj;
5002
- return path20.reduce((acc, key) => acc?.[key], obj);
5002
+ return path21.reduce((acc, key) => acc?.[key], obj);
5003
5003
  }
5004
5004
  function promiseAllObject(promisesObj) {
5005
5005
  const keys = Object.keys(promisesObj);
@@ -5248,11 +5248,11 @@ function aborted(x, startIndex = 0) {
5248
5248
  }
5249
5249
  return false;
5250
5250
  }
5251
- function prefixIssues(path20, issues) {
5251
+ function prefixIssues(path21, issues) {
5252
5252
  return issues.map((iss) => {
5253
5253
  var _a;
5254
5254
  (_a = iss).path ?? (_a.path = []);
5255
- iss.path.unshift(path20);
5255
+ iss.path.unshift(path21);
5256
5256
  return iss;
5257
5257
  });
5258
5258
  }
@@ -17058,8 +17058,8 @@ var require_utils = __commonJS({
17058
17058
  }
17059
17059
  return ind;
17060
17060
  }
17061
- function removeDotSegments(path20) {
17062
- let input = path20;
17061
+ function removeDotSegments(path21) {
17062
+ let input = path21;
17063
17063
  const output = [];
17064
17064
  let nextSlash = -1;
17065
17065
  let len = 0;
@@ -17258,8 +17258,8 @@ var require_schemes = __commonJS({
17258
17258
  wsComponent.secure = void 0;
17259
17259
  }
17260
17260
  if (wsComponent.resourceName) {
17261
- const [path20, query] = wsComponent.resourceName.split("?");
17262
- wsComponent.path = path20 && path20 !== "/" ? path20 : void 0;
17261
+ const [path21, query] = wsComponent.resourceName.split("?");
17262
+ wsComponent.path = path21 && path21 !== "/" ? path21 : void 0;
17263
17263
  wsComponent.query = query;
17264
17264
  wsComponent.resourceName = void 0;
17265
17265
  }
@@ -20612,12 +20612,12 @@ var require_dist = __commonJS({
20612
20612
  throw new Error(`Unknown format "${name}"`);
20613
20613
  return f;
20614
20614
  };
20615
- function addFormats(ajv, list, fs18, exportName) {
20615
+ function addFormats(ajv, list, fs19, exportName) {
20616
20616
  var _a;
20617
20617
  var _b;
20618
20618
  (_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
20619
20619
  for (const f of list)
20620
- ajv.addFormat(f, fs18[f]);
20620
+ ajv.addFormat(f, fs19[f]);
20621
20621
  }
20622
20622
  module.exports = exports = formatsPlugin;
20623
20623
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -21729,8 +21729,8 @@ var require_parseUtil = __commonJS({
21729
21729
  var errors_js_1 = require_errors2();
21730
21730
  var en_js_1 = __importDefault(require_en());
21731
21731
  var makeIssue2 = (params) => {
21732
- const { data, path: path20, errorMaps, issueData } = params;
21733
- const fullPath = [...path20, ...issueData.path || []];
21732
+ const { data, path: path21, errorMaps, issueData } = params;
21733
+ const fullPath = [...path21, ...issueData.path || []];
21734
21734
  const fullIssue = {
21735
21735
  ...issueData,
21736
21736
  path: fullPath
@@ -21884,11 +21884,11 @@ var require_types2 = __commonJS({
21884
21884
  var parseUtil_js_1 = require_parseUtil();
21885
21885
  var util_js_1 = require_util2();
21886
21886
  var ParseInputLazyPath2 = class {
21887
- constructor(parent, value, path20, key) {
21887
+ constructor(parent, value, path21, key) {
21888
21888
  this._cachedPath = [];
21889
21889
  this.parent = parent;
21890
21890
  this.data = value;
21891
- this._path = path20;
21891
+ this._path = path21;
21892
21892
  this._key = key;
21893
21893
  }
21894
21894
  get path() {
@@ -25477,10 +25477,10 @@ var require_zod = __commonJS({
25477
25477
  };
25478
25478
  Object.defineProperty(exports, "__esModule", { value: true });
25479
25479
  exports.z = void 0;
25480
- var z18 = __importStar(require_external());
25481
- exports.z = z18;
25480
+ var z19 = __importStar(require_external());
25481
+ exports.z = z19;
25482
25482
  __exportStar(require_external(), exports);
25483
- exports.default = z18;
25483
+ exports.default = z19;
25484
25484
  }
25485
25485
  });
25486
25486
 
@@ -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.9") {
26393
- return "3.21.9";
26392
+ if ("3.23.0") {
26393
+ return "3.23.0";
26394
26394
  }
26395
26395
  try {
26396
26396
  const require2 = createRequire(import.meta.url);
@@ -27747,8 +27747,13 @@ async function getRelayVersion(http, nowFn = Date.now) {
27747
27747
  if (cached2 !== null && now - cached2.fetchedAt < TTL_MS) {
27748
27748
  return cached2.version;
27749
27749
  }
27750
+ return (await _refresh(http, now))?.version ?? null;
27751
+ }
27752
+ async function _refresh(http, now) {
27750
27753
  try {
27751
- const resp = await http.get("/api/health");
27754
+ const resp = await http.get(
27755
+ "/api/health"
27756
+ );
27752
27757
  const version2 = resp?.version;
27753
27758
  if (typeof version2 !== "string" || version2.length === 0) {
27754
27759
  logger.debug(
@@ -27756,14 +27761,26 @@ async function getRelayVersion(http, nowFn = Date.now) {
27756
27761
  );
27757
27762
  return null;
27758
27763
  }
27759
- cached2 = { version: version2, fetchedAt: now };
27760
- 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;
27761
27768
  } catch (err) {
27762
27769
  const msg = err instanceof Error ? err.message : String(err);
27763
27770
  logger.debug(`relay-version-cache: /api/health fetch failed: ${msg}`);
27764
27771
  return null;
27765
27772
  }
27766
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
+ }
27767
27784
  var TTL_MS, cached2;
27768
27785
  var init_relay_version_cache = __esm({
27769
27786
  "src/backend/relay-version-cache.ts"() {
@@ -27779,6 +27796,8 @@ var MAILBOX_DEPROVISIONED_HEADER, PATHS, HttpBackend;
27779
27796
  var init_http_backend = __esm({
27780
27797
  "src/backend/http-backend.ts"() {
27781
27798
  "use strict";
27799
+ init_relay_version_cache();
27800
+ init_errors4();
27782
27801
  init_health_check();
27783
27802
  init_constants();
27784
27803
  init_config();
@@ -27806,8 +27825,8 @@ var init_http_backend = __esm({
27806
27825
  * The sessionId parameter from StorageBackend is the local CC sessionId;
27807
27826
  * in cloud mode we ignore it and use this.sessionId (the cloud session).
27808
27827
  */
27809
- async registerPeer(peerId, _sessionId, cwd, label, clientVersion, mode, _channelSessionId, capabilities, workspaceId, threadId) {
27810
- const path20 = PATHS.peers.replace(":sessionId", this.sessionId);
27828
+ async registerPeer(peerId, _sessionId, cwd, label, clientVersion, mode, _channelSessionId, capabilities, workspaceId, threadId, peerSecret) {
27829
+ const path21 = PATHS.peers.replace(":sessionId", this.sessionId);
27811
27830
  const body = {
27812
27831
  peerId,
27813
27832
  cwd,
@@ -27835,7 +27854,10 @@ var init_http_backend = __esm({
27835
27854
  body.role = roleCap.slice("role:".length);
27836
27855
  }
27837
27856
  }
27838
- return this.http.post(path20, body);
27857
+ if (peerSecret !== void 0 && await relaySupports(this.http, "peer-ownership")) {
27858
+ body.peerSecret = peerSecret;
27859
+ }
27860
+ return this.http.post(path21, body);
27839
27861
  }
27840
27862
  /**
27841
27863
  * Deregister a peer from the cloud session.
@@ -27847,13 +27869,16 @@ var init_http_backend = __esm({
27847
27869
  * local mail creds. A 404 (peer not found) resolves to `{ removed: false, ... }`, not a throw.
27848
27870
  */
27849
27871
  async deregisterPeer(peerId) {
27850
- const path20 = PATHS.peer.replace(":sessionId", this.sessionId).replace(":peerId", peerId);
27872
+ const path21 = PATHS.peer.replace(":sessionId", this.sessionId).replace(":peerId", peerId);
27851
27873
  try {
27852
- const headers = await this.http.delete(path20, {});
27874
+ const headers = await this.http.delete(path21, {});
27853
27875
  const mailboxDeprovisioned = headers.get(MAILBOX_DEPROVISIONED_HEADER) === "true";
27854
27876
  return { removed: true, mailboxDeprovisioned };
27855
- } catch {
27856
- 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;
27857
27882
  }
27858
27883
  }
27859
27884
  /**
@@ -27870,8 +27895,8 @@ var init_http_backend = __esm({
27870
27895
  * GET /api/sessions/:sessionId/peers
27871
27896
  */
27872
27897
  async listPeers() {
27873
- const path20 = PATHS.peers.replace(":sessionId", this.sessionId);
27874
- const result = await this.http.get(path20);
27898
+ const path21 = PATHS.peers.replace(":sessionId", this.sessionId);
27899
+ const result = await this.http.get(path21);
27875
27900
  return result.peers;
27876
27901
  }
27877
27902
  /**
@@ -27879,8 +27904,8 @@ var init_http_backend = __esm({
27879
27904
  * Calls the lightweight heartbeat endpoint on the cloud relay server.
27880
27905
  */
27881
27906
  async updateLastSeen(peerId) {
27882
- const path20 = PATHS.heartbeat.replace(":sessionId", this.sessionId);
27883
- await this.http.post(path20, { peerId });
27907
+ const path21 = PATHS.heartbeat.replace(":sessionId", this.sessionId);
27908
+ await this.http.post(path21, { peerId });
27884
27909
  }
27885
27910
  /**
27886
27911
  * Record a message in the cloud session.
@@ -27891,7 +27916,7 @@ var init_http_backend = __esm({
27891
27916
  * the input record to construct a full MessageRecord.
27892
27917
  */
27893
27918
  async recordMessage(record2) {
27894
- const path20 = PATHS.messages.replace(":sessionId", this.sessionId);
27919
+ const path21 = PATHS.messages.replace(":sessionId", this.sessionId);
27895
27920
  const body = {
27896
27921
  fromPeerId: record2.fromPeerId,
27897
27922
  toPeerId: record2.toPeerId,
@@ -27900,10 +27925,21 @@ var init_http_backend = __esm({
27900
27925
  if (record2.isRelayEcho === true) {
27901
27926
  body.isRelayEcho = true;
27902
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
+ }
27903
27939
  if (record2.attachments && record2.attachments.length > 0) {
27904
27940
  body.attachments = record2.attachments;
27905
27941
  }
27906
- const resp = await this.http.post(path20, body);
27942
+ const resp = await this.http.post(path21, body);
27907
27943
  return {
27908
27944
  ...record2,
27909
27945
  id: resp.id,
@@ -27917,7 +27953,7 @@ var init_http_backend = __esm({
27917
27953
  * Supports optional peerId filter and limit (default 300).
27918
27954
  */
27919
27955
  async getHistory(peerId, limit, includeRelayEchoes, metadataOnly, dedupInferredEchoes) {
27920
- const path20 = PATHS.messages.replace(":sessionId", this.sessionId);
27956
+ const path21 = PATHS.messages.replace(":sessionId", this.sessionId);
27921
27957
  const query = {};
27922
27958
  if (peerId) {
27923
27959
  query.peerId = peerId;
@@ -27932,7 +27968,7 @@ var init_http_backend = __esm({
27932
27968
  if (dedupInferredEchoes) {
27933
27969
  query.dedupInferredEchoes = "true";
27934
27970
  }
27935
- const result = await this.http.get(path20, query);
27971
+ const result = await this.http.get(path21, query);
27936
27972
  return result.messages;
27937
27973
  }
27938
27974
  /**
@@ -27943,12 +27979,12 @@ var init_http_backend = __esm({
27943
27979
  * so that get-history shows the actual response (not null).
27944
27980
  */
27945
27981
  async updateMessageResponse(messageId, response, durationMs, pending) {
27946
- const path20 = PATHS.message.replace(":sessionId", this.sessionId).replace(":messageId", messageId);
27982
+ const path21 = PATHS.message.replace(":sessionId", this.sessionId).replace(":messageId", messageId);
27947
27983
  const body = { response, durationMs };
27948
27984
  if (pending === true) {
27949
27985
  body.pending = true;
27950
27986
  }
27951
- await this.http.patch(path20, body);
27987
+ await this.http.patch(path21, body);
27952
27988
  }
27953
27989
  /**
27954
27990
  * Validate a session.
@@ -28090,8 +28126,8 @@ var init_http_client = __esm({
28090
28126
  * Perform an authenticated GET request.
28091
28127
  * Appends query parameters to the URL if provided.
28092
28128
  */
28093
- async get(path20, query) {
28094
- const url = new URL(path20, this.baseUrl);
28129
+ async get(path21, query) {
28130
+ const url = new URL(path21, this.baseUrl);
28095
28131
  if (query) {
28096
28132
  for (const [k, v] of Object.entries(query)) {
28097
28133
  url.searchParams.set(k, v);
@@ -28106,8 +28142,8 @@ var init_http_client = __esm({
28106
28142
  /**
28107
28143
  * Perform an authenticated POST request with a JSON body.
28108
28144
  */
28109
- async post(path20, body) {
28110
- const url = new URL(path20, this.baseUrl);
28145
+ async post(path21, body) {
28146
+ const url = new URL(path21, this.baseUrl);
28111
28147
  const resp = await this.fetchWithTimeout(url, {
28112
28148
  method: "POST",
28113
28149
  headers: this.headers(),
@@ -28125,8 +28161,8 @@ var init_http_client = __esm({
28125
28161
  * Perform an authenticated PATCH request with a JSON body.
28126
28162
  * Used to update existing resources (e.g., message response fields).
28127
28163
  */
28128
- async patch(path20, body) {
28129
- const url = new URL(path20, this.baseUrl);
28164
+ async patch(path21, body) {
28165
+ const url = new URL(path21, this.baseUrl);
28130
28166
  const resp = await this.fetchWithTimeout(url, {
28131
28167
  method: "PATCH",
28132
28168
  headers: this.headers(),
@@ -28151,8 +28187,8 @@ var init_http_client = __esm({
28151
28187
  * "Malformed JSON in request body" 400 from cogent-server <=3.1.2 when
28152
28188
  * any client (including third-party tools) DELETEd a peer without a body.
28153
28189
  */
28154
- async delete(path20, body) {
28155
- const url = new URL(path20, this.baseUrl);
28190
+ async delete(path21, body) {
28191
+ const url = new URL(path21, this.baseUrl);
28156
28192
  const headers = {
28157
28193
  "Authorization": `Bearer ${this.token}`
28158
28194
  };
@@ -32335,14 +32371,14 @@ var init_ws_client = __esm({
32335
32371
  if (this.pollTimer) return;
32336
32372
  const poll = async () => {
32337
32373
  try {
32338
- const path20 = `/api/sessions/${this.opts.sessionId}/poll`;
32374
+ const path21 = `/api/sessions/${this.opts.sessionId}/poll`;
32339
32375
  const query = {
32340
32376
  peerId: this.opts.peerId
32341
32377
  };
32342
32378
  if (this.lastMessageId) {
32343
32379
  query.lastMessageId = this.lastMessageId;
32344
32380
  }
32345
- const result = await this.opts.http.get(path20, query);
32381
+ const result = await this.opts.http.get(path21, query);
32346
32382
  if (result.messages && result.messages.length > 0) {
32347
32383
  this.opts.onMessages(result.messages);
32348
32384
  this.lastMessageId = result.messages[result.messages.length - 1].id;
@@ -33061,6 +33097,112 @@ var init_exec_remote = __esm({
33061
33097
  }
33062
33098
  });
33063
33099
 
33100
+ // src/services/wake-log.ts
33101
+ import { execFileSync } from "node:child_process";
33102
+ import crypto4 from "node:crypto";
33103
+ import fs13 from "node:fs";
33104
+ import os9 from "node:os";
33105
+ import path14 from "node:path";
33106
+ function hashCwd2(cwd) {
33107
+ return crypto4.createHash("sha256").update(path14.resolve(cwd)).digest("hex").slice(0, 16);
33108
+ }
33109
+ function wakeLogPath(cwd) {
33110
+ return path14.join(os9.homedir(), ".cogent", "wake-log", `${hashCwd2(cwd)}.jsonl`);
33111
+ }
33112
+ function git(cwd, args) {
33113
+ return execFileSync("git", ["-C", cwd, ...args], {
33114
+ encoding: "utf-8",
33115
+ timeout: 15e3,
33116
+ stdio: ["ignore", "pipe", "ignore"]
33117
+ });
33118
+ }
33119
+ function captureTreeState(cwd) {
33120
+ try {
33121
+ const head = git(cwd, ["rev-parse", "HEAD"]).trim();
33122
+ const entries = {};
33123
+ const raw = git(cwd, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
33124
+ const fields = raw.split("\0").filter((f) => f.length > 0);
33125
+ for (let i = 0; i < fields.length; i++) {
33126
+ const field = fields[i];
33127
+ const status = field.slice(0, 2);
33128
+ entries[field.slice(3)] = status;
33129
+ if (status[0] === "R" || status[0] === "C") i++;
33130
+ }
33131
+ return { head, entries };
33132
+ } catch {
33133
+ return null;
33134
+ }
33135
+ }
33136
+ function diffTreeState(before, after) {
33137
+ if (!before || !after) return null;
33138
+ const changed = /* @__PURE__ */ new Set();
33139
+ if (before.head !== after.head) changed.add("HEAD");
33140
+ for (const [file, status] of Object.entries(after.entries)) {
33141
+ if (before.entries[file] !== status) changed.add(file);
33142
+ }
33143
+ for (const file of Object.keys(before.entries)) {
33144
+ if (!(file in after.entries)) changed.add(file);
33145
+ }
33146
+ return [...changed].sort();
33147
+ }
33148
+ function preview(message) {
33149
+ return message.length > PREVIEW_LIMIT ? `${message.slice(0, PREVIEW_LIMIT)}\u2026` : message;
33150
+ }
33151
+ function appendWakeRecord(record2, cwd) {
33152
+ const target = wakeLogPath(cwd);
33153
+ try {
33154
+ fs13.mkdirSync(path14.dirname(target), { recursive: true });
33155
+ const row = {
33156
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
33157
+ ...record2,
33158
+ messagePreview: preview(record2.messagePreview ?? "")
33159
+ };
33160
+ fs13.appendFileSync(target, `${JSON.stringify(row)}
33161
+ `);
33162
+ pruneIfNeeded(target);
33163
+ } catch {
33164
+ }
33165
+ }
33166
+ function pruneIfNeeded(target) {
33167
+ try {
33168
+ const lines = fs13.readFileSync(target, "utf-8").split("\n").filter((l) => l.length > 0);
33169
+ if (lines.length <= WAKE_LOG_MAX_RECORDS) return;
33170
+ const kept = lines.slice(lines.length - WAKE_LOG_MAX_RECORDS);
33171
+ const tmp = `${target}.tmp-${process.pid}`;
33172
+ fs13.writeFileSync(tmp, `${kept.join("\n")}
33173
+ `);
33174
+ fs13.renameSync(tmp, target);
33175
+ } catch {
33176
+ }
33177
+ }
33178
+ function readWakeLog(cwd, limit) {
33179
+ let lines;
33180
+ try {
33181
+ lines = fs13.readFileSync(wakeLogPath(cwd), "utf-8").split("\n").filter((l) => l.length > 0);
33182
+ } catch {
33183
+ return [];
33184
+ }
33185
+ const rows = [];
33186
+ for (const line of lines) {
33187
+ try {
33188
+ const parsed = JSON.parse(line);
33189
+ if (parsed && typeof parsed === "object" && typeof parsed.messageId === "string") {
33190
+ rows.push(parsed);
33191
+ }
33192
+ } catch {
33193
+ }
33194
+ }
33195
+ return typeof limit === "number" && limit > 0 ? rows.slice(-limit) : rows;
33196
+ }
33197
+ var WAKE_LOG_MAX_RECORDS, PREVIEW_LIMIT;
33198
+ var init_wake_log = __esm({
33199
+ "src/services/wake-log.ts"() {
33200
+ "use strict";
33201
+ WAKE_LOG_MAX_RECORDS = 500;
33202
+ PREVIEW_LIMIT = 500;
33203
+ }
33204
+ });
33205
+
33064
33206
  // src/services/session-resolver.ts
33065
33207
  function decideResolution(candidates, pinnedSessionId, pinnedExists, marginMs) {
33066
33208
  const sorted = [...candidates].sort((a, b) => b.mtimeMs - a.mtimeMs);
@@ -33148,15 +33290,15 @@ var init_session_resolver = __esm({
33148
33290
  });
33149
33291
 
33150
33292
  // src/services/wake-inflight.ts
33151
- import crypto4 from "node:crypto";
33152
- import fs13 from "node:fs";
33153
- import os9 from "node:os";
33154
- import path14 from "node:path";
33155
- function hashCwd2(cwd) {
33156
- return crypto4.createHash("sha256").update(path14.resolve(cwd)).digest("hex").slice(0, 16);
33293
+ import crypto5 from "node:crypto";
33294
+ import fs14 from "node:fs";
33295
+ import os10 from "node:os";
33296
+ import path15 from "node:path";
33297
+ function hashCwd3(cwd) {
33298
+ return crypto5.createHash("sha256").update(path15.resolve(cwd)).digest("hex").slice(0, 16);
33157
33299
  }
33158
33300
  function wakeInflightPath(peerId, cwd = process.cwd()) {
33159
- return path14.join(os9.homedir(), ".cogent", "wake-inflight", `${hashCwd2(cwd)}.${safe(peerId)}.json`);
33301
+ return path15.join(os10.homedir(), ".cogent", "wake-inflight", `${hashCwd3(cwd)}.${safe(peerId)}.json`);
33160
33302
  }
33161
33303
  function ttlMs() {
33162
33304
  let timeout = 3e5;
@@ -33169,17 +33311,17 @@ function ttlMs() {
33169
33311
  }
33170
33312
  function readFileSafe(p) {
33171
33313
  try {
33172
- const parsed = JSON.parse(fs13.readFileSync(p, "utf-8"));
33314
+ const parsed = JSON.parse(fs14.readFileSync(p, "utf-8"));
33173
33315
  return parsed && typeof parsed === "object" && parsed.messages ? parsed : { messages: {} };
33174
33316
  } catch {
33175
33317
  return { messages: {} };
33176
33318
  }
33177
33319
  }
33178
33320
  function writeAtomic(p, data) {
33179
- fs13.mkdirSync(path14.dirname(p), { recursive: true });
33321
+ fs14.mkdirSync(path15.dirname(p), { recursive: true });
33180
33322
  const tmp = `${p}.${process.pid}.tmp`;
33181
- fs13.writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n", "utf-8");
33182
- fs13.renameSync(tmp, p);
33323
+ fs14.writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n", "utf-8");
33324
+ fs14.renameSync(tmp, p);
33183
33325
  }
33184
33326
  function prune(f, now) {
33185
33327
  for (const [id, rec] of Object.entries(f.messages)) {
@@ -33199,6 +33341,15 @@ function markWakeInFlight(peerId, messageId, traceId, cwd = process.cwd()) {
33199
33341
  logger.debug(`wake-inflight: could not mark ${messageId}: ${err}`);
33200
33342
  }
33201
33343
  }
33344
+ function listWakeInFlight(peerId, cwd = process.cwd()) {
33345
+ try {
33346
+ const cur = readFileSafe(wakeInflightPath(peerId, cwd));
33347
+ const now = Date.now();
33348
+ return Object.entries(cur.messages).filter(([, e]) => e.expiresAt > now).map(([messageId, e]) => ({ messageId, ...e })).sort((a, b) => a.startedAt - b.startedAt);
33349
+ } catch {
33350
+ return [];
33351
+ }
33352
+ }
33202
33353
  function clearWakeInFlight(peerId, messageId, cwd = process.cwd()) {
33203
33354
  try {
33204
33355
  const p = wakeInflightPath(peerId, cwd);
@@ -33350,11 +33501,12 @@ function truncate(s) {
33350
33501
  const flat = s.replace(/\s+/g, " ").trim();
33351
33502
  return flat.length > DIAGNOSTIC_MAX ? `${flat.slice(0, DIAGNOSTIC_MAX - 1)}\u2026` : flat;
33352
33503
  }
33353
- var RELAY_STATUS, STATUS_MARKER_RE, DIAGNOSTIC_MAX, AutoRelayService, autoRelay;
33504
+ var RELAY_STATUS, STATUS_MARKER_RE, DIAGNOSTIC_MAX, EARLY_ACK_TTL_MS, EARLY_ACK_MAX, AutoRelayService, autoRelay;
33354
33505
  var init_auto_relay = __esm({
33355
33506
  "src/services/auto-relay.ts"() {
33356
33507
  "use strict";
33357
33508
  init_exec_remote();
33509
+ init_wake_log();
33358
33510
  init_session_resolver();
33359
33511
  init_config();
33360
33512
  init_backend();
@@ -33371,6 +33523,8 @@ var init_auto_relay = __esm({
33371
33523
  };
33372
33524
  STATUS_MARKER_RE = /^\[cogent:(queued|processing|delivered|needs-manual|failed)\]/;
33373
33525
  DIAGNOSTIC_MAX = 220;
33526
+ EARLY_ACK_TTL_MS = 12e4;
33527
+ EARLY_ACK_MAX = 256;
33374
33528
  AutoRelayService = class {
33375
33529
  localPeerId = null;
33376
33530
  localSessionId = null;
@@ -33417,7 +33571,24 @@ var init_auto_relay = __esm({
33417
33571
  // so the first send-message tool call returned literal "Superseded by
33418
33572
  // newer send_message call" instead of the actual peer response.
33419
33573
  responseWaiters = /* @__PURE__ */ new Map();
33574
+ /**
33575
+ * Pending delivery-ack waiters, keyed by MESSAGE ID.
33576
+ *
33577
+ * 🔴 AUD-002. This used to be keyed by PEER ID, so a peer could have only one outstanding
33578
+ * waiter and a second concurrent send displaced the first — resolving it, so the first send
33579
+ * reported "delivered" with no acknowledgment from anyone. Delivery acknowledgment is the one
33580
+ * claim this system must never fabricate.
33581
+ */
33420
33582
  deliveryAckWaiters = /* @__PURE__ */ new Map();
33583
+ /**
33584
+ * Acks that arrived before their waiter existed, keyed by message id.
33585
+ *
33586
+ * The recipient acknowledges over WS as soon as it receives the message, which can beat our own
33587
+ * POST response. Waiters used to be registered BEFORE the send to dodge that race, which is what
33588
+ * forced peer-keying. Buffering the early ack instead lets registration happen after the message
33589
+ * id is known, so correlation stays exact. Entries are consumed once and expire.
33590
+ */
33591
+ recentAcks = /* @__PURE__ */ new Map();
33421
33592
  sendDeliveryAckFn = null;
33422
33593
  /**
33423
33594
  * Set the local peer info. Called from register-peer tool after
@@ -33586,12 +33757,6 @@ var init_auto_relay = __esm({
33586
33757
  cancelResponseWaiter(fromPeerId) {
33587
33758
  this._cancelWaiter(fromPeerId);
33588
33759
  }
33589
- /**
33590
- * Cancel only the active delivery ack waiter for a peer.
33591
- */
33592
- cancelDeliveryAckWaiter(fromPeerId) {
33593
- this._cancelDeliveryAckWaiter(fromPeerId);
33594
- }
33595
33760
  /**
33596
33761
  * Cancel ALL pending response waiters for a peer by rejecting them with
33597
33762
  * a clear error. Used by clearExpectation()/cancelResponseWaiter() to
@@ -33614,39 +33779,73 @@ var init_auto_relay = __esm({
33614
33779
  * resolves when the target peer's MCP sends a delivery_ack frame, or
33615
33780
  * rejects on timeout (recipient likely offline).
33616
33781
  */
33617
- waitForDeliveryAck(fromPeerId, timeoutMs) {
33782
+ waitForDeliveryAck(fromPeerId, messageId, timeoutMs) {
33783
+ if (this.recentAcks.delete(messageId)) {
33784
+ logger.debug(`Auto-relay: delivery ack for ${messageId} was already buffered`);
33785
+ return Promise.resolve();
33786
+ }
33618
33787
  return new Promise((resolve, reject) => {
33619
- this._cancelDeliveryAckWaiter(fromPeerId);
33620
33788
  const timer = setTimeout(() => {
33621
- this.deliveryAckWaiters.delete(fromPeerId);
33622
- reject(new Error(`Delivery ack timeout from ${fromPeerId} after ${timeoutMs}ms`));
33789
+ this.deliveryAckWaiters.delete(messageId);
33790
+ reject(
33791
+ new Error(
33792
+ `Delivery ack timeout from ${fromPeerId} for ${messageId} after ${timeoutMs}ms`
33793
+ )
33794
+ );
33623
33795
  }, timeoutMs);
33624
- this.deliveryAckWaiters.set(fromPeerId, { resolve, timer });
33796
+ this.deliveryAckWaiters.set(messageId, { peerId: fromPeerId, resolve, reject, timer });
33625
33797
  });
33626
33798
  }
33627
33799
  /**
33628
- * Resolve a pending delivery ack waiter. Called by startup.ts onDeliveryAck
33629
- * callback when the server routes a delivery_ack frame to us.
33800
+ * Settle the waiter for exactly this message.
33801
+ *
33802
+ * 🔴 Correlation is by MESSAGE ID, and the acknowledging peer must match the peer we sent to.
33803
+ * The frame has carried `messageId` all along (src/cloud/ws-client.ts:52); startup.ts used to
33804
+ * drop it and resolve by peer, so any ack settled any outstanding send to that peer — and an ack
33805
+ * naming a peer the sender never messaged could settle a waiter outright (AUD-003).
33806
+ *
33807
+ * An ack with no matching waiter is BUFFERED rather than discarded: the recipient can legitimately
33808
+ * acknowledge before our POST response returns.
33630
33809
  */
33631
- resolveDeliveryAck(fromPeerId) {
33632
- const waiter = this.deliveryAckWaiters.get(fromPeerId);
33810
+ resolveDeliveryAck(fromPeerId, messageId) {
33811
+ const waiter = this.deliveryAckWaiters.get(messageId);
33633
33812
  if (waiter) {
33813
+ if (waiter.peerId !== fromPeerId) {
33814
+ logger.warn(
33815
+ `Auto-relay: IGNORED delivery ack for ${messageId} \u2014 claimed by '${fromPeerId}' but that message was sent to '${waiter.peerId}'`
33816
+ );
33817
+ return;
33818
+ }
33634
33819
  clearTimeout(waiter.timer);
33635
- this.deliveryAckWaiters.delete(fromPeerId);
33820
+ this.deliveryAckWaiters.delete(messageId);
33636
33821
  waiter.resolve();
33637
- logger.debug(`Auto-relay: delivery ack received from ${fromPeerId}`);
33822
+ logger.debug(`Auto-relay: delivery ack received from ${fromPeerId} for ${messageId}`);
33823
+ return;
33638
33824
  }
33825
+ this._bufferEarlyAck(messageId);
33826
+ }
33827
+ /** Remember an ack whose waiter does not exist yet, with bounded retention. */
33828
+ _bufferEarlyAck(messageId) {
33829
+ const now = Date.now();
33830
+ for (const [id, at] of this.recentAcks) {
33831
+ if (now - at > EARLY_ACK_TTL_MS) this.recentAcks.delete(id);
33832
+ }
33833
+ while (this.recentAcks.size >= EARLY_ACK_MAX) {
33834
+ const oldest = this.recentAcks.keys().next().value;
33835
+ if (oldest === void 0) break;
33836
+ this.recentAcks.delete(oldest);
33837
+ }
33838
+ this.recentAcks.set(messageId, now);
33839
+ logger.debug(`Auto-relay: buffered early delivery ack for ${messageId}`);
33639
33840
  }
33640
- /**
33641
- * Cancel a pending delivery ack waiter for a peer.
33642
- * Resolves the old promise so callers awaiting it don't hang forever.
33643
- */
33644
33841
  _cancelDeliveryAckWaiter(fromPeerId) {
33645
- const existing = this.deliveryAckWaiters.get(fromPeerId);
33646
- if (existing) {
33647
- clearTimeout(existing.timer);
33648
- this.deliveryAckWaiters.delete(fromPeerId);
33649
- existing.resolve();
33842
+ for (const [messageId, waiter] of this.deliveryAckWaiters) {
33843
+ if (waiter.peerId !== fromPeerId) continue;
33844
+ clearTimeout(waiter.timer);
33845
+ this.deliveryAckWaiters.delete(messageId);
33846
+ waiter.reject(
33847
+ new Error(`Delivery ack wait for ${messageId} cancelled (${fromPeerId})`)
33848
+ );
33650
33849
  }
33651
33850
  }
33652
33851
  /**
@@ -33903,6 +34102,10 @@ var init_auto_relay = __esm({
33903
34102
  const traceId = msg.traceId ?? randomUUID2();
33904
34103
  const startMs = Date.now();
33905
34104
  let replied = false;
34105
+ let treeBefore = null;
34106
+ let wakeExitCode = null;
34107
+ let refused = false;
34108
+ let threw = false;
33906
34109
  try {
33907
34110
  const res = await this.refreshSessionId();
33908
34111
  this._trace(traceId, "resolved", {
@@ -33912,6 +34115,7 @@ var init_auto_relay = __esm({
33912
34115
  mtimeGapMs: res.mtimeGapMs
33913
34116
  });
33914
34117
  if (!res.sessionId) {
34118
+ refused = true;
33915
34119
  this._trace(traceId, "refused", {
33916
34120
  decision: res.decision,
33917
34121
  candidateCount: res.candidateCount
@@ -33940,6 +34144,7 @@ var init_auto_relay = __esm({
33940
34144
  decision: res.decision,
33941
34145
  confidence: res.confidence
33942
34146
  });
34147
+ treeBefore = captureTreeState(this.localCwd);
33943
34148
  const result = await execRemote(
33944
34149
  getConfig().COGENT_PLATFORM,
33945
34150
  this.localSessionId,
@@ -33952,6 +34157,7 @@ var init_auto_relay = __esm({
33952
34157
  { idempotencyKey: msg.id }
33953
34158
  );
33954
34159
  const durationMs = Date.now() - startMs;
34160
+ wakeExitCode = result.exitCode;
33955
34161
  if (result.exitCode === 0 && result.stdout) {
33956
34162
  await this._relayCapturedReply(msg, result, traceId, durationMs, false);
33957
34163
  replied = true;
@@ -34017,6 +34223,7 @@ var init_auto_relay = __esm({
34017
34223
  }
34018
34224
  } catch (err) {
34019
34225
  const durationMs = Date.now() - startMs;
34226
+ threw = true;
34020
34227
  this._trace(traceId, "failed", { threw: true, durationMs });
34021
34228
  logger.error(`Auto-relay: execRemote threw after ${durationMs}ms: ${err}`);
34022
34229
  if (!replied) {
@@ -34024,6 +34231,48 @@ var init_auto_relay = __esm({
34024
34231
  stderr: `EXEC_THREW: ${err}`
34025
34232
  });
34026
34233
  }
34234
+ } finally {
34235
+ this._recordWake(msg, traceId, {
34236
+ outcome: threw ? "threw" : refused ? "refused" : replied ? "replied" : "failed",
34237
+ exitCode: wakeExitCode,
34238
+ durationMs: Date.now() - startMs,
34239
+ treeBefore
34240
+ });
34241
+ }
34242
+ }
34243
+ /**
34244
+ * Write the durable wake record. Wrapped whole: visibility must never be able to fail a
34245
+ * wake that would otherwise have succeeded, so every error here is swallowed and logged.
34246
+ */
34247
+ _recordWake(msg, traceId, outcome) {
34248
+ try {
34249
+ const cwd = this.localCwd;
34250
+ if (!cwd) return;
34251
+ const filesChanged = outcome.treeBefore ? diffTreeState(outcome.treeBefore, captureTreeState(cwd)) : null;
34252
+ appendWakeRecord(
34253
+ {
34254
+ traceId,
34255
+ messageId: msg.id,
34256
+ fromPeerId: msg.fromPeerId,
34257
+ toPeerId: this.localPeerId ?? "",
34258
+ cwd,
34259
+ sessionId: this.localSessionId ?? "",
34260
+ platform: getConfig().COGENT_PLATFORM,
34261
+ outcome: outcome.outcome,
34262
+ exitCode: outcome.exitCode,
34263
+ durationMs: outcome.durationMs,
34264
+ filesChanged,
34265
+ messagePreview: msg.message ?? ""
34266
+ },
34267
+ cwd
34268
+ );
34269
+ if (filesChanged && filesChanged.length > 0) {
34270
+ logger.info(
34271
+ `Auto-relay: wake for message ${msg.id} from ${msg.fromPeerId} changed ${filesChanged.length} path(s): ${filesChanged.slice(0, 10).join(", ")}`
34272
+ );
34273
+ }
34274
+ } catch (err) {
34275
+ logger.warn(`Auto-relay: wake-log record failed (wake itself unaffected): ${err}`);
34027
34276
  }
34028
34277
  }
34029
34278
  /**
@@ -34149,15 +34398,18 @@ var init_auto_relay = __esm({
34149
34398
  for (const [, queue] of this.responseWaiters) {
34150
34399
  for (const waiter of queue) {
34151
34400
  clearTimeout(waiter.timer);
34152
- waiter.resolve("");
34401
+ waiter.reject(new Error("Auto-relay shut down while waiting for a response"));
34153
34402
  }
34154
34403
  }
34155
34404
  this.responseWaiters.clear();
34156
- for (const [, waiter] of this.deliveryAckWaiters) {
34405
+ for (const [messageId, waiter] of this.deliveryAckWaiters) {
34157
34406
  clearTimeout(waiter.timer);
34158
- waiter.resolve();
34407
+ waiter.reject(
34408
+ new Error(`Auto-relay shut down while waiting for delivery ack (${messageId})`)
34409
+ );
34159
34410
  }
34160
34411
  this.deliveryAckWaiters.clear();
34412
+ this.recentAcks.clear();
34161
34413
  this.queue = [];
34162
34414
  this.awarenessOnly.clear();
34163
34415
  this.localPeerId = null;
@@ -34413,9 +34665,9 @@ __export(startup_exports, {
34413
34665
  setInboxNotifier: () => setInboxNotifier,
34414
34666
  triggerReRegistration: () => triggerReRegistration
34415
34667
  });
34416
- import os10 from "node:os";
34417
- import path15 from "node:path";
34418
- import fs14 from "node:fs/promises";
34668
+ import os11 from "node:os";
34669
+ import path16 from "node:path";
34670
+ import fs15 from "node:fs/promises";
34419
34671
  import readline2 from "node:readline";
34420
34672
  import { execFile as execFile5 } from "node:child_process";
34421
34673
  import { promisify as promisify4 } from "node:util";
@@ -34433,7 +34685,7 @@ function bindInboxNotifier() {
34433
34685
  }
34434
34686
  async function loadPersistedConfig() {
34435
34687
  try {
34436
- const raw = await fs14.readFile(PERSIST_PATH, "utf-8");
34688
+ const raw = await fs15.readFile(PERSIST_PATH, "utf-8");
34437
34689
  return JSON.parse(raw);
34438
34690
  } catch {
34439
34691
  return {};
@@ -34441,7 +34693,7 @@ async function loadPersistedConfig() {
34441
34693
  }
34442
34694
  async function savePersistedConfig(config2) {
34443
34695
  try {
34444
- await fs14.writeFile(PERSIST_PATH, JSON.stringify(config2, null, 2) + "\n", "utf-8");
34696
+ await fs15.writeFile(PERSIST_PATH, JSON.stringify(config2, null, 2) + "\n", "utf-8");
34445
34697
  } catch (err) {
34446
34698
  const msg = err instanceof Error ? err.message : String(err);
34447
34699
  logger.warn(`Could not save config to ${PERSIST_PATH}: ${msg}`);
@@ -34476,11 +34728,11 @@ First run detected. Where should cogent-bridge store its data?
34476
34728
  });
34477
34729
  }
34478
34730
  async function validateStateDir(statePath) {
34479
- await fs14.mkdir(statePath, { recursive: true });
34480
- const testFile = path15.join(statePath, ".write-test");
34731
+ await fs15.mkdir(statePath, { recursive: true });
34732
+ const testFile = path16.join(statePath, ".write-test");
34481
34733
  try {
34482
- await fs14.writeFile(testFile, "test", "utf-8");
34483
- await fs14.unlink(testFile);
34734
+ await fs15.writeFile(testFile, "test", "utf-8");
34735
+ await fs15.unlink(testFile);
34484
34736
  } catch {
34485
34737
  throw new BridgeError(
34486
34738
  "STARTUP_FAILED" /* STARTUP_FAILED */,
@@ -34580,7 +34832,7 @@ function createCloudWsClient(endpoint, sessionId, token, http, inbox, pollInterv
34580
34832
  logger.warn(`WebSocket error frame: ${err.code}: ${err.message}`);
34581
34833
  },
34582
34834
  onDeliveryAck: (payload) => {
34583
- autoRelay.resolveDeliveryAck(payload.peerId);
34835
+ autoRelay.resolveDeliveryAck(payload.peerId, payload.messageId);
34584
34836
  },
34585
34837
  onPeerEvicted: () => {
34586
34838
  triggerReRegistration().catch((err) => {
@@ -34667,10 +34919,10 @@ async function runStartup() {
34667
34919
  if (envStatePath) {
34668
34920
  statePath = envStatePath;
34669
34921
  } else {
34670
- const defaultPath = path15.join(os10.homedir(), ".cogent");
34922
+ const defaultPath = path16.join(os11.homedir(), ".cogent");
34671
34923
  statePath = await firstRunPrompt(defaultPath);
34672
34924
  }
34673
- if (!envStatePath && statePath !== path15.join(os10.homedir(), ".cogent")) {
34925
+ if (!envStatePath && statePath !== path16.join(os11.homedir(), ".cogent")) {
34674
34926
  process.env.COGENT_STATE_PATH = statePath;
34675
34927
  } else if (!envStatePath) {
34676
34928
  process.env.COGENT_STATE_PATH = statePath;
@@ -34734,7 +34986,7 @@ async function runStartup() {
34734
34986
  }
34735
34987
  initLogger(
34736
34988
  config2.COGENT_LOG_LEVEL,
34737
- path15.join(config2.COGENT_STATE_PATH, "logs")
34989
+ path16.join(config2.COGENT_STATE_PATH, "logs")
34738
34990
  );
34739
34991
  if (isCloud && effectiveSessionId && effectiveToken) {
34740
34992
  cloudInbox = new MessageInbox();
@@ -34796,7 +35048,7 @@ var init_startup = __esm({
34796
35048
  init_auto_relay();
34797
35049
  UUID_V4_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
34798
35050
  execFileAsync3 = promisify4(execFile5);
34799
- PERSIST_PATH = path15.join(os10.homedir(), ".cogent-config.json");
35051
+ PERSIST_PATH = path16.join(os11.homedir(), ".cogent-config.json");
34800
35052
  legacyWarnEmitted = false;
34801
35053
  cloudWsClient = null;
34802
35054
  cloudInbox = null;
@@ -34810,13 +35062,13 @@ var init_startup = __esm({
34810
35062
  });
34811
35063
 
34812
35064
  // src/cloud/mail-credential-store.ts
34813
- import crypto5 from "node:crypto";
34814
- import fs15 from "node:fs/promises";
34815
- import path16 from "node:path";
34816
- import os11 from "node:os";
35065
+ import crypto6 from "node:crypto";
35066
+ import fs16 from "node:fs/promises";
35067
+ import path17 from "node:path";
35068
+ import os12 from "node:os";
34817
35069
  function defaultMailCredentialPath(cwd = process.cwd()) {
34818
- const hash = crypto5.createHash("sha256").update(path16.resolve(cwd)).digest("hex").slice(0, 16);
34819
- return path16.join(os11.homedir(), ".cogent", "mail-credentials", `${hash}.json`);
35070
+ const hash = crypto6.createHash("sha256").update(path17.resolve(cwd)).digest("hex").slice(0, 16);
35071
+ return path17.join(os12.homedir(), ".cogent", "mail-credentials", `${hash}.json`);
34820
35072
  }
34821
35073
  function resolveMailCredentialPath(credentialPath) {
34822
35074
  if (credentialPath) return credentialPath;
@@ -34827,16 +35079,16 @@ function resolveMailCredentialPath(credentialPath) {
34827
35079
  async function loadMailCredentials(credentialPath) {
34828
35080
  const filePath = resolveMailCredentialPath(credentialPath);
34829
35081
  try {
34830
- return JSON.parse(await fs15.readFile(filePath, "utf-8"));
35082
+ return JSON.parse(await fs16.readFile(filePath, "utf-8"));
34831
35083
  } catch {
34832
35084
  return null;
34833
35085
  }
34834
35086
  }
34835
35087
  async function saveMailCredentials(creds, credentialPath) {
34836
35088
  const filePath = resolveMailCredentialPath(credentialPath);
34837
- await fs15.mkdir(path16.dirname(filePath), { recursive: true, mode: 448 });
34838
- await fs15.writeFile(filePath, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
34839
- await fs15.chmod(filePath, 384);
35089
+ await fs16.mkdir(path17.dirname(filePath), { recursive: true, mode: 448 });
35090
+ await fs16.writeFile(filePath, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
35091
+ await fs16.chmod(filePath, 384);
34840
35092
  }
34841
35093
  async function persistProvisionedMailbox(mailbox, credentialPath) {
34842
35094
  if (!mailbox?.address || !mailbox.password) return false;
@@ -34857,7 +35109,7 @@ async function persistProvisionedMailbox(mailbox, credentialPath) {
34857
35109
  async function clearMailCredentials(credentialPath) {
34858
35110
  const filePath = resolveMailCredentialPath(credentialPath);
34859
35111
  try {
34860
- await fs15.unlink(filePath);
35112
+ await fs16.unlink(filePath);
34861
35113
  } catch (err) {
34862
35114
  if (err.code !== "ENOENT") throw err;
34863
35115
  }
@@ -34871,7 +35123,11 @@ var init_mail_credential_store = __esm({
34871
35123
  });
34872
35124
 
34873
35125
  // src/tools/register-peer.ts
34874
- import crypto6 from "node:crypto";
35126
+ import crypto7 from "node:crypto";
35127
+ function unresolvedSessionReason(r) {
35128
+ if (r.sessionId) return null;
35129
+ return r.candidateCount === 0 ? "no-candidates" : "ambiguous";
35130
+ }
34875
35131
  function registerRegisterPeerTool(server) {
34876
35132
  server.registerTool(
34877
35133
  "cogent_register_peer",
@@ -34883,6 +35139,9 @@ function registerRegisterPeerTool(server) {
34883
35139
  sessionId: import_zod3.z.string().describe("Claude Code session ID (used with --resume)"),
34884
35140
  cwd: import_zod3.z.string().describe("Absolute path to the project working directory"),
34885
35141
  label: import_zod3.z.string().describe("Human-readable label, e.g. 'Cogent_Backend' or 'Cogent_Frontend'"),
35142
+ peerSecret: import_zod3.z.string().optional().describe(
35143
+ "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."
35144
+ ),
34886
35145
  mode: import_zod3.z.enum(["observer", "agent"]).optional().default("agent").describe(
34887
35146
  '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.'
34888
35147
  ),
@@ -34897,7 +35156,7 @@ function registerRegisterPeerTool(server) {
34897
35156
  openWorldHint: false
34898
35157
  }
34899
35158
  },
34900
- async ({ peerId, sessionId, cwd, label, mode, role }) => {
35159
+ async ({ peerId, sessionId, cwd, label, mode, role, peerSecret: argPeerSecret }) => {
34901
35160
  try {
34902
35161
  const config2 = getConfig();
34903
35162
  const hasExplicitCloudSession = !!config2.COGENT_SESSION_ID || process.env.COGENT_TRUST_PERSISTED_CREDENTIALS === "1";
@@ -34930,7 +35189,7 @@ function registerRegisterPeerTool(server) {
34930
35189
  }
34931
35190
  }
34932
35191
  if (!cloudSessionId) {
34933
- autoSecret = crypto6.randomBytes(16).toString("hex");
35192
+ autoSecret = crypto7.randomBytes(16).toString("hex");
34934
35193
  const resp = await fetch(
34935
35194
  `${endpoint}/api/sessions`,
34936
35195
  {
@@ -34990,6 +35249,14 @@ function registerRegisterPeerTool(server) {
34990
35249
  }
34991
35250
  const workspaceId = deriveWorkspaceId(cwd);
34992
35251
  const threadId = sessionId;
35252
+ let storedPeerSecret = argPeerSecret;
35253
+ if (storedPeerSecret === void 0 && isCloudMode()) {
35254
+ try {
35255
+ storedPeerSecret = (await loadCredentials())?.peerSecret;
35256
+ } catch {
35257
+ storedPeerSecret = void 0;
35258
+ }
35259
+ }
34993
35260
  const peer = await backend.registerPeer(
34994
35261
  peerId,
34995
35262
  sessionId,
@@ -35000,15 +35267,18 @@ function registerRegisterPeerTool(server) {
35000
35267
  channelSessionId,
35001
35268
  capabilities,
35002
35269
  workspaceId,
35003
- threadId
35270
+ threadId,
35271
+ storedPeerSecret
35004
35272
  );
35005
35273
  if (isCloudMode()) {
35006
35274
  try {
35007
35275
  const creds = await loadCredentials();
35008
- if (creds && creds.peerId !== peerId) {
35276
+ const issuedSecret = peer?.peerSecret;
35277
+ if (creds && (creds.peerId !== peerId || issuedSecret && creds.peerSecret !== issuedSecret)) {
35009
35278
  await saveCredentials({
35010
35279
  ...creds,
35011
35280
  peerId,
35281
+ ...issuedSecret ? { peerSecret: issuedSecret } : {},
35012
35282
  savedAt: (/* @__PURE__ */ new Date()).toISOString()
35013
35283
  });
35014
35284
  }
@@ -35036,16 +35306,17 @@ function registerRegisterPeerTool(server) {
35036
35306
  logger.info(
35037
35307
  `Register: session resolved ${resolution.decision}/${resolution.confidence} (${resolution.candidateCount} candidate[s]) -> ${localSessionId}`
35038
35308
  );
35039
- const noLocalSession = resolution.candidateCount === 0;
35040
- if (noLocalSession) {
35309
+ const unresolved = unresolvedSessionReason(resolution);
35310
+ if (unresolved) {
35041
35311
  try {
35042
35312
  const base = getConfig().COGENT_PLATFORM === "cc" ? await transcriptBaseSummary() : "(codex: scans CODEX_HOME/sessions)";
35313
+ 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`;
35043
35314
  logger.warn(
35044
- `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.`
35315
+ `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.`
35045
35316
  );
35046
35317
  } catch (err) {
35047
35318
  logger.warn(
35048
- `Register: NO local session found for cwd=${cwd} (auto-wake WILL be refused); could not describe the transcript base: ${err.message}`
35319
+ `Register: unresolved session (${unresolved}) for cwd=${cwd} (auto-wake WILL be refused); could not describe the transcript base: ${err.message}`
35049
35320
  );
35050
35321
  }
35051
35322
  }
@@ -35059,8 +35330,8 @@ function registerRegisterPeerTool(server) {
35059
35330
  confidence: resolution.confidence,
35060
35331
  candidateCount: resolution.candidateCount,
35061
35332
  // Only on the broken path — keeps the happy-path result terse.
35062
- ...noLocalSession ? {
35063
- 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.",
35333
+ ...unresolved ? {
35334
+ 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.",
35064
35335
  scannedPaths: resolution.probedPaths ?? []
35065
35336
  } : {}
35066
35337
  }
@@ -35276,8 +35547,9 @@ function registerSendMessageTool(server) {
35276
35547
  if (isCloud && success2) {
35277
35548
  const effectiveAckTimeout = Math.min(ACK_TIMEOUT_MS, config2.COGENT_TIMEOUT_MS);
35278
35549
  const fastPathMs = Math.min(config2.COGENT_FASTPATH_MS, config2.COGENT_TIMEOUT_MS);
35279
- const ackPromise = autoRelay.waitForDeliveryAck(toPeerId, effectiveAckTimeout);
35280
35550
  const responsePromise = autoRelay.waitForResponse(toPeerId, fastPathMs);
35551
+ responsePromise.catch(() => {
35552
+ });
35281
35553
  let outboundRecord;
35282
35554
  try {
35283
35555
  outboundRecord = await backend.recordMessage({
@@ -35293,6 +35565,11 @@ function registerSendMessageTool(server) {
35293
35565
  autoRelay.clearExpectation(toPeerId);
35294
35566
  throw recordErr;
35295
35567
  }
35568
+ const ackPromise = autoRelay.waitForDeliveryAck(
35569
+ toPeerId,
35570
+ outboundRecord.id,
35571
+ effectiveAckTimeout
35572
+ );
35296
35573
  let ackReceived = false;
35297
35574
  try {
35298
35575
  await ackPromise;
@@ -35473,6 +35750,7 @@ var init_message = __esm({
35473
35750
  timestamp: import_zod6.z.string().datetime().describe("ISO 8601 message timestamp"),
35474
35751
  durationMs: import_zod6.z.number().nullable().describe("Round-trip duration in ms"),
35475
35752
  success: import_zod6.z.boolean().describe("Whether delivery succeeded"),
35753
+ pending: import_zod6.z.boolean().optional().describe("Delivered+acked; the reply is still expected"),
35476
35754
  error: import_zod6.z.string().nullable().describe("Error message if delivery failed"),
35477
35755
  originPlatform: import_zod6.z.enum(["cc", "codex", "slack", "gchat", "web", "gemini", "whatsapp", "telegram", "discord"]).optional().describe("Platform that originated this message"),
35478
35756
  attachments: import_zod6.z.array(AttachmentSchema).optional().describe("Cogent Mail M2.5 \u2014 file references (byte transport = email)")
@@ -35562,15 +35840,31 @@ var init_errors5 = __esm({
35562
35840
  });
35563
35841
 
35564
35842
  // cogent/dist/schemas/api-requests.js
35565
- var import_zod10, CreateSessionRequestSchema, JoinSessionRequestSchema, ResolveSessionRequestSchema, RegisterPeerRequestSchema, DeregisterPeerRequestSchema, SendMessageRequestSchema, GetHistoryQuerySchema, PollQuerySchema;
35843
+ var import_zod10, FIELD_LIMITS, CreateSessionRequestSchema, JoinSessionRequestSchema, ResolveSessionRequestSchema, RegisterPeerRequestSchema, DeregisterPeerRequestSchema, SendMessageRequestSchema, GetHistoryQuerySchema, PollQuerySchema;
35566
35844
  var init_api_requests = __esm({
35567
35845
  "cogent/dist/schemas/api-requests.js"() {
35568
35846
  "use strict";
35569
35847
  import_zod10 = __toESM(require_zod(), 1);
35848
+ FIELD_LIMITS = {
35849
+ /** A shared channel secret. Long passphrases are fine; novels are not. */
35850
+ SECRET_MAX: 1024,
35851
+ /** Org_ID. */
35852
+ ORG_ID_MAX: 256,
35853
+ /** Peer identifier. */
35854
+ PEER_ID_MAX: 128,
35855
+ /** Human-readable peer label. */
35856
+ PEER_LABEL_MAX: 256,
35857
+ /** Working directory — comfortably above PATH_MAX (4096) on every platform we support. */
35858
+ CWD_MAX: 8192,
35859
+ /** A single message body. */
35860
+ MESSAGE_MAX: 1048576,
35861
+ /** One platform-identity value (e.g. a Slack user id). */
35862
+ PLATFORM_IDENTITY_VALUE_MAX: 512
35863
+ };
35570
35864
  CreateSessionRequestSchema = import_zod10.z.object({
35571
35865
  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)"),
35572
- secret: import_zod10.z.string().min(8).describe("Shared secret for joining the session (min 8 chars)"),
35573
- orgId: import_zod10.z.string().min(1).optional().describe("Optional Org_ID -- gates the channel to Business Edition orgs (multi-tenant relay only)")
35866
+ secret: import_zod10.z.string().min(8).max(FIELD_LIMITS.SECRET_MAX).describe("Shared secret for joining the session (min 8 chars)"),
35867
+ 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)")
35574
35868
  }).strict();
35575
35869
  JoinSessionRequestSchema = import_zod10.z.object({
35576
35870
  secret: import_zod10.z.string().min(1).describe("Shared secret for the session"),
@@ -35581,13 +35875,14 @@ var init_api_requests = __esm({
35581
35875
  orgId: import_zod10.z.string().min(1).optional().describe("Optional Org_ID -- required to resolve an Org_ID-gated business channel")
35582
35876
  }).strict();
35583
35877
  RegisterPeerRequestSchema = import_zod10.z.object({
35584
- peerId: import_zod10.z.string().min(1).describe("Unique peer identifier"),
35585
- cwd: import_zod10.z.string().describe("Peer working directory path"),
35586
- label: import_zod10.z.string().describe("Human-readable peer label"),
35878
+ peerId: import_zod10.z.string().min(1).max(FIELD_LIMITS.PEER_ID_MAX).describe("Unique peer identifier"),
35879
+ peerSecret: import_zod10.z.string().min(1).max(256).optional().describe("AUD-003: proof of ownership when reclaiming a peer from a different token"),
35880
+ cwd: import_zod10.z.string().max(FIELD_LIMITS.CWD_MAX).describe("Peer working directory path"),
35881
+ label: import_zod10.z.string().max(FIELD_LIMITS.PEER_LABEL_MAX).describe("Human-readable peer label"),
35587
35882
  platform: import_zod10.z.enum(["cc", "codex", "slack", "gchat", "web", "gemini"]).optional().describe("Platform this peer belongs to"),
35588
35883
  type: import_zod10.z.enum(["agent", "human"]).optional().describe("Peer type"),
35589
35884
  transport: import_zod10.z.enum(["ws", "slack-bot", "gchat-bot", "websocket"]).optional().describe("Transport mechanism"),
35590
- platformIdentity: import_zod10.z.record(import_zod10.z.string(), import_zod10.z.string()).optional().describe("Platform-specific identity fields"),
35885
+ 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"),
35591
35886
  role: import_zod10.z.string().max(64).optional().describe("A2A role advertised by this peer (Agent Card)"),
35592
35887
  capabilities: import_zod10.z.array(import_zod10.z.string().max(64)).max(16).optional().describe("Capability strings \u2192 A2A Agent Card skills[]")
35593
35888
  }).strict();
@@ -35595,8 +35890,11 @@ var init_api_requests = __esm({
35595
35890
  SendMessageRequestSchema = import_zod10.z.object({
35596
35891
  fromPeerId: import_zod10.z.string().min(1).describe("Sender peer ID"),
35597
35892
  toPeerId: import_zod10.z.string().min(1).describe("Recipient peer ID (use '*' for broadcast)"),
35598
- message: import_zod10.z.string().min(1).describe("Message content"),
35599
- originPlatform: import_zod10.z.enum(["cc", "codex", "slack", "gchat", "web", "gemini"]).optional().describe("Platform originating this message")
35893
+ message: import_zod10.z.string().min(1).max(FIELD_LIMITS.MESSAGE_MAX).describe("Message content"),
35894
+ originPlatform: import_zod10.z.enum(["cc", "codex", "slack", "gchat", "web", "gemini"]).optional().describe("Platform originating this message"),
35895
+ success: import_zod10.z.boolean().optional().describe("Delivery outcome as reported by the sender"),
35896
+ error: import_zod10.z.string().nullable().optional().describe("Failure code as reported by the sender"),
35897
+ durationMs: import_zod10.z.number().int().nonnegative().nullable().optional().describe("Sender-measured duration")
35600
35898
  }).strict();
35601
35899
  GetHistoryQuerySchema = import_zod10.z.object({
35602
35900
  peerId: import_zod10.z.string().optional().describe("Filter messages by peer ID"),
@@ -36206,8 +36504,69 @@ var init_health_check2 = __esm({
36206
36504
  }
36207
36505
  });
36208
36506
 
36507
+ // src/tools/wake-log.ts
36508
+ function registerWakeLogTool(server) {
36509
+ server.registerTool(
36510
+ "cogent_wake_log",
36511
+ {
36512
+ title: "Wake Log",
36513
+ description: "Show the auto-resume (wake) sessions Cogent has run in this working directory: which peer's message triggered each one, when, how long it took, the outcome, and WHICH FILES IT CHANGED. Use this when files changed with no corresponding edit in your session, when you need to establish who authored a change before committing it, or to check whether a wake is running now.",
36514
+ inputSchema: {
36515
+ limit: import_zod14.z.number().int().positive().optional().describe("Return only the most recent N wakes (default: all retained)."),
36516
+ peerId: import_zod14.z.string().optional().describe(
36517
+ "Your local peer id. Required to list wakes running RIGHT NOW \u2014 in-flight markers are keyed per peer, and two peers can share one directory."
36518
+ ),
36519
+ cwd: import_zod14.z.string().optional().describe("Directory to inspect (default: this bridge's working directory).")
36520
+ },
36521
+ annotations: {
36522
+ readOnlyHint: true,
36523
+ destructiveHint: false,
36524
+ idempotentHint: true,
36525
+ openWorldHint: false
36526
+ }
36527
+ },
36528
+ async ({ limit, peerId, cwd }) => {
36529
+ try {
36530
+ const target = cwd ?? process.cwd();
36531
+ const wakes = readWakeLog(target, limit);
36532
+ const mutatingWakeCount = wakes.filter(
36533
+ (w) => Array.isArray(w.filesChanged) && w.filesChanged.length > 0
36534
+ ).length;
36535
+ const active = peerId ? listWakeInFlight(peerId, target) : [];
36536
+ return successResult({
36537
+ cwd: target,
36538
+ count: wakes.length,
36539
+ mutatingWakeCount,
36540
+ wakes,
36541
+ active,
36542
+ ...peerId ? {} : { activeHint: "Pass peerId to see wakes running right now (markers are per-peer)." },
36543
+ note: "filesChanged is a git snapshot diff across each wake's window. If you were editing the same tree at the same time, your edits appear here too \u2014 it is what changed while the wake ran, not proof of authorship. null means the directory is not a git worktree."
36544
+ });
36545
+ } catch (err) {
36546
+ return errorResult(
36547
+ new BridgeError(
36548
+ "STATE_CORRUPT" /* STATE_CORRUPT */,
36549
+ `Could not read the wake log: ${err instanceof Error ? err.message : String(err)}`,
36550
+ "The log lives at ~/.cogent/wake-log/<sha256(cwd)[..16]>.jsonl \u2014 check it is readable."
36551
+ )
36552
+ );
36553
+ }
36554
+ }
36555
+ );
36556
+ }
36557
+ var import_zod14;
36558
+ var init_wake_log2 = __esm({
36559
+ "src/tools/wake-log.ts"() {
36560
+ "use strict";
36561
+ import_zod14 = __toESM(require_zod(), 1);
36562
+ init_errors4();
36563
+ init_wake_log();
36564
+ init_wake_inflight();
36565
+ }
36566
+ });
36567
+
36209
36568
  // src/tools/create-session.ts
36210
- import crypto7 from "node:crypto";
36569
+ import crypto8 from "node:crypto";
36211
36570
  function registerCreateSessionTool(server) {
36212
36571
  server.registerTool(
36213
36572
  "cogent_create_session",
@@ -36215,8 +36574,8 @@ function registerCreateSessionTool(server) {
36215
36574
  title: "Create Cloud Session",
36216
36575
  description: "Create a new cloud bridge session on the relay server. Returns session ID and secret for other peers to join. Only available when COGENT_ENDPOINT is configured.",
36217
36576
  inputSchema: {
36218
- label: import_zod14.z.string().optional().describe("Human-readable label for the session"),
36219
- secret: import_zod14.z.string().min(8).optional().describe(
36577
+ label: import_zod15.z.string().optional().describe("Human-readable label for the session"),
36578
+ secret: import_zod15.z.string().min(8).optional().describe(
36220
36579
  "Shared secret for session authentication (min 8 chars). Auto-generated if omitted."
36221
36580
  )
36222
36581
  },
@@ -36238,7 +36597,7 @@ function registerCreateSessionTool(server) {
36238
36597
  )
36239
36598
  );
36240
36599
  }
36241
- const sessionSecret = secret ?? crypto7.randomBytes(16).toString("hex");
36600
+ const sessionSecret = secret ?? crypto8.randomBytes(16).toString("hex");
36242
36601
  const resp = await fetch(
36243
36602
  `${config2.COGENT_ENDPOINT}/api/sessions`,
36244
36603
  {
@@ -36309,11 +36668,11 @@ COGENT_PLATFORM=${config2.COGENT_PLATFORM}`,
36309
36668
  }
36310
36669
  );
36311
36670
  }
36312
- var import_zod14;
36671
+ var import_zod15;
36313
36672
  var init_create_session = __esm({
36314
36673
  "src/tools/create-session.ts"() {
36315
36674
  "use strict";
36316
- import_zod14 = __toESM(require_zod(), 1);
36675
+ import_zod15 = __toESM(require_zod(), 1);
36317
36676
  init_config();
36318
36677
  init_errors4();
36319
36678
  init_credential_store();
@@ -36329,11 +36688,11 @@ function registerJoinSessionTool(server) {
36329
36688
  title: "Join Cloud Channel",
36330
36689
  description: 'Join an existing cloud bridge channel by its channel name (e.g. "mt-space") and shared secret. The channel name is the human-readable label provided by the user, NOT a UUID. Do NOT pass a Claude Code session ID here \u2014 use the channel/space name the user gave you.',
36331
36690
  inputSchema: {
36332
- channel: import_zod15.z.string().describe(
36691
+ channel: import_zod16.z.string().describe(
36333
36692
  'The channel name to join (e.g. "mt-space"). This is the human-readable name the user provided, NOT a UUID. Do NOT use a Claude Code session ID here.'
36334
36693
  ),
36335
- secret: import_zod15.z.string().min(1).describe("The shared secret / password for the channel"),
36336
- orgId: import_zod15.z.string().min(1).optional().describe(
36694
+ secret: import_zod16.z.string().min(1).describe("The shared secret / password for the channel"),
36695
+ orgId: import_zod16.z.string().min(1).optional().describe(
36337
36696
  "Org_ID \u2014 the 3rd credential for a Team (business) channel. Provide it for team channels; omit for free channels."
36338
36697
  )
36339
36698
  },
@@ -36452,11 +36811,11 @@ function registerJoinSessionTool(server) {
36452
36811
  }
36453
36812
  );
36454
36813
  }
36455
- var import_zod15, UUID_V4_RE2;
36814
+ var import_zod16, UUID_V4_RE2;
36456
36815
  var init_join_session = __esm({
36457
36816
  "src/tools/join-session.ts"() {
36458
36817
  "use strict";
36459
- import_zod15 = __toESM(require_zod(), 1);
36818
+ import_zod16 = __toESM(require_zod(), 1);
36460
36819
  init_config();
36461
36820
  init_errors4();
36462
36821
  init_credential_store();
@@ -36520,12 +36879,12 @@ function registerSetupMailTool(server) {
36520
36879
  title: "Configure this agent's mailbox",
36521
36880
  description: "Store the agent's mailbox address + password (from the admin Mail panel) locally so cogent_send_mail and cogent_fetch_mail can use them. Hosts default to mail.cogent.tools (IMAP 993 / SMTP 465).",
36522
36881
  inputSchema: {
36523
- address: import_zod16.z.string().describe("Full mailbox address, e.g. claude-agent-backend@mail.cogent.tools"),
36524
- password: import_zod16.z.string().describe("Mailbox password (shown once in the admin Mail panel on create/rotate)"),
36525
- imapHost: import_zod16.z.string().optional().describe(`IMAP host (default ${DEFAULT_MAIL_HOST})`),
36526
- imapPort: import_zod16.z.number().int().positive().optional().describe("IMAP port (default 993)"),
36527
- smtpHost: import_zod16.z.string().optional().describe(`SMTP host (default ${DEFAULT_MAIL_HOST})`),
36528
- smtpPort: import_zod16.z.number().int().positive().optional().describe("SMTP submission port (default 465)")
36882
+ address: import_zod17.z.string().describe("Full mailbox address, e.g. claude-agent-backend@mail.cogent.tools"),
36883
+ password: import_zod17.z.string().describe("Mailbox password (shown once in the admin Mail panel on create/rotate)"),
36884
+ imapHost: import_zod17.z.string().optional().describe(`IMAP host (default ${DEFAULT_MAIL_HOST})`),
36885
+ imapPort: import_zod17.z.number().int().positive().optional().describe("IMAP port (default 993)"),
36886
+ smtpHost: import_zod17.z.string().optional().describe(`SMTP host (default ${DEFAULT_MAIL_HOST})`),
36887
+ smtpPort: import_zod17.z.number().int().positive().optional().describe("SMTP submission port (default 465)")
36529
36888
  },
36530
36889
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
36531
36890
  },
@@ -36549,11 +36908,11 @@ function registerSetupMailTool(server) {
36549
36908
  }
36550
36909
  );
36551
36910
  }
36552
- var import_zod16, DEFAULT_MAIL_HOST;
36911
+ var import_zod17, DEFAULT_MAIL_HOST;
36553
36912
  var init_setup_mail = __esm({
36554
36913
  "src/tools/setup-mail.ts"() {
36555
36914
  "use strict";
36556
- import_zod16 = __toESM(require_zod(), 1);
36915
+ import_zod17 = __toESM(require_zod(), 1);
36557
36916
  init_errors4();
36558
36917
  init_mail_credential_store();
36559
36918
  init_validate();
@@ -36562,13 +36921,13 @@ var init_setup_mail = __esm({
36562
36921
  });
36563
36922
 
36564
36923
  // src/mail/mail-sender.ts
36565
- import fs16 from "node:fs/promises";
36566
- import path17 from "node:path";
36924
+ import fs17 from "node:fs/promises";
36925
+ import path18 from "node:path";
36567
36926
  async function buildAndSendMail(sender, creds, params) {
36568
36927
  const attachments = [];
36569
36928
  for (const p of params.attachmentPaths ?? []) {
36570
- const content = await fs16.readFile(p);
36571
- attachments.push({ filename: path17.basename(p), content });
36929
+ const content = await fs17.readFile(p);
36930
+ attachments.push({ filename: path18.basename(p), content });
36572
36931
  }
36573
36932
  const { messageId } = await sender.send({
36574
36933
  from: creds.address,
@@ -36644,10 +37003,10 @@ function registerSendMailTool(server, deps = {}) {
36644
37003
  title: "Send an email from this agent's mailbox",
36645
37004
  description: "Send an email (with optional file attachments) from this agent's Cogent mailbox to another address. Requires cogent_setup_mail to have stored the mailbox credentials first.",
36646
37005
  inputSchema: {
36647
- to: import_zod17.z.string().describe("Recipient email address, e.g. other-agent-backend@mail.cogent.tools"),
36648
- subject: import_zod17.z.string().describe("Email subject"),
36649
- body: import_zod17.z.string().describe("Plain-text email body"),
36650
- attachments: import_zod17.z.array(import_zod17.z.string()).optional().describe("Optional local file paths to attach (each is read and attached by basename)")
37006
+ to: import_zod18.z.string().describe("Recipient email address, e.g. other-agent-backend@mail.cogent.tools"),
37007
+ subject: import_zod18.z.string().describe("Email subject"),
37008
+ body: import_zod18.z.string().describe("Plain-text email body"),
37009
+ attachments: import_zod18.z.array(import_zod18.z.string()).optional().describe("Optional local file paths to attach (each is read and attached by basename)")
36651
37010
  },
36652
37011
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
36653
37012
  },
@@ -36682,11 +37041,11 @@ function registerSendMailTool(server, deps = {}) {
36682
37041
  }
36683
37042
  );
36684
37043
  }
36685
- var import_zod17;
37044
+ var import_zod18;
36686
37045
  var init_send_mail = __esm({
36687
37046
  "src/tools/send-mail.ts"() {
36688
37047
  "use strict";
36689
- import_zod17 = __toESM(require_zod(), 1);
37048
+ import_zod18 = __toESM(require_zod(), 1);
36690
37049
  init_errors4();
36691
37050
  init_mail_credential_store();
36692
37051
  init_mail_sender();
@@ -36696,16 +37055,16 @@ var init_send_mail = __esm({
36696
37055
  });
36697
37056
 
36698
37057
  // src/mail/mail-fetcher.ts
36699
- import fs17 from "node:fs/promises";
36700
- import path18 from "node:path";
37058
+ import fs18 from "node:fs/promises";
37059
+ import path19 from "node:path";
36701
37060
  async function saveAttachments(attachments, downloadDir) {
36702
37061
  if (attachments.length === 0) return [];
36703
- await fs17.mkdir(downloadDir, { recursive: true });
37062
+ await fs18.mkdir(downloadDir, { recursive: true });
36704
37063
  const saved = [];
36705
37064
  for (const a of attachments) {
36706
- const name = path18.basename(a.filename) || "attachment";
36707
- const savedPath = path18.join(downloadDir, name);
36708
- await fs17.writeFile(savedPath, a.content);
37065
+ const name = path19.basename(a.filename) || "attachment";
37066
+ const savedPath = path19.join(downloadDir, name);
37067
+ await fs18.writeFile(savedPath, a.content);
36709
37068
  saved.push({ filename: name, savedPath, size: a.content.length });
36710
37069
  }
36711
37070
  return saved;
@@ -36836,8 +37195,8 @@ var init_mail_fetcher = __esm({
36836
37195
  });
36837
37196
 
36838
37197
  // src/tools/fetch-mail.ts
36839
- import os12 from "node:os";
36840
- import path19 from "node:path";
37198
+ import os13 from "node:os";
37199
+ import path20 from "node:path";
36841
37200
  function registerFetchMailTool(server, deps = {}) {
36842
37201
  const makeFetcher = deps.fetcherFactory ?? ((creds) => new ImapflowMailFetcher(creds));
36843
37202
  server.registerTool(
@@ -36846,11 +37205,11 @@ function registerFetchMailTool(server, deps = {}) {
36846
37205
  title: "Read this agent's mailbox",
36847
37206
  description: "List messages in this agent's Cogent mailbox, or fetch one message (text + attachments saved locally) by uid. Requires cogent_setup_mail to have stored the mailbox credentials.",
36848
37207
  inputSchema: {
36849
- action: import_zod18.z.enum(["list", "fetch"]).optional().describe("'list' (default) or 'fetch' one message"),
36850
- uid: import_zod18.z.number().int().positive().optional().describe("Message uid to fetch (required when action='fetch')"),
36851
- unreadOnly: import_zod18.z.boolean().optional().describe("List only unread messages (default false)"),
36852
- limit: import_zod18.z.number().int().positive().optional().describe("Max messages to list (default 25)"),
36853
- downloadDir: import_zod18.z.string().optional().describe(`Directory to save fetched attachments (default ${DEFAULT_DOWNLOAD_DIR})`)
37208
+ action: import_zod19.z.enum(["list", "fetch"]).optional().describe("'list' (default) or 'fetch' one message"),
37209
+ uid: import_zod19.z.number().int().positive().optional().describe("Message uid to fetch (required when action='fetch')"),
37210
+ unreadOnly: import_zod19.z.boolean().optional().describe("List only unread messages (default false)"),
37211
+ limit: import_zod19.z.number().int().positive().optional().describe("Max messages to list (default 25)"),
37212
+ downloadDir: import_zod19.z.string().optional().describe(`Directory to save fetched attachments (default ${DEFAULT_DOWNLOAD_DIR})`)
36854
37213
  },
36855
37214
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
36856
37215
  },
@@ -36889,17 +37248,17 @@ function registerFetchMailTool(server, deps = {}) {
36889
37248
  }
36890
37249
  );
36891
37250
  }
36892
- var import_zod18, DEFAULT_DOWNLOAD_DIR;
37251
+ var import_zod19, DEFAULT_DOWNLOAD_DIR;
36893
37252
  var init_fetch_mail = __esm({
36894
37253
  "src/tools/fetch-mail.ts"() {
36895
37254
  "use strict";
36896
- import_zod18 = __toESM(require_zod(), 1);
37255
+ import_zod19 = __toESM(require_zod(), 1);
36897
37256
  init_errors4();
36898
37257
  init_mail_credential_store();
36899
37258
  init_mail_fetcher();
36900
37259
  init_auth_error();
36901
37260
  init_validate();
36902
- DEFAULT_DOWNLOAD_DIR = path19.join(os12.homedir(), ".cogent", "mail-downloads");
37261
+ DEFAULT_DOWNLOAD_DIR = path20.join(os13.homedir(), ".cogent", "mail-downloads");
36903
37262
  }
36904
37263
  });
36905
37264
 
@@ -36999,6 +37358,7 @@ async function main() {
36999
37358
  registerListPeersTool(server);
37000
37359
  registerGetHistoryTool(server);
37001
37360
  registerHealthCheckTool(server);
37361
+ registerWakeLogTool(server);
37002
37362
  registerCreateSessionTool(server);
37003
37363
  registerJoinSessionTool(server);
37004
37364
  registerSetupMailTool(server);
@@ -37071,6 +37431,7 @@ var init_index = __esm({
37071
37431
  init_get_history();
37072
37432
  init_deregister_peer();
37073
37433
  init_health_check2();
37434
+ init_wake_log2();
37074
37435
  init_create_session();
37075
37436
  init_join_session();
37076
37437
  init_setup_mail();