@andreprado/agentkit 0.1.0-alpha.16 → 0.1.0-alpha.17

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 (32) hide show
  1. package/README.md +8 -0
  2. package/docs/guides/add-channel.md +2 -0
  3. package/docs/guides/add-managed-composio.md +137 -0
  4. package/docs/guides/channels-production-handoff.md +1 -1
  5. package/docs/guides/connect-telegram.md +3 -1
  6. package/docs/guides/prepare-deploy.md +3 -1
  7. package/docs/llms-full.txt +13 -1
  8. package/docs/llms.txt +7 -0
  9. package/package.json +1 -1
  10. package/src/cli/commands/channels.ts +119 -7
  11. package/src/cli/commands/transcribe.ts +171 -0
  12. package/src/cli/deploy-readiness.ts +59 -3
  13. package/src/cli/help.ts +15 -0
  14. package/src/cli/index.ts +130 -0
  15. package/src/create-project.ts +4 -32
  16. package/src/index.ts +145 -0
  17. package/src/runtime/chat.ts +2 -1
  18. package/src/runtime/config.ts +129 -0
  19. package/src/runtime/core/manifest.ts +29 -2
  20. package/src/runtime/deploy-readiness.ts +31 -1
  21. package/src/runtime/inspect.ts +108 -4
  22. package/src/runtime/integrations/composio.ts +257 -0
  23. package/src/runtime/skills.ts +95 -0
  24. package/src/runtime/targets/cloudflare/build.ts +160 -5
  25. package/src/runtime/tool-runner.ts +2 -1
  26. package/src/templates/skills/agentkit-capsule/SKILL.md +1 -0
  27. package/src/templates/skills/agentkit-channels/SKILL.md +2 -0
  28. package/src/templates/skills/agentkit-channels/references/channel-debugging.md +3 -1
  29. package/src/templates/skills/agentkit-channels/references/telegram.md +3 -1
  30. package/src/templates/skills/agentkit-deploy/SKILL.md +1 -1
  31. package/src/templates/skills/agentkit-integrations/SKILL.md +66 -0
  32. package/src/templates/skills/agentkit-troubleshooting/SKILL.md +3 -2
package/README.md CHANGED
@@ -37,6 +37,8 @@ agentkit tool <name> [--input <path-or-json>]
37
37
  agentkit knowledge add <path-or-url>
38
38
  agentkit knowledge sync
39
39
  agentkit knowledge search <query> [--top-k <number>]
40
+ agentkit integrations status
41
+ agentkit integrations connect composio --toolkit gmail
40
42
  agentkit spec init --brief <text>
41
43
  agentkit db migrate
42
44
  agentkit sync run
@@ -50,18 +52,24 @@ Knowledge indexes local `.md`, `.txt`, and `.csv` sources into the capsule datab
50
52
 
51
53
  Every chat run receives dynamic runtime date context: current ISO timestamp, local date, weekday, local date/time, and timezone. Set `timeZone` in `agentkit.config.ts` for scheduling agents so relative dates like "today" and "next Friday" resolve in the right business/user timezone.
52
54
 
55
+ AgentKit-managed Composio is a paid hosted integration layer for per-agent connected apps. Configure it with `composioManaged({...})`, deploy with a `managed_composio` entitlement, then use `agentkit integrations connect composio --toolkit <slug>` to create hosted Connect Links. BYO Composio remains available through normal `defineTool` wrappers.
56
+
53
57
  ## Deploy Later
54
58
 
55
59
  ```sh
56
60
  agentkit login --token <token>
57
61
  agentkit deploy doctor
62
+ agentkit skills status
58
63
  agentkit secret set OPENAI_API_KEY --from-local-env
59
64
  agentkit deploy
60
65
  agentkit deploy status
66
+ agentkit channels test support-telegram --message "hello"
67
+ agentkit transcribe smoke --provider groq
61
68
  agentkit chat-ui --deploy
62
69
  ```
63
70
 
64
71
  Production secrets are managed secrets, not committed `.env` values.
