Rename
This commit is contained in:
parent
1928077e02
commit
22eb3d52a1
@ -1,19 +1,19 @@
|
|||||||
# @clawdbot/agentmail
|
# @moltbot/agentmail
|
||||||
|
|
||||||
Email channel plugin for Clawdbot via [AgentMail](https://agentmail.to).
|
Email channel plugin for Moltbot via [AgentMail](https://agentmail.to).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
From npm:
|
From npm:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
clawdbot plugins install @clawdbot/agentmail
|
moltbot plugins install @moltbot/agentmail
|
||||||
```
|
```
|
||||||
|
|
||||||
From local checkout:
|
From local checkout:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
clawdbot plugins install ./extensions/agentmail
|
moltbot plugins install ./extensions/agentmail
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
|
import type { MoltbotPluginApi } from "clawdbot/plugin-sdk";
|
||||||
import { emptyPluginConfigSchema } from "clawdbot/plugin-sdk";
|
import { emptyPluginConfigSchema } from "clawdbot/plugin-sdk";
|
||||||
|
|
||||||
import { agentmailPlugin } from "./src/channel.js";
|
import { agentmailPlugin } from "./src/channel.js";
|
||||||
@ -9,7 +9,7 @@ const plugin = {
|
|||||||
name: "AgentMail",
|
name: "AgentMail",
|
||||||
description: "Email channel plugin via AgentMail API",
|
description: "Email channel plugin via AgentMail API",
|
||||||
configSchema: emptyPluginConfigSchema(),
|
configSchema: emptyPluginConfigSchema(),
|
||||||
register(api: ClawdbotPluginApi) {
|
register(api: MoltbotPluginApi) {
|
||||||
setAgentMailRuntime(api.runtime);
|
setAgentMailRuntime(api.runtime);
|
||||||
api.registerChannel({ plugin: agentmailPlugin });
|
api.registerChannel({ plugin: agentmailPlugin });
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/agentmail",
|
"name": "@moltbot/agentmail",
|
||||||
"version": "2026.1.26",
|
"version": "2026.1.26",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot AgentMail email channel plugin",
|
"description": "Moltbot AgentMail email channel plugin",
|
||||||
"clawdbot": {
|
"moltbot": {
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"./index.ts"
|
"./index.ts"
|
||||||
],
|
],
|
||||||
@ -18,7 +18,7 @@
|
|||||||
"quickstartAllowFrom": true
|
"quickstartAllowFrom": true
|
||||||
},
|
},
|
||||||
"install": {
|
"install": {
|
||||||
"npmSpec": "@clawdbot/agentmail",
|
"npmSpec": "@moltbot/agentmail",
|
||||||
"localPath": "extensions/agentmail",
|
"localPath": "extensions/agentmail",
|
||||||
"defaultChoice": "npm"
|
"defaultChoice": "npm"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "clawdbot/plugin-sdk";
|
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "clawdbot/plugin-sdk";
|
||||||
|
|
||||||
import type { AgentMailConfig, CoreConfig, ResolvedAgentMailAccount } from "./utils.js";
|
import type {
|
||||||
|
AgentMailConfig,
|
||||||
|
CoreConfig,
|
||||||
|
ResolvedAgentMailAccount,
|
||||||
|
} from "./utils.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists all AgentMail account IDs.
|
* Lists all AgentMail account IDs.
|
||||||
@ -33,14 +37,15 @@ export type ResolvedAgentMailCredentials = {
|
|||||||
*/
|
*/
|
||||||
export function resolveCredentials(
|
export function resolveCredentials(
|
||||||
cfg: CoreConfig,
|
cfg: CoreConfig,
|
||||||
env: Record<string, string | undefined> = process.env,
|
env: Record<string, string | undefined> = process.env
|
||||||
): ResolvedAgentMailCredentials {
|
): ResolvedAgentMailCredentials {
|
||||||
const base = cfg.channels?.agentmail ?? {};
|
const base = cfg.channels?.agentmail ?? {};
|
||||||
return {
|
return {
|
||||||
apiKey: base.token || env.AGENTMAIL_TOKEN,
|
apiKey: base.token || env.AGENTMAIL_TOKEN,
|
||||||
inboxId: base.emailAddress || env.AGENTMAIL_EMAIL_ADDRESS,
|
inboxId: base.emailAddress || env.AGENTMAIL_EMAIL_ADDRESS,
|
||||||
webhookUrl: base.webhookUrl || env.AGENTMAIL_WEBHOOK_URL,
|
webhookUrl: base.webhookUrl || env.AGENTMAIL_WEBHOOK_URL,
|
||||||
webhookPath: base.webhookPath || env.AGENTMAIL_WEBHOOK_PATH || "/webhooks/agentmail",
|
webhookPath:
|
||||||
|
base.webhookPath || env.AGENTMAIL_WEBHOOK_PATH || "/webhooks/agentmail",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -140,7 +140,8 @@ export const agentmailPlugin: ChannelPlugin<ResolvedAgentMailAccount> = {
|
|||||||
validateInput: ({ input }) => {
|
validateInput: ({ input }) => {
|
||||||
if (input.useEnv) return null;
|
if (input.useEnv) return null;
|
||||||
if (!input.token?.trim()) return "AgentMail requires --token";
|
if (!input.token?.trim()) return "AgentMail requires --token";
|
||||||
if (!input.emailAddress?.trim()) return "AgentMail requires --email-address";
|
if (!input.emailAddress?.trim())
|
||||||
|
return "AgentMail requires --email-address";
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
applyAccountConfig: ({ cfg, input }) => {
|
applyAccountConfig: ({ cfg, input }) => {
|
||||||
@ -152,11 +153,17 @@ export const agentmailPlugin: ChannelPlugin<ResolvedAgentMailAccount> = {
|
|||||||
agentmail: {
|
agentmail: {
|
||||||
...existing,
|
...existing,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
...(input.useEnv ? {} : {
|
...(input.useEnv
|
||||||
...(input.token?.trim() ? { token: input.token.trim() } : {}),
|
? {}
|
||||||
...(input.emailAddress?.trim() ? { emailAddress: input.emailAddress.trim() } : {}),
|
: {
|
||||||
...(input.webhookPath?.trim() ? { webhookPath: input.webhookPath.trim() } : {}),
|
...(input.token?.trim() ? { token: input.token.trim() } : {}),
|
||||||
}),
|
...(input.emailAddress?.trim()
|
||||||
|
? { emailAddress: input.emailAddress.trim() }
|
||||||
|
: {}),
|
||||||
|
...(input.webhookPath?.trim()
|
||||||
|
? { webhookPath: input.webhookPath.trim() }
|
||||||
|
: {}),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -16,18 +16,32 @@ export type MonitorAgentMailOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Runtime state tracking
|
// Runtime state tracking
|
||||||
const runtimeState = new Map<string, {
|
const runtimeState = new Map<
|
||||||
running: boolean;
|
string,
|
||||||
lastStartAt: number | null;
|
{
|
||||||
lastStopAt: number | null;
|
running: boolean;
|
||||||
lastError: string | null;
|
lastStartAt: number | null;
|
||||||
lastInboundAt?: number | null;
|
lastStopAt: number | null;
|
||||||
lastOutboundAt?: number | null;
|
lastError: string | null;
|
||||||
}>();
|
lastInboundAt?: number | null;
|
||||||
|
lastOutboundAt?: number | null;
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
|
||||||
function recordState(accountId: string, state: Partial<typeof runtimeState extends Map<string, infer V> ? V : never>) {
|
function recordState(
|
||||||
|
accountId: string,
|
||||||
|
state: Partial<typeof runtimeState extends Map<string, infer V> ? V : never>
|
||||||
|
) {
|
||||||
const key = `agentmail:${accountId}`;
|
const key = `agentmail:${accountId}`;
|
||||||
runtimeState.set(key, { ...runtimeState.get(key) ?? { running: false, lastStartAt: null, lastStopAt: null, lastError: null }, ...state });
|
runtimeState.set(key, {
|
||||||
|
...(runtimeState.get(key) ?? {
|
||||||
|
running: false,
|
||||||
|
lastStartAt: null,
|
||||||
|
lastStopAt: null,
|
||||||
|
lastError: null,
|
||||||
|
}),
|
||||||
|
...state,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAgentMailRuntimeState(accountId: string) {
|
export function getAgentMailRuntimeState(accountId: string) {
|
||||||
@ -47,7 +61,9 @@ function sendJson(res: ServerResponse, status: number, data: unknown) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Builds message body from webhook payload as fallback when API fetch fails. */
|
/** Builds message body from webhook payload as fallback when API fetch fails. */
|
||||||
function buildFallbackBody(message: NonNullable<MessageReceivedEvent["message"]>): string {
|
function buildFallbackBody(
|
||||||
|
message: NonNullable<MessageReceivedEvent["message"]>
|
||||||
|
): string {
|
||||||
const subject = message.subject ? `Subject: ${message.subject}\n\n` : "";
|
const subject = message.subject ? `Subject: ${message.subject}\n\n` : "";
|
||||||
return `${subject}${extractMessageBody(message)}`;
|
return `${subject}${extractMessageBody(message)}`;
|
||||||
}
|
}
|
||||||
@ -98,7 +114,11 @@ export async function monitorAgentMailProvider(
|
|||||||
const allowlist = agentmailConfig?.allowlist ?? [];
|
const allowlist = agentmailConfig?.allowlist ?? [];
|
||||||
const blocklist = agentmailConfig?.blocklist ?? [];
|
const blocklist = agentmailConfig?.blocklist ?? [];
|
||||||
|
|
||||||
recordState(accountId, { running: true, lastStartAt: Date.now(), lastError: null });
|
recordState(accountId, {
|
||||||
|
running: true,
|
||||||
|
lastStartAt: Date.now(),
|
||||||
|
lastError: null,
|
||||||
|
});
|
||||||
logger.info(`AgentMail: starting monitor for ${inboxId}`);
|
logger.info(`AgentMail: starting monitor for ${inboxId}`);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { AgentMailClient } from "agentmail";
|
import { AgentMailClient } from "agentmail";
|
||||||
import type {
|
import type {
|
||||||
ChannelOnboardingAdapter,
|
ChannelOnboardingAdapter,
|
||||||
ClawdbotConfig,
|
MoltbotConfig,
|
||||||
} from "clawdbot/plugin-sdk";
|
} from "clawdbot/plugin-sdk";
|
||||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "clawdbot/plugin-sdk";
|
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "clawdbot/plugin-sdk";
|
||||||
|
|
||||||
@ -51,9 +51,9 @@ async function listInboxes(client: AgentMailClient): Promise<string[]> {
|
|||||||
|
|
||||||
/** Helper to build config with agentmail channel updates. */
|
/** Helper to build config with agentmail channel updates. */
|
||||||
export function updateAgentMailConfig(
|
export function updateAgentMailConfig(
|
||||||
cfg: ClawdbotConfig,
|
cfg: MoltbotConfig,
|
||||||
updates: Partial<AgentMailConfig>
|
updates: Partial<AgentMailConfig>
|
||||||
): ClawdbotConfig {
|
): MoltbotConfig {
|
||||||
const channels = (cfg.channels ?? {}) as Record<string, unknown>;
|
const channels = (cfg.channels ?? {}) as Record<string, unknown>;
|
||||||
const agentmail = (channels.agentmail ?? {}) as AgentMailConfig;
|
const agentmail = (channels.agentmail ?? {}) as AgentMailConfig;
|
||||||
return {
|
return {
|
||||||
@ -65,7 +65,7 @@ export function updateAgentMailConfig(
|
|||||||
...updates,
|
...updates,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as ClawdbotConfig;
|
} as MoltbotConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const agentmailOnboardingAdapter: ChannelOnboardingAdapter = {
|
export const agentmailOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||||
@ -93,7 +93,7 @@ export const agentmailOnboardingAdapter: ChannelOnboardingAdapter = {
|
|||||||
? normalizeAccountId(accountOverrides.agentmail)
|
? normalizeAccountId(accountOverrides.agentmail)
|
||||||
: defaultAccountId;
|
: defaultAccountId;
|
||||||
|
|
||||||
let next = cfg as ClawdbotConfig;
|
let next = cfg as MoltbotConfig;
|
||||||
const account = resolveAgentMailAccount({
|
const account = resolveAgentMailAccount({
|
||||||
cfg: next as CoreConfig,
|
cfg: next as CoreConfig,
|
||||||
accountId,
|
accountId,
|
||||||
@ -164,7 +164,8 @@ export const agentmailOnboardingAdapter: ChannelOnboardingAdapter = {
|
|||||||
|
|
||||||
// Ask if gateway has a public URL
|
// Ask if gateway has a public URL
|
||||||
const hasPublicUrl = await prompter.confirm({
|
const hasPublicUrl = await prompter.confirm({
|
||||||
message: "Does your gateway have a public URL? (for automatic webhook setup)",
|
message:
|
||||||
|
"Does your gateway have a public URL? (for automatic webhook setup)",
|
||||||
initialValue: false,
|
initialValue: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -180,13 +181,18 @@ export const agentmailOnboardingAdapter: ChannelOnboardingAdapter = {
|
|||||||
validate: (v) => {
|
validate: (v) => {
|
||||||
const trimmed = v?.trim();
|
const trimmed = v?.trim();
|
||||||
if (!trimmed) return "Required";
|
if (!trimmed) return "Required";
|
||||||
if (!trimmed.startsWith("http://") && !trimmed.startsWith("https://")) {
|
if (
|
||||||
|
!trimmed.startsWith("http://") &&
|
||||||
|
!trimmed.startsWith("https://")
|
||||||
|
) {
|
||||||
return "Must start with http:// or https://";
|
return "Must start with http:// or https://";
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
).trim().replace(/\/+$/, ""); // Remove trailing slashes
|
)
|
||||||
|
.trim()
|
||||||
|
.replace(/\/+$/, ""); // Remove trailing slashes
|
||||||
|
|
||||||
// Ask for webhook path
|
// Ask for webhook path
|
||||||
const customPath = await prompter.confirm({
|
const customPath = await prompter.confirm({
|
||||||
@ -214,7 +220,7 @@ export const agentmailOnboardingAdapter: ChannelOnboardingAdapter = {
|
|||||||
await client.webhooks.create({
|
await client.webhooks.create({
|
||||||
url: `${baseUrl}${webhookPath}`,
|
url: `${baseUrl}${webhookPath}`,
|
||||||
eventTypes: ["message.received"],
|
eventTypes: ["message.received"],
|
||||||
clientId: `clawdbot-${emailAddress}`, // Idempotent per inbox
|
clientId: `moltbot-${emailAddress}`, // Idempotent per inbox
|
||||||
});
|
});
|
||||||
await prompter.note(
|
await prompter.note(
|
||||||
`Webhook registered: ${baseUrl}${webhookPath}`,
|
`Webhook registered: ${baseUrl}${webhookPath}`,
|
||||||
@ -266,7 +272,8 @@ export const agentmailOnboardingAdapter: ChannelOnboardingAdapter = {
|
|||||||
if (addAllowlist) {
|
if (addAllowlist) {
|
||||||
const entry = String(
|
const entry = String(
|
||||||
await prompter.text({
|
await prompter.text({
|
||||||
message: "Email or domain to allow (e.g., user@example.com or example.com)",
|
message:
|
||||||
|
"Email or domain to allow (e.g., user@example.com or example.com)",
|
||||||
})
|
})
|
||||||
).trim();
|
).trim();
|
||||||
|
|
||||||
@ -306,7 +313,7 @@ export const agentmailOnboardingAdapter: ChannelOnboardingAdapter = {
|
|||||||
" Event: message.received",
|
" Event: message.received",
|
||||||
"",
|
"",
|
||||||
"The gateway must be publicly accessible for webhooks to work.",
|
"The gateway must be publicly accessible for webhooks to work.",
|
||||||
"Without this, Clawdbot won't receive incoming emails.",
|
"Without this, Moltbot won't receive incoming emails.",
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
"Webhook Setup Required"
|
"Webhook Setup Required"
|
||||||
);
|
);
|
||||||
|
|||||||
@ -36,14 +36,20 @@ async function sendMessage(params: {
|
|||||||
/** Outbound adapter for the AgentMail channel. */
|
/** Outbound adapter for the AgentMail channel. */
|
||||||
export const agentmailOutbound: ChannelOutboundAdapter = {
|
export const agentmailOutbound: ChannelOutboundAdapter = {
|
||||||
deliveryMode: "direct",
|
deliveryMode: "direct",
|
||||||
chunker: (text, limit) => getAgentMailRuntime().channel.text.chunkMarkdownText(text, limit),
|
chunker: (text, limit) =>
|
||||||
|
getAgentMailRuntime().channel.text.chunkMarkdownText(text, limit),
|
||||||
chunkerMode: "markdown",
|
chunkerMode: "markdown",
|
||||||
textChunkLimit: 4000,
|
textChunkLimit: 4000,
|
||||||
|
|
||||||
sendText: ({ to, text, replyToId }) => sendMessage({ to, text, replyToId: replyToId ?? undefined }),
|
sendText: ({ to, text, replyToId }) =>
|
||||||
|
sendMessage({ to, text, replyToId: replyToId ?? undefined }),
|
||||||
|
|
||||||
sendMedia: ({ to, text, mediaUrl, replyToId }) => {
|
sendMedia: ({ to, text, mediaUrl, replyToId }) => {
|
||||||
const fullText = mediaUrl ? `${text}\n\nAttachment: ${mediaUrl}` : text;
|
const fullText = mediaUrl ? `${text}\n\nAttachment: ${mediaUrl}` : text;
|
||||||
return sendMessage({ to, text: fullText, replyToId: replyToId ?? undefined });
|
return sendMessage({
|
||||||
|
to,
|
||||||
|
text: fullText,
|
||||||
|
replyToId: replyToId ?? undefined,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -21,7 +21,6 @@ export type CoreConfig = {
|
|||||||
/** Resolved AgentMail account with runtime state. */
|
/** Resolved AgentMail account with runtime state. */
|
||||||
export type ResolvedAgentMailAccount = {
|
export type ResolvedAgentMailAccount = {
|
||||||
accountId: string;
|
accountId: string;
|
||||||
/** Account name for identifying this configuration. */
|
|
||||||
name?: string;
|
name?: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
configured: boolean;
|
configured: boolean;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user