UNPKG

@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.

110 lines • 5.47 kB
import crypto from "node:crypto"; import fs from "node:fs/promises"; import path from "node:path"; import os from "node:os"; /** * Agent-owned local mailbox credentials (Cogent Mail M2). The mailbox password is a * SECRET — the control plane hands it to the bridge ONCE (on provisioning/rotation) * and never stores it, so the ONLY durable copy lives here, in an owner-only file. * `cogent_send_mail` / `cogent_fetch_mail` read it; `cogent_rotate_mail_creds` updates it. * * Mirrors {@link ./credential-store} deliberately: a per-cwd file so project A's mailbox * can't bleed into project B (the same isolation the cloud-credential store enforces). */ /** * The single source of truth for "this agent has no mailbox". * * 🔴 IT USED TO NAME AN IMPOSSIBLE ACTION (fixed 2026-09-04). Both mail tools independently said * "Run cogent_setup_mail with the address + password from the admin Mail panel first". That panel * is a TEAM-ONLY surface: free channels never provision a mailbox, and the free relay has no * CONTROL_PLANE_URL, so no mailbox can ever exist there. A real agent escalated to its operator * for a password that is never issued to anyone — and the operator could not supply it either. * * Deliberately does NOT assert which tier the caller is on: `PersistedCredentials` carries no * orgId, so the bridge genuinely cannot tell, and a confident wrong answer is worse than an honest * either/or. Mail is Team-only by product decision (operator, 2026-09-04) — this fixes the wording, * not the tier. * * Shared by send-mail and fetch-mail so the two can never drift apart again. */ export const MAIL_NOT_CONFIGURED_HINT = "Cogent Mail requires a Team channel, where each agent's mailbox is provisioned AUTOMATICALLY " + "when it registers — there is nothing to set up by hand and no password for anyone to hand over. " + "If this IS a Team channel, restart the agent so it re-registers and retries provisioning (the " + "relay logs the reason if it fails). Free channels do not have mailboxes. cogent_setup_mail " + "remains available for a self-hosted or manually provisioned mailbox."; /** The Cogent Mail box hostname — default IMAP/SMTP host for a mailbox (M2). */ export const MAIL_DEFAULT_HOST = "mail.cogent.tools"; /** * Per-cwd default location: * ~/.cogent/mail-credentials/<sha256(absoluteCwd)[..16]>.json * Each working directory (= one agent identity = one mailbox) gets its own file. */ function defaultMailCredentialPath(cwd = process.cwd()) { const hash = crypto.createHash("sha256").update(path.resolve(cwd)).digest("hex").slice(0, 16); return path.join(os.homedir(), ".cogent", "mail-credentials", `${hash}.json`); } /** * Resolve the mailbox-credential path with the override hierarchy: * 1. explicit `credentialPath` arg (tests, advanced users) * 2. process.env.COGENT_MAIL_CREDENTIALS_FILE (operators with custom paths) * 3. defaultMailCredentialPath(process.cwd()) (per-cwd, the default) */ export function resolveMailCredentialPath(credentialPath) { if (credentialPath) return credentialPath; const envOverride = process.env.COGENT_MAIL_CREDENTIALS_FILE; if (envOverride) return envOverride; return defaultMailCredentialPath(); } /** Load persisted mailbox credentials. Returns null on any error (absent / corrupt). */ export async function loadMailCredentials(credentialPath) { const filePath = resolveMailCredentialPath(credentialPath); try { return JSON.parse(await fs.readFile(filePath, "utf-8")); } catch { return null; } } /** Save (upsert) mailbox credentials as 0600 JSON; creates the owner-only parent dir. */ export async function saveMailCredentials(creds, credentialPath) { const filePath = resolveMailCredentialPath(credentialPath); // The password is a secret: owner-only dir + file. `mode` on mkdir/writeFile only // applies on creation, so chmod afterward to also tighten a pre-existing file. await fs.mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 }); await fs.writeFile(filePath, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf-8", mode: 0o600 }); await fs.chmod(filePath, 0o600); } /** * Persist an auto-provisioned mailbox (Cogent Mail M2.7) with default hosts (IMAP 993 / SMTP 465 * on {@link MAIL_DEFAULT_HOST}). Returns true if it saved, false if there was nothing to save * (no address, or no one-time password — e.g. a re-register that didn't re-issue the password). * The caller uses this best-effort: a save failure must not break peer registration. */ export async function persistProvisionedMailbox(mailbox, credentialPath) { if (!mailbox?.address || !mailbox.password) return false; await saveMailCredentials({ address: mailbox.address, password: mailbox.password, imapHost: MAIL_DEFAULT_HOST, imapPort: 993, smtpHost: MAIL_DEFAULT_HOST, smtpPort: 465, savedAt: new Date().toISOString(), }, credentialPath); return true; } /** Delete the mailbox-credential file. Idempotent — a missing file is not an error. */ export async function clearMailCredentials(credentialPath) { const filePath = resolveMailCredentialPath(credentialPath); try { await fs.unlink(filePath); } catch (err) { if (err.code !== "ENOENT") throw err; } } //# sourceMappingURL=mail-credential-store.js.map