@essentialai/cogent-plugin 3.14.1 → 3.16.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.14.1",
3
+ "version": "3.16.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: path12, errorMaps, issueData } = params;
1263
- const fullPath = [...path12, ...issueData.path || []];
1262
+ const { data, path: path16, errorMaps, issueData } = params;
1263
+ const fullPath = [...path16, ...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, path12, key) {
1543
+ constructor(parent, value, path16, key) {
1544
1544
  this._cachedPath = [];
1545
1545
  this.parent = parent;
1546
1546
  this.data = value;
1547
- this._path = path12;
1547
+ this._path = path16;
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, path12) {
5000
- if (!path12)
4999
+ function getElementAtPath(obj, path16) {
5000
+ if (!path16)
5001
5001
  return obj;
5002
- return path12.reduce((acc, key) => acc?.[key], obj);
5002
+ return path16.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(path12, issues) {
5251
+ function prefixIssues(path16, issues) {
5252
5252
  return issues.map((iss) => {
5253
5253
  var _a;
5254
5254
  (_a = iss).path ?? (_a.path = []);
5255
- iss.path.unshift(path12);
5255
+ iss.path.unshift(path16);
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(path12) {
17062
- let input = path12;
17061
+ function removeDotSegments(path16) {
17062
+ let input = path16;
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 [path12, query] = wsComponent.resourceName.split("?");
17262
- wsComponent.path = path12 && path12 !== "/" ? path12 : void 0;
17261
+ const [path16, query] = wsComponent.resourceName.split("?");
17262
+ wsComponent.path = path16 && path16 !== "/" ? path16 : 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, fs12, exportName) {
20615
+ function addFormats(ajv, list, fs15, 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, fs12[f]);
20620
+ ajv.addFormat(f, fs15[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: path12, errorMaps, issueData } = params;
21733
- const fullPath = [...path12, ...issueData.path || []];
21732
+ const { data, path: path16, errorMaps, issueData } = params;
21733
+ const fullPath = [...path16, ...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, path12, key) {
21887
+ constructor(parent, value, path16, key) {
21888
21888
  this._cachedPath = [];
21889
21889
  this.parent = parent;
21890
21890
  this.data = value;
21891
- this._path = path12;
21891
+ this._path = path16;
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 z15 = __importStar(require_external());
25481
- exports.z = z15;
25480
+ var z18 = __importStar(require_external());
25481
+ exports.z = z18;
25482
25482
  __exportStar(require_external(), exports);
25483
- exports.default = z15;
25483
+ exports.default = z18;
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.14.1") {
26393
- return "3.14.1";
26392
+ if ("3.16.0") {
26393
+ return "3.16.0";
26394
26394
  }
26395
26395
  try {
26396
26396
  const require2 = createRequire(import.meta.url);
@@ -27562,7 +27562,7 @@ var init_http_backend = __esm({
27562
27562
  * in cloud mode we ignore it and use this.sessionId (the cloud session).
27563
27563
  */
27564
27564
  async registerPeer(peerId, _sessionId, cwd, label, clientVersion, mode, _channelSessionId, capabilities, workspaceId, threadId) {
27565
- const path12 = PATHS.peers.replace(":sessionId", this.sessionId);
27565
+ const path16 = PATHS.peers.replace(":sessionId", this.sessionId);
27566
27566
  const body = {
27567
27567
  peerId,
27568
27568
  cwd,
@@ -27590,7 +27590,7 @@ var init_http_backend = __esm({
27590
27590
  body.role = roleCap.slice("role:".length);
27591
27591
  }
27592
27592
  }
27593
- return this.http.post(path12, body);
27593
+ return this.http.post(path16, body);
27594
27594
  }
27595
27595
  /**
27596
27596
  * Deregister a peer from the cloud session.
@@ -27599,9 +27599,9 @@ var init_http_backend = __esm({
27599
27599
  * Returns true on success, false if the peer was not found.
27600
27600
  */
27601
27601
  async deregisterPeer(peerId) {
27602
- const path12 = PATHS.peer.replace(":sessionId", this.sessionId).replace(":peerId", peerId);
27602
+ const path16 = PATHS.peer.replace(":sessionId", this.sessionId).replace(":peerId", peerId);
27603
27603
  try {
27604
- await this.http.delete(path12, {});
27604
+ await this.http.delete(path16, {});
27605
27605
  return true;
27606
27606
  } catch {
27607
27607
  return false;
@@ -27621,8 +27621,8 @@ var init_http_backend = __esm({
27621
27621
  * GET /api/sessions/:sessionId/peers
27622
27622
  */
27623
27623
  async listPeers() {
27624
- const path12 = PATHS.peers.replace(":sessionId", this.sessionId);
27625
- const result = await this.http.get(path12);
27624
+ const path16 = PATHS.peers.replace(":sessionId", this.sessionId);
27625
+ const result = await this.http.get(path16);
27626
27626
  return result.peers;
27627
27627
  }
27628
27628
  /**
@@ -27630,8 +27630,8 @@ var init_http_backend = __esm({
27630
27630
  * Calls the lightweight heartbeat endpoint on the cloud relay server.
27631
27631
  */
27632
27632
  async updateLastSeen(peerId) {
27633
- const path12 = PATHS.heartbeat.replace(":sessionId", this.sessionId);
27634
- await this.http.post(path12, { peerId });
27633
+ const path16 = PATHS.heartbeat.replace(":sessionId", this.sessionId);
27634
+ await this.http.post(path16, { peerId });
27635
27635
  }
27636
27636
  /**
27637
27637
  * Record a message in the cloud session.
@@ -27642,7 +27642,7 @@ var init_http_backend = __esm({
27642
27642
  * the input record to construct a full MessageRecord.
27643
27643
  */
27644
27644
  async recordMessage(record2) {
27645
- const path12 = PATHS.messages.replace(":sessionId", this.sessionId);
27645
+ const path16 = PATHS.messages.replace(":sessionId", this.sessionId);
27646
27646
  const body = {
27647
27647
  fromPeerId: record2.fromPeerId,
27648
27648
  toPeerId: record2.toPeerId,
@@ -27651,7 +27651,7 @@ var init_http_backend = __esm({
27651
27651
  if (record2.isRelayEcho === true) {
27652
27652
  body.isRelayEcho = true;
27653
27653
  }
27654
- const resp = await this.http.post(path12, body);
27654
+ const resp = await this.http.post(path16, body);
27655
27655
  return {
27656
27656
  ...record2,
27657
27657
  id: resp.id,
@@ -27665,7 +27665,7 @@ var init_http_backend = __esm({
27665
27665
  * Supports optional peerId filter and limit (default 300).
27666
27666
  */
27667
27667
  async getHistory(peerId, limit, includeRelayEchoes, metadataOnly, dedupInferredEchoes) {
27668
- const path12 = PATHS.messages.replace(":sessionId", this.sessionId);
27668
+ const path16 = PATHS.messages.replace(":sessionId", this.sessionId);
27669
27669
  const query = {};
27670
27670
  if (peerId) {
27671
27671
  query.peerId = peerId;
@@ -27680,7 +27680,7 @@ var init_http_backend = __esm({
27680
27680
  if (dedupInferredEchoes) {
27681
27681
  query.dedupInferredEchoes = "true";
27682
27682
  }
27683
- const result = await this.http.get(path12, query);
27683
+ const result = await this.http.get(path16, query);
27684
27684
  return result.messages;
27685
27685
  }
27686
27686
  /**
@@ -27691,12 +27691,12 @@ var init_http_backend = __esm({
27691
27691
  * so that get-history shows the actual response (not null).
27692
27692
  */
27693
27693
  async updateMessageResponse(messageId, response, durationMs, pending) {
27694
- const path12 = PATHS.message.replace(":sessionId", this.sessionId).replace(":messageId", messageId);
27694
+ const path16 = PATHS.message.replace(":sessionId", this.sessionId).replace(":messageId", messageId);
27695
27695
  const body = { response, durationMs };
27696
27696
  if (pending === true) {
27697
27697
  body.pending = true;
27698
27698
  }
27699
- await this.http.patch(path12, body);
27699
+ await this.http.patch(path16, body);
27700
27700
  }
27701
27701
  /**
27702
27702
  * Validate a session.
@@ -27838,8 +27838,8 @@ var init_http_client = __esm({
27838
27838
  * Perform an authenticated GET request.
27839
27839
  * Appends query parameters to the URL if provided.
27840
27840
  */
27841
- async get(path12, query) {
27842
- const url = new URL(path12, this.baseUrl);
27841
+ async get(path16, query) {
27842
+ const url = new URL(path16, this.baseUrl);
27843
27843
  if (query) {
27844
27844
  for (const [k, v] of Object.entries(query)) {
27845
27845
  url.searchParams.set(k, v);
@@ -27854,8 +27854,8 @@ var init_http_client = __esm({
27854
27854
  /**
27855
27855
  * Perform an authenticated POST request with a JSON body.
27856
27856
  */
27857
- async post(path12, body) {
27858
- const url = new URL(path12, this.baseUrl);
27857
+ async post(path16, body) {
27858
+ const url = new URL(path16, this.baseUrl);
27859
27859
  const resp = await this.fetchWithTimeout(url, {
27860
27860
  method: "POST",
27861
27861
  headers: this.headers(),
@@ -27873,8 +27873,8 @@ var init_http_client = __esm({
27873
27873
  * Perform an authenticated PATCH request with a JSON body.
27874
27874
  * Used to update existing resources (e.g., message response fields).
27875
27875
  */
27876
- async patch(path12, body) {
27877
- const url = new URL(path12, this.baseUrl);
27876
+ async patch(path16, body) {
27877
+ const url = new URL(path16, this.baseUrl);
27878
27878
  const resp = await this.fetchWithTimeout(url, {
27879
27879
  method: "PATCH",
27880
27880
  headers: this.headers(),
@@ -27897,8 +27897,8 @@ var init_http_client = __esm({
27897
27897
  * "Malformed JSON in request body" 400 from cogent-server <=3.1.2 when
27898
27898
  * any client (including third-party tools) DELETEd a peer without a body.
27899
27899
  */
27900
- async delete(path12, body) {
27901
- const url = new URL(path12, this.baseUrl);
27900
+ async delete(path16, body) {
27901
+ const url = new URL(path16, this.baseUrl);
27902
27902
  const headers = {
27903
27903
  "Authorization": `Bearer ${this.token}`
27904
27904
  };
@@ -32077,14 +32077,14 @@ var init_ws_client = __esm({
32077
32077
  if (this.pollTimer) return;
32078
32078
  const poll = async () => {
32079
32079
  try {
32080
- const path12 = `/api/sessions/${this.opts.sessionId}/poll`;
32080
+ const path16 = `/api/sessions/${this.opts.sessionId}/poll`;
32081
32081
  const query = {
32082
32082
  peerId: this.opts.peerId
32083
32083
  };
32084
32084
  if (this.lastMessageId) {
32085
32085
  query.lastMessageId = this.lastMessageId;
32086
32086
  }
32087
- const result = await this.opts.http.get(path12, query);
32087
+ const result = await this.opts.http.get(path16, query);
32088
32088
  if (result.messages && result.messages.length > 0) {
32089
32089
  this.opts.onMessages(result.messages);
32090
32090
  this.lastMessageId = result.messages[result.messages.length - 1].id;
@@ -33080,6 +33080,7 @@ var init_auto_relay = __esm({
33080
33080
  logger.info(`Auto-relay: processing message from ${fromLabel} (${msg.fromPeerId})`);
33081
33081
  const traceId = msg.traceId ?? randomUUID2();
33082
33082
  const startMs = Date.now();
33083
+ let replied = false;
33083
33084
  try {
33084
33085
  const res = await this.refreshSessionId();
33085
33086
  this._trace(traceId, "resolved", {
@@ -33126,6 +33127,7 @@ var init_auto_relay = __esm({
33126
33127
  const durationMs = Date.now() - startMs;
33127
33128
  if (result.exitCode === 0 && result.stdout) {
33128
33129
  await this._relayCapturedReply(msg, result, traceId, durationMs, false);
33130
+ replied = true;
33129
33131
  } else {
33130
33132
  this._trace(traceId, "failed", { exitCode: result.exitCode, sandboxBlocked: result.sandboxBlocked, durationMs });
33131
33133
  logger.warn(
@@ -33142,6 +33144,7 @@ var init_auto_relay = __esm({
33142
33144
  const bypassRetry = await execRemote(cfg.COGENT_PLATFORM, this.localSessionId, formatted, this.localCwd);
33143
33145
  if (bypassRetry.exitCode === 0 && bypassRetry.stdout) {
33144
33146
  await this._relayCapturedReply(msg, bypassRetry, traceId, Date.now() - startMs, true);
33147
+ replied = true;
33145
33148
  return;
33146
33149
  }
33147
33150
  logger.warn(`Auto-relay: sandbox-bypass retry also failed (exit=${bypassRetry.exitCode})`);
@@ -33171,15 +33174,49 @@ var init_auto_relay = __esm({
33171
33174
  const retry = await execRemote(getConfig().COGENT_PLATFORM, this.localSessionId, formatted, this.localCwd);
33172
33175
  if (retry.exitCode === 0 && retry.stdout) {
33173
33176
  await this._relayCapturedReply(msg, retry, traceId, Date.now() - startMs, true);
33177
+ replied = true;
33174
33178
  } else {
33175
33179
  logger.warn(`Auto-relay: retry also failed (exit=${retry.exitCode})`);
33176
33180
  }
33177
33181
  }
33178
33182
  }
33183
+ if (!replied) {
33184
+ await this._recordNoReplyFailure(msg, traceId, Date.now() - startMs);
33185
+ }
33179
33186
  } catch (err) {
33180
33187
  const durationMs = Date.now() - startMs;
33181
33188
  this._trace(traceId, "failed", { threw: true, durationMs });
33182
33189
  logger.error(`Auto-relay: execRemote threw after ${durationMs}ms: ${err}`);
33190
+ if (!replied) {
33191
+ await this._recordNoReplyFailure(msg, traceId, durationMs);
33192
+ }
33193
+ }
33194
+ }
33195
+ /**
33196
+ * Fail-loud (2026-08-11): a wake that captured NO reply — empty stdout, a
33197
+ * non-zero exit with no rotated-session retry, a failed retry, or a thrown
33198
+ * execRemote — used to record NOTHING, so the sender saw response:null with no
33199
+ * error (the invisible failure that hid the demo busy-miss). Record ONE visible
33200
+ * success:false notice to the sender so a human/agent knows to reply manually.
33201
+ * On Codex this pairs with Wake-C (check-on-stop), which auto-recovers the reply
33202
+ * at the next turn boundary. Refusal (AMBIGUOUS_SESSION) and sandbox-blocked
33203
+ * paths record their own failure and return before this, so no double-record.
33204
+ */
33205
+ async _recordNoReplyFailure(msg, traceId, durationMs) {
33206
+ try {
33207
+ await getBackend().recordMessage({
33208
+ fromPeerId: this.localPeerId,
33209
+ toPeerId: msg.fromPeerId,
33210
+ 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})`,
33211
+ response: null,
33212
+ durationMs,
33213
+ success: false,
33214
+ error: "NO_REPLY_CAPTURED",
33215
+ isRelayEcho: true,
33216
+ traceId
33217
+ });
33218
+ } catch (recordErr) {
33219
+ logger.error(`Auto-relay: failed to record no-reply failure: ${recordErr}`);
33183
33220
  }
33184
33221
  }
33185
33222
  /**
@@ -33882,8 +33919,61 @@ var init_startup = __esm({
33882
33919
  }
33883
33920
  });
33884
33921
 
33885
- // src/tools/register-peer.ts
33922
+ // src/cloud/mail-credential-store.ts
33886
33923
  import crypto3 from "node:crypto";
33924
+ import fs12 from "node:fs/promises";
33925
+ import path12 from "node:path";
33926
+ import os7 from "node:os";
33927
+ function defaultMailCredentialPath(cwd = process.cwd()) {
33928
+ const hash = crypto3.createHash("sha256").update(path12.resolve(cwd)).digest("hex").slice(0, 16);
33929
+ return path12.join(os7.homedir(), ".cogent", "mail-credentials", `${hash}.json`);
33930
+ }
33931
+ function resolveMailCredentialPath(credentialPath) {
33932
+ if (credentialPath) return credentialPath;
33933
+ const envOverride = process.env.COGENT_MAIL_CREDENTIALS_FILE;
33934
+ if (envOverride) return envOverride;
33935
+ return defaultMailCredentialPath();
33936
+ }
33937
+ async function loadMailCredentials(credentialPath) {
33938
+ const filePath = resolveMailCredentialPath(credentialPath);
33939
+ try {
33940
+ return JSON.parse(await fs12.readFile(filePath, "utf-8"));
33941
+ } catch {
33942
+ return null;
33943
+ }
33944
+ }
33945
+ async function saveMailCredentials(creds, credentialPath) {
33946
+ const filePath = resolveMailCredentialPath(credentialPath);
33947
+ await fs12.mkdir(path12.dirname(filePath), { recursive: true, mode: 448 });
33948
+ await fs12.writeFile(filePath, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
33949
+ await fs12.chmod(filePath, 384);
33950
+ }
33951
+ async function persistProvisionedMailbox(mailbox, credentialPath) {
33952
+ if (!mailbox?.address || !mailbox.password) return false;
33953
+ await saveMailCredentials(
33954
+ {
33955
+ address: mailbox.address,
33956
+ password: mailbox.password,
33957
+ imapHost: MAIL_DEFAULT_HOST,
33958
+ imapPort: 993,
33959
+ smtpHost: MAIL_DEFAULT_HOST,
33960
+ smtpPort: 465,
33961
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
33962
+ },
33963
+ credentialPath
33964
+ );
33965
+ return true;
33966
+ }
33967
+ var MAIL_DEFAULT_HOST;
33968
+ var init_mail_credential_store = __esm({
33969
+ "src/cloud/mail-credential-store.ts"() {
33970
+ "use strict";
33971
+ MAIL_DEFAULT_HOST = "mail.cogent.tools";
33972
+ }
33973
+ });
33974
+
33975
+ // src/tools/register-peer.ts
33976
+ import crypto4 from "node:crypto";
33887
33977
  function registerRegisterPeerTool(server) {
33888
33978
  server.registerTool(
33889
33979
  "cogent_register_peer",
@@ -33942,7 +34032,7 @@ function registerRegisterPeerTool(server) {
33942
34032
  }
33943
34033
  }
33944
34034
  if (!cloudSessionId) {
33945
- autoSecret = crypto3.randomBytes(16).toString("hex");
34035
+ autoSecret = crypto4.randomBytes(16).toString("hex");
33946
34036
  const resp = await fetch(
33947
34037
  `${endpoint}/api/sessions`,
33948
34038
  {
@@ -34021,6 +34111,15 @@ function registerRegisterPeerTool(server) {
34021
34111
  );
34022
34112
  }
34023
34113
  }
34114
+ try {
34115
+ if (await persistProvisionedMailbox(peer?.mailbox)) {
34116
+ logger.info(`Cogent Mail: saved auto-provisioned mailbox ${peer.mailbox?.address}`);
34117
+ }
34118
+ } catch (err) {
34119
+ logger.warn(
34120
+ `Cogent Mail: could not save auto-provisioned mailbox creds: ${err.message}`
34121
+ );
34122
+ }
34024
34123
  const resolution = await resolveSession(
34025
34124
  getConfig().COGENT_PLATFORM,
34026
34125
  cwd,
@@ -34103,6 +34202,7 @@ var init_register_peer = __esm({
34103
34202
  init_backend();
34104
34203
  init_errors4();
34105
34204
  init_credential_store();
34205
+ init_mail_credential_store();
34106
34206
  init_logger();
34107
34207
  init_heartbeat();
34108
34208
  init_auto_relay();
@@ -34412,7 +34512,8 @@ var init_peer = __esm({
34412
34512
  workspaceId: import_zod5.z.string().optional().describe("Durable workspace identifier (Slice 5) \u2014 stable id not tied to the mutable cwd hint"),
34413
34513
  threadId: import_zod5.z.string().optional().describe("Durable thread/conversation identifier (Slice 5)"),
34414
34514
  role: import_zod5.z.string().max(64).optional().describe("A2A role advertised by this peer (Agent Card)"),
34415
- capabilities: import_zod5.z.array(import_zod5.z.string().max(64)).max(16).optional().describe("Capability strings \u2192 A2A Agent Card skills[]")
34515
+ capabilities: import_zod5.z.array(import_zod5.z.string().max(64)).max(16).optional().describe("Capability strings \u2192 A2A Agent Card skills[]"),
34516
+ mailboxAddress: import_zod5.z.string().max(320).optional().describe("Auto-provisioned Cogent Mail address (non-secret); NEVER the password")
34416
34517
  });
34417
34518
  }
34418
34519
  });
@@ -34760,6 +34861,19 @@ function toSkills(peer) {
34760
34861
  };
34761
34862
  });
34762
34863
  }
34864
+ function toMailboxExtensions(peer) {
34865
+ const address = peer.mailboxAddress;
34866
+ if (!address)
34867
+ return [];
34868
+ return [
34869
+ {
34870
+ uri: COGENT_MAILBOX_EXTENSION_URI,
34871
+ description: "Cogent Mail mailbox address (non-secret) for this peer.",
34872
+ required: false,
34873
+ params: { address }
34874
+ }
34875
+ ];
34876
+ }
34763
34877
  function synthDescription(peer) {
34764
34878
  const parts = [`Cogent fabric peer on ${peer.platform ?? "cc"}`];
34765
34879
  if (peer.role)
@@ -34775,6 +34889,7 @@ function peerInfoToAgentCard(peer, ctx) {
34775
34889
  const version2 = ctx.clientVersion ?? peer.clientVersion ?? "0.0.0";
34776
34890
  const provider = { organization: `Cogent \xB7 ${peer.platform ?? "cc"}`, url: "https://cogent.tools" };
34777
34891
  const skills = toSkills(peer);
34892
+ const extensions = toMailboxExtensions(peer);
34778
34893
  if (ctx.cardVersion === "0.3.0") {
34779
34894
  return {
34780
34895
  protocolVersion: "0.3.0",
@@ -34783,7 +34898,7 @@ function peerInfoToAgentCard(peer, ctx) {
34783
34898
  url,
34784
34899
  version: version2,
34785
34900
  provider,
34786
- capabilities: {},
34901
+ capabilities: extensions.length ? { extensions } : {},
34787
34902
  defaultInputModes: ["text/plain"],
34788
34903
  defaultOutputModes: ["text/plain"],
34789
34904
  skills,
@@ -34804,7 +34919,7 @@ function peerInfoToAgentCard(peer, ctx) {
34804
34919
  tenant: ctx.sessionId
34805
34920
  }
34806
34921
  ],
34807
- capabilities: { extensions: [] },
34922
+ capabilities: { extensions },
34808
34923
  defaultInputModes: ["text/plain"],
34809
34924
  defaultOutputModes: ["text/plain"],
34810
34925
  skills,
@@ -34813,12 +34928,13 @@ function peerInfoToAgentCard(peer, ctx) {
34813
34928
  signatures: []
34814
34929
  };
34815
34930
  }
34816
- var COGENT_PROTOCOL_BINDING, A2A_PROTOCOL_VERSION;
34931
+ var COGENT_PROTOCOL_BINDING, A2A_PROTOCOL_VERSION, COGENT_MAILBOX_EXTENSION_URI;
34817
34932
  var init_agent_card = __esm({
34818
34933
  "cogent/dist/a2a/agent-card.js"() {
34819
34934
  "use strict";
34820
34935
  COGENT_PROTOCOL_BINDING = "COGENT-RELAY";
34821
34936
  A2A_PROTOCOL_VERSION = "1.0";
34937
+ COGENT_MAILBOX_EXTENSION_URI = "https://cogent.tools/a2a/ext/mailbox";
34822
34938
  }
34823
34939
  });
34824
34940
 
@@ -35113,7 +35229,7 @@ var init_health_check2 = __esm({
35113
35229
  });
35114
35230
 
35115
35231
  // src/tools/create-session.ts
35116
- import crypto4 from "node:crypto";
35232
+ import crypto5 from "node:crypto";
35117
35233
  function registerCreateSessionTool(server) {
35118
35234
  server.registerTool(
35119
35235
  "cogent_create_session",
@@ -35144,7 +35260,7 @@ function registerCreateSessionTool(server) {
35144
35260
  )
35145
35261
  );
35146
35262
  }
35147
- const sessionSecret = secret ?? crypto4.randomBytes(16).toString("hex");
35263
+ const sessionSecret = secret ?? crypto5.randomBytes(16).toString("hex");
35148
35264
  const resp = await fetch(
35149
35265
  `${config2.COGENT_ENDPOINT}/api/sessions`,
35150
35266
  {
@@ -35372,6 +35488,386 @@ var init_join_session = __esm({
35372
35488
  }
35373
35489
  });
35374
35490
 
35491
+ // src/mail/validate.ts
35492
+ function isValidEmail(value) {
35493
+ return value.length <= 254 && EMAIL_RE.test(value);
35494
+ }
35495
+ function assertValidEmail(value, field) {
35496
+ if (!isValidEmail(value)) {
35497
+ throw new BridgeError(
35498
+ "INVALID_INPUT" /* INVALID_INPUT */,
35499
+ `${field} is not a valid email address: ${JSON.stringify(value)}`,
35500
+ `Provide a full address like agent-channel@mail.cogent.tools`
35501
+ );
35502
+ }
35503
+ }
35504
+ var EMAIL_RE;
35505
+ var init_validate = __esm({
35506
+ "src/mail/validate.ts"() {
35507
+ "use strict";
35508
+ init_errors4();
35509
+ EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
35510
+ }
35511
+ });
35512
+
35513
+ // src/tools/setup-mail.ts
35514
+ function registerSetupMailTool(server) {
35515
+ server.registerTool(
35516
+ "cogent_setup_mail",
35517
+ {
35518
+ title: "Configure this agent's mailbox",
35519
+ 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).",
35520
+ inputSchema: {
35521
+ address: import_zod16.z.string().describe("Full mailbox address, e.g. claude-agent-backend@mail.cogent.tools"),
35522
+ password: import_zod16.z.string().describe("Mailbox password (shown once in the admin Mail panel on create/rotate)"),
35523
+ imapHost: import_zod16.z.string().optional().describe(`IMAP host (default ${DEFAULT_MAIL_HOST})`),
35524
+ imapPort: import_zod16.z.number().int().positive().optional().describe("IMAP port (default 993)"),
35525
+ smtpHost: import_zod16.z.string().optional().describe(`SMTP host (default ${DEFAULT_MAIL_HOST})`),
35526
+ smtpPort: import_zod16.z.number().int().positive().optional().describe("SMTP submission port (default 465)")
35527
+ },
35528
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
35529
+ },
35530
+ async ({ address, password, imapHost, imapPort, smtpHost, smtpPort }) => {
35531
+ try {
35532
+ assertValidEmail(address, "address");
35533
+ await saveMailCredentials({
35534
+ address,
35535
+ password,
35536
+ imapHost: imapHost ?? DEFAULT_MAIL_HOST,
35537
+ imapPort: imapPort ?? 993,
35538
+ smtpHost: smtpHost ?? DEFAULT_MAIL_HOST,
35539
+ smtpPort: smtpPort ?? 465,
35540
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
35541
+ });
35542
+ return successResult({ configured: true, address });
35543
+ } catch (err) {
35544
+ return errorResult(err);
35545
+ }
35546
+ }
35547
+ );
35548
+ }
35549
+ var import_zod16, DEFAULT_MAIL_HOST;
35550
+ var init_setup_mail = __esm({
35551
+ "src/tools/setup-mail.ts"() {
35552
+ "use strict";
35553
+ import_zod16 = __toESM(require_zod(), 1);
35554
+ init_errors4();
35555
+ init_mail_credential_store();
35556
+ init_validate();
35557
+ DEFAULT_MAIL_HOST = MAIL_DEFAULT_HOST;
35558
+ }
35559
+ });
35560
+
35561
+ // src/mail/mail-sender.ts
35562
+ import fs13 from "node:fs/promises";
35563
+ import path13 from "node:path";
35564
+ async function buildAndSendMail(sender, creds, params) {
35565
+ const attachments = [];
35566
+ for (const p of params.attachmentPaths ?? []) {
35567
+ const content = await fs13.readFile(p);
35568
+ attachments.push({ filename: path13.basename(p), content });
35569
+ }
35570
+ const { messageId } = await sender.send({
35571
+ from: creds.address,
35572
+ to: params.to,
35573
+ subject: params.subject,
35574
+ text: params.text,
35575
+ attachments
35576
+ });
35577
+ return { messageId, from: creds.address, to: params.to };
35578
+ }
35579
+ var NodemailerMailSender;
35580
+ var init_mail_sender = __esm({
35581
+ "src/mail/mail-sender.ts"() {
35582
+ "use strict";
35583
+ NodemailerMailSender = class {
35584
+ constructor(creds, transportFactory) {
35585
+ this.creds = creds;
35586
+ this.transportFactory = transportFactory;
35587
+ }
35588
+ creds;
35589
+ transportFactory;
35590
+ async send(mail) {
35591
+ const transport = this.transportFactory ? await this.transportFactory() : await this.defaultTransport();
35592
+ const info = await transport.sendMail({
35593
+ from: mail.from,
35594
+ to: mail.to,
35595
+ subject: mail.subject,
35596
+ text: mail.text,
35597
+ attachments: mail.attachments.map((a) => ({ filename: a.filename, content: a.content }))
35598
+ });
35599
+ return { messageId: info.messageId ?? "" };
35600
+ }
35601
+ async defaultTransport() {
35602
+ const specifier = "nodemailer";
35603
+ const nodemailer = await import(specifier);
35604
+ return nodemailer.createTransport({
35605
+ host: this.creds.smtpHost,
35606
+ port: this.creds.smtpPort,
35607
+ secure: this.creds.smtpPort === 465,
35608
+ // implicit TLS on 465, STARTTLS on 587
35609
+ auth: { user: this.creds.address, pass: this.creds.password }
35610
+ });
35611
+ }
35612
+ };
35613
+ }
35614
+ });
35615
+
35616
+ // src/tools/send-mail.ts
35617
+ function registerSendMailTool(server, deps = {}) {
35618
+ const makeSender = deps.senderFactory ?? ((creds) => new NodemailerMailSender(creds));
35619
+ server.registerTool(
35620
+ "cogent_send_mail",
35621
+ {
35622
+ title: "Send an email from this agent's mailbox",
35623
+ 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.",
35624
+ inputSchema: {
35625
+ to: import_zod17.z.string().describe("Recipient email address, e.g. other-agent-backend@mail.cogent.tools"),
35626
+ subject: import_zod17.z.string().describe("Email subject"),
35627
+ body: import_zod17.z.string().describe("Plain-text email body"),
35628
+ attachments: import_zod17.z.array(import_zod17.z.string()).optional().describe("Optional local file paths to attach (each is read and attached by basename)")
35629
+ },
35630
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
35631
+ },
35632
+ async ({ to, subject, body, attachments }) => {
35633
+ try {
35634
+ assertValidEmail(to, "to");
35635
+ const creds = await loadMailCredentials();
35636
+ if (!creds) {
35637
+ return errorResult(
35638
+ new BridgeError(
35639
+ "INVALID_INPUT" /* INVALID_INPUT */,
35640
+ "No mailbox is configured for this agent",
35641
+ "Run cogent_setup_mail with the address + password from the admin Mail panel first"
35642
+ )
35643
+ );
35644
+ }
35645
+ const result = await buildAndSendMail(makeSender(creds), creds, {
35646
+ to: [to],
35647
+ subject,
35648
+ text: body,
35649
+ attachmentPaths: attachments
35650
+ });
35651
+ return successResult({ ...result, attachmentCount: attachments?.length ?? 0 });
35652
+ } catch (err) {
35653
+ return errorResult(err);
35654
+ }
35655
+ }
35656
+ );
35657
+ }
35658
+ var import_zod17;
35659
+ var init_send_mail = __esm({
35660
+ "src/tools/send-mail.ts"() {
35661
+ "use strict";
35662
+ import_zod17 = __toESM(require_zod(), 1);
35663
+ init_errors4();
35664
+ init_mail_credential_store();
35665
+ init_mail_sender();
35666
+ init_validate();
35667
+ }
35668
+ });
35669
+
35670
+ // src/mail/mail-fetcher.ts
35671
+ import fs14 from "node:fs/promises";
35672
+ import path14 from "node:path";
35673
+ async function saveAttachments(attachments, downloadDir) {
35674
+ if (attachments.length === 0) return [];
35675
+ await fs14.mkdir(downloadDir, { recursive: true });
35676
+ const saved = [];
35677
+ for (const a of attachments) {
35678
+ const name = path14.basename(a.filename) || "attachment";
35679
+ const savedPath = path14.join(downloadDir, name);
35680
+ await fs14.writeFile(savedPath, a.content);
35681
+ saved.push({ filename: name, savedPath, size: a.content.length });
35682
+ }
35683
+ return saved;
35684
+ }
35685
+ async function defaultImapSessionFactory(creds) {
35686
+ const specifier = "imapflow";
35687
+ const { ImapFlow } = await import(specifier);
35688
+ const client = new ImapFlow({
35689
+ host: creds.imapHost,
35690
+ port: creds.imapPort,
35691
+ secure: creds.imapPort === 993,
35692
+ auth: { user: creds.address, pass: creds.password },
35693
+ logger: false
35694
+ });
35695
+ await client.connect();
35696
+ await client.mailboxOpen("INBOX");
35697
+ return {
35698
+ async list(unreadOnly) {
35699
+ const criteria = unreadOnly ? { seen: false } : { all: true };
35700
+ const rows = [];
35701
+ for await (const msg of client.fetch(criteria, { uid: true, envelope: true, flags: true })) {
35702
+ const env = msg.envelope ?? {};
35703
+ rows.push({
35704
+ uid: msg.uid,
35705
+ from: env.from?.[0]?.address ?? "",
35706
+ subject: env.subject ?? "",
35707
+ date: env.date instanceof Date ? env.date.toISOString() : String(env.date ?? ""),
35708
+ unread: !hasFlag(msg.flags, "\\Seen")
35709
+ });
35710
+ }
35711
+ return rows.reverse();
35712
+ },
35713
+ async fetch(uid) {
35714
+ const msg = await client.fetchOne(uid, { uid: true, envelope: true, bodyStructure: true }, { uid: true });
35715
+ if (!msg) throw new Error(`no message with uid ${uid}`);
35716
+ const parts = flattenBodyStructure(msg.bodyStructure);
35717
+ let text = "";
35718
+ const attachments = [];
35719
+ for (const p of parts) {
35720
+ if (p.filename) {
35721
+ const dl = await client.download(uid, p.part, { uid: true });
35722
+ attachments.push({ filename: p.filename, content: await streamToBuffer(dl.content) });
35723
+ } else if (p.isText && !text) {
35724
+ const dl = await client.download(uid, p.part, { uid: true });
35725
+ text = (await streamToBuffer(dl.content)).toString("utf-8");
35726
+ }
35727
+ }
35728
+ const env = msg.envelope ?? {};
35729
+ return {
35730
+ from: env.from?.[0]?.address ?? "",
35731
+ subject: env.subject ?? "",
35732
+ date: env.date instanceof Date ? env.date.toISOString() : String(env.date ?? ""),
35733
+ text,
35734
+ attachments
35735
+ };
35736
+ },
35737
+ async close() {
35738
+ try {
35739
+ await client.logout();
35740
+ } catch {
35741
+ }
35742
+ }
35743
+ };
35744
+ }
35745
+ function flattenBodyStructure(node, acc = []) {
35746
+ if (!node || typeof node !== "object") return acc;
35747
+ const n = node;
35748
+ if (Array.isArray(n.childNodes) && n.childNodes.length > 0) {
35749
+ for (const child of n.childNodes) flattenBodyStructure(child, acc);
35750
+ return acc;
35751
+ }
35752
+ const type = (n.type ?? "").toLowerCase();
35753
+ const filename = n.dispositionParameters?.filename ?? n.parameters?.name;
35754
+ const isAttachment = (n.disposition ?? "").toLowerCase() === "attachment" || Boolean(filename);
35755
+ acc.push({
35756
+ part: n.part ?? "1",
35757
+ // single-part messages have no `part`; imapflow addresses them as "1"
35758
+ type,
35759
+ isText: type === "text/plain" && !isAttachment,
35760
+ filename: isAttachment ? filename : void 0
35761
+ });
35762
+ return acc;
35763
+ }
35764
+ function hasFlag(flags, flag) {
35765
+ if (flags instanceof Set) return flags.has(flag);
35766
+ if (Array.isArray(flags)) return flags.includes(flag);
35767
+ return false;
35768
+ }
35769
+ async function streamToBuffer(stream) {
35770
+ const chunks = [];
35771
+ if (!stream) return Buffer.alloc(0);
35772
+ for await (const chunk of stream) chunks.push(Buffer.from(chunk));
35773
+ return Buffer.concat(chunks);
35774
+ }
35775
+ var ImapflowMailFetcher;
35776
+ var init_mail_fetcher = __esm({
35777
+ "src/mail/mail-fetcher.ts"() {
35778
+ "use strict";
35779
+ ImapflowMailFetcher = class {
35780
+ constructor(creds, sessionFactory = defaultImapSessionFactory) {
35781
+ this.creds = creds;
35782
+ this.sessionFactory = sessionFactory;
35783
+ }
35784
+ creds;
35785
+ sessionFactory;
35786
+ async list(opts = {}) {
35787
+ const session = await this.sessionFactory(this.creds);
35788
+ try {
35789
+ let rows = await session.list(opts.unreadOnly ?? false);
35790
+ if (opts.limit && opts.limit > 0) rows = rows.slice(0, opts.limit);
35791
+ return rows;
35792
+ } finally {
35793
+ await session.close();
35794
+ }
35795
+ }
35796
+ async fetchOne(uid, downloadDir) {
35797
+ const session = await this.sessionFactory(this.creds);
35798
+ try {
35799
+ const c2 = await session.fetch(uid);
35800
+ const saved = await saveAttachments(c2.attachments, downloadDir);
35801
+ return { uid, from: c2.from, subject: c2.subject, date: c2.date, text: c2.text, attachments: saved };
35802
+ } finally {
35803
+ await session.close();
35804
+ }
35805
+ }
35806
+ };
35807
+ }
35808
+ });
35809
+
35810
+ // src/tools/fetch-mail.ts
35811
+ import os8 from "node:os";
35812
+ import path15 from "node:path";
35813
+ function registerFetchMailTool(server, deps = {}) {
35814
+ const makeFetcher = deps.fetcherFactory ?? ((creds) => new ImapflowMailFetcher(creds));
35815
+ server.registerTool(
35816
+ "cogent_fetch_mail",
35817
+ {
35818
+ title: "Read this agent's mailbox",
35819
+ 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.",
35820
+ inputSchema: {
35821
+ action: import_zod18.z.enum(["list", "fetch"]).optional().describe("'list' (default) or 'fetch' one message"),
35822
+ uid: import_zod18.z.number().int().positive().optional().describe("Message uid to fetch (required when action='fetch')"),
35823
+ unreadOnly: import_zod18.z.boolean().optional().describe("List only unread messages (default false)"),
35824
+ limit: import_zod18.z.number().int().positive().optional().describe("Max messages to list (default 25)"),
35825
+ downloadDir: import_zod18.z.string().optional().describe(`Directory to save fetched attachments (default ${DEFAULT_DOWNLOAD_DIR})`)
35826
+ },
35827
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
35828
+ },
35829
+ async ({ action, uid, unreadOnly, limit, downloadDir }) => {
35830
+ try {
35831
+ const creds = await loadMailCredentials();
35832
+ if (!creds) {
35833
+ return errorResult(
35834
+ new BridgeError(
35835
+ "INVALID_INPUT" /* INVALID_INPUT */,
35836
+ "No mailbox is configured for this agent",
35837
+ "Run cogent_setup_mail with the address + password from the admin Mail panel first"
35838
+ )
35839
+ );
35840
+ }
35841
+ const fetcher = makeFetcher(creds);
35842
+ if (action === "fetch") {
35843
+ if (uid === void 0) {
35844
+ return errorResult(
35845
+ new BridgeError("INVALID_INPUT" /* INVALID_INPUT */, "uid is required when action='fetch'", "Pass the uid from a cogent_fetch_mail list")
35846
+ );
35847
+ }
35848
+ const message = await fetcher.fetchOne(uid, downloadDir ?? DEFAULT_DOWNLOAD_DIR);
35849
+ return successResult({ mailbox: creds.address, message });
35850
+ }
35851
+ const messages = await fetcher.list({ unreadOnly, limit: limit ?? 25 });
35852
+ return successResult({ mailbox: creds.address, count: messages.length, messages });
35853
+ } catch (err) {
35854
+ return errorResult(err);
35855
+ }
35856
+ }
35857
+ );
35858
+ }
35859
+ var import_zod18, DEFAULT_DOWNLOAD_DIR;
35860
+ var init_fetch_mail = __esm({
35861
+ "src/tools/fetch-mail.ts"() {
35862
+ "use strict";
35863
+ import_zod18 = __toESM(require_zod(), 1);
35864
+ init_errors4();
35865
+ init_mail_credential_store();
35866
+ init_mail_fetcher();
35867
+ DEFAULT_DOWNLOAD_DIR = path15.join(os8.homedir(), ".cogent", "mail-downloads");
35868
+ }
35869
+ });
35870
+
35375
35871
  // src/index.ts
35376
35872
  var index_exports = {};
35377
35873
  async function main() {
@@ -35385,6 +35881,9 @@ async function main() {
35385
35881
  registerHealthCheckTool(server);
35386
35882
  registerCreateSessionTool(server);
35387
35883
  registerJoinSessionTool(server);
35884
+ registerSetupMailTool(server);
35885
+ registerSendMailTool(server);
35886
+ registerFetchMailTool(server);
35388
35887
  if (cloudInbox) {
35389
35888
  server.registerResource(
35390
35889
  "cogent_inbox",
@@ -35444,6 +35943,9 @@ var init_index = __esm({
35444
35943
  init_health_check2();
35445
35944
  init_create_session();
35446
35945
  init_join_session();
35946
+ init_setup_mail();
35947
+ init_send_mail();
35948
+ init_fetch_mail();
35447
35949
  init_heartbeat();
35448
35950
  process.on("uncaughtException", (err) => {
35449
35951
  if (err.code === "EPIPE") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@essentialai/cogent-plugin",
3
- "version": "3.14.1",
3
+ "version": "3.16.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",