talon-agent 1.12.0 → 1.34.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 (451) hide show
  1. package/README.md +111 -52
  2. package/package.json +39 -12
  3. package/prompts/README.md +112 -0
  4. package/prompts/base.md +12 -12
  5. package/prompts/discord.md +10 -47
  6. package/prompts/heartbeat.md +23 -8
  7. package/prompts/identity.md +12 -22
  8. package/prompts/native.md +23 -0
  9. package/prompts/system/contract-text-or-tools.md +8 -0
  10. package/prompts/system/contract-text-preferred.md +5 -0
  11. package/prompts/system/contract-tool-only.md +10 -0
  12. package/prompts/system/cron.md +3 -0
  13. package/prompts/system/daily-memory.md +3 -0
  14. package/prompts/system/goals.md +7 -0
  15. package/prompts/system/heartbeat-agent.md +44 -0
  16. package/prompts/system/persistent-memory.md +8 -0
  17. package/prompts/system/skills.md +9 -0
  18. package/prompts/system/triggers.md +5 -0
  19. package/prompts/system/workspace.md +9 -0
  20. package/prompts/teams.md +4 -14
  21. package/prompts/telegram.md +17 -89
  22. package/src/app.ts +278 -0
  23. package/src/backend/claude-sdk/constants.ts +22 -10
  24. package/src/backend/claude-sdk/factory.ts +79 -22
  25. package/src/backend/claude-sdk/handler.ts +431 -160
  26. package/src/backend/claude-sdk/index.ts +6 -2
  27. package/src/backend/claude-sdk/mcp-ready.ts +54 -0
  28. package/src/backend/claude-sdk/model-drift.ts +65 -0
  29. package/src/backend/claude-sdk/model-provider.ts +12 -6
  30. package/src/backend/claude-sdk/models/convert.ts +169 -0
  31. package/src/backend/claude-sdk/models/discovery.ts +203 -0
  32. package/src/backend/claude-sdk/models/index.ts +18 -0
  33. package/src/backend/claude-sdk/models/parsing.ts +264 -0
  34. package/src/backend/claude-sdk/models/static.ts +45 -0
  35. package/src/backend/claude-sdk/one-shot.ts +19 -13
  36. package/src/backend/claude-sdk/options.ts +228 -104
  37. package/src/backend/claude-sdk/state.ts +1 -1
  38. package/src/backend/claude-sdk/stream.ts +153 -45
  39. package/src/backend/claude-sdk/warm.ts +12 -5
  40. package/src/backend/codex/auth.ts +216 -40
  41. package/src/backend/codex/constants.ts +63 -22
  42. package/src/backend/codex/discovery.ts +427 -0
  43. package/src/backend/codex/factory.ts +63 -33
  44. package/src/backend/codex/handler/events.ts +239 -0
  45. package/src/backend/codex/handler/index.ts +15 -0
  46. package/src/backend/codex/handler/message.ts +669 -0
  47. package/src/backend/codex/handler/state.ts +14 -0
  48. package/src/backend/codex/handler/usage.ts +68 -0
  49. package/src/backend/codex/index.ts +2 -2
  50. package/src/backend/codex/init.ts +92 -11
  51. package/src/backend/codex/mcp-config.ts +89 -62
  52. package/src/backend/codex/models.ts +305 -54
  53. package/src/backend/codex/oauth-incompat.ts +302 -0
  54. package/src/backend/codex/one-shot.ts +91 -8
  55. package/src/backend/codex/state.ts +54 -3
  56. package/src/backend/codex/token-usage.ts +434 -0
  57. package/src/backend/kilo/factory.ts +60 -35
  58. package/src/backend/kilo/handler/index.ts +13 -0
  59. package/src/backend/kilo/handler/message.ts +317 -0
  60. package/src/backend/kilo/handler/state.ts +14 -0
  61. package/src/backend/kilo/handler/turn.ts +321 -0
  62. package/src/backend/kilo/index.ts +5 -5
  63. package/src/backend/kilo/model-provider.ts +26 -168
  64. package/src/backend/kilo/models/index.ts +52 -0
  65. package/src/backend/kilo/one-shot.ts +20 -142
  66. package/src/backend/kilo/server.ts +14 -21
  67. package/src/backend/openai-agents/builtins.ts +215 -114
  68. package/src/backend/openai-agents/constants.ts +41 -15
  69. package/src/backend/openai-agents/discovery.ts +332 -0
  70. package/src/backend/openai-agents/factory.ts +66 -28
  71. package/src/backend/openai-agents/handler/events.ts +170 -0
  72. package/src/backend/openai-agents/handler/index.ts +13 -0
  73. package/src/backend/openai-agents/handler/message.ts +527 -0
  74. package/src/backend/openai-agents/handler/state.ts +13 -0
  75. package/src/backend/openai-agents/index.ts +22 -4
  76. package/src/backend/openai-agents/init.ts +51 -88
  77. package/src/backend/openai-agents/mcp-pool.ts +252 -0
  78. package/src/backend/openai-agents/models.ts +118 -11
  79. package/src/backend/openai-agents/session.ts +277 -0
  80. package/src/backend/openai-agents/state.ts +82 -11
  81. package/src/backend/opencode/factory.ts +54 -25
  82. package/src/backend/opencode/handler/index.ts +13 -0
  83. package/src/backend/opencode/handler/message.ts +325 -0
  84. package/src/backend/opencode/handler/state.ts +14 -0
  85. package/src/backend/opencode/handler/turn.ts +304 -0
  86. package/src/backend/opencode/index.ts +2 -2
  87. package/src/backend/opencode/model-provider.ts +26 -168
  88. package/src/backend/opencode/models/index.ts +51 -0
  89. package/src/backend/opencode/one-shot.ts +20 -137
  90. package/src/backend/opencode/server.ts +12 -11
  91. package/src/backend/remote-server/client.ts +13 -6
  92. package/src/backend/remote-server/events.ts +59 -9
  93. package/src/backend/remote-server/lifecycle.ts +3 -1
  94. package/src/backend/remote-server/mcp.ts +52 -19
  95. package/src/backend/remote-server/model-catalog/catalog.ts +270 -0
  96. package/src/backend/remote-server/model-catalog/index.ts +100 -0
  97. package/src/backend/remote-server/model-catalog/presentation.ts +298 -0
  98. package/src/backend/remote-server/model-catalog/provider.ts +193 -0
  99. package/src/backend/remote-server/model-catalog/resolve.ts +225 -0
  100. package/src/backend/remote-server/model-catalog/types.ts +136 -0
  101. package/src/backend/remote-server/one-shot.ts +225 -0
  102. package/src/backend/remote-server/session-helpers.ts +3 -4
  103. package/src/backend/remote-server/state.ts +1 -1
  104. package/src/backend/shared/delivery-contract.ts +161 -0
  105. package/src/backend/shared/delivery.ts +49 -0
  106. package/src/backend/shared/flow-violation.ts +38 -7
  107. package/src/backend/shared/frontends.ts +66 -0
  108. package/src/backend/shared/handle-retry.ts +244 -0
  109. package/src/backend/shared/handler-to-events.ts +186 -0
  110. package/src/backend/shared/handler-types.ts +57 -0
  111. package/src/backend/shared/index.ts +51 -5
  112. package/src/backend/shared/metrics.ts +199 -0
  113. package/src/backend/shared/model-retry.ts +1 -1
  114. package/src/backend/shared/prompt-format.ts +69 -0
  115. package/src/backend/shared/session-name.ts +1 -1
  116. package/src/backend/shared/stream-state.ts +36 -2
  117. package/src/backend/shared/system-prompt.ts +200 -23
  118. package/src/bootstrap.ts +338 -38
  119. package/src/cli/chat.ts +49 -0
  120. package/src/cli/config-view.ts +100 -0
  121. package/src/cli/config.ts +105 -0
  122. package/src/cli/context.ts +23 -0
  123. package/src/cli/daemon.ts +95 -0
  124. package/src/cli/doctor.ts +53 -0
  125. package/src/cli/index.ts +139 -0
  126. package/src/cli/logs.ts +60 -0
  127. package/src/cli/menu.ts +104 -0
  128. package/src/cli/setup.ts +455 -0
  129. package/src/cli/status.ts +75 -0
  130. package/src/cli.ts +20 -1211
  131. package/src/core/agent-runtime/README.md +173 -0
  132. package/src/{backend/registry.ts → core/agent-runtime/backend-registry.ts} +15 -6
  133. package/src/core/agent-runtime/capabilities.ts +330 -0
  134. package/src/core/agent-runtime/contract-tests.ts +378 -0
  135. package/src/core/agent-runtime/events.ts +291 -0
  136. package/src/core/agent-runtime/index.ts +62 -0
  137. package/src/core/agent-runtime/model-ref.ts +130 -0
  138. package/src/core/background/cron.ts +469 -0
  139. package/src/core/{dream.ts → background/dream.ts} +118 -53
  140. package/src/core/background/heartbeat/agent.ts +333 -0
  141. package/src/core/background/heartbeat/index.ts +25 -0
  142. package/src/core/background/heartbeat/scheduler.ts +211 -0
  143. package/src/core/background/heartbeat/state.ts +153 -0
  144. package/src/core/background/index.ts +21 -0
  145. package/src/core/background/isolated-agent.ts +115 -0
  146. package/src/core/background/job-health.ts +140 -0
  147. package/src/core/background/job-oneshot.ts +165 -0
  148. package/src/core/background/job-prompt.ts +54 -0
  149. package/src/core/{pulse.ts → background/pulse.ts} +6 -5
  150. package/src/core/background/triggers/command.ts +60 -0
  151. package/src/core/background/triggers/exit.ts +202 -0
  152. package/src/core/background/triggers/index.ts +38 -0
  153. package/src/core/background/triggers/output.ts +142 -0
  154. package/src/core/background/triggers/resume.ts +127 -0
  155. package/src/core/background/triggers/spawn.ts +282 -0
  156. package/src/core/background/triggers/state.ts +52 -0
  157. package/src/core/constants.ts +57 -28
  158. package/src/core/daemon/control.ts +229 -0
  159. package/src/core/daemon/discovery.ts +169 -0
  160. package/src/core/daemon/pidfile.ts +98 -0
  161. package/src/core/doctor.ts +459 -0
  162. package/src/core/engine/backend-controller/holders.ts +17 -0
  163. package/src/core/engine/backend-controller/index.ts +71 -0
  164. package/src/core/engine/backend-controller/legacy.ts +111 -0
  165. package/src/core/engine/backend-controller/pool.ts +296 -0
  166. package/src/core/engine/backend-controller/rebind.ts +205 -0
  167. package/src/core/engine/backend-controller/state.ts +150 -0
  168. package/src/core/engine/backend-controller/types.ts +51 -0
  169. package/src/core/engine/dispatcher.ts +68 -0
  170. package/src/core/engine/gateway-actions/cron.ts +518 -0
  171. package/src/core/engine/gateway-actions/fetch-url.ts +120 -0
  172. package/src/core/engine/gateway-actions/goals.ts +178 -0
  173. package/src/core/engine/gateway-actions/history.ts +53 -0
  174. package/src/core/engine/gateway-actions/index.ts +60 -0
  175. package/src/core/engine/gateway-actions/models.ts +120 -0
  176. package/src/core/engine/gateway-actions/plugins.ts +83 -0
  177. package/src/core/engine/gateway-actions/scripts.ts +127 -0
  178. package/src/core/engine/gateway-actions/shared.ts +66 -0
  179. package/src/core/engine/gateway-actions/skills.ts +120 -0
  180. package/src/core/engine/gateway-actions/triggers.ts +237 -0
  181. package/src/core/engine/gateway-actions/types.ts +18 -0
  182. package/src/core/{gateway.ts → engine/gateway.ts} +223 -60
  183. package/src/core/engine/index.ts +20 -0
  184. package/src/core/engine/model-audit.ts +121 -0
  185. package/src/core/errors.ts +68 -6
  186. package/src/core/mcp-hub/children.ts +254 -0
  187. package/src/core/mcp-hub/index.ts +335 -0
  188. package/src/core/mcp-hub/proxy-server.ts +45 -0
  189. package/src/core/mcp-hub/talon-server.ts +71 -0
  190. package/src/core/memory/retrieval.ts +90 -0
  191. package/src/core/models/active-model.ts +441 -0
  192. package/src/core/{models.ts → models/catalog.ts} +6 -0
  193. package/src/core/models/index.ts +17 -0
  194. package/src/core/models/reasoning-levels.ts +57 -0
  195. package/src/core/plugin/actions.ts +32 -0
  196. package/src/core/plugin/builtins.ts +162 -0
  197. package/src/core/plugin/index.ts +37 -0
  198. package/src/core/plugin/loader.ts +289 -0
  199. package/src/core/plugin/mcp.ts +86 -0
  200. package/src/core/plugin/registry.ts +124 -0
  201. package/src/core/plugin/types.ts +126 -0
  202. package/src/core/prompt/assemble.ts +273 -0
  203. package/src/core/prompt/disk-prompts.ts +41 -0
  204. package/src/core/prompt/embed.ts +107 -0
  205. package/src/core/prompt/embedded-prompts.ts +85 -0
  206. package/src/core/prompt/index.ts +30 -0
  207. package/src/core/prompt/templates.ts +124 -0
  208. package/src/core/prompt/workspace-listing.ts +91 -0
  209. package/src/core/scripting/lua-runner.ts +210 -0
  210. package/src/core/scripts/runner.ts +125 -0
  211. package/src/core/soul/README.md +110 -0
  212. package/src/core/soul/RESEARCH.md +98 -0
  213. package/src/core/soul/associative.ts +98 -0
  214. package/src/core/soul/centrality.ts +98 -0
  215. package/src/core/soul/cluster.ts +83 -0
  216. package/src/core/soul/compiler.ts +207 -0
  217. package/src/core/soul/consolidate.ts +179 -0
  218. package/src/core/soul/critic.ts +159 -0
  219. package/src/core/soul/dag.ts +265 -0
  220. package/src/core/soul/delta.ts +123 -0
  221. package/src/core/soul/drift.ts +99 -0
  222. package/src/core/soul/embedder.ts +129 -0
  223. package/src/core/soul/emergent-critic.ts +96 -0
  224. package/src/core/soul/forgetting.ts +131 -0
  225. package/src/core/soul/governance.ts +93 -0
  226. package/src/core/soul/hash.ts +97 -0
  227. package/src/core/soul/hdc.ts +154 -0
  228. package/src/core/soul/index.ts +140 -0
  229. package/src/core/soul/kernel.ts +540 -0
  230. package/src/core/soul/lattice.ts +103 -0
  231. package/src/core/soul/lens.ts +110 -0
  232. package/src/core/soul/projector.ts +240 -0
  233. package/src/core/soul/reflect.ts +170 -0
  234. package/src/core/soul/reflex.ts +164 -0
  235. package/src/core/soul/retrieve.ts +146 -0
  236. package/src/core/soul/salience.ts +142 -0
  237. package/src/core/soul/service.ts +204 -0
  238. package/src/core/soul/settings.ts +47 -0
  239. package/src/core/soul/signals.ts +117 -0
  240. package/src/core/soul/talon-embedder.ts +80 -0
  241. package/src/core/soul/taps.ts +199 -0
  242. package/src/core/soul/types.ts +298 -0
  243. package/src/core/soul/valence.ts +83 -0
  244. package/src/core/tools/goals.ts +117 -0
  245. package/src/core/tools/index.ts +10 -0
  246. package/src/core/tools/mcp-env.ts +94 -0
  247. package/src/core/tools/mcp-server.ts +27 -4
  248. package/src/core/tools/messaging.ts +122 -29
  249. package/src/core/tools/models.ts +39 -0
  250. package/src/core/tools/scheduling.ts +133 -14
  251. package/src/core/tools/schemas.ts +16 -4
  252. package/src/core/tools/scripts.ts +78 -0
  253. package/src/core/tools/skills.ts +90 -0
  254. package/src/core/tools/stickers.ts +1 -1
  255. package/src/core/tools/triggers.ts +19 -3
  256. package/src/core/tools/types.ts +6 -6
  257. package/src/core/types.ts +62 -112
  258. package/src/core/update/self-update.ts +269 -0
  259. package/src/core/weaver/index.ts +10 -0
  260. package/src/core/weaver/loom.ts +125 -0
  261. package/src/core/weaver/thread-session.ts +51 -0
  262. package/src/core/weaver/thread.ts +196 -0
  263. package/src/core/weaver/weaver.ts +357 -0
  264. package/src/frontend/discord/actions/chat-info.ts +268 -0
  265. package/src/frontend/discord/actions/index.ts +77 -0
  266. package/src/frontend/discord/actions/media.ts +190 -0
  267. package/src/frontend/discord/actions/messaging.ts +277 -0
  268. package/src/frontend/discord/actions/shared.ts +100 -0
  269. package/src/frontend/discord/actions/types.ts +33 -0
  270. package/src/frontend/discord/admin.ts +13 -23
  271. package/src/frontend/discord/callbacks/autocomplete.ts +55 -0
  272. package/src/frontend/discord/{callbacks.ts → callbacks/components.ts} +150 -190
  273. package/src/frontend/discord/callbacks/index.ts +14 -0
  274. package/src/frontend/discord/callbacks/modals.ts +76 -0
  275. package/src/frontend/discord/callbacks/shared.ts +22 -0
  276. package/src/frontend/discord/commands/admin.ts +88 -0
  277. package/src/frontend/discord/commands/definitions.ts +200 -0
  278. package/src/frontend/discord/commands/index.ts +19 -0
  279. package/src/frontend/discord/commands/info.ts +106 -0
  280. package/src/frontend/discord/commands/router.ts +149 -0
  281. package/src/frontend/discord/commands/session.ts +89 -0
  282. package/src/frontend/discord/commands/settings.ts +405 -0
  283. package/src/frontend/discord/commands/shared.ts +50 -0
  284. package/src/frontend/discord/formatting.ts +8 -34
  285. package/src/frontend/discord/handlers/access.ts +248 -0
  286. package/src/frontend/discord/handlers/context.ts +161 -0
  287. package/src/frontend/discord/handlers/delivery.ts +94 -0
  288. package/src/frontend/discord/handlers/index.ts +34 -0
  289. package/src/frontend/discord/handlers/messages.ts +120 -0
  290. package/src/frontend/discord/handlers/queue.ts +135 -0
  291. package/src/frontend/discord/handlers/registry.ts +29 -0
  292. package/src/frontend/discord/handlers/state.ts +94 -0
  293. package/src/frontend/discord/helpers.ts +46 -57
  294. package/src/frontend/discord/index.ts +10 -7
  295. package/src/frontend/discord/middleware.ts +2 -2
  296. package/src/frontend/native/actions.ts +133 -0
  297. package/src/frontend/native/chats.ts +146 -0
  298. package/src/frontend/native/discovery.ts +50 -0
  299. package/src/frontend/native/index.ts +1227 -0
  300. package/src/frontend/native/logs.ts +95 -0
  301. package/src/frontend/native/protocol.ts +253 -0
  302. package/src/frontend/native/server.ts +612 -0
  303. package/src/frontend/native/settings.ts +205 -0
  304. package/src/frontend/native/turn-meta.ts +43 -0
  305. package/src/frontend/shared/format.ts +51 -0
  306. package/src/frontend/shared/reasoning-levels.ts +46 -0
  307. package/src/frontend/shared/session-status.ts +189 -0
  308. package/src/frontend/shared/status-context.ts +125 -0
  309. package/src/frontend/teams/actions.ts +7 -5
  310. package/src/frontend/teams/formatting.ts +7 -18
  311. package/src/frontend/teams/graph.ts +2 -1
  312. package/src/frontend/teams/index.ts +76 -54
  313. package/src/frontend/telegram/actions/chat-info.ts +323 -0
  314. package/src/frontend/telegram/actions/index.ts +68 -0
  315. package/src/frontend/telegram/actions/media.ts +224 -0
  316. package/src/frontend/telegram/actions/messaging.ts +295 -0
  317. package/src/frontend/telegram/actions/shared.ts +58 -0
  318. package/src/frontend/telegram/actions/types.ts +31 -0
  319. package/src/frontend/telegram/admin.ts +12 -22
  320. package/src/frontend/telegram/callbacks/effort.ts +68 -0
  321. package/src/frontend/telegram/callbacks/index.ts +68 -0
  322. package/src/frontend/telegram/callbacks/model.ts +311 -0
  323. package/src/frontend/telegram/callbacks/pulse.ts +50 -0
  324. package/src/frontend/telegram/callbacks/settings.ts +177 -0
  325. package/src/frontend/telegram/callbacks/shared.ts +71 -0
  326. package/src/frontend/telegram/commands/admin.ts +226 -0
  327. package/src/frontend/telegram/commands/definitions.ts +60 -0
  328. package/src/frontend/telegram/commands/index.ts +39 -0
  329. package/src/frontend/telegram/commands/info.ts +125 -0
  330. package/src/frontend/telegram/commands/session.ts +77 -0
  331. package/src/frontend/telegram/commands/settings.ts +345 -0
  332. package/src/frontend/telegram/commands/state.ts +34 -0
  333. package/src/frontend/telegram/formatting.ts +14 -25
  334. package/src/frontend/telegram/handlers/access.ts +287 -0
  335. package/src/frontend/telegram/handlers/context.ts +210 -0
  336. package/src/frontend/telegram/handlers/delivery.ts +236 -0
  337. package/src/frontend/telegram/handlers/index.ts +39 -0
  338. package/src/frontend/telegram/handlers/messages.ts +477 -0
  339. package/src/frontend/telegram/handlers/queue.ts +241 -0
  340. package/src/frontend/telegram/handlers/state.ts +75 -0
  341. package/src/frontend/telegram/helpers/diagnostics.ts +188 -0
  342. package/src/frontend/telegram/helpers/format.ts +41 -0
  343. package/src/frontend/telegram/helpers/index.ts +13 -0
  344. package/src/frontend/telegram/{helpers.ts → helpers/menu.ts} +209 -210
  345. package/src/frontend/telegram/index.ts +24 -27
  346. package/src/frontend/telegram/middleware.ts +16 -3
  347. package/src/frontend/telegram/model-callbacks.ts +14 -0
  348. package/src/frontend/telegram/model-menu.ts +317 -0
  349. package/src/frontend/telegram/sticker-library.ts +178 -0
  350. package/src/frontend/telegram/userbot.ts +2 -44
  351. package/src/frontend/terminal/commands.ts +47 -41
  352. package/src/frontend/terminal/index.ts +46 -31
  353. package/src/frontend/terminal/input.ts +5 -0
  354. package/src/frontend/terminal/renderer.ts +4 -2
  355. package/src/index.ts +13 -171
  356. package/src/login.ts +3 -2
  357. package/src/native/blake3-wasm-bytes.ts +7 -0
  358. package/src/native/blake3.ts +234 -0
  359. package/src/native/htmlents-wasm-bytes.ts +7 -0
  360. package/src/native/htmlents.ts +56 -0
  361. package/src/native/prelude.d.mts +168 -0
  362. package/src/native/prelude.mjs +1561 -0
  363. package/src/native/registry.ts +142 -0
  364. package/src/native/runtime.ts +164 -0
  365. package/src/native/scheduler-core/gleam.d.mts +2 -0
  366. package/src/native/scheduler-core/gleam.mjs +1 -0
  367. package/src/native/scheduler-core/scheduler_core.d.mts +76 -0
  368. package/src/native/scheduler-core/scheduler_core.mjs +252 -0
  369. package/src/native/scheduler-core.ts +124 -0
  370. package/src/native/sqlguard-wasm-bytes.ts +7 -0
  371. package/src/native/sqlguard.ts +107 -0
  372. package/src/native/strsim-wasm-bytes.ts +7 -0
  373. package/src/native/strsim.ts +98 -0
  374. package/src/native/textops-wasm-bytes.ts +7 -0
  375. package/src/native/textops.ts +83 -0
  376. package/src/native/warden.ts +287 -0
  377. package/src/plugins/github/index.ts +1 -1
  378. package/src/plugins/mempalace/index.ts +1 -1
  379. package/src/plugins/playwright/index.ts +1 -1
  380. package/src/storage/chat-settings.ts +335 -85
  381. package/src/storage/cron-store.ts +266 -109
  382. package/src/storage/daily-log.ts +15 -3
  383. package/src/storage/db.ts +206 -0
  384. package/src/storage/goal-store.ts +186 -0
  385. package/src/storage/history.ts +137 -188
  386. package/src/storage/kv.ts +52 -0
  387. package/src/storage/legacy-import.ts +47 -0
  388. package/src/storage/media-index.ts +118 -61
  389. package/src/storage/repositories/chat-settings-repo.ts +60 -0
  390. package/src/storage/repositories/cron-repo.ts +128 -0
  391. package/src/storage/repositories/goals-repo.ts +122 -0
  392. package/src/storage/repositories/history-repo.ts +207 -0
  393. package/src/storage/repositories/kv-repo.ts +25 -0
  394. package/src/storage/repositories/media-index-repo.ts +147 -0
  395. package/src/storage/repositories/scripts-repo.ts +81 -0
  396. package/src/storage/repositories/sessions-repo.ts +129 -0
  397. package/src/storage/repositories/triggers-repo.ts +158 -0
  398. package/src/storage/repositories/turn-meta-repo.ts +32 -0
  399. package/src/storage/scheduled-store.ts +84 -0
  400. package/src/storage/script-store.ts +198 -0
  401. package/src/storage/sessions.ts +227 -102
  402. package/src/storage/skill-store.ts +325 -0
  403. package/src/storage/sql/README.md +18 -0
  404. package/src/storage/sql/chat-settings.sql +11 -0
  405. package/src/storage/sql/cron.sql +40 -0
  406. package/src/storage/sql/db.sql +11 -0
  407. package/src/storage/sql/embed.ts +118 -0
  408. package/src/storage/sql/goals.sql +41 -0
  409. package/src/storage/sql/history.sql +83 -0
  410. package/src/storage/sql/kv.sql +11 -0
  411. package/src/storage/sql/media-index.sql +45 -0
  412. package/src/storage/sql/schema.sql +233 -0
  413. package/src/storage/sql/scripts.sql +28 -0
  414. package/src/storage/sql/sessions.sql +22 -0
  415. package/src/storage/sql/statements.generated.ts +500 -0
  416. package/src/storage/sql/triggers.sql +69 -0
  417. package/src/storage/sql/turn-meta.sql +24 -0
  418. package/src/storage/sticker-store.ts +141 -0
  419. package/src/storage/trigger-store.ts +104 -126
  420. package/src/storage/turn-meta.ts +118 -0
  421. package/src/util/chat-id.ts +30 -0
  422. package/src/util/config.ts +313 -195
  423. package/src/util/fs-path.ts +32 -0
  424. package/src/util/log.ts +53 -31
  425. package/src/util/mcp-launcher.ts +351 -33
  426. package/src/util/paths.ts +45 -17
  427. package/src/util/tail-file.ts +24 -0
  428. package/src/util/workspace.ts +161 -10
  429. package/tsconfig.json +1 -0
  430. package/src/backend/claude-sdk/models.ts +0 -501
  431. package/src/backend/codex/handler.ts +0 -595
  432. package/src/backend/kilo/handler.ts +0 -707
  433. package/src/backend/kilo/models.ts +0 -762
  434. package/src/backend/openai-agents/handler.ts +0 -539
  435. package/src/backend/openai-agents/mcp.ts +0 -139
  436. package/src/backend/opencode/handler.ts +0 -663
  437. package/src/backend/opencode/models.ts +0 -741
  438. package/src/core/cron.ts +0 -184
  439. package/src/core/dispatcher.ts +0 -144
  440. package/src/core/gateway-actions.ts +0 -603
  441. package/src/core/heartbeat.ts +0 -575
  442. package/src/core/plugin.ts +0 -776
  443. package/src/core/triggers.ts +0 -640
  444. package/src/frontend/discord/actions.ts +0 -729
  445. package/src/frontend/discord/commands.ts +0 -1036
  446. package/src/frontend/discord/handlers.ts +0 -798
  447. package/src/frontend/telegram/actions.ts +0 -731
  448. package/src/frontend/telegram/callbacks.ts +0 -419
  449. package/src/frontend/telegram/commands.ts +0 -642
  450. package/src/frontend/telegram/handlers.ts +0 -1352
  451. package/src/util/mcp-launcher.mjs +0 -140
