@essentialai/cogent-plugin 3.20.5 → 3.20.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cogent",
3
- "version": "3.20.5",
3
+ "version": "3.20.6",
4
4
  "description": "Inter-session communication bridge for Claude Code with Slack integration. Enables CC agents and Slack team members to communicate in real time.",
5
5
  "author": {
6
6
  "name": "Essential AI Solutions",
@@ -1259,8 +1259,8 @@ var init_parseUtil = __esm({
1259
1259
  init_errors();
1260
1260
  init_en();
1261
1261
  makeIssue = (params) => {
1262
- const { data, path: path18, errorMaps, issueData } = params;
1263
- const fullPath = [...path18, ...issueData.path || []];
1262
+ const { data, path: path19, errorMaps, issueData } = params;
1263
+ const fullPath = [...path19, ...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, path18, key) {
1543
+ constructor(parent, value, path19, key) {
1544
1544
  this._cachedPath = [];
1545
1545
  this.parent = parent;
1546
1546
  this.data = value;
1547
- this._path = path18;
1547
+ this._path = path19;
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, path18) {
5000
- if (!path18)
4999
+ function getElementAtPath(obj, path19) {
5000
+ if (!path19)
5001
5001
  return obj;
5002
- return path18.reduce((acc, key) => acc?.[key], obj);
5002
+ return path19.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(path18, issues) {
5251
+ function prefixIssues(path19, issues) {
5252
5252
  return issues.map((iss) => {
5253
5253
  var _a;
5254
5254
  (_a = iss).path ?? (_a.path = []);
5255
- iss.path.unshift(path18);
5255
+ iss.path.unshift(path19);
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(path18) {
17062
- let input = path18;
17061
+ function removeDotSegments(path19) {
17062
+ let input = path19;
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 [path18, query] = wsComponent.resourceName.split("?");
17262
- wsComponent.path = path18 && path18 !== "/" ? path18 : void 0;
17261
+ const [path19, query] = wsComponent.resourceName.split("?");
17262
+ wsComponent.path = path19 && path19 !== "/" ? path19 : 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, fs16, exportName) {
20615
+ function addFormats(ajv, list, fs17, 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, fs16[f]);
20620
+ ajv.addFormat(f, fs17[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: path18, errorMaps, issueData } = params;
21733
- const fullPath = [...path18, ...issueData.path || []];
21732
+ const { data, path: path19, errorMaps, issueData } = params;
21733
+ const fullPath = [...path19, ...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, path18, key) {
21887
+ constructor(parent, value, path19, key) {
21888
21888
  this._cachedPath = [];
21889
21889
  this.parent = parent;
21890
21890
  this.data = value;
21891
- this._path = path18;
21891
+ this._path = path19;
21892
21892
  this._key = key;
21893
21893
  }
21894
21894
  get path() {
@@ -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.20.5") {
26393
- return "3.20.5";
26392
+ if ("3.20.6") {
26393
+ return "3.20.6";
26394
26394
  }
26395
26395
  try {
26396
26396
  const require2 = createRequire(import.meta.url);
@@ -27624,7 +27624,7 @@ var init_http_backend = __esm({
27624
27624
  * in cloud mode we ignore it and use this.sessionId (the cloud session).
27625
27625
  */
27626
27626
  async registerPeer(peerId, _sessionId, cwd, label, clientVersion, mode, _channelSessionId, capabilities, workspaceId, threadId) {
27627
- const path18 = PATHS.peers.replace(":sessionId", this.sessionId);
27627
+ const path19 = PATHS.peers.replace(":sessionId", this.sessionId);
27628
27628
  const body = {
27629
27629
  peerId,
27630
27630
  cwd,
@@ -27652,7 +27652,7 @@ var init_http_backend = __esm({
27652
27652
  body.role = roleCap.slice("role:".length);
27653
27653
  }
27654
27654
  }
27655
- return this.http.post(path18, body);
27655
+ return this.http.post(path19, body);
27656
27656
  }
27657
27657
  /**
27658
27658
  * Deregister a peer from the cloud session.
@@ -27664,9 +27664,9 @@ var init_http_backend = __esm({
27664
27664
  * local mail creds. A 404 (peer not found) resolves to `{ removed: false, ... }`, not a throw.
27665
27665
  */
27666
27666
  async deregisterPeer(peerId) {
27667
- const path18 = PATHS.peer.replace(":sessionId", this.sessionId).replace(":peerId", peerId);
27667
+ const path19 = PATHS.peer.replace(":sessionId", this.sessionId).replace(":peerId", peerId);
27668
27668
  try {
27669
- const headers = await this.http.delete(path18, {});
27669
+ const headers = await this.http.delete(path19, {});
27670
27670
  const mailboxDeprovisioned = headers.get(MAILBOX_DEPROVISIONED_HEADER) === "true";
27671
27671
  return { removed: true, mailboxDeprovisioned };
27672
27672
  } catch {
@@ -27687,8 +27687,8 @@ var init_http_backend = __esm({
27687
27687
  * GET /api/sessions/:sessionId/peers
27688
27688
  */
27689
27689
  async listPeers() {
27690
- const path18 = PATHS.peers.replace(":sessionId", this.sessionId);
27691
- const result = await this.http.get(path18);
27690
+ const path19 = PATHS.peers.replace(":sessionId", this.sessionId);
27691
+ const result = await this.http.get(path19);
27692
27692
  return result.peers;
27693
27693
  }
27694
27694
  /**
@@ -27696,8 +27696,8 @@ var init_http_backend = __esm({
27696
27696
  * Calls the lightweight heartbeat endpoint on the cloud relay server.
27697
27697
  */
27698
27698
  async updateLastSeen(peerId) {
27699
- const path18 = PATHS.heartbeat.replace(":sessionId", this.sessionId);
27700
- await this.http.post(path18, { peerId });
27699
+ const path19 = PATHS.heartbeat.replace(":sessionId", this.sessionId);
27700
+ await this.http.post(path19, { peerId });
27701
27701
  }
27702
27702
  /**
27703
27703
  * Record a message in the cloud session.
@@ -27708,7 +27708,7 @@ var init_http_backend = __esm({
27708
27708
  * the input record to construct a full MessageRecord.
27709
27709
  */
27710
27710
  async recordMessage(record2) {
27711
- const path18 = PATHS.messages.replace(":sessionId", this.sessionId);
27711
+ const path19 = PATHS.messages.replace(":sessionId", this.sessionId);
27712
27712
  const body = {
27713
27713
  fromPeerId: record2.fromPeerId,
27714
27714
  toPeerId: record2.toPeerId,
@@ -27720,7 +27720,7 @@ var init_http_backend = __esm({
27720
27720
  if (record2.attachments && record2.attachments.length > 0) {
27721
27721
  body.attachments = record2.attachments;
27722
27722
  }
27723
- const resp = await this.http.post(path18, body);
27723
+ const resp = await this.http.post(path19, body);
27724
27724
  return {
27725
27725
  ...record2,
27726
27726
  id: resp.id,
@@ -27734,7 +27734,7 @@ var init_http_backend = __esm({
27734
27734
  * Supports optional peerId filter and limit (default 300).
27735
27735
  */
27736
27736
  async getHistory(peerId, limit, includeRelayEchoes, metadataOnly, dedupInferredEchoes) {
27737
- const path18 = PATHS.messages.replace(":sessionId", this.sessionId);
27737
+ const path19 = PATHS.messages.replace(":sessionId", this.sessionId);
27738
27738
  const query = {};
27739
27739
  if (peerId) {
27740
27740
  query.peerId = peerId;
@@ -27749,7 +27749,7 @@ var init_http_backend = __esm({
27749
27749
  if (dedupInferredEchoes) {
27750
27750
  query.dedupInferredEchoes = "true";
27751
27751
  }
27752
- const result = await this.http.get(path18, query);
27752
+ const result = await this.http.get(path19, query);
27753
27753
  return result.messages;
27754
27754
  }
27755
27755
  /**
@@ -27760,12 +27760,12 @@ var init_http_backend = __esm({
27760
27760
  * so that get-history shows the actual response (not null).
27761
27761
  */
27762
27762
  async updateMessageResponse(messageId, response, durationMs, pending) {
27763
- const path18 = PATHS.message.replace(":sessionId", this.sessionId).replace(":messageId", messageId);
27763
+ const path19 = PATHS.message.replace(":sessionId", this.sessionId).replace(":messageId", messageId);
27764
27764
  const body = { response, durationMs };
27765
27765
  if (pending === true) {
27766
27766
  body.pending = true;
27767
27767
  }
27768
- await this.http.patch(path18, body);
27768
+ await this.http.patch(path19, body);
27769
27769
  }
27770
27770
  /**
27771
27771
  * Validate a session.
@@ -27907,8 +27907,8 @@ var init_http_client = __esm({
27907
27907
  * Perform an authenticated GET request.
27908
27908
  * Appends query parameters to the URL if provided.
27909
27909
  */
27910
- async get(path18, query) {
27911
- const url = new URL(path18, this.baseUrl);
27910
+ async get(path19, query) {
27911
+ const url = new URL(path19, this.baseUrl);
27912
27912
  if (query) {
27913
27913
  for (const [k, v] of Object.entries(query)) {
27914
27914
  url.searchParams.set(k, v);
@@ -27923,8 +27923,8 @@ var init_http_client = __esm({
27923
27923
  /**
27924
27924
  * Perform an authenticated POST request with a JSON body.
27925
27925
  */
27926
- async post(path18, body) {
27927
- const url = new URL(path18, this.baseUrl);
27926
+ async post(path19, body) {
27927
+ const url = new URL(path19, this.baseUrl);
27928
27928
  const resp = await this.fetchWithTimeout(url, {
27929
27929
  method: "POST",
27930
27930
  headers: this.headers(),
@@ -27942,8 +27942,8 @@ var init_http_client = __esm({
27942
27942
  * Perform an authenticated PATCH request with a JSON body.
27943
27943
  * Used to update existing resources (e.g., message response fields).
27944
27944
  */
27945
- async patch(path18, body) {
27946
- const url = new URL(path18, this.baseUrl);
27945
+ async patch(path19, body) {
27946
+ const url = new URL(path19, this.baseUrl);
27947
27947
  const resp = await this.fetchWithTimeout(url, {
27948
27948
  method: "PATCH",
27949
27949
  headers: this.headers(),
@@ -27968,8 +27968,8 @@ var init_http_client = __esm({
27968
27968
  * "Malformed JSON in request body" 400 from cogent-server <=3.1.2 when
27969
27969
  * any client (including third-party tools) DELETEd a peer without a body.
27970
27970
  */
27971
- async delete(path18, body) {
27972
- const url = new URL(path18, this.baseUrl);
27971
+ async delete(path19, body) {
27972
+ const url = new URL(path19, this.baseUrl);
27973
27973
  const headers = {
27974
27974
  "Authorization": `Bearer ${this.token}`
27975
27975
  };
@@ -32152,14 +32152,14 @@ var init_ws_client = __esm({
32152
32152
  if (this.pollTimer) return;
32153
32153
  const poll = async () => {
32154
32154
  try {
32155
- const path18 = `/api/sessions/${this.opts.sessionId}/poll`;
32155
+ const path19 = `/api/sessions/${this.opts.sessionId}/poll`;
32156
32156
  const query = {
32157
32157
  peerId: this.opts.peerId
32158
32158
  };
32159
32159
  if (this.lastMessageId) {
32160
32160
  query.lastMessageId = this.lastMessageId;
32161
32161
  }
32162
- const result = await this.opts.http.get(path18, query);
32162
+ const result = await this.opts.http.get(path19, query);
32163
32163
  if (result.messages && result.messages.length > 0) {
32164
32164
  this.opts.onMessages(result.messages);
32165
32165
  this.lastMessageId = result.messages[result.messages.length - 1].id;
@@ -32920,6 +32920,81 @@ var init_session_resolver = __esm({
32920
32920
  }
32921
32921
  });
32922
32922
 
32923
+ // src/services/wake-inflight.ts
32924
+ import crypto3 from "node:crypto";
32925
+ import fs12 from "node:fs";
32926
+ import os7 from "node:os";
32927
+ import path13 from "node:path";
32928
+ function hashCwd(cwd) {
32929
+ return crypto3.createHash("sha256").update(path13.resolve(cwd)).digest("hex").slice(0, 16);
32930
+ }
32931
+ function wakeInflightPath(peerId, cwd = process.cwd()) {
32932
+ return path13.join(os7.homedir(), ".cogent", "wake-inflight", `${hashCwd(cwd)}.${safe(peerId)}.json`);
32933
+ }
32934
+ function ttlMs() {
32935
+ let timeout = 3e5;
32936
+ try {
32937
+ const configured = getConfig()?.COGENT_TIMEOUT_MS;
32938
+ if (typeof configured === "number" && Number.isFinite(configured) && configured > 0) timeout = configured;
32939
+ } catch {
32940
+ }
32941
+ return Math.min(timeout * INFLIGHT_TIMEOUT_FACTOR, INFLIGHT_MAX_MS);
32942
+ }
32943
+ function readFileSafe(p) {
32944
+ try {
32945
+ const parsed = JSON.parse(fs12.readFileSync(p, "utf-8"));
32946
+ return parsed && typeof parsed === "object" && parsed.messages ? parsed : { messages: {} };
32947
+ } catch {
32948
+ return { messages: {} };
32949
+ }
32950
+ }
32951
+ function writeAtomic(p, data) {
32952
+ fs12.mkdirSync(path13.dirname(p), { recursive: true });
32953
+ const tmp = `${p}.${process.pid}.tmp`;
32954
+ fs12.writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n", "utf-8");
32955
+ fs12.renameSync(tmp, p);
32956
+ }
32957
+ function prune(f, now) {
32958
+ for (const [id, rec] of Object.entries(f.messages)) {
32959
+ const ok = rec && typeof rec.startedAt === "number" && typeof rec.expiresAt === "number" && Number.isFinite(rec.expiresAt) && rec.expiresAt > now;
32960
+ if (!ok) delete f.messages[id];
32961
+ }
32962
+ }
32963
+ function markWakeInFlight(peerId, messageId, traceId, cwd = process.cwd()) {
32964
+ try {
32965
+ const now = Date.now();
32966
+ const p = wakeInflightPath(peerId, cwd);
32967
+ const cur = readFileSafe(p);
32968
+ prune(cur, now);
32969
+ cur.messages[messageId] = { startedAt: now, expiresAt: now + ttlMs(), ...traceId ? { traceId } : {} };
32970
+ writeAtomic(p, cur);
32971
+ } catch (err) {
32972
+ logger.debug(`wake-inflight: could not mark ${messageId}: ${err}`);
32973
+ }
32974
+ }
32975
+ function clearWakeInFlight(peerId, messageId, cwd = process.cwd()) {
32976
+ try {
32977
+ const p = wakeInflightPath(peerId, cwd);
32978
+ const cur = readFileSafe(p);
32979
+ delete cur.messages[messageId];
32980
+ prune(cur, Date.now());
32981
+ writeAtomic(p, cur);
32982
+ } catch (err) {
32983
+ logger.debug(`wake-inflight: could not clear ${messageId}: ${err}`);
32984
+ }
32985
+ }
32986
+ var INFLIGHT_MAX_MS, INFLIGHT_TIMEOUT_FACTOR, safe;
32987
+ var init_wake_inflight = __esm({
32988
+ "src/services/wake-inflight.ts"() {
32989
+ "use strict";
32990
+ init_config();
32991
+ init_logger();
32992
+ INFLIGHT_MAX_MS = 18e5;
32993
+ INFLIGHT_TIMEOUT_FACTOR = 3;
32994
+ safe = (s) => String(s).replace(/[^A-Za-z0-9._-]/g, "_");
32995
+ }
32996
+ });
32997
+
32923
32998
  // src/services/auto-relay.ts
32924
32999
  import { randomUUID as randomUUID2 } from "node:crypto";
32925
33000
  function buildStatusNotice(status, peerId, traceId) {
@@ -32983,6 +33058,7 @@ var init_auto_relay = __esm({
32983
33058
  init_config();
32984
33059
  init_backend();
32985
33060
  init_codex_cli();
33061
+ init_wake_inflight();
32986
33062
  init_logger();
32987
33063
  RELAY_STATUS = {
32988
33064
  QUEUED: "queued",
@@ -33390,6 +33466,9 @@ var init_auto_relay = __esm({
33390
33466
  );
33391
33467
  }
33392
33468
  }
33469
+ if (this.localPeerId && !this.awarenessOnly.has(msg.id)) {
33470
+ markWakeInFlight(this.localPeerId, msg.id, msg.traceId);
33471
+ }
33393
33472
  if (this.processing) {
33394
33473
  logger.debug(`Auto-relay: queuing message from ${msg.fromPeerId} (busy processing)`);
33395
33474
  this.queue.push(msg);
@@ -33422,8 +33501,13 @@ var init_auto_relay = __esm({
33422
33501
  async _dispatch(msg) {
33423
33502
  if (this.awarenessOnly.delete(msg.id)) {
33424
33503
  await this._injectPeerBroadcast(msg);
33425
- } else {
33504
+ return;
33505
+ }
33506
+ const inflightPeer = this.localPeerId;
33507
+ try {
33426
33508
  await this._processMessage(msg);
33509
+ } finally {
33510
+ if (inflightPeer) clearWakeInFlight(inflightPeer, msg.id);
33427
33511
  }
33428
33512
  }
33429
33513
  /**
@@ -34017,15 +34101,15 @@ __export(startup_exports, {
34017
34101
  runStartup: () => runStartup,
34018
34102
  triggerReRegistration: () => triggerReRegistration
34019
34103
  });
34020
- import os7 from "node:os";
34021
- import path13 from "node:path";
34022
- import fs12 from "node:fs/promises";
34104
+ import os8 from "node:os";
34105
+ import path14 from "node:path";
34106
+ import fs13 from "node:fs/promises";
34023
34107
  import readline2 from "node:readline";
34024
34108
  import { execFile as execFile5 } from "node:child_process";
34025
34109
  import { promisify as promisify4 } from "node:util";
34026
34110
  async function loadPersistedConfig() {
34027
34111
  try {
34028
- const raw = await fs12.readFile(PERSIST_PATH, "utf-8");
34112
+ const raw = await fs13.readFile(PERSIST_PATH, "utf-8");
34029
34113
  return JSON.parse(raw);
34030
34114
  } catch {
34031
34115
  return {};
@@ -34033,7 +34117,7 @@ async function loadPersistedConfig() {
34033
34117
  }
34034
34118
  async function savePersistedConfig(config2) {
34035
34119
  try {
34036
- await fs12.writeFile(PERSIST_PATH, JSON.stringify(config2, null, 2) + "\n", "utf-8");
34120
+ await fs13.writeFile(PERSIST_PATH, JSON.stringify(config2, null, 2) + "\n", "utf-8");
34037
34121
  } catch (err) {
34038
34122
  const msg = err instanceof Error ? err.message : String(err);
34039
34123
  logger.warn(`Could not save config to ${PERSIST_PATH}: ${msg}`);
@@ -34068,11 +34152,11 @@ First run detected. Where should cogent-bridge store its data?
34068
34152
  });
34069
34153
  }
34070
34154
  async function validateStateDir(statePath) {
34071
- await fs12.mkdir(statePath, { recursive: true });
34072
- const testFile = path13.join(statePath, ".write-test");
34155
+ await fs13.mkdir(statePath, { recursive: true });
34156
+ const testFile = path14.join(statePath, ".write-test");
34073
34157
  try {
34074
- await fs12.writeFile(testFile, "test", "utf-8");
34075
- await fs12.unlink(testFile);
34158
+ await fs13.writeFile(testFile, "test", "utf-8");
34159
+ await fs13.unlink(testFile);
34076
34160
  } catch {
34077
34161
  throw new BridgeError(
34078
34162
  "STARTUP_FAILED" /* STARTUP_FAILED */,
@@ -34218,10 +34302,10 @@ async function runStartup() {
34218
34302
  if (envStatePath) {
34219
34303
  statePath = envStatePath;
34220
34304
  } else {
34221
- const defaultPath = path13.join(os7.homedir(), ".cogent");
34305
+ const defaultPath = path14.join(os8.homedir(), ".cogent");
34222
34306
  statePath = await firstRunPrompt(defaultPath);
34223
34307
  }
34224
- if (!envStatePath && statePath !== path13.join(os7.homedir(), ".cogent")) {
34308
+ if (!envStatePath && statePath !== path14.join(os8.homedir(), ".cogent")) {
34225
34309
  process.env.COGENT_STATE_PATH = statePath;
34226
34310
  } else if (!envStatePath) {
34227
34311
  process.env.COGENT_STATE_PATH = statePath;
@@ -34299,7 +34383,7 @@ async function runStartup() {
34299
34383
  }
34300
34384
  initLogger(
34301
34385
  config2.COGENT_LOG_LEVEL,
34302
- path13.join(config2.COGENT_STATE_PATH, "logs")
34386
+ path14.join(config2.COGENT_STATE_PATH, "logs")
34303
34387
  );
34304
34388
  if (isCloud && effectiveSessionId && effectiveToken) {
34305
34389
  cloudInbox = new MessageInbox();
@@ -34360,7 +34444,7 @@ var init_startup = __esm({
34360
34444
  init_auto_relay();
34361
34445
  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;
34362
34446
  execFileAsync3 = promisify4(execFile5);
34363
- PERSIST_PATH = path13.join(os7.homedir(), ".cogent-config.json");
34447
+ PERSIST_PATH = path14.join(os8.homedir(), ".cogent-config.json");
34364
34448
  legacyWarnEmitted = false;
34365
34449
  cloudWsClient = null;
34366
34450
  cloudInbox = null;
@@ -34370,13 +34454,13 @@ var init_startup = __esm({
34370
34454
  });
34371
34455
 
34372
34456
  // src/cloud/mail-credential-store.ts
34373
- import crypto3 from "node:crypto";
34374
- import fs13 from "node:fs/promises";
34375
- import path14 from "node:path";
34376
- import os8 from "node:os";
34457
+ import crypto4 from "node:crypto";
34458
+ import fs14 from "node:fs/promises";
34459
+ import path15 from "node:path";
34460
+ import os9 from "node:os";
34377
34461
  function defaultMailCredentialPath(cwd = process.cwd()) {
34378
- const hash = crypto3.createHash("sha256").update(path14.resolve(cwd)).digest("hex").slice(0, 16);
34379
- return path14.join(os8.homedir(), ".cogent", "mail-credentials", `${hash}.json`);
34462
+ const hash = crypto4.createHash("sha256").update(path15.resolve(cwd)).digest("hex").slice(0, 16);
34463
+ return path15.join(os9.homedir(), ".cogent", "mail-credentials", `${hash}.json`);
34380
34464
  }
34381
34465
  function resolveMailCredentialPath(credentialPath) {
34382
34466
  if (credentialPath) return credentialPath;
@@ -34387,16 +34471,16 @@ function resolveMailCredentialPath(credentialPath) {
34387
34471
  async function loadMailCredentials(credentialPath) {
34388
34472
  const filePath = resolveMailCredentialPath(credentialPath);
34389
34473
  try {
34390
- return JSON.parse(await fs13.readFile(filePath, "utf-8"));
34474
+ return JSON.parse(await fs14.readFile(filePath, "utf-8"));
34391
34475
  } catch {
34392
34476
  return null;
34393
34477
  }
34394
34478
  }
34395
34479
  async function saveMailCredentials(creds, credentialPath) {
34396
34480
  const filePath = resolveMailCredentialPath(credentialPath);
34397
- await fs13.mkdir(path14.dirname(filePath), { recursive: true, mode: 448 });
34398
- await fs13.writeFile(filePath, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
34399
- await fs13.chmod(filePath, 384);
34481
+ await fs14.mkdir(path15.dirname(filePath), { recursive: true, mode: 448 });
34482
+ await fs14.writeFile(filePath, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
34483
+ await fs14.chmod(filePath, 384);
34400
34484
  }
34401
34485
  async function persistProvisionedMailbox(mailbox, credentialPath) {
34402
34486
  if (!mailbox?.address || !mailbox.password) return false;
@@ -34417,7 +34501,7 @@ async function persistProvisionedMailbox(mailbox, credentialPath) {
34417
34501
  async function clearMailCredentials(credentialPath) {
34418
34502
  const filePath = resolveMailCredentialPath(credentialPath);
34419
34503
  try {
34420
- await fs13.unlink(filePath);
34504
+ await fs14.unlink(filePath);
34421
34505
  } catch (err) {
34422
34506
  if (err.code !== "ENOENT") throw err;
34423
34507
  }
@@ -34431,7 +34515,7 @@ var init_mail_credential_store = __esm({
34431
34515
  });
34432
34516
 
34433
34517
  // src/tools/register-peer.ts
34434
- import crypto4 from "node:crypto";
34518
+ import crypto5 from "node:crypto";
34435
34519
  function registerRegisterPeerTool(server) {
34436
34520
  server.registerTool(
34437
34521
  "cogent_register_peer",
@@ -34490,7 +34574,7 @@ function registerRegisterPeerTool(server) {
34490
34574
  }
34491
34575
  }
34492
34576
  if (!cloudSessionId) {
34493
- autoSecret = crypto4.randomBytes(16).toString("hex");
34577
+ autoSecret = crypto5.randomBytes(16).toString("hex");
34494
34578
  const resp = await fetch(
34495
34579
  `${endpoint}/api/sessions`,
34496
34580
  {
@@ -35748,7 +35832,7 @@ var init_health_check2 = __esm({
35748
35832
  });
35749
35833
 
35750
35834
  // src/tools/create-session.ts
35751
- import crypto5 from "node:crypto";
35835
+ import crypto6 from "node:crypto";
35752
35836
  function registerCreateSessionTool(server) {
35753
35837
  server.registerTool(
35754
35838
  "cogent_create_session",
@@ -35779,7 +35863,7 @@ function registerCreateSessionTool(server) {
35779
35863
  )
35780
35864
  );
35781
35865
  }
35782
- const sessionSecret = secret ?? crypto5.randomBytes(16).toString("hex");
35866
+ const sessionSecret = secret ?? crypto6.randomBytes(16).toString("hex");
35783
35867
  const resp = await fetch(
35784
35868
  `${config2.COGENT_ENDPOINT}/api/sessions`,
35785
35869
  {
@@ -36103,13 +36187,13 @@ var init_setup_mail = __esm({
36103
36187
  });
36104
36188
 
36105
36189
  // src/mail/mail-sender.ts
36106
- import fs14 from "node:fs/promises";
36107
- import path15 from "node:path";
36190
+ import fs15 from "node:fs/promises";
36191
+ import path16 from "node:path";
36108
36192
  async function buildAndSendMail(sender, creds, params) {
36109
36193
  const attachments = [];
36110
36194
  for (const p of params.attachmentPaths ?? []) {
36111
- const content = await fs14.readFile(p);
36112
- attachments.push({ filename: path15.basename(p), content });
36195
+ const content = await fs15.readFile(p);
36196
+ attachments.push({ filename: path16.basename(p), content });
36113
36197
  }
36114
36198
  const { messageId } = await sender.send({
36115
36199
  from: creds.address,
@@ -36237,16 +36321,16 @@ var init_send_mail = __esm({
36237
36321
  });
36238
36322
 
36239
36323
  // src/mail/mail-fetcher.ts
36240
- import fs15 from "node:fs/promises";
36241
- import path16 from "node:path";
36324
+ import fs16 from "node:fs/promises";
36325
+ import path17 from "node:path";
36242
36326
  async function saveAttachments(attachments, downloadDir) {
36243
36327
  if (attachments.length === 0) return [];
36244
- await fs15.mkdir(downloadDir, { recursive: true });
36328
+ await fs16.mkdir(downloadDir, { recursive: true });
36245
36329
  const saved = [];
36246
36330
  for (const a of attachments) {
36247
- const name = path16.basename(a.filename) || "attachment";
36248
- const savedPath = path16.join(downloadDir, name);
36249
- await fs15.writeFile(savedPath, a.content);
36331
+ const name = path17.basename(a.filename) || "attachment";
36332
+ const savedPath = path17.join(downloadDir, name);
36333
+ await fs16.writeFile(savedPath, a.content);
36250
36334
  saved.push({ filename: name, savedPath, size: a.content.length });
36251
36335
  }
36252
36336
  return saved;
@@ -36377,8 +36461,8 @@ var init_mail_fetcher = __esm({
36377
36461
  });
36378
36462
 
36379
36463
  // src/tools/fetch-mail.ts
36380
- import os9 from "node:os";
36381
- import path17 from "node:path";
36464
+ import os10 from "node:os";
36465
+ import path18 from "node:path";
36382
36466
  function registerFetchMailTool(server, deps = {}) {
36383
36467
  const makeFetcher = deps.fetcherFactory ?? ((creds) => new ImapflowMailFetcher(creds));
36384
36468
  server.registerTool(
@@ -36440,7 +36524,7 @@ var init_fetch_mail = __esm({
36440
36524
  init_mail_fetcher();
36441
36525
  init_auth_error();
36442
36526
  init_validate();
36443
- DEFAULT_DOWNLOAD_DIR = path17.join(os9.homedir(), ".cogent", "mail-downloads");
36527
+ DEFAULT_DOWNLOAD_DIR = path18.join(os10.homedir(), ".cogent", "mail-downloads");
36444
36528
  }
36445
36529
  });
36446
36530
 
@@ -149,6 +149,50 @@ export function isMetaNonAnswer(text) {
149
149
  return /already (answered|replied|responded|sent|handled)|no duplicate|duplicate (not )?sent|answered (already|via|through)|responded (already|via|through)/.test(t);
150
150
  }
151
151
 
152
+ /**
153
+ * Hard ceiling on how long ANY marker may suppress this hook, whatever the writer stamped.
154
+ * MUST equal wake-inflight.ts INFLIGHT_MAX_MS.
155
+ */
156
+ export const INFLIGHT_MAX_MS = 1_800_000; // 30 min
157
+
158
+ /**
159
+ * Message ids that rail B is CURRENTLY handling — read from the wake-inflight marker
160
+ * (src/services/wake-inflight.ts). COG-20: rail B records a reply only after it has
161
+ * captured it, but rail C fires at the end of the very turn B is capturing. Without this,
162
+ * C tells the agent "unanswered" about a message B is milliseconds from delivering; the
163
+ * agent appends an extra message, and B captures THAT instead of the real answer.
164
+ *
165
+ * EXPIRY COMES FROM THE WRITER. Only rail B knows how long its wake may legitimately run
166
+ * (COGENT_TIMEOUT_MS, and a wake can run execRemote twice via the rotated-session retry),
167
+ * so B stamps `expiresAt`. A fixed reader-side TTL would expire while long or retried wakes
168
+ * are still running and reopen the race on exactly the slow turns rail C exists for.
169
+ *
170
+ * But the reader must not trust it unconditionally: `startedAt + INFLIGHT_MAX_MS` caps every
171
+ * entry, so a corrupt, hostile, or forgotten marker can never mute this hook indefinitely.
172
+ * Anything malformed or expired is ignored — a bad marker degrades LOUD (we speak up, as
173
+ * before the fix), never SILENT (the safety net disappearing).
174
+ *
175
+ * @param {{messages?:Record<string,{startedAt?:number,expiresAt?:number}>}|null} marker
176
+ * @param {number} nowMs
177
+ * @param {number} [maxMs] - hard cap on suppression, default INFLIGHT_MAX_MS
178
+ * @returns {string[]} message ids still legitimately in flight
179
+ */
180
+ export function activeInFlightIds(marker, nowMs, maxMs = INFLIGHT_MAX_MS) {
181
+ const msgs = marker && typeof marker === "object" ? marker.messages : null;
182
+ if (!msgs || typeof msgs !== "object") return [];
183
+ const out = [];
184
+ for (const [id, rec] of Object.entries(msgs)) {
185
+ if (!rec || typeof rec !== "object") continue;
186
+ const started = typeof rec.startedAt === "number" && Number.isFinite(rec.startedAt) ? rec.startedAt : null;
187
+ const expires = typeof rec.expiresAt === "number" && Number.isFinite(rec.expiresAt) ? rec.expiresAt : null;
188
+ if (started === null || expires === null) continue; // corrupt → do not suppress
189
+ if (expires <= nowMs) continue; // the writer's own deadline passed → the wake is dead
190
+ if (nowMs - started > maxMs) continue; // writer stamped an absurd deadline → cap it
191
+ out.push(id);
192
+ }
193
+ return out;
194
+ }
195
+
152
196
  /** Build the { decision: "block" } reason string handed back to the agent. */
153
197
  export function buildBlockReason(items, { firstUse }) {
154
198
  const n = items.length;
@@ -17,10 +17,28 @@ import {
17
17
  selectUnanswered,
18
18
  buildBlockReason,
19
19
  credHashForCwd,
20
+ activeInFlightIds,
20
21
  } from "./check-on-stop.lib.mjs";
21
22
 
22
23
  const HTTP_TIMEOUT_MS = 5000; // < the 8s hook timeout; only bites on a degraded relay
23
24
 
25
+ /**
26
+ * `~/.cogent/wake-inflight/<sha256(cwd)[..16]>.<peerId>.json` — MUST equal
27
+ * src/services/wake-inflight.ts wakeInflightPath(). Rail B writes it while it is handling a
28
+ * message; we read it so we never call a message "unanswered" that B is about to answer.
29
+ *
30
+ * HOMEDIR-BASED, deliberately NOT COGENT_STATE_PATH — same reasoning as credPath above. Rail
31
+ * B reads its config from the MCP server's env; this hook reads its own process env, which
32
+ * the host spawns from the user's shell. An operator setting COGENT_STATE_PATH in the
33
+ * .mcp.json env block would put the writer and the reader in different directories and the
34
+ * guard would silently stop working. Keyed by peerId too: two peers can share a cwd, and a
35
+ * human broadcast carries the SAME message id to every peer.
36
+ */
37
+ function inflightPath(cwd, me) {
38
+ const safe = (s) => String(s).replace(/[^A-Za-z0-9._-]/g, "_");
39
+ return path.join(os.homedir(), ".cogent", "wake-inflight", `${credHashForCwd(cwd)}.${safe(me)}.json`);
40
+ }
41
+
24
42
  async function readStdin() {
25
43
  const chunks = [];
26
44
  for await (const c of process.stdin) chunks.push(c);
@@ -129,7 +147,15 @@ async function main() {
129
147
  return process.exit(0); // network/timeout → silent
130
148
  }
131
149
 
132
- const { items } = selectUnanswered({ messages, me, scope });
150
+ const all = selectUnanswered({ messages, me, scope }).items;
151
+
152
+ // COG-20: drop anything rail B is CURRENTLY waking for. B records its reply only after
153
+ // capture, and we fire at the end of the very turn it is capturing — so without this we
154
+ // would tell the agent a message is unanswered milliseconds before B delivers it. The
155
+ // agent then appends an extra message and B captures THAT, not the real answer.
156
+ // Deliberately id-scoped, not a blanket mute: any OTHER unanswered message still surfaces.
157
+ const inFlight = activeInFlightIds(await readJson(inflightPath(cwd, me)), Date.now());
158
+ const items = inFlight.length ? all.filter((m) => !inFlight.includes(m.id)) : all;
133
159
  const unansweredIds = items.map((m) => m.id);
134
160
 
135
161
  // First run: PRIME — record the current backlog as already-surfaced so C never
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@essentialai/cogent-plugin",
3
- "version": "3.20.5",
3
+ "version": "3.20.6",
4
4
  "description": "Cogent — Claude Code plugin (skills + slash-commands + MCP server) for the cross-agent comms fabric.",
5
5
  "author": { "name": "Essential AI Solutions Ltd.", "url": "https://essentialai.uk" },
6
6
  "homepage": "https://cogent.tools",