talon-agent 1.10.1 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/README.md +69 -40
  2. package/package.json +24 -3
  3. package/prompts/base.md +12 -8
  4. package/prompts/discord.md +84 -0
  5. package/prompts/identity.md +6 -6
  6. package/src/backend/claude-sdk/factory.ts +79 -0
  7. package/src/backend/claude-sdk/handler.ts +106 -129
  8. package/src/backend/claude-sdk/index.ts +7 -1
  9. package/src/backend/claude-sdk/model-provider.ts +21 -5
  10. package/src/backend/claude-sdk/one-shot.ts +277 -0
  11. package/src/backend/claude-sdk/options.ts +113 -15
  12. package/src/backend/claude-sdk/warm.ts +4 -3
  13. package/src/backend/codex/auth.ts +165 -0
  14. package/src/backend/codex/constants.ts +73 -0
  15. package/src/backend/codex/factory.ts +67 -0
  16. package/src/backend/codex/handler.ts +595 -0
  17. package/src/backend/codex/index.ts +33 -0
  18. package/src/backend/codex/init.ts +166 -0
  19. package/src/backend/codex/mcp-config.ts +137 -0
  20. package/src/backend/codex/models.ts +241 -0
  21. package/src/backend/codex/one-shot.ts +239 -0
  22. package/src/backend/codex/state.ts +41 -0
  23. package/src/backend/kilo/events.ts +60 -0
  24. package/src/backend/kilo/factory.ts +97 -0
  25. package/src/backend/kilo/handler.ts +707 -0
  26. package/src/backend/kilo/index.ts +87 -0
  27. package/src/backend/kilo/model-provider.ts +178 -0
  28. package/src/backend/kilo/models.ts +762 -0
  29. package/src/backend/kilo/one-shot.ts +162 -0
  30. package/src/backend/kilo/server.ts +332 -0
  31. package/src/backend/kilo/sessions.ts +101 -0
  32. package/src/backend/openai-agents/builtins.ts +338 -0
  33. package/src/backend/openai-agents/constants.ts +54 -0
  34. package/src/backend/openai-agents/factory.ts +63 -0
  35. package/src/backend/openai-agents/handler.ts +539 -0
  36. package/src/backend/openai-agents/index.ts +30 -0
  37. package/src/backend/openai-agents/init.ts +273 -0
  38. package/src/backend/openai-agents/mcp.ts +139 -0
  39. package/src/backend/openai-agents/models.ts +388 -0
  40. package/src/backend/openai-agents/state.ts +61 -0
  41. package/src/backend/opencode/factory.ts +86 -0
  42. package/src/backend/opencode/handler.ts +582 -117
  43. package/src/backend/opencode/index.ts +3 -0
  44. package/src/backend/opencode/model-provider.ts +0 -1
  45. package/src/backend/opencode/models.ts +1 -2
  46. package/src/backend/opencode/one-shot.ts +157 -0
  47. package/src/backend/opencode/server.ts +119 -278
  48. package/src/backend/opencode/sessions.ts +67 -460
  49. package/src/backend/registry.ts +117 -0
  50. package/src/backend/remote-server/client.ts +63 -0
  51. package/src/backend/remote-server/events.ts +410 -0
  52. package/src/backend/remote-server/index.ts +65 -0
  53. package/src/backend/remote-server/lifecycle.ts +197 -0
  54. package/src/backend/remote-server/mcp.ts +312 -0
  55. package/src/backend/remote-server/messages.ts +52 -0
  56. package/src/backend/remote-server/providers.ts +118 -0
  57. package/src/backend/remote-server/session-helpers.ts +543 -0
  58. package/src/backend/remote-server/sessions.ts +108 -0
  59. package/src/backend/remote-server/sse-stream.ts +80 -0
  60. package/src/backend/remote-server/state.ts +93 -0
  61. package/src/backend/shared/delivered-text.ts +103 -0
  62. package/src/backend/shared/delivery.ts +184 -0
  63. package/src/backend/shared/flow-violation.ts +92 -0
  64. package/src/backend/shared/index.ts +75 -0
  65. package/src/backend/shared/model-retry.ts +72 -0
  66. package/src/backend/shared/prompt-format.ts +71 -0
  67. package/src/backend/shared/session-name.ts +41 -0
  68. package/src/backend/shared/sleep.ts +26 -0
  69. package/src/backend/shared/stream-state.ts +247 -0
  70. package/src/backend/shared/system-prompt.ts +71 -0
  71. package/src/backend/shared/usage.ts +51 -0
  72. package/src/bootstrap.ts +61 -84
  73. package/src/cli.ts +313 -29
  74. package/src/core/dream.ts +53 -108
  75. package/src/core/errors.ts +7 -4
  76. package/src/core/gateway-actions.ts +219 -0
  77. package/src/core/gateway.ts +38 -10
  78. package/src/core/heartbeat.ts +184 -116
  79. package/src/core/tools/bridge.ts +21 -2
  80. package/src/core/tools/chat.ts +4 -4
  81. package/src/core/tools/history.ts +11 -9
  82. package/src/core/tools/index.ts +70 -12
  83. package/src/core/tools/mcp-server.ts +2 -1
  84. package/src/core/tools/media.ts +1 -1
  85. package/src/core/tools/members.ts +6 -6
  86. package/src/core/tools/messaging.ts +85 -21
  87. package/src/core/tools/scheduling.ts +1 -1
  88. package/src/core/tools/schemas.ts +62 -0
  89. package/src/core/tools/triggers.ts +147 -0
  90. package/src/core/tools/types.ts +7 -1
  91. package/src/core/triggers.ts +640 -0
  92. package/src/core/types.ts +135 -10
  93. package/src/frontend/discord/actions.ts +729 -0
  94. package/src/frontend/discord/admin.ts +208 -0
  95. package/src/frontend/discord/callbacks.ts +589 -0
  96. package/src/frontend/discord/commands.ts +1036 -0
  97. package/src/frontend/discord/errors.ts +145 -0
  98. package/src/frontend/discord/formatting.ts +101 -0
  99. package/src/frontend/discord/handlers.ts +798 -0
  100. package/src/frontend/discord/helpers.ts +188 -0
  101. package/src/frontend/discord/index.ts +360 -0
  102. package/src/frontend/discord/middleware.ts +83 -0
  103. package/src/frontend/telegram/callbacks.ts +215 -81
  104. package/src/frontend/telegram/commands.ts +34 -53
  105. package/src/frontend/telegram/formatting.ts +5 -2
  106. package/src/frontend/telegram/handlers.ts +123 -7
  107. package/src/frontend/telegram/helpers.ts +224 -2
  108. package/src/frontend/telegram/model-callbacks.ts +98 -0
  109. package/src/index.ts +9 -0
  110. package/src/storage/chat-settings.ts +19 -0
  111. package/src/storage/cron-store.ts +1 -0
  112. package/src/storage/history.ts +2 -5
  113. package/src/storage/trigger-store.ts +373 -0
  114. package/src/util/config.ts +102 -3
  115. package/src/util/log.ts +2 -0
  116. package/src/util/mcp-launcher.mjs +70 -2
  117. package/src/util/mcp-launcher.ts +14 -2
  118. package/src/util/paths.ts +4 -0
  119. package/src/util/respawn.ts +74 -0
  120. package/src/__tests__/chat-id.test.ts +0 -91
  121. package/src/__tests__/chat-settings.test.ts +0 -471
  122. package/src/__tests__/claude-sdk-models.test.ts +0 -146
  123. package/src/__tests__/claude-sdk-options.test.ts +0 -205
  124. package/src/__tests__/cleanup-registry.test.ts +0 -58
  125. package/src/__tests__/compose-tools.test.ts +0 -216
  126. package/src/__tests__/config.test.ts +0 -716
  127. package/src/__tests__/cron-store-extended.test.ts +0 -661
  128. package/src/__tests__/cron-store.test.ts +0 -574
  129. package/src/__tests__/daily-log.test.ts +0 -357
  130. package/src/__tests__/disallowed-tools.test.ts +0 -64
  131. package/src/__tests__/dispatcher.test.ts +0 -784
  132. package/src/__tests__/dream.test.ts +0 -1145
  133. package/src/__tests__/end-turn.test.ts +0 -307
  134. package/src/__tests__/errors-extended.test.ts +0 -428
  135. package/src/__tests__/errors.test.ts +0 -332
  136. package/src/__tests__/fixtures/test-mcp-server.ts +0 -37
  137. package/src/__tests__/fuzz.test.ts +0 -375
  138. package/src/__tests__/gateway-actions.test.ts +0 -1772
  139. package/src/__tests__/gateway-context.test.ts +0 -102
  140. package/src/__tests__/gateway-http.test.ts +0 -436
  141. package/src/__tests__/gateway-retry.test.ts +0 -355
  142. package/src/__tests__/gateway-withRetry-extended.test.ts +0 -343
  143. package/src/__tests__/graph.test.ts +0 -830
  144. package/src/__tests__/handlers-stream.test.ts +0 -203
  145. package/src/__tests__/handlers.test.ts +0 -2972
  146. package/src/__tests__/heartbeat.test.ts +0 -388
  147. package/src/__tests__/history-extended.test.ts +0 -775
  148. package/src/__tests__/history-persistence.test.ts +0 -227
  149. package/src/__tests__/history.test.ts +0 -693
  150. package/src/__tests__/integration/sdk-stub.test.ts +0 -208
  151. package/src/__tests__/integration/stub-claude/build-sea.mjs +0 -114
  152. package/src/__tests__/integration/stub-claude/fake-claude.mjs +0 -352
  153. package/src/__tests__/integration/stub-claude/helpers.ts +0 -263
  154. package/src/__tests__/integration/stub-claude/protocol.ts +0 -108
  155. package/src/__tests__/integration/stub-claude/sea-config.json +0 -7
  156. package/src/__tests__/integration/talon-bootstrap.ts +0 -206
  157. package/src/__tests__/integration/talon-functional.test.ts +0 -190
  158. package/src/__tests__/integration.test.ts +0 -224
  159. package/src/__tests__/log-init.test.ts +0 -129
  160. package/src/__tests__/log.test.ts +0 -129
  161. package/src/__tests__/mcp-launcher-functional.test.ts +0 -334
  162. package/src/__tests__/mcp-launcher.test.ts +0 -139
  163. package/src/__tests__/mcp-lifecycle.test.ts +0 -165
  164. package/src/__tests__/media-index.test.ts +0 -559
  165. package/src/__tests__/mempalace-plugin.test.ts +0 -350
  166. package/src/__tests__/metrics.test.ts +0 -76
  167. package/src/__tests__/opencode-models.test.ts +0 -117
  168. package/src/__tests__/opencode-summary.test.ts +0 -105
  169. package/src/__tests__/opencode-ui.test.ts +0 -94
  170. package/src/__tests__/package.functional.test.ts +0 -178
  171. package/src/__tests__/plugin.test.ts +0 -962
  172. package/src/__tests__/reload-plugins.test.ts +0 -342
  173. package/src/__tests__/sessions.test.ts +0 -877
  174. package/src/__tests__/storage-save-errors.test.ts +0 -342
  175. package/src/__tests__/teams-frontend.test.ts +0 -762
  176. package/src/__tests__/telegram-formatting.test.ts +0 -86
  177. package/src/__tests__/telegram-helpers.test.ts +0 -151
  178. package/src/__tests__/telegram.test.ts +0 -176
  179. package/src/__tests__/terminal-commands.test.ts +0 -666
  180. package/src/__tests__/terminal-frontend.test.ts +0 -141
  181. package/src/__tests__/terminal-renderer.test.ts +0 -501
  182. package/src/__tests__/time.test.ts +0 -107
  183. package/src/__tests__/tool-functional.test.ts +0 -615
  184. package/src/__tests__/tool-id-coercion.test.ts +0 -136
  185. package/src/__tests__/watchdog.test.ts +0 -285
  186. package/src/__tests__/workspace-migrate.test.ts +0 -256
  187. package/src/__tests__/workspace.test.ts +0 -284
