@essentialai/cogent-plugin 3.15.0 → 3.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/bridge/cogent-bridge.mjs +260 -24
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cogent",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.17.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",
|
package/bridge/cogent-bridge.mjs
CHANGED
|
@@ -26389,8 +26389,8 @@ var init_stdio2 = __esm({
|
|
|
26389
26389
|
// src/constants.ts
|
|
26390
26390
|
import { createRequire } from "node:module";
|
|
26391
26391
|
function resolveVersion() {
|
|
26392
|
-
if ("3.
|
|
26393
|
-
return "3.
|
|
26392
|
+
if ("3.17.0") {
|
|
26393
|
+
return "3.17.0";
|
|
26394
26394
|
}
|
|
26395
26395
|
try {
|
|
26396
26396
|
const require2 = createRequire(import.meta.url);
|
|
@@ -26679,7 +26679,13 @@ var init_config = __esm({
|
|
|
26679
26679
|
// "human-broadcast" (toPeerId === "*" from a human origin: slack/gchat/web).
|
|
26680
26680
|
// Default both. Agent-origin broadcasts are never auto-surfaced by C (storm-safe;
|
|
26681
26681
|
// COGENT_BROADCAST_AWARENESS already handles seeing peer broadcasts).
|
|
26682
|
-
COGENT_CHECK_ON_STOP_SCOPE: import_zod2.z.string().default("directed,human-broadcast")
|
|
26682
|
+
COGENT_CHECK_ON_STOP_SCOPE: import_zod2.z.string().default("directed,human-broadcast"),
|
|
26683
|
+
// Cogent Mail F4 — the mailbox domains an agent may configure + send to. CSV; default is the
|
|
26684
|
+
// single Cogent mail domain. Cogent mail is ORGANISATION-ONLY (compliance: an accountable org
|
|
26685
|
+
// owns every mailbox), so BYO external / corporate-domain mailboxes are BLOCKED for now — this
|
|
26686
|
+
// allowlist is the configurable seam for adding per-org corporate domains in a future release.
|
|
26687
|
+
// Baked default here (a LIBRARY default, not plugin env) so Team agents get it too.
|
|
26688
|
+
COGENT_MAIL_ALLOWED_DOMAINS: import_zod2.z.string().default("mail.cogent.tools")
|
|
26683
26689
|
});
|
|
26684
26690
|
_config = null;
|
|
26685
26691
|
}
|
|
@@ -27463,8 +27469,8 @@ var init_file_backend = __esm({
|
|
|
27463
27469
|
threadId
|
|
27464
27470
|
);
|
|
27465
27471
|
}
|
|
27466
|
-
deregisterPeer(peerId) {
|
|
27467
|
-
return deregisterPeer(peerId);
|
|
27472
|
+
async deregisterPeer(peerId) {
|
|
27473
|
+
return { removed: await deregisterPeer(peerId), mailboxDeprovisioned: false };
|
|
27468
27474
|
}
|
|
27469
27475
|
getPeer(peerId) {
|
|
27470
27476
|
return getPeer(peerId);
|
|
@@ -27531,7 +27537,7 @@ var init_relay_version_cache = __esm({
|
|
|
27531
27537
|
});
|
|
27532
27538
|
|
|
27533
27539
|
// src/backend/http-backend.ts
|
|
27534
|
-
var PATHS, HttpBackend;
|
|
27540
|
+
var MAILBOX_DEPROVISIONED_HEADER, PATHS, HttpBackend;
|
|
27535
27541
|
var init_http_backend = __esm({
|
|
27536
27542
|
"src/backend/http-backend.ts"() {
|
|
27537
27543
|
"use strict";
|
|
@@ -27539,6 +27545,7 @@ var init_http_backend = __esm({
|
|
|
27539
27545
|
init_constants();
|
|
27540
27546
|
init_config();
|
|
27541
27547
|
init_relay_version_cache();
|
|
27548
|
+
MAILBOX_DEPROVISIONED_HEADER = "X-Cogent-Mailbox-Deprovisioned";
|
|
27542
27549
|
PATHS = {
|
|
27543
27550
|
peers: "/api/sessions/:sessionId/peers",
|
|
27544
27551
|
peer: "/api/sessions/:sessionId/peers/:peerId",
|
|
@@ -27596,15 +27603,19 @@ var init_http_backend = __esm({
|
|
|
27596
27603
|
* Deregister a peer from the cloud session.
|
|
27597
27604
|
* DELETE /api/sessions/:sessionId/peers/:peerId
|
|
27598
27605
|
*
|
|
27599
|
-
* Returns
|
|
27606
|
+
* Returns `{ removed, mailboxDeprovisioned }`. `mailboxDeprovisioned` reflects the relay's
|
|
27607
|
+
* `X-Cogent-Mailbox-Deprovisioned` response header (Cogent Mail F3) — true ONLY when the relay
|
|
27608
|
+
* confirmed the Team mailbox was reaped, so the deregister-peer tool can clear the now-dead
|
|
27609
|
+
* local mail creds. A 404 (peer not found) resolves to `{ removed: false, ... }`, not a throw.
|
|
27600
27610
|
*/
|
|
27601
27611
|
async deregisterPeer(peerId) {
|
|
27602
27612
|
const path16 = PATHS.peer.replace(":sessionId", this.sessionId).replace(":peerId", peerId);
|
|
27603
27613
|
try {
|
|
27604
|
-
await this.http.delete(path16, {});
|
|
27605
|
-
|
|
27614
|
+
const headers = await this.http.delete(path16, {});
|
|
27615
|
+
const mailboxDeprovisioned = headers.get(MAILBOX_DEPROVISIONED_HEADER) === "true";
|
|
27616
|
+
return { removed: true, mailboxDeprovisioned };
|
|
27606
27617
|
} catch {
|
|
27607
|
-
return false;
|
|
27618
|
+
return { removed: false, mailboxDeprovisioned: false };
|
|
27608
27619
|
}
|
|
27609
27620
|
}
|
|
27610
27621
|
/**
|
|
@@ -27651,6 +27662,9 @@ var init_http_backend = __esm({
|
|
|
27651
27662
|
if (record2.isRelayEcho === true) {
|
|
27652
27663
|
body.isRelayEcho = true;
|
|
27653
27664
|
}
|
|
27665
|
+
if (record2.attachments && record2.attachments.length > 0) {
|
|
27666
|
+
body.attachments = record2.attachments;
|
|
27667
|
+
}
|
|
27654
27668
|
const resp = await this.http.post(path16, body);
|
|
27655
27669
|
return {
|
|
27656
27670
|
...record2,
|
|
@@ -27890,7 +27904,9 @@ var init_http_client = __esm({
|
|
|
27890
27904
|
}
|
|
27891
27905
|
/**
|
|
27892
27906
|
* Perform an authenticated DELETE request.
|
|
27893
|
-
* Returns
|
|
27907
|
+
* Returns the RESPONSE HEADERS (a 204 carries no body, but the relay conveys
|
|
27908
|
+
* out-of-band signals — e.g. Cogent Mail F3's `X-Cogent-Mailbox-Deprovisioned` —
|
|
27909
|
+
* via headers, so callers that need them can read them without a body).
|
|
27894
27910
|
*
|
|
27895
27911
|
* When no body is provided, Content-Type is omitted so servers don't try
|
|
27896
27912
|
* to parse an empty payload as JSON. This was the root cause of a
|
|
@@ -27913,6 +27929,7 @@ var init_http_client = __esm({
|
|
|
27913
27929
|
if (!resp.ok && resp.status !== 204) {
|
|
27914
27930
|
await this.throwMappedError(resp);
|
|
27915
27931
|
}
|
|
27932
|
+
return resp.headers;
|
|
27916
27933
|
}
|
|
27917
27934
|
/**
|
|
27918
27935
|
* Update the Bearer token (e.g., after join-session returns a new token).
|
|
@@ -32514,27 +32531,40 @@ function parseStatusNotice(message) {
|
|
|
32514
32531
|
const m = STATUS_MARKER_RE.exec(message);
|
|
32515
32532
|
return m ? m[1] : null;
|
|
32516
32533
|
}
|
|
32517
|
-
function
|
|
32534
|
+
function formatAttachmentsBlock(attachments) {
|
|
32535
|
+
if (!attachments || attachments.length === 0) return "";
|
|
32536
|
+
const lines = attachments.map((a) => {
|
|
32537
|
+
const name = a.name || a.mailMessageId || a.url || "file";
|
|
32538
|
+
const ref = a.mailMessageId ? `fetch via cogent_fetch_mail (${a.mailMessageId})` : a.url ? a.url : "no fetchable reference";
|
|
32539
|
+
const meta = [a.mimeType, a.size != null ? `${a.size}B` : null].filter(Boolean).join(", ");
|
|
32540
|
+
return ` [file: ${name}${meta ? ` (${meta})` : ""} \u2014 ${ref}]`;
|
|
32541
|
+
});
|
|
32542
|
+
return `
|
|
32543
|
+
|
|
32544
|
+
\u{1F4CE} Attachments (byte transport = email):
|
|
32545
|
+
${lines.join("\n")}`;
|
|
32546
|
+
}
|
|
32547
|
+
function formatRelayMessage(fromLabel, fromPeerId, message, attachments) {
|
|
32518
32548
|
return `[Cogent Bridge message from ${fromLabel} (${fromPeerId})]
|
|
32519
32549
|
|
|
32520
|
-
${message}
|
|
32550
|
+
${message}${formatAttachmentsBlock(attachments)}
|
|
32521
32551
|
|
|
32522
32552
|
---
|
|
32523
32553
|
Respond directly to the message above. Your entire response is delivered automatically to whoever should receive it \u2014 a broadcast question (to the whole channel) is answered to everyone; a direct message, to the sender. You do NOT need to \u2014 and should NOT \u2014 call cogent_send_message or any bridge tool to deliver it; just answer normally.`;
|
|
32524
32554
|
}
|
|
32525
|
-
function formatInjectOnlyMessage(fromLabel, fromPeerId, message) {
|
|
32555
|
+
function formatInjectOnlyMessage(fromLabel, fromPeerId, message, attachments) {
|
|
32526
32556
|
return `[Cogent Bridge: Response from ${fromLabel} (${fromPeerId})]
|
|
32527
32557
|
|
|
32528
|
-
${message}
|
|
32558
|
+
${message}${formatAttachmentsBlock(attachments)}
|
|
32529
32559
|
|
|
32530
32560
|
---
|
|
32531
32561
|
This is the response to your earlier cogent_send_message. No reply will be relayed.
|
|
32532
32562
|
To continue the conversation, use cogent_send_message again.`;
|
|
32533
32563
|
}
|
|
32534
|
-
function formatPeerBroadcastNotice(fromLabel, fromPeerId, message) {
|
|
32564
|
+
function formatPeerBroadcastNotice(fromLabel, fromPeerId, message, attachments) {
|
|
32535
32565
|
return `[Cogent Bridge: broadcast from ${fromLabel} (${fromPeerId}) to the whole channel]
|
|
32536
32566
|
|
|
32537
|
-
${message}
|
|
32567
|
+
${message}${formatAttachmentsBlock(attachments)}
|
|
32538
32568
|
|
|
32539
32569
|
---
|
|
32540
32570
|
FYI ONLY \u2014 another agent addressed the whole channel. Take note for context, but do NOT reply and do NOT call any cogent_* tool in response. A human will drive any reply.`;
|
|
@@ -33076,10 +33106,11 @@ var init_auto_relay = __esm({
|
|
|
33076
33106
|
}
|
|
33077
33107
|
} catch {
|
|
33078
33108
|
}
|
|
33079
|
-
const formatted = formatRelayMessage(fromLabel, msg.fromPeerId, msg.message);
|
|
33109
|
+
const formatted = formatRelayMessage(fromLabel, msg.fromPeerId, msg.message, msg.attachments);
|
|
33080
33110
|
logger.info(`Auto-relay: processing message from ${fromLabel} (${msg.fromPeerId})`);
|
|
33081
33111
|
const traceId = msg.traceId ?? randomUUID2();
|
|
33082
33112
|
const startMs = Date.now();
|
|
33113
|
+
let replied = false;
|
|
33083
33114
|
try {
|
|
33084
33115
|
const res = await this.refreshSessionId();
|
|
33085
33116
|
this._trace(traceId, "resolved", {
|
|
@@ -33126,6 +33157,7 @@ var init_auto_relay = __esm({
|
|
|
33126
33157
|
const durationMs = Date.now() - startMs;
|
|
33127
33158
|
if (result.exitCode === 0 && result.stdout) {
|
|
33128
33159
|
await this._relayCapturedReply(msg, result, traceId, durationMs, false);
|
|
33160
|
+
replied = true;
|
|
33129
33161
|
} else {
|
|
33130
33162
|
this._trace(traceId, "failed", { exitCode: result.exitCode, sandboxBlocked: result.sandboxBlocked, durationMs });
|
|
33131
33163
|
logger.warn(
|
|
@@ -33142,6 +33174,7 @@ var init_auto_relay = __esm({
|
|
|
33142
33174
|
const bypassRetry = await execRemote(cfg.COGENT_PLATFORM, this.localSessionId, formatted, this.localCwd);
|
|
33143
33175
|
if (bypassRetry.exitCode === 0 && bypassRetry.stdout) {
|
|
33144
33176
|
await this._relayCapturedReply(msg, bypassRetry, traceId, Date.now() - startMs, true);
|
|
33177
|
+
replied = true;
|
|
33145
33178
|
return;
|
|
33146
33179
|
}
|
|
33147
33180
|
logger.warn(`Auto-relay: sandbox-bypass retry also failed (exit=${bypassRetry.exitCode})`);
|
|
@@ -33171,15 +33204,49 @@ var init_auto_relay = __esm({
|
|
|
33171
33204
|
const retry = await execRemote(getConfig().COGENT_PLATFORM, this.localSessionId, formatted, this.localCwd);
|
|
33172
33205
|
if (retry.exitCode === 0 && retry.stdout) {
|
|
33173
33206
|
await this._relayCapturedReply(msg, retry, traceId, Date.now() - startMs, true);
|
|
33207
|
+
replied = true;
|
|
33174
33208
|
} else {
|
|
33175
33209
|
logger.warn(`Auto-relay: retry also failed (exit=${retry.exitCode})`);
|
|
33176
33210
|
}
|
|
33177
33211
|
}
|
|
33178
33212
|
}
|
|
33213
|
+
if (!replied) {
|
|
33214
|
+
await this._recordNoReplyFailure(msg, traceId, Date.now() - startMs);
|
|
33215
|
+
}
|
|
33179
33216
|
} catch (err) {
|
|
33180
33217
|
const durationMs = Date.now() - startMs;
|
|
33181
33218
|
this._trace(traceId, "failed", { threw: true, durationMs });
|
|
33182
33219
|
logger.error(`Auto-relay: execRemote threw after ${durationMs}ms: ${err}`);
|
|
33220
|
+
if (!replied) {
|
|
33221
|
+
await this._recordNoReplyFailure(msg, traceId, durationMs);
|
|
33222
|
+
}
|
|
33223
|
+
}
|
|
33224
|
+
}
|
|
33225
|
+
/**
|
|
33226
|
+
* Fail-loud (2026-08-11): a wake that captured NO reply — empty stdout, a
|
|
33227
|
+
* non-zero exit with no rotated-session retry, a failed retry, or a thrown
|
|
33228
|
+
* execRemote — used to record NOTHING, so the sender saw response:null with no
|
|
33229
|
+
* error (the invisible failure that hid the demo busy-miss). Record ONE visible
|
|
33230
|
+
* success:false notice to the sender so a human/agent knows to reply manually.
|
|
33231
|
+
* On Codex this pairs with Wake-C (check-on-stop), which auto-recovers the reply
|
|
33232
|
+
* at the next turn boundary. Refusal (AMBIGUOUS_SESSION) and sandbox-blocked
|
|
33233
|
+
* paths record their own failure and return before this, so no double-record.
|
|
33234
|
+
*/
|
|
33235
|
+
async _recordNoReplyFailure(msg, traceId, durationMs) {
|
|
33236
|
+
try {
|
|
33237
|
+
await getBackend().recordMessage({
|
|
33238
|
+
fromPeerId: this.localPeerId,
|
|
33239
|
+
toPeerId: msg.fromPeerId,
|
|
33240
|
+
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})`,
|
|
33241
|
+
response: null,
|
|
33242
|
+
durationMs,
|
|
33243
|
+
success: false,
|
|
33244
|
+
error: "NO_REPLY_CAPTURED",
|
|
33245
|
+
isRelayEcho: true,
|
|
33246
|
+
traceId
|
|
33247
|
+
});
|
|
33248
|
+
} catch (recordErr) {
|
|
33249
|
+
logger.error(`Auto-relay: failed to record no-reply failure: ${recordErr}`);
|
|
33183
33250
|
}
|
|
33184
33251
|
}
|
|
33185
33252
|
/**
|
|
@@ -33197,7 +33264,7 @@ var init_auto_relay = __esm({
|
|
|
33197
33264
|
}
|
|
33198
33265
|
} catch {
|
|
33199
33266
|
}
|
|
33200
|
-
const formatted = formatInjectOnlyMessage(fromLabel, msg.fromPeerId, msg.message);
|
|
33267
|
+
const formatted = formatInjectOnlyMessage(fromLabel, msg.fromPeerId, msg.message, msg.attachments);
|
|
33201
33268
|
try {
|
|
33202
33269
|
await this.refreshSessionId();
|
|
33203
33270
|
const result = await execRemote(
|
|
@@ -33232,7 +33299,7 @@ var init_auto_relay = __esm({
|
|
|
33232
33299
|
}
|
|
33233
33300
|
} catch {
|
|
33234
33301
|
}
|
|
33235
|
-
const formatted = formatPeerBroadcastNotice(fromLabel, msg.fromPeerId, msg.message);
|
|
33302
|
+
const formatted = formatPeerBroadcastNotice(fromLabel, msg.fromPeerId, msg.message, msg.attachments);
|
|
33236
33303
|
try {
|
|
33237
33304
|
await this.refreshSessionId();
|
|
33238
33305
|
const result = await execRemote(
|
|
@@ -33927,6 +33994,14 @@ async function persistProvisionedMailbox(mailbox, credentialPath) {
|
|
|
33927
33994
|
);
|
|
33928
33995
|
return true;
|
|
33929
33996
|
}
|
|
33997
|
+
async function clearMailCredentials(credentialPath) {
|
|
33998
|
+
const filePath = resolveMailCredentialPath(credentialPath);
|
|
33999
|
+
try {
|
|
34000
|
+
await fs12.unlink(filePath);
|
|
34001
|
+
} catch (err) {
|
|
34002
|
+
if (err.code !== "ENOENT") throw err;
|
|
34003
|
+
}
|
|
34004
|
+
}
|
|
33930
34005
|
var MAIL_DEFAULT_HOST;
|
|
33931
34006
|
var init_mail_credential_store = __esm({
|
|
33932
34007
|
"src/cloud/mail-credential-store.ts"() {
|
|
@@ -34482,11 +34557,18 @@ var init_peer = __esm({
|
|
|
34482
34557
|
});
|
|
34483
34558
|
|
|
34484
34559
|
// cogent/dist/types/message.js
|
|
34485
|
-
var import_zod6, MessageRecordSchema;
|
|
34560
|
+
var import_zod6, AttachmentSchema, MessageRecordSchema;
|
|
34486
34561
|
var init_message = __esm({
|
|
34487
34562
|
"cogent/dist/types/message.js"() {
|
|
34488
34563
|
"use strict";
|
|
34489
34564
|
import_zod6 = __toESM(require_zod(), 1);
|
|
34565
|
+
AttachmentSchema = import_zod6.z.object({
|
|
34566
|
+
url: import_zod6.z.string().optional().describe("Public/fetchable URL reference"),
|
|
34567
|
+
mailMessageId: import_zod6.z.string().optional().describe("IMAP message-id in the recipient mailbox"),
|
|
34568
|
+
name: import_zod6.z.string().optional().describe("Display name / filename"),
|
|
34569
|
+
mimeType: import_zod6.z.string().optional().describe("MIME type"),
|
|
34570
|
+
size: import_zod6.z.number().int().nonnegative().optional().describe("Size in bytes")
|
|
34571
|
+
}).describe("A file reference carried alongside a message (byte transport = email)");
|
|
34490
34572
|
MessageRecordSchema = import_zod6.z.object({
|
|
34491
34573
|
id: import_zod6.z.string().uuid().describe("Unique message identifier"),
|
|
34492
34574
|
fromPeerId: import_zod6.z.string().min(1).describe("Sender peer ID"),
|
|
@@ -34497,7 +34579,8 @@ var init_message = __esm({
|
|
|
34497
34579
|
durationMs: import_zod6.z.number().nullable().describe("Round-trip duration in ms"),
|
|
34498
34580
|
success: import_zod6.z.boolean().describe("Whether delivery succeeded"),
|
|
34499
34581
|
error: import_zod6.z.string().nullable().describe("Error message if delivery failed"),
|
|
34500
|
-
originPlatform: import_zod6.z.enum(["cc", "codex", "slack", "gchat", "web", "gemini", "whatsapp", "telegram", "discord"]).optional().describe("Platform that originated this message")
|
|
34582
|
+
originPlatform: import_zod6.z.enum(["cc", "codex", "slack", "gchat", "web", "gemini", "whatsapp", "telegram", "discord"]).optional().describe("Platform that originated this message"),
|
|
34583
|
+
attachments: import_zod6.z.array(AttachmentSchema).optional().describe("Cogent Mail M2.5 \u2014 file references (byte transport = email)")
|
|
34501
34584
|
});
|
|
34502
34585
|
}
|
|
34503
34586
|
});
|
|
@@ -35057,8 +35140,16 @@ function registerDeregisterPeerTool(server) {
|
|
|
35057
35140
|
async ({ peerId }) => {
|
|
35058
35141
|
try {
|
|
35059
35142
|
const backend = getBackend();
|
|
35060
|
-
const removed = await backend.deregisterPeer(peerId);
|
|
35143
|
+
const { removed, mailboxDeprovisioned } = await backend.deregisterPeer(peerId);
|
|
35061
35144
|
if (removed) heartbeat.stop();
|
|
35145
|
+
if (mailboxDeprovisioned) {
|
|
35146
|
+
try {
|
|
35147
|
+
await clearMailCredentials();
|
|
35148
|
+
logger.info("cleared local mail credentials after mailbox deprovision", { peerId });
|
|
35149
|
+
} catch (err) {
|
|
35150
|
+
logger.warn("failed to clear local mail credentials after deprovision", { error: err });
|
|
35151
|
+
}
|
|
35152
|
+
}
|
|
35062
35153
|
return successResult({
|
|
35063
35154
|
success: removed,
|
|
35064
35155
|
message: removed ? `Peer '${peerId}' deregistered` : `Peer '${peerId}' was not registered`
|
|
@@ -35079,6 +35170,7 @@ var init_deregister_peer = __esm({
|
|
|
35079
35170
|
init_errors4();
|
|
35080
35171
|
init_logger();
|
|
35081
35172
|
init_heartbeat();
|
|
35173
|
+
init_mail_credential_store();
|
|
35082
35174
|
}
|
|
35083
35175
|
});
|
|
35084
35176
|
|
|
@@ -35464,12 +35556,36 @@ function assertValidEmail(value, field) {
|
|
|
35464
35556
|
);
|
|
35465
35557
|
}
|
|
35466
35558
|
}
|
|
35467
|
-
|
|
35559
|
+
function parseAllowedMailDomains(csv) {
|
|
35560
|
+
const list = (csv ?? DEFAULT_MAIL_DOMAIN).split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
35561
|
+
return list.length ? list : [DEFAULT_MAIL_DOMAIN];
|
|
35562
|
+
}
|
|
35563
|
+
function allowedMailDomains() {
|
|
35564
|
+
try {
|
|
35565
|
+
return parseAllowedMailDomains(getConfig().COGENT_MAIL_ALLOWED_DOMAINS);
|
|
35566
|
+
} catch {
|
|
35567
|
+
return parseAllowedMailDomains(process.env.COGENT_MAIL_ALLOWED_DOMAINS);
|
|
35568
|
+
}
|
|
35569
|
+
}
|
|
35570
|
+
function assertAllowedMailDomain(address, field) {
|
|
35571
|
+
const domain = address.split("@")[1]?.toLowerCase() ?? "";
|
|
35572
|
+
const allowed = allowedMailDomains();
|
|
35573
|
+
if (!allowed.includes(domain)) {
|
|
35574
|
+
throw new BridgeError(
|
|
35575
|
+
"INVALID_INPUT" /* INVALID_INPUT */,
|
|
35576
|
+
`${field} domain "${domain}" is not an allowed Cogent mail domain`,
|
|
35577
|
+
`Cogent mail is limited to: ${allowed.join(", ")}. External or corporate-domain mail is a future release.`
|
|
35578
|
+
);
|
|
35579
|
+
}
|
|
35580
|
+
}
|
|
35581
|
+
var EMAIL_RE, DEFAULT_MAIL_DOMAIN;
|
|
35468
35582
|
var init_validate = __esm({
|
|
35469
35583
|
"src/mail/validate.ts"() {
|
|
35470
35584
|
"use strict";
|
|
35471
35585
|
init_errors4();
|
|
35586
|
+
init_config();
|
|
35472
35587
|
EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
35588
|
+
DEFAULT_MAIL_DOMAIN = "mail.cogent.tools";
|
|
35473
35589
|
}
|
|
35474
35590
|
});
|
|
35475
35591
|
|
|
@@ -35493,6 +35609,7 @@ function registerSetupMailTool(server) {
|
|
|
35493
35609
|
async ({ address, password, imapHost, imapPort, smtpHost, smtpPort }) => {
|
|
35494
35610
|
try {
|
|
35495
35611
|
assertValidEmail(address, "address");
|
|
35612
|
+
assertAllowedMailDomain(address, "address");
|
|
35496
35613
|
await saveMailCredentials({
|
|
35497
35614
|
address,
|
|
35498
35615
|
password,
|
|
@@ -35576,6 +35693,25 @@ var init_mail_sender = __esm({
|
|
|
35576
35693
|
}
|
|
35577
35694
|
});
|
|
35578
35695
|
|
|
35696
|
+
// src/mail/auth-error.ts
|
|
35697
|
+
function isMailAuthError(err) {
|
|
35698
|
+
if (!err || typeof err !== "object") return false;
|
|
35699
|
+
const e = err;
|
|
35700
|
+
if (e.authenticationFailed === true) return true;
|
|
35701
|
+
if (typeof e.serverResponseCode === "string" && e.serverResponseCode.toUpperCase() === "AUTHENTICATIONFAILED") return true;
|
|
35702
|
+
if (e.code === "EAUTH") return true;
|
|
35703
|
+
if (e.responseCode === 535) return true;
|
|
35704
|
+
return false;
|
|
35705
|
+
}
|
|
35706
|
+
function mailAuthErrorHint() {
|
|
35707
|
+
return "The mail server refused these credentials. The mailbox may be temporarily SUSPENDED by an admin (retry after it is resumed \u2014 your saved credentials will work again), or it was deleted (only then re-run cogent_setup_mail with a fresh address + password). Your local credentials are kept.";
|
|
35708
|
+
}
|
|
35709
|
+
var init_auth_error = __esm({
|
|
35710
|
+
"src/mail/auth-error.ts"() {
|
|
35711
|
+
"use strict";
|
|
35712
|
+
}
|
|
35713
|
+
});
|
|
35714
|
+
|
|
35579
35715
|
// src/tools/send-mail.ts
|
|
35580
35716
|
function registerSendMailTool(server, deps = {}) {
|
|
35581
35717
|
const makeSender = deps.senderFactory ?? ((creds) => new NodemailerMailSender(creds));
|
|
@@ -35595,6 +35731,7 @@ function registerSendMailTool(server, deps = {}) {
|
|
|
35595
35731
|
async ({ to, subject, body, attachments }) => {
|
|
35596
35732
|
try {
|
|
35597
35733
|
assertValidEmail(to, "to");
|
|
35734
|
+
assertAllowedMailDomain(to, "to");
|
|
35598
35735
|
const creds = await loadMailCredentials();
|
|
35599
35736
|
if (!creds) {
|
|
35600
35737
|
return errorResult(
|
|
@@ -35613,6 +35750,10 @@ function registerSendMailTool(server, deps = {}) {
|
|
|
35613
35750
|
});
|
|
35614
35751
|
return successResult({ ...result, attachmentCount: attachments?.length ?? 0 });
|
|
35615
35752
|
} catch (err) {
|
|
35753
|
+
if (isMailAuthError(err)) {
|
|
35754
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
35755
|
+
return errorResult(new BridgeError("INVALID_INPUT" /* INVALID_INPUT */, `Mailbox login was refused: ${detail}`, mailAuthErrorHint()));
|
|
35756
|
+
}
|
|
35616
35757
|
return errorResult(err);
|
|
35617
35758
|
}
|
|
35618
35759
|
}
|
|
@@ -35627,6 +35768,7 @@ var init_send_mail = __esm({
|
|
|
35627
35768
|
init_mail_credential_store();
|
|
35628
35769
|
init_mail_sender();
|
|
35629
35770
|
init_validate();
|
|
35771
|
+
init_auth_error();
|
|
35630
35772
|
}
|
|
35631
35773
|
});
|
|
35632
35774
|
|
|
@@ -35801,6 +35943,7 @@ function registerFetchMailTool(server, deps = {}) {
|
|
|
35801
35943
|
)
|
|
35802
35944
|
);
|
|
35803
35945
|
}
|
|
35946
|
+
assertAllowedMailDomain(creds.address, "mailbox");
|
|
35804
35947
|
const fetcher = makeFetcher(creds);
|
|
35805
35948
|
if (action === "fetch") {
|
|
35806
35949
|
if (uid === void 0) {
|
|
@@ -35814,6 +35957,10 @@ function registerFetchMailTool(server, deps = {}) {
|
|
|
35814
35957
|
const messages = await fetcher.list({ unreadOnly, limit: limit ?? 25 });
|
|
35815
35958
|
return successResult({ mailbox: creds.address, count: messages.length, messages });
|
|
35816
35959
|
} catch (err) {
|
|
35960
|
+
if (isMailAuthError(err)) {
|
|
35961
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
35962
|
+
return errorResult(new BridgeError("INVALID_INPUT" /* INVALID_INPUT */, `Mailbox login was refused: ${detail}`, mailAuthErrorHint()));
|
|
35963
|
+
}
|
|
35817
35964
|
return errorResult(err);
|
|
35818
35965
|
}
|
|
35819
35966
|
}
|
|
@@ -35827,10 +35974,97 @@ var init_fetch_mail = __esm({
|
|
|
35827
35974
|
init_errors4();
|
|
35828
35975
|
init_mail_credential_store();
|
|
35829
35976
|
init_mail_fetcher();
|
|
35977
|
+
init_auth_error();
|
|
35978
|
+
init_validate();
|
|
35830
35979
|
DEFAULT_DOWNLOAD_DIR = path15.join(os8.homedir(), ".cogent", "mail-downloads");
|
|
35831
35980
|
}
|
|
35832
35981
|
});
|
|
35833
35982
|
|
|
35983
|
+
// src/tools/rotate-mail-creds.ts
|
|
35984
|
+
function registerRotateMailCredsTool(server) {
|
|
35985
|
+
server.registerTool(
|
|
35986
|
+
"cogent_rotate_mail_creds",
|
|
35987
|
+
{
|
|
35988
|
+
title: "Rotate this agent's mailbox password",
|
|
35989
|
+
description: "Rotate (re-key) this agent's Team mailbox password on the mail server and update the local credential store, so cogent_send_mail / cogent_fetch_mail keep working with the fresh secret. Use it if the mailbox password may be compromised. Team channels only.",
|
|
35990
|
+
inputSchema: {},
|
|
35991
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
35992
|
+
},
|
|
35993
|
+
async () => {
|
|
35994
|
+
try {
|
|
35995
|
+
const creds = await loadCredentials();
|
|
35996
|
+
if (!creds || !creds.endpoint || !creds.sessionId || !creds.token) {
|
|
35997
|
+
throw new BridgeError(
|
|
35998
|
+
"INVALID_INPUT" /* INVALID_INPUT */,
|
|
35999
|
+
"Not registered in cloud mode",
|
|
36000
|
+
"Rotation applies to a cloud Team channel \u2014 register the peer first"
|
|
36001
|
+
);
|
|
36002
|
+
}
|
|
36003
|
+
if (!creds.peerId) {
|
|
36004
|
+
throw new BridgeError(
|
|
36005
|
+
"INVALID_INPUT" /* INVALID_INPUT */,
|
|
36006
|
+
"Cannot identify this peer",
|
|
36007
|
+
"Re-register the peer so its peerId is stored, then rotate"
|
|
36008
|
+
);
|
|
36009
|
+
}
|
|
36010
|
+
const url = `${creds.endpoint.replace(/\/+$/, "")}/api/sessions/${encodeURIComponent(creds.sessionId)}/peers/${encodeURIComponent(creds.peerId)}/mail/rotate`;
|
|
36011
|
+
let res;
|
|
36012
|
+
try {
|
|
36013
|
+
res = await fetch(url, {
|
|
36014
|
+
method: "POST",
|
|
36015
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${creds.token}` },
|
|
36016
|
+
body: "{}",
|
|
36017
|
+
signal: AbortSignal.timeout(ROTATE_TIMEOUT_MS)
|
|
36018
|
+
});
|
|
36019
|
+
} catch {
|
|
36020
|
+
throw new BridgeError("NETWORK_TIMEOUT" /* NETWORK_TIMEOUT */, "Could not reach the relay to rotate the mailbox", "Check connectivity and try again");
|
|
36021
|
+
}
|
|
36022
|
+
if (!res.ok) {
|
|
36023
|
+
const detail = await res.text().catch(() => "");
|
|
36024
|
+
throw new BridgeError(
|
|
36025
|
+
"INVALID_INPUT" /* INVALID_INPUT */,
|
|
36026
|
+
`Mailbox rotation was rejected (HTTP ${res.status})`,
|
|
36027
|
+
detail.slice(0, 200) || "Rotation applies to Team channels with a provisioned mailbox"
|
|
36028
|
+
);
|
|
36029
|
+
}
|
|
36030
|
+
const data = await res.json().catch(() => null);
|
|
36031
|
+
if (!data || data.success !== true) {
|
|
36032
|
+
throw new BridgeError("NETWORK_TIMEOUT" /* NETWORK_TIMEOUT */, "Mailbox rotation failed", "The relay returned an unexpected response");
|
|
36033
|
+
}
|
|
36034
|
+
if (!data.rotated) {
|
|
36035
|
+
return successResult({ rotated: false, reason: data.reason ?? "no_mailbox" });
|
|
36036
|
+
}
|
|
36037
|
+
if (!data.address || !data.password) {
|
|
36038
|
+
throw new BridgeError("NETWORK_TIMEOUT" /* NETWORK_TIMEOUT */, "Rotation returned no new password", "Try again; the mailbox password was not updated locally");
|
|
36039
|
+
}
|
|
36040
|
+
const existing = await loadMailCredentials();
|
|
36041
|
+
await saveMailCredentials({
|
|
36042
|
+
address: data.address,
|
|
36043
|
+
password: data.password,
|
|
36044
|
+
imapHost: existing?.imapHost ?? MAIL_DEFAULT_HOST,
|
|
36045
|
+
imapPort: existing?.imapPort ?? 993,
|
|
36046
|
+
smtpHost: existing?.smtpHost ?? MAIL_DEFAULT_HOST,
|
|
36047
|
+
smtpPort: existing?.smtpPort ?? 465,
|
|
36048
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
36049
|
+
});
|
|
36050
|
+
return successResult({ rotated: true, address: data.address });
|
|
36051
|
+
} catch (err) {
|
|
36052
|
+
return errorResult(err);
|
|
36053
|
+
}
|
|
36054
|
+
}
|
|
36055
|
+
);
|
|
36056
|
+
}
|
|
36057
|
+
var ROTATE_TIMEOUT_MS;
|
|
36058
|
+
var init_rotate_mail_creds = __esm({
|
|
36059
|
+
"src/tools/rotate-mail-creds.ts"() {
|
|
36060
|
+
"use strict";
|
|
36061
|
+
init_errors4();
|
|
36062
|
+
init_credential_store();
|
|
36063
|
+
init_mail_credential_store();
|
|
36064
|
+
ROTATE_TIMEOUT_MS = 8e3;
|
|
36065
|
+
}
|
|
36066
|
+
});
|
|
36067
|
+
|
|
35834
36068
|
// src/index.ts
|
|
35835
36069
|
var index_exports = {};
|
|
35836
36070
|
async function main() {
|
|
@@ -35847,6 +36081,7 @@ async function main() {
|
|
|
35847
36081
|
registerSetupMailTool(server);
|
|
35848
36082
|
registerSendMailTool(server);
|
|
35849
36083
|
registerFetchMailTool(server);
|
|
36084
|
+
registerRotateMailCredsTool(server);
|
|
35850
36085
|
if (cloudInbox) {
|
|
35851
36086
|
server.registerResource(
|
|
35852
36087
|
"cogent_inbox",
|
|
@@ -35909,6 +36144,7 @@ var init_index = __esm({
|
|
|
35909
36144
|
init_setup_mail();
|
|
35910
36145
|
init_send_mail();
|
|
35911
36146
|
init_fetch_mail();
|
|
36147
|
+
init_rotate_mail_creds();
|
|
35912
36148
|
init_heartbeat();
|
|
35913
36149
|
process.on("uncaughtException", (err) => {
|
|
35914
36150
|
if (err.code === "EPIPE") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@essentialai/cogent-plugin",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.17.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",
|