@andreprado/agentkit 0.1.0-alpha.12 → 0.1.0-alpha.14
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/docs/guides/add-channel.md +13 -0
- package/docs/guides/agentkit-skills-architecture.md +1 -0
- package/docs/guides/channels-implementation-map.md +2 -2
- package/docs/guides/channels-production-handoff.md +17 -1
- package/docs/guides/connect-telegram.md +1 -1
- package/docs/guides/connect-whatsapp-zapster.md +11 -5
- package/package.json +1 -2
- package/src/cli/cloud-client.ts +8 -3
- package/src/cli/commands/channels.ts +196 -7
- package/src/cli/help.ts +5 -0
- package/src/runtime/channels/telegram.ts +17 -2
- package/src/runtime/channels/whatsapp-zapster.ts +19 -3
- package/src/runtime/channels.ts +4 -1
- package/src/runtime/config.ts +22 -10
- package/src/runtime/targets/cloudflare/build.ts +1 -0
- package/src/templates/skills/agentkit-channels/references/channel-buffering.md +8 -1
- package/src/templates/skills/agentkit-channels/references/channel-debugging.md +10 -1
|
@@ -21,6 +21,7 @@ agentkit channels setup support-telegram
|
|
|
21
21
|
agentkit channels status support-telegram
|
|
22
22
|
agentkit channels test support-telegram --message "hello"
|
|
23
23
|
agentkit channels deliveries list support-telegram
|
|
24
|
+
agentkit channels buffers list support-telegram
|
|
24
25
|
```
|
|
25
26
|
|
|
26
27
|
Use `--api <url>` with hosted commands when testing against a non-default AgentKit Cloud API.
|
|
@@ -76,6 +77,16 @@ Buffering is scoped to one channel conversation. AgentKit still validates and de
|
|
|
76
77
|
|
|
77
78
|
Use `buffer: { mode: "off" }` or omit `buffer` to process each inbound message as its own agent run.
|
|
78
79
|
|
|
80
|
+
Buffer controls:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
agentkit channels buffers list support-whatsapp
|
|
84
|
+
agentkit channels buffers show support-whatsapp <conversation-id>
|
|
85
|
+
agentkit channels buffers flush support-whatsapp <conversation-id>
|
|
86
|
+
agentkit channels buffers clear support-whatsapp <conversation-id>
|
|
87
|
+
agentkit channels buffers retry support-whatsapp <conversation-id>
|
|
88
|
+
```
|
|
89
|
+
|
|
79
90
|
## Safety Rules
|
|
80
91
|
|
|
81
92
|
- Never put provider token values in `agentkit.config.ts`.
|
|
@@ -93,9 +104,11 @@ agentkit inspect
|
|
|
93
104
|
agentkit channels list
|
|
94
105
|
agentkit channels test support-telegram --message "hello"
|
|
95
106
|
agentkit channels deliveries list support-telegram
|
|
107
|
+
agentkit channels buffers list support-telegram
|
|
96
108
|
```
|
|
97
109
|
|
|
98
110
|
Expected hosted status includes `NAME`, `TYPE`, `PROVIDER`, `STATUS`, and `LAST_EVENT`. Secret output is name plus `set` or `missing`, never the value.
|
|
111
|
+
Outbound provider success is `provider_sent`. Explicit dry-run mode is `adapter_stubbed`, which means the provider was not called.
|
|
99
112
|
|
|
100
113
|
## Troubleshooting
|
|
101
114
|
|
|
@@ -351,6 +351,7 @@ Do not ship these into user capsules by default:
|
|
|
351
351
|
- account grant/revoke;
|
|
352
352
|
- backend contract implementation;
|
|
353
353
|
- Cloudflare/Turso/R2 provisioning internals;
|
|
354
|
+
- release-lane ownership;
|
|
354
355
|
- package publishing.
|
|
355
356
|
|
|
356
357
|
Those workflows belong in maintainer skills inside the AgentKit repository, not in generated user capsules.
|
|
@@ -154,7 +154,7 @@ Add:
|
|
|
154
154
|
- `GET /v1/deploys/{deploy_id}/channels`;
|
|
155
155
|
- `GET /v1/channels/{channel_id}`;
|
|
156
156
|
- `DELETE /v1/channels/{channel_id}`;
|
|
157
|
-
- public webhook routes for `/channels/{
|
|
157
|
+
- public webhook routes for `/channels/{channel_name}/{type}/{provider}/webhook`, with old `chn_*` routes accepted for compatibility;
|
|
158
158
|
- delivery inspection routes for CLI use.
|
|
159
159
|
|
|
160
160
|
The local `npm run agentkit:operator -- serve` path can use an in-memory or local SQLite fake store first. Production Postgres support can follow once the contract is tested. In both stores, persist only secret names and statuses, never secret values.
|
|
@@ -192,7 +192,7 @@ Use this boundary for V1 local cloud tests:
|
|
|
192
192
|
| --- | --- | --- |
|
|
193
193
|
| Channel resource metadata | Yes: `channels` table or equivalent fake store keyed by `chn_...`. | Read-only manifest input after deploy. |
|
|
194
194
|
| Channel secret references | Yes: names and `set`/`missing` status only. | Runtime receives injected secret values by name; no readback. |
|
|
195
|
-
| Channel endpoint URL | Yes: generated from deploy URL plus stable channel
|
|
195
|
+
| Channel endpoint URL | Yes: generated from deploy URL plus stable channel name. | Worker forwards deploy ID and routes requests by channel name, type, and provider; old `chn_*` URLs remain compatibility routes. |
|
|
196
196
|
| Channel delivery records | Yes: `deliveries` table keyed by `del_...` for CLI inspection. | Worker/consumer reports state transitions back to control plane or durable storage. |
|
|
197
197
|
| Channel event audit records | Yes: compact event records keyed by `chevt_...`, with redacted provider metadata. | Ingress creates or reports event records after provider validation. |
|
|
198
198
|
| External identity mappings | Fake DO-compatible store for local tests, keyed by `chid_...`. | Durable Object owns strongly consistent identity to conversation mapping. |
|
|
@@ -44,7 +44,7 @@ META_WHATSAPP_VERIFY_TOKEN
|
|
|
44
44
|
agentkit channels add telegram support-telegram
|
|
45
45
|
agentkit channels add whatsapp support-whatsapp --provider zapster
|
|
46
46
|
agentkit channels setup support-telegram --apply
|
|
47
|
-
agentkit channels
|
|
47
|
+
agentkit channels connect whatsapp support-whatsapp --provider zapster
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
5. Run smoke checks:
|
|
@@ -55,6 +55,21 @@ agentkit channels test support-telegram --message "hello"
|
|
|
55
55
|
agentkit channels status support-whatsapp
|
|
56
56
|
agentkit channels test support-whatsapp --message "hello"
|
|
57
57
|
agentkit channels deliveries list support-telegram --since 1h
|
|
58
|
+
agentkit channels deliveries list support-whatsapp --since 1h
|
|
59
|
+
agentkit channels buffers list support-whatsapp
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
After deploy, record this exact handoff block:
|
|
63
|
+
|
|
64
|
+
```txt
|
|
65
|
+
Deploy URL: https://<deploy-host>
|
|
66
|
+
Telegram channel URL: https://<deploy-host>/channels/support-telegram/telegram/telegram/webhook
|
|
67
|
+
Zapster channel URL: https://<deploy-host>/channels/support-whatsapp/whatsapp/zapster/webhook
|
|
68
|
+
Required provider webhook settings: Telegram setWebhook URL/secret token; Zapster webhook URL plus optional ZAPSTER_WEBHOOK_TOKEN query parameter
|
|
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
|
|
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
|
+
Known unverified items: any channel with no real provider inbound, no provider_sent outbound, stale buffers, or AGENTKIT_CHANNEL_SEND_DRY_RUN=1
|
|
58
73
|
```
|
|
59
74
|
|
|
60
75
|
## Local Fake Versus Real Provider Gates
|
|
@@ -82,6 +97,7 @@ Zapster smoke requires `ZAPSTER_API_KEY`, `ZAPSTER_INSTANCE_ID`, and `AGENTKIT_Z
|
|
|
82
97
|
- Invalid Telegram signature or Zapster origin headers return `401` and create a failed delivery.
|
|
83
98
|
- Duplicate provider event returns `200` with `duplicate` and creates no second queue job.
|
|
84
99
|
- Accepted inbound text creates one queue job and one outbound delivery.
|
|
100
|
+
- Real outbound success is `provider_sent` and includes a provider message ID. `adapter_stubbed` means dry-run mode built a request but did not call the provider.
|
|
85
101
|
- Buffered inbound bursts stay in `buffered` state, then flush into one queue job and one outbound delivery.
|
|
86
102
|
- Retryable failures dead-letter after the configured retry ceiling.
|
|
87
103
|
- `channel_limit_exceeded` does not create queue backlog.
|
|
@@ -95,7 +95,7 @@ agentkit channels deliveries show <delivery-id>
|
|
|
95
95
|
Expected webhook URL shape:
|
|
96
96
|
|
|
97
97
|
```txt
|
|
98
|
-
https://<deploy-host>/channels/
|
|
98
|
+
https://<deploy-host>/channels/support-telegram/telegram/telegram/webhook
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
## Troubleshooting
|
|
@@ -12,11 +12,11 @@ Use this after `whatsappChannel({ name: "support-whatsapp", provider: "zapster"
|
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
14
|
agentkit deploy
|
|
15
|
-
agentkit channels
|
|
16
|
-
agentkit channels setup support-whatsapp
|
|
15
|
+
agentkit channels connect whatsapp support-whatsapp --provider zapster
|
|
17
16
|
agentkit channels status support-whatsapp
|
|
18
17
|
agentkit channels test support-whatsapp --message "hello"
|
|
19
18
|
agentkit channels deliveries list support-whatsapp --since 24h
|
|
19
|
+
agentkit channels buffers list support-whatsapp
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
Required secrets:
|
|
@@ -80,13 +80,13 @@ whatsappChannel({
|
|
|
80
80
|
Expected webhook URL shape:
|
|
81
81
|
|
|
82
82
|
```txt
|
|
83
|
-
https://<deploy-host>/channels/
|
|
83
|
+
https://<deploy-host>/channels/support-whatsapp/whatsapp/zapster/webhook
|
|
84
84
|
```
|
|
85
85
|
|
|
86
86
|
If the channel declares `ZAPSTER_WEBHOOK_TOKEN`, register the Zapster URL with the token as a query parameter:
|
|
87
87
|
|
|
88
88
|
```txt
|
|
89
|
-
https://<deploy-host>/channels/
|
|
89
|
+
https://<deploy-host>/channels/support-whatsapp/whatsapp/zapster/webhook?token=<ZAPSTER_WEBHOOK_TOKEN>
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
## Safety Rules
|
|
@@ -98,7 +98,9 @@ https://<deploy-host>/channels/chn_<id>/whatsapp/zapster/webhook?token=<ZAPSTER_
|
|
|
98
98
|
- Use optional `ZAPSTER_WEBHOOK_TOKEN` in the webhook URL when the endpoint should require an extra secret known only to AgentKit and Zapster.
|
|
99
99
|
- Unsupported media should be logged as skipped/unsupported without creating an agent run.
|
|
100
100
|
- AgentKit handles Zapster `message.received` envelopes with event id at `id`, message text at `data.content.text`, and contact identity at `data.sender.id`.
|
|
101
|
-
- Outbound replies call `POST https://api.zapsterapi.com/v1/wa/messages` with bearer auth and a JSON body containing `recipient`, `text`, and `instance_id`.
|
|
101
|
+
- Outbound replies call `POST https://api.zapsterapi.com/v1/wa/messages` with bearer auth and a JSON body containing `recipient`, `text`, and `instance_id`.
|
|
102
|
+
- Real provider success is recorded as `provider_sent` only when Zapster returns a provider message ID.
|
|
103
|
+
- Only set `AGENTKIT_CHANNEL_SEND_DRY_RUN=1` in tests when Zapster should not receive a real message. Dry-run deliveries are recorded as `adapter_stubbed`, not sent.
|
|
102
104
|
|
|
103
105
|
## Verification
|
|
104
106
|
|
|
@@ -107,6 +109,10 @@ agentkit channels status support-whatsapp
|
|
|
107
109
|
agentkit channels test support-whatsapp --message "hello"
|
|
108
110
|
agentkit channels deliveries list support-whatsapp
|
|
109
111
|
agentkit channels deliveries show <delivery-id>
|
|
112
|
+
agentkit channels buffers list support-whatsapp
|
|
113
|
+
agentkit channels buffers flush support-whatsapp <conversation-id>
|
|
114
|
+
agentkit channels buffers clear support-whatsapp <conversation-id>
|
|
115
|
+
agentkit channels buffers retry support-whatsapp <conversation-id>
|
|
110
116
|
```
|
|
111
117
|
|
|
112
118
|
## Troubleshooting
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andreprado/agentkit",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.14",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
"docs",
|
|
21
21
|
"!docs/guides/backend-contracts.md",
|
|
22
22
|
"!docs/guides/debug-channel.md",
|
|
23
|
-
"!docs/guides/deploy-agentkit-cloud-hostinger.md",
|
|
24
23
|
"README.md"
|
|
25
24
|
],
|
|
26
25
|
"publishConfig": {
|
package/src/cli/cloud-client.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
1
|
+
import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
@@ -97,6 +97,7 @@ export async function readCloudAuth(): Promise<CloudAuthConfig | null> {
|
|
|
97
97
|
export async function writeCloudAuth(config: CloudAuthConfig): Promise<void> {
|
|
98
98
|
await mkdir(join(homedir(), ".agentkit"), { recursive: true });
|
|
99
99
|
await writeFile(cloudAuthPath(), `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
|
|
100
|
+
await chmod(cloudAuthPath(), 0o600);
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
export async function clearCloudAuth(): Promise<void> {
|
|
@@ -178,13 +179,17 @@ function cloudAuthMatchesApiUrl(auth: CloudAuthConfig, apiUrl: string): boolean
|
|
|
178
179
|
export async function cloudApiRequest(apiUrl: string, path: string, init: RequestInit): Promise<unknown> {
|
|
179
180
|
const auth = await readCloudAuth();
|
|
180
181
|
const headers = new Headers(init.headers);
|
|
182
|
+
const apiToken =
|
|
183
|
+
auth?.source === "file" && !cloudAuthMatchesApiUrl(auth, apiUrl)
|
|
184
|
+
? undefined
|
|
185
|
+
: auth?.token;
|
|
181
186
|
|
|
182
187
|
if (!headers.has("Content-Type") && init.body !== undefined) {
|
|
183
188
|
headers.set("Content-Type", "application/json");
|
|
184
189
|
}
|
|
185
190
|
|
|
186
|
-
if (
|
|
187
|
-
headers.set("Authorization", `Bearer ${
|
|
191
|
+
if (apiToken && !headers.has("Authorization")) {
|
|
192
|
+
headers.set("Authorization", `Bearer ${apiToken}`);
|
|
188
193
|
}
|
|
189
194
|
|
|
190
195
|
const response = await fetch(new URL(path, parseCloudApiUrl(apiUrl)).href, {
|
|
@@ -43,6 +43,35 @@ type CloudDelivery = {
|
|
|
43
43
|
}>;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
+
type CloudChannelBuffer = {
|
|
47
|
+
channel_id: string;
|
|
48
|
+
deploy_id: string;
|
|
49
|
+
conversation_id: string;
|
|
50
|
+
job_id?: string;
|
|
51
|
+
delivery_id?: string;
|
|
52
|
+
buffered_delivery_ids: string[];
|
|
53
|
+
buffered_message_count: number;
|
|
54
|
+
buffered_char_count: number;
|
|
55
|
+
status: string;
|
|
56
|
+
first_received_at: string;
|
|
57
|
+
last_received_at: string;
|
|
58
|
+
available_at?: string;
|
|
59
|
+
age_ms: number;
|
|
60
|
+
stale: boolean;
|
|
61
|
+
message_preview: string;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
type ChannelBufferActionResponse = {
|
|
65
|
+
buffer: CloudChannelBuffer | null;
|
|
66
|
+
job: {
|
|
67
|
+
id: string;
|
|
68
|
+
status: string;
|
|
69
|
+
conversation_id: string;
|
|
70
|
+
} | null;
|
|
71
|
+
cleared?: boolean;
|
|
72
|
+
retried?: boolean;
|
|
73
|
+
};
|
|
74
|
+
|
|
46
75
|
type ChannelSetupResponse = {
|
|
47
76
|
channel: CloudChannel;
|
|
48
77
|
setup?: {
|
|
@@ -65,6 +94,7 @@ type ChannelDoctorReport = {
|
|
|
65
94
|
message: string;
|
|
66
95
|
fix?: string;
|
|
67
96
|
}>;
|
|
97
|
+
buffers: CloudChannelBuffer[];
|
|
68
98
|
last_delivery: {
|
|
69
99
|
inbound: CloudDelivery | null;
|
|
70
100
|
outbound: CloudDelivery | null;
|
|
@@ -76,7 +106,7 @@ type ChannelDoctorReport = {
|
|
|
76
106
|
export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
|
|
77
107
|
await loadLocalEnvIntoProcess(process.cwd());
|
|
78
108
|
|
|
79
|
-
const [subcommand, first, second] = args.positional;
|
|
109
|
+
const [subcommand, first, second, third] = args.positional;
|
|
80
110
|
|
|
81
111
|
if (subcommand === "list") {
|
|
82
112
|
const deployState = await readDeployStateIfExists(process.cwd());
|
|
@@ -154,7 +184,11 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
|
|
|
154
184
|
return;
|
|
155
185
|
}
|
|
156
186
|
|
|
157
|
-
if (channel.type === "whatsapp") {
|
|
187
|
+
if (channel.type === "whatsapp" && channel.provider === "zapster") {
|
|
188
|
+
const smoke = await runChannelSmoke(channel, args.flags.api, args.flags.message);
|
|
189
|
+
console.log(`Webhook smoke: ${smoke.status}${smoke.delivery_id ? ` (${smoke.delivery_id})` : ""}`);
|
|
190
|
+
console.log(`Next human step: paste ${channel.webhook_url} into ${channel.provider}'s webhook settings.`);
|
|
191
|
+
} else if (channel.type === "whatsapp") {
|
|
158
192
|
console.log("Smoke: skipped until the provider webhook is configured.");
|
|
159
193
|
console.log(`Next human step: paste ${channel.webhook_url} into ${channel.provider}'s webhook settings.`);
|
|
160
194
|
} else {
|
|
@@ -286,8 +320,53 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
|
|
|
286
320
|
}
|
|
287
321
|
}
|
|
288
322
|
|
|
323
|
+
if (subcommand === "buffers") {
|
|
324
|
+
if (first === "list") {
|
|
325
|
+
if (!second) {
|
|
326
|
+
throw new Error("Usage: agentkit channels buffers list <name>");
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const channel = await resolveCloudChannelByName(second, args.flags.api);
|
|
330
|
+
const response = await cloudGet<{ buffers: CloudChannelBuffer[] }>(
|
|
331
|
+
parseCloudApiUrl(args.flags.api),
|
|
332
|
+
`/v1/channels/${encodeURIComponent(channel.id)}/buffers`,
|
|
333
|
+
);
|
|
334
|
+
printChannelBuffers(response.buffers);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (first === "show") {
|
|
339
|
+
if (!second) {
|
|
340
|
+
throw new Error("Usage: agentkit channels buffers show [<name>] <conversation-id>");
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const target = await resolveChannelBufferTarget(second, third, args.flags.api);
|
|
344
|
+
const response = await cloudGet<{ buffer: CloudChannelBuffer }>(
|
|
345
|
+
parseCloudApiUrl(args.flags.api),
|
|
346
|
+
`/v1/channels/${encodeURIComponent(target.channel.id)}/buffers/${encodeURIComponent(target.conversationId)}`,
|
|
347
|
+
);
|
|
348
|
+
console.log(JSON.stringify(response.buffer, null, 2));
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (first === "flush" || first === "clear" || first === "retry") {
|
|
353
|
+
if (!second) {
|
|
354
|
+
throw new Error(`Usage: agentkit channels buffers ${first} [<name>] <conversation-id>`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const target = await resolveChannelBufferTarget(second, third, args.flags.api);
|
|
358
|
+
const response = await cloudPost<ChannelBufferActionResponse>(
|
|
359
|
+
parseCloudApiUrl(args.flags.api),
|
|
360
|
+
`/v1/channels/${encodeURIComponent(target.channel.id)}/buffers/${encodeURIComponent(target.conversationId)}/${first}`,
|
|
361
|
+
{},
|
|
362
|
+
);
|
|
363
|
+
printChannelBufferAction(first, response);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
289
368
|
throw new Error(
|
|
290
|
-
"Usage: agentkit channels list | add | connect | setup | status | doctor | test | deliveries list | deliveries show",
|
|
369
|
+
"Usage: agentkit channels list | add | connect | setup | status | doctor | test | deliveries list | deliveries show | buffers list | buffers show | buffers flush | buffers clear | buffers retry",
|
|
291
370
|
);
|
|
292
371
|
}
|
|
293
372
|
|
|
@@ -635,6 +714,16 @@ function printChannelDoctor(report: ChannelDoctorReport): void {
|
|
|
635
714
|
` Outbound: ${report.last_delivery.outbound ? `${report.last_delivery.outbound.status} ${report.last_delivery.outbound.updated_at ?? report.last_delivery.outbound.received_at}` : "none"}`,
|
|
636
715
|
);
|
|
637
716
|
|
|
717
|
+
if (report.buffers.length > 0) {
|
|
718
|
+
console.log("");
|
|
719
|
+
console.log("Buffers:");
|
|
720
|
+
for (const buffer of report.buffers) {
|
|
721
|
+
console.log(
|
|
722
|
+
` ${buffer.conversation_id}: ${buffer.status} messages=${buffer.buffered_message_count} stale=${buffer.stale ? "yes" : "no"}`,
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
638
727
|
if (report.probable_error) {
|
|
639
728
|
console.log("");
|
|
640
729
|
console.log(`Probable error: ${report.probable_error}`);
|
|
@@ -660,19 +749,119 @@ function formatCheckStatus(status: ChannelDoctorReport["checks"][number]["status
|
|
|
660
749
|
return "UNKNOWN";
|
|
661
750
|
}
|
|
662
751
|
|
|
752
|
+
function printChannelBuffers(buffers: CloudChannelBuffer[]): void {
|
|
753
|
+
if (buffers.length === 0) {
|
|
754
|
+
console.log("No channel buffers found.");
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
console.log("CONVERSATION\tSTATUS\tMESSAGES\tCHARS\tSTALE\tAVAILABLE_AT\tJOB");
|
|
759
|
+
for (const buffer of buffers) {
|
|
760
|
+
console.log(
|
|
761
|
+
[
|
|
762
|
+
buffer.conversation_id,
|
|
763
|
+
buffer.status,
|
|
764
|
+
String(buffer.buffered_message_count),
|
|
765
|
+
String(buffer.buffered_char_count),
|
|
766
|
+
buffer.stale ? "yes" : "no",
|
|
767
|
+
buffer.available_at ?? "",
|
|
768
|
+
buffer.job_id ?? "",
|
|
769
|
+
].join("\t"),
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function printChannelBufferAction(action: string, response: ChannelBufferActionResponse): void {
|
|
775
|
+
const conversationId = response.buffer?.conversation_id ?? response.job?.conversation_id ?? "unknown";
|
|
776
|
+
|
|
777
|
+
if (action === "flush") {
|
|
778
|
+
console.log(
|
|
779
|
+
response.job
|
|
780
|
+
? `Flushed buffer ${conversationId}; queued job ${response.job.id}.`
|
|
781
|
+
: `No queued buffer was flushed for ${conversationId}.`,
|
|
782
|
+
);
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if (action === "clear") {
|
|
787
|
+
console.log(
|
|
788
|
+
response.cleared
|
|
789
|
+
? `Cleared buffer ${conversationId}.`
|
|
790
|
+
: `No queued buffer was cleared for ${conversationId}.`,
|
|
791
|
+
);
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
console.log(
|
|
796
|
+
response.retried && response.job
|
|
797
|
+
? `Retried channel job ${response.job.id} for ${conversationId}.`
|
|
798
|
+
: `No failed or dead-lettered job was retried for ${conversationId}.`,
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
|
|
663
802
|
async function resolveCloudChannelByName(name: string, apiFlag: string | boolean | undefined): Promise<CloudChannel> {
|
|
803
|
+
const channels = await listCloudChannelsForCurrentDeploy(apiFlag);
|
|
804
|
+
const channel = channels.find((candidate) => candidate.name === name);
|
|
805
|
+
|
|
806
|
+
if (!channel) {
|
|
807
|
+
const deployState = await readDeployStateRequired(process.cwd());
|
|
808
|
+
throw new Error(`Channel "${name}" was not found for deploy ${deployState.deploy_id}.`);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
return channel;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
async function listCloudChannelsForCurrentDeploy(apiFlag: string | boolean | undefined): Promise<CloudChannel[]> {
|
|
664
815
|
const deployState = await readDeployStateRequired(process.cwd());
|
|
665
816
|
const response = await cloudGet<{ channels: CloudChannel[] }>(
|
|
666
817
|
parseCloudApiUrl(apiFlag),
|
|
667
818
|
`/v1/deploys/${encodeURIComponent(deployState.deploy_id)}/channels`,
|
|
668
819
|
);
|
|
669
|
-
const channel = response.channels.find((candidate) => candidate.name === name);
|
|
670
820
|
|
|
671
|
-
|
|
672
|
-
|
|
821
|
+
return response.channels;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
async function resolveChannelBufferTarget(
|
|
825
|
+
first: string,
|
|
826
|
+
second: string | undefined,
|
|
827
|
+
apiFlag: string | boolean | undefined,
|
|
828
|
+
): Promise<{ channel: CloudChannel; conversationId: string }> {
|
|
829
|
+
if (second) {
|
|
830
|
+
return {
|
|
831
|
+
channel: await resolveCloudChannelByName(first, apiFlag),
|
|
832
|
+
conversationId: second,
|
|
833
|
+
};
|
|
673
834
|
}
|
|
674
835
|
|
|
675
|
-
|
|
836
|
+
const conversationId = first;
|
|
837
|
+
const channels = await listCloudChannelsForCurrentDeploy(apiFlag);
|
|
838
|
+
const matches: CloudChannel[] = [];
|
|
839
|
+
|
|
840
|
+
for (const channel of channels) {
|
|
841
|
+
const response = await cloudGet<{ buffers: CloudChannelBuffer[] }>(
|
|
842
|
+
parseCloudApiUrl(apiFlag),
|
|
843
|
+
`/v1/channels/${encodeURIComponent(channel.id)}/buffers`,
|
|
844
|
+
);
|
|
845
|
+
|
|
846
|
+
if (response.buffers.some((buffer) => buffer.conversation_id === conversationId)) {
|
|
847
|
+
matches.push(channel);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
if (matches.length === 1) {
|
|
852
|
+
return {
|
|
853
|
+
channel: matches[0],
|
|
854
|
+
conversationId,
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (matches.length > 1) {
|
|
859
|
+
throw new Error(
|
|
860
|
+
`Conversation ${conversationId} has buffers in multiple channels. Use: agentkit channels buffers show <name> ${conversationId}`,
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
throw new Error(`No channel buffer found for conversation ${conversationId}.`);
|
|
676
865
|
}
|
|
677
866
|
|
|
678
867
|
function channelProviderForCli(type: string, providerFlag: string | boolean | undefined): string {
|
package/src/cli/help.ts
CHANGED
|
@@ -84,6 +84,11 @@ Usage:
|
|
|
84
84
|
agentkit channels test <name> [--message <text>] [--fixture <path>] [--api <url>]
|
|
85
85
|
agentkit channels deliveries list <name> [--api <url>]
|
|
86
86
|
agentkit channels deliveries show <delivery-id> [--api <url>]
|
|
87
|
+
agentkit channels buffers list <name> [--api <url>]
|
|
88
|
+
agentkit channels buffers show [<name>] <conversation-id> [--api <url>]
|
|
89
|
+
agentkit channels buffers flush [<name>] <conversation-id> [--api <url>]
|
|
90
|
+
agentkit channels buffers clear [<name>] <conversation-id> [--api <url>]
|
|
91
|
+
agentkit channels buffers retry [<name>] <conversation-id> [--api <url>]
|
|
87
92
|
agentkit inspect
|
|
88
93
|
agentkit build [--target cloudflare|container]
|
|
89
94
|
agentkit login --token <token> [--api <url>]
|
|
@@ -156,7 +156,7 @@ export async function sendTelegramMessage(
|
|
|
156
156
|
|
|
157
157
|
return {
|
|
158
158
|
ok: true,
|
|
159
|
-
status: "
|
|
159
|
+
status: "stubbed",
|
|
160
160
|
providerRequestId: request.idempotencyKey,
|
|
161
161
|
providerMetadata: {
|
|
162
162
|
method: request.method,
|
|
@@ -247,11 +247,26 @@ export async function sendTelegramMessage(
|
|
|
247
247
|
const result = isRecord(payload.result) ? payload.result : undefined;
|
|
248
248
|
const messageId = readNumberishString(result ?? {}, "message_id");
|
|
249
249
|
|
|
250
|
+
if (!messageId) {
|
|
251
|
+
return {
|
|
252
|
+
ok: false,
|
|
253
|
+
retryable: false,
|
|
254
|
+
code: "channel_send_failed",
|
|
255
|
+
message: "Telegram sendMessage did not return result.message_id.",
|
|
256
|
+
providerRequestId: request.idempotencyKey,
|
|
257
|
+
providerMetadata: {
|
|
258
|
+
method: request.method,
|
|
259
|
+
url: request.url,
|
|
260
|
+
body: request.body,
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
250
265
|
return {
|
|
251
266
|
ok: true,
|
|
252
267
|
status: "sent",
|
|
253
268
|
providerRequestId: request.idempotencyKey,
|
|
254
|
-
|
|
269
|
+
providerMessageId: messageId,
|
|
255
270
|
providerMetadata: {
|
|
256
271
|
method: request.method,
|
|
257
272
|
url: request.url,
|
|
@@ -263,7 +263,7 @@ export async function sendZapsterMessage(
|
|
|
263
263
|
|
|
264
264
|
return {
|
|
265
265
|
ok: true,
|
|
266
|
-
status: "
|
|
266
|
+
status: "stubbed",
|
|
267
267
|
providerRequestId: request.idempotencyKey,
|
|
268
268
|
providerMetadata: {
|
|
269
269
|
method: request.method,
|
|
@@ -351,11 +351,27 @@ export async function sendZapsterMessage(
|
|
|
351
351
|
};
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
+
if (!messageId) {
|
|
355
|
+
return {
|
|
356
|
+
ok: false,
|
|
357
|
+
retryable: false,
|
|
358
|
+
code: "channel_send_failed",
|
|
359
|
+
message: "Zapster sendMessage did not return message_id or id.",
|
|
360
|
+
providerRequestId: request.idempotencyKey,
|
|
361
|
+
providerMetadata: {
|
|
362
|
+
method: request.method,
|
|
363
|
+
url: request.url,
|
|
364
|
+
status: response.status,
|
|
365
|
+
body: redactedPayload,
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
354
370
|
return {
|
|
355
371
|
ok: true,
|
|
356
372
|
status: "sent",
|
|
357
|
-
providerRequestId:
|
|
358
|
-
|
|
373
|
+
providerRequestId: request.idempotencyKey,
|
|
374
|
+
providerMessageId: messageId,
|
|
359
375
|
providerMetadata: {
|
|
360
376
|
method: request.method,
|
|
361
377
|
url: request.url,
|
package/src/runtime/channels.ts
CHANGED
|
@@ -13,6 +13,9 @@ export type ChannelDeliveryState =
|
|
|
13
13
|
| "queued"
|
|
14
14
|
| "running"
|
|
15
15
|
| "agent_completed"
|
|
16
|
+
| "adapter_stubbed"
|
|
17
|
+
| "provider_request_built"
|
|
18
|
+
| "provider_sent"
|
|
16
19
|
| "outbound_sent"
|
|
17
20
|
| "provider_failed"
|
|
18
21
|
| "synthetic_expected_failure"
|
|
@@ -89,7 +92,7 @@ export type ChannelSendResult =
|
|
|
89
92
|
ok: true;
|
|
90
93
|
providerRequestId?: string;
|
|
91
94
|
providerMessageId?: string;
|
|
92
|
-
status: "sent" | "delivered";
|
|
95
|
+
status: "stubbed" | "sent" | "delivered";
|
|
93
96
|
providerMetadata?: Record<string, unknown>;
|
|
94
97
|
}
|
|
95
98
|
| {
|
package/src/runtime/config.ts
CHANGED
|
@@ -32,6 +32,7 @@ const KNOWLEDGE_EMBEDDING_PROVIDERS = new Set<AgentKnowledgeEmbeddingProvider>([
|
|
|
32
32
|
const CHANNEL_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
33
33
|
const SECRET_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
|
|
34
34
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
35
|
+
let configImportNonce = 0;
|
|
35
36
|
|
|
36
37
|
export type LoadedAgentCapsule = {
|
|
37
38
|
root: string;
|
|
@@ -108,15 +109,13 @@ function resolveLocalStoragePath(root: string, config: AgentConfig): string | un
|
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
async function loadConfigModule(configPath: string): Promise<unknown> {
|
|
111
|
-
const
|
|
112
|
-
configUrl.searchParams.set("t", String(Date.now()));
|
|
112
|
+
const source = await readFile(configPath, "utf8");
|
|
113
113
|
|
|
114
114
|
try {
|
|
115
|
-
|
|
116
|
-
return module.default;
|
|
115
|
+
return await importConfigSource(configPath, source);
|
|
117
116
|
} catch (error) {
|
|
118
117
|
if (isMissingAgentKitSelfImport(error)) {
|
|
119
|
-
return loadConfigModuleWithLocalSelfImport(configPath, error);
|
|
118
|
+
return loadConfigModuleWithLocalSelfImport(configPath, source, error);
|
|
120
119
|
}
|
|
121
120
|
|
|
122
121
|
throw new AgentKitError(
|
|
@@ -127,8 +126,11 @@ async function loadConfigModule(configPath: string): Promise<unknown> {
|
|
|
127
126
|
}
|
|
128
127
|
}
|
|
129
128
|
|
|
130
|
-
async function loadConfigModuleWithLocalSelfImport(
|
|
131
|
-
|
|
129
|
+
async function loadConfigModuleWithLocalSelfImport(
|
|
130
|
+
configPath: string,
|
|
131
|
+
source: string,
|
|
132
|
+
originalError: unknown,
|
|
133
|
+
): Promise<unknown> {
|
|
132
134
|
const localAgentKitUrl = pathToFileURL(resolve(moduleDir, "../index.ts")).href;
|
|
133
135
|
const patchedSource = source.replace(
|
|
134
136
|
/from\s+["']@andreprado\/agentkit["']/g,
|
|
@@ -143,15 +145,20 @@ async function loadConfigModuleWithLocalSelfImport(configPath: string, originalE
|
|
|
143
145
|
);
|
|
144
146
|
}
|
|
145
147
|
|
|
148
|
+
return importConfigSource(configPath, patchedSource);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function importConfigSource(configPath: string, source: string): Promise<unknown> {
|
|
152
|
+
const nonce = nextConfigImportNonce();
|
|
146
153
|
const tempConfigPath = join(
|
|
147
154
|
dirname(configPath),
|
|
148
|
-
`.agentkit.config.loader-${process.pid}-${
|
|
155
|
+
`.agentkit.config.loader-${process.pid}-${nonce}-${Math.random().toString(16).slice(2)}.ts`,
|
|
149
156
|
);
|
|
150
157
|
|
|
151
158
|
try {
|
|
152
|
-
await writeFile(tempConfigPath,
|
|
159
|
+
await writeFile(tempConfigPath, source);
|
|
153
160
|
const configUrl = pathToFileURL(tempConfigPath);
|
|
154
|
-
configUrl.searchParams.set("t",
|
|
161
|
+
configUrl.searchParams.set("t", nonce);
|
|
155
162
|
const module = (await import(configUrl.href)) as { default?: unknown };
|
|
156
163
|
return module.default;
|
|
157
164
|
} catch (error) {
|
|
@@ -165,6 +172,11 @@ async function loadConfigModuleWithLocalSelfImport(configPath: string, originalE
|
|
|
165
172
|
}
|
|
166
173
|
}
|
|
167
174
|
|
|
175
|
+
function nextConfigImportNonce(): string {
|
|
176
|
+
configImportNonce += 1;
|
|
177
|
+
return `${Date.now()}-${configImportNonce}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
168
180
|
function isMissingAgentKitSelfImport(error: unknown): boolean {
|
|
169
181
|
const message = error instanceof Error ? error.message : String(error);
|
|
170
182
|
return message.includes("@andreprado/agentkit");
|
|
@@ -781,6 +781,7 @@ export default {
|
|
|
781
781
|
|
|
782
782
|
function isHostedChannelWebhookPath(pathname) {
|
|
783
783
|
return (
|
|
784
|
+
/^\\/channels\\/[^/]+\\/(?:website\\/agentkit|telegram\\/telegram|whatsapp\\/(?:zapster|meta))\\/webhook$/.test(pathname) ||
|
|
784
785
|
/^\\/channels\\/[^/]+\\/(?:website|telegram)\\/webhook$/.test(pathname) ||
|
|
785
786
|
/^\\/channels\\/[^/]+\\/whatsapp\\/(?:zapster|meta)\\/webhook$/.test(pathname)
|
|
786
787
|
);
|
|
@@ -43,6 +43,11 @@ Behavior:
|
|
|
43
43
|
Debug:
|
|
44
44
|
|
|
45
45
|
```sh
|
|
46
|
+
agentkit channels buffers list <name>
|
|
47
|
+
agentkit channels buffers show <conversation-id>
|
|
48
|
+
agentkit channels buffers flush <conversation-id>
|
|
49
|
+
agentkit channels buffers clear <conversation-id>
|
|
50
|
+
agentkit channels buffers retry <conversation-id>
|
|
46
51
|
agentkit channels deliveries list <name>
|
|
47
52
|
agentkit channels deliveries show <delivery-id>
|
|
48
53
|
```
|
|
@@ -54,5 +59,7 @@ buffered
|
|
|
54
59
|
queued
|
|
55
60
|
running
|
|
56
61
|
agent_completed
|
|
57
|
-
|
|
62
|
+
provider_request_built
|
|
63
|
+
provider_sent
|
|
64
|
+
adapter_stubbed
|
|
58
65
|
```
|
|
@@ -9,6 +9,11 @@ agentkit channels doctor <name>
|
|
|
9
9
|
agentkit channels test <name> --message "hello"
|
|
10
10
|
agentkit channels deliveries list <name> --since 24h
|
|
11
11
|
agentkit channels deliveries show <delivery-id>
|
|
12
|
+
agentkit channels buffers list <name>
|
|
13
|
+
agentkit channels buffers show <conversation-id>
|
|
14
|
+
agentkit channels buffers flush <conversation-id>
|
|
15
|
+
agentkit channels buffers clear <conversation-id>
|
|
16
|
+
agentkit channels buffers retry <conversation-id>
|
|
12
17
|
```
|
|
13
18
|
|
|
14
19
|
Common states:
|
|
@@ -21,7 +26,9 @@ buffered
|
|
|
21
26
|
queued
|
|
22
27
|
running
|
|
23
28
|
agent_completed
|
|
24
|
-
|
|
29
|
+
provider_request_built
|
|
30
|
+
provider_sent
|
|
31
|
+
adapter_stubbed
|
|
25
32
|
delivered
|
|
26
33
|
provider_failed
|
|
27
34
|
synthetic_expected_failure
|
|
@@ -39,3 +46,5 @@ Common errors:
|
|
|
39
46
|
- `channel_limit_exceeded`: backpressure skipped the message.
|
|
40
47
|
- `synthetic_expected_failure`: a synthetic test reached AgentKit, but the provider correctly rejected a fake test recipient.
|
|
41
48
|
- `buffered` delivery state: message is waiting for the channel quiet window or max wait before one coalesced agent run is queued.
|
|
49
|
+
- `adapter_stubbed`: explicit dry-run mode built a provider request but did not call the provider.
|
|
50
|
+
- `provider_sent`: the provider accepted the outbound send and returned a provider message ID.
|