@desplega.ai/agent-swarm 1.83.2 → 1.84.1
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 +48 -8
- package/openapi.json +24 -3
- package/package.json +1 -1
- package/src/be/migrations/076_kapso_sender_user_backfill.sql +43 -0
- package/src/commands/context-preamble.ts +178 -0
- package/src/commands/runner.ts +28 -1
- package/src/http/users.ts +11 -3
- package/src/http/webhooks.ts +101 -0
- package/src/integrations/kapso/client.ts +198 -0
- package/src/integrations/kapso/config.ts +104 -0
- package/src/integrations/kapso/inbound.ts +147 -0
- package/src/prompts/base-prompt.ts +15 -2
- package/src/prompts/session-templates.ts +26 -12
- package/src/server.ts +14 -0
- package/src/tests/agentmail-sending-skill.test.ts +75 -0
- package/src/tests/agents-list-model-display.test.ts +33 -0
- package/src/tests/base-prompt.test.ts +90 -1
- package/src/tests/http-users.test.ts +53 -0
- package/src/tests/kapso-client.test.ts +94 -0
- package/src/tests/kapso-inbound.test.ts +257 -0
- package/src/tests/kv-page-proxy.test.ts +1 -0
- package/src/tests/pagination-metrics.test.ts +4 -4
- package/src/tests/prompt-template-session.test.ts +13 -3
- package/src/tests/runner-context-preamble.test.ts +202 -0
- package/src/tests/tool-annotations.test.ts +3 -2
- package/src/tools/cancel-task.ts +13 -5
- package/src/tools/get-task-details.ts +18 -10
- package/src/tools/get-tasks.ts +9 -4
- package/src/tools/register-kapso-number.ts +210 -0
- package/src/tools/send-task.ts +9 -5
- package/src/tools/task-action.ts +20 -10
- package/src/tools/templates.ts +35 -0
- package/src/tools/tool-config.ts +6 -0
- package/src/tools/whatsapp-message.ts +135 -0
- package/templates/skills/agentmail-sending/SKILL.md +169 -0
- package/templates/skills/kapso-whatsapp/SKILL.md +383 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { sendKapsoText } from "@/integrations/kapso/client";
|
|
4
|
+
import { getKapsoConfig } from "@/integrations/kapso/config";
|
|
5
|
+
import { createToolRegistrar } from "@/tools/utils";
|
|
6
|
+
|
|
7
|
+
/** Shared structured-error message for the 24h session-window case. */
|
|
8
|
+
const SESSION_WINDOW_HINT =
|
|
9
|
+
'Outside the 24h WhatsApp session window — free-form text is rejected. Use a pre-approved template message (see the `kapso-whatsapp` skill, "Send a template").';
|
|
10
|
+
|
|
11
|
+
const outputSchema = z.object({
|
|
12
|
+
yourAgentId: z.string().uuid().optional(),
|
|
13
|
+
success: z.boolean(),
|
|
14
|
+
message: z.string(),
|
|
15
|
+
messageId: z.string().optional(),
|
|
16
|
+
sessionWindowExpired: z.boolean().optional(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const registerSendWhatsappMessageTool = (server: McpServer) => {
|
|
20
|
+
createToolRegistrar(server)(
|
|
21
|
+
"send-whatsapp-message",
|
|
22
|
+
{
|
|
23
|
+
title: "Send WhatsApp Message",
|
|
24
|
+
annotations: { openWorldHint: true },
|
|
25
|
+
description:
|
|
26
|
+
"Send a free-form WhatsApp text via Kapso (within the 24h session window). Thin wrapper over the Kapso Meta-proxy send. For templates/media/reactions use the `kapso-whatsapp` skill. If the recipient is outside the 24h window the call returns a structured error pointing at the template path.",
|
|
27
|
+
inputSchema: z.object({
|
|
28
|
+
phoneNumberId: z
|
|
29
|
+
.string()
|
|
30
|
+
.min(1)
|
|
31
|
+
.describe("The swarm's Kapso/Meta phone-number ID to send from (KAPSO_PHONE_NUMBER_ID)."),
|
|
32
|
+
to: z
|
|
33
|
+
.string()
|
|
34
|
+
.min(1)
|
|
35
|
+
.describe("Recipient phone in E.164 WITHOUT '+' (e.g. '15551234567')."),
|
|
36
|
+
body: z.string().min(1).describe("Message text."),
|
|
37
|
+
previewUrl: z
|
|
38
|
+
.boolean()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Render a link preview for URLs in the body (default false)."),
|
|
41
|
+
}),
|
|
42
|
+
outputSchema,
|
|
43
|
+
},
|
|
44
|
+
async ({ phoneNumberId, to, body, previewUrl }, requestInfo) => {
|
|
45
|
+
return sendAndFormat({ phoneNumberId, to, body, previewUrl }, requestInfo.agentId, undefined);
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const registerReplyWhatsappMessageTool = (server: McpServer) => {
|
|
51
|
+
createToolRegistrar(server)(
|
|
52
|
+
"reply-whatsapp-message",
|
|
53
|
+
{
|
|
54
|
+
title: "Reply to WhatsApp Message",
|
|
55
|
+
annotations: { openWorldHint: true },
|
|
56
|
+
description:
|
|
57
|
+
"Quote-reply a WhatsApp message via Kapso — same as send-whatsapp-message but threads to a specific inbound WAMID via context.message_id. Recipient is inferred from the conversation; pass the original sender's phone as `to`.",
|
|
58
|
+
inputSchema: z.object({
|
|
59
|
+
phoneNumberId: z
|
|
60
|
+
.string()
|
|
61
|
+
.min(1)
|
|
62
|
+
.describe("The swarm's Kapso/Meta phone-number ID to send from (KAPSO_PHONE_NUMBER_ID)."),
|
|
63
|
+
to: z.string().min(1).describe("Recipient phone in E.164 WITHOUT '+'."),
|
|
64
|
+
inReplyTo: z
|
|
65
|
+
.string()
|
|
66
|
+
.min(1)
|
|
67
|
+
.describe("The inbound WAMID to quote-reply (set as context.message_id)."),
|
|
68
|
+
body: z.string().min(1).describe("Reply text."),
|
|
69
|
+
}),
|
|
70
|
+
outputSchema,
|
|
71
|
+
},
|
|
72
|
+
async ({ phoneNumberId, to, inReplyTo, body }, requestInfo) => {
|
|
73
|
+
return sendAndFormat({ phoneNumberId, to, body }, requestInfo.agentId, inReplyTo);
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
async function sendAndFormat(
|
|
79
|
+
params: { phoneNumberId: string; to: string; body: string; previewUrl?: boolean },
|
|
80
|
+
agentId: string | undefined,
|
|
81
|
+
contextMessageId: string | undefined,
|
|
82
|
+
) {
|
|
83
|
+
try {
|
|
84
|
+
const config = getKapsoConfig();
|
|
85
|
+
if (!config.apiKey) {
|
|
86
|
+
const msg = "KAPSO_API_KEY is not configured in swarm config.";
|
|
87
|
+
return {
|
|
88
|
+
content: [{ type: "text" as const, text: msg }],
|
|
89
|
+
structuredContent: { yourAgentId: agentId, success: false, message: msg },
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const result = await sendKapsoText({
|
|
94
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
95
|
+
apiKey: config.apiKey,
|
|
96
|
+
phoneNumberId: params.phoneNumberId,
|
|
97
|
+
to: params.to,
|
|
98
|
+
body: params.body,
|
|
99
|
+
previewUrl: params.previewUrl,
|
|
100
|
+
contextMessageId,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (result.ok) {
|
|
104
|
+
const text = `Sent WhatsApp message to ${params.to} (wamid ${result.messageId ?? "unknown"})`;
|
|
105
|
+
return {
|
|
106
|
+
content: [{ type: "text" as const, text }],
|
|
107
|
+
structuredContent: {
|
|
108
|
+
yourAgentId: agentId,
|
|
109
|
+
success: true,
|
|
110
|
+
message: text,
|
|
111
|
+
messageId: result.messageId,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const text = result.sessionWindowExpired
|
|
117
|
+
? `${SESSION_WINDOW_HINT} (Kapso: ${result.errorMessage})`
|
|
118
|
+
: `Kapso send failed: ${result.errorMessage}`;
|
|
119
|
+
return {
|
|
120
|
+
content: [{ type: "text" as const, text }],
|
|
121
|
+
structuredContent: {
|
|
122
|
+
yourAgentId: agentId,
|
|
123
|
+
success: false,
|
|
124
|
+
message: text,
|
|
125
|
+
sessionWindowExpired: result.sessionWindowExpired,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
} catch (err) {
|
|
129
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
130
|
+
return {
|
|
131
|
+
content: [{ type: "text" as const, text: `Error: ${errorMessage}` }],
|
|
132
|
+
structuredContent: { yourAgentId: agentId, success: false, message: errorMessage },
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agentmail-sending
|
|
3
|
+
description: Canonical AgentMail send-message API reference for swarm agents. Pins the base URL, required field names, text-only rendering workaround, BCC policy, and ready-to-copy curl / swarm-script examples so agents do not rediscover the API surface at runtime.
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# AgentMail Sending
|
|
8
|
+
|
|
9
|
+
## Canonical Base URL
|
|
10
|
+
|
|
11
|
+
Use this base URL exactly:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
https://api.agentmail.to/v0/
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
DO NOT use `api.agentmail.ai`. That host is a hallucination and will not send mail through AgentMail's current API.
|
|
18
|
+
|
|
19
|
+
## Canonical Send-Message Fields
|
|
20
|
+
|
|
21
|
+
For `POST /inboxes/{inbox}/messages/send`, the JSON body fields are exactly:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
to
|
|
25
|
+
bcc
|
|
26
|
+
subject
|
|
27
|
+
text
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Use `text`, NOT `text_body`, `body`, or `content`.
|
|
31
|
+
|
|
32
|
+
Do NOT pass `html`. AgentMail has a known rendering bug: when `html` is passed with `text`, the HTML body can be empty and email clients may show a blank email. AgentMail renders `text` correctly on its own.
|
|
33
|
+
|
|
34
|
+
## Rule 0: One-Shots Stay One-Shots
|
|
35
|
+
|
|
36
|
+
For a one-off send, such as a kickoff email or a single notification, do not create a reusable swarm-script. Use raw `curl` from Bash, or inline `script_run` if you need swarm-visible execution.
|
|
37
|
+
|
|
38
|
+
Only use `script_upsert` when the send will be reused by a workflow that fires repeatedly.
|
|
39
|
+
|
|
40
|
+
## Default Example: Raw curl
|
|
41
|
+
|
|
42
|
+
Use this direct API call first. It does not assume any SDK is installed.
|
|
43
|
+
|
|
44
|
+
Endpoint:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
https://api.agentmail.to/v0/inboxes/{inbox}/messages/send
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
INBOX="<agentmail-inbox-id>"
|
|
52
|
+
|
|
53
|
+
curl -sS -X POST "https://api.agentmail.to/v0/inboxes/${INBOX}/messages/send" \
|
|
54
|
+
-H "Authorization: Bearer $AGENTMAIL_API_KEY" \
|
|
55
|
+
-H "Content-Type: application/json" \
|
|
56
|
+
--data-binary @- <<'JSON'
|
|
57
|
+
{
|
|
58
|
+
"to": ["recipient@example.com"],
|
|
59
|
+
"bcc": ["oversight@example.com"],
|
|
60
|
+
"subject": "Subject line",
|
|
61
|
+
"text": "Plain-text email body."
|
|
62
|
+
}
|
|
63
|
+
JSON
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Notes:
|
|
67
|
+
|
|
68
|
+
- `AGENTMAIL_API_KEY` must be configured in swarm config or exported into the shell before running curl.
|
|
69
|
+
- Keep `bcc` for external recipients so a human oversight inbox sees outbound email.
|
|
70
|
+
- Do not add `html`; `text` is the canonical content field.
|
|
71
|
+
|
|
72
|
+
## Reusable Workflow Example: script_upsert
|
|
73
|
+
|
|
74
|
+
Use this only when the send is part of a reusable workflow. The script resolves the API key from swarm config at runtime and calls the same raw HTTP endpoint with `fetch`.
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
await script_upsert({
|
|
78
|
+
name: "send-agentmail-text-email",
|
|
79
|
+
description: "Send a text-only AgentMail message from a reusable workflow.",
|
|
80
|
+
intent: "Reusable workflow email send via AgentMail raw API",
|
|
81
|
+
scope: "agent",
|
|
82
|
+
source: `
|
|
83
|
+
import type { ScriptContext } from "swarm-sdk";
|
|
84
|
+
|
|
85
|
+
type Args = {
|
|
86
|
+
inbox: string;
|
|
87
|
+
to: string[];
|
|
88
|
+
bcc: string[];
|
|
89
|
+
subject: string;
|
|
90
|
+
text: string;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export default async (args: Args, ctx: ScriptContext) => {
|
|
94
|
+
const redactedKey = ctx.swarm.config.get('AGENTMAIL_API_KEY');
|
|
95
|
+
if (!redactedKey) {
|
|
96
|
+
throw new Error("AGENTMAIL_API_KEY is not configured in swarm config");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const apiKey = ctx.stdlib.Redacted.value(redactedKey);
|
|
100
|
+
const response = await ctx.stdlib.fetch(
|
|
101
|
+
\`https://api.agentmail.to/v0/inboxes/\${encodeURIComponent(args.inbox)}/messages/send\`,
|
|
102
|
+
{
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: {
|
|
105
|
+
Authorization: \`Bearer \${apiKey}\`,
|
|
106
|
+
"Content-Type": "application/json",
|
|
107
|
+
},
|
|
108
|
+
body: JSON.stringify({
|
|
109
|
+
to: args.to,
|
|
110
|
+
bcc: args.bcc,
|
|
111
|
+
subject: args.subject,
|
|
112
|
+
text: args.text,
|
|
113
|
+
}),
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const responseText = await response.text();
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
throw new Error(\`AgentMail send failed: \${response.status} \${responseText}\`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return responseText ? JSON.parse(responseText) : { ok: true };
|
|
123
|
+
};
|
|
124
|
+
`,
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Run it from a workflow with args shaped like:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"inbox": "<agentmail-inbox-id>",
|
|
133
|
+
"to": ["recipient@example.com"],
|
|
134
|
+
"bcc": ["oversight@example.com"],
|
|
135
|
+
"subject": "Subject line",
|
|
136
|
+
"text": "Plain-text email body."
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## BCC Policy
|
|
141
|
+
|
|
142
|
+
All outbound emails to external recipients MUST include a human oversight email address in `bcc`. This gives the operator visibility into what the swarm sends.
|
|
143
|
+
|
|
144
|
+
Exception: internal emails between the swarm's own agent inboxes do not need BCC.
|
|
145
|
+
|
|
146
|
+
## Human Approval
|
|
147
|
+
|
|
148
|
+
Never send outreach or cold emails to external recipients without explicit human approval. Draft the email, present it for review, and send only after receiving approval.
|
|
149
|
+
|
|
150
|
+
## Checklist
|
|
151
|
+
|
|
152
|
+
Before every AgentMail send:
|
|
153
|
+
|
|
154
|
+
- Use `https://api.agentmail.to/v0/`.
|
|
155
|
+
- Use only `to`, `bcc`, `subject`, and `text` in the send-message JSON body.
|
|
156
|
+
- Use `text`, not `text_body`, `body`, or `content`.
|
|
157
|
+
- Do not pass `html`.
|
|
158
|
+
- BCC a human oversight address for external recipients.
|
|
159
|
+
- Get human approval for outreach or cold email.
|
|
160
|
+
- Use raw `curl` or inline `script_run` for one-offs; reserve `script_upsert` for reusable workflow sends.
|
|
161
|
+
|
|
162
|
+
## Common Errors
|
|
163
|
+
|
|
164
|
+
| Symptom | Cause / fix |
|
|
165
|
+
|---|---|
|
|
166
|
+
| 404 on `/v0/inboxes/.../send` | Check the base URL. Use `api.agentmail.to`, not `api.agentmail.ai`. |
|
|
167
|
+
| 422 `{"detail":"text Field required"}` | The request used `text_body` or `body` instead of `text`. |
|
|
168
|
+
| 401 | `AGENTMAIL_API_KEY` is not configured in swarm config. In scripts, use `swarm.config.get('AGENTMAIL_API_KEY')`. |
|
|
169
|
+
| HTML rendering bug | Do not pass `html` at all. AgentMail renders `text` correctly. |
|