@andreprado/agentkit 0.1.0-alpha.5 → 0.1.0-alpha.7

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 (87) hide show
  1. package/README.md +9 -0
  2. package/docs/guides/add-channel.md +25 -0
  3. package/docs/guides/add-knowledge.md +134 -0
  4. package/docs/guides/agentkit-skills-architecture.md +471 -0
  5. package/docs/guides/channels-production-handoff.md +2 -0
  6. package/docs/guides/connect-telegram.md +17 -0
  7. package/docs/guides/connect-whatsapp-zapster.md +16 -0
  8. package/docs/guides/create-agent.md +10 -1
  9. package/docs/guides/run-evals.md +36 -1
  10. package/docs/llms-full.txt +90 -1
  11. package/docs/llms.txt +9 -2
  12. package/package.json +2 -1
  13. package/src/cli/cloud-client.ts +10 -2
  14. package/src/cli/commands/channels.ts +67 -3
  15. package/src/cli/commands/knowledge.ts +136 -0
  16. package/src/cli/deploy-chat-ui.ts +7 -0
  17. package/src/cli/deploy-readiness.ts +19 -0
  18. package/src/cli/help.ts +26 -2
  19. package/src/cli/index.ts +140 -8
  20. package/src/cloud/artifact.ts +92 -1
  21. package/src/cloud/contracts.ts +16 -0
  22. package/src/create-project.ts +38 -6
  23. package/src/index.ts +142 -1
  24. package/src/providers/pi.ts +1 -1
  25. package/src/providers/test.ts +1 -1
  26. package/src/runtime/channel-buffer.ts +30 -0
  27. package/src/runtime/channels.ts +1 -0
  28. package/src/runtime/chat.ts +21 -2
  29. package/src/runtime/config.ts +175 -0
  30. package/src/runtime/core/manifest.ts +37 -0
  31. package/src/runtime/database.ts +93 -2
  32. package/src/runtime/db-commands.ts +9 -0
  33. package/src/runtime/deploy-readiness.ts +12 -0
  34. package/src/runtime/dev-server.ts +201 -11
  35. package/src/runtime/evals.ts +210 -20
  36. package/src/runtime/inspect.ts +39 -0
  37. package/src/runtime/knowledge/chunk.ts +333 -0
  38. package/src/runtime/knowledge/config.ts +135 -0
  39. package/src/runtime/knowledge/embeddings.ts +133 -0
  40. package/src/runtime/knowledge/ingest.ts +521 -0
  41. package/src/runtime/knowledge/prompt-policy.ts +30 -0
  42. package/src/runtime/knowledge/retrieve.ts +283 -0
  43. package/src/runtime/knowledge/schema.ts +56 -0
  44. package/src/runtime/knowledge/tool.ts +64 -0
  45. package/src/runtime/knowledge/vector.ts +258 -0
  46. package/src/runtime/spec.ts +152 -0
  47. package/src/runtime/sync.ts +144 -0
  48. package/src/runtime/targets/cloudflare/build.ts +514 -4
  49. package/src/runtime/tools.ts +121 -1
  50. package/src/runtime/traces.ts +41 -0
  51. package/src/storage/sqlite.ts +141 -0
  52. package/src/templates/blank.ts +16 -5
  53. package/src/templates/dentista.ts +17 -2
  54. package/src/templates/skills/agentkit-build-agent/SKILL.md +51 -0
  55. package/src/templates/skills/agentkit-build-agent/templates/appointment-intake.instructions.md +20 -0
  56. package/src/templates/skills/agentkit-build-agent/templates/sales-qualifier.instructions.md +17 -0
  57. package/src/templates/skills/agentkit-build-agent/templates/support-agent.instructions.md +16 -0
  58. package/src/templates/skills/agentkit-capsule/SKILL.md +62 -0
  59. package/src/templates/skills/agentkit-capsule/references/docs-router.md +15 -0
  60. package/src/templates/skills/agentkit-channels/SKILL.md +62 -0
  61. package/src/templates/skills/agentkit-channels/references/channel-buffering.md +58 -0
  62. package/src/templates/skills/agentkit-channels/references/channel-debugging.md +37 -0
  63. package/src/templates/skills/agentkit-channels/references/telegram.md +37 -0
  64. package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +37 -0
  65. package/src/templates/skills/agentkit-database/SKILL.md +45 -0
  66. package/src/templates/skills/agentkit-database/templates/appointments.schema.sql +15 -0
  67. package/src/templates/skills/agentkit-database/templates/leads.schema.sql +17 -0
  68. package/src/templates/skills/agentkit-deploy/SKILL.md +44 -0
  69. package/src/templates/skills/agentkit-evals/SKILL.md +60 -0
  70. package/src/templates/skills/agentkit-evals/templates/multi-turn.eval.md +22 -0
  71. package/src/templates/skills/agentkit-evals/templates/no-leak.eval.md +14 -0
  72. package/src/templates/skills/agentkit-evals/templates/smoke.eval.md +14 -0
  73. package/src/templates/skills/agentkit-evals/templates/tool-call.eval.md +18 -0
  74. package/src/templates/skills/agentkit-knowledge/SKILL.md +40 -0
  75. package/src/templates/skills/agentkit-knowledge/templates/faq.md +14 -0
  76. package/src/templates/skills/agentkit-knowledge/templates/policies.md +14 -0
  77. package/src/templates/skills/agentkit-knowledge/templates/prices.csv +3 -0
  78. package/src/templates/skills/agentkit-prompts/SKILL.md +45 -0
  79. package/src/templates/skills/agentkit-prompts/templates/knowledge-grounded-faq.instructions.md +11 -0
  80. package/src/templates/skills/agentkit-provider/SKILL.md +57 -0
  81. package/src/templates/skills/agentkit-security/SKILL.md +55 -0
  82. package/src/templates/skills/agentkit-tools/SKILL.md +36 -0
  83. package/src/templates/skills/agentkit-tools/examples/database-write.tool.md +35 -0
  84. package/src/templates/skills/agentkit-tools/examples/eval-safe-external-action.tool.md +37 -0
  85. package/src/templates/skills/agentkit-tools/examples/lookup-order.tool.md +46 -0
  86. package/src/templates/skills/agentkit-troubleshooting/SKILL.md +52 -0
  87. package/src/templates/support.ts +15 -4
