diff --git a/extensions/agentmail/package.json b/extensions/agentmail/package.json index ba7cf13d8..69a53374d 100644 --- a/extensions/agentmail/package.json +++ b/extensions/agentmail/package.json @@ -13,8 +13,7 @@ "selectionLabel": "AgentMail (Email Inbox API)", "docsPath": "/channels/agentmail", "docsLabel": "agentmail", - "blurb": "Email channel via AgentMail; configure API key and inbox.", - "order": 0, + "blurb": "email channel via AgentMail; dedicated agent inbox API.", "quickstartAllowFrom": true }, "install": { diff --git a/extensions/agentmail/src/accounts.ts b/extensions/agentmail/src/accounts.ts index a70845b4a..c80f257bf 100644 --- a/extensions/agentmail/src/accounts.ts +++ b/extensions/agentmail/src/accounts.ts @@ -16,11 +16,10 @@ export function listAgentMailAccountIds(_cfg: CoreConfig): string[] { /** * Returns the default AgentMail account ID. + * Currently only supports a single default account. */ -export function resolveDefaultAgentMailAccountId(cfg: CoreConfig): string { - const ids = listAgentMailAccountIds(cfg); - if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID; - return ids[0] ?? DEFAULT_ACCOUNT_ID; +export function resolveDefaultAgentMailAccountId(_cfg: CoreConfig): string { + return DEFAULT_ACCOUNT_ID; } /** Resolved AgentMail credentials and paths. */ diff --git a/extensions/agentmail/src/channel.ts b/extensions/agentmail/src/channel.ts index 920a10844..69346b71e 100644 --- a/extensions/agentmail/src/channel.ts +++ b/extensions/agentmail/src/channel.ts @@ -23,12 +23,13 @@ import type { CoreConfig, ResolvedAgentMailAccount } from "./utils.js"; const meta = { id: "agentmail", - label: "AgentMail (Email Inbox API)", + label: "AgentMail", selectionLabel: "AgentMail (Email Inbox API)", + detailLabel: "AgentMail", docsPath: "/channels/agentmail", docsLabel: "agentmail", - blurb: "Email channel via AgentMail; configure token and email address.", - order: 80, + blurb: "email channel via AgentMail; dedicated agent inbox API.", + systemImage: "envelope", quickstartAllowFrom: true, }; diff --git a/extensions/agentmail/src/client.ts b/extensions/agentmail/src/client.ts index 9022b2d09..d08b5490e 100644 --- a/extensions/agentmail/src/client.ts +++ b/extensions/agentmail/src/client.ts @@ -5,15 +5,21 @@ import { getAgentMailRuntime } from "./runtime.js"; import type { CoreConfig } from "./utils.js"; let sharedClient: AgentMailClient | null = null; +let sharedClientKey: string | null = null; -/** Creates or returns a shared AgentMailClient instance. */ +/** Creates or returns a shared AgentMailClient instance. Recreates if key changed. */ export function getAgentMailClient(apiKey?: string): AgentMailClient { - if (sharedClient) return sharedClient; - const key = apiKey ?? getResolvedCredentials().apiKey; if (!key) throw new Error("AgentMail token is required"); + // Return cached client if key matches + if (sharedClient && sharedClientKey === key) { + return sharedClient; + } + + // Create new client (key changed or first call) sharedClient = new AgentMailClient({ apiKey: key }); + sharedClientKey = key; return sharedClient; } diff --git a/extensions/agentmail/src/filtering.ts b/extensions/agentmail/src/filtering.ts index b7119b56c..3b5b9bb3c 100644 --- a/extensions/agentmail/src/filtering.ts +++ b/extensions/agentmail/src/filtering.ts @@ -14,11 +14,8 @@ export function matchesList(senderEmail: string, list: string[]): boolean { return list.some((entry) => { const normalizedEntry = entry.toLowerCase().trim(); - return ( - normalizedEntry === normalizedSender || // exact email match - normalizedEntry === domain || // exact domain match - normalizedSender.endsWith(`@${normalizedEntry}`) // domain suffix match - ); + // Match exact email or domain + return normalizedEntry === normalizedSender || normalizedEntry === domain; }); } diff --git a/extensions/agentmail/src/monitor.ts b/extensions/agentmail/src/monitor.ts index dc119a311..f2122c1a5 100644 --- a/extensions/agentmail/src/monitor.ts +++ b/extensions/agentmail/src/monitor.ts @@ -44,6 +44,7 @@ function recordState( }); } +/** Returns runtime state for status checks. */ export function getAgentMailRuntimeState(accountId: string) { return runtimeState.get(`agentmail:${accountId}`); } @@ -158,8 +159,12 @@ export async function monitorAgentMailProvider( return sendJson(res, 200, { ok: true, filtered: true }); } - // Label message as allowed - await labelMessageAllowed(client, inboxId, message.messageId); + // Label message as allowed (best effort - don't fail if labeling fails) + try { + await labelMessageAllowed(client, inboxId, message.messageId); + } catch (labelErr) { + logVerbose(`agentmail: failed to label message: ${String(labelErr)}`); + } recordState(accountId, { lastInboundAt: Date.now() }); diff --git a/extensions/agentmail/src/thread.ts b/extensions/agentmail/src/thread.ts index 55308a8e7..30f37dff3 100644 --- a/extensions/agentmail/src/thread.ts +++ b/extensions/agentmail/src/thread.ts @@ -94,8 +94,8 @@ export async function fetchFormattedThread( const messages = thread.messages.map(formatMessage).join("\n\n"); return `${header}\n\n${messages}`; - } catch (err) { - console.error(`Failed to fetch thread: ${String(err)}`); + } catch { + // Caller handles fallback to webhook payload return ""; } } diff --git a/src/channels/dock.ts b/src/channels/dock.ts index 600499091..ff2bc41d1 100644 --- a/src/channels/dock.ts +++ b/src/channels/dock.ts @@ -213,6 +213,30 @@ const DOCKS: Record = { }), }, }, + agentmail: { + id: "agentmail", + capabilities: { + chatTypes: ["direct"], + media: true, + threads: true, + }, + outbound: { textChunkLimit: 4000 }, + config: { + resolveAllowFrom: ({ cfg }) => + ((cfg.channels?.agentmail as { allowFrom?: string[] } | undefined)?.allowFrom ?? []).map( + (entry) => String(entry), + ), + formatAllowFrom: ({ allowFrom }) => formatLower(allowFrom), + }, + threading: { + buildToolContext: ({ context, hasRepliedRef }) => ({ + currentChannelId: context.To?.trim() || undefined, + currentThreadTs: + context.MessageThreadId != null ? String(context.MessageThreadId) : undefined, + hasRepliedRef, + }), + }, + }, googlechat: { id: "googlechat", capabilities: {