72
+ After upgrading AgentKit in an existing capsule, run `agentkit skills status` and `agentkit skills sync` if local agent-facing skills are stale.
65
73
  Private hosted deploys automatically store a local chat/UI deploy access token at `.agentkit/chat-access-token.json`. `agentkit chat-ui --deploy` serves a local UI pointed at the hosted deploy without exposing that token to browser code.
66
74
 
67
75
  The npm package contains the public CLI/runtime/client surface only. AgentKit Cloud's control-plane server, operator commands, Postgres store, and Cloudflare/Turso/R2 publisher live in the private repo workspace and are not part of the published package.
@@ -160,6 +160,8 @@ bun test
160
160
  agentkit inspect
161
161
  agentkit channels list
162
162
  agentkit channels test support-telegram --message "hello"
163
+ agentkit channels test-audio support-telegram --fixture voice-note
164
+ agentkit transcribe smoke --provider groq
163
165
  agentkit channels deliveries list support-telegram
164
166
  agentkit channels buffers list support-telegram
165
167
  ```
@@ -0,0 +1,137 @@
1
+ # Add Managed Composio
2
+
3
+ ## Goal
4
+
5
+ Enable AgentKit-managed Composio for one deployed agent so the agent can use explicitly allowed external app actions without the user owning Composio credentials.
6
+
7
+ Use BYO `defineTool` wrappers instead when the user wants to use their own Composio account/API key for free.
8
+
9
+ ## Contract
10
+
11
+ Managed Composio is paid hosted AgentKit infrastructure:
12
+
13
+ - It works per agent/project, not per client.
14
+ - It requires an AgentKit Cloud account with `managed_composio`.
15
+ - AgentKit Cloud injects `COMPOSIO_API_KEY`; do not put it in `.env`, `.env.schema`, or `agentkit.config.ts`.
16
+ - AgentKit Cloud assigns the deployed Composio `user_id` from the account, project, agent, and integration name. The local inspect/build id is only a preview.
17
+ - The agent gets one generated tool, `agentkit_composio_execute`, only when explicit Composio action slugs are configured.
18
+ - Anonymous deploys cannot use managed Composio.
19
+
20
+ ## Minimal Config
21
+
22
+ Edit `agentkit.config.ts`:
23
+
24
+ ```ts
25
+ import { composioManaged, defineAgent } from "@andreprado/agentkit";
26
+
27
+ export default defineAgent({
28
+ name: "acme-receptionist",
29
+ runtime: "edge",
30
+ provider: {
31
+ name: "test",
32
+ model: "fake",
33
+ },
34
+ instructions: "./prompts/instructions.md",
35
+ secrets: [],
36
+ tools: [],
37
+ integrations: [
38
+ composioManaged({
39
+ toolkits: ["gmail", "googlecalendar"],
40
+ tools: {
41
+ gmail: ["GMAIL_FETCH_EMAILS", "GMAIL_SEND_EMAIL"],
42
+ googlecalendar: ["GOOGLECALENDAR_CREATE_EVENT"],
43
+ },
44
+ }),
45
+ ],
46
+ access: {
47
+ mode: "private",
48
+ },
49
+ storage: {
50
+ driver: "agentkit",
51
+ database: {
52
+ driver: "turso",
53
+ },
54
+ },
55
+ });
56
+ ```
57
+
58
+ ## Deploy
59
+
60
+ ```sh
61
+ agentkit login --token agk_user_...
62
+ agentkit deploy doctor
63
+ agentkit deploy
64
+ agentkit integrations status
65
+ ```
66
+
67
+ Expected readiness:
68
+
69
+ - `cloudflare_deploy_alpha` is active.
70
+ - `managed_composio` is active.
71
+ - `COMPOSIO_API_KEY` appears as an AgentKit-managed secret, not a user-managed hosted secret.
72
+
73
+ ## Connect Apps
74
+
75
+ After deploy, create a hosted Composio Connect Link:
76
+
77
+ ```sh
78
+ agentkit integrations connect composio --toolkit gmail
79
+ agentkit integrations connect composio --toolkit googlecalendar
80
+ ```
81
+
82
+ AgentKit prints a URL. Send that URL to the person who owns the app account.
83
+
84
+ If the deploy has only one toolkit, `--toolkit` can be omitted.
85
+
86
+ ## Operator Setup
87
+
88
+ Trusted AgentKit Cloud operators grant access and configure Composio auth configs:
89
+
90
+ ```sh
91
+ npm run agentkit:operator -- accounts grant user@example.com --managed-composio
92
+ ```
93
+
94
+ Required control-plane environment:
95
+
96
+ ```txt
97
+ AGENTKIT_SECRET_COMPOSIO_API_KEY=<project API key>
98
+ AGENTKIT_COMPOSIO_AUTH_CONFIG_GMAIL=<auth config id>
99
+ AGENTKIT_COMPOSIO_AUTH_CONFIG_GOOGLECALENDAR=<auth config id>
100
+ ```
101
+
102
+ Use one `AGENTKIT_COMPOSIO_AUTH_CONFIG_<TOOLKIT>` value per toolkit slug, uppercased with non-alphanumeric characters converted to `_`.
103
+
104
+ ## Verification
105
+
106
+ ```sh
107
+ agentkit inspect
108
+ agentkit build --target cloudflare
109
+ agentkit deploy doctor
110
+ agentkit integrations status
111
+ ```
112
+
113
+ Expected:
114
+
115
+ - `inspect.integrations[0].provider` is `composio`.
116
+ - `inspect.tools` includes `agentkit_composio_execute` when allowed actions are configured.
117
+ - Build manifest includes `integrations`.
118
+ - Build manifest includes `COMPOSIO_API_KEY`.
119
+ - `deploy doctor` does not ask the user to set `COMPOSIO_API_KEY`.
120
+
121
+ ## Troubleshooting
122
+
123
+ `managed_composio_entitlement_required`:
124
+
125
+ Log in with a paid AgentKit Cloud account that has `managed_composio`.
126
+
127
+ `managed_composio_auth_config_missing`:
128
+
129
+ An operator must set `AGENTKIT_COMPOSIO_AUTH_CONFIG_<TOOLKIT>` on the control plane.
130
+
131
+ `managed_composio_not_configured`:
132
+
133
+ An operator must set `AGENTKIT_SECRET_COMPOSIO_API_KEY` on the control plane.
134
+
135
+ `integration_toolkit_required`:
136
+
137
+ The deploy has multiple configured toolkits. Re-run connect with `--toolkit <slug>`.
@@ -67,7 +67,7 @@ Telegram channel URL: https://<deploy-host>/channels/support-telegram/telegram/t
67
67
  Zapster channel URL: https://<deploy-host>/channels/support-whatsapp/whatsapp/zapster/webhook
68
68
  Required provider webhook settings: Telegram setWebhook URL/secret token; Zapster webhook URL plus optional ZAPSTER_WEBHOOK_TOKEN query parameter
69
69
  Required managed secrets: TELEGRAM_BOT_TOKEN, TELEGRAM_WEBHOOK_SECRET, ZAPSTER_API_KEY, ZAPSTER_INSTANCE_ID, ZAPSTER_WEBHOOK_ID
70
- Smoke commands: agentkit channels doctor <name>; agentkit channels test <name> --message "hello"; agentkit channels deliveries list <name> --since 1h
70
+ Smoke commands: agentkit channels doctor <name>; agentkit channels test <name> --message "hello"; agentkit channels test-audio <name> --fixture voice-note for audio channels; agentkit transcribe smoke --provider groq for transcription; agentkit channels deliveries list <name> --since 1h
71
71
  Rollback command: curl -X DELETE "$AGENTKIT_CLOUD_API_URL/v1/channels/<channel-id>" -H "Authorization: Bearer $AGENTKIT_CLOUD_API_TOKEN"; then remove provider webhook registration before rolling back the Worker
72
72
  Known unverified items: any channel with no real provider inbound, no provider_sent outbound, stale buffers, or AGENTKIT_CHANNEL_SEND_DRY_RUN=1
73
73
  ```
