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