Webhook config
This commit is contained in:
parent
f399bdfab1
commit
f0f5b51dd2
@ -81,8 +81,8 @@ Minimal config:
|
||||
| `enabled` | boolean | Enable/disable the channel (default: true) |
|
||||
| `token` | string | AgentMail API token (required) |
|
||||
| `emailAddress` | string | AgentMail inbox email address to monitor (required) |
|
||||
| `webhookUrl` | string | Gateway public base URL (e.g., `https://gw.ngrok.io`) |
|
||||
| `webhookPath` | string | Custom webhook path (default: `/webhooks/agentmail`) |
|
||||
| `webhookUrl` | string | Full public webhook URL (e.g., `https://gw.ngrok.io/webhooks/agentmail`) |
|
||||
| `webhookPath` | string | Local webhook path (derived from URL or default: `/webhooks/agentmail`) |
|
||||
| `allowFrom` | string[] | Allowed sender emails/domains (empty = allow all) |
|
||||
|
||||
## Sender Filtering
|
||||
|
||||
@ -96,10 +96,47 @@ describe("resolveCredentials", () => {
|
||||
it("resolves webhookUrl from env", () => {
|
||||
const cfg: CoreConfig = {};
|
||||
const env = {
|
||||
AGENTMAIL_WEBHOOK_URL: "https://env-gateway.example.com",
|
||||
AGENTMAIL_WEBHOOK_URL: "https://env-gateway.example.com/hooks/email",
|
||||
};
|
||||
const result = resolveCredentials(cfg, env);
|
||||
expect(result.webhookUrl).toBe("https://env-gateway.example.com");
|
||||
expect(result.webhookUrl).toBe("https://env-gateway.example.com/hooks/email");
|
||||
});
|
||||
|
||||
it("derives webhookPath from webhookUrl", () => {
|
||||
const cfg: CoreConfig = {
|
||||
channels: {
|
||||
agentmail: {
|
||||
webhookUrl: "https://my-gateway.ngrok.io/custom/path",
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = resolveCredentials(cfg, {});
|
||||
expect(result.webhookPath).toBe("/custom/path");
|
||||
});
|
||||
|
||||
it("uses default webhookPath when URL has no path", () => {
|
||||
const cfg: CoreConfig = {
|
||||
channels: {
|
||||
agentmail: {
|
||||
webhookUrl: "https://my-gateway.ngrok.io",
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = resolveCredentials(cfg, {});
|
||||
expect(result.webhookPath).toBe("/webhooks/agentmail");
|
||||
});
|
||||
|
||||
it("explicit webhookPath takes precedence over URL-derived path", () => {
|
||||
const cfg: CoreConfig = {
|
||||
channels: {
|
||||
agentmail: {
|
||||
webhookUrl: "https://my-gateway.ngrok.io/url-path",
|
||||
webhookPath: "/explicit-path",
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = resolveCredentials(cfg, {});
|
||||
expect(result.webhookPath).toBe("/explicit-path");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -31,21 +31,46 @@ export type ResolvedAgentMailCredentials = {
|
||||
webhookPath: string;
|
||||
};
|
||||
|
||||
const DEFAULT_WEBHOOK_PATH = "/webhooks/agentmail";
|
||||
|
||||
/** Extracts the path from a URL string. Returns undefined if just root "/". */
|
||||
function extractPathFromUrl(url: string): string | undefined {
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
// Return undefined if just root path "/" - use default instead
|
||||
if (!parsed.pathname || parsed.pathname === "/") {
|
||||
return undefined;
|
||||
}
|
||||
return parsed.pathname;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves AgentMail credentials from config and environment.
|
||||
* Maps user-facing keys (token, emailAddress) to SDK names (apiKey, inboxId).
|
||||
* Derives webhookPath from webhookUrl if not explicitly set.
|
||||
*/
|
||||
export function resolveCredentials(
|
||||
cfg: CoreConfig,
|
||||
env: Record<string, string | undefined> = process.env
|
||||
): ResolvedAgentMailCredentials {
|
||||
const base = cfg.channels?.agentmail ?? {};
|
||||
const webhookUrl = base.webhookUrl || env.AGENTMAIL_WEBHOOK_URL;
|
||||
|
||||
// Derive path from URL if not explicitly set
|
||||
let webhookPath = base.webhookPath || env.AGENTMAIL_WEBHOOK_PATH;
|
||||
if (!webhookPath && webhookUrl) {
|
||||
webhookPath = extractPathFromUrl(webhookUrl);
|
||||
}
|
||||
webhookPath = webhookPath || DEFAULT_WEBHOOK_PATH;
|
||||
|
||||
return {
|
||||
apiKey: base.token || env.AGENTMAIL_TOKEN,
|
||||
inboxId: base.emailAddress || env.AGENTMAIL_EMAIL_ADDRESS,
|
||||
webhookUrl: base.webhookUrl || env.AGENTMAIL_WEBHOOK_URL,
|
||||
webhookPath:
|
||||
base.webhookPath || env.AGENTMAIL_WEBHOOK_PATH || "/webhooks/agentmail",
|
||||
webhookUrl,
|
||||
webhookPath,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -13,9 +13,9 @@ export const AgentMailConfigSchema = z.object({
|
||||
token: z.string().optional(),
|
||||
/** AgentMail inbox email address to monitor (required). */
|
||||
emailAddress: z.string().optional(),
|
||||
/** Public base URL of the gateway (e.g., https://my-gateway.ngrok.io). */
|
||||
/** Full public webhook URL (e.g., https://my-gateway.ngrok.io/webhooks/agentmail). */
|
||||
webhookUrl: z.string().optional(),
|
||||
/** Custom webhook path for receiving emails (default: /webhooks/agentmail). */
|
||||
/** Local webhook path (default: /webhooks/agentmail). Derived from webhookUrl if not set. */
|
||||
webhookPath: z.string().optional(),
|
||||
/** Allowed sender emails/domains. Empty = allow all. */
|
||||
allowFrom: z.array(z.string()).optional(),
|
||||
|
||||
@ -173,11 +173,11 @@ export const agentmailOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
let webhookPath = DEFAULT_WEBHOOK_PATH;
|
||||
|
||||
if (hasPublicUrl) {
|
||||
// Get the public base URL
|
||||
const baseUrl = String(
|
||||
// Get the full public webhook URL
|
||||
webhookUrl = String(
|
||||
await prompter.text({
|
||||
message: "Gateway public URL",
|
||||
placeholder: "https://my-gateway.ngrok.io",
|
||||
message: "Full webhook URL",
|
||||
placeholder: `https://my-gateway.ngrok.io${DEFAULT_WEBHOOK_PATH}`,
|
||||
validate: (v) => {
|
||||
const trimmed = v?.trim();
|
||||
if (!trimmed) return "Required";
|
||||
@ -190,42 +190,24 @@ export const agentmailOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
return undefined;
|
||||
},
|
||||
})
|
||||
)
|
||||
.trim()
|
||||
.replace(/\/+$/, ""); // Remove trailing slashes
|
||||
).trim();
|
||||
|
||||
// Ask for webhook path
|
||||
const customPath = await prompter.confirm({
|
||||
message: `Customize webhook path? (default: ${DEFAULT_WEBHOOK_PATH})`,
|
||||
initialValue: false,
|
||||
});
|
||||
|
||||
if (customPath) {
|
||||
const pathInput = String(
|
||||
await prompter.text({
|
||||
message: "Webhook path",
|
||||
placeholder: DEFAULT_WEBHOOK_PATH,
|
||||
})
|
||||
).trim();
|
||||
|
||||
if (pathInput) {
|
||||
webhookPath = pathInput.startsWith("/") ? pathInput : `/${pathInput}`;
|
||||
}
|
||||
// Derive path from URL
|
||||
try {
|
||||
const parsed = new URL(webhookUrl);
|
||||
webhookPath = parsed.pathname || DEFAULT_WEBHOOK_PATH;
|
||||
} catch {
|
||||
webhookPath = DEFAULT_WEBHOOK_PATH;
|
||||
}
|
||||
|
||||
webhookUrl = baseUrl;
|
||||
|
||||
// Auto-register webhook with AgentMail
|
||||
try {
|
||||
await client.webhooks.create({
|
||||
url: `${baseUrl}${webhookPath}`,
|
||||
url: webhookUrl,
|
||||
eventTypes: ["message.received"],
|
||||
clientId: `moltbot-${emailAddress}`, // Idempotent per inbox
|
||||
clientId: `clawdbot-${emailAddress}`, // Idempotent per inbox
|
||||
});
|
||||
await prompter.note(
|
||||
`Webhook registered: ${baseUrl}${webhookPath}`,
|
||||
"Webhook Created"
|
||||
);
|
||||
await prompter.note(`Webhook registered: ${webhookUrl}`, "Webhook Created");
|
||||
} catch (err) {
|
||||
// Webhook may already exist or API error - show warning but continue
|
||||
await prompter.note(
|
||||
@ -233,34 +215,15 @@ export const agentmailOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
`Could not auto-register webhook: ${String(err)}`,
|
||||
"",
|
||||
"You may need to configure it manually in the AgentMail dashboard:",
|
||||
` URL: ${baseUrl}${webhookPath}`,
|
||||
` URL: ${webhookUrl}`,
|
||||
" Event: message.received",
|
||||
].join("\n"),
|
||||
"Webhook Warning"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// No public URL - ask for path only and show manual instructions
|
||||
const customPath = await prompter.confirm({
|
||||
message: `Customize webhook path? (default: ${DEFAULT_WEBHOOK_PATH})`,
|
||||
initialValue: false,
|
||||
});
|
||||
|
||||
if (customPath) {
|
||||
const pathInput = String(
|
||||
await prompter.text({
|
||||
message: "Webhook path",
|
||||
placeholder: DEFAULT_WEBHOOK_PATH,
|
||||
})
|
||||
).trim();
|
||||
|
||||
if (pathInput) {
|
||||
webhookPath = pathInput.startsWith("/") ? pathInput : `/${pathInput}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save webhook config
|
||||
// Save webhook config (webhookPath derived from URL or default)
|
||||
next = updateAgentMailConfig(next, { webhookUrl, webhookPath });
|
||||
|
||||
// Ask about allowFrom
|
||||
|
||||
Loading…
Reference in New Issue
Block a user