@@ -141,6 +141,8 @@ Against AgentKit Cloud, `--apply` runs through the Cloud API and uses managed ho
141
141
  ```sh
142
142
  agentkit channels status support-telegram
143
143
  agentkit channels test support-telegram --message "hello"
144
+ agentkit channels test-audio support-telegram --fixture voice-note
145
+ agentkit transcribe smoke --provider groq
144
146
  agentkit channels deliveries show <delivery-id>
145
147
  ```
146
148
 
@@ -165,4 +167,4 @@ The update is malformed or is not a supported private text message.
165
167
  Set `GROQ_API_KEY` or the custom secret named in `transcription.secret` as a managed hosted secret.
166
168
 
167
169
  `transcription_audio_format_unsupported`:
168
- The configured transcription provider does not accept the Telegram file format. Use Groq for OGG/Opus voice notes in V1.
170
+ The configured transcription provider does not accept the Telegram file format. Use Groq for OGG/Opus voice notes in V1. `agentkit inspect` warns when a Telegram channel uses `audio.mode: "transcribe"` with OpenAI transcription.
@@ -192,7 +192,9 @@ Before a hosted production deploy, run:
192
192
  npm run agentkit -- deploy doctor
193
193
  ```
194
194
 
195
- The doctor checks AgentKit Cloud login, alpha deploy entitlement, online deploy capacity, declared hosted secrets, local `.env` names that have not been uploaded with `agentkit secret set`, and private-access runtime token handling. `agentkit deploy` runs the same readiness check automatically before building and uploading the artifact.
195
+ The doctor checks AgentKit Cloud login, Node version, alpha deploy entitlement, online deploy capacity, declared hosted secrets, local `.env` names that have not been uploaded with `npm run agentkit -- secret set`, and private-access runtime token handling. `agentkit deploy` runs the same readiness check automatically before building and uploading the artifact.
196
+
197
+ If the capsule configures `composioManaged({...})`, the doctor also checks the paid `managed_composio` entitlement. `COMPOSIO_API_KEY` is AgentKit-managed in this path; the user must not set it with `agentkit secret set`. Operators configure `AGENTKIT_SECRET_COMPOSIO_API_KEY` and per-toolkit `AGENTKIT_COMPOSIO_AUTH_CONFIG_<TOOLKIT>` values on the control plane.
196
198
 
197
199
  `alpha_access_required`:
198
200
 
@@ -56,8 +56,13 @@ agentkit channels add <website|telegram|whatsapp> <name> [--provider zapster|met
56
56
  agentkit channels setup <name> [--apply] [--api <url>]
57
57
  agentkit channels status <name> [--api <url>]
58
58
  agentkit channels test <name> [--message <text>] [--fixture <path>] [--api <url>]
59
+ agentkit channels test-audio <name> [--fixture voice-note|audio-file|<path>] [--api <url>]
59
60
  agentkit channels deliveries list <name> [--api <url>]
60
61
  agentkit channels deliveries show <delivery-id> [--api <url>]
62
+ agentkit integrations status [--api <url>]
63
+ agentkit integrations connect composio [--toolkit <slug>] [--callback-url <url>] [--api <url>]
64
+ agentkit skills status
65
+ agentkit skills sync
61
66
  agentkit inspect
62
67
  agentkit build [--target cloudflare|container]
63
68
  agentkit login --token <token>
@@ -68,6 +73,7 @@ agentkit deploy smoke [--message <text>] [--api <url>]
68
73
  agentkit deploy status
69
74
  agentkit deploy pause
70
75
  agentkit deploy resume
76
+ agentkit transcribe smoke [--provider test|openai|groq] [--model <model>] [--fixture <audio-file>]
71
77
  agentkit secret set <NAME> <VALUE>
72
78
  agentkit secret set <NAME> --stdin
73
79
  agentkit secret set <NAME> --from-env [ENV_NAME]
@@ -93,6 +99,8 @@ agentkit help commands
93
99
 
94
100
  Prefer `env set --stdin` or `--from-env` for local secret values, and prefer `secret set --stdin`, `--from-env`, or `--from-local-env` for hosted secrets. Inline `<VALUE>` forms exist for simple non-sensitive values, but agents should avoid putting secrets in shell history.
95
101
 
102
+ Managed Composio is configured with `composioManaged({...})` in `agentkit.config.ts` and is paid hosted AgentKit infrastructure. It requires a non-anonymous AgentKit Cloud deploy with `managed_composio`, uses one Composio settings profile per agent, injects `COMPOSIO_API_KEY` as an AgentKit-managed secret, and exposes the generated `agentkit_composio_execute` tool only for explicit configured action slugs. See `docs/guides/add-managed-composio.md`.
103
+
96
104
  Planned commands described by the contract but not implemented yet:
97
105
 
98
106
  ```sh
