@andreprado/agentkit 0.1.0-alpha.9 → 0.1.0

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.
Files changed (117) hide show
  1. package/README.md +18 -1
  2. package/docs/guides/add-channel.md +251 -7
  3. package/docs/guides/add-knowledge.md +10 -0
  4. package/docs/guides/add-managed-composio.md +165 -0
  5. package/docs/guides/add-tool.md +10 -3
  6. package/docs/guides/channel-security.md +162 -32
  7. package/docs/guides/connect-discord.md +178 -0
  8. package/docs/guides/connect-slack.md +126 -0
  9. package/docs/guides/connect-telegram.md +61 -1
  10. package/docs/guides/connect-whatsapp-evolution.md +121 -0
  11. package/docs/guides/connect-whatsapp-uazapi.md +139 -0
  12. package/docs/guides/connect-whatsapp-zapster.md +119 -16
  13. package/docs/guides/create-agent.md +31 -4
  14. package/docs/guides/debug-channel.md +159 -0
  15. package/docs/guides/improve-from-production.md +151 -0
  16. package/docs/guides/prepare-deploy.md +32 -14
  17. package/docs/guides/replay-production-traces.md +72 -0
  18. package/docs/guides/run-evals.md +95 -25
  19. package/docs/guides/security-rules.md +9 -5
  20. package/docs/guides/send-feedback.md +135 -0
  21. package/docs/guides/use-jev.md +67 -0
  22. package/docs/guides/use-provider.md +70 -3
  23. package/docs/llms-full.txt +295 -25
  24. package/docs/llms.txt +54 -7
  25. package/package.json +3 -7
  26. package/src/cli/args.ts +23 -2
  27. package/src/cli/cloud-client.ts +121 -9
  28. package/src/cli/commands/channels.ts +856 -36
  29. package/src/cli/commands/feedback.ts +438 -0
  30. package/src/cli/commands/provider.ts +47 -0
  31. package/src/cli/commands/transcribe.ts +171 -0
  32. package/src/cli/deploy-chat-ui.ts +232 -18
  33. package/src/cli/deploy-readiness.ts +227 -14
  34. package/src/cli/help.ts +67 -9
  35. package/src/cli/index.ts +740 -35
  36. package/src/cli/new-command.ts +41 -0
  37. package/src/cloud/client.ts +4 -3
  38. package/src/cloud/contracts.ts +1 -1
  39. package/src/create-project.ts +18 -35
  40. package/src/index.ts +565 -11
  41. package/src/providers/codex-auth.ts +111 -0
  42. package/src/providers/pi.ts +88 -19
  43. package/src/providers/test.ts +36 -0
  44. package/src/providers/types.ts +8 -0
  45. package/src/runtime/channel-test-harness.ts +21 -1
  46. package/src/runtime/channels/discord.ts +896 -0
  47. package/src/runtime/channels/generic-webhook.ts +974 -0
  48. package/src/runtime/channels/slack.ts +646 -0
  49. package/src/runtime/channels/telegram.ts +343 -12
  50. package/src/runtime/channels/whatsapp-evolution.ts +1357 -0
  51. package/src/runtime/channels/whatsapp-meta.ts +9 -0
  52. package/src/runtime/channels/whatsapp-uazapi.ts +1323 -0
  53. package/src/runtime/channels/whatsapp-zapster.ts +674 -40
  54. package/src/runtime/channels.ts +83 -3
  55. package/src/runtime/chat.ts +70 -44
  56. package/src/runtime/config.ts +489 -20
  57. package/src/runtime/core/manifest.ts +75 -5
  58. package/src/runtime/core/targets.ts +5 -5
  59. package/src/runtime/deploy-readiness.ts +34 -4
  60. package/src/runtime/dev-server.ts +639 -39
  61. package/src/runtime/env.ts +8 -3
  62. package/src/runtime/evals.ts +445 -74
  63. package/src/runtime/improve.ts +868 -0
  64. package/src/runtime/inspect.ts +173 -4
  65. package/src/runtime/integrations/composio.ts +425 -0
  66. package/src/runtime/knowledge/retrieve.ts +25 -5
  67. package/src/runtime/knowledge/schema.ts +45 -1
  68. package/src/runtime/prompt-context.ts +141 -0
  69. package/src/runtime/runtime-contract.ts +71 -7
  70. package/src/runtime/skills.ts +95 -0
  71. package/src/runtime/targets/cloudflare/build.ts +1010 -208
  72. package/src/runtime/targets/container/server.ts +1 -1
  73. package/src/runtime/targets/vps/deploy.ts +26 -9
  74. package/src/runtime/tool-runner.ts +9 -1
  75. package/src/runtime/tools.ts +26 -2
  76. package/src/runtime/transcription.ts +483 -0
  77. package/src/storage/sqlite.ts +7 -2
  78. package/src/templates/blank.ts +37 -9
  79. package/src/templates/dentista.ts +40 -14
  80. package/src/templates/skills/agentkit-build-agent/SKILL.md +34 -5
  81. package/src/templates/skills/agentkit-build-agent/templates/appointment-intake.instructions.md +2 -1
  82. package/src/templates/skills/agentkit-capsule/SKILL.md +32 -3
  83. package/src/templates/skills/agentkit-capsule/references/docs-router.md +2 -2
  84. package/src/templates/skills/agentkit-channels/SKILL.md +66 -1
  85. package/src/templates/skills/agentkit-channels/references/channel-buffering.md +8 -1
  86. package/src/templates/skills/agentkit-channels/references/channel-debugging.md +28 -3
  87. package/src/templates/skills/agentkit-channels/references/discord.md +93 -0
  88. package/src/templates/skills/agentkit-channels/references/slack.md +56 -0
  89. package/src/templates/skills/agentkit-channels/references/telegram.md +34 -0
  90. package/src/templates/skills/agentkit-channels/references/whatsapp-evolution.md +57 -0
  91. package/src/templates/skills/agentkit-channels/references/whatsapp-uazapi.md +54 -0
  92. package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +42 -8
  93. package/src/templates/skills/agentkit-database/SKILL.md +11 -0
  94. package/src/templates/skills/agentkit-deploy/SKILL.md +9 -1
  95. package/src/templates/skills/agentkit-evals/SKILL.md +77 -13
  96. package/src/templates/skills/agentkit-evals/templates/multi-turn.eval.md +13 -6
  97. package/src/templates/skills/agentkit-evals/templates/no-leak.eval.md +8 -4
  98. package/src/templates/skills/agentkit-evals/templates/smoke.eval.md +8 -4
  99. package/src/templates/skills/agentkit-evals/templates/tool-call.eval.md +16 -7
  100. package/src/templates/skills/agentkit-improve/SKILL.md +96 -0
  101. package/src/templates/skills/agentkit-improve/references/replay-side-effects.md +18 -0
  102. package/src/templates/skills/agentkit-improve/references/trace-packets.md +22 -0
  103. package/src/templates/skills/agentkit-improve/templates/regression.eval.md +18 -0
  104. package/src/templates/skills/agentkit-integrations/SKILL.md +98 -0
  105. package/src/templates/skills/agentkit-knowledge/SKILL.md +4 -1
  106. package/src/templates/skills/agentkit-prompts/SKILL.md +3 -1
  107. package/src/templates/skills/agentkit-provider/SKILL.md +29 -4
  108. package/src/templates/skills/agentkit-security/SKILL.md +5 -2
  109. package/src/templates/skills/agentkit-tools/SKILL.md +8 -1
  110. package/src/templates/skills/agentkit-tools/examples/eval-safe-external-action.tool.md +8 -8
  111. package/src/templates/skills/agentkit-tools/examples/jev-service-fit.tool.md +110 -0
  112. package/src/templates/skills/agentkit-troubleshooting/SKILL.md +25 -1
  113. package/src/templates/support.ts +42 -12
  114. package/docs/guides/agentkit-skills-architecture.md +0 -471
  115. package/docs/guides/channels-implementation-map.md +0 -243
  116. package/docs/guides/channels-production-handoff.md +0 -101
  117. package/docs/portable-deploy-release-checklist.md +0 -41