@@ -0,0 +1,287 @@
1
+ /**
2
+ * Access control — DM whitelist, group admin-membership checks, unauthorized
3
+ * notifications, and DM-user tracking.
4
+ */
5
+
6
+ import type { Bot, Context } from "grammy";
7
+ import { appendDailyLog } from "../../../storage/daily-log.js";
8
+ import { log, logWarn } from "../../../util/log.js";
9
+ import { getSenderName } from "./context.js";
10
+ import {
11
+ knownDmUsers,
12
+ KNOWN_DM_USERS_CAP,
13
+ accessConfig,
14
+ verifiedGroups,
15
+ MAX_VERIFIED_GROUPS,
16
+ VERIFIED_GROUP_TTL_MS,
17
+ unauthorizedCooldown,
18
+ UNAUTHORIZED_COOLDOWN_MS,
19
+ MAX_UNAUTHORIZED_COOLDOWNS,
20
+ } from "./state.js";
21
+
22
+ export function trackDmUser(
23
+ senderId: number,
24
+ senderName: string,
25
+ senderUsername?: string,
26
+ ): void {
27
+ if (knownDmUsers.has(senderId)) return;
28
+ // Evict oldest 10% when cap reached (Set maintains insertion order)
29
+ if (knownDmUsers.size >= KNOWN_DM_USERS_CAP) {
30
+ const evictCount = Math.floor(KNOWN_DM_USERS_CAP * 0.1);
31
+ const iter = knownDmUsers.values();
32
+ for (let i = 0; i < evictCount; i++) {
33
+ knownDmUsers.delete(iter.next().value as number);
34
+ }
35
+ }
36
+ knownDmUsers.add(senderId);
37
+ const tag = senderUsername ? ` (@${senderUsername})` : "";
38
+ log("users", `New DM user: ${senderName}${tag} [id:${senderId}]`);
39
+ appendDailyLog("System", `New DM user: ${senderName}${tag} [id:${senderId}]`);
40
+ }
41
+
42
+ export function setAccessControl(cfg: {
43
+ allowedUsers?: number[];
44
+ adminUserId?: number;
45
+ }): void {
46
+ accessConfig.allowedUserIds = cfg.allowedUsers?.length
47
+ ? new Set(cfg.allowedUsers)
48
+ : null;
49
+ accessConfig.adminId = cfg.adminUserId ?? 0;
50
+ }
51
+
52
+ /**
53
+ * Check if a DM user is allowed. Returns true if no whitelist is set.
54
+ */
55
+ function isDmAllowed(senderId: number | undefined): boolean {
56
+ if (!accessConfig.allowedUserIds) return true;
57
+ return senderId !== undefined && accessConfig.allowedUserIds.has(senderId);
58
+ }
59
+
60
+ /**
61
+ * Check if the admin is a member of a group. Caches results for 10 minutes.
62
+ */
63
+ async function isAdminInGroup(bot: Bot, chatId: number): Promise<boolean> {
64
+ if (!accessConfig.adminId) return true; // no admin configured, allow all groups
65
+ const cached = verifiedGroups.get(chatId);
66
+ if (cached !== undefined && cached.expiresAt > Date.now()) {
67
+ return cached.isMember;
68
+ }
69
+ // Expired or missing — delete stale entry
70
+ if (cached) verifiedGroups.delete(chatId);
71
+
72
+ // Prevent unbounded growth — evict expired entries first, then clear if still over
73
+ if (verifiedGroups.size >= MAX_VERIFIED_GROUPS) {
74
+ const now = Date.now();
75
+ for (const [k, v] of verifiedGroups) {
76
+ if (v.expiresAt <= now) verifiedGroups.delete(k);
77
+ }
78
+ if (verifiedGroups.size >= MAX_VERIFIED_GROUPS) verifiedGroups.clear();
79
+ }
80
+
81
+ try {
82
+ const member = await bot.api.getChatMember(chatId, accessConfig.adminId);
83
+ const isMember = !["left", "kicked"].includes(member.status);
84
+ verifiedGroups.set(chatId, {
85
+ isMember,
86
+ expiresAt: Date.now() + VERIFIED_GROUP_TTL_MS,
87
+ });
88
+ return isMember;
89
+ } catch (err) {
90
+ logWarn(
91
+ "bot",
92
+ `isAdminInGroup check failed for chat ${chatId}: ${err instanceof Error ? err.message : err}`,
93
+ );
94
+ // API error (e.g. bot can't query members) — deny by default
95
+ verifiedGroups.set(chatId, {
96
+ isMember: false,
97
+ expiresAt: Date.now() + VERIFIED_GROUP_TTL_MS,
98
+ });
99
+ return false;
100
+ }
101
+ }
102
+
103
+ export function shouldHandleInGroup(ctx: Context): boolean {
104
+ if (!ctx.chat || !ctx.message) return false;
105
+ const isGroup = ctx.chat.type === "group" || ctx.chat.type === "supergroup";
106
+ if (!isGroup) return true;
107
+ const text = ctx.message.text || ctx.message.caption || "";
108
+ const botUser = ctx.me.username;
109
+ // Word-boundary match — @botname must not be followed by alphanumeric/underscore
110
+ const mentioned =
111
+ botUser && new RegExp(`@${botUser}(?![a-zA-Z0-9_])`, "i").test(text);
112
+ const repliedToBot = ctx.message.reply_to_message?.from?.id === ctx.me.id;
113
+ return !!(mentioned || repliedToBot);
114
+ }
115
+
116
+ /**
117
+ * Full access check: DM whitelist + group admin membership.
118
+ * Returns true if the message should be processed.
119
+ * Warns unauthorized users and notifies the admin.
120
+ */
121
+ export async function isAccessAllowed(
122
+ ctx: Context,
123
+ bot: Bot,
124
+ ): Promise<boolean> {
125
+ if (!ctx.chat) return false;
126
+ const isGroup = ctx.chat.type === "group" || ctx.chat.type === "supergroup";
127
+
128
+ if (!isGroup) {
129
+ if (isDmAllowed(ctx.from?.id)) return true;
130
+ await notifyUnauthorized(bot, ctx, "dm");
131
+ return false;
132
+ }
133
+
134
+ if (await isAdminInGroup(bot, ctx.chat.id)) return true;
135
+ await notifyUnauthorized(bot, ctx, "group");
136
+ return false;
137
+ }
138
+
139
+ /**
140
+ * Maximum length of an unauthorized message body to retain in logs.
141
+ * Keeps abusive payloads (large pastes, attachment captions etc.) bounded
142
+ * while still preserving enough context to understand what was sent.
143
+ */
144
+ const UNAUTHORIZED_BODY_MAX_LEN = 1024;
145
+
146
+ /**
147
+ * Best-effort preview of an unauthorized message for forensics.
148
+ *
149
+ * Returns the visible text payload (text or caption), a short tag for
150
+ * media-only messages (`[sticker: 🤖]`, `[photo]`, `[voice 14s]`, etc.),
151
+ * or `undefined` if there's nothing meaningful to capture (e.g. a service
152
+ * message or empty content).
153
+ *
154
+ * Truncated to UNAUTHORIZED_BODY_MAX_LEN to keep log lines bounded.
155
+ */
156
+ export function extractUnauthorizedPreview(
157
+ message:
158
+ | {
159
+ text?: string;
160
+ caption?: string;
161
+ sticker?: { emoji?: string; set_name?: string };
162
+ photo?: unknown;
163
+ voice?: { duration?: number };
164
+ video?: unknown;
165
+ video_note?: unknown;
166
+ audio?: unknown;
167
+ animation?: unknown;
168
+ document?: { file_name?: string };
169
+ contact?: unknown;
170
+ location?: unknown;
171
+ poll?: { question?: string };
172
+ dice?: { emoji?: string };
173
+ }
174
+ | undefined,
175
+ ): string | undefined {
176
+ if (!message) return undefined;
177
+
178
+ const text = message.text ?? message.caption;
179
+ if (typeof text === "string" && text.trim().length > 0) {
180
+ return text.length > UNAUTHORIZED_BODY_MAX_LEN
181
+ ? `${text.slice(0, UNAUTHORIZED_BODY_MAX_LEN)}… [truncated]`
182
+ : text;
183
+ }
184
+
185
+ if (message.sticker) {
186
+ const emoji = message.sticker.emoji ?? "?";
187
+ const set = message.sticker.set_name
188
+ ? ` from ${message.sticker.set_name}`
189
+ : "";
190
+ return `[sticker: ${emoji}${set}]`;
191
+ }
192
+ if (message.photo) return "[photo]";
193
+ if (message.voice) {
194
+ const dur = message.voice.duration;
195
+ return dur ? `[voice ${dur}s]` : "[voice]";
196
+ }
197
+ if (message.video_note) return "[video note]";
198
+ if (message.video) return "[video]";
199
+ if (message.audio) return "[audio]";
200
+ if (message.animation) return "[animation]";
201
+ if (message.document) {
202
+ return message.document.file_name
203
+ ? `[document: ${message.document.file_name}]`
204
+ : "[document]";
205
+ }
206
+ if (message.contact) return "[contact]";
207
+ if (message.location) return "[location]";
208
+ if (message.poll) {
209
+ return message.poll.question
210
+ ? `[poll: ${message.poll.question}]`
211
+ : "[poll]";
212
+ }
213
+ if (message.dice) return `[dice: ${message.dice.emoji ?? "🎲"}]`;
214
+
215
+ return undefined;
216
+ }
217
+
218
+ async function notifyUnauthorized(
219
+ bot: Bot,
220
+ ctx: Context,
221
+ type: "dm" | "group",
222
+ ): Promise<void> {
223
+ const sender = getSenderName(ctx.from);
224
+ const username = ctx.from?.username ? ` (@${ctx.from.username})` : "";
225
+ const userId = ctx.from?.id ?? "unknown";
226
+
227
+ // Capture message body BEFORE the cooldown check — every unauthorized
228
+ // attempt should be recorded for forensics, even if the user-facing
229
+ // warning + admin notification are suppressed by cooldown. Without
230
+ // this, follow-up DMs from a known social-engineering account vanish
231
+ // entirely from logs.
232
+ const body = extractUnauthorizedPreview(
233
+ ctx.message as Parameters<typeof extractUnauthorizedPreview>[0],
234
+ );
235
+ if (body) {
236
+ try {
237
+ appendDailyLog(
238
+ `⚠️ UNAUTHORIZED ${sender}${username} [id:${userId}]`,
239
+ body,
240
+ );
241
+ } catch {
242
+ /* daily log unavailable — fall through to talon.log */
243
+ }
244
+ logWarn(
245
+ "access",
246
+ `Unauthorized ${type} body from ${sender}${username} [id:${userId}]: ${body.slice(0, 200)}`,
247
+ );
248
+ }
249
+
250
+ const key = type === "dm" ? `dm:${ctx.from?.id}` : `group:${ctx.chat?.id}`;
251
+ const now = Date.now();
252
+ const lastWarned = unauthorizedCooldown.get(key);
253
+ if (lastWarned && now - lastWarned < UNAUTHORIZED_COOLDOWN_MS) return;
254
+ if (unauthorizedCooldown.size >= MAX_UNAUTHORIZED_COOLDOWNS) {
255
+ unauthorizedCooldown.clear();
256
+ }
257
+ unauthorizedCooldown.set(key, now);
258
+
259
+ // Warn the user
260
+ try {
261
+ await bot.api.sendMessage(
262
+ ctx.chat!.id,
263
+ "⚠️ Unauthorized access. This bot is private. This attempt has been reported to the bot owner.",
264
+ );
265
+ } catch {
266
+ /* can't send — ignore */
267
+ }
268
+
269
+ // Notify admin
270
+ if (accessConfig.adminId) {
271
+ const detail =
272
+ type === "dm"
273
+ ? `🚨 Unauthorized DM from ${sender}${username} [id:${userId}]`
274
+ : `🚨 Unauthorized group access: "${(ctx.chat as { title?: string })?.title ?? ctx.chat!.id}" [id:${ctx.chat!.id}] by ${sender}${username}`;
275
+ const detailWithBody = body ? `${detail}\n\n${body.slice(0, 400)}` : detail;
276
+ try {
277
+ await bot.api.sendMessage(accessConfig.adminId, detailWithBody);
278
+ } catch {
279
+ /* admin unreachable — ignore */
280
+ }
281
+ }
282
+
283
+ logWarn(
284
+ "access",
285
+ `Unauthorized ${type}: ${sender}${username} [id:${userId}] in chat ${ctx.chat!.id}`,
286
+ );
287
+ }
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Message-context helpers — sender name, reply/forward context strings, and
3
+ * Telegram file downloads (with image magic-byte validation).
4
+ */
5
+
6
+ import type { Bot } from "grammy";
7
+ import type { TalonConfig } from "../../../util/config.js";
8
+ import { writeFileSync, mkdirSync, existsSync } from "node:fs";
9
+ import { resolve } from "node:path";
10
+ import { logWarn } from "../../../util/log.js";
11
+
12
+ export function getSenderName(
13
+ from: { first_name?: string; last_name?: string } | undefined,
14
+ ): string {
15
+ return (
16
+ [from?.first_name, from?.last_name].filter(Boolean).join(" ") || "User"
17
+ );
18
+ }
19
+
20
+ export function getReplyContext(
21
+ replyMsg:
22
+ | {
23
+ message_id?: number;
24
+ from?: { id: number; first_name?: string; last_name?: string };
25
+ text?: string;
26
+ caption?: string;
27
+ photo?: unknown[];
28
+ document?: unknown;
29
+ video?: unknown;
30
+ voice?: unknown;
31
+ audio?: unknown;
32
+ sticker?: unknown;
33
+ animation?: unknown;
34
+ }
35
+ | undefined,
36
+ botId: number,
37
+ quote?: { text?: string; is_manual?: boolean } | undefined,
38
+ ): string {
39
+ if (!replyMsg) return "";
40
+
41
+ const author =
42
+ replyMsg.from?.id === botId
43
+ ? "bot"
44
+ : [replyMsg.from?.first_name, replyMsg.from?.last_name]
45
+ .filter(Boolean)
46
+ .join(" ") || "User";
47
+ const text = replyMsg.text || replyMsg.caption || "";
48
+ const msgIdTag = replyMsg.message_id ? ` msg_id:${replyMsg.message_id}` : "";
49
+
50
+ // Detect media type
51
+ const mediaType = replyMsg.photo
52
+ ? "photo"
53
+ : replyMsg.video
54
+ ? "video"
55
+ : replyMsg.document
56
+ ? "document"
57
+ : replyMsg.voice
58
+ ? "voice"
59
+ : replyMsg.audio
60
+ ? "audio"
61
+ : replyMsg.sticker
62
+ ? "sticker"
63
+ : replyMsg.animation
64
+ ? "animation"
65
+ : null;
66
+ const mediaPart = mediaType ? ` [${mediaType}]` : "";
67
+
68
+ // Build context — always include if there's a message_id (even if no text)
69
+ const textPart = text ? `: "${text.slice(0, 500)}"` : "";
70
+
71
+ // Telegram lets users highlight a specific portion of a message when
72
+ // replying (Bot API 7.0+). When present, surface it explicitly so the model
73
+ // sees which part the user is pointing at, not just the whole replied-to
74
+ // message. `is_manual=false` means Telegram chose the snippet automatically
75
+ // (long messages) — still useful signal, so include either way.
76
+ const quoteText = quote?.text?.trim();
77
+ const quotePart = quoteText
78
+ ? `\n[Quoted portion: "${quoteText.slice(0, 500)}"]`
79
+ : "";
80
+
81
+ if (!textPart && !mediaPart && !msgIdTag && !quotePart) return "";
82
+
83
+ return `[Replying to ${author}${textPart}${mediaPart}${msgIdTag}]${quotePart}\n\n`;
84
+ }
85
+
86
+ /**
87
+ * If the replied-to message contains a photo, download it and return a prompt
88
+ * line pointing to the saved file so the model can see it. Returns "" if no photo.
89
+ */
90
+ export async function downloadReplyPhoto(
91
+ replyMsg:
92
+ | {
93
+ photo?: {
94
+ file_id: string;
95
+ file_unique_id: string;
96
+ width?: number;
97
+ height?: number;
98
+ }[];
99
+ }
100
+ | undefined,
101
+ bot: Bot,
102
+ config: TalonConfig,
103
+ ): Promise<string> {
104
+ if (!replyMsg?.photo?.length) return "";
105
+ try {
106
+ // Pick the largest photo size (last in array)
107
+ const bestPhoto = replyMsg.photo[replyMsg.photo.length - 1];
108
+ const savedPath = await downloadTelegramFile(
109
+ bot,
110
+ config,
111
+ bestPhoto.file_id,
112
+ `reply_photo_${bestPhoto.file_unique_id}.jpg`,
113
+ );
114
+ return `[Replied-to message contains a photo saved to: ${savedPath} — read it to view]\n`;
115
+ } catch (err) {
116
+ logWarn(
117
+ "bot",
118
+ `Failed to download reply photo: ${err instanceof Error ? err.message : err}`,
119
+ );
120
+ return "";
121
+ }
122
+ }
123
+
124
+ export function getForwardContext(msg: {
125
+ forward_origin?: {
126
+ type: string;
127
+ sender_user?: { first_name?: string; last_name?: string };
128
+ sender_user_name?: string;
129
+ chat?: { title?: string };
130
+ };
131
+ }): string {
132
+ const origin = msg.forward_origin;
133
+ if (!origin) return "";
134
+ let from = "someone";
135
+ if (origin.type === "user" && origin.sender_user) {
136
+ from = [origin.sender_user.first_name, origin.sender_user.last_name]
137
+ .filter(Boolean)
138
+ .join(" ");
139
+ } else if (origin.type === "hidden_user" && origin.sender_user_name) {
140
+ from = origin.sender_user_name;
141
+ } else if (
142
+ (origin.type === "channel" || origin.type === "chat") &&
143
+ origin.chat
144
+ ) {
145
+ from = origin.chat.title || "a chat";
146
+ }
147
+ return `[Forwarded from ${from}]\n`;
148
+ }
149
+
150
+ export async function downloadTelegramFile(
151
+ bot: Bot,
152
+ config: TalonConfig,
153
+ fileId: string,
154
+ fileName: string,
155
+ ): Promise<string> {
156
+ const file = await bot.api.getFile(fileId);
157
+ if (!file.file_path) throw new Error("Could not get file path from Telegram");
158
+
159
+ const url = `https://api.telegram.org/file/bot${config.botToken}/${file.file_path}`;
160
+ const resp = await fetch(url);
161
+ if (!resp.ok) throw new Error(`Download failed: ${resp.status}`);
162
+
163
+ // Guard against excessively large files (50MB limit)
164
+ const MAX_DOWNLOAD_BYTES = 50 * 1024 * 1024;
165
+ const contentLength = resp.headers.get("content-length");
166
+ if (contentLength && parseInt(contentLength, 10) > MAX_DOWNLOAD_BYTES) {
167
+ throw new Error(
168
+ `File too large (${Math.round(parseInt(contentLength, 10) / 1024 / 1024)}MB, max 50MB)`,
169
+ );
170
+ }
171
+
172
+ const buffer = Buffer.from(await resp.arrayBuffer());
173
+ if (buffer.length === 0)
174
+ throw new Error("Downloaded file is empty (0 bytes)");
175
+
176
+ // Validate image files — prevent saving HTML/garbage as .jpg/.png
177
+ // (corrupt "images" poison the session permanently on resume)
178
+ const imageExts = [".jpg", ".jpeg", ".png", ".gif", ".webp"];
179
+ const isImageExt = imageExts.some((ext) =>
180
+ fileName.toLowerCase().endsWith(ext),
181
+ );
182
+ if (isImageExt) {
183
+ const m = buffer.subarray(0, 16);
184
+ const validImage =
185
+ (m[0] === 0xff && m[1] === 0xd8) || // JPEG
186
+ (m[0] === 0x89 && m[1] === 0x50 && m[2] === 0x4e && m[3] === 0x47) || // PNG
187
+ (m[0] === 0x47 && m[1] === 0x49 && m[2] === 0x46) || // GIF
188
+ (m[0] === 0x52 &&
189
+ m[1] === 0x49 &&
190
+ m[2] === 0x46 &&
191
+ m[3] === 0x46 &&
192
+ m[8] === 0x57 &&
193
+ m[9] === 0x45 &&
194
+ m[10] === 0x42 &&
195
+ m[11] === 0x50); // WebP
196
+ if (!validImage) {
197
+ throw new Error(
198
+ `File "${fileName}" has image extension but invalid content — not saving to prevent session corruption`,
199
+ );
200
+ }
201
+ }
202
+
203
+ const uploadsDir = resolve(config.workspace, "uploads");
204
+ if (!existsSync(uploadsDir)) mkdirSync(uploadsDir, { recursive: true });
205
+
206
+ const safeName = `${Date.now()}-${fileName.replace(/[^a-zA-Z0-9._-]/g, "_")}`;
207
+ const destPath = resolve(uploadsDir, safeName);
208
+ writeFileSync(destPath, buffer);
209
+ return destPath;
210
+ }