@@ -398,11 +406,13 @@ agentkit channels list
398
406
  agentkit channels add telegram support-telegram
399
407
  agentkit channels setup support-telegram
400
408
  agentkit channels test support-telegram --message "hello"
409
+ agentkit channels test-audio support-telegram --fixture voice-note
410
+ agentkit transcribe smoke --provider groq
401
411
  agentkit channels deliveries list support-telegram
402
412
  agentkit channels deliveries show <delivery-id>
403
413
  ```
404
414
 
405
- `channels setup` is read-only by default. `channels setup <telegram-name> --apply` calls Telegram `setWebhook` and requires `TELEGRAM_BOT_TOKEN` plus `TELEGRAM_WEBHOOK_SECRET`.
415
+ `channels connect` creates or refreshes the channel resource, validates secrets, runs provider setup when supported, then runs the official synthetic smoke. `channels setup` is read-only by default. `channels setup <telegram-name> --apply` calls Telegram `setWebhook` and requires `TELEGRAM_BOT_TOKEN` plus `TELEGRAM_WEBHOOK_SECRET`.
406
416
 
407
417
  Default tests are offline. Real provider smoke tests are opt-in:
408
418
 
@@ -796,6 +806,8 @@ Account/access flow:
796
806
  agentkit secret set OPENAI_API_KEY --from-local-env
797
807
  agentkit secret sync --from-local
798
808
  agentkit secret list
809
+ agentkit skills status
810
+ agentkit skills sync
799
811
  agentkit access token create website-chat --out .agentkit/website-chat-access-token.json
800
812
  agentkit access token list
801
813
  ```