@@ -1,3 +1,4 @@
1
+ import { createHmac } from "node:crypto";
1
2
  import { readFile } from "node:fs/promises";
2
3
  import { resolve } from "node:path";
3
4
 
@@ -14,12 +15,18 @@ type DeployState = LocalDeployState;
14
15
  type CloudChannel = {
15
16
  id: string;
16
17
  name: string;
17
- type: "website" | "telegram" | "whatsapp";
18
- provider: "agentkit" | "telegram" | "zapster" | "meta";
18
+ type: "website" | "telegram" | "whatsapp" | "discord" | "slack" | "webhook";
19
+ provider: "agentkit" | "telegram" | "zapster" | "meta" | "uazapi" | "evolution" | "discord" | "slack" | "generic";
20
+ mode?: "interactions" | "bot";
21
+ direction?: "inbound" | "outbound";
19
22
  status: string;
20
23
  webhook_url: string;
21
24
  required_secrets: Array<{ name: string; status: string }>;
22
25
  buffer?: AgentChannel["buffer"];
26
+ audio?: AgentChannel["audio"];
27
+ reply_to?: Extract<AgentChannel, { replyTo?: unknown }>["replyTo"];
28
+ url_secret?: string;
29
+ auth?: Extract<AgentChannel, { type: "webhook"; direction: "outbound" }>["auth"];
23
30
  };
24
31
 
25
32
  type CloudDelivery = {
@@ -43,13 +50,44 @@ type CloudDelivery = {
43
50
  }>;
44
51
  };
45
52
 