@@ -51,6 +51,21 @@ export default defineAgent({
51
51
  });
52
52
  ```
53
53
 
54
+ To answer once after a burst of Telegram messages, enable channel buffering:
55
+
56
+ ```ts
57
+ telegramChannel({
58
+ name: "support-telegram",
59
+ buffer: {
60
+ mode: "debounce",
61
+ quietWindowMs: 1500,
62
+ maxWaitMs: 8000,
63
+ maxMessages: 20,
64
+ maxChars: 8000,
65
+ },
66
+ })
67
+ ```
68
+
54
69
  ## Setup Behavior
55
70
 
56
71
  `agentkit channels setup support-telegram` is read-only and prints the webhook URL.
@@ -61,6 +76,8 @@ export default defineAgent({
61
76
  - `secret_token`: `TELEGRAM_WEBHOOK_SECRET`;
62
77
  - `allowed_updates`: `["message"]`.
63
78
 
79
+ Against AgentKit Cloud, `--apply` runs through the Cloud API and uses managed hosted secrets. The legacy local fallback only uses `TELEGRAM_BOT_TOKEN` and `TELEGRAM_WEBHOOK_SECRET` from the shell when the Cloud API does not expose channel setup.
80
+
64
81
  ## Safety Rules
65
82
 
66
83
  - Use `--apply` only when real Telegram secrets are present.
@@ -50,6 +50,22 @@ export default defineAgent({
50
50
  });
51
51
  ```
52
52
 
53
+ To handle clients who send several WhatsApp messages before waiting, enable channel buffering:
54
+
55
+ ```ts
56
+ whatsappChannel({
57
+ name: "support-whatsapp",
58
+ provider: "zapster",
59
+ buffer: {
60
+ mode: "debounce",
61
+ quietWindowMs: 2500,
62
+ maxWaitMs: 12000,
63
+ maxMessages: 20,
64
+ maxChars: 8000,
65
+ },
66
+ })
67
+ ```
68
+
53
69
  ## Setup Behavior
54
70
 
55
71
  `agentkit channels setup support-whatsapp` prints the stable AgentKit webhook URL. Paste it into Zapster webhook settings and configure Zapster to send the same shared secret as `ZAPSTER_WEBHOOK_SECRET`.
@@ -84,7 +84,16 @@ Primary flow:
84
84
  Develop an appointment and intake agent for an ophthalmology office.
85
85
  ```
86
86
 
87
- The generated `AGENTS.md`, `AGENTKIT.md`, and `CLAUDE.md` tell the coding agent which files to edit and which verification commands to run. There is no wizard or recipe layer: the coding agent edits the capsule directly from the scaffold, contract, and owner request.
87
+ The generated `AGENTS.md`, `AGENTKIT.md`, `CLAUDE.md`, and `skills/` pack tell the coding agent which files to edit, which task skill to load, and which verification commands to run. There is no wizard or recipe layer: the coding agent edits the capsule directly from the scaffold, contract, and owner request. The default router is `skills/agentkit-capsule/SKILL.md`; `llms-full.txt` is reserved for complete-contract checks.
88
+
89
+ After the owner gives the general idea, the coding agent should create or update the implementation contract itself:
90
+
91
+ ```sh
92
+ npm run agentkit -- spec init --brief "Develop an appointment and intake agent for an ophthalmology office."
93
+ npm run agentkit -- spec check
94
+ ```
95
+
96
+ `AGENT_SPEC.md` is an internal working contract for the coding agent. It is not a form the owner must fill before work starts.
88
97
 
89
98
  Optional shortcut when copying a prompt into another coding agent:
90
99
 
@@ -22,6 +22,15 @@ Run evals:
22
22
  agentkit eval run
23
23
  ```