package/docs/llms.txt CHANGED
@@ -16,6 +16,7 @@ Task guides:
16
16
  - Build container artifact: `agentkit build --target container`
17
17
  - Generate VPS handoff: `agentkit deploy --target vps --host agent.example.com --dry-run`
18
18
  - Add hosted channels: `docs/guides/add-channel.md`
19
+ - Add AgentKit-managed Composio: `docs/guides/add-managed-composio.md`
19
20
  - Buffer rapid channel messages: `docs/guides/add-channel.md#buffer-bursty-messages`
20
21
  - Connect Telegram: `docs/guides/connect-telegram.md`
21
22
  - Connect WhatsApp through Zapster: `docs/guides/connect-whatsapp-zapster.md`
@@ -48,7 +49,13 @@ agentkit channels add telegram support-telegram
48
49
  agentkit channels setup support-telegram
49
50
  agentkit channels status support-telegram
50
51
  agentkit channels test support-telegram --message "hello"
52
+ agentkit channels test-audio support-telegram --fixture voice-note
53
+ agentkit transcribe smoke --provider groq
51
54
  agentkit channels deliveries list support-telegram
55
+ agentkit integrations status
56
+ agentkit integrations connect composio --toolkit gmail
57
+ agentkit skills status
58
+ agentkit skills sync
52
59
  agentkit login --token agk_user_...
53
60
  agentkit deploy doctor
54
61
  agentkit deploy --smoke "hello"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andreprado/agentkit",
