@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.
59 lines • 3.63 kB
JavaScript
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 { NodemailerMailSender, buildAndSendMail } from "../mail/mail-sender.js";
import { assertValidEmail, assertAllowedMailDomain } from "../mail/validate.js";
import { isMailAuthError, mailAuthErrorHint } from "../mail/auth-error.js";
/**
* cogent_send_mail — send an email from this agent's mailbox (Cogent Mail M2.2). Reads the
* agent's own creds from the local store, composes the message with optional attachments read
* from local file paths, and submits over SMTP (as the agent — no admin). The heavy SMTP lib
* loads lazily only when this runs. Thin glue over buildAndSendMail (unit-tested in mail-sender.test).
* `deps.senderFactory` lets tests drive an in-memory sender instead of real SMTP.
*/
export function registerSendMailTool(server, deps = {}) {
const makeSender = deps.senderFactory ?? ((creds) => new NodemailerMailSender(creds));
server.registerTool("cogent_send_mail", {
title: "Send an email from this agent's mailbox",
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.",
inputSchema: {
to: z.string().describe("Recipient email address, e.g. other-agent-backend@mail.cogent.tools"),
subject: z.string().describe("Email subject"),
body: z.string().describe("Plain-text email body"),
attachments: z
.array(z.string())
.optional()
.describe("Optional local file paths to attach (each is read and attached by basename)"),
},
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
}, async ({ to, subject, body, attachments }) => {
try {
assertValidEmail(to, "to"); // fail fast at the tool boundary, before the SMTP sink
assertAllowedMailDomain(to, "to"); // F4: org-only mail — no sending to external domains
const creds = await loadMailCredentials();
if (!creds) {
return errorResult(new BridgeError(BridgeErrorCode.INVALID_INPUT, "No mailbox is configured for this agent", MAIL_NOT_CONFIGURED_HINT));
}
const result = await buildAndSendMail(makeSender(creds), creds, {
to: [to],
subject,
text: body,
attachmentPaths: attachments,
});
return successResult({ ...result, attachmentCount: attachments?.length ?? 0 });
}
catch (err) {
// Cogent Mail F3: on an SMTP 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=send-mail.js.map