24
24
 
25
+ Create an eval from a stored conversation:
26
+
27
+ ```sh
28
+ agentkit conversations list
29
+ agentkit conversations trace <conversation-id>
30
+ agentkit eval from-conversation <conversation-id>
31
+ agentkit eval run
32
+ ```
33
+
25
34
  ## Files Created Or Edited
26
35
 
27
36
  Create or edit:
@@ -62,7 +71,33 @@ matches_regex
62
71
  persisted_tool_call
63
72
  ```
64
73
 
65
- `persisted_tool_call` validates the tool call saved in local SQLite `tool_calls`, not a provider-specific raw response shape. It can be a tool name string or an object with `name`, `input`, `output`, `status`, and/or `visibility`.
74
+ Multi-turn conversation evals use `turns`:
75
+
76
+ ```ts
77
+ export default {
78
+ name: "buyer under budget",
79
+ turns: [
80
+ {
81
+ input: "I want a house up to 600k near Pinheiros.",
82
+ expect: {
83
+ persisted_tool_call: {
84
+ name: "buscar_imoveis",
85
+ status: "completed",
86
+ input: { maxPrice: 600000 },
87
+ },
88
+ },
89
+ },
90
+ {
91
+ input: "Show me the best two.",
92
+ expect: {
93
+ contains: ["Pinheiros", "R$"],
94
+ },
95
+ },
96
+ ],
97
+ };
98
+ ```
99
+
100
+ `persisted_tool_call` validates the tool call saved in local SQLite `tool_calls`, not a provider-specific raw response shape. It can be a tool name string or an object with `name`, `input`, `output`, `rendered`, `status`, and/or `visibility`.
66
101
 
67
102
  Use response assertions and persisted tool assertions together when internal operational output must not leak:
68
103
 
@@ -40,6 +40,10 @@ agentkit chat-ui --deploy
40
40
  agentkit chat-ui --deploy [--port <number>] [--token-file <path>]
41
41
  agentkit chat --message <text> [--conversation-id <id>]
42
42
  agentkit tool <name> [--input <path-or-json>]
43
+ agentkit knowledge add <path-or-url>
44
+ agentkit knowledge sync
45
+ agentkit knowledge inspect
46
+ agentkit knowledge search <query> [--top-k <number>]
43
47
  agentkit db migrate
44
48
  agentkit db reset --yes
45
49
  agentkit db shell
@@ -144,7 +148,7 @@ npm run agentkit -- handoff codex "Develop an appointment and intake agent for a
144
148
  npm run agentkit -- handoff claude "Develop an appointment and intake agent for an ophthalmology office."
145
149
  ```
146
150
 
147
- The command prints a ready-to-paste prompt that points the coding agent at `AGENTKIT.md` and the packaged `llms-full.txt` contract.
151
+ The command prints a ready-to-paste prompt that points the coding agent at `AGENTKIT.md`, the repo-local `skills/agentkit-capsule/SKILL.md` router when present, and the packaged `llms.txt` docs router. Load `llms-full.txt` only when a skill or ambiguous framework behavior requires the complete contract.
148
152
 
149
153
  The generated docs and handoff prompt must make UI testing explicit. For local UI testing, run `npm run dev`, open the printed `Chat:` URL, and tell the owner the exact URL. For hosted UI testing after deploy, run `npm run agentkit -- chat-ui --deploy`, open the printed `Chat:` URL, and tell the owner it is connected to the hosted deploy.
150
154
 
@@ -239,6 +243,73 @@ npm run chat -- --message "hello"
239
243
 
240
244
  If a provider key is missing, the runtime returns `secret_not_found`.
241
245
 