3
- "version": "0.1.0-alpha.16",
3
+ "version": "0.1.0-alpha.17",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "repository": {
@@ -162,8 +162,10 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
162
162
  let channel = created.channel;
163
163
  printChannelStatus(channel);
164
164
  printSecretRepairHint(channel);
165
+ console.log("Channel resource: created_or_updated");
165
166
 
166
167
  if (hasMissingRequiredSecrets(channel)) {
168
+ console.log("Secrets: missing");
167
169
  console.log("");
168
170
  console.log("Connect blocked: set the missing managed secrets, then rerun this same command.");
169
171
  console.log("Run: npm run agentkit -- secret sync --from-local");
@@ -171,31 +173,40 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
171
173
  return;
172
174
  }
173
175
 
176
+ console.log("Secrets: validated");
177
+
174
178
  if (channel.type === "telegram") {
175
179
  const setup = await applyTelegramWebhook(channel, args.flags.api);
176
180
  channel = setup.channel ?? channel;
181
+ console.log("Telegram setWebhook: OK");
177
182
  console.log("Telegram webhook confirmed.");
178
183
  if (setup.setup?.get_webhook_info) {
179
184
  console.log(`Telegram pending updates: ${setup.setup.get_webhook_info.pending_update_count}`);
180
185
  }
181
186
 
182
- const smoke = await runChannelSmoke(channel, args.flags.api, args.flags.message);
183
- console.log(`Smoke: ${smoke.status}${smoke.delivery_id ? ` (${smoke.delivery_id})` : ""}`);
187
+ const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
184
188
  printTelegramHumanNextStep(channel.name, setup.setup?.bot_username);
189
+ if (!smoke) {
190
+ process.exitCode = 1;
191
+ }
185
192
  return;
186
193
  }
187
194
 
188
195
  if (channel.type === "whatsapp" && channel.provider === "zapster") {
189
- const smoke = await runChannelSmoke(channel, args.flags.api, args.flags.message);
190
- console.log(`Webhook smoke: ${smoke.status}${smoke.delivery_id ? ` (${smoke.delivery_id})` : ""}`);
196
+ const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
191
197
  console.log(`Next human step: paste ${channel.webhook_url} into ${channel.provider}'s webhook settings.`);
198
+ if (!smoke) {
199
+ process.exitCode = 1;
200
+ }
192
201
  } else if (channel.type === "whatsapp") {
193
202
  console.log("Smoke: skipped until the provider webhook is configured.");
194
203
  console.log(`Next human step: paste ${channel.webhook_url} into ${channel.provider}'s webhook settings.`);
195
204
  } else {
196
- const smoke = await runChannelSmoke(channel, args.flags.api, args.flags.message);
197
- console.log(`Smoke: ${smoke.status}${smoke.delivery_id ? ` (${smoke.delivery_id})` : ""}`);
205
+ const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
198
206
  console.log(`Next human step: use ${channel.webhook_url} from your website client.`);
207
+ if (!smoke) {
208
+ process.exitCode = 1;
209
+ }
199
210
  }
200
211
  return;
201
212
  }
@@ -274,6 +285,25 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
274
285
  return;
275
286
  }
276
287
 
288
+ if (subcommand === "test-audio") {
289
+ if (!first) {
290
+ throw new Error("Usage: agentkit channels test-audio <name> [--fixture voice-note|audio-file|<path>]");
291
+ }
292
+
293
+ const channel = await resolveCloudChannelByName(first, args.flags.api);
294
+ const apiUrl = parseCloudApiUrl(args.flags.api);
295
+ const body = await channelAudioTestBody(channel, args.flags.fixture);
296
+ const signed = await runServerSignedChannelTest(channel, apiUrl, body);
297
+ const response = signed.response;
298
+ const payload = signed.payload;
299
+
300
+ console.log(JSON.stringify(payload, null, 2));
301
+ if (!response.ok) {
302
+ process.exitCode = 1;
303
+ }
304
+ return;
305
+ }
306
+
277
307
  if (subcommand === "deliveries") {
278
308
  if (first === "list") {
279
309
  if (!second) {
@@ -367,7 +397,7 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
367
397
  }
368
398
 
369
399
  throw new Error(
370
- "Usage: agentkit channels list | add | connect | setup | status | doctor | test | deliveries list | deliveries show | buffers list | buffers show | buffers flush | buffers clear | buffers retry",
400
+ "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",
371
401
  );
372
402
  }
373
403
 
@@ -419,6 +449,20 @@ async function runChannelSmoke(
419
449
  return { status: "unknown" };
420
450
  }
421
451
 
452
+ async function runConnectSmoke(
453
+ run: () => Promise<{ status: string; delivery_id?: string }>,
454
+ ): Promise<{ status: string; delivery_id?: string } | null> {
455
+ try {
456
+ const smoke = await run();
457
+ console.log(`Synthetic smoke: ${smoke.status}${smoke.delivery_id ? ` (${smoke.delivery_id})` : ""}`);
458
+ return smoke;
459
+ } catch (error) {
460
+ console.log("Synthetic smoke: failed");
461
+ console.log(` ${formatCommandError(error)}`);
462
+ return null;
463
+ }
464
+ }
465
+
422
466
  async function runServerSignedChannelTest(
423
467
  channel: CloudChannel,
424
468
  apiUrl: string,
@@ -601,6 +645,10 @@ function isRecord(value: unknown): value is Record<string, unknown> {
601
645
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
602
646
  }
603
647
 
648
+ function formatCommandError(error: unknown): string {
649
+ return error instanceof Error ? error.message : String(error);
650
+ }
651
+
604
652
  async function readDeployStateIfExists(cwd: string): Promise<DeployState | null> {
605
653
  return readLocalDeployStateIfExists(cwd);
606
654
  }
@@ -974,6 +1022,70 @@ async function channelTestBody(
974
1022
  });
975
1023
  }
