@alfe.ai/openclaw-email 0.0.0-stage → 0.1.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/README.md +13 -2
- package/dist/index.cjs +6 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/plugin.cjs +8 -0
- package/dist/plugin.d.cts +196 -0
- package/dist/plugin.d.cts.map +1 -0
- package/dist/plugin.d.ts +196 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +2 -0
- package/dist/plugin2.cjs +1795 -0
- package/dist/plugin2.d.cts +2 -0
- package/dist/plugin2.d.ts +2 -0
- package/dist/plugin2.js +1780 -0
- package/dist/plugin2.js.map +1 -0
- package/openclaw.plugin.json +32 -0
- package/package.json +64 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin2.js","names":["ACCOUNT_ID","errorMessage"],"sources":["../src/hash.ts","../src/channel.ts","../src/credentials.ts","../src/errors.ts","../src/host-version.ts","../src/admission.ts","../src/state.ts","../src/outbound.ts","../src/sender-context.ts","../src/sender-keeper.ts","../src/pipeline.ts","../src/poller.ts","../src/runtime-config.ts","../src/status.ts","../src/plugin.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\n\n/**\n * Session and target identifiers derived from mailbox and thread ids.\n *\n * OpenClaw lowercases peer ids, and Graph conversation ids are\n * case-sensitive, so ids are hashed to lowercase hex rather than embedded.\n */\n\nexport function shortHash(value: string): string {\n return createHash('sha256').update(value, 'utf8').digest('hex').slice(0, 16);\n}\n\n/** Mailbox addresses are case-insensitive. */\nexport function mailboxHash(address: string): string {\n return shortHash(address.trim().toLowerCase());\n}\n\n/** Provider thread ids are case-sensitive. */\nexport function threadHash(threadId: string): string {\n return shortHash(threadId);\n}\n\n/** One OpenClaw session per email thread (`session.dmScope: per-channel-peer`). */\nexport function peerIdFor(mailboxAddress: string, threadId: string): string {\n return `email:${mailboxHash(mailboxAddress)}:${threadHash(threadId)}`;\n}\n\n/** Outbound target for a thread. `sendText` gets no session, so the thread rides in `to`. */\nexport function threadTarget(hash: string): string {\n return `thread:${hash}`;\n}\n\nconst THREAD_TARGET = /^thread:([0-9a-f]{16})$/;\n\nexport function parseThreadTarget(to: string | undefined): string | null {\n const match = THREAD_TARGET.exec(to?.trim() ?? '');\n return match?.[1] ?? null;\n}\n\n/** `a***@example.com`: enough to correlate logs without logging the address. */\nexport function maskAddress(address: string): string {\n const at = address.lastIndexOf('@');\n if (at <= 0) return '***';\n return `${address.slice(0, 1)}***${address.slice(at)}`;\n}\n","/**\n * The registered OpenClaw `email` channel.\n *\n * Turn replies go through dispatch's `deliver` callback; this channel carries\n * sends made OUTSIDE a turn (the `message` tool, cron, follow-ups). 9.5's\n * outbound `sendText` context has no session or peer, so the thread is\n * encoded in the target: `thread:<threadHash>`, the same value dispatch sets\n * as the session's `OriginatingTo`.\n */\nimport { parseThreadTarget } from './hash.js';\nimport type { SendOutcome } from './outbound.js';\nimport type { ChannelStatusSnapshot } from './status.js';\n\nexport const CHANNEL_ID = 'email';\nconst ACCOUNT_ID = 'default';\n\n/** What the channel needs from the running plugin. */\nexport interface EmailChannelHandle {\n status(): ChannelStatusSnapshot;\n /** True when this mailbox has a conversation for the thread hash. */\n hasThread(hash: string): boolean;\n sendToThread(to: string, text: string): Promise<SendOutcome>;\n}\n\ninterface OutboundDeliveryResult {\n channel: typeof CHANNEL_ID;\n messageId: string;\n target: { kind: 'conversation'; id: string };\n timestamp: number;\n}\n\nexport function createEmailChannelPlugin(getHandle: () => EmailChannelHandle | null) {\n return {\n id: CHANNEL_ID,\n\n meta: {\n id: CHANNEL_ID,\n label: 'Email',\n selectionLabel: 'Email (Gmail & Outlook)',\n detailLabel: 'Email',\n docsPath: '/channels/email',\n docsLabel: 'email',\n blurb: \"Email threads in the agent's connected Gmail or Outlook mailbox.\",\n systemImage: 'envelope',\n order: 110,\n forceAccountBinding: false,\n showConfigured: true,\n },\n\n capabilities: {\n chatTypes: ['direct' as const],\n reactions: false,\n edit: false,\n unsend: false,\n reply: true,\n effects: false,\n groupManagement: false,\n threads: false,\n media: false,\n nativeCommands: false,\n polls: false,\n },\n\n config: {\n listAccountIds(): string[] {\n return [ACCOUNT_ID];\n },\n resolveAccount(_cfg: unknown, accountId?: string | null): { accountId: string; enabled: boolean } {\n return { accountId: accountId ?? ACCOUNT_ID, enabled: true };\n },\n defaultAccountId(): string {\n return ACCOUNT_ID;\n },\n isEnabled(): boolean {\n return true;\n },\n isConfigured(): boolean {\n return getHandle() !== null;\n },\n describeAccount(account: { accountId: string }) {\n const status = getHandle()?.status();\n return {\n accountId: account.accountId,\n enabled: true,\n configured: status !== undefined,\n running: status?.state === 'ok',\n statusState: status?.state ?? 'stopped',\n ...(status?.reason ? { stateReason: status.reason } : {}),\n lastError: status?.state === 'error' ? (status.message ?? null) : null,\n lastMessageAt: status?.lastMessageAt ?? null,\n };\n },\n },\n\n outbound: {\n deliveryMode: 'direct' as const,\n // One email per send: OpenClaw must not chunk a message into several emails.\n textChunkLimit: 100_000,\n\n resolveTarget(params: { to?: string }): { ok: true; to: string } | { ok: false; error: Error } {\n const hash = parseThreadTarget(params.to);\n if (!hash) return { ok: false, error: new Error('Invalid email target: use thread:<threadHash>') };\n const handle = getHandle();\n if (handle && !handle.hasThread(hash)) {\n return { ok: false, error: new Error('Unknown email thread for this mailbox') };\n }\n return { ok: true, to: `thread:${hash}` };\n },\n\n async sendText(ctx: { to: string; text: string }): Promise<OutboundDeliveryResult> {\n const handle = getHandle();\n if (!handle) throw new Error('Email channel is not running; outbound email is disabled');\n const outcome = await handle.sendToThread(ctx.to, ctx.text);\n if (outcome.kind !== 'sent') throw new Error(`Email not sent (${outcome.kind})`);\n return {\n channel: CHANNEL_ID,\n messageId: outcome.messageId,\n target: { kind: 'conversation', id: ctx.to },\n timestamp: Date.now(),\n };\n },\n },\n };\n}\n","/**\n * Mailbox access tokens from Connect. No refresh token lives on the VM.\n *\n * `getConnectionCredentials(connectionId)` returns the Connection's STORED\n * access token: it identifies the provider and mailbox but does not refresh.\n * Gmail and Graph tokens expire after about an hour, so once that token is\n * expired (or rejected) the plugin asks Connect's per-account refresh route,\n * which exchanges the refresh token server-side and persists the result. The\n * refresh is pinned to this channel's `connectionId` (two Connections can share\n * a mailbox address; the route answers 409 for the wrong one):\n * - Microsoft: `refreshMicrosoftAccountToken(accountIdentifier, { connectionId })`.\n * - Google: `refreshSocialAccount(\"google\", accountIdentifier, { connectionId })`, the\n * provider-generic client method for the same\n * `POST /agent/connect/{provider}/accounts/{accountIdentifier}/refresh`\n * route (the client has no Google-named wrapper).\n * An email Channel's parent Connection is an API-role Connection whose\n * accountIdentifier is the mailbox address (Connect enforces both), which is\n * exactly what that route resolves.\n *\n * Drivers call the token provider once, then again only after a 401, so any\n * call after the first hand-out means \"the last token was rejected\": refresh.\n */\nimport type { AccessTokenProvider } from \"@alfe.ai/email\";\n\nexport interface CredentialsClient {\n getConnectionCredentials(connectionId: string): Promise<{\n provider: string;\n accountIdentifier?: string;\n accessToken?: string;\n [key: string]: unknown;\n }>;\n refreshMicrosoftAccountToken(\n accountIdentifier: string,\n options?: { connectionId?: string },\n ): Promise<{ accessToken: string; expiresAt: string }>;\n refreshSocialAccount(\n provider: string,\n accountIdentifier: string,\n options?: { connectionId?: string },\n ): Promise<{ accessToken: string; expiresAt: string }>;\n}\n\nexport type MailboxProvider = \"google\" | \"microsoft\";\n\n/**\n * - `reconnect`: the Connection cannot serve this mailbox until the user\n * reconnects it.\n * - `agent-auth`: the Agent API rejected THIS AGENT's own token (401). That is\n * an agent credential problem, not a mailbox one; reconnecting the mailbox\n * would not fix it.\n */\nexport class CredentialError extends Error {\n constructor(message: string, readonly reason: \"reconnect\" | \"agent-auth\" = \"reconnect\") {\n super(message);\n this.name = \"CredentialError\";\n }\n}\n\n/** Treat a token as expired this long before its stated expiry. */\nconst EXPIRY_SKEW_MS = 60_000;\n/** Agent API statuses that mean \"this Connection is unusable\", not \"try again\". */\nconst RECONNECT_STATUSES = new Set([400, 403, 404]);\n/**\n * A pinned refresh answers 409 both for a transient race and when the mailbox\n * now resolves to a different Connection. One 409 is retried; this many in a\n * row means the configured Connection no longer serves this mailbox.\n */\nexport const MAX_CONSECUTIVE_REFRESH_CONFLICTS = 3;\n\nexport class ConnectionCredentials {\n private described: { provider: MailboxProvider; accountIdentifier: string } | null = null;\n private storedToken: { token: string; expiresAt: number } | null = null;\n private inflight: Promise<string> | null = null;\n private refreshConflicts = 0;\n\n constructor(\n private readonly client: CredentialsClient,\n private readonly connectionId: string,\n private readonly now: () => number = Date.now,\n ) {}\n\n /** Fetch the Connection once to learn its provider and mailbox. */\n async describe(): Promise<{ provider: MailboxProvider; accountIdentifier: string }> {\n if (this.described) return this.described;\n const creds = await this.call(() => this.client.getConnectionCredentials(this.connectionId));\n if (creds.provider !== \"google\" && creds.provider !== \"microsoft\") {\n throw new CredentialError(\"The email channel's Connection is not a Google or Microsoft mailbox\");\n }\n if (typeof creds.accountIdentifier !== \"string\" || !creds.accountIdentifier.includes(\"@\")) {\n throw new CredentialError(\"The email channel's Connection has no mailbox address\");\n }\n this.described = { provider: creds.provider, accountIdentifier: creds.accountIdentifier };\n const expiresAt = parseExpiry(creds.accessTokenExpiresAt);\n if (typeof creds.accessToken === \"string\" && creds.accessToken !== \"\" && expiresAt > this.now() + EXPIRY_SKEW_MS) {\n this.storedToken = { token: creds.accessToken, expiresAt };\n }\n return this.described;\n }\n\n /** The driver's {@link AccessTokenProvider}. */\n readonly getAccessToken: AccessTokenProvider = () => {\n this.inflight ??= this.nextToken().finally(() => {\n this.inflight = null;\n });\n return this.inflight;\n };\n\n private async nextToken(): Promise<string> {\n const { provider, accountIdentifier } = await this.describe();\n // The stored token is handed out at most once: a second call means the\n // driver got a 401 with it.\n const stored = this.storedToken;\n this.storedToken = null;\n if (stored && stored.expiresAt > this.now() + EXPIRY_SKEW_MS) return stored.token;\n\n let refreshed: { accessToken: string };\n try {\n refreshed = await this.call(() =>\n provider === \"microsoft\"\n ? this.client.refreshMicrosoftAccountToken(accountIdentifier, { connectionId: this.connectionId })\n : this.client.refreshSocialAccount(\"google\", accountIdentifier, { connectionId: this.connectionId }),\n );\n } catch (err) {\n if ((err as { status?: unknown } | null)?.status !== 409) {\n this.refreshConflicts = 0;\n throw err;\n }\n this.refreshConflicts += 1;\n if (this.refreshConflicts >= MAX_CONSECUTIVE_REFRESH_CONFLICTS) {\n throw new CredentialError(\n `Connect refused ${String(this.refreshConflicts)} refreshes in a row because this mailbox no longer resolves to the channel's Connection`,\n );\n }\n throw err; // transient: the poller backs off and retries\n }\n this.refreshConflicts = 0;\n if (typeof refreshed.accessToken !== \"string\" || refreshed.accessToken === \"\") {\n throw new CredentialError(\"Connect returned no access token for the mailbox\");\n }\n return refreshed.accessToken;\n }\n\n private async call<T>(fn: () => Promise<T>): Promise<T> {\n try {\n return await fn();\n } catch (err) {\n const status = (err as { status?: unknown } | null)?.status;\n if (status === 401) throw new CredentialError(\"The Agent API rejected this agent's token\", \"agent-auth\");\n if (typeof status === \"number\" && RECONNECT_STATUSES.has(status)) {\n throw new CredentialError(`Connect rejected the mailbox Connection (${String(status)})`);\n }\n throw err;\n }\n }\n}\n\nfunction parseExpiry(value: unknown): number {\n if (typeof value !== \"string\" || value === \"\") return 0;\n const ms = Date.parse(value);\n return Number.isFinite(ms) ? ms : 0;\n}\n","import { MailboxAuthError, MailboxScopeError } from '@alfe.ai/email';\nimport { CredentialError } from './credentials.js';\n\n/**\n * Errors only the user can fix by reconnecting the mailbox: revoked consent,\n * a token without a needed scope (an Outlook grant from before Mail.Send), or\n * a Connection Connect will no longer serve.\n */\nexport function isReconnectError(err: unknown): boolean {\n return err instanceof MailboxAuthError\n || err instanceof MailboxScopeError\n || (err instanceof CredentialError && err.reason === 'reconnect');\n}\n\n/** The Agent API rejected this agent's own token: not fixable by reconnecting the mailbox. */\nexport function isAgentAuthError(err: unknown): boolean {\n return err instanceof CredentialError && err.reason === 'agent-auth';\n}\n\nexport function reconnectMessage(err: unknown): string {\n if (err instanceof MailboxScopeError) {\n return 'The mailbox connection is missing a permission email needs (for Outlook, Mail.Send); reconnect the account';\n }\n if (err instanceof MailboxAuthError) return 'The mailbox rejected its credentials; reconnect the account';\n return `${err instanceof Error ? err.message : String(err)}; reconnect the account`;\n}\n","/**\n * Tolerant OpenClaw host-version gate (copy of\n * `@alfe.ai/openclaw-identity/src/host-version.ts`; kept local so the plugins\n * do not depend on each other).\n *\n * `api.runtime.version` is the host's VERSION string; `OPENCLAW_BUNDLED_VERSION`\n * can override it. Accept a bare `YYYY.M.P`, a branded `OpenClaw 2026.9.5 (sha)`\n * line, or a pre-release / build suffix (`2026.9.5-beta.1` counts as its base\n * release). Anything unparseable is \"unknown\" and the caller refuses to start.\n */\n\nconst VERSION_TOKEN = /(?:^|[^\\d.])(\\d{4})\\.(\\d+)\\.(\\d+)(?=$|[^\\d.]|\\.[^\\d])/u;\n\nexport function parseHostVersion(value: unknown): [number, number, number] | undefined {\n if (typeof value !== 'string') return undefined;\n const match = VERSION_TOKEN.exec(value.trim());\n if (!match) return undefined;\n return [Number(match[1]), Number(match[2]), Number(match[3])];\n}\n\n/** True only when `version` parses and is at least `minimum`. */\nexport function isHostAtLeast(version: unknown, minimum: string): boolean {\n const have = parseHostVersion(version);\n const want = parseHostVersion(minimum);\n if (!have || !want) return false;\n for (let i = 0; i < 3; i++) {\n if (have[i] !== want[i]) return (have[i] ?? 0) > (want[i] ?? 0);\n }\n return true;\n}\n\n/**\n * The tool gate in `@alfe.ai/openclaw-identity` needs 9.5's per-sender hook\n * context (`before_tool_call` `ctx.requester`). Mirrors\n * `package.json#openclaw.install.minHostVersion`.\n */\nexport const MIN_HOST_VERSION = '2026.9.5';\n","/**\n * Sender admission: may this sender instruct this agent over email?\n *\n * Same evaluation as `@alfe.ai/openclaw-identity`'s `agent-exec-gate.ts`\n * (copied, not imported, so the plugins stay independent): one\n * `createAbility(raw permission strings).has(...)` call against the scoped\n * `agent:<agentId>` gate, so `cannot` (`!agent:chat`) entries are honoured.\n */\nimport { createAbility } from \"@auriclabs/roles\";\n\ndeclare module \"@auriclabs/roles\" {\n interface PermissionDefinitions {\n agent: \"exec\" | \"chat\" | \"manage\" | \"read\" | \"create\" | \"update\" | \"delete\";\n all: \"manage\";\n }\n}\n\nexport function allowsAgentChat(permissions: readonly unknown[], agentId: string): boolean {\n // Without an agentId the gate scope cannot be built: deny.\n if (!agentId) return false;\n const ability = createAbility(permissions as never);\n const gatePermission = {\n subject: \"agent\",\n action: \"chat\",\n scope: `agent:${agentId}`,\n } as unknown as Parameters<typeof ability.has>[0];\n return ability.has(gatePermission);\n}\n","/**\n * Per-mailbox poller state: `<stateDir>/email/<mailboxHash>.json`.\n *\n * One file holds the sync cursor, the dedupe LRU, the replied set, the thread\n * map out-of-turn sends resolve against, loop-cap counters, the resolve retry\n * queue and the admitted-but-unfinished turns. Writes are atomic (owner-only\n * temp file, then rename) and serialised. A missing, unreadable or invalid\n * file starts fresh, which reseeds the cursor, so history is never replayed.\n */\nimport { randomUUID } from 'node:crypto';\nimport { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport type { EmailAddress } from '@alfe.ai/email';\nimport { mailboxHash } from './hash.js';\nimport type { Logger } from './types.js';\n\n/** What `composeReply` needs from the email being answered. */\nexport interface ParentRef {\n id: string;\n threadId: string;\n messageId: string;\n references: string[];\n from: EmailAddress;\n to: EmailAddress[];\n cc: EmailAddress[];\n subject: string;\n /** DMARC-verified sender: reply-all. Unverified: reply to From only. */\n verified: boolean;\n threadIndex?: string;\n threadTopic?: string;\n}\n\nexport interface ThreadRecord {\n threadId: string;\n /** The latest admitted inbound email: replies and out-of-turn sends answer it. */\n parent: ParentRef;\n lastActivityAt: number;\n}\n\n/** A message whose sender resolve failed; retried, then dropped. Left unread. */\nexport interface RetryEntry {\n id: string;\n threadId: string;\n /** Retries already made. */\n attempts: number;\n nextAt: number;\n /** A restart-recovered turn: already seen, so only admission decides it. */\n recovering?: boolean;\n}\n\n/** An admitted message whose turn has not finished. Re-run after a restart. */\nexport interface PendingEntry {\n id: string;\n threadId: string;\n}\n\nexport interface SeenEntry {\n /** Provider message id. */\n id: string;\n /** RFC Message-ID, or `''` when unknown yet. */\n mid: string;\n}\n\nexport interface MailboxStateData {\n version: 1;\n mailbox: string;\n cursor: string | null;\n seen: SeenEntry[];\n /** Inbound Message-IDs the agent has answered (recorded before sending). */\n replied: string[];\n threads: Record<string, ThreadRecord>;\n /** Reply timestamps per thread hash, for the hourly loop cap. */\n replies: Record<string, number[]>;\n retry: RetryEntry[];\n pending: PendingEntry[];\n lastMessageAt: number;\n}\n\nexport const LIMITS = {\n seen: 500,\n replied: 500,\n threads: 500,\n retry: 100,\n pending: 100,\n} as const;\n\n/** Bounds on what a thread record keeps from attacker-controlled headers. */\nexport const PARENT_LIMITS = { addresses: 50, references: 20, subject: 998, address: 320, name: 256, id: 998 } as const;\n\nconst HOUR_MS = 60 * 60_000;\n\nexport function stateFilePath(stateDir: string, mailbox: string): string {\n return join(stateDir, 'email', `${mailboxHash(mailbox)}.json`);\n}\n\nfunction freshState(mailbox: string): MailboxStateData {\n return {\n version: 1,\n mailbox,\n cursor: null,\n seen: [],\n replied: [],\n threads: {},\n replies: {},\n retry: [],\n pending: [],\n lastMessageAt: 0,\n };\n}\n\nexport class StateStore {\n private data: MailboxStateData;\n private writeChain: Promise<void> = Promise.resolve();\n\n constructor(\n private readonly file: string,\n private readonly mailbox: string,\n private readonly log: Logger,\n ) {\n this.data = freshState(mailbox);\n }\n\n async load(): Promise<void> {\n try {\n const parsed: unknown = JSON.parse(await readFile(this.file, 'utf8'));\n if (!isMailboxState(parsed)) throw new Error('invalid state shape');\n if (parsed.mailbox !== this.mailbox) throw new Error('state belongs to another mailbox');\n this.data = parsed;\n } catch (err) {\n if (!isMissingFile(err)) {\n this.log.warn(`Ignoring unreadable email state; the mailbox will be reseeded (${errorMessage(err)})`);\n }\n this.data = freshState(this.mailbox);\n }\n }\n\n /** Atomic, serialised write of the current state. */\n save(): Promise<void> {\n const contents = JSON.stringify(this.data);\n const next = this.writeChain.then(async () => {\n const temporary = `${this.file}.${String(process.pid)}.${randomUUID()}.tmp`;\n try {\n await mkdir(dirname(this.file), { recursive: true, mode: 0o700 });\n await writeFile(temporary, contents, { mode: 0o600 });\n await rename(temporary, this.file);\n } catch (err) {\n await rm(temporary, { force: true }).catch(() => undefined);\n throw err;\n }\n });\n this.writeChain = next.catch(() => undefined);\n return next;\n }\n\n // ── cursor ──\n get cursor(): string | null {\n return this.data.cursor;\n }\n\n set cursor(value: string | null) {\n this.data.cursor = value;\n }\n\n get lastMessageAt(): number {\n return this.data.lastMessageAt;\n }\n\n set lastMessageAt(value: number) {\n this.data.lastMessageAt = value;\n }\n\n // ── dedupe ──\n hasSeenId(id: string): boolean {\n return this.data.seen.some((entry) => entry.id === id);\n }\n\n hasSeenMessageId(mid: string): boolean {\n return mid !== '' && this.data.seen.some((entry) => entry.mid === mid);\n }\n\n markSeen(id: string, mid: string): void {\n this.data.seen = this.data.seen.filter((entry) => entry.id !== id);\n this.data.seen.push({ id, mid });\n this.trim(this.data.seen, LIMITS.seen, 'dedupe');\n }\n\n // ── replies ──\n isReplied(mid: string): boolean {\n return this.data.replied.includes(mid);\n }\n\n markReplied(mid: string): void {\n if (this.data.replied.includes(mid)) return;\n this.data.replied.push(mid);\n this.trim(this.data.replied, LIMITS.replied, 'replied');\n }\n\n /** Undo {@link markReplied} for a reply that was definitely not sent. */\n unmarkReplied(mid: string): void {\n this.data.replied = this.data.replied.filter((entry) => entry !== mid);\n }\n\n /** Undo {@link recordReply} for a reply that was definitely not sent. */\n forgetReply(hash: string, at: number): void {\n if (!Object.hasOwn(this.data.replies, hash)) return;\n const times = this.data.replies[hash] ?? [];\n const index = times.lastIndexOf(at);\n if (index !== -1) times.splice(index, 1);\n }\n\n repliesInLastHour(hash: string, now: number): number {\n return (this.data.replies[hash] ?? []).filter((at) => now - at < HOUR_MS).length;\n }\n\n recordReply(hash: string, now: number): void {\n const recent = (this.data.replies[hash] ?? []).filter((at) => now - at < HOUR_MS);\n recent.push(now);\n this.data.replies[hash] = recent;\n for (const [key, times] of Object.entries(this.data.replies)) {\n // eslint-disable-next-line @typescript-eslint/no-dynamic-delete -- pruning a bounded map\n if (!times.some((at) => now - at < HOUR_MS)) delete this.data.replies[key];\n }\n }\n\n // ── threads ──\n getThread(hash: string): ThreadRecord | undefined {\n return Object.hasOwn(this.data.threads, hash) ? this.data.threads[hash] : undefined;\n }\n\n upsertThread(hash: string, record: ThreadRecord): void {\n this.data.threads[hash] = record;\n const entries = Object.entries(this.data.threads);\n if (entries.length <= LIMITS.threads) return;\n entries.sort((a, b) => a[1].lastActivityAt - b[1].lastActivityAt);\n for (const [key] of entries.slice(0, entries.length - LIMITS.threads)) {\n // eslint-disable-next-line @typescript-eslint/no-dynamic-delete -- evicting the oldest threads\n delete this.data.threads[key];\n }\n this.log.debug(`Email state thread map full; evicted ${String(entries.length - LIMITS.threads)} oldest thread(s)`);\n }\n\n // ── retry queue ──\n retryEntries(): readonly RetryEntry[] {\n return this.data.retry;\n }\n\n hasRetry(id: string): boolean {\n return this.data.retry.some((entry) => entry.id === id);\n }\n\n getRetry(id: string): RetryEntry | undefined {\n return this.data.retry.find((entry) => entry.id === id);\n }\n\n putRetry(entry: RetryEntry): void {\n this.removeRetry(entry.id);\n this.data.retry.push(entry);\n this.trim(this.data.retry, LIMITS.retry, 'retry');\n }\n\n removeRetry(id: string): void {\n this.data.retry = this.data.retry.filter((entry) => entry.id !== id);\n }\n\n // ── pending turns ──\n pendingEntries(): readonly PendingEntry[] {\n return this.data.pending;\n }\n\n addPending(entry: PendingEntry): void {\n this.removePending(entry.id);\n this.data.pending.push(entry);\n this.trim(this.data.pending, LIMITS.pending, 'pending');\n }\n\n removePending(id: string): void {\n this.data.pending = this.data.pending.filter((entry) => entry.id !== id);\n }\n\n private trim(list: unknown[], max: number, what: string): void {\n const dropped = trimFront(list, max);\n if (dropped > 0) this.log.debug(`Email state ${what} list full; dropped ${String(dropped)} oldest entr${dropped === 1 ? 'y' : 'ies'}`);\n }\n\n /** For tests and diagnostics. */\n snapshot(): MailboxStateData {\n return structuredClone(this.data);\n }\n}\n\nfunction trimFront(list: unknown[], max: number): number {\n const excess = list.length - max;\n if (excess <= 0) return 0;\n list.splice(0, excess);\n return excess;\n}\n\nfunction isMissingFile(err: unknown): boolean {\n return typeof err === 'object' && err !== null && 'code' in err && err.code === 'ENOENT';\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n// ── shape validation ──\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isString(value: unknown): value is string {\n return typeof value === 'string';\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value);\n}\n\nfunction isBoundedString(value: unknown, max: number): value is string {\n return typeof value === 'string' && value.length <= max;\n}\n\nfunction isAddress(value: unknown): value is EmailAddress {\n return isRecord(value)\n && isBoundedString(value.address, PARENT_LIMITS.address)\n && (value.name === undefined || isBoundedString(value.name, PARENT_LIMITS.name));\n}\n\nfunction isAddressList(value: unknown): value is EmailAddress[] {\n return Array.isArray(value) && value.length <= PARENT_LIMITS.addresses && value.every(isAddress);\n}\n\nfunction isParentRef(value: unknown): value is ParentRef {\n const id = (v: unknown): boolean => isBoundedString(v, PARENT_LIMITS.id);\n return (\n isRecord(value)\n && id(value.id)\n && id(value.threadId)\n && id(value.messageId)\n && Array.isArray(value.references) && value.references.length <= PARENT_LIMITS.references && value.references.every(id)\n && isAddress(value.from)\n && isAddressList(value.to)\n && isAddressList(value.cc)\n && isBoundedString(value.subject, PARENT_LIMITS.subject)\n && typeof value.verified === 'boolean'\n && (value.threadIndex === undefined || id(value.threadIndex))\n && (value.threadTopic === undefined || isBoundedString(value.threadTopic, PARENT_LIMITS.subject))\n );\n}\n\nfunction isIdPair(value: unknown): value is PendingEntry {\n return isRecord(value) && isString(value.id) && isString(value.threadId);\n}\n\nfunction isMailboxState(value: unknown): value is MailboxStateData {\n if (!isRecord(value) || value.version !== 1 || !isString(value.mailbox)) return false;\n if (value.cursor !== null && !isString(value.cursor)) return false;\n if (!isFiniteNumber(value.lastMessageAt)) return false;\n if (!Array.isArray(value.seen) || value.seen.length > LIMITS.seen) return false;\n if (!value.seen.every((entry) => isRecord(entry) && isString(entry.id) && isString(entry.mid))) return false;\n if (!Array.isArray(value.replied) || value.replied.length > LIMITS.replied || !value.replied.every(isString)) return false;\n if (!isRecord(value.threads) || Object.keys(value.threads).length > LIMITS.threads) return false;\n for (const thread of Object.values(value.threads)) {\n if (!isRecord(thread) || !isString(thread.threadId) || !isFiniteNumber(thread.lastActivityAt) || !isParentRef(thread.parent)) {\n return false;\n }\n }\n if (!isRecord(value.replies)) return false;\n for (const times of Object.values(value.replies)) {\n if (!Array.isArray(times) || !times.every(isFiniteNumber)) return false;\n }\n if (!Array.isArray(value.retry) || value.retry.length > LIMITS.retry) return false;\n if (!value.retry.every((entry) => isIdPair(entry)\n && isFiniteNumber((entry as RetryEntry).attempts)\n && isFiniteNumber((entry as RetryEntry).nextAt)\n && ((entry as RetryEntry).recovering === undefined || typeof (entry as RetryEntry).recovering === 'boolean'))) {\n return false;\n }\n if (!Array.isArray(value.pending) || value.pending.length > LIMITS.pending || !value.pending.every(isIdPair)) return false;\n return true;\n}\n","/**\n * Outbound email: one reply per turn, reply idempotency, loop cap, and\n * out-of-turn sends to a thread target.\n *\n * Turn replies arrive through dispatch's `deliver` callback once per reply\n * payload; {@link TurnBuffer} collects them and the pipeline sends a single\n * email when the dispatch promise resolves (the turn-end signal on 9.5).\n *\n * Idempotency is keyed by the inbound Message-ID: the key is recorded and\n * persisted BEFORE the send, so a restart never re-sends a recorded reply (a\n * crash mid-send may lose it). Within one send, a retry happens only after a\n * provider throttle (429) or a connection failure, never after a timeout or\n * 5xx where the provider may already have accepted the message; a connection\n * that drops after the provider accepted the request is the one remaining way\n * to get a duplicate.\n *\n * Recipients: a DMARC-verified sender gets reply-all (the thread's people);\n * an unverified sender gets a reply to From only, so a forged From cannot turn\n * the agent into a relay to arbitrary To/Cc addresses. Either way at most\n * {@link MAX_RECIPIENTS} addresses are kept.\n */\nimport {\n composeReply,\n MailboxAuthError,\n MailboxHttpError,\n MailboxRateLimitError,\n MailboxScopeError,\n type EmailAddress,\n type MailboxDriver,\n type NormalizedEmail,\n} from '@alfe.ai/email';\nimport { parseThreadTarget } from './hash.js';\nimport { PARENT_LIMITS, type ParentRef, type StateStore } from './state.js';\nimport type { Logger, OutboundReplyPayload } from './types.js';\n\nexport const DEFAULT_LOOP_CAP = 10;\n/** Delays before each send retry (3 retries). */\nexport const SEND_RETRY_DELAYS_MS = [1_000, 4_000, 16_000] as const;\n/** Agent output is bounded before it becomes an email. */\nconst MAX_REPLY_CHARS = 100_000;\nexport const MAX_RECIPIENTS = 20;\n\n\nexport interface OutboundDeps {\n driver: Pick<MailboxDriver, 'sendMime'>;\n state: StateStore;\n ownAddress: string;\n log: Logger;\n now: () => number;\n sleep: (ms: number) => Promise<void>;\n loopCap: number;\n}\n\nexport type SendOutcome =\n | { kind: 'sent'; messageId: string }\n | { kind: 'empty' }\n | { kind: 'duplicate' }\n | { kind: 'capped' }\n | { kind: 'no-recipients' };\n\n/** Collects a turn's reply payloads. */\nexport class TurnBuffer {\n private readonly parts: string[] = [];\n\n push(payload: OutboundReplyPayload): void {\n const text = typeof payload.text === 'string' ? payload.text.trim() : '';\n if (text) this.parts.push(text);\n }\n\n text(): string {\n return this.parts.join('\\n\\n');\n }\n}\n\n/** What a reply needs from the inbound email, bounded before it is persisted. */\nexport function parentRefOf(email: NormalizedEmail, verified: boolean, log?: Logger): ParentRef {\n const limit = PARENT_LIMITS;\n const list = (entries: EmailAddress[], what: string): EmailAddress[] => {\n if (entries.length > limit.addresses) {\n log?.warn(`Email ${what} list truncated to ${String(limit.addresses)} of ${String(entries.length)} addresses`);\n }\n return entries.slice(0, limit.addresses).map(boundAddress);\n };\n return {\n id: email.id.slice(0, limit.id),\n threadId: email.threadId.slice(0, limit.id),\n messageId: email.messageId.slice(0, limit.id),\n references: email.references.slice(-limit.references).map((ref) => ref.slice(0, limit.id)),\n from: boundAddress(email.from),\n to: list(email.to, 'To'),\n cc: list(email.cc, 'Cc'),\n subject: email.subject.slice(0, limit.subject),\n verified,\n ...(email.threadIndex !== undefined ? { threadIndex: email.threadIndex.slice(0, limit.id) } : {}),\n ...(email.threadTopic !== undefined ? { threadTopic: email.threadTopic.slice(0, limit.subject) } : {}),\n };\n}\n\nfunction boundAddress(entry: EmailAddress): EmailAddress {\n return {\n address: entry.address.slice(0, PARENT_LIMITS.address),\n ...(entry.name !== undefined ? { name: entry.name.slice(0, PARENT_LIMITS.name) } : {}),\n };\n}\n\n/**\n * Who the reply goes to. Unverified senders: From only. Verified: From + To +\n * Cc. Own address removed, deduped case-insensitively, capped.\n */\nexport function replyAudience(parent: ParentRef, ownAddress: string, log: Logger): { to: EmailAddress[]; cc: EmailAddress[] } {\n const seen = new Set([ownAddress.trim().toLowerCase()]);\n const keep = (entries: EmailAddress[]): EmailAddress[] => entries.filter((entry) => {\n const key = entry.address.trim().toLowerCase();\n if (key === '' || seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n const to = keep(parent.verified ? [parent.from, ...parent.to] : [parent.from]);\n const cc = parent.verified ? keep(parent.cc) : [];\n const total = to.length + cc.length;\n if (total <= MAX_RECIPIENTS) return { to, cc };\n log.warn(`Email reply recipients capped at ${String(MAX_RECIPIENTS)} of ${String(total)}`);\n const cappedTo = to.slice(0, MAX_RECIPIENTS);\n return { to: cappedTo, cc: cc.slice(0, MAX_RECIPIENTS - cappedTo.length) };\n}\n\nfunction asParentEmail(parent: ParentRef, audience: { to: EmailAddress[]; cc: EmailAddress[] }): NormalizedEmail {\n const [from = { address: '' }, ...to] = audience.to;\n return {\n id: parent.id,\n threadId: parent.threadId,\n messageId: parent.messageId,\n references: parent.references,\n // composeReply addresses From + To, then Cc; the audience is already final.\n from,\n to,\n cc: audience.cc,\n subject: parent.subject,\n ...(parent.threadIndex !== undefined ? { threadIndex: parent.threadIndex } : {}),\n ...(parent.threadTopic !== undefined ? { threadTopic: parent.threadTopic } : {}),\n textBody: '',\n headers: new Map(),\n headerList: [],\n attachments: [],\n };\n}\n\n/**\n * Send one reply on a thread. `inboundKey` (the inbound Message-ID) makes the\n * send idempotent; out-of-turn sends have none.\n */\nexport async function sendReply(\n deps: OutboundDeps,\n args: { threadHash: string; parent: ParentRef; markdown: string; inboundKey?: string },\n): Promise<SendOutcome> {\n const markdown = bound(args.markdown.trim());\n if (!markdown) return { kind: 'empty' };\n if (args.inboundKey !== undefined && deps.state.isReplied(args.inboundKey)) return { kind: 'duplicate' };\n\n const now = deps.now();\n if (deps.state.repliesInLastHour(args.threadHash, now) >= deps.loopCap) {\n deps.log.warn(`Email reply suppressed: thread reached ${String(deps.loopCap)} replies in the last hour`);\n return { kind: 'capped' };\n }\n\n const audience = replyAudience(args.parent, deps.ownAddress, deps.log);\n const composed = composeReply({ parent: asParentEmail(args.parent, audience), ownAddress: deps.ownAddress, markdown });\n if (composed.recipients.length === 0) {\n deps.log.warn('Email reply skipped: the thread has no recipients other than this mailbox');\n return { kind: 'no-recipients' };\n }\n\n // Record before send: at-most-once across a crash.\n if (args.inboundKey !== undefined) deps.state.markReplied(args.inboundKey);\n deps.state.recordReply(args.threadHash, now);\n await deps.state.save();\n\n for (let attempt = 0; ; attempt++) {\n try {\n await deps.driver.sendMime(composed.raw, { threadId: args.parent.threadId });\n return { kind: 'sent', messageId: messageIdOf(composed.raw) };\n } catch (err) {\n if (!isSafeToRetrySend(err) || attempt >= SEND_RETRY_DELAYS_MS.length) {\n if (isDefinitelyNotSent(err)) {\n // The provider refused before accepting anything: release the\n // idempotency key so a later turn for this message can reply.\n if (args.inboundKey !== undefined) deps.state.unmarkReplied(args.inboundKey);\n deps.state.forgetReply(args.threadHash, now);\n await deps.state.save().catch(() => undefined);\n }\n throw err;\n }\n const delay = SEND_RETRY_DELAYS_MS[attempt] ?? 0;\n const retryAfter = (err as { retryAfterMs?: unknown } | null)?.retryAfterMs;\n const wait = typeof retryAfter === 'number' && retryAfter > delay ? Math.min(retryAfter, 60_000) : delay;\n deps.log.warn(`Email send failed; retrying in ${String(wait)}ms (${errorMessage(err)})`);\n await deps.sleep(wait);\n }\n }\n}\n\n/** Out-of-turn send (message tool, cron, follow-ups) to `thread:<threadHash>`. */\nexport async function sendToThread(deps: OutboundDeps, to: string, text: string): Promise<SendOutcome> {\n const hash = parseThreadTarget(to);\n if (!hash) throw new Error('Invalid email target: use thread:<threadHash>');\n const thread = deps.state.getThread(hash);\n if (!thread) throw new Error('Unknown email thread: this mailbox has no conversation with that target');\n return await sendReply(deps, { threadHash: hash, parent: thread.parent, markdown: text });\n}\n\n/**\n * Retry a send only when the provider cannot have accepted it: a throttle, or\n * a fetch that failed to connect. A timeout, a 5xx, auth and scope errors are\n * final (the message may already be out, or retrying cannot help).\n */\nexport function isSafeToRetrySend(err: unknown): boolean {\n if (err instanceof MailboxRateLimitError) return true;\n if (err instanceof MailboxHttpError) return false;\n // Undici reports a failed connect as `TypeError: fetch failed` with the\n // socket error as `cause`; timeouts surface as AbortError/TimeoutError.\n if (!(err instanceof TypeError)) return false;\n const code = (err.cause as { code?: unknown } | undefined)?.code;\n return typeof code === 'string' && CONNECT_ERRORS.has(code);\n}\n\n/**\n * The provider refused the send before accepting it: auth, scope, throttle,\n * any other 4xx except 408 (request timeout), or a failed connect. A 5xx,\n * a timeout or an unknown error is ambiguous and keeps the at-most-once key.\n */\nexport function isDefinitelyNotSent(err: unknown): boolean {\n if (err instanceof MailboxAuthError || err instanceof MailboxScopeError) return true;\n if (err instanceof MailboxHttpError) return err.status >= 400 && err.status < 500 && err.status !== 408;\n return isSafeToRetrySend(err);\n}\n\nconst CONNECT_ERRORS = new Set(['ECONNREFUSED', 'ENOTFOUND', 'EAI_AGAIN', 'ENETUNREACH', 'EHOSTUNREACH', 'UND_ERR_CONNECT_TIMEOUT']);\n\nfunction bound(text: string): string {\n return text.length > MAX_REPLY_CHARS ? `${text.slice(0, MAX_REPLY_CHARS)}\\n\\n[reply truncated]` : text;\n}\n\nfunction messageIdOf(raw: string): string {\n return /^Message-ID: (<[^>\\r\\n]+>)/im.exec(raw)?.[1] ?? '';\n}\n\nexport function errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n","/**\n * Cross-plugin sender-context registry (contract `alfe.senderContext.v1`),\n * write side.\n *\n * `@alfe.ai/openclaw-identity` reads this before re-resolving a sender in its\n * tool gate, because stock OpenClaw message metadata drops Alfe identity\n * fields and the identity plugin's own resolve cannot repeat this channel's\n * DMARC-verified `emailVerified` evidence.\n *\n * The store lives on `globalThis` under a registry symbol so it survives the\n * OpenClaw 9.x double plugin load and is shared across packages without a\n * plugin-kit dependency. Each package ships its own copy of this contract;\n * keep the key, the key format, and the entry shape identical to\n * `packages/openclaw-identity/src/sender-context.ts`.\n *\n * - Key: `${sessionKey}\\u0000${senderId}`, using OpenClaw's run `SessionKey`\n * and `SenderId`.\n * - This plugin owns its entries: written before dispatch, deleted when the\n * dispatch promise resolves. `expiresAt` (epoch ms) is the crash backstop,\n * refreshed while a long turn is still running.\n */\n\nconst KEY = Symbol.for(\"alfe.senderContext.v1\");\n\nexport interface SenderContext {\n identityId: string | null;\n status: string;\n permissions: string[];\n expiresAt: number;\n}\n\ntype Store = Map<string, SenderContext>;\n\nexport function senderContextStore(): Store {\n const g = globalThis as Record<symbol, unknown>;\n return (g[KEY] ??= new Map()) as Store;\n}\n\nexport function senderContextKey(sessionKey: string, senderId: string): string {\n return `${sessionKey}\\u0000${senderId}`;\n}\n\n/** Entries live at most this long without a refresh. */\nexport const SENDER_CONTEXT_TTL_MS = 2 * 60_000;\n\nexport function writeSenderContext(\n sessionKey: string,\n senderId: string,\n entry: Omit<SenderContext, \"expiresAt\">,\n now = Date.now(),\n): SenderContext {\n const written: SenderContext = {\n identityId: entry.identityId,\n status: entry.status,\n permissions: [...entry.permissions],\n expiresAt: now + SENDER_CONTEXT_TTL_MS,\n };\n senderContextStore().set(senderContextKey(sessionKey, senderId), written);\n return written;\n}\n\n/** Extend the TTL of an entry this writer wrote, if it is still the live one. */\nexport function touchSenderContext(\n sessionKey: string,\n senderId: string,\n written: SenderContext,\n now = Date.now(),\n): void {\n if (senderContextStore().get(senderContextKey(sessionKey, senderId)) === written) {\n written.expiresAt = now + SENDER_CONTEXT_TTL_MS;\n }\n}\n\n/** Delete the entry only if it is still the one this writer wrote. */\nexport function deleteSenderContext(sessionKey: string, senderId: string, written: SenderContext): void {\n const store = senderContextStore();\n const key = senderContextKey(sessionKey, senderId);\n if (store.get(key) === written) store.delete(key);\n}\n","/**\n * Keeps each admitted email sender's registry entry alive across the session.\n *\n * Later runs in the same session (exec completions, `sessions_yield`\n * continuations) have no `requester`. `@alfe.ai/openclaw-identity` then looks\n * up the session's sender itself; if this plugin's entry is gone it\n * re-resolves WITHOUT `emailVerified`, and a verified sender is downgraded to\n * wildcard-only permissions. So the entry outlives the turn:\n *\n * - kept for up to {@link RETENTION_MS} (30 min, matching openclaw-identity's\n * sender record) since that sender's last admitted inbound;\n * - rewritten every heartbeat so its `expiresAt` stays within the reader's\n * 5-minute cap (entries carry a 2-minute TTL);\n * - re-resolved with the stored `emailVerified` verdict at least every\n * {@link RE_RESOLVE_MS}, so a revoked grant stops applying within one\n * re-resolve; a denial, an outage or a resolve error deletes the entry\n * (fail closed: the identity plugin falls back to its own resolve);\n * - bounded to {@link MAX_ENTRIES} live entries, least recently admitted\n * evicted first; `stop()` deletes every entry this keeper owns.\n */\nimport { allowsAgentChat } from './admission.js';\nimport { errorMessage } from './outbound.js';\nimport type { IdentityResolver } from './pipeline.js';\nimport {\n deleteSenderContext,\n touchSenderContext,\n writeSenderContext,\n type SenderContext,\n} from './sender-context.js';\nimport type { Logger } from './types.js';\n\nexport const RETENTION_MS = 30 * 60_000;\nexport const RE_RESOLVE_MS = 5 * 60_000;\nexport const HEARTBEAT_MS = 60_000;\nexport const MAX_ENTRIES = 200;\n\ninterface Kept {\n sessionKey: string;\n senderId: string;\n from: string;\n verified: boolean;\n written: SenderContext;\n lastAdmittedAt: number;\n lastResolvedAt: number;\n resolving: Promise<void> | null;\n}\n\nexport interface SenderKeeperDeps {\n identity: IdentityResolver;\n agentId: string;\n log: Logger;\n now: () => number;\n heartbeatMs?: number;\n reResolveMs?: number;\n retentionMs?: number;\n maxEntries?: number;\n}\n\nexport class SenderContextKeeper {\n private readonly entries = new Map<string, Kept>();\n private timer: ReturnType<typeof setInterval> | null = null;\n private stopped = false;\n\n constructor(private readonly deps: SenderKeeperDeps) {}\n\n /** Write (or refresh) the entry for an admitted inbound, before its dispatch. */\n admit(\n sessionKey: string,\n senderId: string,\n sender: { from: string; verified: boolean; context: Omit<SenderContext, 'expiresAt'> },\n ): void {\n if (this.stopped) return;\n const now = this.deps.now();\n const key = `${sessionKey}\\u0000${senderId}`;\n const previous = this.entries.get(key);\n if (previous) this.entries.delete(key); // re-insert: most recently admitted last\n this.entries.set(key, {\n sessionKey,\n senderId,\n from: sender.from,\n verified: sender.verified,\n written: writeSenderContext(sessionKey, senderId, sender.context, now),\n lastAdmittedAt: now,\n lastResolvedAt: now,\n resolving: null,\n });\n this.evictOverflow();\n this.ensureTimer();\n }\n\n /** One heartbeat: expire, re-resolve or rewrite every kept entry. Exposed for tests. */\n async tick(): Promise<void> {\n const now = this.deps.now();\n const retention = this.deps.retentionMs ?? RETENTION_MS;\n const reResolve = this.deps.reResolveMs ?? RE_RESOLVE_MS;\n const work: Promise<void>[] = [];\n for (const [key, kept] of [...this.entries]) {\n if (now - kept.lastAdmittedAt >= retention) {\n this.drop(key, kept);\n } else if (now - kept.lastResolvedAt >= reResolve) {\n kept.resolving ??= this.reResolve(key, kept).finally(() => { kept.resolving = null; });\n work.push(kept.resolving);\n } else {\n touchSenderContext(kept.sessionKey, kept.senderId, kept.written, now);\n }\n }\n await Promise.all(work);\n if (this.entries.size === 0) this.clearTimer();\n }\n\n /** Stop heartbeats and delete every entry this keeper owns. */\n stop(): void {\n this.stopped = true;\n this.clearTimer();\n for (const [key, kept] of [...this.entries]) this.drop(key, kept);\n }\n\n size(): number {\n return this.entries.size;\n }\n\n private async reResolve(key: string, kept: Kept): Promise<void> {\n let keep = false;\n let reason = 'denied';\n try {\n const resolved = await this.deps.identity.resolveIdentity({\n provider: 'email',\n platformId: `email:${kept.from}`,\n email: kept.from,\n emailVerified: kept.verified,\n });\n if (resolved.permissionsUnavailable === true) {\n reason = 'permissions unavailable';\n } else if (allowsAgentChat(resolved.permissions, this.deps.agentId)) {\n keep = true;\n if (this.entries.get(key) !== kept) return; // replaced by a newer admission meanwhile\n const now = this.deps.now();\n deleteSenderContext(kept.sessionKey, kept.senderId, kept.written);\n kept.written = writeSenderContext(kept.sessionKey, kept.senderId, {\n identityId: resolved.identityId,\n status: resolved.status,\n permissions: resolved.permissions,\n }, now);\n kept.lastResolvedAt = now;\n }\n } catch (err) {\n reason = `resolve failed: ${errorMessage(err)}`;\n }\n if (!keep && this.entries.get(key) === kept) {\n this.deps.log.info(`Email sender context released on re-resolve (${reason})`);\n this.drop(key, kept);\n }\n }\n\n private drop(key: string, kept: Kept): void {\n deleteSenderContext(kept.sessionKey, kept.senderId, kept.written);\n if (this.entries.get(key) === kept) this.entries.delete(key);\n }\n\n private evictOverflow(): void {\n const max = this.deps.maxEntries ?? MAX_ENTRIES;\n while (this.entries.size > max) {\n const oldest = this.entries.entries().next();\n if (oldest.done) return;\n this.drop(oldest.value[0], oldest.value[1]);\n this.deps.log.debug('Email sender context limit reached; evicted the least recently admitted sender');\n }\n }\n\n private ensureTimer(): void {\n if (this.timer || this.stopped) return;\n this.timer = setInterval(() => {\n void this.tick().catch((err: unknown) => {\n this.deps.log.warn(`Email sender context heartbeat failed: ${errorMessage(err)}`);\n });\n }, this.deps.heartbeatMs ?? HEARTBEAT_MS);\n this.timer.unref();\n }\n\n private clearTimer(): void {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n }\n}\n","/**\n * Inbound pipeline, in order:\n * dedupe → filters → resolve → admission → per-thread serialisation →\n * sender-context registry write (kept after the turn) → dispatch → reply → markHandled.\n *\n * Admission and enqueue are quick, so the poller is never blocked by a long\n * agent turn; the turn itself runs on the thread's promise chain.\n *\n * Per-thread serialisation: OpenClaw would merge a second message into an\n * active run, and `ctx.requester.senderId` would still name the first sender.\n * Each thread's turns therefore chain on the previous turn's dispatch promise\n * (which resolves at turn end on 9.5; `agent_end` is fire-and-forget and not\n * ordered after delivery), so every message runs as its own turn with its own\n * sender context.\n *\n * Denied, filtered and dropped messages are left unread; only a completed\n * turn marks its message handled.\n */\nimport {\n evaluateAuth,\n MessageTooLargeError,\n parseEmail,\n shouldIgnore,\n type AuthVerdict,\n type MailboxDriver,\n type NormalizedEmail,\n type SyncedMessage,\n} from '@alfe.ai/email';\nimport { allowsAgentChat } from './admission.js';\nimport { isReconnectError } from './errors.js';\nimport { maskAddress, peerIdFor, threadHash, threadTarget } from './hash.js';\nimport {\n DEFAULT_LOOP_CAP,\n errorMessage,\n parentRefOf,\n sendReply,\n sendToThread,\n TurnBuffer,\n type OutboundDeps,\n type SendOutcome,\n} from './outbound.js';\nimport type { SenderContext } from './sender-context.js';\nimport { SenderContextKeeper, type SenderKeeperDeps } from './sender-keeper.js';\nimport type { ParentRef, PendingEntry, RetryEntry, StateStore } from './state.js';\nimport type { Logger, TurnRuntime } from './types.js';\n\nexport interface IdentityResolver {\n resolveIdentity(args: {\n provider: string;\n platformId: string;\n email?: string;\n emailVerified?: boolean;\n }): Promise<{\n identityId: string | null;\n status: string;\n permissions: string[];\n permissionsUnavailable?: boolean;\n }>;\n}\n\nexport interface PipelineDeps {\n driver: MailboxDriver;\n state: StateStore;\n identity: IdentityResolver;\n agentId: string;\n ownAddress: string;\n runtime: TurnRuntime;\n log: Logger;\n now?: () => number;\n sleep?: (ms: number) => Promise<void>;\n loopCap?: number;\n /** Delay before resolve retry 1, 2 and 3. After the third failed retry the message is dropped. */\n retryDelaysMs?: readonly number[];\n /** Sender-context retention tuning (heartbeat, re-resolve, retention, max entries). */\n senderKeeper?: Pick<SenderKeeperDeps, 'heartbeatMs' | 'reResolveMs' | 'retentionMs' | 'maxEntries'>;\n /** Sender authentication. Defaults to `evaluateAuth` on the provider's trusted headers; a test seam. */\n evaluateSender?: (\n email: NormalizedEmail,\n fromDomain: string,\n trust: MailboxDriver['trust'],\n mailbox: string,\n ) => AuthVerdict | Promise<AuthVerdict>;\n /** A turn still running after this long is released (no reply, left unread). */\n turnTimeoutMs?: number;\n /** A send failed in a way only reconnecting the mailbox fixes. */\n onReconnectRequired?: (err: unknown) => void;\n /** A reply was sent. */\n onSent?: () => void;\n}\n\nexport type ProcessOutcome = 'dispatched' | 'skipped' | 'ignored' | 'denied' | 'queued' | 'dropped';\n\nexport const RESOLVE_RETRY_DELAYS_MS = [30_000, 2 * 60_000, 10 * 60_000] as const;\nconst CHANNEL = 'email';\nconst ACCOUNT_ID = 'default';\nconst MAX_BODY_CHARS = 32_000;\nexport const DEFAULT_TURN_TIMEOUT_MS = 15 * 60_000;\nconst MAX_ATTACHMENTS_LISTED = 10;\n\nexport class EmailPipeline {\n private readonly chains = new Map<string, Promise<void>>();\n private readonly now: () => number;\n private readonly sleep: (ms: number) => Promise<void>;\n private readonly loopCap: number;\n private readonly retryDelays: readonly number[];\n /** Keeps admitted senders' registry entries alive across the session (see sender-keeper.ts). */\n readonly senders: SenderContextKeeper;\n private readonly turnTimeoutMs: number;\n private readonly outbound: OutboundDeps;\n private stopped = false;\n private warnedNoRoute = false;\n\n constructor(private readonly deps: PipelineDeps) {\n this.now = deps.now ?? Date.now;\n this.sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));\n this.loopCap = deps.loopCap ?? DEFAULT_LOOP_CAP;\n this.retryDelays = deps.retryDelaysMs ?? RESOLVE_RETRY_DELAYS_MS;\n this.senders = new SenderContextKeeper({\n identity: deps.identity,\n agentId: deps.agentId,\n log: deps.log,\n now: this.now,\n ...deps.senderKeeper,\n });\n this.turnTimeoutMs = deps.turnTimeoutMs ?? DEFAULT_TURN_TIMEOUT_MS;\n this.outbound = {\n driver: deps.driver,\n state: deps.state,\n ownAddress: deps.ownAddress,\n log: deps.log,\n now: this.now,\n sleep: this.sleep,\n loopCap: this.loopCap,\n };\n }\n\n /** A message a sync round surfaced. */\n async processSynced(message: SyncedMessage): Promise<ProcessOutcome> {\n const { state } = this.deps;\n if (state.hasSeenId(message.id) || state.hasRetry(message.id)) return 'skipped';\n return await this.admit(message, { retry: undefined, recovering: false });\n }\n\n /** A due retry-queue entry. */\n async processRetry(entry: RetryEntry): Promise<ProcessOutcome> {\n // A recovered turn that hit an identity outage keeps its recovery status:\n // it is already in `seen`, and only admission decides it again.\n return await this.admit(entry, { retry: entry, recovering: entry.recovering === true });\n }\n\n /** A turn that was admitted but had not finished when the process stopped. */\n async processPending(entry: PendingEntry): Promise<ProcessOutcome> {\n return await this.admit(entry, { retry: undefined, recovering: true });\n }\n\n /** Out-of-turn send to `thread:<threadHash>`. */\n async sendToThread(to: string, text: string): Promise<SendOutcome> {\n try {\n const outcome = await sendToThread(this.outbound, to, text);\n if (outcome.kind === 'sent') this.deps.onSent?.();\n return outcome;\n } catch (err) {\n if (isReconnectError(err)) this.deps.onReconnectRequired?.(err);\n throw err;\n }\n }\n\n /** Stop starting new turns. Running turns finish; unfinished ones stay pending. */\n stop(): void {\n this.stopped = true;\n }\n\n /** After the drain: stop sender-context heartbeats and delete this plugin's entries. */\n close(): void {\n this.stopped = true;\n this.senders.stop();\n }\n\n /** Resolves when every queued turn has settled. */\n async idle(): Promise<void> {\n while (this.chains.size > 0) {\n await Promise.all([...this.chains.values()]);\n }\n }\n\n private async admit(\n message: SyncedMessage,\n ctx: { retry: RetryEntry | undefined; recovering: boolean },\n ): Promise<ProcessOutcome> {\n const { state, driver, log } = this.deps;\n\n let email: NormalizedEmail;\n let raw: Uint8Array;\n try {\n raw = await driver.fetchRaw(message.id);\n email = await parseEmail(raw, { id: message.id, threadId: message.threadId });\n } catch (err) {\n if (isUnprocessable(err)) {\n log.warn(`Email skipped: the message cannot be processed (${errorName(err)})`);\n this.finishWithoutTurn(message.id, '');\n await state.save();\n return 'dropped';\n }\n throw err;\n }\n\n const mid = email.messageId;\n const inboundKey = replyKeyOf(email);\n if (!ctx.recovering && state.hasSeenMessageId(mid)) {\n this.finishWithoutTurn(message.id, mid);\n await state.save();\n return 'skipped';\n }\n\n const ignoreReason = shouldIgnore(email, this.deps.ownAddress);\n if (ignoreReason !== null) {\n log.debug(`Email ignored (${ignoreReason})`);\n this.finishWithoutTurn(message.id, mid);\n await state.save();\n return 'ignored';\n }\n\n const thread = threadHash(email.threadId);\n if (state.repliesInLastHour(thread, this.now()) >= this.loopCap) {\n log.warn(`Email ignored: thread reached the ${String(this.loopCap)}-replies-per-hour loop cap`);\n this.finishWithoutTurn(message.id, mid);\n await state.save();\n return 'ignored';\n }\n\n const from = senderAddressOf(email);\n const fromDomain = from.slice(from.lastIndexOf('@') + 1);\n const verdictOrPromise = this.deps.evaluateSender\n ? this.deps.evaluateSender(email, fromDomain, driver.trust, this.deps.ownAddress)\n // Async (the Gmail ARC check does DNS). Never throws: DNS failure is unverified.\n : evaluateAuth({ headerList: email.headerList, raw }, fromDomain, driver.trust, this.deps.ownAddress, {\n // Gmail internalDate: the ARC replay check needs it (absent → unverified).\n receivedAt: driver.receivedAt?.(message.id),\n });\n const verdict = await Promise.resolve(verdictOrPromise);\n\n let resolved: Awaited<ReturnType<IdentityResolver['resolveIdentity']>>;\n try {\n resolved = await this.deps.identity.resolveIdentity({\n provider: 'email',\n platformId: `email:${from}`,\n email: from,\n emailVerified: verdict.verified,\n });\n if (resolved.permissionsUnavailable === true) throw new Error('permissions unavailable');\n } catch (err) {\n return await this.resolveFailed(message, ctx, err);\n }\n state.removeRetry(message.id);\n\n if (!allowsAgentChat(resolved.permissions, this.deps.agentId)) {\n log.info(`Email from ${maskAddress(from)} not admitted (no agent:chat; auth ${verdict.reason})`);\n this.finishWithoutTurn(message.id, mid);\n await state.save();\n return 'denied';\n }\n\n // Admission was re-checked above, so a revoked grant is not re-run.\n if (ctx.recovering && state.isReplied(inboundKey)) {\n // The reply went out before the restart; only the bookkeeping is left.\n await this.markHandled(email.id);\n this.finishWithoutTurn(message.id, mid);\n await state.save();\n return 'skipped';\n }\n\n const now = this.now();\n state.markSeen(message.id, mid);\n const parent = parentRefOf(email, verdict.verified, log);\n state.upsertThread(thread, { threadId: email.threadId, parent, lastActivityAt: now });\n state.addPending({ id: message.id, threadId: message.threadId });\n state.lastMessageAt = now;\n await state.save();\n\n this.enqueue(thread, () => this.runTurn(email, parent, thread, {\n identityId: resolved.identityId,\n status: resolved.status,\n permissions: resolved.permissions,\n }));\n return 'dispatched';\n }\n\n private async resolveFailed(\n message: SyncedMessage,\n ctx: { retry: RetryEntry | undefined; recovering: boolean },\n err: unknown,\n ): Promise<ProcessOutcome> {\n const { state, log } = this.deps;\n const attempts = ctx.retry === undefined ? 0 : ctx.retry.attempts + 1;\n if (attempts >= this.retryDelays.length) {\n log.error(`Email dropped: sender resolve failed after ${String(attempts)} retries (${errorMessage(err)})`);\n this.finishWithoutTurn(message.id, '');\n await state.save();\n return 'dropped';\n }\n log.warn(`Email sender resolve failed; retry ${String(attempts + 1)} queued (${errorMessage(err)})`);\n state.putRetry({\n id: message.id,\n threadId: message.threadId,\n attempts,\n nextAt: this.now() + (this.retryDelays[attempts] ?? 0),\n ...(ctx.recovering ? { recovering: true } : {}),\n });\n await state.save();\n return 'queued';\n }\n\n private finishWithoutTurn(id: string, mid: string): void {\n const { state } = this.deps;\n if (mid !== '' || !state.hasSeenId(id)) state.markSeen(id, mid);\n state.removeRetry(id);\n state.removePending(id);\n }\n\n private enqueue(thread: string, task: () => Promise<void>): void {\n const previous = this.chains.get(thread) ?? Promise.resolve();\n const next = previous.then(task).catch((err: unknown) => {\n this.deps.log.error(`Email turn failed: ${errorMessage(err)}`);\n });\n this.chains.set(thread, next);\n void next.finally(() => {\n if (this.chains.get(thread) === next) this.chains.delete(thread);\n });\n }\n\n private async runTurn(\n email: NormalizedEmail,\n parent: ParentRef,\n thread: string,\n sender: Omit<SenderContext, 'expiresAt'>,\n ): Promise<void> {\n if (this.stopped) return; // stays pending; re-run after restart\n const { runtime, state, log, ownAddress } = this.deps;\n const cfg = runtime.readConfig();\n const peer = { kind: 'direct' as const, id: peerIdFor(ownAddress, email.threadId) };\n const from = senderAddressOf(email);\n const senderId = `email:${from}`;\n\n const sessionKey = this.resolveSessionKey(cfg, peer);\n // Written before dispatch and kept after it, so later no-requester runs in\n // this session still see the verified sender (sender-keeper.ts).\n if (sessionKey) this.senders.admit(sessionKey, senderId, { from, verified: parent.verified, context: sender });\n\n const buffer = new TurnBuffer();\n let failed = false;\n let timedOut = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n // A hung turn must not hold the sender context (and the thread) forever.\n const timeout = new Promise<'timeout'>((resolve) => {\n timer = setTimeout(() => { resolve('timeout'); }, this.turnTimeoutMs);\n });\n try {\n const dispatched = runtime.dispatch({\n cfg,\n runtime: { channel: runtime.channelRuntime },\n channel: CHANNEL,\n channelLabel: 'Email',\n accountId: ACCOUNT_ID,\n peer,\n senderId,\n senderAddress: senderId,\n recipientAddress: `email:${ownAddress.toLowerCase()}`,\n conversationLabel: `[Email] ${from}`,\n rawBody: renderBody(email),\n messageId: email.messageId || email.id,\n timestamp: this.now(),\n provider: CHANNEL,\n originatingTo: threadTarget(thread),\n deliver: (payload) => {\n buffer.push(payload);\n return Promise.resolve();\n },\n onRecordError: (err) => {\n failed = true;\n log.error(`Email session record error: ${errorMessage(err)}`);\n },\n onDispatchError: (err, info) => {\n failed = true;\n log.error(`Email dispatch error (${info.kind}): ${errorMessage(err)}`);\n },\n });\n if (await Promise.race([dispatched, timeout]) === 'timeout') {\n failed = true;\n timedOut = true;\n log.error(`Email turn exceeded ${String(this.turnTimeoutMs)}ms; releasing the thread without a reply`);\n void dispatched.catch(() => undefined);\n }\n } catch (err) {\n failed = true;\n log.error(`Email dispatch failed: ${errorMessage(err)}`);\n } finally {\n clearTimeout(timer);\n }\n\n try {\n // A timed-out turn may still be producing output; never send a partial reply.\n const markdown = timedOut ? '' : buffer.text();\n const outcome = await sendReply(this.outbound, {\n threadHash: thread,\n parent,\n markdown,\n inboundKey: replyKeyOf(email),\n });\n if (outcome.kind === 'sent') this.deps.onSent?.();\n } catch (err) {\n // Unsent (or possibly unsent) reply: leave the message unread.\n failed = true;\n log.error(`Email reply could not be sent: ${errorMessage(err)}`);\n if (isReconnectError(err)) this.deps.onReconnectRequired?.(err);\n }\n\n // A failed turn is left unread so the mailbox shows it was not answered.\n if (!failed) await this.markHandled(email.id);\n state.removePending(email.id);\n await state.save().catch((err: unknown) => {\n log.error(`Email state save failed: ${errorMessage(err)}`);\n });\n }\n\n private resolveSessionKey(cfg: Record<string, unknown>, peer: { kind: 'direct'; id: string }): string | null {\n const { runtime, log } = this.deps;\n try {\n const session = cfg.session as { store?: unknown } | undefined;\n const resolved = runtime.resolveRoute?.({\n cfg,\n channel: CHANNEL,\n accountId: ACCOUNT_ID,\n peer,\n runtime: runtime.channelRuntime,\n sessionStore: session?.store,\n });\n const key = resolved?.route.sessionKey;\n if (typeof key === 'string' && key !== '') return key;\n } catch (err) {\n log.warn(`Email session route could not be resolved (${errorMessage(err)})`);\n }\n if (!this.warnedNoRoute) {\n this.warnedNoRoute = true;\n log.warn('Email sender context unavailable: the identity plugin will resolve senders without email verification');\n }\n return null;\n }\n\n private async markHandled(id: string): Promise<void> {\n try {\n await this.deps.driver.markHandled(id);\n } catch (err) {\n this.deps.log.warn(`Email could not be marked handled: ${errorMessage(err)}`);\n }\n }\n}\n\n/** Untrusted content, bounded before it reaches the model. */\nfunction renderBody(email: NormalizedEmail): string {\n const subject = stripControl(email.subject).slice(0, 500);\n let body = email.textBody.length > MAX_BODY_CHARS\n ? `${email.textBody.slice(0, MAX_BODY_CHARS)}\\n\\n[message truncated]`\n : email.textBody;\n const attachments = email.attachments.slice(0, MAX_ATTACHMENTS_LISTED)\n .map((a) => `${stripControl(a.filename).slice(0, 200) || 'unnamed'} (${stripControl(a.contentType).slice(0, 100)}, ${String(a.size)} bytes)`);\n if (attachments.length > 0) {\n const more = email.attachments.length - attachments.length;\n body += `\\n\\n[Attachments, content not included: ${attachments.join(', ')}${more > 0 ? `, and ${String(more)} more` : ''}]`;\n }\n return `Subject: ${subject}\\n\\n${body}`;\n}\n\nfunction stripControl(value: string): string {\n // eslint-disable-next-line no-control-regex -- removing control characters from untrusted header text\n return value.replace(/[\\u0000-\\u001f\\u007f]/g, ' ').trim();\n}\n\n/** Permanent per-message failures: skip the message rather than block the mailbox. */\nfunction isUnprocessable(err: unknown): boolean {\n if (err instanceof MessageTooLargeError) return true;\n const status = (err as { status?: unknown } | null)?.status;\n return status === 404;\n}\n\nfunction errorName(err: unknown): string {\n return err instanceof Error ? err.name : 'unknown';\n}\n\n/** The reply idempotency key: the inbound Message-ID, else the provider id. */\nfunction replyKeyOf(email: NormalizedEmail): string {\n return email.messageId || `id:${email.id}`;\n}\n\n/** Lowercased From address with control characters removed (it reaches ctx and logs). */\nfunction senderAddressOf(email: NormalizedEmail): string {\n return stripControl(email.from.address).replace(/\\s+/g, '').toLowerCase();\n}\n","/**\n * The mailbox poller: one per configured mailbox (v1: one per agent).\n *\n * Adaptive interval, as in the Google Chat poller:\n * active (mail in the last 5 min) → 1 s\n * warm (mail in the last 30 min) → 5 s\n * idle → 15 s\n * dormant (2 h or more without mail) → 30 s\n *\n * The first run seeds the cursor (`seedCursor()`), so historic mail is never\n * answered. An expired cursor reseeds with a warning; nothing is replayed.\n * The cursor advances only after every message of a sync round has been\n * decided (admitted, queued for retry, or skipped), so a transient failure\n * re-reads the round and dedupe absorbs the repeats.\n *\n * Revoked consent, a missing scope, or an unusable Connection puts the\n * channel in the `error` state with reason `reconnect`; the poller keeps the\n * cursor and retries slowly, resuming where it stopped once the user\n * reconnects.\n */\nimport { MailboxRateLimitError, type MailboxDriver } from '@alfe.ai/email';\nimport { isAgentAuthError, isReconnectError, reconnectMessage } from './errors.js';\nimport { errorMessage } from './outbound.js';\nimport type { EmailPipeline } from './pipeline.js';\nimport type { StateStore } from './state.js';\nimport type { ChannelStatus } from './status.js';\nimport type { Logger } from './types.js';\n\nexport const INTERVALS_MS = { active: 1_000, warm: 5_000, idle: 15_000, dormant: 30_000 } as const;\nconst ACTIVE_WINDOW_MS = 5 * 60_000;\nconst WARM_WINDOW_MS = 30 * 60_000;\nconst DORMANT_AFTER_MS = 2 * 60 * 60_000;\nconst RECONNECT_BACKOFF_MS = 5 * 60_000;\nconst MAX_ERROR_BACKOFF_MS = 60_000;\n/** A message that fails this many rounds in a row is skipped so it cannot block the mailbox. */\nconst MAX_MESSAGE_FAILURES = 3;\n\nexport function computeInterval(lastMessageAt: number, now: number): number {\n if (lastMessageAt <= 0) return INTERVALS_MS.idle;\n const elapsed = now - lastMessageAt;\n if (elapsed < ACTIVE_WINDOW_MS) return INTERVALS_MS.active;\n if (elapsed < WARM_WINDOW_MS) return INTERVALS_MS.warm;\n if (elapsed < DORMANT_AFTER_MS) return INTERVALS_MS.idle;\n return INTERVALS_MS.dormant;\n}\n\n/** Everything a poll round needs once credentials and the mailbox are known. */\nexport interface MailboxSession {\n driver: MailboxDriver;\n state: StateStore;\n pipeline: EmailPipeline;\n}\n\nexport interface PollerDeps {\n /** Build the session (credentials, whoami, state load). Retried until it succeeds. */\n bootstrap: () => Promise<MailboxSession>;\n status: ChannelStatus;\n log: Logger;\n now?: () => number;\n /** How long stop() waits for running turns to finish. */\n drainTimeoutMs?: number;\n}\n\nexport const DEFAULT_DRAIN_TIMEOUT_MS = 30_000;\n\nexport class EmailPoller {\n private session: MailboxSession | null = null;\n private recovering: Set<string> | null = null;\n private timer: ReturnType<typeof setTimeout> | null = null;\n private running = false;\n private inflight: Promise<void> | null = null;\n private errorStreak = 0;\n private readonly failures = new Map<string, number>();\n private readonly now: () => number;\n\n constructor(private readonly deps: PollerDeps) {\n this.now = deps.now ?? Date.now;\n }\n\n start(): void {\n if (this.running) return;\n this.running = true;\n this.schedule(0);\n }\n\n async stop(): Promise<void> {\n this.running = false;\n if (this.timer) clearTimeout(this.timer);\n this.timer = null;\n await this.inflight?.catch(() => undefined);\n const session = this.session;\n session?.pipeline.stop();\n // Let running turns reply and record before the store is released, so a\n // fast stop/start cannot re-run (and re-answer) a turn that was finishing.\n if (session) await this.drain(session);\n session?.pipeline.close();\n await session?.state.save().catch((err: unknown) => {\n this.deps.log.error(`Email state save failed on stop: ${errorMessage(err)}`);\n });\n this.deps.status.stopped();\n }\n\n private async drain(session: MailboxSession): Promise<void> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const limit = this.deps.drainTimeoutMs ?? DEFAULT_DRAIN_TIMEOUT_MS;\n const timedOut = new Promise<'timeout'>((resolve) => { timer = setTimeout(() => { resolve('timeout'); }, limit); });\n const result = await Promise.race([session.pipeline.idle().then(() => 'idle' as const), timedOut]);\n clearTimeout(timer);\n if (result === 'timeout') {\n this.deps.log.warn(`Email stop: turns still running after ${String(limit)}ms; they stay pending and re-run after restart`);\n }\n }\n\n /** The live session, for out-of-turn sends. */\n current(): MailboxSession | null {\n return this.session;\n }\n\n /**\n * One poll round. Returns the delay before the next round. Exposed for tests;\n * the timer loop calls it.\n */\n async cycle(): Promise<number> {\n try {\n const delay = await this.round();\n this.errorStreak = 0;\n this.deps.status.ok();\n return delay;\n } catch (err) {\n return this.onError(err);\n }\n }\n\n private schedule(delay: number): void {\n if (!this.running) return;\n this.timer = setTimeout(() => {\n this.timer = null;\n this.inflight = this.cycle().then((next) => {\n this.inflight = null;\n this.schedule(next);\n });\n }, delay);\n }\n\n private async round(): Promise<number> {\n if (!this.session) {\n this.session = await this.deps.bootstrap();\n this.recovering = new Set(this.session.state.pendingEntries().map((entry) => entry.id));\n }\n const { driver, state, pipeline } = this.session;\n\n if (state.cursor === null) {\n state.cursor = await driver.seedCursor();\n await state.save();\n this.deps.log.info('Email mailbox seeded; only mail arriving from now on will be answered');\n return this.interval(state);\n }\n\n await this.recoverPending();\n await this.processDueRetries();\n\n const result = await driver.sync(state.cursor);\n if (result.kind === 'cursor-expired') {\n this.deps.log.warn('Email sync cursor expired; reseeding (mail received while offline beyond the provider window is not answered)');\n state.cursor = await driver.seedCursor();\n await state.save();\n return this.interval(state);\n }\n\n for (const message of result.messages) {\n await this.guarded(message.id, () => pipeline.processSynced(message), () => {\n state.markSeen(message.id, '');\n });\n }\n\n state.cursor = result.nextCursor;\n await state.save();\n if (result.messages.length > 0) this.deps.status.messageSeen(this.now());\n return this.interval(state);\n }\n\n private async recoverPending(): Promise<void> {\n const session = this.session;\n if (!session || !this.recovering || this.recovering.size === 0) return;\n const { state, pipeline } = session;\n for (const entry of [...state.pendingEntries()]) {\n if (!this.recovering.has(entry.id)) continue;\n await this.guarded(entry.id, () => pipeline.processPending(entry), () => {\n state.removePending(entry.id);\n state.removeRetry(entry.id);\n state.markSeen(entry.id, '');\n });\n this.recovering.delete(entry.id);\n }\n this.recovering.clear();\n }\n\n private async processDueRetries(): Promise<void> {\n const session = this.session;\n if (!session) return;\n const now = this.now();\n const { state, pipeline } = session;\n for (const entry of [...state.retryEntries()]) {\n if (entry.nextAt > now) continue;\n await this.guarded(entry.id, () => pipeline.processRetry(entry), () => {\n state.removeRetry(entry.id);\n state.removePending(entry.id);\n state.markSeen(entry.id, '');\n });\n }\n }\n\n /**\n * Run one message's step. Throttling and credential problems are not the\n * message's fault and fail the round as-is. Any other error fails the round\n * too (so it is retried in order), but after {@link MAX_MESSAGE_FAILURES}\n * consecutive failures the message is given up so it cannot block the mailbox.\n */\n private async guarded(id: string, step: () => Promise<unknown>, giveUp: () => void): Promise<void> {\n try {\n await step();\n this.failures.delete(id);\n } catch (err) {\n if (isReconnectError(err) || isAgentAuthError(err) || err instanceof MailboxRateLimitError) throw err;\n const count = (this.failures.get(id) ?? 0) + 1;\n if (count < MAX_MESSAGE_FAILURES) {\n this.failures.set(id, count);\n throw err;\n }\n this.failures.delete(id);\n this.deps.log.error(`Email skipped after ${String(count)} failed attempts: ${errorMessage(err)}`);\n giveUp();\n await this.session?.state.save();\n }\n }\n\n private interval(state: StateStore): number {\n return computeInterval(state.lastMessageAt, this.now());\n }\n\n private onError(err: unknown): number {\n if (isReconnectError(err)) {\n this.deps.status.error('reconnect', reconnectMessage(err));\n return RECONNECT_BACKOFF_MS;\n }\n if (isAgentAuthError(err)) {\n this.deps.status.error('agent-auth', \"The Agent API rejected this agent's token; check the agent's Alfe API key\");\n return RECONNECT_BACKOFF_MS;\n }\n this.errorStreak += 1;\n const backoff = Math.min(2_000 * 2 ** (this.errorStreak - 1), MAX_ERROR_BACKOFF_MS);\n const retryAfter = (err as { retryAfterMs?: unknown } | null)?.retryAfterMs;\n const delay = typeof retryAfter === 'number' && Number.isFinite(retryAfter) && retryAfter > 0\n ? Math.max(backoff, Math.min(retryAfter, 15 * 60_000))\n : backoff;\n this.deps.log.warn(`Email poll failed; retrying in ${String(delay)}ms (${errorMessage(err)})`);\n return delay;\n }\n}\n","/**\n * Read the live OpenClaw config through the plugin runtime.\n *\n * OpenClaw 2026.9.x replaced `runtime.config.loadConfig()` with\n * `runtime.config.current()`; 2026.6.11 exposes only `loadConfig()`. On 9.5\n * `current()` returns the process-wide pinned config object BY REFERENCE (not a\n * copy, not frozen): treat the result as read-only, mutating it corrupts config\n * for every plugin in the process. Both verified on the dists (2026-09-21). A managed\n * fleet is mixed across a runtime upgrade, so support both shapes.\n *\n * Kept local rather than in `@alfe.ai/openclaw-plugin-kit`: a kit release fans\n * out to every plugin package and its manifest pin.\n */\nexport interface RuntimeConfigApi {\n current?: () => unknown;\n loadConfig?: () => Record<string, unknown>;\n}\n\nexport function readRuntimeConfig(config: RuntimeConfigApi | undefined): Record<string, unknown> {\n if (typeof config?.current === 'function') {\n return (config.current() ?? {}) as Record<string, unknown>;\n }\n if (typeof config?.loadConfig === 'function') {\n return config.loadConfig();\n }\n throw new Error('OpenClaw plugin runtime exposes neither config.current() nor config.loadConfig()');\n}\n","/**\n * Channel health, reported through the registered channel's\n * `describeAccount` snapshot (`statusState` / `stateReason` / `lastError`).\n *\n * There is no agent-side endpoint for channel status (openclaw-mobile and\n * openclaw-teams have none either), so v1 logs the transition and exposes it\n * in OpenClaw's own channel status; it does not call back to the cloud.\n */\nimport type { Logger } from './types.js';\n\nexport type ChannelState = 'starting' | 'ok' | 'error' | 'stopped';\n/**\n * `reconnect`: the user must reconnect the mailbox (revoked consent, missing scope).\n * `agent-auth`: the Agent API rejected this agent's own token.\n */\nexport type ChannelErrorReason = 'reconnect' | 'agent-auth' | 'config' | 'unsupported-runtime';\n\n/** Which path raised the error; only a success on the same path clears it. */\nexport type ErrorSource = 'poll' | 'send';\n\nexport interface ChannelStatusSnapshot {\n state: ChannelState;\n reason?: ChannelErrorReason;\n message?: string;\n source?: ErrorSource;\n lastMessageAt: number | null;\n}\n\nexport class ChannelStatus {\n private current: ChannelStatusSnapshot = { state: 'starting', lastMessageAt: null };\n\n constructor(private readonly log: Logger) {}\n\n snapshot(): ChannelStatusSnapshot {\n return { ...this.current };\n }\n\n /**\n * A successful poll must not hide a send-side error (an Outlook token that\n * reads mail but lacks Mail.Send), so each path clears only its own error.\n */\n ok(source: ErrorSource = 'poll'): void {\n if (this.current.state === 'error') {\n if (this.current.source !== source) return;\n this.log.info('Email channel recovered');\n }\n this.current = { state: 'ok', lastMessageAt: this.current.lastMessageAt };\n }\n\n error(reason: ChannelErrorReason, message: string, source: ErrorSource = 'poll'): void {\n const changed = this.current.state !== 'error' || this.current.reason !== reason || this.current.message !== message;\n this.current = { state: 'error', reason, message, source, lastMessageAt: this.current.lastMessageAt };\n if (changed) this.log.error(`Email channel error (${reason}): ${message}`);\n }\n\n stopped(): void {\n this.current = { state: 'stopped', lastMessageAt: this.current.lastMessageAt };\n }\n\n messageSeen(at: number): void {\n this.current.lastMessageAt = at;\n }\n}\n","/**\n * @alfe.ai/openclaw-email — chat with the agent through its connected Gmail\n * or Outlook mailbox.\n *\n * mailbox (poll 1-30 s via @alfe.ai/email drivers)\n * → EmailPoller → EmailPipeline (dedupe, filters, resolve, agent:chat admission)\n * → dispatchInboundDirectDmWithRuntime() per thread, serialised\n * ← deliver() payloads buffered → one reply-all email per turn\n * registered `email` channel → out-of-turn sends to `thread:<threadHash>`\n *\n * Requires OpenClaw >= 2026.9.5 (`package.json#openclaw.install.minHostVersion`\n * plus a runtime check): the per-sender tool gate in\n * `@alfe.ai/openclaw-identity` relies on 9.5's `before_tool_call`\n * `ctx.requester`.\n *\n * Config (`plugins.entries.<id>.config`, projected from the agent's Connect\n * `email` Channel by desired state): `{ channelId, connectionId }`.\n */\n\nimport { createRequire } from 'node:module';\nimport { AgentApiClient } from '@alfe.ai/agent-api-client';\nimport { resolveConfig } from '@alfe.ai/config';\nimport { GmailDriver, GraphDriver, type MailboxDriver } from '@alfe.ai/email';\nimport {\n getActivationKey,\n guardedStart,\n resetActivation,\n resolveOpenClawSdk,\n} from '@alfe.ai/openclaw-plugin-kit';\nimport { createEmailChannelPlugin, type EmailChannelHandle } from './channel.js';\nimport { ConnectionCredentials } from './credentials.js';\nimport { reconnectMessage } from './errors.js';\nimport { isHostAtLeast, MIN_HOST_VERSION } from './host-version.js';\nimport { EmailPipeline } from './pipeline.js';\nimport { EmailPoller, type MailboxSession } from './poller.js';\nimport { readRuntimeConfig, type RuntimeConfigApi } from './runtime-config.js';\nimport { stateFilePath, StateStore } from './state.js';\nimport { ChannelStatus } from './status.js';\nimport type { DispatchInboundFn, Logger, ResolveRouteFn } from './types.js';\n\nconst require = createRequire(import.meta.url);\nconst pkg = require('../package.json') as { version: string };\n\nexport const PLUGIN_ID = '@alfe.ai/openclaw-email';\n\n// ── OpenClaw plugin API (inline subset) ─────────────────────\n\ninterface PluginRuntime {\n version?: unknown;\n config: RuntimeConfigApi;\n channel: unknown;\n state?: { resolveStateDir?: () => string };\n}\n\ninterface PluginServiceContext {\n stateDir?: string;\n logger?: Logger;\n}\n\nexport interface PluginApi {\n logger: Logger;\n registrationMode?: 'full' | 'setup-only' | 'setup-runtime' | 'cli-metadata';\n pluginConfig?: unknown;\n runtime?: PluginRuntime;\n registerChannel(channel: ReturnType<typeof createEmailChannelPlugin>): void;\n registerService(service: {\n id: string;\n start: (ctx: PluginServiceContext) => void | Promise<void>;\n stop?: (ctx: PluginServiceContext) => void | Promise<void>;\n }): void;\n}\n\nexport interface EmailPluginConfig {\n channelId: string;\n connectionId: string;\n}\n\nexport function parsePluginConfig(value: unknown): EmailPluginConfig | null {\n if (typeof value !== 'object' || value === null) return null;\n const { channelId, connectionId } = value as Record<string, unknown>;\n if (typeof channelId !== 'string' || channelId.trim() === '') return null;\n if (typeof connectionId !== 'string' || connectionId.trim() === '') return null;\n return { channelId: channelId.trim(), connectionId: connectionId.trim() };\n}\n\n// ── Process-wide state ──────────────────────────────────────\n// On globalThis so the OpenClaw 9.x double plugin load shares ONE poller and\n// both module instances' channels and stop paths see it.\n\nconst ACTIVATION_KEY = getActivationKey('email');\nconst RUNTIME_KEY = Symbol.for('alfe.openclaw-email.runtime.v1');\n\ninterface RuntimeHandle {\n poller: EmailPoller | null;\n status: ChannelStatus | null;\n}\n\nfunction runtimeHandle(): RuntimeHandle {\n const g = globalThis as Record<symbol, unknown>;\n return (g[RUNTIME_KEY] ??= { poller: null, status: null }) as RuntimeHandle;\n}\n\nfunction channelHandle(): EmailChannelHandle | null {\n const handle = runtimeHandle();\n const { poller, status } = handle;\n if (!poller || !status) return null;\n return {\n status: () => status.snapshot(),\n hasThread: (hash) => poller.current()?.state.getThread(hash) !== undefined,\n sendToThread: async (to, text) => {\n const session = poller.current();\n if (!session) throw new Error('Email channel is still starting; retry shortly');\n return await session.pipeline.sendToThread(to, text);\n },\n };\n}\n\nasync function stopRunning(log: Logger): Promise<void> {\n // Stop side effects BEFORE resetting the activation flag.\n const handle = runtimeHandle();\n const poller = handle.poller;\n handle.poller = null;\n await poller?.stop();\n resetActivation(ACTIVATION_KEY);\n log.info('Email channel stopped');\n}\n\n// ── Plugin ──────────────────────────────────────────────────\n\nconst plugin = {\n id: PLUGIN_ID,\n name: 'Email',\n description: \"Email channel via the agent's connected Gmail or Outlook mailbox\",\n version: pkg.version,\n\n activate(api: PluginApi) {\n const log = api.logger;\n const mode = api.registrationMode;\n\n if (mode !== undefined && mode !== 'full') {\n // Metadata / setup passes: declare the channel, start nothing.\n api.registerChannel(createEmailChannelPlugin(() => null));\n return;\n }\n\n if (!isHostAtLeast(api.runtime?.version, MIN_HOST_VERSION)) {\n log.error(\n `Email channel requires OpenClaw >= ${MIN_HOST_VERSION} (host reports ${describeVersion(api.runtime?.version)}); not activating`,\n );\n return;\n }\n\n // Contributions re-register on every activate; only the poller is guarded.\n api.registerChannel(createEmailChannelPlugin(channelHandle));\n api.registerService({\n id: 'email-poller',\n start: (ctx) => { startService(api, ctx); },\n stop: async () => { await stopRunning(log); },\n });\n log.info('Email channel registered');\n },\n\n async deactivate(api: PluginApi) {\n await stopRunning(api.logger);\n },\n};\n\nfunction startService(api: PluginApi, ctx: PluginServiceContext): void {\n const log = api.logger;\n guardedStart(ACTIVATION_KEY, log, () => {\n const handle = runtimeHandle();\n const status = new ChannelStatus(log);\n handle.status = status;\n\n const config = parsePluginConfig(api.pluginConfig);\n if (!config) {\n status.error('config', 'Plugin config needs channelId and connectionId');\n resetActivation(ACTIVATION_KEY);\n return;\n }\n\n const runtime = api.runtime;\n const dispatch = resolveOpenClawSdk<DispatchInboundFn>(log, {\n unresolvableNote: 'OpenClaw SDK not resolvable; email dispatch is disabled',\n });\n const resolveRoute = resolveOpenClawSdk<ResolveRouteFn>(log, {\n specifier: 'openclaw/plugin-sdk/inbound-envelope',\n exportName: 'resolveInboundRouteEnvelopeBuilderWithRuntime',\n unresolvableNote: 'OpenClaw route resolver not resolvable; email sender context is disabled',\n });\n const stateDir = ctx.stateDir ?? runtime?.state?.resolveStateDir?.();\n if (!dispatch || !runtime || !stateDir) {\n log.error('Email channel runtime prerequisites unavailable; polling disabled');\n resetActivation(ACTIVATION_KEY);\n return;\n }\n\n const alfe = resolveConfig();\n const client = new AgentApiClient({ apiKey: alfe.apiKey, apiUrl: alfe.apiUrl });\n const credentials = new ConnectionCredentials(client, config.connectionId);\n\n const bootstrap = async (): Promise<MailboxSession> => {\n const described = await credentials.describe();\n const driver: MailboxDriver = described.provider === 'google'\n ? new GmailDriver({ getAccessToken: credentials.getAccessToken })\n : new GraphDriver({ getAccessToken: credentials.getAccessToken });\n const { address } = await driver.whoami();\n const mailbox = address.trim().toLowerCase();\n const { agentId } = await client.whoami();\n const state = new StateStore(stateFilePath(stateDir, mailbox), mailbox, log);\n await state.load();\n const pipeline = new EmailPipeline({\n driver,\n state,\n identity: client,\n agentId,\n ownAddress: mailbox,\n runtime: {\n dispatch,\n resolveRoute,\n readConfig: () => readRuntimeConfig(runtime.config),\n channelRuntime: runtime.channel,\n },\n log,\n onReconnectRequired: (err) => { status.error('reconnect', reconnectMessage(err), 'send'); },\n onSent: () => { status.ok('send'); },\n });\n log.info(`Email channel polling ${described.provider} mailbox for channel ${config.channelId}`);\n return { driver, state, pipeline };\n };\n\n const poller = new EmailPoller({ bootstrap, status, log });\n handle.poller = poller;\n poller.start();\n });\n}\n\nfunction describeVersion(value: unknown): string {\n return typeof value === 'string' ? value.slice(0, 64) : 'no version';\n}\n\nexport default { ...plugin, register: plugin.activate.bind(plugin) };\n"],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "@alfe.ai/openclaw-email",
|
|
3
|
+
"name": "Email",
|
|
4
|
+
"description": "Email channel: receive mail in the agent's connected Gmail or Outlook mailbox and reply in the same thread",
|
|
5
|
+
"entry": "./dist/plugin.js",
|
|
6
|
+
"activation": { "onStartup": true },
|
|
7
|
+
"configSchema": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"additionalProperties": false,
|
|
10
|
+
"required": ["channelId", "connectionId"],
|
|
11
|
+
"properties": {
|
|
12
|
+
"channelId": { "type": "string", "minLength": 1 },
|
|
13
|
+
"connectionId": { "type": "string", "minLength": 1 }
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"channels": ["email"],
|
|
17
|
+
"channelConfigs": {
|
|
18
|
+
"email": {
|
|
19
|
+
"schema": {
|
|
20
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
21
|
+
"type": "object",
|
|
22
|
+
"additionalProperties": false,
|
|
23
|
+
"properties": {
|
|
24
|
+
"name": { "type": "string" },
|
|
25
|
+
"enabled": { "type": "boolean" }
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"label": "Email",
|
|
29
|
+
"description": "Email threads in the agent's connected Gmail or Outlook mailbox"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/openclaw-email",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenClaw email channel plugin: chat with an Alfe agent through its connected Gmail or Outlook mailbox",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/plugin.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"require": "./dist/index.cjs",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./plugin": {
|
|
15
|
+
"types": "./dist/plugin.d.ts",
|
|
16
|
+
"require": "./dist/plugin.cjs",
|
|
17
|
+
"import": "./dist/plugin.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"openclaw": {
|
|
21
|
+
"extensions": [
|
|
22
|
+
"./dist/plugin.js"
|
|
23
|
+
],
|
|
24
|
+
"install": {
|
|
25
|
+
"minHostVersion": ">=2026.9.5"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"openclaw.plugin.json"
|
|
31
|
+
],
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@auriclabs/roles": "0.1.1",
|
|
34
|
+
"@alfe.ai/agent-api-client": "^0.20.0",
|
|
35
|
+
"@alfe.ai/config": "^0.4.1",
|
|
36
|
+
"@alfe.ai/email": "^0.1.0",
|
|
37
|
+
"@alfe.ai/openclaw-plugin-kit": "0.2.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"openclaw": ">=2026.3.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"openclaw": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"license": "UNLICENSED",
|
|
48
|
+
"homepage": "https://alfe.ai",
|
|
49
|
+
"author": "Alfe (https://alfe.ai)",
|
|
50
|
+
"keywords": [
|
|
51
|
+
"alfe",
|
|
52
|
+
"ai-agents",
|
|
53
|
+
"agent",
|
|
54
|
+
"email",
|
|
55
|
+
"gmail",
|
|
56
|
+
"outlook",
|
|
57
|
+
"openclaw",
|
|
58
|
+
"plugin"
|
|
59
|
+
],
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "tsdown",
|
|
62
|
+
"dev": "tsdown --watch",
|
|
63
|
+
"test": "vitest run",
|
|
64
|
+
"typecheck": "tsc --noEmit",
|
|
65
|
+
"lint": "eslint ."
|
|
66
|
+
}
|
|
6
67
|
}
|