246
+ ## Knowledge Contract
247
+
248
+ Knowledge is AgentKit's native retrieval layer for facts the agent should ground in source files. Use it for FAQs, prices, policies, service descriptions, procedures, CSV tables, and reference docs. Do not put secrets, credentials, `.env` contents, or live customer/payment records in Knowledge. Use tools for live or authorization-sensitive data.
249
+
250
+ Configure Knowledge in `agentkit.config.ts`:
251
+
252
+ ```ts
253
+ knowledge: {
254
+ sources: [
255
+ "knowledge/faq.md",
256
+ { path: "knowledge/prices.csv", title: "Prices" },
257
+ ],
258
+ retrieval: {
259
+ topK: 8,
260
+ hybrid: false,
261
+ },
262
+ },
263
+ ```
264
+
265
+ Local Knowledge supports `.md`, `.markdown`, `.txt`, and `.csv` sources inside the Agent Capsule. Markdown chunks follow headings, text chunks follow paragraphs, and CSV chunks preserve row data with headers.
266
+
267
+ Embeddings are configured separately from the chat provider. The default provider is `none`, which gives local lexical search without an API key. For OpenAI embeddings:
268
+
269
+ ```ts
270
+ knowledge: {
271
+ sources: ["knowledge/faq.md"],
272
+ embedding: {
273
+ provider: "openai",
274
+ model: "text-embedding-3-small",
275
+ secret: "KNOWLEDGE_OPENAI_API_KEY",
276
+ },
277
+ retrieval: {
278
+ topK: 8,
279
+ hybrid: true,
280
+ },
281
+ },
282
+ ```
283
+
284
+ Set the local embedding secret with `agentkit env set KNOWLEDGE_OPENAI_API_KEY --stdin`. Do not commit the value.
285
+
286
+ Knowledge commands:
287
+
288
+ ```sh
289
+ agentkit knowledge add knowledge/faq.md
290
+ agentkit knowledge sync
291
+ agentkit knowledge inspect
292
+ agentkit knowledge search "refund policy" --top-k 3
293
+ ```
294
+
295
+ `knowledge add` indexes one local path. `knowledge sync` indexes all configured `knowledge.sources` and skips unchanged files by content hash. `agentkit dev` and `agentkit chat` also sync configured Knowledge automatically before local runs. `knowledge inspect` lists indexed sources and chunk counts. `knowledge search` validates retrieval before relying on the agent. When embeddings are configured locally, AgentKit stores canonical chunks in `.agentkit/agentkit.db`, rebuilds a local libSQL vector sidecar at `.agentkit/agentkit.vectors.db`, uses native `libsql_vector_idx` semantic search, and falls back to stored JSON embeddings if the native vector path is unavailable.
296
+
297
+ When `knowledge` is configured, AgentKit automatically registers the internal chat tool `agentkit_search_knowledge` and appends a prompt policy. The policy tells the agent to search before answering business-specific factual questions and not to expose raw retrieval JSON, scores, chunk IDs, or tool output objects. With `test/fake`, verify the internal tool directly:
298
+
299
+ ```sh
300
+ agentkit chat --message '{"tool":"agentkit_search_knowledge","input":{"query":"refund policy","topK":1}}'
301
+ ```
302
+
303
+ Expected output:
304
+
305
+ ```txt
306
+ Tool agentkit_search_knowledge: completed
307
+ ```
308
+
309
+ Cloudflare Knowledge deploys require `storage.driver: "agentkit"` and `storage.database.driver: "turso"`. `agentkit deploy doctor` and `agentkit build --target cloudflare` fail clearly when Knowledge is configured without Turso. Cloudflare artifacts include the Knowledge manifest, required embedding secret names, internal Knowledge schema, packaged local source contents, prompt policy, and hosted `agentkit_search_knowledge` runtime. During `agentkit deploy`, AgentKit Cloud applies the Knowledge schema, chunks packaged local sources, creates embeddings when configured, deletes stale hosted sources, and syncs sources, chunks, embedding metadata, FTS rows, and a native Turso `libsql_vector_idx` index into the project Turso database before publishing the Worker. Local `agentkit knowledge add/sync`, `agentkit dev`, and `agentkit chat` index configured Knowledge into local SQLite and the local libSQL vector sidecar; hosted deploy syncs configured local Knowledge sources automatically from the deploy artifact so private source material and embedding secrets do not move into client code. Hosted semantic search uses Turso native vector search when embeddings are configured and falls back to stored JSON embeddings if the native vector path is unavailable.
310
+
311
+ Full guide: `docs/guides/add-knowledge.md`.
312
+
242
313
  ## Channel Contract
243
314
 
244
315
  Channels are hosted inbound/outbound conversation transports. They are separate from tools: channels receive user messages, while tools let the agent call external systems.
@@ -272,6 +343,24 @@ Rules:
272
343
  - Config stores secret names only, never secret values.
273
344
  - AgentKit owns channel webhook URLs, dedupe, identities, queue state, and delivery logs.
274
345
  - Do not store channel plumbing in the user's Turso database.
346
+ - Use `buffer.mode: "debounce"` when a channel should coalesce rapid client messages into one agent run.
347
+ - Buffered deliveries show `buffered`, then flush to one `queued` run after `quietWindowMs`, `maxWaitMs`, `maxMessages`, or `maxChars`.
348
+
349
+ Channel buffer example:
350
+
351
+ ```ts
352
+ whatsappChannel({
353
+ name: "support-whatsapp",
354
+ provider: "zapster",
355
+ buffer: {
356
+ mode: "debounce",
357
+ quietWindowMs: 2500,
358
+ maxWaitMs: 12000,
359
+ maxMessages: 20,
360
+ maxChars: 8000,
361
+ },
362
+ })
363
+ ```
275
364
 
