@essentialai/cogent-bridge
Version:
Cogent Bridge — cross-agent comms for Claude Code, OpenAI Codex and Slack. Codex users: install with `curl -fsSL https://cogent.tools/install.sh | sh`, then start with `cogent-codex` for real-time peer wake.
68 lines • 4.42 kB
JavaScript
import os from "node:os";
import path from "node:path";
import { z } from "zod";
import { BridgeError, BridgeErrorCode, successResult, errorResult } from "../errors.js";
import { loadMailCredentials, MAIL_NOT_CONFIGURED_HINT } from "../cloud/mail-credential-store.js";
import { ImapflowMailFetcher } from "../mail/mail-fetcher.js";
import { isMailAuthError, mailAuthErrorHint } from "../mail/auth-error.js";
import { assertAllowedMailDomain } from "../mail/validate.js";
/** Where fetched attachments land by default (per-agent download dir). */
const DEFAULT_DOWNLOAD_DIR = path.join(os.homedir(), ".cogent", "mail-downloads");
/**
* cogent_fetch_mail — read this agent's own mailbox (Cogent Mail M2.3). Two actions:
* - "list" (default): lightweight envelope summaries (uid/from/subject/date/unread), no bodies.
* - "fetch": download one message by uid — text body + attachments saved to a local dir.
* On-demand only (no background polling — Hybrid C). imapflow loads lazily off the cold path.
* `deps.fetcherFactory` lets tests drive a fake IMAP inbox instead of a real server.
*/
export function registerFetchMailTool(server, deps = {}) {
const makeFetcher = deps.fetcherFactory ?? ((creds) => new ImapflowMailFetcher(creds));
server.registerTool("cogent_fetch_mail", {
title: "Read this agent's mailbox",
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.",
inputSchema: {
action: z.enum(["list", "fetch"]).optional().describe("'list' (default) or 'fetch' one message"),
uid: z.number().int().positive().optional().describe("Message uid to fetch (required when action='fetch')"),
unreadOnly: z.boolean().optional().describe("List only unread messages (default false)"),
limit: z.number().int().positive().optional().describe("Max messages to list (default 25)"),
downloadDir: z
.string()
.optional()
.describe(`Directory to save fetched attachments (default ${DEFAULT_DOWNLOAD_DIR})`),
},
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
}, async ({ action, uid, unreadOnly, limit, downloadDir }) => {
try {
const creds = await loadMailCredentials();
if (!creds) {
return errorResult(new BridgeError(BridgeErrorCode.INVALID_INPUT, "No mailbox is configured for this agent", MAIL_NOT_CONFIGURED_HINT));
}
// F4: org-only mail — refuse to connect to a mailbox on a non-allowed domain (guards creds
// that predate the allowlist, e.g. an externally-configured box).
assertAllowedMailDomain(creds.address, "mailbox");
const fetcher = makeFetcher(creds);
if (action === "fetch") {
if (uid === undefined) {
return errorResult(new BridgeError(BridgeErrorCode.INVALID_INPUT, "uid is required when action='fetch'", "Pass the uid from a cogent_fetch_mail list"));
}
const message = await fetcher.fetchOne(uid, downloadDir ?? DEFAULT_DOWNLOAD_DIR);
return successResult({ mailbox: creds.address, message });
}
const messages = await fetcher.list({ unreadOnly, limit: limit ?? 25 });
return successResult({ mailbox: creds.address, count: messages.length, messages });
}
catch (err) {
// Cogent Mail F3: on an IMAP AUTH failure, do NOT clear the local creds — a temporary admin
// SUSPEND is wire-indistinguishable from a reap-delete, and wiping still-valid creds would
// permanently lock the agent out after the box is resumed. A genuine reap already clears creds
// via the deregister deprovision-header path; here we just surface an actionable hint.
if (isMailAuthError(err)) {
const detail = err instanceof Error ? err.message : String(err);
return errorResult(new BridgeError(BridgeErrorCode.INVALID_INPUT, `Mailbox login was refused: ${detail}`, mailAuthErrorHint()));
}
return errorResult(err);
}
});
}
//# sourceMappingURL=fetch-mail.js.map