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,124 @@
1
+ /**
2
+ * Package-owned prompt templates — loader + Liquid renderer.
3
+ *
4
+ * Talon's prompt text lives in two places with different ownership:
5
+ *
6
+ * - **User-editable prompts** (`identity.md`, `base.md`/`custom.md`,
7
+ * frontend files like `telegram.md`) are seeded once into
8
+ * `~/.talon/prompts/` and read from there — user edits win, and
9
+ * package updates deliberately never overwrite them. These are
10
+ * rendered with plain `{{name}}` string replacement by their
11
+ * consumers (heartbeat/dream), NOT through Liquid — a user file
12
+ * must never be able to break prompt assembly with a syntax error.
13
+ *
14
+ * - **System templates** (`prompts/system/*.md` — delivery
15
+ * contracts, capability docs, section wrappers) are read straight
16
+ * from the PACKAGE directory and are NOT seeded. They describe
17
+ * runtime behaviour that is versioned with the code (tool names,
18
+ * flow enforcement, trigger limits); a stale seeded copy would
19
+ * silently document a contract the code no longer implements.
20
+ *
21
+ * System templates are [Liquid](https://liquidjs.com) — `{{name}}`
22
+ * output (missing → empty string, matching the legacy renderer),
23
+ * `{% if %}`/`{% else %}` conditionals, and `{% render 'partial' %}`
24
+ * includes resolved against `prompts/system/`. Liquid was chosen over
25
+ * a homegrown DSL so prompt text can be composed (sections,
26
+ * conditionals, partials) inside fewer files instead of one file per
27
+ * fragment — and over JS-in-template engines because templates stay
28
+ * pure prose: no code execution.
29
+ *
30
+ * Templates are parsed once per process and cached; rendering with
31
+ * vars is pure in-memory work.
32
+ */
33
+
34
+ import type { FS } from "liquidjs";
35
+ import { Liquid, type Template } from "liquidjs";
36
+ import { promptAssetExists, readPromptAsset } from "#prompt-assets";
37
+
38
+ // ── Renderer ────────────────────────────────────────────────────────────────
39
+
40
+ export type TemplateVars = Record<string, string | undefined>;
41
+
42
+ /**
43
+ * Map a Liquid lookup (a bare partial name or `<name>.md`) to its rel path
44
+ * under the package `prompts/system/` directory. System templates live
45
+ * flat there and are referenced by bare name in `{% render %}`.
46
+ */
47
+ const systemRel = (file: string): string => {
48
+ const base = file.split(/[\\/]/).pop() ?? file;
49
+ return `system/${base.endsWith(".md") ? base : `${base}.md`}`;
50
+ };
51
+
52
+ /**
53
+ * A Liquid filesystem backed by the `#prompt-assets` seam instead of the
54
+ * real disk, so `{% render 'partial' %}` includes resolve identically
55
+ * under tsx (reads `prompts/system/*.md` from disk) and a
56
+ * `bun build --compile` binary (reads the same files embedded in the
57
+ * binary). System templates are flat, so resolution is by basename.
58
+ */
59
+ const liquidFs: FS = {
60
+ sep: "/",
61
+ dirname: () => ".",
62
+ resolve: (_dir, file, ext) => (file.endsWith(ext) ? file : `${file}${ext}`),
63
+ existsSync: (file) => promptAssetExists(systemRel(file)),
64
+ readFileSync: (file) => readPromptAsset(systemRel(file)),
65
+ exists: (file) => Promise.resolve(promptAssetExists(systemRel(file))),
66
+ readFile: (file) => Promise.resolve(readPromptAsset(systemRel(file))),
67
+ contains: () => Promise.resolve(true),
68
+ containsSync: () => true,
69
+ };
70
+
71
+ /**
72
+ * One engine instance per process. The custom `fs` lets templates compose
73
+ * via `{% render 'partial-name' %}` against `prompts/system/`. Variables
74
+ * stay lenient (unknown → empty string) — a template must degrade to
75
+ * readable prose when an optional var (e.g. a frontend without
76
+ * reactions) is absent.
77
+ */
78
+ const liquid = new Liquid({
79
+ fs: liquidFs,
80
+ extname: ".md",
81
+ // JS truthiness, not Shopify's: `""` and `0` are falsy. The legacy
82
+ // renderer treated empty-string vars as "absent", and callers rely
83
+ // on it (`truncated: truncated ? "yes" : undefined`-style flags).
84
+ jsTruthy: true,
85
+ });
86
+
87
+ /**
88
+ * Render a raw Liquid template string with `vars`. For one-off
89
+ * strings (tests, dynamic snippets); file templates should go through
90
+ * `loadSystemTemplate`, which caches the parse.
91
+ */
92
+ export function renderTemplate(template: string, vars: TemplateVars): string {
93
+ return liquid.parseAndRenderSync(template, vars);
94
+ }
95
+
96
+ // ── Loader ──────────────────────────────────────────────────────────────────
97
+
98
+ const parseCache = new Map<string, Template[]>();
99
+
100
+ /**
101
+ * Load a system template by name (e.g. `"contract-tool-only"`) and
102
+ * render it with `vars`. Throws if the file is missing — system
103
+ * templates ship with the package, so absence is a packaging bug, not
104
+ * a user-configuration state.
105
+ */
106
+ export function loadSystemTemplate(
107
+ name: string,
108
+ vars: TemplateVars = {},
109
+ ): string {
110
+ const file = `${name}.md`;
111
+ let parsed = parseCache.get(file);
112
+ if (parsed === undefined) {
113
+ // The filepath argument anchors relative {% render %} resolution
114
+ // (resolved against the prompt-asset seam, not the disk).
115
+ parsed = liquid.parse(readPromptAsset(`system/${file}`).trim(), file);
116
+ parseCache.set(file, parsed);
117
+ }
118
+ return liquid.renderSync(parsed, vars) as string;
119
+ }
120
+
121
+ /** Test seam: drop the parse cache (e.g. after writing fixture templates). */
122
+ export function clearTemplateCache(): void {
123
+ parseCache.clear();
124
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Workspace file listing for the system prompt's dynamic tail.
3
+ *
4
+ * Renders a compact tree of `~/.talon/workspace/` so the model knows
5
+ * what artifacts exist. Volatile by nature (file sizes change as logs
6
+ * grow), so it always belongs to the DYNAMIC prompt part — never the
7
+ * cacheable static prefix.
8
+ *
9
+ * Any directory with more than `COLLAPSE_THRESHOLD` rendered entries
10
+ * collapses to a single `name/ (N files)` summary line, so per-file
11
+ * sizes inside such a directory are never needed. The tree is built
12
+ * lazily: `statSync` is deferred to render time and only ever called
13
+ * for files that actually appear in the output. The eager
14
+ * implementation stat'd every file during the walk — on a workspace
15
+ * with tens of thousands of files (logs, build artifacts, media) that
16
+ * meant one blocking syscall per file on every session start, just to
17
+ * print a handful of summary lines.
18
+ */
19
+
20
+ import { readdirSync, statSync } from "node:fs";
21
+ import { resolve } from "node:path";
22
+
23
+ const COLLAPSE_THRESHOLD = 8;
24
+
25
+ // A node contributes `count` rendered lines to its parent (used to
26
+ // decide the collapse) and renders those lines lazily via `render()`.
27
+ type Node = { count: number; render: () => string[] };
28
+
29
+ function buildNode(dir: string, prefix: string): Node {
30
+ const children: Node[] = [];
31
+ try {
32
+ for (const e of readdirSync(dir, { withFileTypes: true })) {
33
+ if (
34
+ e.name.startsWith(".") ||
35
+ e.name === "node_modules" ||
36
+ e.name === "talon.log"
37
+ )
38
+ continue;
39
+ const full = resolve(dir, e.name);
40
+ if (e.isDirectory()) {
41
+ const sub = buildNode(full, `${prefix}${e.name}/`);
42
+ if (sub.count === 0) continue; // empty dir → omitted
43
+ if (sub.count <= COLLAPSE_THRESHOLD) {
44
+ children.push(sub); // expand inline
45
+ } else {
46
+ // Collapse to one summary line — never render (or stat) the
47
+ // files inside.
48
+ const line = `${prefix}${e.name}/ (${sub.count} files)`;
49
+ children.push({ count: 1, render: () => [line] });
50
+ }
51
+ } else {
52
+ children.push({
53
+ count: 1,
54
+ render: () => {
55
+ let sz = 0;
56
+ try {
57
+ sz = statSync(full).size;
58
+ } catch {
59
+ /* file vanished between readdir and stat — show 0B */
60
+ }
61
+ return [
62
+ `${prefix}${e.name} (${sz < 1024 ? sz + "B" : (sz / 1024).toFixed(0) + "KB"})`,
63
+ ];
64
+ },
65
+ });
66
+ }
67
+ }
68
+ } catch {
69
+ /* unreadable dir → treat as empty */
70
+ }
71
+ const count = children.reduce((n, c) => n + c.count, 0);
72
+ return { count, render: () => children.flatMap((c) => c.render()) };
73
+ }
74
+
75
+ /**
76
+ * Render the `## Current Workspace Contents` section, or an empty
77
+ * string when the workspace is missing or empty.
78
+ */
79
+ export function renderWorkspaceListing(workspaceDir: string): string {
80
+ try {
81
+ const files = buildNode(workspaceDir, "").render();
82
+ if (files.length === 0) return "";
83
+ return (
84
+ "## Current Workspace Contents\n\n" +
85
+ files.map((f) => ` ${f}`).join("\n")
86
+ );
87
+ } catch {
88
+ /* no workspace yet */
89
+ return "";
90
+ }
91
+ }
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Lua trigger runner — executes a trigger script inside a WASM-sandboxed
3
+ * Lua 5.4 VM (wasmoon), speaking the same stdout protocol bash/python/node
4
+ * triggers use (see core/background/triggers.ts).
5
+ *
6
+ * Why a separate process at all: the trigger supervisor's whole contract
7
+ * (kill/respawn, SIGTERM grace, log streaming, TALON_FIRE line scanning)
8
+ * is built around child processes. Lua therefore runs as `talon _lua-run
9
+ * <script>` — the same self-reinvocation shape MCP supervision uses
10
+ * (util/mcp-launcher.ts) — so it works identically for tsx source runs,
11
+ * npm installs, and bun-compiled binaries with no source tree on disk.
12
+ *
13
+ * Sandbox (verified empirically, not aspirational):
14
+ * - wasmoon is Lua 5.4 compiled to WASM under emscripten. The VM's
15
+ * filesystem is MEMFS: `io.open("/etc/hostname")` returns nil, and
16
+ * `os.getenv` sees only emscripten's stub environment — the host
17
+ * process env is invisible. No sockets, no subprocesses.
18
+ * - The full Lua stdlib (string/table/math/os.time/os.clock/io) works
19
+ * INSIDE the VM; io/os just can't reach the host.
20
+ * - The only host surface is the injected `talon` API below.
21
+ *
22
+ * Host API (global `talon` table):
23
+ * talon.fire(text) — emit `TALON_FIRE: <text>` on stdout (newlines in
24
+ * text collapsed to spaces); fires a mid-run wake-up.
25
+ * talon.log(text) — stderr line; lands in the trigger run-log as
26
+ * `[stderr] …`.
27
+ * talon.sleep(ms) — synchronous-looking sleep. Implemented as a JS
28
+ * promise the Lua thread suspends on via wasmoon's
29
+ * `:await()` (the engine yields the coroutine and
30
+ * resumes it when the promise settles).
31
+ *
32
+ * Fire-protocol forgery guard: ALL VM-visible stdout — print(), io.write,
33
+ * io.output():write, anything emscripten emits — funnels through
34
+ * console.log (emscripten's `out` channel). We patch BOTH console.log and
35
+ * process.stdout.write for the run: under node, console.log delegates to
36
+ * the stream's write method, but under bun (compiled binaries) console.log
37
+ * is native and bypasses it entirely — patching only the stream was
38
+ * verified to let a forged print() through in the compiled binary.
39
+ * Everything is line-buffered: any script-originated line starting with
40
+ * `TALON_FIRE:` is escaped with one leading space, which defeats the
41
+ * supervisor's startsWith() check while keeping the text readable in the
42
+ * run-log. Only talon.fire writes through the saved raw stream method, and
43
+ * it terminates any pending partial script line first so a trailing
44
+ * io.write fragment can't glue onto (and break) the protocol line. This is
45
+ * an anti-accident/anti-confusion guard at the stdout choke points —
46
+ * the host sandbox above is what provides actual isolation.
47
+ *
48
+ * Known stdio quirk (verified): `io.write("text")` WITHOUT a trailing
49
+ * newline sits in emscripten's internal TTY buffer and is never flushed —
50
+ * not at chunk end, not at engine close — so it's silently lost, exactly
51
+ * like unflushed C stdio. Scripts should end stdout writes with "\n"
52
+ * (print() always does).
53
+ *
54
+ * Exit codes: 0 — script completed; 1 — Lua error (message + traceback on
55
+ * stderr); 2 — usage error (no script path / unreadable file).
56
+ */
57
+
58
+ import { readFileSync } from "node:fs";
59
+ import { basename } from "node:path";
60
+
61
+ /** Hidden CLI subcommand that turns a Talon process into the Lua runner.
62
+ * Dispatched by src/index.ts and src/cli.ts before the app graph loads. */
63
+ export const LUA_RUN_SUBCOMMAND = "_lua-run";
64
+
65
+ /** Must match FIRE_PREFIX in core/background/triggers.ts. */
66
+ const FIRE_PREFIX = "TALON_FIRE:";
67
+
68
+ /**
69
+ * Run a Lua script file to completion inside a fresh wasmoon engine.
70
+ * Throws on Lua errors (message shaped `<file>:<line>: <msg>` plus
71
+ * traceback). Restores process.stdout.write before returning.
72
+ */
73
+ export async function runLuaScript(scriptPath: string): Promise<void> {
74
+ const source = readFileSync(scriptPath, "utf-8");
75
+
76
+ const originalWrite = process.stdout.write;
77
+ const originalConsoleLog = console.log;
78
+ const rawWrite = originalWrite.bind(process.stdout);
79
+
80
+ // ── Line-buffered, fire-escaping stdout for everything the VM emits ──
81
+ let pending = "";
82
+ const emitLine = (line: string): void => {
83
+ rawWrite(line.startsWith(FIRE_PREFIX) ? ` ${line}\n` : `${line}\n`);
84
+ };
85
+ const scriptWrite = (text: string): void => {
86
+ pending += text;
87
+ let nl: number;
88
+ while ((nl = pending.indexOf("\n")) !== -1) {
89
+ emitLine(pending.slice(0, nl));
90
+ pending = pending.slice(nl + 1);
91
+ }
92
+ };
93
+ const flushPending = (): void => {
94
+ if (pending.length === 0) return;
95
+ emitLine(pending);
96
+ pending = "";
97
+ };
98
+
99
+ // Both patches MUST be installed before wasmoon is imported: its
100
+ // emscripten glue captures `console.log.bind(console)` at module-eval
101
+ // time. Under node the binding still routes through the stream's write
102
+ // method at call time, but under bun (compiled binaries) console.log is
103
+ // native and a pre-patch binding writes straight to fd 1 — verified to
104
+ // let a forged print() through when the patch came after the import.
105
+ process.stdout.write = ((
106
+ chunk: string | Uint8Array,
107
+ encodingOrCb?: BufferEncoding | ((err?: Error | null) => void),
108
+ cb?: (err?: Error | null) => void,
109
+ ): boolean => {
110
+ scriptWrite(
111
+ typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf-8"),
112
+ );
113
+ const callback = typeof encodingOrCb === "function" ? encodingOrCb : cb;
114
+ callback?.();
115
+ return true;
116
+ }) as typeof process.stdout.write;
117
+
118
+ // Emscripten calls console.log with a single already-formatted string
119
+ // per line; joining covers stray multi-arg calls.
120
+ console.log = (...args: unknown[]): void => {
121
+ scriptWrite(`${args.map(String).join(" ")}\n`);
122
+ };
123
+
124
+ let lua: import("wasmoon").LuaEngine | undefined;
125
+ try {
126
+ // Lazy import: entrypoints and triggers.ts import this module just for
127
+ // LUA_RUN_SUBCOMMAND — they must not pay for the embedded WASM bundle.
128
+ const { LuaFactory } = await import("wasmoon");
129
+ const factory = new LuaFactory();
130
+ lua = await factory.createEngine();
131
+
132
+ // ── Host API ──
133
+ lua.global.set("__talon_fire", (text: unknown) => {
134
+ flushPending();
135
+ const single = String(text)
136
+ .replace(/[\r\n]+/g, " ")
137
+ .trim();
138
+ rawWrite(`${FIRE_PREFIX} ${single}\n`);
139
+ });
140
+ lua.global.set("__talon_log", (text: unknown) => {
141
+ process.stderr.write(`${String(text)}\n`);
142
+ });
143
+ lua.global.set("__talon_sleep", (ms: unknown) => {
144
+ const delay = Math.max(0, Number(ms) || 0);
145
+ return new Promise<void>((resolve) => setTimeout(resolve, delay));
146
+ });
147
+
148
+ // The sleep wrapper hides the promise plumbing: scripts just call
149
+ // talon.sleep(ms) and the Lua thread suspends until it resolves.
150
+ await lua.doString(`
151
+ talon = {
152
+ fire = function(text) __talon_fire(tostring(text)) end,
153
+ log = function(text) __talon_log(tostring(text)) end,
154
+ sleep = function(ms) __talon_sleep(ms):await() end,
155
+ }
156
+ `);
157
+
158
+ // Mount under the script's basename so Lua errors read
159
+ // `<name>.lua:<line>: <msg>` instead of `[string "…"]`.
160
+ const mounted = `/${basename(scriptPath)}`;
161
+ await factory.mountFile(mounted, source);
162
+ await lua.doFile(mounted);
163
+ } finally {
164
+ // Close the engine BEFORE restoring the patches: if any
165
+ // wasmoon/emscripten teardown ever flushes buffered VM stdout, it must
166
+ // still hit the sanitizer. (Empirically nothing flushes today — see
167
+ // header — but the safe order costs nothing.)
168
+ lua?.global.close();
169
+ console.log = originalConsoleLog;
170
+ process.stdout.write = originalWrite;
171
+ flushPending();
172
+ }
173
+ }
174
+
175
+ /**
176
+ * `_lua-run` entry: run argv[0] as a Lua script and exit with the
177
+ * protocol's exit code. Never resolves — mirrors runSupervisor() so
178
+ * entrypoints can `await` it and nothing after the dispatch ever runs.
179
+ */
180
+ export async function runLuaMain(argvTail: string[]): Promise<never> {
181
+ const scriptPath = argvTail[0];
182
+ if (!scriptPath) {
183
+ process.stderr.write("lua-runner: missing script path\n");
184
+ process.exit(2);
185
+ }
186
+
187
+ let code = 0;
188
+ try {
189
+ await runLuaScript(scriptPath);
190
+ } catch (err) {
191
+ process.stderr.write(
192
+ `${err instanceof Error ? err.message : String(err)}\n`,
193
+ );
194
+ code = 1;
195
+ }
196
+
197
+ // Don't process.exit() straight away: stdout/stderr to a pipe flush
198
+ // asynchronously and an immediate exit can drop the tail of the output
199
+ // (including the final TALON_FIRE line). exitCode is the fallback if a
200
+ // stream never runs its callback.
201
+ process.exitCode = code;
202
+ let remaining = 2;
203
+ const done = (): void => {
204
+ remaining -= 1;
205
+ if (remaining === 0) process.exit(code);
206
+ };
207
+ process.stdout.write("", done);
208
+ process.stderr.write("", done);
209
+ return new Promise<never>(() => {});
210
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Script runner — run-to-completion execution of saved scripts.
3
+ *
4
+ * Unlike triggers (supervised long-running watchers with a fire
5
+ * protocol), a script invocation is a plain subprocess: spawn the
6
+ * interpreter on the script with the caller's args, capture output,
7
+ * enforce a timeout, return the result to the calling turn. Local
8
+ * execution — no model involvement, no token cost.
9
+ *
10
+ * Security posture matches triggers and the agent's Bash tool: the
11
+ * bot account (its OS user, its workspace) is the boundary, not the
12
+ * script contents.
13
+ */
14
+
15
+ import { spawn } from "node:child_process";
16
+ import { dirs } from "../../util/paths.js";
17
+ import { log } from "../../util/log.js";
18
+ import { commandForLanguage } from "../background/triggers/index.js";
19
+ import type { Script } from "../../storage/script-store.js";
20
+
21
+ export const DEFAULT_SCRIPT_TIMEOUT_SECONDS = 60;
22
+ export const MAX_SCRIPT_TIMEOUT_SECONDS = 300;
23
+
24
+ /** Cap on captured stdout/stderr — keeps tool results context-friendly. */
25
+ const OUTPUT_CAP_CHARS = 16_000;
26
+
27
+ export type ScriptRunResult = {
28
+ exitCode: number | null;
29
+ stdout: string;
30
+ stderr: string;
31
+ timedOut: boolean;
32
+ durationMs: number;
33
+ };
34
+
35
+ export function validateScriptTimeout(seconds: number): string | null {
36
+ if (!Number.isFinite(seconds) || seconds <= 0)
37
+ return "Timeout must be a positive number";
38
+ if (seconds > MAX_SCRIPT_TIMEOUT_SECONDS)
39
+ return `Timeout exceeds max (${MAX_SCRIPT_TIMEOUT_SECONDS}s)`;
40
+ return null;
41
+ }
42
+
43
+ /**
44
+ * Run a script to completion. Resolves (never rejects) with the
45
+ * captured result; spawn failures surface as `exitCode: null` with
46
+ * the error message in stderr.
47
+ */
48
+ export function runScript(
49
+ script: Script,
50
+ args: readonly string[] = [],
51
+ timeoutSeconds = DEFAULT_SCRIPT_TIMEOUT_SECONDS,
52
+ ): Promise<ScriptRunResult> {
53
+ const command = commandForLanguage(script.language);
54
+ if (!command) {
55
+ return Promise.resolve({
56
+ exitCode: null,
57
+ stdout: "",
58
+ stderr: `No ${script.language} interpreter available on this host`,
59
+ timedOut: false,
60
+ durationMs: 0,
61
+ });
62
+ }
63
+
64
+ const t0 = Date.now();
65
+ return new Promise((resolvePromise) => {
66
+ let stdout = "";
67
+ let stderr = "";
68
+ let timedOut = false;
69
+ let settled = false;
70
+
71
+ const child = spawn(
72
+ command.cmd,
73
+ [...command.args, script.scriptPath, ...args],
74
+ {
75
+ cwd: dirs.workspace,
76
+ env: process.env,
77
+ stdio: ["ignore", "pipe", "pipe"],
78
+ windowsHide: true,
79
+ },
80
+ );
81
+
82
+ const timer = setTimeout(() => {
83
+ timedOut = true;
84
+ child.kill("SIGTERM");
85
+ // SIGKILL backstop for scripts that trap/ignore SIGTERM.
86
+ setTimeout(() => child.kill("SIGKILL"), 5_000).unref();
87
+ // Settle without waiting for 'close': a grandchild that
88
+ // inherited the stdio pipes (e.g. bash spawning `sleep`) keeps
89
+ // them open after the direct child dies, so 'close' may never
90
+ // fire. Whatever output was captured by now is the result.
91
+ setTimeout(() => settle(null), 1_500).unref();
92
+ }, timeoutSeconds * 1000);
93
+ timer.unref();
94
+
95
+ child.stdout.on("data", (chunk: Buffer) => {
96
+ if (stdout.length < OUTPUT_CAP_CHARS) stdout += chunk.toString("utf-8");
97
+ });
98
+ child.stderr.on("data", (chunk: Buffer) => {
99
+ if (stderr.length < OUTPUT_CAP_CHARS) stderr += chunk.toString("utf-8");
100
+ });
101
+
102
+ const settle = (exitCode: number | null, spawnError?: string) => {
103
+ if (settled) return;
104
+ settled = true;
105
+ clearTimeout(timer);
106
+ const durationMs = Date.now() - t0;
107
+ log(
108
+ "scripts",
109
+ `ran "${script.name}" (exit=${exitCode ?? "n/a"}${timedOut ? ", TIMED OUT" : ""}, ${durationMs}ms)`,
110
+ );
111
+ resolvePromise({
112
+ exitCode,
113
+ stdout: stdout.slice(0, OUTPUT_CAP_CHARS),
114
+ stderr: spawnError
115
+ ? `${spawnError}\n${stderr}`.slice(0, OUTPUT_CAP_CHARS)
116
+ : stderr.slice(0, OUTPUT_CAP_CHARS),
117
+ timedOut,
118
+ durationMs,
119
+ });
120
+ };
121
+
122
+ child.on("error", (err) => settle(null, err.message));
123
+ child.on("close", (code) => settle(code));
124
+ });
125
+ }
@@ -0,0 +1,110 @@
1
+ # Soul Kernel
2
+
3
+ Talon's compiled identity. Not a markdown prompt, not a JSON blob — a
4
+ **content-addressed Merkle DAG** of typed identity nodes, learned **mechanically**
5
+ from behavioral telemetry and projected into the system prompt by **selection**.
6
+
7
+ > The model is the brain. Talon is the body. The Soul is the persistent identity
8
+ > artifact that tells the brain how to be Talon.
9
+
10
+ ## The thesis: the model never writes its own soul
11
+
12
+ There are two jobs — _being the self_ (the reasoning model reads the soul and
13
+ acts) and _writing the self_ (turning experience into identity). If the reasoning
14
+ model does the second, it can rationalize, flatter itself, and confabulate a
15
+ coherent self-story that isn't true. So **writing the self is strictly
16
+ model-free**: continuous, deterministic, evidence-grounded. The reasoning model is
17
+ a _reader_ of the soul, never its author. That is the safety property, not just a
18
+ cost saving.
19
+
20
+ The only neural component anywhere is an optional **local embedder** (for
21
+ clustering/dedup/contradiction-distance) — a frozen fixed function, not an agent.
22
+ Everything else is counters, exponential decay, dot products, and selection.
23
+
24
+ ## The substrate
25
+
26
+ A node's identity is the sha-256 of its canonical content. The single most
27
+ important decision is the split between:
28
+
29
+ - **Content** (hashed, immutable) — a node's semantic identity: kind + verbatim
30
+ payload + referenced child hashes. A change of content is a new node.
31
+ - **State** (mutable, keyed by hash) — salience, evidence weight, activation
32
+ counts. Churns every tick; lives _outside_ the Merkle structure.
33
+
34
+ So reinforcing a value a thousand times never churns a single content hash.
35
+ Versioning, provenance, dedup, and partial recompilation fall out structurally;
36
+ learning and decay live in the state layer.
37
+
38
+ ## The five structures
39
+
40
+ | Structure | Node kind | What it is |
41
+ | --------- | --------------- | ------------------------------------------------------------------------------------------- |
42
+ | Spine | `spine` | append-only causal narrative — _why_ Talon became Talon |
43
+ | Lattice | `value` + edges | values as evidence clusters; tension/affinity edges self-organize via Hebbian co-activation |
44
+ | Reflexes | `reflex` | `trigger → guard → action` rules the harness _enforces_ |
45
+ | Holograph | `lens` | identity refracted per interlocutor (selection + reweighting) |
46
+ | Critic | (classifiers) | frozen feature-based gates over failure modes |
47
+
48
+ The atoms under all of it are `evidence` nodes — verbatim ground truth. The kernel
49
+ never paraphrases; it only ever quotes.
50
+
51
+ ## The model-free pipeline
52
+
53
+ ```
54
+ telemetry ──▶ signals ──▶ compiler ──▶ DAG (+ salience/Hebbian) ──▶ projector ──▶ runtime.md
55
+ (harness) (structured) (arithmetic) (content + state) (selection) (injected)
56
+ ```
57
+
58
+ - **Signals** (`signals.ts`) — structured harness events (reaction, engagement,
59
+ correction, reflex-fire). Interpretation already happened; no transcript reading.
60
+ - **Compiler** (`compiler.ts`) — routes each signal to pure update rules. Outcome
61
+ signals reinforce/penalize the values that were live in that turn's projection
62
+ and Hebbian-coactivate them; corrections store verbatim evidence + a Spine event.
63
+ - **Salience** (`salience.ts`) — lazy exponential decay, reinforcement, Hebbian
64
+ edges, predictive-coding delta rule. The whole "learning" is here, all arithmetic.
65
+ - **Projector** (`projector.ts`) — selects verbatim evidence, salience-ordered
66
+ under a token budget. Reflexes are never budgeted away. The soul cannot
67
+ hallucinate itself.
68
+ - **Critic** (`critic.ts`) — frozen classifiers for wall-of-text, sycophancy,
69
+ emoji-overload.
70
+ - **Kernel** (`kernel.ts`) — orchestrator: genesis → ingest → commit → project,
71
+ with a git-like commit chain and JSON persistence.
72
+
73
+ ## File map
74
+
75
+ ```
76
+ types.ts node payloads, edges, activation state, commits, config
77
+ hash.ts canonical serialization + content addressing + Merkle root
78
+ dag.ts the DAG store: integrity, dirty propagation, snapshot/restore
79
+ salience.ts decay / reinforce / coactivate / prediction-error (the math)
80
+ reflex.ts predicate registry + enforcer + the three seed reflexes
81
+ signals.ts structured telemetry types + emoji→valence
82
+ compiler.ts signal → arithmetic update routing (writing the self)
83
+ projector.ts DAG → runtime surface by selection (reading the self)
84
+ critic.ts frozen failure-mode classifiers
85
+ kernel.ts orchestrator: lifecycle, commits, persistence
86
+ index.ts public surface
87
+ ```
88
+
89
+ ## Status
90
+
91
+ Implemented and tested (150+ tests, model-free end to end):
92
+
93
+ - **Substrate** — content-addressed Merkle DAG, dirty-tracking, commits, persistence.
94
+ - **Learning** — salience decay/reinforce, Hebbian edges, predictive-coding updates.
95
+ - **Emergence** — value clustering (`crystallize`), self-reorganizing `consolidate`
96
+ ("dream") with adaptive thresholds + state migration, reflection into `theme`s.
97
+ - **Embedding** — our own `TalonEmbedder` (model-free) behind a pluggable seam.
98
+ - **Adaptation** — learned valence (meaning from outcomes), FSRS/DSR forgetting,
99
+ ADWIN drift → Spine epochs, emergent failure modes.
100
+ - **Recall** — Generative-Agents context retrieval, PageRank centrality, modern
101
+ Hopfield associative recall, VSA/HDC compositional episodic memory.
102
+ - **Refraction** — relational holograph lens compilation.
103
+ - **Surfaces** — projector (selection), delta stream, mechanical Critic.
104
+ - **Governance** — protected-node approval queue; runtime gate (off by default).
105
+
106
+ See `RESEARCH.md` for the literature behind each mechanism.
107
+
108
+ Remaining: **harness wiring** — telemetry taps (reactions/engagement/corrections →
109
+ signals), prompt injection of the projected surface, and live reflex enforcement.
110
+ This touches the live Talon runtime and is gated behind `TALON_SOUL_ENABLED`.