276
365
  Useful guides:
277
366
 
package/docs/llms.txt CHANGED
@@ -2,12 +2,13 @@
2
2
 
3
3
  AgentKit creates and runs Agent Capsules: folders with `agentkit.config.ts`, prompts, tools, evals, local storage, and agent-facing docs.
4
4
 
5
- Read `docs/llms-full.txt` when you need the whole operating contract.
5
+ Read `docs/llms-full.txt` only when you need the whole operating contract.
6
6
 
7
7
  Task guides:
8
8
 
9
9
  - Create a capsule: `docs/guides/create-agent.md`
10
10
  - Add a TypeScript tool: `docs/guides/add-tool.md`
11
+ - Add Knowledge from local docs/CSVs: `docs/guides/add-knowledge.md`
11
12
  - Run or prepare evals: `docs/guides/run-evals.md`
12
13
  - Switch from `test/fake` to a real provider: `docs/guides/use-provider.md`
13
14
  - Prepare for hosted deploy: `docs/guides/prepare-deploy.md`
@@ -15,11 +16,13 @@ Task guides:
15
16
  - Build container artifact: `agentkit build --target container`
16
17
  - Generate VPS handoff: `agentkit deploy --target vps --host agent.example.com --dry-run`
17
18
  - Add hosted channels: `docs/guides/add-channel.md`
19
+ - Buffer rapid channel messages: `docs/guides/add-channel.md#buffer-bursty-messages`
18
20
  - Connect Telegram: `docs/guides/connect-telegram.md`
19
21
  - Connect WhatsApp through Zapster: `docs/guides/connect-whatsapp-zapster.md`
20
22
  - Follow channel webhook and delivery-log safety rules: `docs/guides/channel-security.md`
21
23
  - Prepare Channels for production: `docs/guides/channels-production-handoff.md`
22
24
  - Follow secret, access, and tool safety rules: `docs/guides/security-rules.md`
25
+ - Plan repo-local skills for coding agents: `docs/guides/agentkit-skills-architecture.md`
23
26
 
24
27
  Current local commands:
25
28
 
@@ -33,6 +36,10 @@ agentkit env unset <NAME>
33
36
  agentkit handoff codex|claude [goal]
34
37
  agentkit chat --message "hello"
35
38
  agentkit tool <name> --input <path-or-json>
39
+ agentkit knowledge add <path-or-url>
40
+ agentkit knowledge sync
41
+ agentkit knowledge inspect
42
+ agentkit knowledge search <query> [--top-k <number>]
36
43
  agentkit eval run
37
44
  agentkit conversations list
38
45
  agentkit conversations show <conversation-id>
@@ -58,7 +65,7 @@ agentkit dev
58
65
  agentkit open