976
1024
 
1025
+ async function channelAudioTestBody(
1026
+ channel: CloudChannel,
1027
+ fixtureFlag: string | boolean | undefined,
1028
+ ): Promise<string> {
1029
+ if (typeof fixtureFlag === "string" && isExplicitFixturePath(fixtureFlag)) {
1030
+ return readFile(resolve(process.cwd(), fixtureFlag), "utf8");
1031
+ }
1032
+
1033
+ const fixture = typeof fixtureFlag === "string" ? fixtureFlag : "voice-note";
1034
+
1035
+ if (channel.type === "telegram") {
1036
+ if (fixture !== "voice-note" && fixture !== "telegram-voice-note" && fixture !== "telegram-audio-message") {
1037
+ throw new Error('Telegram audio smoke supports --fixture voice-note or --fixture telegram-voice-note.');
1038
+ }
1039
+
1040
+ return JSON.stringify({
1041
+ update_id: Date.now(),
1042
+ message: {
1043
+ message_id: 1,
1044
+ date: Math.floor(Date.now() / 1000),
1045
+ chat: { id: 456, type: "private" },
1046
+ from: { id: 123, first_name: "AgentKit" },
1047
+ voice: {
1048
+ file_id: `agentkit_voice_${Date.now()}`,
1049
+ file_unique_id: `agentkit_voice_unique_${Date.now()}`,
1050
+ duration: 12,
1051
+ mime_type: "audio/ogg",
1052
+ file_size: 42,
1053
+ },
1054
+ },
1055
+ });
1056
+ }
1057
+
1058
+ if (channel.type === "whatsapp" && channel.provider === "zapster") {
1059
+ if (fixture !== "voice-note" && fixture !== "audio-file" && fixture !== "zapster-audio" && fixture !== "zapster-audio-message") {
1060
+ throw new Error('Zapster audio smoke supports --fixture voice-note, --fixture audio-file, or --fixture zapster-audio.');
1061
+ }
1062
+
1063
+ return JSON.stringify({
1064
+ event_id: `zap_evt_audio_${Date.now()}`,
1065
+ instance_id: "local",
1066
+ message: {
1067
+ id: `wamid.audio.${Date.now()}`,
1068
+ from: "5511999999999",
1069
+ type: "audio",
1070
+ audio: {
1071
+ id: `media_audio_${Date.now()}`,
1072
+ url: `https://api.zapsterapi.com/media/audio/media_audio_${Date.now()}.ogg`,
1073
+ mime_type: "audio/ogg",
1074
+ duration: 12,
1075
+ file_size: 42,
1076
+ ...(fixture === "voice-note" ? { ptt: "true" } : {}),
1077
+ },
1078
+ },
1079
+ });
1080
+ }
1081
+
1082
+ throw new Error(`Audio smoke is not supported for ${channel.type}/${channel.provider}.`);
1083
+ }
1084
+
1085
+ function isExplicitFixturePath(value: string): boolean {
1086
+ return value.includes("/") || value.startsWith(".") || value.endsWith(".json");
1087
+ }
1088
+
977
1089
  function channelTestHeaders(channel: CloudChannel): Headers {
978
1090
  const headers = new Headers({ "Content-Type": "application/json" });
979
1091