@essentialai/cogent-plugin 3.19.2 → 3.20.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cogent",
3
- "version": "3.19.2",
3
+ "version": "3.20.0",
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: path17, errorMaps, issueData } = params;
1263
- const fullPath = [...path17, ...issueData.path || []];
1262
+ const { data, path: path18, errorMaps, issueData } = params;
1263
+ const fullPath = [...path18, ...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, path17, key) {
1543
+ constructor(parent, value, path18, key) {
1544
1544
  this._cachedPath = [];
1545
1545
  this.parent = parent;
1546
1546
  this.data = value;
1547
- this._path = path17;
1547
+ this._path = path18;
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, path17) {
5000
- if (!path17)
4999
+ function getElementAtPath(obj, path18) {
5000
+ if (!path18)
5001
5001
  return obj;
5002
- return path17.reduce((acc, key) => acc?.[key], obj);
5002
+ return path18.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(path17, issues) {
5251
+ function prefixIssues(path18, issues) {
5252
5252
  return issues.map((iss) => {
5253
5253
  var _a;
5254
5254
  (_a = iss).path ?? (_a.path = []);
5255
- iss.path.unshift(path17);
5255
+ iss.path.unshift(path18);
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(path17) {
17062
- let input = path17;
17061
+ function removeDotSegments(path18) {
17062
+ let input = path18;
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 [path17, query] = wsComponent.resourceName.split("?");
17262
- wsComponent.path = path17 && path17 !== "/" ? path17 : void 0;
17261
+ const [path18, query] = wsComponent.resourceName.split("?");
17262
+ wsComponent.path = path18 && path18 !== "/" ? path18 : 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, fs15, exportName) {
20615
+ function addFormats(ajv, list, fs16, 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, fs15[f]);
20620
+ ajv.addFormat(f, fs16[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: path17, errorMaps, issueData } = params;
21733
- const fullPath = [...path17, ...issueData.path || []];
21732
+ const { data, path: path18, errorMaps, issueData } = params;
21733
+ const fullPath = [...path18, ...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, path17, key) {
21887
+ constructor(parent, value, path18, key) {
21888
21888
  this._cachedPath = [];
21889
21889
  this.parent = parent;
21890
21890
  this.data = value;
21891
- this._path = path17;
21891
+ this._path = path18;
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.19.2") {
26393
- return "3.19.2";
26392
+ if ("3.20.0") {
26393
+ return "3.20.0";
26394
26394
  }
26395
26395
  try {
26396
26396
  const require2 = createRequire(import.meta.url);
@@ -26610,6 +26610,18 @@ var init_config = __esm({
26610
26610
  COGENT_PLATFORM: import_zod2.z.enum(["cc", "codex", "slack", "gchat", "web"]).default("cc"),
26611
26611
  COGENT_CODEX_PATH: import_zod2.z.string().default("codex"),
26612
26612
  COGENT_CODEX_SANDBOX: import_zod2.z.enum(["full-auto", "read-only", "workspace-write", "danger-full-access", "bypass"]).default("full-auto"),
26613
+ // Codex real-time-wake rail selector. codex >=0.145 has a SINGLE-WRITER
26614
+ // thread-store: a codex agent running in an interactive TUI holds an exclusive
26615
+ // writer on its own thread, so the current rail (`exec-resume` =
26616
+ // `codex exec resume <same id>`) fast-fails `-32600 already has an active
26617
+ // writer` and the peer never gets a real-time reply. The `app-server` rail
26618
+ // injects the peer's message as a turn THROUGH codex's app-server daemon (the
26619
+ // daemon is the single writer; the human's `codex --remote` TUI and the bridge
26620
+ // are both just clients → no conflict). DEFAULT stays `exec-resume` so this is
26621
+ // purely additive/opt-in: nothing changes for any agent until a codex user
26622
+ // launches via the daemon (see bin/cogent-codex) and sets this to `app-server`.
26623
+ // Only affects COGENT_PLATFORM=codex; claude/gemini/mail rails never read it.
26624
+ COGENT_CODEX_WAKE: import_zod2.z.enum(["exec-resume", "app-server"]).default("exec-resume"),
26613
26625
  // Auto-recovery for a blocked Codex sandbox. On a host where the OS sandbox
26614
26626
  // can't initialize (unprivileged user namespaces blocked — Ubuntu 24.04's
26615
26627
  // apparmor_restrict_unprivileged_userns, containers, hardened kernels),
@@ -27589,7 +27601,7 @@ var init_http_backend = __esm({
27589
27601
  * in cloud mode we ignore it and use this.sessionId (the cloud session).
27590
27602
  */
27591
27603
  async registerPeer(peerId, _sessionId, cwd, label, clientVersion, mode, _channelSessionId, capabilities, workspaceId, threadId) {
27592
- const path17 = PATHS.peers.replace(":sessionId", this.sessionId);
27604
+ const path18 = PATHS.peers.replace(":sessionId", this.sessionId);
27593
27605
  const body = {
27594
27606
  peerId,
27595
27607
  cwd,
@@ -27617,7 +27629,7 @@ var init_http_backend = __esm({
27617
27629
  body.role = roleCap.slice("role:".length);
27618
27630
  }
27619
27631
  }
27620
- return this.http.post(path17, body);
27632
+ return this.http.post(path18, body);
27621
27633
  }
27622
27634
  /**
27623
27635
  * Deregister a peer from the cloud session.
@@ -27629,9 +27641,9 @@ var init_http_backend = __esm({
27629
27641
  * local mail creds. A 404 (peer not found) resolves to `{ removed: false, ... }`, not a throw.
27630
27642
  */
27631
27643
  async deregisterPeer(peerId) {
27632
- const path17 = PATHS.peer.replace(":sessionId", this.sessionId).replace(":peerId", peerId);
27644
+ const path18 = PATHS.peer.replace(":sessionId", this.sessionId).replace(":peerId", peerId);
27633
27645
  try {
27634
- const headers = await this.http.delete(path17, {});
27646
+ const headers = await this.http.delete(path18, {});
27635
27647
  const mailboxDeprovisioned = headers.get(MAILBOX_DEPROVISIONED_HEADER) === "true";
27636
27648
  return { removed: true, mailboxDeprovisioned };
27637
27649
  } catch {
@@ -27652,8 +27664,8 @@ var init_http_backend = __esm({
27652
27664
  * GET /api/sessions/:sessionId/peers
27653
27665
  */
27654
27666
  async listPeers() {
27655
- const path17 = PATHS.peers.replace(":sessionId", this.sessionId);
27656
- const result = await this.http.get(path17);
27667
+ const path18 = PATHS.peers.replace(":sessionId", this.sessionId);
27668
+ const result = await this.http.get(path18);
27657
27669
  return result.peers;
27658
27670
  }
27659
27671
  /**
@@ -27661,8 +27673,8 @@ var init_http_backend = __esm({
27661
27673
  * Calls the lightweight heartbeat endpoint on the cloud relay server.
27662
27674
  */
27663
27675
  async updateLastSeen(peerId) {
27664
- const path17 = PATHS.heartbeat.replace(":sessionId", this.sessionId);
27665
- await this.http.post(path17, { peerId });
27676
+ const path18 = PATHS.heartbeat.replace(":sessionId", this.sessionId);
27677
+ await this.http.post(path18, { peerId });
27666
27678
  }
27667
27679
  /**
27668
27680
  * Record a message in the cloud session.
@@ -27673,7 +27685,7 @@ var init_http_backend = __esm({
27673
27685
  * the input record to construct a full MessageRecord.
27674
27686
  */
27675
27687
  async recordMessage(record2) {
27676
- const path17 = PATHS.messages.replace(":sessionId", this.sessionId);
27688
+ const path18 = PATHS.messages.replace(":sessionId", this.sessionId);
27677
27689
  const body = {
27678
27690
  fromPeerId: record2.fromPeerId,
27679
27691
  toPeerId: record2.toPeerId,
@@ -27685,7 +27697,7 @@ var init_http_backend = __esm({
27685
27697
  if (record2.attachments && record2.attachments.length > 0) {
27686
27698
  body.attachments = record2.attachments;
27687
27699
  }
27688
- const resp = await this.http.post(path17, body);
27700
+ const resp = await this.http.post(path18, body);
27689
27701
  return {
27690
27702
  ...record2,
27691
27703
  id: resp.id,
@@ -27699,7 +27711,7 @@ var init_http_backend = __esm({
27699
27711
  * Supports optional peerId filter and limit (default 300).
27700
27712
  */
27701
27713
  async getHistory(peerId, limit, includeRelayEchoes, metadataOnly, dedupInferredEchoes) {
27702
- const path17 = PATHS.messages.replace(":sessionId", this.sessionId);
27714
+ const path18 = PATHS.messages.replace(":sessionId", this.sessionId);
27703
27715
  const query = {};
27704
27716
  if (peerId) {
27705
27717
  query.peerId = peerId;
@@ -27714,7 +27726,7 @@ var init_http_backend = __esm({
27714
27726
  if (dedupInferredEchoes) {
27715
27727
  query.dedupInferredEchoes = "true";
27716
27728
  }
27717
- const result = await this.http.get(path17, query);
27729
+ const result = await this.http.get(path18, query);
27718
27730
  return result.messages;
27719
27731
  }
27720
27732
  /**
@@ -27725,12 +27737,12 @@ var init_http_backend = __esm({
27725
27737
  * so that get-history shows the actual response (not null).
27726
27738
  */
27727
27739
  async updateMessageResponse(messageId, response, durationMs, pending) {
27728
- const path17 = PATHS.message.replace(":sessionId", this.sessionId).replace(":messageId", messageId);
27740
+ const path18 = PATHS.message.replace(":sessionId", this.sessionId).replace(":messageId", messageId);
27729
27741
  const body = { response, durationMs };
27730
27742
  if (pending === true) {
27731
27743
  body.pending = true;
27732
27744
  }
27733
- await this.http.patch(path17, body);
27745
+ await this.http.patch(path18, body);
27734
27746
  }
27735
27747
  /**
27736
27748
  * Validate a session.
@@ -27872,8 +27884,8 @@ var init_http_client = __esm({
27872
27884
  * Perform an authenticated GET request.
27873
27885
  * Appends query parameters to the URL if provided.
27874
27886
  */
27875
- async get(path17, query) {
27876
- const url = new URL(path17, this.baseUrl);
27887
+ async get(path18, query) {
27888
+ const url = new URL(path18, this.baseUrl);
27877
27889
  if (query) {
27878
27890
  for (const [k, v] of Object.entries(query)) {
27879
27891
  url.searchParams.set(k, v);
@@ -27888,8 +27900,8 @@ var init_http_client = __esm({
27888
27900
  /**
27889
27901
  * Perform an authenticated POST request with a JSON body.
27890
27902
  */
27891
- async post(path17, body) {
27892
- const url = new URL(path17, this.baseUrl);
27903
+ async post(path18, body) {
27904
+ const url = new URL(path18, this.baseUrl);
27893
27905
  const resp = await this.fetchWithTimeout(url, {
27894
27906
  method: "POST",
27895
27907
  headers: this.headers(),
@@ -27907,8 +27919,8 @@ var init_http_client = __esm({
27907
27919
  * Perform an authenticated PATCH request with a JSON body.
27908
27920
  * Used to update existing resources (e.g., message response fields).
27909
27921
  */
27910
- async patch(path17, body) {
27911
- const url = new URL(path17, this.baseUrl);
27922
+ async patch(path18, body) {
27923
+ const url = new URL(path18, this.baseUrl);
27912
27924
  const resp = await this.fetchWithTimeout(url, {
27913
27925
  method: "PATCH",
27914
27926
  headers: this.headers(),
@@ -27933,8 +27945,8 @@ var init_http_client = __esm({
27933
27945
  * "Malformed JSON in request body" 400 from cogent-server <=3.1.2 when
27934
27946
  * any client (including third-party tools) DELETEd a peer without a body.
27935
27947
  */
27936
- async delete(path17, body) {
27937
- const url = new URL(path17, this.baseUrl);
27948
+ async delete(path18, body) {
27949
+ const url = new URL(path18, this.baseUrl);
27938
27950
  const headers = {
27939
27951
  "Authorization": `Bearer ${this.token}`
27940
27952
  };
@@ -32117,14 +32129,14 @@ var init_ws_client = __esm({
32117
32129
  if (this.pollTimer) return;
32118
32130
  const poll = async () => {
32119
32131
  try {
32120
- const path17 = `/api/sessions/${this.opts.sessionId}/poll`;
32132
+ const path18 = `/api/sessions/${this.opts.sessionId}/poll`;
32121
32133
  const query = {
32122
32134
  peerId: this.opts.peerId
32123
32135
  };
32124
32136
  if (this.lastMessageId) {
32125
32137
  query.lastMessageId = this.lastMessageId;
32126
32138
  }
32127
- const result = await this.opts.http.get(path17, query);
32139
+ const result = await this.opts.http.get(path18, query);
32128
32140
  if (result.messages && result.messages.length > 0) {
32129
32141
  this.opts.onMessages(result.messages);
32130
32142
  this.lastMessageId = result.messages[result.messages.length - 1].id;
@@ -32278,11 +32290,13 @@ function execCodex(sessionId, message, cwd, timeoutMs) {
32278
32290
  if (code !== 0) {
32279
32291
  await cleanupFile();
32280
32292
  const sandboxBlocked = SANDBOX_FAILURE_RE.test(stderr);
32293
+ const codexLiveSession = ACTIVE_WRITER_RE.test(stderr);
32281
32294
  safeResolve({
32282
32295
  stdout: "",
32283
32296
  stderr: `CLI_EXEC_FAILED: codex exited with code ${code}. stderr: ${stderr}`,
32284
32297
  exitCode: code,
32285
- ...sandboxBlocked ? { sandboxBlocked: true } : {}
32298
+ ...sandboxBlocked ? { sandboxBlocked: true } : {},
32299
+ ...codexLiveSession ? { codexLiveSession: true } : {}
32286
32300
  });
32287
32301
  return;
32288
32302
  }
@@ -32412,7 +32426,7 @@ async function validateCodexSession(sessionId) {
32412
32426
  return false;
32413
32427
  }
32414
32428
  }
32415
- var sandboxOverride, SANDBOX_FAILURE_RE;
32429
+ var sandboxOverride, SANDBOX_FAILURE_RE, ACTIVE_WRITER_RE;
32416
32430
  var init_codex_cli = __esm({
32417
32431
  "src/services/codex-cli.ts"() {
32418
32432
  "use strict";
@@ -32420,14 +32434,329 @@ var init_codex_cli = __esm({
32420
32434
  init_agent_config();
32421
32435
  sandboxOverride = null;
32422
32436
  SANDBOX_FAILURE_RE = /bwrap:|setting up uid ?map|\/proc\/self\/(uid|gid)_map|newuidmap|user namespaces?|RTM_NEWADDR|unshare[^\n]*(Operation not permitted|Permission denied)|clone3?[^\n]*(EPERM|not permitted)/i;
32437
+ ACTIVE_WRITER_RE = /already has an active writer|thread-store conflict|-32600/i;
32438
+ }
32439
+ });
32440
+
32441
+ // src/services/codex-app-server.ts
32442
+ import path12 from "node:path";
32443
+ import fs11 from "node:fs/promises";
32444
+ import { spawn as spawn3 } from "node:child_process";
32445
+ function appServerSocketPath(codexHome = codexHomeDir()) {
32446
+ return path12.join(codexHome, "app-server-control", "app-server-control.sock");
32447
+ }
32448
+ function rpcUrl(sock) {
32449
+ return `ws+unix://${sock}:/rpc`;
32450
+ }
32451
+ async function ensureDaemon(codexHome = codexHomeDir(), deps = {}) {
32452
+ if (ensuredSock) return ensuredSock;
32453
+ const run = deps.run ?? defaultRun;
32454
+ const socketExists = deps.socketExists ?? defaultSocketExists;
32455
+ const waitMs = deps.waitMs ?? 8e3;
32456
+ const pollMs = deps.pollMs ?? 150;
32457
+ const codexPath = getConfig().COGENT_CODEX_PATH;
32458
+ const env = { ...process.env, CODEX_HOME: codexHome };
32459
+ const res = await run(codexPath, ["app-server", "daemon", "start"], env);
32460
+ if (/managed standalone Codex install not found/i.test(`${res.stdout}
32461
+ ${res.stderr}`)) {
32462
+ throw new Error(
32463
+ "MANAGED_CODEX_REQUIRED: the codex app-server daemon needs the managed-standalone codex install, which the npm/Homebrew @openai/codex package does not provide. Install it with: curl -fsSL https://chatgpt.com/codex/install.sh | sh then relaunch via cogent-codex. (Until then, codex answers at its next turn, not in real time.)"
32464
+ );
32465
+ }
32466
+ let sock = appServerSocketPath(codexHome);
32467
+ try {
32468
+ const parsed = JSON.parse(res.stdout.trim());
32469
+ if (parsed && typeof parsed.socketPath === "string") sock = parsed.socketPath;
32470
+ } catch {
32471
+ }
32472
+ const deadline = Date.now() + waitMs;
32473
+ while (!await socketExists(sock)) {
32474
+ if (Date.now() >= deadline) {
32475
+ throw new Error(
32476
+ `app-server daemon socket did not appear at ${sock} within ${waitMs}ms (stderr: ${res.stderr.trim() || "none"})`
32477
+ );
32478
+ }
32479
+ await sleep2(pollMs);
32480
+ }
32481
+ ensuredSock = sock;
32482
+ return sock;
32483
+ }
32484
+ function isAgentMessage(item) {
32485
+ return typeof item?.type === "string" && /^agent[_-]?message$/i.test(item.type);
32486
+ }
32487
+ function itemText(item) {
32488
+ if (typeof item?.text === "string") return item.text;
32489
+ if (Array.isArray(item?.content)) {
32490
+ return item.content.filter((c2) => typeof c2?.text === "string").map((c2) => c2.text).join("");
32491
+ }
32492
+ return "";
32493
+ }
32494
+ function extractAgentMessageText(notes) {
32495
+ const fromCompleted = [];
32496
+ const fromStream = [];
32497
+ for (const n of notes) {
32498
+ if (n.method === "turn/completed") {
32499
+ const items = n.params?.items ?? n.params?.turn?.items ?? [];
32500
+ for (const it of items) if (isAgentMessage(it)) fromCompleted.push(itemText(it));
32501
+ } else if (n.method === "item/completed") {
32502
+ const it = n.params?.item;
32503
+ if (isAgentMessage(it)) fromStream.push(itemText(it));
32504
+ }
32505
+ }
32506
+ const chosen = fromCompleted.length > 0 ? fromCompleted : fromStream;
32507
+ return chosen.join("").trim();
32508
+ }
32509
+ function sandboxPolicyFor(mode, cwd) {
32510
+ switch (mode) {
32511
+ case "read-only":
32512
+ return { type: "readOnly", networkAccess: false };
32513
+ case "danger-full-access":
32514
+ case "bypass":
32515
+ return { type: "dangerFullAccess" };
32516
+ case "workspace-write":
32517
+ case "full-auto":
32518
+ default:
32519
+ return {
32520
+ type: "workspaceWrite",
32521
+ writableRoots: [cwd],
32522
+ networkAccess: false,
32523
+ excludeTmpdirEnvVar: false,
32524
+ excludeSlashTmp: false
32525
+ };
32526
+ }
32527
+ }
32528
+ function execCodexViaAppServer(threadId, message, cwd, timeoutMs, deps = {}) {
32529
+ const key = deps.idempotencyKey ? `${threadId}::${deps.idempotencyKey}` : null;
32530
+ if (!key) return _injectTurn(threadId, message, cwd, timeoutMs, deps);
32531
+ const existing = idempotencyCache.get(key);
32532
+ if (existing) {
32533
+ return existing.done ? Promise.resolve(existing.done.result) : existing.promise;
32534
+ }
32535
+ const promise = _injectTurn(threadId, message, cwd, timeoutMs, deps);
32536
+ const entry = { promise };
32537
+ idempotencyCache.set(key, entry);
32538
+ void promise.then((result) => {
32539
+ entry.done = { result, at: Date.now() };
32540
+ const t = setTimeout(() => idempotencyCache.delete(key), IDEMPOTENCY_TTL_MS);
32541
+ if (typeof t.unref === "function") t.unref();
32542
+ });
32543
+ return promise;
32544
+ }
32545
+ async function _injectTurn(threadId, message, cwd, timeoutMs, deps = {}) {
32546
+ const config2 = getConfig();
32547
+ const effectiveTimeout = timeoutMs ?? config2.COGENT_TIMEOUT_MS;
32548
+ const truncated = config2.COGENT_CHAR_LIMIT > 0 && message.length > config2.COGENT_CHAR_LIMIT ? message.slice(0, config2.COGENT_CHAR_LIMIT) + "\n...[truncated]" : message;
32549
+ const sock = deps.socketPath ?? appServerSocketPath();
32550
+ const wsFactory = deps.wsFactory ?? defaultWsFactory;
32551
+ let client;
32552
+ try {
32553
+ const ws = wsFactory(rpcUrl(sock), { perMessageDeflate: false, headers: { Host: "localhost" } });
32554
+ client = new AppServerClient(ws);
32555
+ await client.waitOpen();
32556
+ await client.request("initialize", {
32557
+ clientInfo: { name: "cogent-bridge", version: "1" },
32558
+ capabilities: { experimentalApi: true }
32559
+ });
32560
+ } catch (err) {
32561
+ return {
32562
+ stdout: "",
32563
+ stderr: `APP_SERVER_UNAVAILABLE: ${err.message}`,
32564
+ exitCode: 1,
32565
+ appServerUnavailable: true
32566
+ };
32567
+ }
32568
+ try {
32569
+ await client.request("thread/resume", { threadId, cwd });
32570
+ const buffered = [];
32571
+ let myTurnId;
32572
+ const isMine = (msg) => msg.method === "turn/completed" && (myTurnId !== void 0 ? msg.params?.turn?.id === myTurnId : msg.params?.threadId === threadId);
32573
+ let deliver = null;
32574
+ const off = client.onNotification((msg) => {
32575
+ if (msg.method !== "turn/completed") return;
32576
+ if (myTurnId === void 0) {
32577
+ buffered.push(msg);
32578
+ return;
32579
+ }
32580
+ if (isMine(msg) && deliver) {
32581
+ const d = deliver;
32582
+ deliver = null;
32583
+ d(msg);
32584
+ }
32585
+ });
32586
+ const startResult = await client.request("turn/start", {
32587
+ threadId,
32588
+ input: [{ type: "text", text: truncated }],
32589
+ // Bound the injected peer turn by the CONFIGURED sandbox (not the human
32590
+ // session's privilege). See sandboxPolicyFor.
32591
+ sandboxPolicy: sandboxPolicyFor(config2.COGENT_CODEX_SANDBOX, cwd),
32592
+ // Native correlation id (daemon does NOT auto-dedupe on it — our own
32593
+ // idempotency guard does; this is for daemon-side correlation/telemetry).
32594
+ ...deps.idempotencyKey ? { clientUserMessageId: deps.idempotencyKey } : {}
32595
+ });
32596
+ myTurnId = startResult?.turn?.id;
32597
+ const turnCompleted = await new Promise((resolve) => {
32598
+ deliver = resolve;
32599
+ const early = buffered.find(isMine);
32600
+ if (early) {
32601
+ deliver = null;
32602
+ resolve(early);
32603
+ return;
32604
+ }
32605
+ setTimeout(() => {
32606
+ if (deliver) {
32607
+ deliver = null;
32608
+ resolve(null);
32609
+ }
32610
+ }, effectiveTimeout);
32611
+ });
32612
+ off();
32613
+ if (!turnCompleted) {
32614
+ return {
32615
+ stdout: "",
32616
+ stderr: `CLI_TIMEOUT: app-server turn did not complete within ${effectiveTimeout}ms`,
32617
+ exitCode: null
32618
+ };
32619
+ }
32620
+ const text = extractAgentMessageText([turnCompleted]);
32621
+ return {
32622
+ stdout: text,
32623
+ stderr: "",
32624
+ exitCode: 0,
32625
+ captureContract: "event_stream",
32626
+ stable: true
32627
+ };
32628
+ } catch (err) {
32629
+ return {
32630
+ stdout: "",
32631
+ stderr: `CLI_EXEC_FAILED: app-server turn failed: ${err.message}`,
32632
+ exitCode: 1
32633
+ };
32634
+ } finally {
32635
+ client.close();
32636
+ }
32637
+ }
32638
+ var defaultWsFactory, ensuredSock, defaultRun, defaultSocketExists, sleep2, AppServerClient, idempotencyCache, IDEMPOTENCY_TTL_MS;
32639
+ var init_codex_app_server = __esm({
32640
+ "src/services/codex-app-server.ts"() {
32641
+ "use strict";
32642
+ init_wrapper();
32643
+ init_config();
32644
+ init_agent_config();
32645
+ defaultWsFactory = (url, opts) => new wrapper_default(url, opts);
32646
+ ensuredSock = null;
32647
+ defaultRun = (cmd, args, env) => new Promise((resolve) => {
32648
+ const child = spawn3(cmd, args, { env, stdio: ["ignore", "pipe", "pipe"] });
32649
+ let stdout = "";
32650
+ let stderr = "";
32651
+ child.stdout.on("data", (c2) => stdout += c2.toString());
32652
+ child.stderr.on("data", (c2) => stderr += c2.toString());
32653
+ child.on("error", (err) => resolve({ stdout, stderr: stderr + String(err), code: 127 }));
32654
+ child.on("close", (code) => resolve({ stdout, stderr, code }));
32655
+ });
32656
+ defaultSocketExists = async (p) => {
32657
+ try {
32658
+ await fs11.stat(p);
32659
+ return true;
32660
+ } catch {
32661
+ return false;
32662
+ }
32663
+ };
32664
+ sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
32665
+ AppServerClient = class {
32666
+ ws;
32667
+ nextId = 1;
32668
+ pending = /* @__PURE__ */ new Map();
32669
+ notifyListeners = /* @__PURE__ */ new Set();
32670
+ opened;
32671
+ openResolved = false;
32672
+ constructor(ws) {
32673
+ this.ws = ws;
32674
+ this.opened = new Promise((resolve, reject) => {
32675
+ ws.on("open", () => {
32676
+ this.openResolved = true;
32677
+ resolve();
32678
+ });
32679
+ ws.on("unexpected-response", (_req, res) => {
32680
+ if (!this.openResolved) reject(new Error(`app-server ws upgrade rejected: HTTP ${res?.statusCode}`));
32681
+ });
32682
+ ws.on("error", (err) => {
32683
+ if (!this.openResolved) reject(err);
32684
+ else this._failAll(err);
32685
+ });
32686
+ ws.on("close", () => {
32687
+ if (this.openResolved) this._failAll(new Error("app-server ws closed"));
32688
+ });
32689
+ });
32690
+ ws.on("message", (data) => this._onMessage(data));
32691
+ }
32692
+ /** Await the open handshake. Rejects (before open) on connect/upgrade errors. */
32693
+ waitOpen() {
32694
+ return this.opened;
32695
+ }
32696
+ _failAll(err) {
32697
+ for (const [, p] of this.pending) p.reject(err);
32698
+ this.pending.clear();
32699
+ }
32700
+ _onMessage(data) {
32701
+ let msg;
32702
+ try {
32703
+ msg = JSON.parse(data.toString());
32704
+ } catch {
32705
+ return;
32706
+ }
32707
+ if (typeof msg.id === "number" && this.pending.has(msg.id)) {
32708
+ const p = this.pending.get(msg.id);
32709
+ this.pending.delete(msg.id);
32710
+ if (msg.error) p.reject(Object.assign(new Error(msg.error.message ?? "rpc error"), { rpcError: msg.error }));
32711
+ else p.resolve(msg.result);
32712
+ return;
32713
+ }
32714
+ if (typeof msg.method === "string") {
32715
+ for (const cb of this.notifyListeners) cb(msg);
32716
+ }
32717
+ }
32718
+ /** Issue a JSON-RPC request and resolve with its result (rejects on rpc error). */
32719
+ request(method, params) {
32720
+ const id = this.nextId++;
32721
+ const p = new Promise((resolve, reject) => {
32722
+ this.pending.set(id, { resolve, reject });
32723
+ });
32724
+ this.ws.send(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
32725
+ return p;
32726
+ }
32727
+ /** Subscribe to server notifications; returns an unsubscribe fn. */
32728
+ onNotification(cb) {
32729
+ this.notifyListeners.add(cb);
32730
+ return () => this.notifyListeners.delete(cb);
32731
+ }
32732
+ close() {
32733
+ try {
32734
+ this.ws.close();
32735
+ } catch {
32736
+ }
32737
+ }
32738
+ };
32739
+ idempotencyCache = /* @__PURE__ */ new Map();
32740
+ IDEMPOTENCY_TTL_MS = 3e5;
32423
32741
  }
32424
32742
  });
32425
32743
 
32426
32744
  // src/services/exec-remote.ts
32427
- function execRemote(platform, sessionId, message, cwd, timeoutMs) {
32745
+ async function execCodexWake(sessionId, message, cwd, timeoutMs, idempotencyKey) {
32746
+ if (getConfig().COGENT_CODEX_WAKE === "app-server") {
32747
+ try {
32748
+ const sock = await ensureDaemon();
32749
+ const result = await execCodexViaAppServer(sessionId, message, cwd, timeoutMs, { socketPath: sock, idempotencyKey });
32750
+ if (!result.appServerUnavailable) return result;
32751
+ } catch {
32752
+ }
32753
+ }
32754
+ return execCodex(sessionId, message, cwd, timeoutMs);
32755
+ }
32756
+ function execRemote(platform, sessionId, message, cwd, timeoutMs, opts) {
32428
32757
  switch (platform) {
32429
32758
  case "codex":
32430
- return execCodex(sessionId, message, cwd, timeoutMs);
32759
+ return execCodexWake(sessionId, message, cwd, timeoutMs, opts?.idempotencyKey);
32431
32760
  case "cc":
32432
32761
  default:
32433
32762
  return execClaude(sessionId, message, cwd, timeoutMs);
@@ -32463,8 +32792,10 @@ function validateSessionFor(platform, sessionId, cwd) {
32463
32792
  var init_exec_remote = __esm({
32464
32793
  "src/services/exec-remote.ts"() {
32465
32794
  "use strict";
32795
+ init_config();
32466
32796
  init_cc_cli();
32467
32797
  init_codex_cli();
32798
+ init_codex_app_server();
32468
32799
  }
32469
32800
  });
32470
32801
 
@@ -33172,7 +33503,12 @@ var init_auto_relay = __esm({
33172
33503
  getConfig().COGENT_PLATFORM,
33173
33504
  this.localSessionId,
33174
33505
  formatted,
33175
- this.localCwd
33506
+ this.localCwd,
33507
+ void 0,
33508
+ // Idempotency: the codex app-server rail runs turns on the daemon
33509
+ // independent of our client, so a redelivery of this same message must
33510
+ // not start a second turn. Keyed by the stable relay message id.
33511
+ { idempotencyKey: msg.id }
33176
33512
  );
33177
33513
  const durationMs = Date.now() - startMs;
33178
33514
  if (result.exitCode === 0 && result.stdout) {
@@ -33221,7 +33557,7 @@ var init_auto_relay = __esm({
33221
33557
  if (freshId && freshId !== this.localSessionId) {
33222
33558
  logger.info(`Auto-relay: retry with rotated session ${this.localSessionId} -> ${freshId}`);
33223
33559
  this.localSessionId = freshId;
33224
- const retry = await execRemote(getConfig().COGENT_PLATFORM, this.localSessionId, formatted, this.localCwd);
33560
+ const retry = await execRemote(getConfig().COGENT_PLATFORM, this.localSessionId, formatted, this.localCwd, void 0, { idempotencyKey: msg.id });
33225
33561
  if (retry.exitCode === 0 && retry.stdout) {
33226
33562
  await this._relayCapturedReply(msg, retry, traceId, Date.now() - startMs, true);
33227
33563
  replied = true;
@@ -33231,7 +33567,9 @@ var init_auto_relay = __esm({
33231
33567
  }
33232
33568
  }
33233
33569
  if (!replied) {
33234
- await this._recordNoReplyFailure(msg, traceId, Date.now() - startMs);
33570
+ await this._recordNoReplyFailure(msg, traceId, Date.now() - startMs, {
33571
+ codexLiveSession: result.codexLiveSession === true
33572
+ });
33235
33573
  }
33236
33574
  } catch (err) {
33237
33575
  const durationMs = Date.now() - startMs;
@@ -33252,16 +33590,18 @@ var init_auto_relay = __esm({
33252
33590
  * at the next turn boundary. Refusal (AMBIGUOUS_SESSION) and sandbox-blocked
33253
33591
  * paths record their own failure and return before this, so no double-record.
33254
33592
  */
33255
- async _recordNoReplyFailure(msg, traceId, durationMs) {
33593
+ async _recordNoReplyFailure(msg, traceId, durationMs, opts) {
33594
+ const codexLive = opts?.codexLiveSession === true;
33595
+ const message = codexLive ? `\u{1F4E8} Queued for "${this.localPeerId}": it's live in an interactive Codex session, so Cogent can't resume it in real time \u2014 it will see this at its next turn. For real-time replies, launch it via \`cogent-codex\` (COGENT_CODEX_WAKE=app-server). (trace ${traceId})` : `\u26A0\uFE0F Cogent could not capture a reply from "${this.localPeerId}" (it may have been busy, or the resume produced no output). Manual response required \u2014 the target agent must reply in its own session. (trace ${traceId})`;
33256
33596
  try {
33257
33597
  await getBackend().recordMessage({
33258
33598
  fromPeerId: this.localPeerId,
33259
33599
  toPeerId: msg.fromPeerId,
33260
- message: `\u26A0\uFE0F Cogent could not capture a reply from "${this.localPeerId}" (it may have been busy, or the resume produced no output). Manual response required \u2014 the target agent must reply in its own session. (trace ${traceId})`,
33600
+ message,
33261
33601
  response: null,
33262
33602
  durationMs,
33263
33603
  success: false,
33264
- error: "NO_REPLY_CAPTURED",
33604
+ error: codexLive ? "CODEX_LIVE_SESSION" : "NO_REPLY_CAPTURED",
33265
33605
  isRelayEcho: true,
33266
33606
  traceId
33267
33607
  });
@@ -33624,14 +33964,14 @@ __export(startup_exports, {
33624
33964
  triggerReRegistration: () => triggerReRegistration
33625
33965
  });
33626
33966
  import os6 from "node:os";
33627
- import path12 from "node:path";
33628
- import fs11 from "node:fs/promises";
33967
+ import path13 from "node:path";
33968
+ import fs12 from "node:fs/promises";
33629
33969
  import readline2 from "node:readline";
33630
33970
  import { execFile as execFile5 } from "node:child_process";
33631
33971
  import { promisify as promisify4 } from "node:util";
33632
33972
  async function loadPersistedConfig() {
33633
33973
  try {
33634
- const raw = await fs11.readFile(PERSIST_PATH, "utf-8");
33974
+ const raw = await fs12.readFile(PERSIST_PATH, "utf-8");
33635
33975
  return JSON.parse(raw);
33636
33976
  } catch {
33637
33977
  return {};
@@ -33639,7 +33979,7 @@ async function loadPersistedConfig() {
33639
33979
  }
33640
33980
  async function savePersistedConfig(config2) {
33641
33981
  try {
33642
- await fs11.writeFile(PERSIST_PATH, JSON.stringify(config2, null, 2) + "\n", "utf-8");
33982
+ await fs12.writeFile(PERSIST_PATH, JSON.stringify(config2, null, 2) + "\n", "utf-8");
33643
33983
  } catch (err) {
33644
33984
  const msg = err instanceof Error ? err.message : String(err);
33645
33985
  logger.warn(`Could not save config to ${PERSIST_PATH}: ${msg}`);
@@ -33674,11 +34014,11 @@ First run detected. Where should cogent-bridge store its data?
33674
34014
  });
33675
34015
  }
33676
34016
  async function validateStateDir(statePath) {
33677
- await fs11.mkdir(statePath, { recursive: true });
33678
- const testFile = path12.join(statePath, ".write-test");
34017
+ await fs12.mkdir(statePath, { recursive: true });
34018
+ const testFile = path13.join(statePath, ".write-test");
33679
34019
  try {
33680
- await fs11.writeFile(testFile, "test", "utf-8");
33681
- await fs11.unlink(testFile);
34020
+ await fs12.writeFile(testFile, "test", "utf-8");
34021
+ await fs12.unlink(testFile);
33682
34022
  } catch {
33683
34023
  throw new BridgeError(
33684
34024
  "STARTUP_FAILED" /* STARTUP_FAILED */,
@@ -33824,10 +34164,10 @@ async function runStartup() {
33824
34164
  if (envStatePath) {
33825
34165
  statePath = envStatePath;
33826
34166
  } else {
33827
- const defaultPath = path12.join(os6.homedir(), ".cogent");
34167
+ const defaultPath = path13.join(os6.homedir(), ".cogent");
33828
34168
  statePath = await firstRunPrompt(defaultPath);
33829
34169
  }
33830
- if (!envStatePath && statePath !== path12.join(os6.homedir(), ".cogent")) {
34170
+ if (!envStatePath && statePath !== path13.join(os6.homedir(), ".cogent")) {
33831
34171
  process.env.COGENT_STATE_PATH = statePath;
33832
34172
  } else if (!envStatePath) {
33833
34173
  process.env.COGENT_STATE_PATH = statePath;
@@ -33905,7 +34245,7 @@ async function runStartup() {
33905
34245
  }
33906
34246
  initLogger(
33907
34247
  config2.COGENT_LOG_LEVEL,
33908
- path12.join(config2.COGENT_STATE_PATH, "logs")
34248
+ path13.join(config2.COGENT_STATE_PATH, "logs")
33909
34249
  );
33910
34250
  if (isCloud && effectiveSessionId && effectiveToken) {
33911
34251
  cloudInbox = new MessageInbox();
@@ -33965,7 +34305,7 @@ var init_startup = __esm({
33965
34305
  init_auto_relay();
33966
34306
  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;
33967
34307
  execFileAsync3 = promisify4(execFile5);
33968
- PERSIST_PATH = path12.join(os6.homedir(), ".cogent-config.json");
34308
+ PERSIST_PATH = path13.join(os6.homedir(), ".cogent-config.json");
33969
34309
  legacyWarnEmitted = false;
33970
34310
  cloudWsClient = null;
33971
34311
  cloudInbox = null;
@@ -33976,12 +34316,12 @@ var init_startup = __esm({
33976
34316
 
33977
34317
  // src/cloud/mail-credential-store.ts
33978
34318
  import crypto3 from "node:crypto";
33979
- import fs12 from "node:fs/promises";
33980
- import path13 from "node:path";
34319
+ import fs13 from "node:fs/promises";
34320
+ import path14 from "node:path";
33981
34321
  import os7 from "node:os";
33982
34322
  function defaultMailCredentialPath(cwd = process.cwd()) {
33983
- const hash = crypto3.createHash("sha256").update(path13.resolve(cwd)).digest("hex").slice(0, 16);
33984
- return path13.join(os7.homedir(), ".cogent", "mail-credentials", `${hash}.json`);
34323
+ const hash = crypto3.createHash("sha256").update(path14.resolve(cwd)).digest("hex").slice(0, 16);
34324
+ return path14.join(os7.homedir(), ".cogent", "mail-credentials", `${hash}.json`);
33985
34325
  }
33986
34326
  function resolveMailCredentialPath(credentialPath) {
33987
34327
  if (credentialPath) return credentialPath;
@@ -33992,16 +34332,16 @@ function resolveMailCredentialPath(credentialPath) {
33992
34332
  async function loadMailCredentials(credentialPath) {
33993
34333
  const filePath = resolveMailCredentialPath(credentialPath);
33994
34334
  try {
33995
- return JSON.parse(await fs12.readFile(filePath, "utf-8"));
34335
+ return JSON.parse(await fs13.readFile(filePath, "utf-8"));
33996
34336
  } catch {
33997
34337
  return null;
33998
34338
  }
33999
34339
  }
34000
34340
  async function saveMailCredentials(creds, credentialPath) {
34001
34341
  const filePath = resolveMailCredentialPath(credentialPath);
34002
- await fs12.mkdir(path13.dirname(filePath), { recursive: true, mode: 448 });
34003
- await fs12.writeFile(filePath, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
34004
- await fs12.chmod(filePath, 384);
34342
+ await fs13.mkdir(path14.dirname(filePath), { recursive: true, mode: 448 });
34343
+ await fs13.writeFile(filePath, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
34344
+ await fs13.chmod(filePath, 384);
34005
34345
  }
34006
34346
  async function persistProvisionedMailbox(mailbox, credentialPath) {
34007
34347
  if (!mailbox?.address || !mailbox.password) return false;
@@ -34022,7 +34362,7 @@ async function persistProvisionedMailbox(mailbox, credentialPath) {
34022
34362
  async function clearMailCredentials(credentialPath) {
34023
34363
  const filePath = resolveMailCredentialPath(credentialPath);
34024
34364
  try {
34025
- await fs12.unlink(filePath);
34365
+ await fs13.unlink(filePath);
34026
34366
  } catch (err) {
34027
34367
  if (err.code !== "ENOENT") throw err;
34028
34368
  }
@@ -35708,13 +36048,13 @@ var init_setup_mail = __esm({
35708
36048
  });
35709
36049
 
35710
36050
  // src/mail/mail-sender.ts
35711
- import fs13 from "node:fs/promises";
35712
- import path14 from "node:path";
36051
+ import fs14 from "node:fs/promises";
36052
+ import path15 from "node:path";
35713
36053
  async function buildAndSendMail(sender, creds, params) {
35714
36054
  const attachments = [];
35715
36055
  for (const p of params.attachmentPaths ?? []) {
35716
- const content = await fs13.readFile(p);
35717
- attachments.push({ filename: path14.basename(p), content });
36056
+ const content = await fs14.readFile(p);
36057
+ attachments.push({ filename: path15.basename(p), content });
35718
36058
  }
35719
36059
  const { messageId } = await sender.send({
35720
36060
  from: creds.address,
@@ -35842,16 +36182,16 @@ var init_send_mail = __esm({
35842
36182
  });
35843
36183
 
35844
36184
  // src/mail/mail-fetcher.ts
35845
- import fs14 from "node:fs/promises";
35846
- import path15 from "node:path";
36185
+ import fs15 from "node:fs/promises";
36186
+ import path16 from "node:path";
35847
36187
  async function saveAttachments(attachments, downloadDir) {
35848
36188
  if (attachments.length === 0) return [];
35849
- await fs14.mkdir(downloadDir, { recursive: true });
36189
+ await fs15.mkdir(downloadDir, { recursive: true });
35850
36190
  const saved = [];
35851
36191
  for (const a of attachments) {
35852
- const name = path15.basename(a.filename) || "attachment";
35853
- const savedPath = path15.join(downloadDir, name);
35854
- await fs14.writeFile(savedPath, a.content);
36192
+ const name = path16.basename(a.filename) || "attachment";
36193
+ const savedPath = path16.join(downloadDir, name);
36194
+ await fs15.writeFile(savedPath, a.content);
35855
36195
  saved.push({ filename: name, savedPath, size: a.content.length });
35856
36196
  }
35857
36197
  return saved;
@@ -35983,7 +36323,7 @@ var init_mail_fetcher = __esm({
35983
36323
 
35984
36324
  // src/tools/fetch-mail.ts
35985
36325
  import os8 from "node:os";
35986
- import path16 from "node:path";
36326
+ import path17 from "node:path";
35987
36327
  function registerFetchMailTool(server, deps = {}) {
35988
36328
  const makeFetcher = deps.fetcherFactory ?? ((creds) => new ImapflowMailFetcher(creds));
35989
36329
  server.registerTool(
@@ -36045,7 +36385,7 @@ var init_fetch_mail = __esm({
36045
36385
  init_mail_fetcher();
36046
36386
  init_auth_error();
36047
36387
  init_validate();
36048
- DEFAULT_DOWNLOAD_DIR = path16.join(os8.homedir(), ".cogent", "mail-downloads");
36388
+ DEFAULT_DOWNLOAD_DIR = path17.join(os8.homedir(), ".cogent", "mail-downloads");
36049
36389
  }
36050
36390
  });
36051
36391
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@essentialai/cogent-plugin",
3
- "version": "3.19.2",
3
+ "version": "3.20.0",
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",