Improvements
This commit is contained in:
parent
cabba2ef91
commit
5f4a7fd917
@ -8,8 +8,8 @@ import {
|
|||||||
type ChannelPlugin,
|
type ChannelPlugin,
|
||||||
} from "clawdbot/plugin-sdk";
|
} from "clawdbot/plugin-sdk";
|
||||||
|
|
||||||
import { resolveAgentMailAccount, resolveCredentials } from "./accounts.js";
|
import { resolveAgentMailAccount } from "./accounts.js";
|
||||||
import { getAgentMailClient } from "./client.js";
|
import { getAgentMailClient, getResolvedCredentials, NOT_CONFIGURED_ERROR } from "./client.js";
|
||||||
import { AgentMailConfigSchema } from "./config-schema.js";
|
import { AgentMailConfigSchema } from "./config-schema.js";
|
||||||
import { agentmailOnboardingAdapter } from "./onboarding.js";
|
import { agentmailOnboardingAdapter } from "./onboarding.js";
|
||||||
import { agentmailOutbound } from "./outbound.js";
|
import { agentmailOutbound } from "./outbound.js";
|
||||||
@ -70,13 +70,9 @@ export const agentmailPlugin: ChannelPlugin<ResolvedAgentMailAccount> = {
|
|||||||
emailAddress: account.inboxId,
|
emailAddress: account.inboxId,
|
||||||
}),
|
}),
|
||||||
resolveAllowFrom: ({ cfg }) =>
|
resolveAllowFrom: ({ cfg }) =>
|
||||||
((cfg as CoreConfig).channels?.agentmail?.allowFrom ?? []).map((entry) =>
|
((cfg as CoreConfig).channels?.agentmail?.allowFrom ?? []).map(String),
|
||||||
String(entry)
|
|
||||||
),
|
|
||||||
formatAllowFrom: ({ allowFrom }) =>
|
formatAllowFrom: ({ allowFrom }) =>
|
||||||
allowFrom
|
allowFrom.map((e) => String(e).toLowerCase().trim()).filter(Boolean),
|
||||||
.map((entry) => String(entry).toLowerCase().trim())
|
|
||||||
.filter(Boolean),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
security: {
|
security: {
|
||||||
@ -98,12 +94,7 @@ export const agentmailPlugin: ChannelPlugin<ResolvedAgentMailAccount> = {
|
|||||||
messaging: {
|
messaging: {
|
||||||
normalizeTarget: (raw) => raw.trim() || undefined,
|
normalizeTarget: (raw) => raw.trim() || undefined,
|
||||||
targetResolver: {
|
targetResolver: {
|
||||||
looksLikeId: (raw) => {
|
looksLikeId: (raw) => /\S+@\S+\.\S+/.test(raw.trim()),
|
||||||
const trimmed = raw.trim();
|
|
||||||
if (!trimmed) return false;
|
|
||||||
// Check if it looks like an email
|
|
||||||
return trimmed.includes("@") && trimmed.includes(".");
|
|
||||||
},
|
|
||||||
hint: "<email@example.com>",
|
hint: "<email@example.com>",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -164,28 +155,15 @@ export const agentmailPlugin: ChannelPlugin<ResolvedAgentMailAccount> = {
|
|||||||
probe: s.probe,
|
probe: s.probe,
|
||||||
lastProbeAt: s.lastProbeAt ?? null,
|
lastProbeAt: s.lastProbeAt ?? null,
|
||||||
}),
|
}),
|
||||||
probeAccount: async ({ cfg }) => {
|
probeAccount: async () => {
|
||||||
try {
|
try {
|
||||||
const { apiKey, inboxId } = resolveCredentials(cfg as CoreConfig);
|
const { apiKey, inboxId } = getResolvedCredentials();
|
||||||
if (!apiKey || !inboxId)
|
if (!apiKey || !inboxId) return { ok: false, error: NOT_CONFIGURED_ERROR, elapsedMs: 0 };
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
error: "Missing token or email address",
|
|
||||||
elapsedMs: 0,
|
|
||||||
};
|
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
const inbox = await getAgentMailClient(apiKey).inboxes.get(inboxId);
|
const inbox = await getAgentMailClient(apiKey).inboxes.get(inboxId);
|
||||||
return {
|
return { ok: true, elapsedMs: Date.now() - start, meta: { inboxId: inbox.inboxId } };
|
||||||
ok: true,
|
|
||||||
elapsedMs: Date.now() - start,
|
|
||||||
meta: { inboxId: inbox.inboxId },
|
|
||||||
};
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return {
|
return { ok: false, error: err instanceof Error ? err.message : String(err), elapsedMs: 0 };
|
||||||
ok: false,
|
|
||||||
error: err instanceof Error ? err.message : String(err),
|
|
||||||
elapsedMs: 0,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
buildAccountSnapshot: ({ account: a, runtime: r, probe }) => ({
|
buildAccountSnapshot: ({ account: a, runtime: r, probe }) => ({
|
||||||
|
|||||||
@ -4,34 +4,31 @@ import { resolveCredentials } from "./accounts.js";
|
|||||||
import { getAgentMailRuntime } from "./runtime.js";
|
import { getAgentMailRuntime } from "./runtime.js";
|
||||||
import type { CoreConfig } from "./utils.js";
|
import type { CoreConfig } from "./utils.js";
|
||||||
|
|
||||||
|
export const NOT_CONFIGURED_ERROR = "AgentMail not configured (missing token or email address)";
|
||||||
|
|
||||||
let sharedClient: AgentMailClient | null = null;
|
let sharedClient: AgentMailClient | null = null;
|
||||||
let sharedClientKey: string | null = null;
|
let sharedClientKey: string | null = null;
|
||||||
|
|
||||||
/** Creates or returns a shared AgentMailClient instance. Recreates if key changed. */
|
|
||||||
export function getAgentMailClient(apiKey?: string): AgentMailClient {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Resolves credentials from current config. */
|
/** Resolves credentials from current config. */
|
||||||
export function getResolvedCredentials() {
|
export function getResolvedCredentials() {
|
||||||
return resolveCredentials(getAgentMailRuntime().config.loadConfig() as CoreConfig);
|
return resolveCredentials(getAgentMailRuntime().config.loadConfig() as CoreConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Creates or returns a shared AgentMailClient instance. Recreates if key changed. */
|
||||||
|
export function getAgentMailClient(apiKey?: string): AgentMailClient {
|
||||||
|
const key = apiKey ?? getResolvedCredentials().apiKey;
|
||||||
|
if (!key) throw new Error("AgentMail token is required");
|
||||||
|
|
||||||
|
if (sharedClient && sharedClientKey === key) return sharedClient;
|
||||||
|
|
||||||
|
sharedClient = new AgentMailClient({ apiKey: key });
|
||||||
|
sharedClientKey = key;
|
||||||
|
return sharedClient;
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns client and inboxId, or throws if not configured. */
|
/** Returns client and inboxId, or throws if not configured. */
|
||||||
export function getClientAndInbox(): { client: AgentMailClient; inboxId: string } {
|
export function getClientAndInbox(): { client: AgentMailClient; inboxId: string } {
|
||||||
const { apiKey, inboxId } = getResolvedCredentials();
|
const { apiKey, inboxId } = getResolvedCredentials();
|
||||||
if (!apiKey || !inboxId) throw new Error("AgentMail not configured (missing token or email address)");
|
if (!apiKey || !inboxId) throw new Error(NOT_CONFIGURED_ERROR);
|
||||||
return { client: getAgentMailClient(apiKey), inboxId };
|
return { client: getAgentMailClient(apiKey), inboxId };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import type { AgentMail } from "agentmail";
|
import type { AgentMail } from "agentmail";
|
||||||
|
|
||||||
import { resolveCredentials } from "./accounts.js";
|
import { getAgentMailClient, getResolvedCredentials, NOT_CONFIGURED_ERROR } from "./client.js";
|
||||||
import { getAgentMailClient } from "./client.js";
|
|
||||||
import { getAgentMailRuntime } from "./runtime.js";
|
import { getAgentMailRuntime } from "./runtime.js";
|
||||||
import { checkSenderAllowed, labelMessageAllowed } from "./filtering.js";
|
import { checkSenderAllowed, labelMessageAllowed } from "./filtering.js";
|
||||||
import { extractMessageBody, fetchFormattedThread } from "./thread.js";
|
import { extractMessageBody, fetchFormattedThread } from "./thread.js";
|
||||||
@ -54,9 +53,9 @@ export async function monitorAgentMailProvider(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const accountId = opts.accountId ?? "default";
|
const accountId = opts.accountId ?? "default";
|
||||||
const { apiKey, inboxId } = resolveCredentials(cfg);
|
const { apiKey, inboxId } = getResolvedCredentials();
|
||||||
if (!apiKey || !inboxId) {
|
if (!apiKey || !inboxId) {
|
||||||
logger.warn("AgentMail not configured (missing token or email address)");
|
logger.warn(NOT_CONFIGURED_ERROR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
import { AgentMailClient } from "agentmail";
|
import { AgentMailClient } from "agentmail";
|
||||||
import type {
|
import type { ChannelOnboardingAdapter, MoltbotConfig } from "clawdbot/plugin-sdk";
|
||||||
ChannelOnboardingAdapter,
|
|
||||||
MoltbotConfig,
|
|
||||||
} from "clawdbot/plugin-sdk";
|
|
||||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "clawdbot/plugin-sdk";
|
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "clawdbot/plugin-sdk";
|
||||||
|
|
||||||
import { resolveAgentMailAccount, resolveCredentials } from "./accounts.js";
|
import { resolveAgentMailAccount } from "./accounts.js";
|
||||||
import type { AgentMailConfig, CoreConfig } from "./utils.js";
|
import type { AgentMailConfig, CoreConfig } from "./utils.js";
|
||||||
|
|
||||||
const channel = "agentmail" as const;
|
const channel = "agentmail" as const;
|
||||||
@ -55,14 +52,13 @@ export const agentmailOnboardingAdapter: ChannelOnboardingAdapter = {
|
|||||||
channel,
|
channel,
|
||||||
|
|
||||||
getStatus: async ({ cfg }) => {
|
getStatus: async ({ cfg }) => {
|
||||||
const { apiKey, inboxId } = resolveCredentials(cfg as CoreConfig);
|
const account = resolveAgentMailAccount({ cfg: cfg as CoreConfig });
|
||||||
const configured = Boolean(apiKey && inboxId);
|
|
||||||
return {
|
return {
|
||||||
channel,
|
channel,
|
||||||
configured,
|
configured: account.configured,
|
||||||
statusLines: [`AgentMail: ${configured ? `configured (${inboxId})` : "needs token"}`],
|
statusLines: [`AgentMail: ${account.configured ? `configured (${account.inboxId})` : "needs token"}`],
|
||||||
selectionHint: configured ? "configured" : "not configured",
|
selectionHint: account.configured ? "configured" : "not configured",
|
||||||
quickstartScore: configured ? 1 : 5,
|
quickstartScore: account.configured ? 1 : 5,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -73,9 +69,6 @@ export const agentmailOnboardingAdapter: ChannelOnboardingAdapter = {
|
|||||||
|
|
||||||
let next = cfg as MoltbotConfig;
|
let next = cfg as MoltbotConfig;
|
||||||
const account = resolveAgentMailAccount({ cfg: next as CoreConfig, accountId });
|
const account = resolveAgentMailAccount({ cfg: next as CoreConfig, accountId });
|
||||||
const { apiKey, inboxId } = resolveCredentials(next as CoreConfig);
|
|
||||||
const configured = Boolean(apiKey && inboxId);
|
|
||||||
|
|
||||||
const canUseEnv = accountId === DEFAULT_ACCOUNT_ID && Boolean(process.env.AGENTMAIL_TOKEN?.trim());
|
const canUseEnv = accountId === DEFAULT_ACCOUNT_ID && Boolean(process.env.AGENTMAIL_TOKEN?.trim());
|
||||||
|
|
||||||
// If env var token is available and not already configured, offer to use it
|
// If env var token is available and not already configured, offer to use it
|
||||||
@ -92,9 +85,9 @@ export const agentmailOnboardingAdapter: ChannelOnboardingAdapter = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If already configured, ask to keep
|
// If already configured, ask to keep
|
||||||
if (configured) {
|
if (account.configured) {
|
||||||
const keep = await prompter.confirm({
|
const keep = await prompter.confirm({
|
||||||
message: `AgentMail already configured (${inboxId}). Keep current settings?`,
|
message: `AgentMail already configured (${account.inboxId}). Keep current settings?`,
|
||||||
initialValue: true,
|
initialValue: true,
|
||||||
});
|
});
|
||||||
if (keep) return { cfg: next };
|
if (keep) return { cfg: next };
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type { ChannelOutboundAdapter } from "clawdbot/plugin-sdk";
|
|||||||
import { getClientAndInbox } from "./client.js";
|
import { getClientAndInbox } from "./client.js";
|
||||||
import { getAgentMailRuntime } from "./runtime.js";
|
import { getAgentMailRuntime } from "./runtime.js";
|
||||||
|
|
||||||
/** Sends a reply to an email message via AgentMail. */
|
/** Sends a reply-all to an email message via AgentMail. */
|
||||||
export async function sendAgentMailReply(params: {
|
export async function sendAgentMailReply(params: {
|
||||||
client: AgentMailClient;
|
client: AgentMailClient;
|
||||||
inboxId: string;
|
inboxId: string;
|
||||||
@ -12,24 +12,23 @@ export async function sendAgentMailReply(params: {
|
|||||||
text: string;
|
text: string;
|
||||||
html?: string;
|
html?: string;
|
||||||
}): Promise<{ messageId: string; threadId: string }> {
|
}): Promise<{ messageId: string; threadId: string }> {
|
||||||
const { client, inboxId, messageId, text, html } = params;
|
return params.client.inboxes.messages.replyAll(params.inboxId, params.messageId, {
|
||||||
return client.inboxes.messages.reply(inboxId, messageId, { text, html });
|
text: params.text,
|
||||||
|
html: params.html,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sends a message (reply or new) and returns standardized result. */
|
/** Sends a message (reply-all or new) and returns standardized result. */
|
||||||
async function sendMessage(params: {
|
async function sendMessage(params: {
|
||||||
to: string;
|
to: string;
|
||||||
text: string;
|
text: string;
|
||||||
html?: string;
|
html?: string;
|
||||||
replyToId?: string;
|
replyToId?: string;
|
||||||
}): Promise<{ channel: "agentmail"; messageId: string; threadId: string }> {
|
}): Promise<{ channel: "agentmail"; messageId: string; threadId: string }> {
|
||||||
const { to, text, html, replyToId } = params;
|
|
||||||
const { client, inboxId } = getClientAndInbox();
|
const { client, inboxId } = getClientAndInbox();
|
||||||
|
const result = params.replyToId
|
||||||
const result = replyToId
|
? await client.inboxes.messages.replyAll(inboxId, params.replyToId, { text: params.text, html: params.html })
|
||||||
? await client.inboxes.messages.reply(inboxId, replyToId, { text, html })
|
: await client.inboxes.messages.send(inboxId, { to: [params.to], text: params.text, html: params.html });
|
||||||
: await client.inboxes.messages.send(inboxId, { to: [to], text, html });
|
|
||||||
|
|
||||||
return { channel: "agentmail", ...result };
|
return { channel: "agentmail", ...result };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import type { AgentMailClient, AgentMail } from "agentmail";
|
import type { AgentMailClient, AgentMail } from "agentmail";
|
||||||
|
|
||||||
import { formatAttachments } from "./attachment.js";
|
import { formatAttachments } from "./attachment.js";
|
||||||
import { formatUtcDate } from "./utils.js";
|
import { formatUtcDate, type Message } from "./utils.js";
|
||||||
|
|
||||||
type Thread = AgentMail.threads.Thread;
|
type Thread = AgentMail.threads.Thread;
|
||||||
type Message = AgentMail.messages.Message;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the body text from a message, preferring extractedText.
|
* Extracts the body text from a message, preferring extractedText.
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { AgentMail } from "agentmail";
|
import type { AgentMail } from "agentmail";
|
||||||
|
import type { MoltbotConfig } from "clawdbot/plugin-sdk";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod";
|
||||||
|
|
||||||
import type { AgentMailConfigSchema } from "./config-schema.js";
|
import type { AgentMailConfigSchema } from "./config-schema.js";
|
||||||
@ -6,16 +7,9 @@ import type { AgentMailConfigSchema } from "./config-schema.js";
|
|||||||
/** AgentMail channel configuration. */
|
/** AgentMail channel configuration. */
|
||||||
export type AgentMailConfig = z.infer<typeof AgentMailConfigSchema>;
|
export type AgentMailConfig = z.infer<typeof AgentMailConfigSchema>;
|
||||||
|
|
||||||
/** Core config shape with AgentMail channel. */
|
/** Core config with AgentMail channel typed. */
|
||||||
export type CoreConfig = {
|
export type CoreConfig = MoltbotConfig & {
|
||||||
channels?: {
|
channels?: { agentmail?: AgentMailConfig };
|
||||||
agentmail?: AgentMailConfig;
|
|
||||||
};
|
|
||||||
session?: {
|
|
||||||
store?: string;
|
|
||||||
[key: string]: unknown;
|
|
||||||
};
|
|
||||||
[key: string]: unknown;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Resolved AgentMail account with runtime state. */
|
/** Resolved AgentMail account with runtime state. */
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user