talon-agent 4.6.1 → 5.0.1

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 (310) hide show
  1. package/README.md +3 -3
  2. package/package.json +2 -1
  3. package/prompts/README.md +2 -2
  4. package/prompts/system/memory-recall.md +16 -0
  5. package/src/app.ts +22 -7
  6. package/src/backend/claude-sdk/factory.ts +21 -41
  7. package/src/backend/claude-sdk/handler.ts +28 -7
  8. package/src/backend/claude-sdk/host/in-process.ts +147 -0
  9. package/src/backend/claude-sdk/one-shot.ts +1 -1
  10. package/src/backend/claude-sdk/options.ts +7 -6
  11. package/src/backend/claude-sdk/stream.ts +30 -3
  12. package/src/backend/claude-sdk/warm.ts +1 -1
  13. package/src/backend/codex/constants.ts +1 -1
  14. package/src/backend/codex/factory.ts +2 -2
  15. package/src/backend/codex/handler/events.ts +1 -1
  16. package/src/backend/codex/handler/message.ts +26 -12
  17. package/src/backend/codex/handler/rollout-accounting.ts +1 -1
  18. package/src/backend/codex/init.ts +1 -1
  19. package/src/backend/codex/mcp-config.ts +2 -2
  20. package/src/backend/codex/one-shot.ts +1 -1
  21. package/src/backend/kilo/handler/message.ts +4 -1
  22. package/src/backend/openai-agents/constants.ts +1 -1
  23. package/src/backend/openai-agents/factory.ts +2 -2
  24. package/src/backend/openai-agents/handler/events.ts +1 -1
  25. package/src/backend/openai-agents/handler/message.ts +8 -4
  26. package/src/backend/openai-agents/init.ts +1 -1
  27. package/src/backend/openai-agents/mcp-pool.ts +2 -2
  28. package/src/backend/opencode/handler/message.ts +4 -1
  29. package/src/backend/remote-server/chat-turn.ts +31 -23
  30. package/src/backend/remote-server/events.ts +3 -3
  31. package/src/backend/remote-server/factory.ts +6 -3
  32. package/src/backend/remote-server/index.ts +1 -1
  33. package/src/backend/remote-server/mcp.ts +1 -1
  34. package/src/backend/remote-server/one-shot.ts +1 -1
  35. package/src/backend/remote-server/server-bindings.ts +1 -1
  36. package/src/backend/remote-server/turn.ts +1 -1
  37. package/src/backend/runtime/cache/cache-metrics.ts +126 -0
  38. package/src/backend/{shared → runtime/cache}/cache-telemetry.ts +22 -2
  39. package/src/backend/{shared → runtime}/index.ts +34 -25
  40. package/src/backend/{shared → runtime/prompt}/delivery-contract.ts +1 -1
  41. package/src/backend/{shared → runtime/prompt}/prompt-format.ts +46 -2
  42. package/src/backend/{shared → runtime/prompt}/system-prompt.ts +4 -4
  43. package/src/backend/{shared → runtime/turn}/delivered-text.ts +1 -1
  44. package/src/backend/{shared → runtime/turn}/delivery.ts +3 -3
  45. package/src/backend/{shared → runtime/turn}/handle-retry.ts +5 -5
  46. package/src/backend/{shared → runtime/turn}/handler-to-events.ts +24 -10
  47. package/src/backend/{shared → runtime/turn}/handler-types.ts +7 -1
  48. package/src/backend/{shared → runtime/turn}/model-retry.ts +2 -2
  49. package/src/backend/{shared → runtime/turn}/result-events.ts +2 -2
  50. package/src/backend/{shared → runtime/turn}/stream-state.ts +2 -2
  51. package/src/backend/{shared → runtime/turn}/turn-interrupt.ts +2 -2
  52. package/src/backend/{shared → runtime/turn}/turn-phases.ts +6 -6
  53. package/src/bootstrap.ts +6 -21
  54. package/src/cli.ts +6 -3
  55. package/src/core/agent-runtime/README.md +15 -0
  56. package/src/core/agent-runtime/agent-host.ts +387 -0
  57. package/src/core/agent-runtime/capabilities.ts +11 -0
  58. package/src/core/agent-runtime/contract-tests.ts +92 -1
  59. package/src/core/agent-runtime/events.ts +1 -1
  60. package/src/core/background/{isolated-agent.ts → cron/isolated-agent.ts} +3 -3
  61. package/src/core/background/{job-health.ts → cron/job-health.ts} +1 -1
  62. package/src/core/background/{job-oneshot.ts → cron/job-oneshot.ts} +5 -5
  63. package/src/core/background/{job-prompt.ts → cron/job-prompt.ts} +1 -1
  64. package/src/core/background/{cron.ts → cron/scheduler.ts} +6 -6
  65. package/src/core/background/{cron-spec.ts → cron/spec.ts} +1 -1
  66. package/src/core/background/{dream.ts → dream/index.ts} +10 -15
  67. package/src/core/background/{plan-alerts.ts → pulse/plan-alerts.ts} +3 -3
  68. package/src/core/background/{pulse.ts → pulse/pulse.ts} +8 -5
  69. package/src/core/background/triggers/command.ts +2 -2
  70. package/src/{util → core/config}/harden.ts +1 -1
  71. package/src/core/config/index.ts +16 -12
  72. package/src/{util → core/daemon}/boot-timer.ts +15 -1
  73. package/src/core/daemon/pidfile.ts +1 -1
  74. package/src/core/daemon/resource-sampler.ts +121 -0
  75. package/src/{util → core/daemon}/respawn.ts +1 -1
  76. package/src/core/doctor/index.ts +1 -1
  77. package/src/core/engine/dispatcher.ts +16 -0
  78. package/src/core/engine/gateway-actions/cron.ts +2 -2
  79. package/src/core/engine/gateway-actions/fetch-url.ts +1 -1
  80. package/src/core/engine/gateway-actions/index.ts +4 -1
  81. package/src/core/engine/gateway-actions/memory.ts +335 -0
  82. package/src/core/engine/gateway-actions/native/exec-background.ts +129 -0
  83. package/src/core/engine/gateway-actions/native/exec-remote.ts +85 -0
  84. package/src/core/engine/gateway-actions/native/exec.ts +182 -0
  85. package/src/core/engine/gateway-actions/native/index.ts +46 -0
  86. package/src/core/engine/gateway-actions/native/params.ts +46 -0
  87. package/src/core/engine/gateway-actions/native/read.ts +182 -0
  88. package/src/core/engine/gateway-actions/native/results.ts +34 -0
  89. package/src/core/engine/gateway-actions/native/search.ts +212 -0
  90. package/src/core/engine/gateway-actions/native/shell.ts +39 -0
  91. package/src/core/engine/gateway-actions/native/teleport.ts +55 -0
  92. package/src/core/engine/gateway-actions/native/write.ts +170 -0
  93. package/src/core/engine/gateway.ts +0 -6
  94. package/src/core/frontend-runtime/builtins.ts +2 -2
  95. package/src/{util → core/frontend-runtime}/chat-id.ts +30 -0
  96. package/src/core/mcp-hub/index.ts +1 -1
  97. package/src/core/mcp-hub/talon-server.ts +1 -1
  98. package/src/core/memory/import.ts +3 -2
  99. package/src/core/memory/taps.ts +199 -0
  100. package/src/core/memory/turn-retrieval.ts +222 -0
  101. package/src/core/plugin/mcp.ts +1 -1
  102. package/src/core/prompt/assemble.ts +2 -12
  103. package/src/core/prompt/index.ts +2 -2
  104. package/src/core/prompt/invalidation.ts +1 -1
  105. package/src/core/{scripting/lua-runner.ts → scripts/lua.ts} +1 -1
  106. package/src/core/tasks/index.ts +1 -1
  107. package/src/core/tools/{chat.ts → chat/chat.ts} +1 -1
  108. package/src/core/tools/{cross-send.ts → chat/cross-send.ts} +1 -1
  109. package/src/core/tools/{history.ts → chat/history.ts} +2 -2
  110. package/src/core/tools/{media.ts → chat/media.ts} +1 -1
  111. package/src/core/tools/{members.ts → chat/members.ts} +2 -2
  112. package/src/core/tools/{messaging.ts → chat/messaging.ts} +2 -2
  113. package/src/core/tools/{moderation.ts → chat/moderation.ts} +2 -2
  114. package/src/core/tools/{stickers.ts → chat/stickers.ts} +2 -2
  115. package/src/core/tools/{whatsapp.ts → chat/whatsapp.ts} +1 -1
  116. package/src/core/tools/content/memory.ts +128 -0
  117. package/src/core/tools/{web.ts → content/web.ts} +1 -1
  118. package/src/core/tools/index.ts +21 -19
  119. package/src/core/tools/{admin.ts → ops/admin.ts} +1 -1
  120. package/src/core/tools/{bridge.ts → ops/bridge.ts} +2 -2
  121. package/src/core/tools/{goals.ts → ops/goals.ts} +2 -2
  122. package/src/core/tools/{mesh.ts → ops/mesh.ts} +1 -1
  123. package/src/core/tools/{models.ts → ops/models.ts} +1 -1
  124. package/src/core/tools/{native.ts → ops/native.ts} +1 -1
  125. package/src/core/tools/{scheduling.ts → ops/scheduling.ts} +1 -1
  126. package/src/core/tools/{scripts.ts → ops/scripts.ts} +1 -1
  127. package/src/core/tools/{skills.ts → ops/skills.ts} +1 -1
  128. package/src/core/tools/{triggers.ts → ops/triggers.ts} +1 -1
  129. package/src/core/tools/types.ts +1 -0
  130. package/src/{util → core/vfs}/workspace.ts +2 -2
  131. package/src/core/weaver/turn-cpu.ts +32 -0
  132. package/src/core/weaver/weaver.ts +36 -0
  133. package/src/frontend/discord/admin.ts +1 -1
  134. package/src/frontend/discord/callbacks/components/backend-select.ts +1 -1
  135. package/src/frontend/discord/callbacks/components/effort.ts +1 -1
  136. package/src/frontend/discord/callbacks/components/index.ts +1 -1
  137. package/src/frontend/discord/callbacks/components/pulse.ts +1 -1
  138. package/src/frontend/discord/callbacks/components/settings.ts +2 -2
  139. package/src/frontend/discord/callbacks/modals.ts +4 -1
  140. package/src/frontend/discord/commands/admin.ts +2 -36
  141. package/src/frontend/discord/commands/definitions.ts +0 -11
  142. package/src/frontend/discord/commands/info.ts +1 -1
  143. package/src/frontend/discord/commands/interaction.ts +1 -1
  144. package/src/frontend/discord/commands/router.ts +0 -3
  145. package/src/frontend/discord/commands/session.ts +8 -2
  146. package/src/frontend/discord/commands/settings.ts +2 -2
  147. package/src/frontend/discord/handlers/messages.ts +1 -1
  148. package/src/frontend/discord/handlers/state.ts +1 -1
  149. package/src/frontend/discord/middleware.ts +2 -26
  150. package/src/frontend/discord/ready.ts +1 -1
  151. package/src/frontend/discord/render.ts +41 -259
  152. package/src/frontend/discord/runtime.ts +1 -2
  153. package/src/frontend/native/{auth.ts → bridge/auth.ts} +2 -2
  154. package/src/frontend/native/{discovery.ts → bridge/discovery.ts} +3 -3
  155. package/src/frontend/native/{routes → bridge/routes}/chats.ts +1 -1
  156. package/src/frontend/native/{routes → bridge/routes}/daemon.ts +1 -1
  157. package/src/frontend/native/{routes → bridge/routes}/host.ts +6 -3
  158. package/src/frontend/native/{routes → bridge/routes}/pre-auth.ts +1 -1
  159. package/src/frontend/native/{server.ts → bridge/server.ts} +3 -3
  160. package/src/frontend/native/{tls.ts → bridge/tls.ts} +2 -2
  161. package/src/frontend/native/{chat-lifecycle.ts → chats/chat-lifecycle.ts} +3 -3
  162. package/src/frontend/native/{chat-wire.ts → chats/chat-wire.ts} +4 -4
  163. package/src/frontend/native/{chats.ts → chats/chats.ts} +4 -4
  164. package/src/frontend/native/{empty-chat-sweep.ts → chats/empty-chat-sweep.ts} +4 -4
  165. package/src/frontend/native/{history.ts → chats/history.ts} +7 -7
  166. package/src/frontend/native/{reset.ts → chats/reset.ts} +7 -7
  167. package/src/frontend/native/index.ts +13 -10
  168. package/src/frontend/native/{media.ts → media/media.ts} +3 -3
  169. package/src/frontend/native/runtime.ts +1 -1
  170. package/src/frontend/native/{control.ts → surface/control.ts} +4 -4
  171. package/src/frontend/native/{extensions.ts → surface/extensions.ts} +12 -9
  172. package/src/frontend/native/{handlers.ts → surface/handlers.ts} +19 -14
  173. package/src/frontend/native/{logs.ts → surface/logs.ts} +2 -2
  174. package/src/frontend/native/{memory.ts → surface/memory.ts} +2 -2
  175. package/src/frontend/native/{models.ts → surface/models.ts} +17 -10
  176. package/src/frontend/native/{settings.ts → surface/settings.ts} +9 -9
  177. package/src/frontend/native/{status.ts → surface/status.ts} +3 -3
  178. package/src/frontend/native/{actions.ts → turn/actions.ts} +7 -4
  179. package/src/frontend/native/{context.ts → turn/context.ts} +8 -8
  180. package/src/frontend/native/{emit.ts → turn/emit.ts} +7 -7
  181. package/src/frontend/native/{queue.ts → turn/queue.ts} +3 -3
  182. package/src/frontend/native/{turn-meta.ts → turn/turn-meta.ts} +2 -2
  183. package/src/frontend/native/{turn.ts → turn/turn.ts} +9 -9
  184. package/src/frontend/{shared → presentation}/format.ts +4 -3
  185. package/src/frontend/{shared → presentation}/model-commands.ts +1 -1
  186. package/src/frontend/{shared → presentation}/reasoning-levels.ts +12 -0
  187. package/src/frontend/presentation/reports.ts +493 -0
  188. package/src/frontend/{shared → presentation}/session-status.ts +36 -9
  189. package/src/frontend/{shared → presentation}/status-context.ts +101 -2
  190. package/src/frontend/teams/commands.ts +2 -2
  191. package/src/frontend/teams/turn.ts +1 -1
  192. package/src/frontend/telegram/admin/background.ts +1 -1
  193. package/src/frontend/telegram/admin/health.ts +1 -1
  194. package/src/frontend/telegram/admin/sessions.ts +1 -1
  195. package/src/frontend/telegram/callbacks/effort.ts +2 -2
  196. package/src/frontend/telegram/callbacks/metrics.ts +1 -1
  197. package/src/frontend/telegram/callbacks/model/backend.ts +1 -1
  198. package/src/frontend/telegram/callbacks/model/views.ts +1 -1
  199. package/src/frontend/telegram/callbacks/pulse.ts +1 -1
  200. package/src/frontend/telegram/callbacks/query.ts +1 -1
  201. package/src/frontend/telegram/callbacks/settings.ts +3 -3
  202. package/src/frontend/telegram/commands/admin.ts +6 -41
  203. package/src/frontend/telegram/commands/index.ts +1 -1
  204. package/src/frontend/telegram/commands/info.ts +2 -2
  205. package/src/frontend/telegram/commands/session.ts +9 -3
  206. package/src/frontend/telegram/commands/settings.ts +5 -3
  207. package/src/frontend/telegram/handlers/messages.ts +0 -10
  208. package/src/frontend/telegram/handlers/state.ts +1 -1
  209. package/src/frontend/telegram/index.ts +1 -5
  210. package/src/frontend/telegram/middleware.ts +1 -15
  211. package/src/frontend/telegram/model-menu.ts +1 -1
  212. package/src/frontend/telegram/render/html.ts +30 -0
  213. package/src/frontend/telegram/{helpers → render}/menu.ts +46 -19
  214. package/src/frontend/telegram/render/reports.ts +182 -0
  215. package/src/frontend/terminal/builtins/context.ts +2 -2
  216. package/src/frontend/terminal/builtins/session.ts +1 -1
  217. package/src/frontend/terminal/builtins/status.ts +2 -2
  218. package/src/frontend/terminal/index.ts +2 -2
  219. package/src/frontend/terminal/renderer.ts +1 -1
  220. package/src/frontend/whatsapp/access.ts +2 -2
  221. package/src/frontend/whatsapp/actions/history.ts +2 -2
  222. package/src/frontend/whatsapp/actions/messaging.ts +2 -2
  223. package/src/frontend/whatsapp/actions/moderation.ts +1 -1
  224. package/src/frontend/whatsapp/actions/send.ts +1 -1
  225. package/src/frontend/whatsapp/commands.ts +12 -6
  226. package/src/frontend/whatsapp/{connection.ts → connection/connection.ts} +5 -5
  227. package/src/frontend/whatsapp/{pairing-service.ts → connection/pairing-service.ts} +3 -3
  228. package/src/frontend/whatsapp/{wa-logger.ts → connection/wa-logger.ts} +2 -2
  229. package/src/frontend/whatsapp/index.ts +4 -4
  230. package/src/frontend/whatsapp/{inbound.ts → messages/inbound.ts} +15 -15
  231. package/src/frontend/whatsapp/{media-store.ts → messages/media-store.ts} +4 -4
  232. package/src/frontend/whatsapp/{message-store.ts → messages/message-store.ts} +1 -1
  233. package/src/frontend/whatsapp/{turn-recovery.ts → messages/turn-recovery.ts} +4 -4
  234. package/src/frontend/whatsapp/registry.ts +2 -2
  235. package/src/frontend/whatsapp/runtime.ts +1 -1
  236. package/src/index.ts +6 -3
  237. package/src/storage/db.ts +6 -1
  238. package/src/storage/memory.ts +59 -8
  239. package/src/storage/metrics.ts +38 -0
  240. package/src/storage/repositories/sessions-repo.ts +6 -0
  241. package/src/storage/session-record.ts +13 -2
  242. package/src/storage/sessions.ts +12 -0
  243. package/src/storage/sql/db.sql +5 -0
  244. package/src/storage/sql/schema.sql +4 -0
  245. package/src/storage/sql/sessions.sql +3 -3
  246. package/src/storage/sql/statements.generated.ts +10 -3
  247. package/src/storage/sql/turn-meta.sql +1 -1
  248. package/src/util/log.ts +1 -1
  249. package/src/util/paths.ts +0 -2
  250. package/src/util/runtime.ts +1 -1
  251. package/src/core/engine/gateway-actions/native.ts +0 -1035
  252. package/src/core/soul/README.md +0 -110
  253. package/src/core/soul/RESEARCH.md +0 -98
  254. package/src/core/soul/associative.ts +0 -98
  255. package/src/core/soul/centrality.ts +0 -98
  256. package/src/core/soul/cluster.ts +0 -83
  257. package/src/core/soul/compiler.ts +0 -207
  258. package/src/core/soul/consolidate.ts +0 -179
  259. package/src/core/soul/critic.ts +0 -162
  260. package/src/core/soul/dag.ts +0 -265
  261. package/src/core/soul/delta.ts +0 -123
  262. package/src/core/soul/drift.ts +0 -99
  263. package/src/core/soul/embedder.ts +0 -129
  264. package/src/core/soul/emergent-critic.ts +0 -96
  265. package/src/core/soul/forgetting.ts +0 -131
  266. package/src/core/soul/governance.ts +0 -93
  267. package/src/core/soul/hash.ts +0 -97
  268. package/src/core/soul/hdc.ts +0 -154
  269. package/src/core/soul/kernel.ts +0 -540
  270. package/src/core/soul/lattice.ts +0 -103
  271. package/src/core/soul/lens.ts +0 -110
  272. package/src/core/soul/projector.ts +0 -240
  273. package/src/core/soul/reflect.ts +0 -170
  274. package/src/core/soul/reflex.ts +0 -164
  275. package/src/core/soul/retrieve.ts +0 -146
  276. package/src/core/soul/salience.ts +0 -146
  277. package/src/core/soul/service.ts +0 -204
  278. package/src/core/soul/settings.ts +0 -47
  279. package/src/core/soul/signals.ts +0 -117
  280. package/src/core/soul/talon-embedder.ts +0 -80
  281. package/src/core/soul/taps.ts +0 -199
  282. package/src/core/soul/types.ts +0 -298
  283. package/src/core/soul/valence.ts +0 -83
  284. package/src/frontend/telegram/helpers/diagnostics.ts +0 -428
  285. package/src/frontend/telegram/helpers/format.ts +0 -42
  286. package/src/frontend/telegram/helpers/index.ts +0 -13
  287. package/src/util/cleanup-registry.ts +0 -36
  288. /package/src/backend/{shared → runtime}/frontends.ts +0 -0
  289. /package/src/backend/{shared → runtime}/metrics.ts +0 -0
  290. /package/src/backend/{shared → runtime}/sleep.ts +0 -0
  291. /package/src/backend/{shared → runtime/turn}/flow-violation.ts +0 -0
  292. /package/src/backend/{shared → runtime}/usage.ts +0 -0
  293. /package/src/{util/mcp-launcher.ts → core/mcp-hub/launcher.ts} +0 -0
  294. /package/src/{util → core/tools/content}/web-content.ts +0 -0
  295. /package/src/core/tools/{mcp-env.ts → ops/mcp-env.ts} +0 -0
  296. /package/src/{util → core/weaver}/session-name.ts +0 -0
  297. /package/src/frontend/native/{routes → bridge/routes}/index.ts +0 -0
  298. /package/src/frontend/native/{routes → bridge/routes}/memory.ts +0 -0
  299. /package/src/frontend/native/{routes → bridge/routes}/mesh.ts +0 -0
  300. /package/src/frontend/native/{routes → bridge/routes}/models.ts +0 -0
  301. /package/src/frontend/native/{routes → bridge/routes}/params.ts +0 -0
  302. /package/src/frontend/native/{routes → bridge/routes}/table.ts +0 -0
  303. /package/src/frontend/native/{tool-result.ts → turn/tool-result.ts} +0 -0
  304. /package/src/frontend/{shared → presentation}/access.ts +0 -0
  305. /package/src/frontend/{shared → presentation}/plan-usage-report.ts +0 -0
  306. /package/src/frontend/whatsapp/{auth-state.ts → connection/auth-state.ts} +0 -0
  307. /package/src/frontend/whatsapp/{identity.ts → connection/identity.ts} +0 -0
  308. /package/src/frontend/whatsapp/{pairing-lock.ts → connection/pairing-lock.ts} +0 -0
  309. /package/src/frontend/whatsapp/{pairing.ts → connection/pairing.ts} +0 -0
  310. /package/src/frontend/whatsapp/{pins.ts → messages/pins.ts} +0 -0