59
66
  ```
60
67
 
61
- Generated capsules include `AGENTKIT.md` and `AGENTS.md` so Codex, Claude Code, or another coding agent can treat the owner's natural-language request as the brief and start building immediately. `agentkit handoff codex "Develop an ophthalmology office intake agent"` is an optional prompt-printing shortcut for users who are not already inside a coding-agent workspace. There is no wizard or recipe layer: the coding agent edits the capsule directly from the scaffold and contract.
68
+ Generated capsules include `AGENTKIT.md`, `AGENTS.md`, and a repo-local `skills/` pack so Codex, Claude Code, or another coding agent can treat the owner's natural-language request as the brief and start building immediately without loading the full contract by default. Start with `skills/agentkit-capsule/SKILL.md`, then load the task skill for the current work. `agentkit handoff codex "Develop an ophthalmology office intake agent"` is an optional prompt-printing shortcut for users who are not already inside a coding-agent workspace. There is no wizard or recipe layer: the coding agent edits the capsule directly from the scaffold and contract.
62
69
 
63
70
  UI testing is part of the handoff. For local UI testing, run `agentkit dev`, open the printed `Chat:` URL, and tell the owner the exact URL. After hosted deploy, run `agentkit chat-ui --deploy`, open the printed `Chat:` URL, and tell the owner it is connected to the deploy.
64
71
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andreprado/agentkit",
3
- "version": "0.1.0-alpha.5",
3
+ "version": "0.1.0-alpha.7",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "repository": {
@@ -35,6 +35,7 @@
35
35
  "dependencies": {
36
36
  "@earendil-works/pi-ai": "^0.75.5",
37
37
  "@earendil-works/pi-coding-agent": "^0.75.5",
38
+ "@libsql/client": "^0.15.15",
38
39
  "esbuild": "^0.28.0",
39
40
  "tsx": "^4.22.3",
40
41
  "typebox": "^1.1.38"
@@ -153,9 +153,13 @@ export async function cloudPost<T>(apiUrl: string, path: string, body: unknown):
153
153
  return payload as T;
154
154
  }
155
155
 
156
- export function cloudFetch(apiUrl: string, path: string, init: RequestInit = {}): Promise<Response> {
156
+ export async function cloudFetch(apiUrl: string, path: string, init: RequestInit = {}): Promise<Response> {
157
157
  const headers = new Headers(init.headers);
158
- const apiToken = process.env.AGENTKIT_CLOUD_API_TOKEN;
158
+ const auth = await readCloudAuth();
159
+ const apiToken =
160
+ auth?.source === "file" && !cloudAuthMatchesApiUrl(auth, apiUrl)
161
+ ? undefined
162
+ : auth?.token;
159
163
 
160
164
  if (apiToken && !headers.has("Authorization")) {
161
165
  headers.set("Authorization", `Bearer ${apiToken}`);
@@ -167,6 +171,10 @@ export function cloudFetch(apiUrl: string, path: string, init: RequestInit = {})
167
171
  });
168
172
  }
169
173
 
174
+ function cloudAuthMatchesApiUrl(auth: CloudAuthConfig, apiUrl: string): boolean {
175
+ return parseCloudApiUrl(auth.apiUrl) === parseCloudApiUrl(apiUrl);
176
+ }
177
+
170
178
  export async function cloudApiRequest(apiUrl: string, path: string, init: RequestInit): Promise<unknown> {
171
179
  const auth = await readCloudAuth();
172
180
  const headers = new Headers(init.headers);
@@ -6,6 +6,7 @@ import { cloudFetch, cloudGet, cloudPost } from "../cloud-client";
6
6
  import { parseCloudApiUrl } from "../flags";
7
7
  import { loadAgentCapsule } from "../../runtime/config";
8
8
  import { readLocalDeployStateIfExists, type LocalDeployState } from "../../runtime/core/deploy-state";
9
+ import type { AgentChannel } from "../../index";
9
10
 
10
11
  type DeployState = LocalDeployState;
11
12
 
@@ -17,6 +18,7 @@ type CloudChannel = {
17
18
  status: string;
18
19
  webhook_url: string;
19
20
  required_secrets: Array<{ name: string; status: string }>;
21
+ buffer?: AgentChannel["buffer"];
20
22
  };
21
23
 
22
24
  type CloudDelivery = {
@@ -70,11 +72,14 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
70
72
 
71
73
  const deployState = await readDeployStateRequired(process.cwd());
72
74
  const provider = channelProviderForCli(type, args.flags.provider);
75
+ const localChannel = await readLocalChannelConfig(process.cwd(), type, provider, name);
73
76
  const payload = {
74
77
  name,
75
78
  type,
76
79
  provider,
77
- secrets: defaultChannelSecretsForCli(type, provider),
80
+ secrets: localChannel?.secrets ?? defaultChannelSecretsForCli(type, provider),
81
+ ...(localChannel?.limits ? { limits: channelLimitsForApi(localChannel.limits) } : {}),
82
+ ...(localChannel?.buffer ? { buffer: localChannel.buffer } : {}),
78
83
  };
79
84
  const response = await cloudPost<{ channel: CloudChannel }>(
80
85
  parseCloudApiUrl(args.flags.api),
@@ -94,7 +99,7 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
94
99
  printChannelStatus(channel);
95
100
  if (channel.type === "telegram") {
96
101
  if (args.flags.apply) {
97
- await applyTelegramWebhook(channel);
102
+ await applyTelegramWebhook(channel, args.flags.api);
98
103
  console.log("Telegram webhook updated.");
99
104
  } else {
100
105
  console.log(`Setup: call Telegram setWebhook with ${channel.webhook_url}`);
@@ -196,7 +201,23 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
196
201
  );
197
202
  }
198
203
 
199
- async function applyTelegramWebhook(channel: CloudChannel): Promise<void> {
204
+ async function applyTelegramWebhook(
205
+ channel: CloudChannel,
206
+ apiFlag: string | boolean | undefined,
207
+ ): Promise<void> {
208
+ try {
209
+ await cloudPost(
210
+ parseCloudApiUrl(apiFlag),
211
+ `/v1/channels/${encodeURIComponent(channel.id)}/setup`,
212
+ {},
213
+ );
214
+ return;
215
+ } catch (error) {
216
+ if (!shouldFallbackToLocalTelegramSetup(error)) {
217
+ throw error;
218
+ }
219
+ }
220
+
200
221
  const botToken = process.env.TELEGRAM_BOT_TOKEN;
201
222
  const secretToken = process.env.TELEGRAM_WEBHOOK_SECRET;
202
223
 
@@ -227,6 +248,16 @@ async function applyTelegramWebhook(channel: CloudChannel): Promise<void> {
227
248
  }
228
249
  }
229
250
 
251
+ function shouldFallbackToLocalTelegramSetup(error: unknown): boolean {
252
+ if (!(error instanceof Error)) {
253
+ return false;
254
+ }
255
+
256
+ return /Unknown AgentKit Cloud route|does not support channel delivery records yet|does not support channel resources yet/.test(
257
+ error.message,
258
+ );
259
+ }
260
+
230
261
  function isTelegramOk(payload: unknown): payload is { ok: true } {
231
262
  return Boolean(payload && typeof payload === "object" && "ok" in payload && payload.ok === true);
232
263
  }
@@ -280,6 +311,15 @@ function printChannelStatus(channel: CloudChannel): void {
280
311
  for (const secret of channel.required_secrets) {
281
312
  console.log(` ${secret.name}: ${secret.status}`);
282
313
  }
314
+ if (channel.buffer) {
315
+ if (channel.buffer.mode === "off") {
316
+ console.log("Buffer: off");
317
+ } else {
318
+ console.log(
319
+ `Buffer: debounce quiet=${channel.buffer.quietWindowMs}ms max_wait=${channel.buffer.maxWaitMs}ms max_messages=${channel.buffer.maxMessages} max_chars=${channel.buffer.maxChars}`,
320
+ );
321
+ }
322
+ }
283
323
  }
284
324
 
285
325
  async function printChannelDeliverySummary(
@@ -352,6 +392,30 @@ function defaultChannelSecretsForCli(type: string, provider: string): string[] {
352
392
  return [];
353
393
  }
354
394
 
395
+ async function readLocalChannelConfig(
396
+ cwd: string,
397
+ type: string,
398
+ provider: string,
399
+ name: string,
400
+ ): Promise<AgentChannel | null> {
401
+ const capsule = await loadAgentCapsule(cwd);
402
+ return (
403
+ (capsule.config.channels ?? []).find(
404
+ (channel) => channel.name === name && channel.type === type && channel.provider === provider,
405
+ ) ?? null
406
+ );
407
+ }
408
+
409
+ function channelLimitsForApi(limits: NonNullable<AgentChannel["limits"]>): {
410
+ messages_per_day?: number;
411
+ usd_per_day?: number;
412
+ } {
413
+ return {
414
+ ...(limits.messagesPerDay ? { messages_per_day: limits.messagesPerDay } : {}),
415
+ ...(limits.usdPerDay ? { usd_per_day: limits.usdPerDay } : {}),
416
+ };
417
+ }
418
+
355
419
  async function channelTestBody(
356
420
  channel: CloudChannel,
357
421
  messageFlag: string | boolean | undefined,
@@ -0,0 +1,136 @@
1
+ import {
2
+ addKnowledgeSourceFromCwd,
3
+ listKnowledgeSourcesFromCwd,
4
+ syncKnowledgeSourcesFromCwd,
5
+ } from "../../runtime/knowledge/ingest";
6
+ import { searchKnowledgeFromCwd } from "../../runtime/knowledge/retrieve";
7
+ import type { ParsedArgs } from "../args";
8
+
9
+ export async function handleKnowledgeCommand(args: ParsedArgs): Promise<void> {
10
+ const [subcommand, ...rest] = args.positional;
11
+
12
+ if (subcommand === "add") {
13
+ const [source] = rest;
14
+
15
+ if (!source) {
16
+ throw new Error("Usage: agentkit knowledge add <path-or-url>");
17
+ }
18
+
19
+ const summary = await addKnowledgeSourceFromCwd(process.cwd(), source);
20
+ printIngestSummary("Knowledge add", summary);
21
+ return;
22
+ }
23
+
24
+ if (subcommand === "sync") {
25
+ const summary = await syncKnowledgeSourcesFromCwd(process.cwd());
26
+ printIngestSummary("Knowledge sync", summary);
27
+ return;
28
+ }
29
+
30
+ if (subcommand === "inspect") {
31
+ const result = await listKnowledgeSourcesFromCwd(process.cwd());
32
+
33
+ console.log("Knowledge sources");
34
+ console.log(`Database: ${result.databasePath}`);
35
+
36
+ if (result.sources.length === 0) {
37
+ console.log("No knowledge sources indexed.");
38
+ return;
39
+ }
40
+
41
+ console.log("");
42
+ console.log("source\tstatus\tchunks\tupdated_at\ttitle");
43
+
44
+ for (const source of result.sources) {
45
+ console.log(
46
+ [
47
+ source.source,
48
+ source.status,
49
+ String(source.chunkCount),
50
+ source.updatedAt,
51
+ source.title ?? "",
52
+ ].join("\t"),
53
+ );
54
+ }
55
+
56
+ return;
57
+ }
58
+
59
+ if (subcommand === "search") {
60
+ const query = rest.join(" ").trim();
61
+
62
+ if (!query) {
63
+ throw new Error("Usage: agentkit knowledge search <query> [--top-k <number>]");
64
+ }
65
+
66
+ const topK = parseTopK(args.flags["top-k"] ?? args.flags.topK);
67
+ const result = await searchKnowledgeFromCwd(process.cwd(), query, { topK });
68
+
69
+ console.log("Knowledge search");
70
+ console.log(`Database: ${result.databasePath}`);
71
+ console.log(`Query: ${query}`);
72
+
73
+ if (result.results.length === 0) {
74
+ console.log("No matching knowledge chunks found.");
75
+ return;
76
+ }
77
+
78
+ console.log("");
79
+
80
+ for (const [index, item] of result.results.entries()) {
81
+ const source = [
82
+ item.source.path,
83
+ item.source.section ? `section: ${item.source.section}` : null,
84
+ item.source.locator,
85
+ ].filter(Boolean).join(" | ");
86
+
87
+ console.log(`${index + 1}. ${source}`);
88
+ console.log(` score: ${item.score}`);
89
+ console.log(` ${oneLine(item.content)}`);
90
+ }
91
+
92
+ return;
93
+ }
94
+
95
+ throw new Error(
96
+ "Usage: agentkit knowledge add <path-or-url> | sync | inspect | search <query> [--top-k <number>]",
97
+ );
98
+ }
99
+
100
+ function printIngestSummary(label: string, summary: Awaited<ReturnType<typeof addKnowledgeSourceFromCwd>>): void {
101
+ console.log(label);
102
+ console.log(`Database: ${summary.databasePath}`);
103
+ console.log(`${summary.indexed} indexed, ${summary.skipped} skipped, ${summary.failed} failed, ${summary.chunksCreated} chunks`);
104
+
105
+ if (summary.results.length > 0) {
106
+ console.log("");
107
+ console.log("source\tstatus\tchunks\tmessage");
108
+
109
+ for (const result of summary.results) {
110
+ console.log([result.source, result.status, String(result.chunksCreated), result.message].join("\t"));
111
+ }
112
+ }
113
+
114
+ if (summary.failed > 0) {
115
+ process.exitCode = 1;
116
+ }
117
+ }
118
+
119
+ function parseTopK(value: unknown): number | undefined {
120
+ if (value === undefined) {
121
+ return undefined;
122
+ }
123
+
124
+ const topK = Number(value);
125
+
126
+ if (!Number.isInteger(topK) || topK <= 0 || topK > 50) {
127
+ throw new Error("knowledge search --top-k must be a positive integer no larger than 50.");
128
+ }
129
+
130
+ return topK;
131
+ }
132
+
133
+ function oneLine(value: string): string {
134
+ const compact = value.replace(/\s+/g, " ").trim();
135
+ return compact.length <= 220 ? compact : `${compact.slice(0, 217)}...`;
136
+ }
@@ -335,6 +335,13 @@ function renderDeployChatUi(input: { deployUrl: string }): string {
335
335
  messages.scrollTop = messages.scrollHeight;
336
336
  }
337
337
 
338
+ textarea.addEventListener("keydown", (event) => {
339
+ if (event.key !== "Enter" || event.shiftKey || event.isComposing) return;
340
+ event.preventDefault();
341
+ if (send.disabled) return;
342
+ form.requestSubmit();
343
+ });
344
+
338
345
  form.addEventListener("submit", async (event) => {
339
346
  event.preventDefault();
340
347
  const message = textarea.value.trim();
@@ -179,6 +179,25 @@ export async function checkDeployReadiness(options: {
179
179
  });
180
180
  }
181
181
 
182
+ if (context.knowledge.enabled) {
183
+ if (context.knowledge.databaseDriver !== "turso") {
184
+ checks.push({
185
+ status: "fail",
186
+ title: "Knowledge storage",
187
+ message: "Hosted Knowledge requires a Turso database so indexed chunks are available to the Cloudflare runtime.",
188
+ action: 'set storage.database.driver: "turso" in agentkit.config.ts before deploying Knowledge',
189
+ });
190
+ } else {
191
+ checks.push({
192
+ status: "pass",
193
+ title: "Knowledge storage",
194
+ message: `Knowledge is configured with ${context.knowledge.sourceCount} source${
195
+ context.knowledge.sourceCount === 1 ? "" : "s"
196
+ } and Turso storage.`,
197
+ });
198
+ }
199
+ }
200
+
182
201
  if (context.userManagedSecrets.length === 0) {
183
202
  checks.push({
184
203
  status: "pass",