package/README.md CHANGED
@@ -2,25 +2,26 @@
2
2
 
3
3
  [![Node.js](https://img.shields.io/badge/node-%3E%3D22-339933?logo=nodedotjs&logoColor=white)](https://nodejs.org)
4
4
  [![TypeScript](https://img.shields.io/badge/TypeScript-6.0-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
5
- [![Claude](https://img.shields.io/badge/Claude_Agent_SDK-Anthropic-D97706)](https://github.com/anthropics/claude-agent-sdk-typescript)
5
+ [![Backends](https://img.shields.io/badge/backends-Claude_%7C_Kilo_%7C_OpenCode_%7C_Codex-D97706)](#backends)
6
6
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
7
  [![CI](https://github.com/dylanneve1/talon/actions/workflows/ci.yml/badge.svg)](https://github.com/dylanneve1/talon/actions/workflows/ci.yml)
8
8
 
9
- Multi-platform agentic AI harness powered by Claude. Runs on **Telegram**, **Teams**, and **Terminal** with full tool access through MCP.
9
+ Multi-platform agentic AI harness. Runs on **Telegram**, **Discord**, **Microsoft Teams**, and the **Terminal**, with a pluggable backend (**Claude Agent SDK**, **Kilo**, **OpenCode**, or **Codex**) and full tool access through MCP.
10
10
 
11
11
  ---
12
12
 
13
13
  ## Features
14
14
 
15
- | | |
16
- | --------------------- | ------------------------------------------------------------------------------------------------------- |
17
- | **Multi-frontend** | Telegram (Grammy + GramJS userbot), Microsoft Teams (Bot Framework), Terminal with live tool visibility |
18
- | **Claude Agent SDK** | Streaming responses, extended thinking, adaptive effort, 1M token context, dynamic model discovery |
19
- | **MCP tools** | Messaging, media, history, search, web fetch, cron jobs, stickers, file system, admin controls |
20
- | **Plugins** | Hot-reloadable plugin system. Built-in: GitHub, MemPalace, Playwright, Brave Search |
21
- | **Background agents** | Heartbeat (periodic maintenance) and Dream (memory consolidation + diary) |
22
- | **Per-chat settings** | Model, effort level, and pulse toggle per conversation via inline keyboard |
23
- | **Model registry** | Models discovered from the SDK at startup --- new models appear in all pickers automatically |
15
+ | | |
16
+ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
17
+ | **Multi-frontend** | Telegram (Grammy + GramJS userbot), Discord (discord.js), Microsoft Teams (Bot Framework), Terminal with live tool visibility |
18
+ | **Pluggable backend** | Claude Agent SDK, Kilo, OpenCode, Codex selectable per-process via `backend` config. Streaming, model fallback, context-overflow recovery. |
19
+ | **MCP tools** | Messaging, media, history, search, web fetch, cron jobs, triggers, stickers, file system, admin controls |
20
+ | **Plugins** | Hot-reloadable plugin system. Built-in: GitHub, MemPalace, Playwright, Brave Search |
21
+ | **Background agents** | Heartbeat (periodic maintenance) and Dream (memory consolidation + diary) — backend-agnostic |
22
+ | **Triggers** | Self-authored watcher scripts (bash/python/node) that wake the bot when conditions are met |
23
+ | **Per-chat settings** | Model, effort level, and pulse toggle per conversation via inline keyboard |
24
+ | **Model registry** | Models discovered from the active backend at startup — new models appear in all pickers automatically |
24
25
 
25
26
  ---
26
27
 
@@ -41,7 +42,11 @@ npx talon chat # terminal chat mode
41
42
  **Prerequisites:**
42
43
 
43
44
  - [Node.js 22+](https://nodejs.org/)
44
- - [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated (`claude` CLI on PATH)
45
+ - Backend-specific:
46
+ - `claude` backend: [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated (`claude` CLI on PATH).
47
+ - `kilo` backend: nothing extra — `@kilocode/sdk` spawns a local server. Free models are accessible without auth; routed models use Kilo's own credentials.
48
+ - `opencode` backend: nothing extra — `@opencode-ai/sdk` spawns a local server.
49
+ - `codex` backend: install the `codex` CLI (`npm i -g @openai/codex`) and authenticate with `codex login` (ChatGPT auth or `OPENAI_API_KEY`).
45
50
  - Talon runs from a normal source or package install; standalone compiled binaries are not supported.
46
51
 
47
52
  ---
@@ -52,7 +57,7 @@ npx talon chat # terminal chat mode
52
57
  index.ts Composition root
53
58
  |
54
59
  +-- core/ Platform-agnostic engine
55
- | +-- models.ts Model registry (dynamic SDK discovery)
60
+ | +-- models.ts Model registry (dynamic backend discovery)
56
61
  | +-- gateway.ts HTTP bridge for MCP tool calls
57
62
  | +-- dispatcher.ts Per-chat serial, cross-chat parallel execution
58
63
  | +-- plugin.ts Plugin loader, registry, hot-reload
@@ -60,15 +65,23 @@ index.ts Composition root
60
65
  | +-- dream.ts Memory consolidation agent
61
66
  | +-- pulse.ts Conversation-aware group engagement
62
67
  | +-- cron.ts Persistent scheduled jobs
63
- | +-- tools/ MCP tool definitions (13 files)
68
+ | +-- triggers.ts Self-authored watcher scripts
69
+ | +-- tools/ MCP tool definitions
64
70
  |
65
71
  +-- backend/
66
- | +-- claude-sdk/ Claude Agent SDK (modular: handler, stream,
67
- | | options, state, warm, models, constants)
68
- | +-- opencode/ OpenCode SDK alternative backend
72
+ | +-- registry.ts Bootstrap-decoupled backend lookup
73
+ | +-- shared/ Cross-backend helpers (stream state, flow violation,
74
+ | | prompt format, model retry, system prompt, usage)
75
+ | +-- remote-server/ Shared infrastructure for agent-server backends
76
+ | | (MCP registration, sessions, providers, lifecycle)
77
+ | +-- claude-sdk/ Claude Agent SDK (in-process MCP, hooks)
78
+ | +-- kilo/ Kilo HTTP server backend (streaming via SSE)
79
+ | +-- opencode/ OpenCode HTTP server backend
80
+ | +-- codex/ Codex CLI backend (`@openai/codex-sdk`)
69
81
  |
70
82
  +-- frontend/
71
- | +-- telegram/ Grammy bot + GramJS userbot (10 files)
83
+ | +-- telegram/ Grammy bot + GramJS userbot
84
+ | +-- discord/ discord.js v14
72
85
  | +-- teams/ Bot Framework + Graph API
73
86
  | +-- terminal/ Readline CLI with tool call visibility
74
87
  |
@@ -77,7 +90,22 @@ index.ts Composition root
77
90
  +-- util/ Config, logging, workspace, paths, time
78
91
  ```
79
92
 
80
- **Dependency rule:** `core/` imports nothing from `frontend/` or `backend/`. Frontends and backends depend on core types, never on each other.
93
+ **Dependency rule:** `core/` imports nothing from `frontend/` or `backend/`. Frontends and backends depend on core types, never on each other. All four backends (Claude SDK, Kilo, OpenCode, Codex) implement the same `QueryBackend` interface in `core/types.ts`. Kilo and OpenCode additionally share the `remote-server/` infrastructure because they wrap forks of the same upstream HTTP agent server.
94
+
95
+ ---
96
+
97
+ ## Backends
98
+
99
+ Select via the `backend` field in `~/.talon/config.json`. All backends implement the same `QueryBackend` interface — heartbeat, dream, and chat handlers are backend-agnostic.
100
+
101
+ | Backend | `backend` value | Transport | Notes |
102
+ | ---------- | --------------- | ------------------------------------------------------ | ----------------------------------------------------------------------- |
103
+ | Claude SDK | `"claude"` | In-process via `@anthropic-ai/claude-agent-sdk` | Requires the `claude` CLI on `PATH`. Hook-based turn termination. |
104
+ | Kilo | `"kilo"` | Local HTTP server via `@kilocode/sdk` | SSE-streamed turns. Routes to many model providers via Kilo's auth. |
105
+ | OpenCode | `"opencode"` | Local HTTP server via `@opencode-ai/sdk` | SSE-streamed turns; same MCP and session shape as Kilo (upstream fork). |
106
+ | Codex | `"codex"` | Per-turn subprocess via `@openai/codex-sdk` | Requires the `codex` CLI from `@openai/codex` and an OpenAI API key (or ChatGPT auth). MCP servers configured via TOML overrides at thread start. |
107
+
108
+ The Kilo and OpenCode backends share infrastructure (`backend/remote-server/`) since the upstream HTTP API is the same; each backend supplies its own SDK client, port, and delivery suffix. Codex is its own integration on top of the Codex CLI's JSONL event stream.
81
109
 
82
110
  ---
83
111
 
@@ -216,25 +244,25 @@ talon doctor Validate environment and dependencies
216
244
 
217
245
  Config file: `~/.talon/config.json`
218
246
 
219
- | Field | Default | Description |
220
- | -------------------------- | ------------ | ------------------------------------------------------------------- |
221
- | `frontend` | `"telegram"` | `"telegram"`, `"terminal"`, `"teams"`, or an array |
222
- | `backend` | `"claude"` | `"claude"` or `"opencode"` |
223
- | `botToken` | --- | Telegram bot token |
224
- | `model` | `"default"` | Default Claude model. Legacy `claude-*` aliases are still accepted. |
225
- | `concurrency` | `1` | Max concurrent AI queries (1--20) |
226
- | `pulse` | `true` | Periodic group engagement |
227
- | `heartbeat` | `false` | Background maintenance agent |
228
- | `heartbeatIntervalMinutes` | `60` | Heartbeat interval |
229
- | `braveApiKey` | --- | Brave Search API key |
230
- | `timezone` | --- | IANA timezone (e.g. `"Europe/London"`) |
231
- | `plugins` | `[]` | External plugin packages |
232
- | `adminUserId` | --- | Telegram user ID for `/admin` commands |
233
- | `allowedUsers` | --- | Whitelist of Telegram user IDs |
234
- | `apiId` / `apiHash` | --- | Telegram API credentials for full message history |
235
- | `github` | --- | GitHub plugin config (see above) |
236
- | `mempalace` | --- | MemPalace plugin config (see above) |
237
- | `playwright` | --- | Playwright plugin config (see above) |
247
+ | Field | Default | Description |
248
+ | -------------------------- | ------------ | --------------------------------------------------------------- |
249
+ | `frontend` | `"telegram"` | `"telegram"`, `"discord"`, `"teams"`, `"terminal"`, or an array |
250
+ | `backend` | `"claude"` | `"claude"`, `"kilo"`, `"opencode"`, or `"codex"` |
251
+ | `botToken` | --- | Telegram bot token |
252
+ | `model` | `"default"` | Default model. Interpretation depends on the active backend. |
253
+ | `concurrency` | `1` | Max concurrent AI queries (1--20) |
254
+ | `pulse` | `true` | Periodic group engagement |
255
+ | `heartbeat` | `false` | Background maintenance agent |
256
+ | `heartbeatIntervalMinutes` | `60` | Heartbeat interval |
257
+ | `braveApiKey` | --- | Brave Search API key |
258
+ | `timezone` | --- | IANA timezone (e.g. `"Europe/London"`) |
259
+ | `plugins` | `[]` | External plugin packages |
260
+ | `adminUserId` | --- | Telegram user ID for `/admin` commands |
261
+ | `allowedUsers` | --- | Whitelist of Telegram user IDs |
262
+ | `apiId` / `apiHash` | --- | Telegram API credentials for full message history |
263
+ | `github` | --- | GitHub plugin config (see above) |
264
+ | `mempalace` | --- | MemPalace plugin config (see above) |
265
+ | `playwright` | --- | Playwright plugin config (see above) |
238
266
 
239
267
  ---
240
268
 
@@ -258,7 +286,7 @@ Commands: `/model`, `/effort`, `/reset`, `/status`, `/help`
258
286
  docker compose up -d
259
287
  ```
260
288
 
261
- **Systemd:** `talon.service` included in the repository.
289
+ **Systemd:** unit file at `packaging/systemd/talon.service` copy to `/etc/systemd/system/`, set `User=` and `WorkingDirectory=`, then `systemctl enable --now talon`.
262
290
 
263
291
  **Health endpoint:** `GET http://localhost:19876/health` returns JSON with uptime, memory, queue depth, active sessions, and last activity timestamp.
264
292
 
@@ -272,10 +300,11 @@ docker compose up -d
272
300
 
273
301
  ```bash
274
302
  npm run dev # watch mode
275
- npm test # 1300+ tests
303
+ npm test # 2300+ tests across unit / SDK-stub / MCP-functional / integration tiers
276
304
  npm run test:coverage # with coverage report
277
305
  npm run typecheck # tsc --noEmit
278
306
  npm run lint # oxlint
307
+ npm run format # prettier --write
279
308
  ```
280
309
 
281
310
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "talon-agent",
3
- "version": "1.10.1",
3
+ "version": "1.12.0",
4
4
  "description": "Multi-frontend AI agent with full tool access, streaming, cron jobs, and plugin system",
5
5
  "author": "Dylan Neve",
6
6
  "license": "MIT",
@@ -30,7 +30,16 @@
30
30
  },
31
31
  "files": [
32
32
  "bin/",
33
- "src/",
33
+ "src/backend/",
34
+ "src/core/",
35
+ "src/frontend/",
36
+ "src/plugins/",
37
+ "src/storage/",
38
+ "src/util/",
39
+ "src/bootstrap.ts",
40
+ "src/cli.ts",
41
+ "src/index.ts",
42
+ "src/login.ts",
34
43
  "prompts/",
35
44
  "README.md",
36
45
  "tsconfig.json"
@@ -43,6 +52,13 @@
43
52
  "test": "vitest run",
44
53
  "test:ci": "vitest run --reporter=verbose --reporter=json --outputFile=test-results.json",
45
54
  "test:functional": "vitest run --reporter=verbose --reporter=json --outputFile=functional-results.json src/__tests__/package.functional.test.ts src/__tests__/tool-functional.test.ts src/__tests__/mcp-launcher.test.ts src/__tests__/mcp-launcher-functional.test.ts src/__tests__/integration/sdk-stub.test.ts src/__tests__/integration/talon-functional.test.ts",
55
+ "test:integration": "vitest run --reporter=verbose --reporter=json --outputFile=integration-results.json src/__tests__/integration/talon-mcp-functional.test.ts",
56
+ "test:integration:all": "vitest run --reporter=verbose src/__tests__/integration/",
57
+ "test:claude:backend": "vitest run --reporter=verbose --reporter=json --outputFile=claude-backend-results.json src/__tests__/integration/claude-live-discovery.test.ts",
58
+ "test:kilo:backend": "vitest run --reporter=verbose --reporter=json --outputFile=kilo-backend-results.json src/__tests__/integration/kilo-live-discovery.test.ts",
59
+ "test:opencode:backend": "vitest run --reporter=verbose --reporter=json --outputFile=opencode-backend-results.json src/__tests__/integration/opencode-live-discovery.test.ts",
60
+ "test:codex:backend": "vitest run --reporter=verbose --reporter=json --outputFile=codex-backend-results.json src/__tests__/integration/codex-live-discovery.test.ts",
61
+ "tarball:check": "node .github/scripts/tarball-check.mjs",
46
62
  "build:stub-sea": "node src/__tests__/integration/stub-claude/build-sea.mjs",
47
63
  "test:watch": "vitest",
48
64
  "test:coverage": "vitest run --coverage",
@@ -60,12 +76,16 @@
60
76
  "@clack/prompts": "^1.2.0",
61
77
  "@grammyjs/auto-retry": "^2.0.2",
62
78
  "@grammyjs/transformer-throttler": "^1.2.1",
79
+ "@kilocode/sdk": "^7.2.22",
63
80
  "@modelcontextprotocol/sdk": "^1.29.0",
81
+ "@openai/agents": "^0.11.4",
82
+ "@openai/codex-sdk": "^0.130.0",
64
83
  "@opencode-ai/sdk": "^1.4.0",
65
84
  "@playwright/mcp": "^0.0.75",
66
85
  "big-integer": "^1.6.52",
67
86
  "cheerio": "^1.2.0",
68
87
  "croner": "^10.0.1",
88
+ "discord.js": "^14.16.3",
69
89
  "grammy": "^1.42.0",
70
90
  "marked": "^18.0.0",
71
91
  "p-retry": "^8.0.0",
@@ -91,6 +111,7 @@
91
111
  },
92
112
  "overrides": {
93
113
  "@anthropic-ai/sdk": "^0.95.0",
94
- "ip-address": "^10.1.1"
114
+ "ip-address": "^10.1.1",
115
+ "fast-uri": "^3.1.2"
95
116
  }
96
117
  }
package/prompts/base.md CHANGED
@@ -1,13 +1,17 @@
1
1
  Be concise and direct. No filler. Answer directly.
2
2
 
3
- ## Core tools
3
+ ## Tools
4
4
 
5
- - File system: Read, Write, Edit, Bash, Glob, Grep
6
- - Web: web_search(query), fetch_url(url)
7
- - Sub-agents: Agent (for complex multi-step tasks)
8
- - Any plugin tools registered are also available
5
+ Only the tools the runtime registers for this turn are usable — the
6
+ list is attached to this prompt by the backend. Do not invent or
7
+ guess tool names from prior Talon configurations, other agents, or
8
+ typical AI tooling vocabularies; if a name isn't in the registered
9
+ list, calling it will fail the turn.
9
10
 
10
- ## File handling
11
+ When a tool that does what you need isn't present, fall back to
12
+ plain conversation. Don't pretend to perform actions (reading a
13
+ file, running a command, browsing the web) you have no tool for —
14
+ say so plainly instead, and ask the user if you're unsure.
11
15
 
12
- - You have full file system access via Claude Code tools (Read, Write, Edit, Bash).
13
- - You CAN create files. Write them to the `~/.talon/workspace/` directory.
16
+ Workspace artifacts, when persistable for this backend, live under
17
+ `~/.talon/workspace/`.
@@ -0,0 +1,84 @@
1
+ ## Discord Mode
2
+
3
+ In servers (guilds), you'll see messages prefixed with [Name]: — use their name naturally. In DMs, just one user.
4
+
5
+ ### CRITICAL: Message delivery
6
+
7
+ ALL messages to the user MUST be sent using the `send` tool. Your plain text output is **private** — the user never sees it, only you. Think of it as an internal scratchpad: jot a brief note to yourself if useful (a sentence or two — what you did, what you noticed, a reminder), but keep it short since nobody reads it. The only way to reach the user is the `send` tool.
8
+
9
+ ### The `send` tool
10
+
11
+ One tool for everything. Set `type` to choose what to send:
12
+
13
+ - `send(type="text", text="Hello!")` — send a message
14
+ - `send(type="text", text="Hey", reply_to="123456789012345678")` — reply to a specific message (Discord IDs are strings)
15
+ - `send(type="text", text="Pick", buttons=[[{"text":"A","callback_data":"a","style":"primary"}]])` — with buttons
16
+ - `send(type="text", text="Reminder", delay_seconds=60)` — schedule for later
17
+ - `send(type="photo", file_path="img.jpg", caption="Look!")` — send an image
18
+ - `send(type="file", file_path="report.pdf")` — send a document
19
+ - `send(type="video", file_path="clip.mp4")` — send a video
20
+ - `send(type="voice", file_path="audio.ogg")` — send an audio attachment
21
+ - `send(type="poll", question="Best?", options=["A","B","C"])` — create a poll
22
+ - `send(type="dice")` — roll dice
23
+ - `send(type="location", latitude=37.77, longitude=-122.42)` — share a Google Maps location link
24
+ - `send(type="contact", phone_number="+1234", first_name="John")` — share a contact card
25
+
26
+ ALL types support `reply_to` to reply to a specific message.
27
+
28
+ ### Discord-specific
29
+
30
+ - **IDs are strings** — Discord uses snowflakes (17–20 digits). Treat them as opaque strings, not numbers.
31
+ - **Buttons:** the `style` field accepts `"primary"`, `"secondary"`, `"success"`, `"danger"`. URL buttons use `url` instead of `callback_data`.
32
+ - **Markdown is native:** `**bold**`, `*italic*`, `` `code` ``, ` ```fenced``` `, `# headings`, `> quotes`, `||spoilers||`, `[links](url)`. Discord renders these without translation.
33
+ - **Mentions:** the bot is configured to suppress all mentions (`@everyone`, `@here`, role/user pings) so you can't accidentally ping anyone. Don't worry about escaping.
34
+ - **Message limit:** 2000 chars per message. Long messages are auto-chunked at paragraph breaks.
35
+
36
+ ### Other tools
37
+
38
+ - `react(message_id, emoji)` — react to a message (unicode emoji only on Discord; custom emojis need `<:name:id>` format)
39
+ - `edit_message(message_id, text)` — edit a sent message (max 2000 chars)
40
+ - `delete_message(message_id)` — delete a message
41
+ - `pin_message(message_id)` / `unpin_message()` — pin/unpin
42
+ - `read_chat_history(limit)` — read past messages from this channel
43
+ - `search_chat_history(query)` — search recent messages by keyword
44
+ - `list_chat_members()` — list members in this server (guild only)
45
+ - `get_member_info(user_id)` — detailed user info
46
+ - `online_count()` — approximate online member count
47
+
48
+ ### Message IDs
49
+
50
+ The user's message ID is in the prompt as msg_id:N (Discord snowflake string). Use with `reply_to` and `react`.
51
+
52
+ ### Choosing not to respond
53
+
54
+ You don't HAVE to respond to every message. If a message doesn't need a response:
55
+
56
+ - React with an emoji using the `react` tool — preferred way to acknowledge without replying.
57
+ - Or simply don't call `send` and skip it entirely.
58
+ - In servers, prefer reactions over replies for simple acknowledgements.
59
+
60
+ ### Reactions
61
+
62
+ Use naturally: 👍 ❤️ 🔥 😂 🎉 👀 💯. React AND reply when both feel right.
63
+
64
+ ### Buttons & Components
65
+
66
+ When a user presses a button, you'll receive "[Button pressed]" with the custom_id. Buttons can also be a select menu — those come through with the chosen value in the same format.
67
+
68
+ ### File sending
69
+
70
+ - Files users send are saved to `~/.talon/workspace/uploads/`.
71
+ - To send files: write the file, then use `send(type="file", file_path="...")`.
72
+ - File limit depends on the server's boost tier: 10 MB (default), 25 MB (tier 1), 50 MB (tier 2), 100 MB (tier 3). DMs use 10 MB. Larger files get rejected with a clear error — split or upload externally.
73
+ - You CAN send files. NEVER say you can't.
74
+
75
+ ### Servers vs DMs
76
+
77
+ - In servers, you only see messages where you're @mentioned or replied to (default), or any message in a configured channel (alt mode). Outside that, the conversation is happening without you.
78
+ - In DMs, you see everything — but only allowed users can DM you in the first place.
79
+
80
+ ### Style
81
+
82
+ - Concise. No filler.
83
+ - Discord markdown renders natively — use it.
84
+ - In servers, use names naturally.
@@ -9,20 +9,20 @@
9
9
 
10
10
  ## Core
11
11
 
12
- - You're powered by Claude (Anthropic) via the Agent SDK
13
- - You have tools to interact with your current platform directly (send messages, react, etc.)
12
+ - You're a Talon agent. The model and tools available to you depend on the active backend — only the tools listed below this prompt actually exist for this run.
13
+ - You have tools to interact with your current platform directly (send messages, react, etc.) — those are always provided by the frontend.
14
14
 
15
15
  ## Identity Bootstrap
16
16
 
17
- Your identity is defined in `~/.talon/workspace/identity.md`. Read it to know who you are.
17
+ Your identity is stored at `~/.talon/workspace/identity.md`. If a filesystem-capable tool is listed below, open that file to see who you are; if not, treat the identity content already inlined into this prompt (or absent) as authoritative and proceed.
18
18
 
19
- If the identity file is empty or only contains the template comments, you MUST ask the user during your first interaction:
19
+ If the identity file is empty or only contains template comments, you MUST ask the user during your first interaction:
20
20
 
21
21
  - What should I be called?
22
22
  - Who are you / who created me?
23
23
  - What will I be used for?
24
24
 
25
- Write the answers to `~/.talon/workspace/identity.md` using the Write tool. Keep it concise just key facts about who you are. Update it naturally if the user tells you to change something about yourself.
25
+ When a filesystem-capable tool is available, persist the answers to `~/.talon/workspace/identity.md`. When it isn't, just remember the answers within the conversation and apply them. Keep identity content concise key facts only.
26
26
 
27
27
  ## Guidelines
28
28
 
@@ -36,7 +36,7 @@ Write the answers to `~/.talon/workspace/identity.md` using the Write tool. Keep
36
36
 
37
37
  ## Memory Management
38
38
 
39
- When you learn important new information during a conversation, update your memory file (`~/.talon/workspace/memory/memory.md`) using the Write tool. Things worth remembering:
39
+ When you learn important new information during a conversation, persist it to your memory file at `~/.talon/workspace/memory/memory.md` only when a filesystem-capable tool is available for this backend. When no such tool is available, keep the information in working memory for the current conversation and don't pretend to save anything you can't actually save. Things worth remembering:
40
40
 
41
41
  - **User preferences**: communication style, interests, timezone, language, how they like to be addressed
42
42
  - **Important facts**: names, roles, relationships between users, projects they're working on
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Claude SDK backend factory — wires the Anthropic Claude Agent SDK
3
+ * into the registry.
4
+ *
5
+ * Unlike Kilo/OpenCode (which run a local HTTP server), the Claude SDK
6
+ * spawns a per-query subprocess. So this factory also wires the
7
+ * `refreshMcpServers` hot-swap path used by plugin reload + the
8
+ * `evictOrphanSubprocesses` cleanup helper.
9
+ */
10
+
11
+ import { registerBackend } from "../registry.js";
12
+ import type { BackendFactory } from "../registry.js";
13
+ import type { QueryBackend } from "../../core/types.js";
14
+ import { log } from "../../util/log.js";
15
+ import { getPluginMcpServers } from "../../core/plugin.js";
16
+
17
+ import {
18
+ initAgent as initClaudeAgent,
19
+ updateSystemPrompt as claudeUpdateSystemPrompt,
20
+ handleMessage as claudeHandleMessage,
21
+ warmSession as claudeWarmSession,
22
+ getActiveQuery,
23
+ buildMcpServers,
24
+ runOneShotAgent as claudeRunOneShotAgent,
25
+ evictOrphanSubprocesses as claudeEvictOrphanSubprocesses,
26
+ } from "./index.js";
27
+
28
+ import * as modelProvider from "./model-provider.js";
29
+
30
+ // ── Factory ────────────────────────────────────────────────────────────────
31
+
32
+ const claudeSdkFactory: BackendFactory = {
33
+ // The config schema uses `"claude"` for backward compatibility with
34
+ // talon.json files predating the registry. Matching the id here means
35
+ // no migration is needed.
36
+ id: "claude",
37
+ label: "Anthropic",
38
+
39
+ async init(config, ctx) {
40
+ await initClaudeAgent(config, ctx.getBridgePort);
41
+ log("bot", "Backend: Claude SDK (@anthropic-ai/claude-agent-sdk)");
42
+
43
+ const backend: QueryBackend = {
44
+ query: (params) => claudeHandleMessage(params),
45
+ warmSession: (chatId) => claudeWarmSession(chatId),
46
+ updateSystemPrompt: (prompt) => claudeUpdateSystemPrompt(prompt),
47
+ resolveModel: (q) => modelProvider.resolveModel(q),
48
+ getModelInfo: (id) => modelProvider.getModelInfo(id),
49
+ getSettingsPresentation: (m, options) =>
50
+ modelProvider.getSettingsPresentation(m, options),
51
+ getProviders: () => modelProvider.getProviders(),
52
+ getProviderModels: (p, pg, ps) =>
53
+ modelProvider.getProviderModels(p, pg, ps),
54
+ formatModelError: (q, r) => modelProvider.formatModelError(q, r),
55
+ listModels: (f) => modelProvider.listModels(f),
56
+ backendLabel: "Anthropic",
57
+ refreshMcpServers: async (chatId) => {
58
+ const qi = getActiveQuery(chatId);
59
+ if (!qi) return null;
60
+ // Two-phase teardown: remove all MCP servers first so each
61
+ // subprocess receives an OS-agnostic shutdown via stdio, then
62
+ // install the fresh set.
63
+ await qi.setMcpServers({});
64
+ const bridgeUrl = `http://127.0.0.1:${ctx.getBridgePort()}`;
65
+ const freshServers = {
66
+ ...buildMcpServers(chatId),
67
+ ...getPluginMcpServers(bridgeUrl, chatId),
68
+ };
69
+ return qi.setMcpServers(freshServers);
70
+ },
71
+ runOneShotAgent: (p) => claudeRunOneShotAgent(p),
72
+ evictOrphanSubprocesses: (label) => claudeEvictOrphanSubprocesses(label),
73
+ };
74
+
75
+ return { backend };
76
+ },
77
+ };
78
+
79
+ registerBackend(claudeSdkFactory);