@@ -11,7 +11,7 @@
11
11
  import type { WASocket } from "baileys";
12
12
  import type { TalonConfig } from "../../core/config/index.js";
13
13
  import type { Gateway } from "../../core/engine/gateway.js";
14
- import { bareId } from "./identity.js";
14
+ import { bareId } from "./connection/identity.js";
15
15
 
16
16
  type WhatsAppSettings = {
17
17
  allowedJids: string[];
package/src/index.ts CHANGED
@@ -3,13 +3,16 @@
3
3
  *
4
4
  * Dispatches the hidden `_mcp-launch` (MCP supervisor) and `_lua-run`
5
5
  * (WASM Lua trigger runner) subcommands BEFORE the app graph loads —
6
- * both are Talon re-invoking itself (see util/mcp-launcher.ts). The
6
+ * both are Talon re-invoking itself (see core/mcp-hub/launcher.ts). The
7
7
  * dynamic import keeps these helper processes light: they evaluate this
8
8
  * shim and their own module, never the backends/frontends/plugins.
9
9
  */
10
10
 
11
- import { MCP_LAUNCH_SUBCOMMAND, runSupervisor } from "./util/mcp-launcher.js";
12
- import { LUA_RUN_SUBCOMMAND, runLuaMain } from "./core/scripting/lua-runner.js";
11
+ import {
12
+ MCP_LAUNCH_SUBCOMMAND,
13
+ runSupervisor,
14
+ } from "./core/mcp-hub/launcher.js";
15
+ import { LUA_RUN_SUBCOMMAND, runLuaMain } from "./core/scripts/lua.js";
13
16
 
14
17
  if (process.argv[2] === MCP_LAUNCH_SUBCOMMAND) {
15
18
  await runSupervisor(process.argv.slice(3));
package/src/storage/db.ts CHANGED
@@ -69,7 +69,7 @@ let db: SqlDatabase | null = null;
69
69
  * Column reconciliation runs first: `ALTER TABLE … ADD COLUMN` has no
70
70
  * IF NOT EXISTS form, so columns added to already-shipped tables
71
71
  * (media_index.content_hash, sessions.metrics,
72
- * history_messages.attachments) are ensured by attempting the ALTER and
72
+ * history_messages.attachments, sessions.last_turn_ended_at) are ensured by attempting the ALTER and
73
73
  * swallowing the two expected failures — "duplicate column name"
74
74
  * (column already there) and "no such table" (fresh database; the
75
75
  * CREATE TABLE in schema.sql includes the column).
@@ -100,6 +100,11 @@ function ensureSchema(database: SqlDatabase): void {
100
100
  } catch {
101
101
  /* duplicate column or no such table — both mean nothing to do */
102
102
  }
103
+ try {
104
+ database.exec(dbSql.addSessionsLastTurnEndedAtColumn);
105
+ } catch {
106
+ /* duplicate column or no such table — both mean nothing to do */
107
+ }
103
108
  database.exec("BEGIN");
104
109
  try {
105
110
  database.exec(SCHEMA);
@@ -206,6 +206,25 @@ function similarityQuery(text: string): string {
206
206
  .join(" OR ");
207
207
  }
208
208
 
209
+ /**
210
+ * The near-duplicate probe, in one place: live rows of the same
211
+ * kind + subject whose text best matches `text` (bm25 order), with
212
+ * `excludeId` left out. `assertMemory` excludes the row it just wrote;
213
+ * a caller probing *before* writing has nothing to exclude and passes
214
+ * `0`, which is not a valid rowid.
215
+ */
216
+ function similarRows(
217
+ kind: MemoryKind,
218
+ subject: string,
219
+ text: string,
220
+ excludeId: number,
221
+ limit: number,
222
+ ): MemoryRow[] {
223
+ const match = similarityQuery(text);
224
+ if (!match) return [];
225
+ return repo.similar(match, kind, subject, excludeId, limit);
226
+ }
227
+
209
228
  /**
210
229
  * Load a row that a mutation is about to change, or explain why it
211
230
  * can't. Only a live row is mutable: a dropped one is in the graveyard
@@ -280,10 +299,13 @@ export function assertMemory(input: MemoryInput): {
280
299
  const row = normalize(input);
281
300
  return inTransaction(() => {
282
301
  const id = insertWithHistory(row, "assert");
283
- const match = similarityQuery(row.text);
284
- const similar = match
285
- ? repo.similar(match, row.kind, row.subject, id, SIMILAR_LIMIT)
286
- : [];
302
+ const similar = similarRows(
303
+ row.kind,
304
+ row.subject,
305
+ row.text,
306
+ id,
307
+ SIMILAR_LIMIT,
308
+ );
287
309
  return { id, similar };
288
310
  });
289
311
  }
@@ -478,14 +500,43 @@ export function listMemories(opts: MemoryListOptions = {}): MemoryRow[] {
478
500
  * input is quoted into a literal FTS5 expression by the shared
479
501
  * `ftsQuote` core, so operators and punctuation in a query are matched
480
502
  * as text rather than parsed as syntax.
503
+ *
504
+ * `match` picks how the query's terms combine:
505
+ *
506
+ * - `"all"` (default) — every term must appear. Right for a search
507
+ * someone typed on purpose (`talon memory search`, `/memory`).
508
+ * - `"any"` — terms are OR-ed and bm25 ranks, the same expression
509
+ * near-duplicate detection already uses. Right for a query that is
510
+ * a whole sentence nobody wrote as a query — a chat turn's inbound
511
+ * message (`core/memory/turn-retrieval.ts`), where AND-ing every
512
+ * word means matching nothing.
481
513
  */
482
514
  export function searchMemories(
483
515
  query: string,
484
- opts: { kind?: MemoryKind; limit?: number } = {},
516
+ opts: { kind?: MemoryKind; limit?: number; match?: "all" | "any" } = {},
485
517
  ): MemoryRow[] {
486
- const match = ftsQuote(query);
487
- if (!match) return [];
488
- return repo.searchFts(match, opts.kind, opts.limit ?? DEFAULT_SEARCH_LIMIT);
518
+ const expr = opts.match === "any" ? similarityQuery(query) : ftsQuote(query);
519
+ if (!expr) return [];
520
+ return repo.searchFts(expr, opts.kind, opts.limit ?? DEFAULT_SEARCH_LIMIT);
521
+ }
522
+
523
+ /**
524
+ * The near-duplicate candidates a claim *would* report if it were
525
+ * asserted now — the same FTS probe `assertMemory` runs, without
526
+ * writing anything.
527
+ *
528
+ * This is what lets the write path offer supersede-instead-of-append
529
+ * before a second row exists (plan §3.2): `remember` probes first, and
530
+ * only inserts once the caller has either picked a row to supersede or
531
+ * said the claim really is separate.
532
+ */
533
+ export function findSimilarMemories(
534
+ kind: MemoryKind,
535
+ subject: string,
536
+ text: string,
537
+ limit: number = SIMILAR_LIMIT,
538
+ ): MemoryRow[] {
539
+ return similarRows(kind, subject, text, 0, limit);
489
540
  }
490
541
 
491
542
  /** The audit trail for one row, oldest entry first. */
@@ -15,6 +15,7 @@ import { emptyPhases, TURN_PHASES, type TurnPhase } from "./session-record.js";
15
15
  const PHASE_HISTOGRAM: Record<TurnPhase, string> = {
16
16
  queueWait: "turn.queue_wait_ms",
17
17
  warpResolve: "turn.warp_resolve_ms",
18
+ memory: "turn.memory_ms",
18
19
  firstToken: "turn.first_token_ms",
19
20
  stream: "turn.stream_ms",
20
21
  delivery: "turn.delivery_ms",
@@ -30,6 +31,42 @@ const legacyCounters = new Map<string, number>();
30
31
  */
31
32
  const processHistograms = new Map<string, MetricsLatencyAgg>();
32
33
 
34
+ /**
35
+ * A chat's last cross-turn cache verdict — did the turn's FIRST request
36
+ * read the previous turn's prefix (`hit`), pay to re-write it (`miss`), or
37
+ * was nothing cacheable at all (`none`)? Mirrors `CrossTurnVerdict` in
38
+ * backend/runtime/cache/cache-telemetry.ts, which owns the classification; the
39
+ * union is restated here because `/status` renders it and frontends may
40
+ * not import backend/ (.dependency-cruiser.cjs: frontend-not-to-backend).
41
+ */
42
+ export type CacheVerdict = "hit" | "miss" | "none";
43
+
44
+ /**
45
+ * Per-chat last verdict. Process-local and deliberately unpersisted: it
46
+ * describes the live prefix, which dies with the process anyway, and
47
+ * `/status` is the only reader. Bounded like the per-chat maps in
48
+ * cache-telemetry.ts, insertion-ordered eviction.
49
+ */
50
+ const MAX_TRACKED_CHATS = 256;
51
+ const lastCacheVerdicts = new Map<string, CacheVerdict>();
52
+
53
+ /** Record the chat's newest cross-turn cache verdict. */
54
+ export function noteCacheVerdict(chatId: string, verdict: CacheVerdict): void {
55
+ if (
56
+ lastCacheVerdicts.size >= MAX_TRACKED_CHATS &&
57
+ !lastCacheVerdicts.has(chatId)
58
+ ) {
59
+ const oldest = lastCacheVerdicts.keys().next().value;
60
+ if (oldest !== undefined) lastCacheVerdicts.delete(oldest);
61
+ }
62
+ lastCacheVerdicts.set(chatId, verdict);
63
+ }
64
+
65
+ /** The chat's last cross-turn verdict, or undefined if none was recorded. */
66
+ export function getCacheVerdict(chatId: string): CacheVerdict | undefined {
67
+ return lastCacheVerdicts.get(chatId);
68
+ }
69
+
33
70
  export type MetricsSnapshot = {
34
71
  counters: Record<string, number>;
35
72
  histograms: Record<
@@ -202,5 +239,6 @@ export function getTodayMetrics(): MetricsSnapshot {
202
239
  export function resetMetrics(): void {
203
240
  legacyCounters.clear();
204
241
  processHistograms.clear();
242
+ lastCacheVerdicts.clear();
205
243
  resetAllSessionMetrics();
206
244
  }
@@ -42,6 +42,7 @@ type Row = {
42
42
  total_response_ms: number;
43
43
  last_response_ms: number;
44
44
  fastest_response_ms: number | null;
45
+ last_turn_ended_at: number | null;
45
46
  metrics: string | null;
46
47
  };
47
48
 
@@ -81,6 +82,7 @@ function rowToSession(row: Row): SessionState {
81
82
  lastBotMessageId: row.last_bot_message_id ?? undefined,
82
83
  sessionName: row.session_name ?? undefined,
83
84
  lastModel: row.last_model ?? undefined,
85
+ lastTurnEndedAt: row.last_turn_ended_at ?? undefined,
84
86
  };
85
87
  }
86
88
 
@@ -120,6 +122,10 @@ export function upsert(chatId: string, session: SessionState): void {
120
122
  num(usage?.totalResponseMs),
121
123
  num(usage?.lastResponseMs),
122
124
  typeof fastest === "number" && Number.isFinite(fastest) ? fastest : null,
125
+ typeof session.lastTurnEndedAt === "number" &&
126
+ Number.isFinite(session.lastTurnEndedAt)
127
+ ? session.lastTurnEndedAt
128
+ : null,
123
129
  JSON.stringify(session.metrics ?? emptyMetrics()),
124
130
  );
125
131
  }
@@ -56,12 +56,14 @@ export type MetricsLatencyAgg = {
56
56
 
57
57
  /**
58
58
  * Where a turn's wall-clock went, as seen from the Weaver: waiting in the
59
- * chat's FIFO, resolving the warp, until the backend's first event, the
60
- * whole stream, and the slice of it spent inside frontend delivery.
59
+ * chat's FIFO, resolving the warp, retrieving this turn's memory, until
60
+ * the backend's first event, the whole stream, and the slice of it spent
61
+ * inside frontend delivery.
61
62
  */
62
63
  export const TURN_PHASES = [
63
64
  "queueWait",
64
65
  "warpResolve",
66
+ "memory",
65
67
  "firstToken",
66
68
  "stream",
67
69
  "delivery",
@@ -104,6 +106,14 @@ export type SessionState = {
104
106
  sessionName?: string;
105
107
  /** Model used for this session's cost tracking. */
106
108
  lastModel?: string;
109
+ /**
110
+ * When the last turn of this chat finished (ms epoch), or undefined when
111
+ * no turn has completed yet. Distinct from `lastActive`, which any
112
+ * session write moves: this one only ever means "the transcript stopped
113
+ * growing here", which is what the prompt cache's TTL runs against —
114
+ * warm iff `now - lastTurnEndedAt < ttl` (docs/cache-economics.md).
115
+ */
116
+ lastTurnEndedAt?: number;
107
117
  };
108
118
 
109
119
  export const emptyUsage = (): SessionUsage => ({
@@ -146,6 +156,7 @@ export const emptyLatency = (): MetricsLatencyAgg => ({
146
156
  export const emptyPhases = (): MetricsPhaseAggs => ({
147
157
  queueWait: emptyLatency(),
148
158
  warpResolve: emptyLatency(),
159
+ memory: emptyLatency(),
149
160
  firstToken: emptyLatency(),
150
161
  stream: emptyLatency(),
151
162
  delivery: emptyLatency(),
@@ -480,6 +480,10 @@ export function recordUsage(
480
480
  session.usage.estimatedCostUsd += turn.costUsd;
481
481
  }
482
482
  if (turn.model) session.lastModel = turn.model;
483
+ // A turn just ended — this is the timestamp the prompt cache ages
484
+ // against (docs/cache-economics.md). Set on the one seam every backend
485
+ // funnels a finished turn through, failed-but-billed turns included.
486
+ session.lastTurnEndedAt = Date.now();
483
487
  if (turn.durationMs && turn.durationMs > 0) {
484
488
  session.usage.totalResponseMs =
485
489
  (session.usage.totalResponseMs || 0) + turn.durationMs;
@@ -580,6 +584,12 @@ export type SessionInfo = {
580
584
  lastModel?: string;
581
585
  /** True when a turn is currently streaming and `usage` includes its live so-far counts. */
582
586
  turnInProgress?: boolean;
587
+ /**
588
+ * When this chat's last turn finished (ms epoch), or undefined when none
589
+ * has. `/status` reports the idle gap from it; `lastActive` can't stand in
590
+ * because any session write moves that.
591
+ */
592
+ lastTurnEndedAt?: number;
583
593
  };
584
594
 
585
595
  export function getSessionInfo(chatId: string): SessionInfo {
@@ -594,6 +604,7 @@ export function getSessionInfo(chatId: string): SessionInfo {
594
604
  sessionName: session?.sessionName,
595
605
  lastModel: session?.lastModel,
596
606
  turnInProgress: liveTurns.has(chatId),
607
+ lastTurnEndedAt: session?.lastTurnEndedAt,
597
608
  };
598
609
  }
599
610
 
@@ -615,6 +626,7 @@ export function getAllSessions(): Array<{ chatId: string; info: SessionInfo }> {
615
626
  sessionName: session.sessionName,
616
627
  lastModel: session.lastModel,
617
628
  turnInProgress: liveTurns.has(chatId),
629
+ lastTurnEndedAt: session.lastTurnEndedAt,
618
630
  },
619
631
  }));
620
632
  }
@@ -25,3 +25,8 @@ ALTER TABLE sessions ADD COLUMN metrics TEXT NOT NULL DEFAULT '{"lifetime":{"cou
25
25
  -- carry more than one attachment. Fresh databases get the column via
26
26
  -- schema.sql.
27
27
  ALTER TABLE history_messages ADD COLUMN attachments TEXT
28
+
29
+ -- name: addSessionsLastTurnEndedAtColumn
30
+ -- Column reconciliation for databases that shipped before the cache-age
31
+ -- signal existed. Fresh databases get the column via schema.sql.
32
+ ALTER TABLE sessions ADD COLUMN last_turn_ended_at INTEGER
@@ -161,6 +161,10 @@ CREATE TABLE IF NOT EXISTS sessions (
161
161
  total_response_ms REAL NOT NULL DEFAULT 0,
162
162
  last_response_ms REAL NOT NULL DEFAULT 0,
163
163
  fastest_response_ms REAL,
164
+ -- When the chat's last turn finished; NULL until one has. Read as the
165
+ -- prompt cache's age signal (docs/cache-economics.md), which is why it
166
+ -- is separate from last_active (moved by any session write).
167
+ last_turn_ended_at INTEGER,
164
168
  metrics TEXT NOT NULL DEFAULT '{"lifetime":{"counters":{"queries":0,"toolCalls":0,"turnsWithTools":0,"apiCalls":0,"inputTokens":0,"outputTokens":0,"cacheReadTokens":0,"cacheWriteTokens":0,"failedTurns":0,"flowViolationRetries":0,"flowViolationCapExhausted":0,"trailingTextDropped":0},"latency":{"count":0,"sumMs":0,"minMs":null,"maxMs":0},"toolCallsByName":{},"backend":{},"cacheHitPercent":{"count":0,"sumMs":0,"minMs":null,"maxMs":0},"toolCallsPerTurn":{"count":0,"sumMs":0,"minMs":null,"maxMs":0},"apiCallsPerTurn":{"count":0,"sumMs":0,"minMs":null,"maxMs":0}},"buckets":{}}'
165
169
  );
166
170
 
@@ -7,15 +7,15 @@ INSERT OR REPLACE INTO sessions
7
7
  created_at, last_bot_message_id, total_input_tokens, total_output_tokens,
8
8
  total_cache_read, total_cache_write, last_prompt_tokens, context_tokens,
9
9
  context_window, num_api_calls, estimated_cost_usd, total_response_ms,
10
- last_response_ms, fastest_response_ms, metrics)
11
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
10
+ last_response_ms, fastest_response_ms, last_turn_ended_at, metrics)
11
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
12
12
 
13
13
  -- name: all
14
14
  SELECT chat_id, session_id, session_name, last_model, turns, last_active,
15
15
  created_at, last_bot_message_id, total_input_tokens, total_output_tokens,
16
16
  total_cache_read, total_cache_write, last_prompt_tokens, context_tokens,
17
17
  context_window, num_api_calls, estimated_cost_usd, total_response_ms,
18
- last_response_ms, fastest_response_ms, metrics
18
+ last_response_ms, fastest_response_ms, last_turn_ended_at, metrics
19
19
  FROM sessions
20
20
 
21
21
  -- name: remove
@@ -166,6 +166,10 @@ CREATE TABLE IF NOT EXISTS sessions (
166
166
  total_response_ms REAL NOT NULL DEFAULT 0,
167
167
  last_response_ms REAL NOT NULL DEFAULT 0,
168
168
  fastest_response_ms REAL,
169
+ -- When the chat's last turn finished; NULL until one has. Read as the
170
+ -- prompt cache's age signal (docs/cache-economics.md), which is why it
171
+ -- is separate from last_active (moved by any session write).
172
+ last_turn_ended_at INTEGER,
169
173
  metrics TEXT NOT NULL DEFAULT '{"lifetime":{"counters":{"queries":0,"toolCalls":0,"turnsWithTools":0,"apiCalls":0,"inputTokens":0,"outputTokens":0,"cacheReadTokens":0,"cacheWriteTokens":0,"failedTurns":0,"flowViolationRetries":0,"flowViolationCapExhausted":0,"trailingTextDropped":0},"latency":{"count":0,"sumMs":0,"minMs":null,"maxMs":0},"toolCallsByName":{},"backend":{},"cacheHitPercent":{"count":0,"sumMs":0,"minMs":null,"maxMs":0},"toolCallsPerTurn":{"count":0,"sumMs":0,"minMs":null,"maxMs":0},"apiCallsPerTurn":{"count":0,"sumMs":0,"minMs":null,"maxMs":0}},"buckets":{}}'
170
174
  );
171
175
 
@@ -414,6 +418,9 @@ ALTER TABLE sessions ADD COLUMN metrics TEXT NOT NULL DEFAULT '{"lifetime":{"cou
414
418
  -- carry more than one attachment. Fresh databases get the column via
415
419
  -- schema.sql.
416
420
  ALTER TABLE history_messages ADD COLUMN attachments TEXT`,
421
+ addSessionsLastTurnEndedAtColumn: `-- Column reconciliation for databases that shipped before the cache-age
422
+ -- signal existed. Fresh databases get the column via schema.sql.
423
+ ALTER TABLE sessions ADD COLUMN last_turn_ended_at INTEGER`,
417
424
  } as const;
418
425
 
419
426
  export const goalsSql = {
@@ -657,13 +664,13 @@ export const sessionsSql = {
657
664
  created_at, last_bot_message_id, total_input_tokens, total_output_tokens,
658
665
  total_cache_read, total_cache_write, last_prompt_tokens, context_tokens,
659
666
  context_window, num_api_calls, estimated_cost_usd, total_response_ms,
660
- last_response_ms, fastest_response_ms, metrics)
661
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
667
+ last_response_ms, fastest_response_ms, last_turn_ended_at, metrics)
668
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
662
669
  all: `SELECT chat_id, session_id, session_name, last_model, turns, last_active,
663
670
  created_at, last_bot_message_id, total_input_tokens, total_output_tokens,
664
671
  total_cache_read, total_cache_write, last_prompt_tokens, context_tokens,
665
672
  context_window, num_api_calls, estimated_cost_usd, total_response_ms,
666
- last_response_ms, fastest_response_ms, metrics
673
+ last_response_ms, fastest_response_ms, last_turn_ended_at, metrics
667
674
  FROM sessions`,
668
675
  remove: `DELETE FROM sessions WHERE chat_id = ?`,
669
676
  } as const;
@@ -1,4 +1,4 @@
1
- -- Statements for the turn_meta table (see frontend/native/turn-meta.ts).
1
+ -- Statements for the turn_meta table (see frontend/native/turn/turn-meta.ts).
2
2
 
3
3
  -- name: get
4
4
  SELECT meta FROM turn_meta WHERE chat_id = ? AND msg_id = ?
package/src/util/log.ts CHANGED
@@ -62,7 +62,7 @@ export type LogComponent =
62
62
  | "mempalace"
63
63
  | "mem0"
64
64
  | "playwright"
65
- | "soul"
65
+ | "memory"
66
66
  | "stickers"
67
67
  | "backend-controller";
68
68
 
package/src/util/paths.ts CHANGED
@@ -116,8 +116,6 @@ export const files = {
116
116
  triggers: resolve(TALON_ROOT, "data", "triggers.json"),
117
117
  /** Legacy JSON native turn meta (imported into talon.db on first boot) */
118
118
  nativeTurnMeta: resolve(TALON_ROOT, "data", "native-turn-meta.json"),
119
- /** Soul kernel state: ~/.talon/data/soul.json (overridable via config.soul.path) */
120
- soul: resolve(TALON_ROOT, "data", "soul.json"),
121
119
  /** Legacy JSON media index (imported into talon.db on first boot) */
122
120
  mediaIndex: resolve(TALON_ROOT, "data", "media-index.json"),
123
121
  /** Persistent memory: ~/.talon/workspace/memory/memory.md */
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * Bun-COMPILED binaries are a third shape detected separately (argv[1]
13
13
  * points into the embedded virtual fs) — see the existing `$bunfs`
14
- * checks at the spawn sites and in `util/mcp-launcher.ts`.
14
+ * checks at the spawn sites and in `core/mcp-hub/launcher.ts`.
15
15
  */
16
16
 
17
17
  /** True when the current process is executing under the Bun runtime. */