53
+ type CloudChannelBuffer = {
54
+ channel_id: string;
55
+ deploy_id: string;
56
+ conversation_id: string;
57
+ job_id?: string;
58
+ delivery_id?: string;
59
+ buffered_delivery_ids: string[];
60
+ buffered_message_count: number;
61
+ buffered_char_count: number;
62
+ status: string;
63
+ first_received_at: string;
64
+ last_received_at: string;
65
+ available_at?: string;
66
+ age_ms: number;
67
+ stale: boolean;
68
+ message_preview: string;
69
+ };
70
+
71
+ type ChannelBufferActionResponse = {
72
+ buffer: CloudChannelBuffer | null;
73
+ job: {
74
+ id: string;
75
+ status: string;
76
+ conversation_id: string;
77
+ } | null;
78
+ cleared?: boolean;
79
+ retried?: boolean;
80
+ };
81
+
46
82
  type ChannelSetupResponse = {
47
83
  channel: CloudChannel;
48
84
  setup?: {
49
- provider: "telegram";
85
+ provider: "telegram" | "uazapi" | "evolution";
50
86
  webhook_url: string;
51
87
  status: "configured";
52
88
  bot_username?: string;
89
+ webhook_count?: number;
90
+ matched_webhook_id?: string;
53
91
  get_webhook_info?: {
54
92
  url: string;
55
93
  pending_update_count: number;
@@ -65,6 +103,7 @@ type ChannelDoctorReport = {
65
103
  message: string;
66
104
  fix?: string;
67
105
  }>;
106
+ buffers: CloudChannelBuffer[];
68
107
  last_delivery: {
69
108
  inbound: CloudDelivery | null;
70
109
  outbound: CloudDelivery | null;
@@ -76,7 +115,7 @@ type ChannelDoctorReport = {
76
115
  export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
77
116
  await loadLocalEnvIntoProcess(process.cwd());
78
117
 
79
- const [subcommand, first, second] = args.positional;
118
+ const [subcommand, first, second, third] = args.positional;
80
119
 
81
120
  if (subcommand === "list") {
82
121
  const deployState = await readDeployStateIfExists(process.cwd());
@@ -97,6 +136,7 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
97
136
  name: channel.name,
98
137
  type: channel.type,
99
138
  provider: channel.provider,
139
+ ...(channel.type === "discord" ? { mode: channel.mode } : {}),
100
140
  status: "local_config",
101
141
  webhook_url: "",
102
142
  required_secrets: channel.secrets.map((name) => ({ name, status: "missing" })),
@@ -109,11 +149,11 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
109
149
  const type = first;
110
150
  const name = second;
111
151
 
112
- if (!type || !name || (type !== "website" && type !== "telegram" && type !== "whatsapp")) {
113
- throw new Error("Usage: agentkit channels add <website|telegram|whatsapp> <name> [--provider zapster|meta]");
152
+ if (!type || !name || !isCliChannelType(type)) {
153
+ throw new Error("Usage: agentkit channels add <website|telegram|whatsapp|discord|slack|webhook> <name> [--provider zapster|meta|uazapi|evolution] [--mode interactions|bot]");
114
154
  }
115
155
 
116
- const response = await createHostedChannel(type, name, args.flags.provider, args.flags.api);
156
+ const response = await createHostedChannel(type, name, args.flags.provider, args.flags.mode, args.flags.api);
117
157
  printChannelStatus(response.channel);
118
158
  printSecretRepairHint(response.channel);
119
159
  return;
@@ -123,16 +163,18 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
123
163
  const type = first;
124
164
  const name = second;
125
165
 
126
- if (!type || !name || (type !== "website" && type !== "telegram" && type !== "whatsapp")) {
127
- throw new Error("Usage: agentkit channels connect <website|telegram|whatsapp> <name> [--provider zapster|meta]");
166
+ if (!type || !name || !isCliChannelType(type)) {
167
+ throw new Error("Usage: agentkit channels connect <website|telegram|whatsapp|discord|slack|webhook> <name> [--provider zapster|meta|uazapi|evolution] [--mode interactions|bot]");
128
168
  }
129
169
 
130
- const created = await createHostedChannel(type, name, args.flags.provider, args.flags.api);
170
+ const created = await createHostedChannel(type, name, args.flags.provider, args.flags.mode, args.flags.api);
131
171
  let channel = created.channel;
132
172
  printChannelStatus(channel);
133
173
  printSecretRepairHint(channel);
174
+ console.log("Channel resource: created_or_updated");
134
175
 
135
176
  if (hasMissingRequiredSecrets(channel)) {
177
+ console.log("Secrets: missing");
136
178
  console.log("");
137
179
  console.log("Connect blocked: set the missing managed secrets, then rerun this same command.");
138
180
  console.log("Run: npm run agentkit -- secret sync --from-local");
@@ -140,27 +182,104 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
140
182
  return;
141
183
  }
142
184
 
185
+ console.log("Secrets: validated");
186
+
143
187
  if (channel.type === "telegram") {
144
188
  const setup = await applyTelegramWebhook(channel, args.flags.api);
145
189
  channel = setup.channel ?? channel;
190
+ console.log("Telegram setWebhook: OK");
146
191
  console.log("Telegram webhook confirmed.");
147
192
  if (setup.setup?.get_webhook_info) {
148
193
  console.log(`Telegram pending updates: ${setup.setup.get_webhook_info.pending_update_count}`);
149
194
  }
150
195
 
151
- const smoke = await runChannelSmoke(channel, args.flags.api, args.flags.message);
152
- console.log(`Smoke: ${smoke.status}${smoke.delivery_id ? ` (${smoke.delivery_id})` : ""}`);
196
+ const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
153
197
  printTelegramHumanNextStep(channel.name, setup.setup?.bot_username);
198
+ if (!smoke) {
199
+ process.exitCode = 1;
200
+ }
201
+ return;
202
+ }
203
+
204
+ if (channel.type === "whatsapp" && channel.provider === "uazapi") {
205
+ const setup = await applyProviderWebhook(channel, args.flags.api);
206
+ channel = setup.channel ?? channel;
207
+ console.log("UAZAPI webhook setup: OK");
208
+ console.log("UAZAPI webhook confirmed.");
209
+ if (setup.setup?.webhook_count !== undefined) {
210
+ console.log(`UAZAPI webhooks: ${setup.setup.webhook_count}`);
211
+ }
212
+
213
+ const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
214
+ printUazapiHumanNextStep(channel);
215
+ if (!smoke) {
216
+ process.exitCode = 1;
217
+ }
218
+ return;
219
+ }
220
+
221
+ if (channel.type === "whatsapp" && channel.provider === "evolution") {
222
+ const setup = await applyProviderWebhook(channel, args.flags.api);
223
+ channel = setup.channel ?? channel;
224
+ console.log("Evolution webhook setup: OK");
225
+ console.log("Evolution webhook confirmed.");
226
+
227
+ const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
228
+ printEvolutionHumanNextStep(channel);
229
+ if (!smoke) {
230
+ process.exitCode = 1;
231
+ }
232
+ return;
233
+ }
234
+
235
+ if (channel.type === "discord") {
236
+ const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
237
+ printDiscordHumanNextStep(channel);
238
+ if (!smoke) {
239
+ process.exitCode = 1;
240
+ }
241
+ return;
242
+ }
243
+
244
+ if (channel.type === "slack") {
245
+ const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
246
+ printSlackHumanNextStep(channel);
247
+ if (!smoke) {
248
+ process.exitCode = 1;
249
+ }
250
+ return;
251
+ }
252
+
253
+ if (channel.type === "webhook") {
254
+ if (channel.direction === "outbound") {
255
+ console.log("Outbound webhook: configured");
256
+ console.log(`Next human step: make sure ${channel.url_secret ?? "the webhook URL secret"} is set as a managed secret.`);
257
+ return;
258
+ }
259
+
260
+ const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
261
+ printGenericWebhookHumanNextStep(channel);
262
+ if (!smoke) {
263
+ process.exitCode = 1;
264
+ }
154
265
  return;
155
266
  }
156
267
 
157
- if (channel.type === "whatsapp") {
268
+ if (channel.type === "whatsapp" && channel.provider === "zapster") {
269
+ const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
270
+ console.log(`Next human step: paste ${channel.webhook_url} into ${channel.provider}'s webhook settings.`);
271
+ if (!smoke) {
272
+ process.exitCode = 1;
273
+ }
274
+ } else if (channel.type === "whatsapp") {
158
275
  console.log("Smoke: skipped until the provider webhook is configured.");
159
276
  console.log(`Next human step: paste ${channel.webhook_url} into ${channel.provider}'s webhook settings.`);
160
277
  } else {
161
- const smoke = await runChannelSmoke(channel, args.flags.api, args.flags.message);
162
- console.log(`Smoke: ${smoke.status}${smoke.delivery_id ? ` (${smoke.delivery_id})` : ""}`);
278
+ const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
163
279
  console.log(`Next human step: use ${channel.webhook_url} from your website client.`);
280
+ if (!smoke) {
281
+ process.exitCode = 1;
282
+ }
164
283
  }
165
284
  return;
166
285
  }
@@ -182,8 +301,38 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
182
301
  console.log(`Setup: call Telegram setWebhook with ${channel.webhook_url}`);
183
302
  console.log("Use --apply to mutate Telegram webhook settings.");
184
303
  }
304
+ } else if (channel.type === "whatsapp" && channel.provider === "uazapi") {
305
+ if (args.flags.apply) {
306
+ const setup = await applyProviderWebhook(channel, args.flags.api);
307
+ console.log("UAZAPI webhook confirmed.");
308
+ printChannelStatus(setup.channel ?? channel);
309
+ printUazapiHumanNextStep(setup.channel ?? channel);
310
+ } else {
311
+ console.log(`Setup: configure UAZAPI /webhook with ${channel.webhook_url}`);
312
+ console.log("Use --apply to mutate UAZAPI webhook settings.");
313
+ }
314
+ } else if (channel.type === "whatsapp" && channel.provider === "evolution") {
315
+ if (args.flags.apply) {
316
+ const setup = await applyProviderWebhook(channel, args.flags.api);
317
+ console.log("Evolution webhook confirmed.");
318
+ printChannelStatus(setup.channel ?? channel);
319
+ printEvolutionHumanNextStep(setup.channel ?? channel);
320
+ } else {
321
+ console.log(`Setup: configure Evolution /webhook/set/{instance} with ${channel.webhook_url}`);
322
+ console.log("Use --apply to mutate Evolution webhook settings.");
323
+ }
185
324
  } else if (channel.type === "whatsapp") {
186
325
  console.log(`Next step: paste this URL into ${channel.provider}'s webhook settings.`);
326
+ } else if (channel.type === "discord") {
327
+ printDiscordHumanNextStep(channel);
328
+ } else if (channel.type === "slack") {
329
+ printSlackHumanNextStep(channel);
330
+ } else if (channel.type === "webhook") {
331
+ if (channel.direction === "outbound") {
332
+ console.log("Outbound webhook endpoint: configured from managed secrets.");
333
+ } else {
334
+ printGenericWebhookHumanNextStep(channel);
335
+ }
187
336
  } else {
188
337
  console.log(`Website endpoint: ${channel.webhook_url}`);
189
338
  console.log("Use this endpoint from the AgentKit website client or a signed server-to-server test.");
@@ -225,9 +374,32 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
225
374
  throw new Error('Usage: agentkit channels test <name> [--message "hello"] [--fixture <path>]');
226
375
  }
227
376
 
377
+ const channel = await resolveCloudChannelByName(first, args.flags.api);
378
+ if (channel.type === "webhook" && channel.direction === "outbound") {
379
+ throw new Error("Outbound webhook channels do not have an inbound test endpoint. Test them through a source channel that routes replies to this channel.");
380
+ }
381
+ const apiUrl = parseCloudApiUrl(args.flags.api);
382
+ const signed = channel.type === "whatsapp" && channel.provider === "evolution" && !args.flags.fixture
383
+ ? await runServerGeneratedChannelTest(channel, apiUrl, args.flags.message)
384
+ : await runServerSignedChannelTest(channel, apiUrl, await channelTestBody(channel, args.flags.message, args.flags.fixture));
385
+ const response = signed.response;
386
+ const payload = signed.payload;
387
+
388
+ console.log(JSON.stringify(payload, null, 2));
389
+ if (!response.ok) {
390
+ process.exitCode = 1;
391
+ }
392
+ return;
393
+ }
394
+
395
+ if (subcommand === "test-audio") {
396
+ if (!first) {
397
+ throw new Error("Usage: agentkit channels test-audio <name> [--fixture voice-note|audio-file|<path>]");
398
+ }
399
+
228
400
  const channel = await resolveCloudChannelByName(first, args.flags.api);
229
401
  const apiUrl = parseCloudApiUrl(args.flags.api);
230
- const body = await channelTestBody(channel, args.flags.message, args.flags.fixture);
402
+ const body = await channelAudioTestBody(channel, args.flags.fixture);
231
403
  const signed = await runServerSignedChannelTest(channel, apiUrl, body);
232
404
  const response = signed.response;
233
405
  const payload = signed.payload;
@@ -286,27 +458,77 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
286
458
  }
287
459
  }
288
460
 
461
+ if (subcommand === "buffers") {
462
+ if (first === "list") {
463
+ if (!second) {
464
+ throw new Error("Usage: agentkit channels buffers list <name>");
465
+ }
466
+
467
+ const channel = await resolveCloudChannelByName(second, args.flags.api);
468
+ const response = await cloudGet<{ buffers: CloudChannelBuffer[] }>(
469
+ parseCloudApiUrl(args.flags.api),
470
+ `/v1/channels/${encodeURIComponent(channel.id)}/buffers`,
471
+ );
472
+ printChannelBuffers(response.buffers);
473
+ return;
474
+ }
475
+
476
+ if (first === "show") {
477
+ if (!second) {
478
+ throw new Error("Usage: agentkit channels buffers show [<name>] <conversation-id>");
479
+ }
480
+
481
+ const target = await resolveChannelBufferTarget(second, third, args.flags.api);
482
+ const response = await cloudGet<{ buffer: CloudChannelBuffer }>(
483
+ parseCloudApiUrl(args.flags.api),
484
+ `/v1/channels/${encodeURIComponent(target.channel.id)}/buffers/${encodeURIComponent(target.conversationId)}`,
485
+ );
486
+ console.log(JSON.stringify(response.buffer, null, 2));
487
+ return;
488
+ }
489
+
490
+ if (first === "flush" || first === "clear" || first === "retry") {
491
+ if (!second) {
492
+ throw new Error(`Usage: agentkit channels buffers ${first} [<name>] <conversation-id>`);
493
+ }
494
+
495
+ const target = await resolveChannelBufferTarget(second, third, args.flags.api);
496
+ const response = await cloudPost<ChannelBufferActionResponse>(
497
+ parseCloudApiUrl(args.flags.api),
498
+ `/v1/channels/${encodeURIComponent(target.channel.id)}/buffers/${encodeURIComponent(target.conversationId)}/${first}`,
499
+ {},
500
+ );
501
+ printChannelBufferAction(first, response);
502
+ return;
503
+ }
504
+ }
505
+
289
506
  throw new Error(
290
- "Usage: agentkit channels list | add | connect | setup | status | doctor | test | deliveries list | deliveries show",
507
+ "Usage: agentkit channels list | add | connect | setup | status | doctor | test | test-audio | deliveries list | deliveries show | buffers list | buffers show | buffers flush | buffers clear | buffers retry",
291
508
  );
292
509
  }
293
510
 
294
511
  async function createHostedChannel(
295
- type: "website" | "telegram" | "whatsapp",
512
+ type: "website" | "telegram" | "whatsapp" | "discord" | "slack" | "webhook",
296
513
  name: string,
297
514
  providerFlag: string | boolean | undefined,
515
+ modeFlag: string | boolean | undefined,
298
516
  apiFlag: string | boolean | undefined,
299
517
  ): Promise<{ channel: CloudChannel }> {
300
518
  const deployState = await readDeployStateRequired(process.cwd());
301
519
  const provider = channelProviderForCli(type, providerFlag);
302
520
  const localChannel = await readLocalChannelConfig(process.cwd(), type, provider, name);
521
+ const mode = discordModeForCli(type, modeFlag, localChannel);
303
522
  const payload = {
304
523
  name,
305
524
  type,
306
525
  provider,
307
- secrets: localChannel?.secrets ?? defaultChannelSecretsForCli(type, provider),
526
+ ...(mode ? { mode } : {}),
527
+ secrets: localChannel?.secrets ?? defaultChannelSecretsForCli(type, provider, mode),
308
528
  ...(localChannel?.limits ? { limits: channelLimitsForApi(localChannel.limits) } : {}),
309
529
  ...(localChannel?.buffer ? { buffer: localChannel.buffer } : {}),
530
+ ...(localChannel?.audio ? { audio: localChannel.audio } : {}),
531
+ ...channelExtraConfigForApi(localChannel),
310
532
  };
311
533
 
312
534
  return cloudPost<{ channel: CloudChannel }>(
@@ -321,8 +543,10 @@ async function runChannelSmoke(
321
543
  apiFlag: string | boolean | undefined,
322
544
  messageFlag: string | boolean | undefined,
323
545
  ): Promise<{ status: string; delivery_id?: string }> {
324
- const body = await channelTestBody(channel, messageFlag, undefined);
325
- const { response, payload } = await runServerSignedChannelTest(channel, parseCloudApiUrl(apiFlag), body);
546
+ const apiUrl = parseCloudApiUrl(apiFlag);
547
+ const { response, payload } = channel.type === "whatsapp" && channel.provider === "evolution"
548
+ ? await runServerGeneratedChannelTest(channel, apiUrl, messageFlag)
549
+ : await runServerSignedChannelTest(channel, apiUrl, await channelTestBody(channel, messageFlag, undefined));
326
550
 
327
551
  if (!response.ok) {
328
552
  throw new Error(readCloudError(payload, response.status));
@@ -338,6 +562,49 @@ async function runChannelSmoke(
338
562
  return { status: "unknown" };
339
563
  }
340
564
 
565
+ async function runServerGeneratedChannelTest(
566
+ channel: CloudChannel,
567
+ apiUrl: string,
568
+ messageFlag: string | boolean | undefined,
569
+ ): Promise<{ response: Response; payload: unknown }> {
570
+ const body: Record<string, unknown> = {};
571
+
572
+ if (typeof messageFlag === "string") {
573
+ body.message = messageFlag;
574
+ }
575
+
576
+ const response = await cloudFetch(apiUrl, `/v1/channels/${encodeURIComponent(channel.id)}/test`, {
577
+ method: "POST",
578
+ headers: { "Content-Type": "application/json" },
579
+ body: JSON.stringify(body),
580
+ });
581
+ const payload = await response.json().catch(() => null);
582
+
583
+ if (!response.ok && isUnsupportedChannelTestRoute(payload)) {
584
+ return runLocallySignedChannelTest(
585
+ channel,
586
+ apiUrl,
587
+ await channelTestBody(channel, messageFlag, undefined),
588
+ );
589
+ }
590
+
591
+ return { response, payload };
592
+ }
593
+
594
+ async function runConnectSmoke(
595
+ run: () => Promise<{ status: string; delivery_id?: string }>,
596
+ ): Promise<{ status: string; delivery_id?: string } | null> {
597
+ try {
598
+ const smoke = await run();
599
+ console.log(`Synthetic smoke: ${smoke.status}${smoke.delivery_id ? ` (${smoke.delivery_id})` : ""}`);
600
+ return smoke;
601
+ } catch (error) {
602
+ console.log("Synthetic smoke: failed");
603
+ console.log(` ${formatCommandError(error)}`);
604
+ return null;
605
+ }
606
+ }
607
+
341
608
  async function runServerSignedChannelTest(
342
609
  channel: CloudChannel,
343
610
  apiUrl: string,
@@ -362,8 +629,22 @@ async function runLocallySignedChannelTest(
362
629
  apiUrl: string,
363
630
  rawBody: string,
364
631
  ): Promise<{ response: Response; payload: unknown }> {
365
- const path = new URL(channel.webhook_url).pathname;
366
- const headers = channelTestHeaders(channel);
632
+ const webhookUrl = new URL(channel.webhook_url);
633
+
634
+ if (channel.required_secrets.some((secret) => secret.name === "ZAPSTER_WEBHOOK_TOKEN")) {
635
+ webhookUrl.searchParams.set("token", process.env.ZAPSTER_WEBHOOK_TOKEN ?? "");
636
+ }
637
+
638
+ if (channel.required_secrets.some((secret) => secret.name === "UAZAPI_WEBHOOK_TOKEN")) {
639
+ webhookUrl.searchParams.set("token", process.env.UAZAPI_WEBHOOK_TOKEN ?? "");
640
+ }
641
+
642
+ if (channel.required_secrets.some((secret) => secret.name === "EVOLUTION_WEBHOOK_TOKEN")) {
643
+ webhookUrl.searchParams.set("token", process.env.EVOLUTION_WEBHOOK_TOKEN ?? "");
644
+ }
645
+
646
+ const path = `${webhookUrl.pathname}${webhookUrl.search}`;
647
+ const headers = channelTestHeaders(channel, rawBody);
367
648
  const response = await cloudFetch(apiUrl, path, {
368
649
  method: "POST",
369
650
  headers,
@@ -465,6 +746,17 @@ async function applyTelegramWebhook(
465
746
  };
466
747
  }
467
748
 
749
+ async function applyProviderWebhook(
750
+ channel: CloudChannel,
751
+ apiFlag: string | boolean | undefined,
752
+ ): Promise<ChannelSetupResponse> {
753
+ return cloudPost<ChannelSetupResponse>(
754
+ parseCloudApiUrl(apiFlag),
755
+ `/v1/channels/${encodeURIComponent(channel.id)}/setup`,
756
+ {},
757
+ );
758
+ }
759
+
468
760
  function shouldFallbackToLocalTelegramSetup(error: unknown): boolean {
469
761
  if (!(error instanceof Error)) {
470
762
  return false;
@@ -510,14 +802,80 @@ function printTelegramHumanNextStep(channelName: string, botUsername: string | u
510
802
  console.log(` Then run: npm run agentkit -- channels deliveries list ${channelName}`);
511
803
  }
512
804
 
805
+ function printUazapiHumanNextStep(channel: CloudChannel): void {
806
+ console.log("");
807
+ console.log("Next human step:");
808
+ console.log(" Confirm the UAZAPI instance is connected to WhatsApp");
809
+ console.log(' Send "hello" from a real WhatsApp contact');
810
+ console.log(` Then run: npm run agentkit -- channels deliveries list ${channel.name}`);
811
+ }
812
+
813
+ function printEvolutionHumanNextStep(channel: CloudChannel): void {
814
+ console.log("");
815
+ console.log("Next human step:");
816
+ console.log(" Confirm the Evolution API instance is connected to WhatsApp");
817
+ console.log(' Send "hello" from a real WhatsApp contact');
818
+ console.log(` Then run: npm run agentkit -- channels deliveries list ${channel.name}`);
819
+ }
820
+
821
+ function printDiscordHumanNextStep(channel: CloudChannel): void {
822
+ console.log("");
823
+ console.log("Next human step:");
824
+
825
+ if (channel.mode === "bot") {
826
+ console.log(" Open the Discord Developer Portal for your application");
827
+ console.log(" Bot page: enable Message Content Intent");
828
+ console.log(" Installation page: install the app into your server with bot permissions");
829
+ console.log(" Grant the bot View Channel, Read Message History, and Send Messages in the channels it should answer");
830
+ console.log(' Send "hello" in an allowed server channel');
831
+ console.log(` Then run: npm run agentkit -- channels deliveries list ${channel.name}`);
832
+ return;
833
+ }
834
+
835
+ console.log(" Open the Discord Developer Portal for your application");
836
+ console.log(` Paste ${channel.webhook_url} into Interactions Endpoint URL`);
837
+ console.log(" Register a slash command with a string option named message, text, prompt, question, query, or input");
838
+ console.log(` Then run: npm run agentkit -- channels deliveries list ${channel.name}`);
839
+ }
840
+
841
+ function printSlackHumanNextStep(channel: CloudChannel): void {
842
+ console.log("");
843
+ console.log("Next human step:");
844
+ console.log(" Open the Slack app configuration");
845
+ console.log(" Event Subscriptions: enable events");
846
+ console.log(` Request URL: paste ${channel.webhook_url}`);
847
+ console.log(" Subscribe to bot events: app_mention and message.im");
848
+ console.log(" OAuth scopes: app_mentions:read, im:history, chat:write");
849
+ console.log(" Install or reinstall the app to the workspace");
850
+ console.log(' Mention the app with "hello" or send it a direct message');
851
+ console.log(` Then run: npm run agentkit -- channels deliveries list ${channel.name}`);
852
+ }
853
+
854
+ function printGenericWebhookHumanNextStep(channel: CloudChannel): void {
855
+ console.log("");
856
+ console.log("Next human step:");
857
+ console.log(` Point n8n, Make, Zapier, or your server to ${channel.webhook_url}`);
858
+ console.log(" Send JSON with event_id, external_id, and message");
859
+ console.log(" Authenticate with Authorization: Bearer <AGENTKIT_WEBHOOK_SECRET>");
860
+ console.log(` Then run: npm run agentkit -- channels deliveries list ${channel.name}`);
861
+ }
862
+
513
863
  function isRecord(value: unknown): value is Record<string, unknown> {
514
864
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
515
865
  }
516
866
 
867
+ function formatCommandError(error: unknown): string {
868
+ return error instanceof Error ? error.message : String(error);
869
+ }
870
+
517
871
  async function readDeployStateIfExists(cwd: string): Promise<DeployState | null> {
518
872
  return readLocalDeployStateIfExists(cwd);
519
873
  }
520
874
 
875
+ function isCliChannelType(value: string): value is "website" | "telegram" | "whatsapp" | "discord" | "slack" | "webhook" {
876
+ return value === "website" || value === "telegram" || value === "whatsapp" || value === "discord" || value === "slack" || value === "webhook";
877
+ }
878
+
521
879
  async function readDeployStateRequired(cwd: string): Promise<DeployState> {
522
880
  const state = await readDeployStateIfExists(cwd);
523
881
 
@@ -547,12 +905,21 @@ function printChannelStatus(channel: CloudChannel): void {
547
905
  }
548
906
  console.log(`Type: ${channel.type}`);
549
907
  console.log(`Provider: ${channel.provider}`);
908
+ if (channel.type === "discord") {
909
+ console.log(`Mode: ${channel.mode ?? "interactions"}`);
910
+ }
911
+ if (channel.type === "webhook" && channel.direction) {
912
+ console.log(`Direction: ${channel.direction}`);
913
+ }
550
914
  console.log(`Status: ${channel.status}`);
551
915
  if (channel.webhook_url) {
552
916
  console.log(`Webhook URL: ${channel.webhook_url}`);
553
917
  }
554
918
  console.log("Required secrets:");
555
- if (channel.required_secrets.length === 0 && defaultChannelSecretsForCli(channel.type, channel.provider).length > 0) {
919
+ if (
920
+ channel.required_secrets.length === 0 &&
921
+ defaultChannelSecretsForCli(channel.type, channel.provider, channel.mode).length > 0
922
+ ) {
556
923
  console.log(
557
924
  ` control-plane channel metadata bug: required_secrets is empty for ${channel.type}/${channel.provider}`,
558
925
  );
@@ -573,7 +940,10 @@ function printChannelStatus(channel: CloudChannel): void {
573
940
  }
574
941
 
575
942
  function printSecretRepairHint(channel: CloudChannel): void {
576
- if (channel.required_secrets.length > 0 || defaultChannelSecretsForCli(channel.type, channel.provider).length === 0) {
943
+ if (
944
+ channel.required_secrets.length > 0 ||
945
+ defaultChannelSecretsForCli(channel.type, channel.provider, channel.mode).length === 0
946
+ ) {
577
947
  return;
578
948
  }
579
949
 
@@ -583,7 +953,7 @@ function printSecretRepairHint(channel: CloudChannel): void {
583
953
  }
584
954
 
585
955
  function hasMissingRequiredSecrets(channel: CloudChannel): boolean {
586
- const expected = defaultChannelSecretsForCli(channel.type, channel.provider);
956
+ const expected = defaultChannelSecretsForCli(channel.type, channel.provider, channel.mode);
587
957
 
588
958
  if (channel.required_secrets.length === 0 && expected.length > 0) {
589
959
  return true;
@@ -629,6 +999,16 @@ function printChannelDoctor(report: ChannelDoctorReport): void {
629
999
  ` Outbound: ${report.last_delivery.outbound ? `${report.last_delivery.outbound.status} ${report.last_delivery.outbound.updated_at ?? report.last_delivery.outbound.received_at}` : "none"}`,
630
1000
  );
631
1001
 
1002
+ if (report.buffers.length > 0) {
1003
+ console.log("");
1004
+ console.log("Buffers:");
1005
+ for (const buffer of report.buffers) {
1006
+ console.log(
1007
+ ` ${buffer.conversation_id}: ${buffer.status} messages=${buffer.buffered_message_count} stale=${buffer.stale ? "yes" : "no"}`,
1008
+ );
1009
+ }
1010
+ }
1011
+
632
1012
  if (report.probable_error) {
633
1013
  console.log("");
634
1014
  console.log(`Probable error: ${report.probable_error}`);
@@ -654,19 +1034,119 @@ function formatCheckStatus(status: ChannelDoctorReport["checks"][number]["status
654
1034
  return "UNKNOWN";
655
1035
  }
656
1036
 
1037
+ function printChannelBuffers(buffers: CloudChannelBuffer[]): void {
1038
+ if (buffers.length === 0) {
1039
+ console.log("No channel buffers found.");
1040
+ return;
1041
+ }
1042
+
1043
+ console.log("CONVERSATION\tSTATUS\tMESSAGES\tCHARS\tSTALE\tAVAILABLE_AT\tJOB");
1044
+ for (const buffer of buffers) {
1045
+ console.log(
1046
+ [
1047
+ buffer.conversation_id,
1048
+ buffer.status,
1049
+ String(buffer.buffered_message_count),
1050
+ String(buffer.buffered_char_count),
1051
+ buffer.stale ? "yes" : "no",
1052
+ buffer.available_at ?? "",
1053
+ buffer.job_id ?? "",
1054
+ ].join("\t"),
1055
+ );
1056
+ }
1057
+ }
1058
+
1059
+ function printChannelBufferAction(action: string, response: ChannelBufferActionResponse): void {
1060
+ const conversationId = response.buffer?.conversation_id ?? response.job?.conversation_id ?? "unknown";
1061
+
1062
+ if (action === "flush") {
1063
+ console.log(
1064
+ response.job
1065
+ ? `Flushed buffer ${conversationId}; queued job ${response.job.id}.`
1066
+ : `No queued buffer was flushed for ${conversationId}.`,
1067
+ );
1068
+ return;
1069
+ }
1070
+
1071
+ if (action === "clear") {
1072
+ console.log(
1073
+ response.cleared
1074
+ ? `Cleared buffer ${conversationId}.`
1075
+ : `No queued buffer was cleared for ${conversationId}.`,
1076
+ );
1077
+ return;
1078
+ }
1079
+
1080
+ console.log(
1081
+ response.retried && response.job
1082
+ ? `Retried channel job ${response.job.id} for ${conversationId}.`
1083
+ : `No failed or dead-lettered job was retried for ${conversationId}.`,
1084
+ );
1085
+ }
1086
+
657
1087
  async function resolveCloudChannelByName(name: string, apiFlag: string | boolean | undefined): Promise<CloudChannel> {
1088
+ const channels = await listCloudChannelsForCurrentDeploy(apiFlag);
1089
+ const channel = channels.find((candidate) => candidate.name === name);
1090
+
1091
+ if (!channel) {
1092
+ const deployState = await readDeployStateRequired(process.cwd());
1093
+ throw new Error(`Channel "${name}" was not found for deploy ${deployState.deploy_id}.`);
1094
+ }
1095
+
1096
+ return channel;
1097
+ }
1098
+
1099
+ async function listCloudChannelsForCurrentDeploy(apiFlag: string | boolean | undefined): Promise<CloudChannel[]> {
658
1100
  const deployState = await readDeployStateRequired(process.cwd());
659
1101
  const response = await cloudGet<{ channels: CloudChannel[] }>(
660
1102
  parseCloudApiUrl(apiFlag),
661
1103
  `/v1/deploys/${encodeURIComponent(deployState.deploy_id)}/channels`,
662
1104
  );
663
- const channel = response.channels.find((candidate) => candidate.name === name);
664
1105
 
665
- if (!channel) {
666
- throw new Error(`Channel "${name}" was not found for deploy ${deployState.deploy_id}.`);
1106
+ return response.channels;
1107
+ }
1108
+
1109
+ async function resolveChannelBufferTarget(
1110
+ first: string,
1111
+ second: string | undefined,
1112
+ apiFlag: string | boolean | undefined,
1113
+ ): Promise<{ channel: CloudChannel; conversationId: string }> {
1114
+ if (second) {
1115
+ return {
1116
+ channel: await resolveCloudChannelByName(first, apiFlag),
1117
+ conversationId: second,
1118
+ };
667
1119
  }
668
1120
 
669
- return channel;
1121
+ const conversationId = first;
1122
+ const channels = await listCloudChannelsForCurrentDeploy(apiFlag);
1123
+ const matches: CloudChannel[] = [];
1124
+
1125
+ for (const channel of channels) {
1126
+ const response = await cloudGet<{ buffers: CloudChannelBuffer[] }>(
1127
+ parseCloudApiUrl(apiFlag),
1128
+ `/v1/channels/${encodeURIComponent(channel.id)}/buffers`,
1129
+ );
1130
+
1131
+ if (response.buffers.some((buffer) => buffer.conversation_id === conversationId)) {
1132
+ matches.push(channel);
1133
+ }
1134
+ }
1135
+
1136
+ if (matches.length === 1) {
1137
+ return {
1138
+ channel: matches[0],
1139
+ conversationId,
1140
+ };
1141
+ }
1142
+
1143
+ if (matches.length > 1) {
1144
+ throw new Error(
1145
+ `Conversation ${conversationId} has buffers in multiple channels. Use: agentkit channels buffers show <name> ${conversationId}`,
1146
+ );
1147
+ }
1148
+
1149
+ throw new Error(`No channel buffer found for conversation ${conversationId}.`);
670
1150
  }
671
1151
 
672
1152
  function channelProviderForCli(type: string, providerFlag: string | boolean | undefined): string {
@@ -678,16 +1158,60 @@ function channelProviderForCli(type: string, providerFlag: string | boolean | un
678
1158
  return "telegram";
679
1159
  }
680
1160
 
1161
+ if (type === "discord") {
1162
+ return "discord";
1163
+ }
1164
+
1165
+ if (type === "slack") {
1166
+ if (providerFlag !== undefined) {
1167
+ throw new Error("--provider is not supported for Slack channels.");
1168
+ }
1169
+
1170
+ return "slack";
1171
+ }
1172
+
1173
+ if (type === "webhook") {
1174
+ if (providerFlag !== undefined) {
1175
+ throw new Error("--provider is not supported for generic webhook channels.");
1176
+ }
1177
+
1178
+ return "generic";
1179
+ }
1180
+
681
1181
  const provider = providerFlag ?? "zapster";
682
1182
 
683
- if (provider !== "zapster" && provider !== "meta") {
684
- throw new Error('WhatsApp provider must be "zapster" or "meta".');
1183
+ if (provider !== "zapster" && provider !== "meta" && provider !== "uazapi" && provider !== "evolution") {
1184
+ throw new Error('WhatsApp provider must be "zapster", "meta", "uazapi", or "evolution".');
685
1185
  }
686
1186
 
687
1187
  return provider;
688
1188
  }
689
1189
 
690
- function defaultChannelSecretsForCli(type: string, provider: string): string[] {
1190
+ function discordModeForCli(
1191
+ type: string,
1192
+ modeFlag: string | boolean | undefined,
1193
+ localChannel: AgentChannel | null,
1194
+ ): "interactions" | "bot" | undefined {
1195
+ if (type !== "discord") {
1196
+ if (modeFlag !== undefined) {
1197
+ throw new Error("--mode is only supported for Discord channels.");
1198
+ }
1199
+
1200
+ return undefined;
1201
+ }
1202
+
1203
+ if (modeFlag !== undefined) {
1204
+ if (modeFlag !== "interactions" && modeFlag !== "bot") {
1205
+ throw new Error('Discord mode must be "interactions" or "bot".');
1206
+ }
1207
+
1208
+ return modeFlag;
1209
+ }
1210
+
1211
+ return localChannel?.type === "discord" ? localChannel.mode : "interactions";
1212
+ }
1213
+
1214
+ function defaultChannelSecretsForCli(type: string, provider: string, mode?: "interactions" | "bot"): string[] {
691
1215
  if (type === "website") {
692
1216
  return ["AGENTKIT_WEBSITE_CHANNEL_TOKEN"];
693
1217
  }
@@ -697,16 +1221,49 @@ function defaultChannelSecretsForCli(type: string, provider: string): string[] {
697
1221
  }
698
1222
 
699
1223
  if (type === "whatsapp" && provider === "zapster") {
700
- return ["ZAPSTER_API_KEY", "ZAPSTER_WEBHOOK_SECRET"];
1224
+ return ["ZAPSTER_API_KEY", "ZAPSTER_INSTANCE_ID", "ZAPSTER_WEBHOOK_ID", "ZAPSTER_WEBHOOK_TOKEN"];
701
1225
  }
702
1226
 
703
1227
  if (type === "whatsapp" && provider === "meta") {
704
1228
  return ["META_WHATSAPP_ACCESS_TOKEN", "META_WHATSAPP_APP_SECRET", "META_WHATSAPP_VERIFY_TOKEN"];
705
1229
  }
706
1230
 
1231
+ if (type === "whatsapp" && provider === "uazapi") {
1232
+ return ["UAZAPI_BASE_URL", "UAZAPI_TOKEN", "UAZAPI_WEBHOOK_TOKEN"];
1233
+ }
1234
+
1235
+ if (type === "whatsapp" && provider === "evolution") {
1236
+ return ["EVOLUTION_API_BASE_URL", "EVOLUTION_API_KEY", "EVOLUTION_INSTANCE_NAME", "EVOLUTION_WEBHOOK_TOKEN"];
1237
+ }
1238
+
1239
+ if (type === "discord") {
1240
+ return mode === "bot" ? ["DISCORD_BOT_TOKEN"] : ["DISCORD_PUBLIC_KEY"];
1241
+ }
1242
+
1243
+ if (type === "slack") {
1244
+ return ["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"];
1245
+ }
1246
+
1247
+ if (type === "webhook") {
1248
+ return ["AGENTKIT_WEBHOOK_SECRET"];
1249
+ }
1250
+
707
1251
  return [];
708
1252
  }
709
1253
 
1254
+ function channelExtraConfigForApi(channel: AgentChannel | null): Record<string, unknown> {
1255
+ if (!channel) {
1256
+ return {};
1257
+ }
1258
+
1259
+ return {
1260
+ ...("direction" in channel && channel.direction ? { direction: channel.direction } : {}),
1261
+ ...(channel.replyTo ? { reply_to: channel.replyTo } : {}),
1262
+ ...("urlSecret" in channel && channel.urlSecret ? { url_secret: channel.urlSecret } : {}),
1263
+ ...("auth" in channel && channel.auth ? { auth: channel.auth } : {}),
1264
+ };
1265
+ }
1266
+
710
1267
  async function readLocalChannelConfig(
711
1268
  cwd: string,
712
1269
  type: string,
@@ -754,6 +1311,45 @@ async function channelTestBody(
754
1311
  });
755
1312
  }
756
1313
 
1314
+ if (channel.type === "whatsapp" && channel.provider === "uazapi") {
1315
+ return JSON.stringify({
1316
+ EventType: "messages",
1317
+ event: "messages",
1318
+ message: {
1319
+ id: `uaz_msg_${Date.now()}`,
1320
+ messageid: `uaz_wamid_${Date.now()}`,
1321
+ chatid: "5511999999999@s.whatsapp.net",
1322
+ sender: "5511999999999@s.whatsapp.net",
1323
+ senderName: "AgentKit",
1324
+ fromMe: false,
1325
+ wasSentByApi: false,
1326
+ messageType: "text",
1327
+ text: message,
1328
+ messageTimestamp: Date.now(),
1329
+ },
1330
+ });
1331
+ }
1332
+
1333
+ if (channel.type === "whatsapp" && channel.provider === "evolution") {
1334
+ return JSON.stringify({
1335
+ event: "MESSAGES_UPSERT",
1336
+ instance: process.env.EVOLUTION_INSTANCE_NAME ?? "local",
1337
+ data: {
1338
+ key: {
1339
+ remoteJid: "5511999999999@s.whatsapp.net",
1340
+ fromMe: false,
1341
+ id: `evo_wamid_${Date.now()}`,
1342
+ },
1343
+ pushName: "AgentKit",
1344
+ messageType: "conversation",
1345
+ message: {
1346
+ conversation: message,
1347
+ },
1348
+ messageTimestamp: Math.floor(Date.now() / 1000),
1349
+ },
1350
+ });
1351
+ }
1352
+
757
1353
  if (channel.type === "whatsapp") {
758
1354
  return JSON.stringify({
759
1355
  event_id: `evt_${Date.now()}`,
@@ -767,6 +1363,85 @@ async function channelTestBody(
767
1363
  });
768
1364
  }
769
1365
 
1366
+ if (channel.type === "discord") {
1367
+ if (channel.mode === "bot") {
1368
+ return JSON.stringify({
1369
+ id: `discord_message_${Date.now()}`,
1370
+ guild_id: "agentkit_guild",
1371
+ channel_id: "agentkit_channel",
1372
+ author: {
1373
+ id: "agentkit_user",
1374
+ username: "agentkit",
1375
+ },
1376
+ member: {
1377
+ nick: "AgentKit",
1378
+ },
1379
+ content: message,
1380
+ mentions: [],
1381
+ timestamp: new Date().toISOString(),
1382
+ });
1383
+ }
1384
+
1385
+ return JSON.stringify({
1386
+ id: `discord_interaction_${Date.now()}`,
1387
+ application_id: "agentkit_discord_app",
1388
+ type: 2,
1389
+ data: {
1390
+ id: "agentkit_command",
1391
+ name: "ask",
1392
+ type: 1,
1393
+ options: [
1394
+ {
1395
+ name: "message",
1396
+ type: 3,
1397
+ value: message,
1398
+ },
1399
+ ],
1400
+ },
1401
+ guild_id: "agentkit_guild",
1402
+ channel_id: "agentkit_channel",
1403
+ member: {
1404
+ user: {
1405
+ id: "agentkit_user",
1406
+ username: "agentkit",
1407
+ },
1408
+ },
1409
+ token: `agentkit_interaction_token_${Date.now()}`,
1410
+ version: 1,
1411
+ });
1412
+ }
1413
+
1414
+ if (channel.type === "slack") {
1415
+ const now = Date.now();
1416
+
1417
+ return JSON.stringify({
1418
+ token: "deprecated-verification-token",
1419
+ team_id: "T_AGENTKIT",
1420
+ api_app_id: "A_AGENTKIT",
1421
+ type: "event_callback",
1422
+ event_id: `Ev${now}`,
1423
+ event_time: Math.floor(now / 1000),
1424
+ event: {
1425
+ type: "app_mention",
1426
+ user: "UAGENTKITUSER",
1427
+ text: `<@UAGENTKITBOT> ${message}`,
1428
+ ts: `${Math.floor(now / 1000)}.000100`,
1429
+ channel: "C_AGENTKIT",
1430
+ event_ts: `${Math.floor(now / 1000)}.000100`,
1431
+ },
1432
+ authorizations: [],
1433
+ is_ext_shared_channel: false,
1434
+ });
1435
+ }
1436
+
1437
+ if (channel.type === "webhook") {
1438
+ return JSON.stringify({
1439
+ event_id: `evt_${Date.now()}`,
1440
+ external_id: "agentkit-webhook-test",
1441
+ message,
1442
+ });
1443
+ }
1444
+
770
1445
  return JSON.stringify({
771
1446
  event_id: `web_${Date.now()}`,
772
1447
  visitor_id: "visitor_test",
@@ -777,7 +1452,127 @@ async function channelTestBody(
777
1452
  });
778
1453
  }
779
1454
 
780
- function channelTestHeaders(channel: CloudChannel): Headers {
1455
+ async function channelAudioTestBody(
1456
+ channel: CloudChannel,
1457
+ fixtureFlag: string | boolean | undefined,
1458
+ ): Promise<string> {
1459
+ if (typeof fixtureFlag === "string" && isExplicitFixturePath(fixtureFlag)) {
1460
+ return readFile(resolve(process.cwd(), fixtureFlag), "utf8");
1461
+ }
1462
+
1463
+ const fixture = typeof fixtureFlag === "string" ? fixtureFlag : "voice-note";
1464
+
1465
+ if (channel.type === "telegram") {
1466
+ if (fixture !== "voice-note" && fixture !== "telegram-voice-note" && fixture !== "telegram-audio-message") {
1467
+ throw new Error('Telegram audio smoke supports --fixture voice-note or --fixture telegram-voice-note.');
1468
+ }
1469
+
1470
+ return JSON.stringify({
1471
+ update_id: Date.now(),
1472
+ message: {
1473
+ message_id: 1,
1474
+ date: Math.floor(Date.now() / 1000),
1475
+ chat: { id: 456, type: "private" },
1476
+ from: { id: 123, first_name: "AgentKit" },
1477
+ voice: {
1478
+ file_id: `agentkit_voice_${Date.now()}`,
1479
+ file_unique_id: `agentkit_voice_unique_${Date.now()}`,
1480
+ duration: 12,
1481
+ mime_type: "audio/ogg",
1482
+ file_size: 42,
1483
+ },
1484
+ },
1485
+ });
1486
+ }
1487
+
1488
+ if (channel.type === "whatsapp" && channel.provider === "zapster") {
1489
+ if (fixture !== "voice-note" && fixture !== "audio-file" && fixture !== "zapster-audio" && fixture !== "zapster-audio-message") {
1490
+ throw new Error('Zapster audio smoke supports --fixture voice-note, --fixture audio-file, or --fixture zapster-audio.');
1491
+ }
1492
+
1493
+ return JSON.stringify({
1494
+ event_id: `zap_evt_audio_${Date.now()}`,
1495
+ instance_id: "local",
1496
+ message: {
1497
+ id: `wamid.audio.${Date.now()}`,
1498
+ from: "5511999999999",
1499
+ type: "audio",
1500
+ audio: {
1501
+ id: `media_audio_${Date.now()}`,
1502
+ url: `https://api.zapsterapi.com/media/audio/media_audio_${Date.now()}.ogg`,
1503
+ mime_type: "audio/ogg",
1504
+ duration: 12,
1505
+ file_size: 42,
1506
+ ...(fixture === "voice-note" ? { ptt: "true" } : {}),
1507
+ },
1508
+ },
1509
+ });
1510
+ }
1511
+
1512
+ if (channel.type === "whatsapp" && channel.provider === "uazapi") {
1513
+ if (fixture !== "voice-note" && fixture !== "audio-file" && fixture !== "uazapi-audio" && fixture !== "uazapi-audio-message") {
1514
+ throw new Error('UAZAPI audio smoke supports --fixture voice-note, --fixture audio-file, or --fixture uazapi-audio.');
1515
+ }
1516
+
1517
+ return JSON.stringify({
1518
+ EventType: "messages",
1519
+ event: "messages",
1520
+ message: {
1521
+ id: `uaz_audio_${Date.now()}`,
1522
+ messageid: `uaz_wamid_audio_${Date.now()}`,
1523
+ chatid: "5511999999999@s.whatsapp.net",
1524
+ sender: "5511999999999@s.whatsapp.net",
1525
+ senderName: "AgentKit",
1526
+ fromMe: false,
1527
+ wasSentByApi: false,
1528
+ messageType: fixture === "voice-note" ? "ptt" : "audio",
1529
+ mimetype: "audio/ogg",
1530
+ duration: 12,
1531
+ file_size: 42,
1532
+ messageTimestamp: Date.now(),
1533
+ },
1534
+ });
1535
+ }
1536
+
1537
+ if (channel.type === "whatsapp" && channel.provider === "evolution") {
1538
+ if (fixture !== "voice-note" && fixture !== "audio-file" && fixture !== "evolution-audio" && fixture !== "evolution-audio-message") {
1539
+ throw new Error(
1540
+ 'Evolution audio smoke supports --fixture voice-note, --fixture audio-file, --fixture evolution-audio, or --fixture evolution-audio-message.',
1541
+ );
1542
+ }
1543
+
1544
+ return JSON.stringify({
1545
+ event: "MESSAGES_UPSERT",
1546
+ instance: process.env.EVOLUTION_INSTANCE_NAME ?? "local",
1547
+ data: {
1548
+ key: {
1549
+ remoteJid: "5511999999999@s.whatsapp.net",
1550
+ fromMe: false,
1551
+ id: `evo_wamid_audio_${Date.now()}`,
1552
+ },
1553
+ pushName: "AgentKit",
1554
+ messageType: "audioMessage",
1555
+ message: {
1556
+ audioMessage: {
1557
+ mimetype: "audio/ogg",
1558
+ seconds: 12,
1559
+ ptt: fixture === "voice-note",
1560
+ fileLength: 42,
1561
+ },
1562
+ },
1563
+ messageTimestamp: Math.floor(Date.now() / 1000),
1564
+ },
1565
+ });
1566
+ }
1567
+
1568
+ throw new Error(`Audio smoke is not supported for ${channel.type}/${channel.provider}.`);
1569
+ }
1570
+
1571
+ function isExplicitFixturePath(value: string): boolean {
1572
+ return value.includes("/") || value.startsWith(".") || value.endsWith(".json");
1573
+ }
1574
+
1575
+ function channelTestHeaders(channel: CloudChannel, rawBody: string): Headers {
781
1576
  const headers = new Headers({ "Content-Type": "application/json" });
782
1577
 
783
1578
  if (channel.type === "website") {
@@ -793,7 +1588,32 @@ function channelTestHeaders(channel: CloudChannel): Headers {
793
1588
  }
794
1589
 
795
1590
  if (channel.type === "whatsapp" && channel.provider === "zapster") {
796
- headers.set("X-Zapster-Webhook-Secret", process.env.ZAPSTER_WEBHOOK_SECRET ?? "");
1591
+ headers.set("X-Instance-ID", process.env.ZAPSTER_INSTANCE_ID ?? "");
1592
+ headers.set("X-Webhook-ID", process.env.ZAPSTER_WEBHOOK_ID ?? "");
1593
+ headers.set("X-Message-ID", `cli_msg_${Date.now()}`);
1594
+ headers.set("X-Attempt-Count", "1");
1595
+ headers.set("User-Agent", "Zapsterapi/agentkit-cli");
1596
+ }
1597
+
1598
+ if (channel.type === "slack") {
1599
+ const signingSecret = process.env.SLACK_SIGNING_SECRET;
1600
+
1601
+ if (signingSecret) {
1602
+ const timestamp = String(Math.floor(Date.now() / 1000));
1603
+ const signature = createHmac("sha256", signingSecret)
1604
+ .update(`v0:${timestamp}:${rawBody}`)
1605
+ .digest("hex");
1606
+ headers.set("X-Slack-Request-Timestamp", timestamp);
1607
+ headers.set("X-Slack-Signature", `v0=${signature}`);
1608
+ }
1609
+ }
1610
+
1611
+ if (channel.type === "webhook") {
1612
+ const secret = process.env.AGENTKIT_WEBHOOK_SECRET;
1613
+
1614
+ if (secret) {
1615
+ headers.set("Authorization", `Bearer ${secret}`);
1616
+ }
797
1617
  }
798
1618
 
799
1619
  return headers;