@essentialai/cogent-bridge 3.14.1 → 3.16.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/CHANGELOG.md +36 -0
- package/dist/cloud/mail-credential-store.d.ts +46 -0
- package/dist/cloud/mail-credential-store.d.ts.map +1 -0
- package/dist/cloud/mail-credential-store.js +80 -0
- package/dist/cloud/mail-credential-store.js.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/mail/mail-fetcher.d.ts +112 -0
- package/dist/mail/mail-fetcher.d.ts.map +1 -0
- package/dist/mail/mail-fetcher.js +186 -0
- package/dist/mail/mail-fetcher.js.map +1 -0
- package/dist/mail/mail-sender.d.ts +64 -0
- package/dist/mail/mail-sender.d.ts.map +1 -0
- package/dist/mail/mail-sender.js +68 -0
- package/dist/mail/mail-sender.js.map +1 -0
- package/dist/mail/validate.d.ts +4 -0
- package/dist/mail/validate.d.ts.map +1 -0
- package/dist/mail/validate.js +16 -0
- package/dist/mail/validate.js.map +1 -0
- package/dist/services/auto-relay.d.ts +11 -0
- package/dist/services/auto-relay.d.ts.map +1 -1
- package/dist/services/auto-relay.js +45 -0
- package/dist/services/auto-relay.js.map +1 -1
- package/dist/tools/fetch-mail.d.ts +16 -0
- package/dist/tools/fetch-mail.d.ts.map +1 -0
- package/dist/tools/fetch-mail.js +55 -0
- package/dist/tools/fetch-mail.js.map +1 -0
- package/dist/tools/register-peer.d.ts.map +1 -1
- package/dist/tools/register-peer.js +13 -0
- package/dist/tools/register-peer.js.map +1 -1
- package/dist/tools/send-mail.d.ts +16 -0
- package/dist/tools/send-mail.d.ts.map +1 -0
- package/dist/tools/send-mail.js +49 -0
- package/dist/tools/send-mail.js.map +1 -0
- package/dist/tools/setup-mail.d.ts +10 -0
- package/dist/tools/setup-mail.d.ts.map +1 -0
- package/dist/tools/setup-mail.js +47 -0
- package/dist/tools/setup-mail.js.map +1 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -1
- package/server.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.16.0 — 2026-08-11
|
|
4
|
+
|
|
5
|
+
### Fixed — Codex agents silently missed messages while busy (Wake-C parity)
|
|
6
|
+
- **Codex Wake-C.** The `codex-plugin` now ships the check-on-stop **Stop hook** (the same
|
|
7
|
+
platform-agnostic `check-on-stop.mjs` the Claude plugin has). Codex 0.147 honors a Stop hook's
|
|
8
|
+
`{"decision":"block","reason"}` identically to Claude, so a message that arrives while a Codex
|
|
9
|
+
agent is **busy mid-turn** — which the real-time wake cannot interrupt — is now recovered at the
|
|
10
|
+
turn boundary. Before this, Codex had **no** busy-miss net (Claude has had Wake-C since 3.12.2);
|
|
11
|
+
a busy Codex agent dropped the message silently (the 2026-08-10 demo incident). The hook is
|
|
12
|
+
auto-synced from the canonical `plugin/hooks/` at build time so the two never drift.
|
|
13
|
+
- **Fail-loud auto-relay.** A wake that captured **no reply** — empty stdout, a non-zero exit with
|
|
14
|
+
no rotated-session retry, a failed retry, or a thrown `execRemote` — used to record *nothing*, so
|
|
15
|
+
the sender saw `response:null` with no error (an invisible failure). Every such path now surfaces
|
|
16
|
+
one visible `success:false` "manual response required" notice (`NO_REPLY_CAPTURED`) to the sender.
|
|
17
|
+
- **Regression coverage.** New `scripts/codex-wakec-test.mjs` proves, against the live relay, that
|
|
18
|
+
the shipped Codex hook primes silently, emits `decision:block` on a busy-miss, and respects the
|
|
19
|
+
`stop_hook_active` loop guard. The directed wake-matrix only ever tested *idle* recipients — this
|
|
20
|
+
closes the busy-recipient hole that let "ALL PASS" ship while the demo broke.
|
|
21
|
+
|
|
22
|
+
## 3.15.0 — 2026-08-08
|
|
23
|
+
|
|
24
|
+
### Added — Cogent Mail: per-agent mailboxes + auto-provision (M2)
|
|
25
|
+
- **New agent tools**: `cogent_setup_mail` (store the mailbox address+password locally, 0600),
|
|
26
|
+
`cogent_send_mail` (SMTP submission with attachments read from local paths), and
|
|
27
|
+
`cogent_fetch_mail` (on-demand IMAP list/fetch; saves attachments by basename). Every mailbox
|
|
28
|
+
is `[agent]-[channel]@mail.cogent.tools` on the standalone Stalwart box.
|
|
29
|
+
- **Zero-touch provisioning for Team agents**: on a cloud Team `cogent_register_peer`, the relay
|
|
30
|
+
mints (or idempotently resolves) the peer's mailbox via the control plane and returns
|
|
31
|
+
`{address, password}` in the register response; the bridge saves it to its per-cwd 0600 store —
|
|
32
|
+
no manual `cogent_setup_mail` needed. `PeerInfo.mailbox` (response-only; password never stored
|
|
33
|
+
server-side).
|
|
34
|
+
- **A2A card autofill**: each peer's Agent Card advertises its mailbox address via a
|
|
35
|
+
`capabilities.extensions[]` entry (address only; the password never touches the card).
|
|
36
|
+
- Input hardening: `cogent_send_mail`/`cogent_setup_mail` validate the address shape before the
|
|
37
|
+
SMTP/credential sink. `nodemailer`/`imapflow` are lazy-imported (off the cold-start path).
|
|
38
|
+
|
|
3
39
|
## 3.14.1 — 2026-08-07
|
|
4
40
|
|
|
5
41
|
### Added — `cogent init` asks Local vs Cloud (writes the config; no hand-edit)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-owned local mailbox credentials (Cogent Mail M2). The mailbox password is a
|
|
3
|
+
* SECRET — the control plane hands it to the bridge ONCE (on provisioning/rotation)
|
|
4
|
+
* and never stores it, so the ONLY durable copy lives here, in an owner-only file.
|
|
5
|
+
* `cogent_send_mail` / `cogent_fetch_mail` read it; `cogent_rotate_mail_creds` updates it.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors {@link ./credential-store} deliberately: a per-cwd file so project A's mailbox
|
|
8
|
+
* can't bleed into project B (the same isolation the cloud-credential store enforces).
|
|
9
|
+
*/
|
|
10
|
+
export interface PersistedMailCredentials {
|
|
11
|
+
/** Full mailbox address `[peer]-[channel]@mail.cogent.tools`. */
|
|
12
|
+
address: string;
|
|
13
|
+
/** Mailbox password (SECRET) — used for SMTP submission + IMAP fetch. */
|
|
14
|
+
password: string;
|
|
15
|
+
imapHost: string;
|
|
16
|
+
imapPort: number;
|
|
17
|
+
smtpHost: string;
|
|
18
|
+
smtpPort: number;
|
|
19
|
+
savedAt: string;
|
|
20
|
+
}
|
|
21
|
+
/** The Cogent Mail box hostname — default IMAP/SMTP host for a mailbox (M2). */
|
|
22
|
+
export declare const MAIL_DEFAULT_HOST = "mail.cogent.tools";
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the mailbox-credential path with the override hierarchy:
|
|
25
|
+
* 1. explicit `credentialPath` arg (tests, advanced users)
|
|
26
|
+
* 2. process.env.COGENT_MAIL_CREDENTIALS_FILE (operators with custom paths)
|
|
27
|
+
* 3. defaultMailCredentialPath(process.cwd()) (per-cwd, the default)
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveMailCredentialPath(credentialPath?: string): string;
|
|
30
|
+
/** Load persisted mailbox credentials. Returns null on any error (absent / corrupt). */
|
|
31
|
+
export declare function loadMailCredentials(credentialPath?: string): Promise<PersistedMailCredentials | null>;
|
|
32
|
+
/** Save (upsert) mailbox credentials as 0600 JSON; creates the owner-only parent dir. */
|
|
33
|
+
export declare function saveMailCredentials(creds: PersistedMailCredentials, credentialPath?: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Persist an auto-provisioned mailbox (Cogent Mail M2.7) with default hosts (IMAP 993 / SMTP 465
|
|
36
|
+
* on {@link MAIL_DEFAULT_HOST}). Returns true if it saved, false if there was nothing to save
|
|
37
|
+
* (no address, or no one-time password — e.g. a re-register that didn't re-issue the password).
|
|
38
|
+
* The caller uses this best-effort: a save failure must not break peer registration.
|
|
39
|
+
*/
|
|
40
|
+
export declare function persistProvisionedMailbox(mailbox: {
|
|
41
|
+
address?: string;
|
|
42
|
+
password?: string;
|
|
43
|
+
} | undefined, credentialPath?: string): Promise<boolean>;
|
|
44
|
+
/** Delete the mailbox-credential file. Idempotent — a missing file is not an error. */
|
|
45
|
+
export declare function clearMailCredentials(credentialPath?: string): Promise<void>;
|
|
46
|
+
//# sourceMappingURL=mail-credential-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail-credential-store.d.ts","sourceRoot":"","sources":["../../src/cloud/mail-credential-store.ts"],"names":[],"mappings":"AAKA;;;;;;;;GAQG;AACH,MAAM,WAAW,wBAAwB;IACvC,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,gFAAgF;AAChF,eAAO,MAAM,iBAAiB,sBAAsB,CAAC;AAYrD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,CAKzE;AAED,wFAAwF;AACxF,wBAAsB,mBAAmB,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC,CAO3G;AAED,yFAAyF;AACzF,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOjH;AAED;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EAC5D,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,OAAO,CAAC,CAelB;AAED,uFAAuF;AACvF,wBAAsB,oBAAoB,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOjF"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
/** The Cogent Mail box hostname — default IMAP/SMTP host for a mailbox (M2). */
|
|
6
|
+
export const MAIL_DEFAULT_HOST = "mail.cogent.tools";
|
|
7
|
+
/**
|
|
8
|
+
* Per-cwd default location:
|
|
9
|
+
* ~/.cogent/mail-credentials/<sha256(absoluteCwd)[..16]>.json
|
|
10
|
+
* Each working directory (= one agent identity = one mailbox) gets its own file.
|
|
11
|
+
*/
|
|
12
|
+
function defaultMailCredentialPath(cwd = process.cwd()) {
|
|
13
|
+
const hash = crypto.createHash("sha256").update(path.resolve(cwd)).digest("hex").slice(0, 16);
|
|
14
|
+
return path.join(os.homedir(), ".cogent", "mail-credentials", `${hash}.json`);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolve the mailbox-credential path with the override hierarchy:
|
|
18
|
+
* 1. explicit `credentialPath` arg (tests, advanced users)
|
|
19
|
+
* 2. process.env.COGENT_MAIL_CREDENTIALS_FILE (operators with custom paths)
|
|
20
|
+
* 3. defaultMailCredentialPath(process.cwd()) (per-cwd, the default)
|
|
21
|
+
*/
|
|
22
|
+
export function resolveMailCredentialPath(credentialPath) {
|
|
23
|
+
if (credentialPath)
|
|
24
|
+
return credentialPath;
|
|
25
|
+
const envOverride = process.env.COGENT_MAIL_CREDENTIALS_FILE;
|
|
26
|
+
if (envOverride)
|
|
27
|
+
return envOverride;
|
|
28
|
+
return defaultMailCredentialPath();
|
|
29
|
+
}
|
|
30
|
+
/** Load persisted mailbox credentials. Returns null on any error (absent / corrupt). */
|
|
31
|
+
export async function loadMailCredentials(credentialPath) {
|
|
32
|
+
const filePath = resolveMailCredentialPath(credentialPath);
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(await fs.readFile(filePath, "utf-8"));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Save (upsert) mailbox credentials as 0600 JSON; creates the owner-only parent dir. */
|
|
41
|
+
export async function saveMailCredentials(creds, credentialPath) {
|
|
42
|
+
const filePath = resolveMailCredentialPath(credentialPath);
|
|
43
|
+
// The password is a secret: owner-only dir + file. `mode` on mkdir/writeFile only
|
|
44
|
+
// applies on creation, so chmod afterward to also tighten a pre-existing file.
|
|
45
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
46
|
+
await fs.writeFile(filePath, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf-8", mode: 0o600 });
|
|
47
|
+
await fs.chmod(filePath, 0o600);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Persist an auto-provisioned mailbox (Cogent Mail M2.7) with default hosts (IMAP 993 / SMTP 465
|
|
51
|
+
* on {@link MAIL_DEFAULT_HOST}). Returns true if it saved, false if there was nothing to save
|
|
52
|
+
* (no address, or no one-time password — e.g. a re-register that didn't re-issue the password).
|
|
53
|
+
* The caller uses this best-effort: a save failure must not break peer registration.
|
|
54
|
+
*/
|
|
55
|
+
export async function persistProvisionedMailbox(mailbox, credentialPath) {
|
|
56
|
+
if (!mailbox?.address || !mailbox.password)
|
|
57
|
+
return false;
|
|
58
|
+
await saveMailCredentials({
|
|
59
|
+
address: mailbox.address,
|
|
60
|
+
password: mailbox.password,
|
|
61
|
+
imapHost: MAIL_DEFAULT_HOST,
|
|
62
|
+
imapPort: 993,
|
|
63
|
+
smtpHost: MAIL_DEFAULT_HOST,
|
|
64
|
+
smtpPort: 465,
|
|
65
|
+
savedAt: new Date().toISOString(),
|
|
66
|
+
}, credentialPath);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
/** Delete the mailbox-credential file. Idempotent — a missing file is not an error. */
|
|
70
|
+
export async function clearMailCredentials(credentialPath) {
|
|
71
|
+
const filePath = resolveMailCredentialPath(credentialPath);
|
|
72
|
+
try {
|
|
73
|
+
await fs.unlink(filePath);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
if (err.code !== "ENOENT")
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=mail-credential-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail-credential-store.js","sourceRoot":"","sources":["../../src/cloud/mail-credential-store.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,9 @@ import { registerDeregisterPeerTool } from "./tools/deregister-peer.js";
|
|
|
12
12
|
import { registerHealthCheckTool } from "./tools/health-check.js";
|
|
13
13
|
import { registerCreateSessionTool } from "./tools/create-session.js";
|
|
14
14
|
import { registerJoinSessionTool } from "./tools/join-session.js";
|
|
15
|
+
import { registerSetupMailTool } from "./tools/setup-mail.js";
|
|
16
|
+
import { registerSendMailTool } from "./tools/send-mail.js";
|
|
17
|
+
import { registerFetchMailTool } from "./tools/fetch-mail.js";
|
|
15
18
|
import { heartbeat } from "./services/heartbeat.js";
|
|
16
19
|
// ---------------------------------------------------------------------------
|
|
17
20
|
// Global error handlers -- registered FIRST before anything else
|
|
@@ -70,6 +73,9 @@ async function main() {
|
|
|
70
73
|
registerHealthCheckTool(server);
|
|
71
74
|
registerCreateSessionTool(server);
|
|
72
75
|
registerJoinSessionTool(server);
|
|
76
|
+
registerSetupMailTool(server);
|
|
77
|
+
registerSendMailTool(server);
|
|
78
|
+
registerFetchMailTool(server);
|
|
73
79
|
// Register MCP inbox resource if cloud mode is active
|
|
74
80
|
if (cloudInbox) {
|
|
75
81
|
server.registerResource("cogent_inbox", "cogent://inbox", {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { PersistedMailCredentials } from "../cloud/mail-credential-store.js";
|
|
2
|
+
/**
|
|
3
|
+
* Inbound-mail port (Cogent Mail M2.3). Symmetric to the outbound {@link MailSender}:
|
|
4
|
+
* the fetch tool depends on THIS interface, not on imapflow, so it is unit-testable with a
|
|
5
|
+
* fake IMAP session and the IMAP library is swappable + loaded lazily (off the cold-start
|
|
6
|
+
* path). Following RemoteDataSource / Dependency_Injection
|
|
7
|
+
* (typescript-master-vault/concepts/RemoteDataSource.md, Dependency_Injection.md).
|
|
8
|
+
*/
|
|
9
|
+
export interface MailSummary {
|
|
10
|
+
uid: number;
|
|
11
|
+
from: string;
|
|
12
|
+
subject: string;
|
|
13
|
+
date: string;
|
|
14
|
+
unread: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface SavedAttachment {
|
|
17
|
+
filename: string;
|
|
18
|
+
savedPath: string;
|
|
19
|
+
size: number;
|
|
20
|
+
}
|
|
21
|
+
export interface FetchedMail {
|
|
22
|
+
uid: number;
|
|
23
|
+
from: string;
|
|
24
|
+
subject: string;
|
|
25
|
+
date: string;
|
|
26
|
+
text: string;
|
|
27
|
+
attachments: SavedAttachment[];
|
|
28
|
+
}
|
|
29
|
+
export interface MailFetcher {
|
|
30
|
+
/** List messages (most recent first) as lightweight summaries — no bodies downloaded. */
|
|
31
|
+
list(opts?: {
|
|
32
|
+
unreadOnly?: boolean;
|
|
33
|
+
limit?: number;
|
|
34
|
+
}): Promise<MailSummary[]>;
|
|
35
|
+
/** Fetch one message by UID: text body + attachments saved to `downloadDir`. */
|
|
36
|
+
fetchOne(uid: number, downloadDir: string): Promise<FetchedMail>;
|
|
37
|
+
}
|
|
38
|
+
/** Raw envelope row as produced by the IMAP session (already parsed to plain data). */
|
|
39
|
+
export interface RawMessage {
|
|
40
|
+
uid: number;
|
|
41
|
+
from: string;
|
|
42
|
+
subject: string;
|
|
43
|
+
date: string;
|
|
44
|
+
unread: boolean;
|
|
45
|
+
}
|
|
46
|
+
/** Decoded message content as produced by the IMAP session (attachments in memory). */
|
|
47
|
+
export interface RawContent {
|
|
48
|
+
from: string;
|
|
49
|
+
subject: string;
|
|
50
|
+
date: string;
|
|
51
|
+
text: string;
|
|
52
|
+
attachments: {
|
|
53
|
+
filename: string;
|
|
54
|
+
content: Buffer;
|
|
55
|
+
}[];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The minimal IMAP surface the fetcher needs. Returning already-parsed plain data keeps the
|
|
59
|
+
* imapflow specifics behind {@link defaultImapSessionFactory}, so ImapflowMailFetcher's
|
|
60
|
+
* disk-saving + summary logic is unit-tested against a fake session.
|
|
61
|
+
*/
|
|
62
|
+
export interface ImapSession {
|
|
63
|
+
list(unreadOnly: boolean): Promise<RawMessage[]>;
|
|
64
|
+
fetch(uid: number): Promise<RawContent>;
|
|
65
|
+
close(): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
export type ImapSessionFactory = (creds: PersistedMailCredentials) => Promise<ImapSession>;
|
|
68
|
+
/** In-memory fetcher: a fixed inbox. The test double the tool drives. */
|
|
69
|
+
export declare class InMemoryMailFetcher implements MailFetcher {
|
|
70
|
+
private readonly inbox;
|
|
71
|
+
constructor(inbox?: (RawMessage & {
|
|
72
|
+
text: string;
|
|
73
|
+
attachments: {
|
|
74
|
+
filename: string;
|
|
75
|
+
content: Buffer;
|
|
76
|
+
}[];
|
|
77
|
+
})[]);
|
|
78
|
+
list(opts?: {
|
|
79
|
+
unreadOnly?: boolean;
|
|
80
|
+
limit?: number;
|
|
81
|
+
}): Promise<MailSummary[]>;
|
|
82
|
+
fetchOne(uid: number, downloadDir: string): Promise<FetchedMail>;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Real IMAP fetcher. The imapflow session is created LAZILY (dynamic import) so imapflow never
|
|
86
|
+
* loads on the cold-start path — only when the agent actually fetches mail. `sessionFactory` is
|
|
87
|
+
* the injection seam for tests (default connects a real imapflow session).
|
|
88
|
+
*/
|
|
89
|
+
export declare class ImapflowMailFetcher implements MailFetcher {
|
|
90
|
+
private readonly creds;
|
|
91
|
+
private readonly sessionFactory;
|
|
92
|
+
constructor(creds: PersistedMailCredentials, sessionFactory?: ImapSessionFactory);
|
|
93
|
+
list(opts?: {
|
|
94
|
+
unreadOnly?: boolean;
|
|
95
|
+
limit?: number;
|
|
96
|
+
}): Promise<MailSummary[]>;
|
|
97
|
+
fetchOne(uid: number, downloadDir: string): Promise<FetchedMail>;
|
|
98
|
+
}
|
|
99
|
+
/** A leaf MIME part reference extracted from an imapflow bodyStructure tree. */
|
|
100
|
+
export interface MimePartRef {
|
|
101
|
+
part: string;
|
|
102
|
+
type: string;
|
|
103
|
+
isText: boolean;
|
|
104
|
+
filename?: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Flatten an imapflow bodyStructure into its leaf parts. Attachment leaves carry a `filename`
|
|
108
|
+
* (from disposition or content-type params); text/plain leaves are marked `isText`. Multipart
|
|
109
|
+
* container nodes (those with `childNodes`) are recursed, not emitted.
|
|
110
|
+
*/
|
|
111
|
+
export declare function flattenBodyStructure(node: unknown, acc?: MimePartRef[]): MimePartRef[];
|
|
112
|
+
//# sourceMappingURL=mail-fetcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail-fetcher.d.ts","sourceRoot":"","sources":["../../src/mail/mail-fetcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/** In-memory fetcher: a fixed inbox. The test double the tool drives. */
|
|
4
|
+
export class InMemoryMailFetcher {
|
|
5
|
+
inbox;
|
|
6
|
+
constructor(inbox = []) {
|
|
7
|
+
this.inbox = inbox;
|
|
8
|
+
}
|
|
9
|
+
async list(opts = {}) {
|
|
10
|
+
let rows = this.inbox.map(({ uid, from, subject, date, unread }) => ({ uid, from, subject, date, unread }));
|
|
11
|
+
if (opts.unreadOnly)
|
|
12
|
+
rows = rows.filter((m) => m.unread);
|
|
13
|
+
if (opts.limit && opts.limit > 0)
|
|
14
|
+
rows = rows.slice(0, opts.limit);
|
|
15
|
+
return rows;
|
|
16
|
+
}
|
|
17
|
+
async fetchOne(uid, downloadDir) {
|
|
18
|
+
const msg = this.inbox.find((m) => m.uid === uid);
|
|
19
|
+
if (!msg)
|
|
20
|
+
throw new Error(`no message with uid ${uid}`);
|
|
21
|
+
const saved = await saveAttachments(msg.attachments, downloadDir);
|
|
22
|
+
return { uid, from: msg.from, subject: msg.subject, date: msg.date, text: msg.text, attachments: saved };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Real IMAP fetcher. The imapflow session is created LAZILY (dynamic import) so imapflow never
|
|
27
|
+
* loads on the cold-start path — only when the agent actually fetches mail. `sessionFactory` is
|
|
28
|
+
* the injection seam for tests (default connects a real imapflow session).
|
|
29
|
+
*/
|
|
30
|
+
export class ImapflowMailFetcher {
|
|
31
|
+
creds;
|
|
32
|
+
sessionFactory;
|
|
33
|
+
constructor(creds, sessionFactory = defaultImapSessionFactory) {
|
|
34
|
+
this.creds = creds;
|
|
35
|
+
this.sessionFactory = sessionFactory;
|
|
36
|
+
}
|
|
37
|
+
async list(opts = {}) {
|
|
38
|
+
const session = await this.sessionFactory(this.creds);
|
|
39
|
+
try {
|
|
40
|
+
let rows = await session.list(opts.unreadOnly ?? false);
|
|
41
|
+
if (opts.limit && opts.limit > 0)
|
|
42
|
+
rows = rows.slice(0, opts.limit);
|
|
43
|
+
return rows;
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
await session.close();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async fetchOne(uid, downloadDir) {
|
|
50
|
+
const session = await this.sessionFactory(this.creds);
|
|
51
|
+
try {
|
|
52
|
+
const c = await session.fetch(uid);
|
|
53
|
+
const saved = await saveAttachments(c.attachments, downloadDir);
|
|
54
|
+
return { uid, from: c.from, subject: c.subject, date: c.date, text: c.text, attachments: saved };
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
await session.close();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Write each in-memory attachment to `downloadDir` (created if needed), named by basename. */
|
|
62
|
+
async function saveAttachments(attachments, downloadDir) {
|
|
63
|
+
if (attachments.length === 0)
|
|
64
|
+
return [];
|
|
65
|
+
await fs.mkdir(downloadDir, { recursive: true });
|
|
66
|
+
const saved = [];
|
|
67
|
+
for (const a of attachments) {
|
|
68
|
+
const name = path.basename(a.filename) || "attachment";
|
|
69
|
+
const savedPath = path.join(downloadDir, name);
|
|
70
|
+
await fs.writeFile(savedPath, a.content);
|
|
71
|
+
saved.push({ filename: name, savedPath, size: a.content.length });
|
|
72
|
+
}
|
|
73
|
+
return saved;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Default imapflow session. Lazily imported via a variable specifier so tsc/esbuild never
|
|
77
|
+
* statically resolve or bundle imapflow — it loads from node_modules only when an agent fetches
|
|
78
|
+
* mail (off the cold-start path). The imapflow call shapes here are verified against the real
|
|
79
|
+
* mail box at the M2.8 live A→B E2E (the same "wiring proven at live E2E" seam used by
|
|
80
|
+
* NodemailerMailSender.defaultTransport); the disk-save + summary logic around it is unit-tested.
|
|
81
|
+
*/
|
|
82
|
+
async function defaultImapSessionFactory(creds) {
|
|
83
|
+
const specifier = "imapflow";
|
|
84
|
+
const { ImapFlow } = (await import(specifier));
|
|
85
|
+
const client = new ImapFlow({
|
|
86
|
+
host: creds.imapHost,
|
|
87
|
+
port: creds.imapPort,
|
|
88
|
+
secure: creds.imapPort === 993,
|
|
89
|
+
auth: { user: creds.address, pass: creds.password },
|
|
90
|
+
logger: false,
|
|
91
|
+
});
|
|
92
|
+
await client.connect();
|
|
93
|
+
await client.mailboxOpen("INBOX");
|
|
94
|
+
return {
|
|
95
|
+
async list(unreadOnly) {
|
|
96
|
+
const criteria = unreadOnly ? { seen: false } : { all: true };
|
|
97
|
+
const rows = [];
|
|
98
|
+
for await (const msg of client.fetch(criteria, { uid: true, envelope: true, flags: true })) {
|
|
99
|
+
const env = msg.envelope ?? {};
|
|
100
|
+
rows.push({
|
|
101
|
+
uid: msg.uid,
|
|
102
|
+
from: env.from?.[0]?.address ?? "",
|
|
103
|
+
subject: env.subject ?? "",
|
|
104
|
+
date: env.date instanceof Date ? env.date.toISOString() : String(env.date ?? ""),
|
|
105
|
+
unread: !hasFlag(msg.flags, "\\Seen"),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return rows.reverse(); // newest first
|
|
109
|
+
},
|
|
110
|
+
async fetch(uid) {
|
|
111
|
+
const msg = await client.fetchOne(uid, { uid: true, envelope: true, bodyStructure: true }, { uid: true });
|
|
112
|
+
if (!msg)
|
|
113
|
+
throw new Error(`no message with uid ${uid}`);
|
|
114
|
+
const parts = flattenBodyStructure(msg.bodyStructure);
|
|
115
|
+
let text = "";
|
|
116
|
+
const attachments = [];
|
|
117
|
+
for (const p of parts) {
|
|
118
|
+
if (p.filename) {
|
|
119
|
+
const dl = await client.download(uid, p.part, { uid: true });
|
|
120
|
+
attachments.push({ filename: p.filename, content: await streamToBuffer(dl.content) });
|
|
121
|
+
}
|
|
122
|
+
else if (p.isText && !text) {
|
|
123
|
+
const dl = await client.download(uid, p.part, { uid: true });
|
|
124
|
+
text = (await streamToBuffer(dl.content)).toString("utf-8");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const env = msg.envelope ?? {};
|
|
128
|
+
return {
|
|
129
|
+
from: env.from?.[0]?.address ?? "",
|
|
130
|
+
subject: env.subject ?? "",
|
|
131
|
+
date: env.date instanceof Date ? env.date.toISOString() : String(env.date ?? ""),
|
|
132
|
+
text,
|
|
133
|
+
attachments,
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
async close() {
|
|
137
|
+
try {
|
|
138
|
+
await client.logout();
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
/* best-effort close */
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Flatten an imapflow bodyStructure into its leaf parts. Attachment leaves carry a `filename`
|
|
148
|
+
* (from disposition or content-type params); text/plain leaves are marked `isText`. Multipart
|
|
149
|
+
* container nodes (those with `childNodes`) are recursed, not emitted.
|
|
150
|
+
*/
|
|
151
|
+
export function flattenBodyStructure(node, acc = []) {
|
|
152
|
+
if (!node || typeof node !== "object")
|
|
153
|
+
return acc;
|
|
154
|
+
const n = node;
|
|
155
|
+
if (Array.isArray(n.childNodes) && n.childNodes.length > 0) {
|
|
156
|
+
for (const child of n.childNodes)
|
|
157
|
+
flattenBodyStructure(child, acc);
|
|
158
|
+
return acc;
|
|
159
|
+
}
|
|
160
|
+
const type = (n.type ?? "").toLowerCase();
|
|
161
|
+
const filename = n.dispositionParameters?.filename ?? n.parameters?.name;
|
|
162
|
+
const isAttachment = (n.disposition ?? "").toLowerCase() === "attachment" || Boolean(filename);
|
|
163
|
+
acc.push({
|
|
164
|
+
part: n.part ?? "1", // single-part messages have no `part`; imapflow addresses them as "1"
|
|
165
|
+
type,
|
|
166
|
+
isText: type === "text/plain" && !isAttachment,
|
|
167
|
+
filename: isAttachment ? filename : undefined,
|
|
168
|
+
});
|
|
169
|
+
return acc;
|
|
170
|
+
}
|
|
171
|
+
function hasFlag(flags, flag) {
|
|
172
|
+
if (flags instanceof Set)
|
|
173
|
+
return flags.has(flag);
|
|
174
|
+
if (Array.isArray(flags))
|
|
175
|
+
return flags.includes(flag);
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
async function streamToBuffer(stream) {
|
|
179
|
+
const chunks = [];
|
|
180
|
+
if (!stream)
|
|
181
|
+
return Buffer.alloc(0);
|
|
182
|
+
for await (const chunk of stream)
|
|
183
|
+
chunks.push(Buffer.from(chunk));
|
|
184
|
+
return Buffer.concat(chunks);
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=mail-fetcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail-fetcher.js","sourceRoot":"","sources":["../../src/mail/mail-fetcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { PersistedMailCredentials } from "../cloud/mail-credential-store.js";
|
|
2
|
+
/**
|
|
3
|
+
* Outbound-mail port (Cogent Mail M2.2). Following RemoteDataSource / Dependency_Injection
|
|
4
|
+
* (typescript-master-vault/concepts/RemoteDataSource.md, Dependency_Injection.md): the send
|
|
5
|
+
* tool depends on THIS interface, not on nodemailer, so it is unit-testable with an in-memory
|
|
6
|
+
* double and the SMTP library is swappable + loaded lazily (off the cold-start path).
|
|
7
|
+
*/
|
|
8
|
+
export interface OutgoingMail {
|
|
9
|
+
from: string;
|
|
10
|
+
to: string[];
|
|
11
|
+
subject: string;
|
|
12
|
+
text: string;
|
|
13
|
+
attachments: {
|
|
14
|
+
filename: string;
|
|
15
|
+
content: Buffer;
|
|
16
|
+
}[];
|
|
17
|
+
}
|
|
18
|
+
export interface MailSender {
|
|
19
|
+
send(mail: OutgoingMail): Promise<{
|
|
20
|
+
messageId: string;
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
23
|
+
/** In-memory sender: records what was sent. The test double the tool/service drives. */
|
|
24
|
+
export declare class InMemoryMailSender implements MailSender {
|
|
25
|
+
readonly sent: OutgoingMail[];
|
|
26
|
+
send(mail: OutgoingMail): Promise<{
|
|
27
|
+
messageId: string;
|
|
28
|
+
}>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Real SMTP sender. nodemailer is imported LAZILY (dynamic import) so it never loads on
|
|
32
|
+
* the bridge's cold-start path — only when the agent actually sends mail. `transportFactory`
|
|
33
|
+
* is an injection seam for tests (default builds a real nodemailer SMTP transport from the creds).
|
|
34
|
+
*/
|
|
35
|
+
export declare class NodemailerMailSender implements MailSender {
|
|
36
|
+
private readonly creds;
|
|
37
|
+
private readonly transportFactory?;
|
|
38
|
+
constructor(creds: PersistedMailCredentials, transportFactory?: (() => Promise<{
|
|
39
|
+
sendMail(opts: unknown): Promise<{
|
|
40
|
+
messageId?: string;
|
|
41
|
+
}>;
|
|
42
|
+
}>) | undefined);
|
|
43
|
+
send(mail: OutgoingMail): Promise<{
|
|
44
|
+
messageId: string;
|
|
45
|
+
}>;
|
|
46
|
+
private defaultTransport;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Compose and send a mail from the agent's mailbox: reads each attachment from its local path
|
|
50
|
+
* (streaming the bytes into a Buffer), builds the {@link OutgoingMail}, and sends via the port.
|
|
51
|
+
* A missing/unreadable attachment path fails loud (the caller surfaces it as errorResult) rather
|
|
52
|
+
* than silently dropping the file. Returns the message id + the sending address.
|
|
53
|
+
*/
|
|
54
|
+
export declare function buildAndSendMail(sender: MailSender, creds: PersistedMailCredentials, params: {
|
|
55
|
+
to: string[];
|
|
56
|
+
subject: string;
|
|
57
|
+
text: string;
|
|
58
|
+
attachmentPaths?: string[];
|
|
59
|
+
}): Promise<{
|
|
60
|
+
messageId: string;
|
|
61
|
+
from: string;
|
|
62
|
+
to: string[];
|
|
63
|
+
}>;
|
|
64
|
+
//# sourceMappingURL=mail-sender.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail-sender.d.ts","sourceRoot":"","sources":["../../src/mail/mail-sender.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAElF;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACtD;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1D;AAED,wFAAwF;AACxF,qBAAa,kBAAmB,YAAW,UAAU;IACnD,QAAQ,CAAC,IAAI,EAAE,YAAY,EAAE,CAAM;IAC7B,IAAI,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CAI/D;AAED;;;;GAIG;AACH,qBAAa,oBAAqB,YAAW,UAAU;IAEnD,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC;gBADjB,KAAK,EAAE,wBAAwB,EAC/B,gBAAgB,CAAC,GAAE,MAAM,OAAO,CAAC;QAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;YAAE,SAAS,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,aAAA;IAG3G,IAAI,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;YAYhD,gBAAgB;CAY/B;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,wBAAwB,EAC/B,MAAM,EAAE;IAAE,EAAE,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAClF,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAc5D"}
|