@vellumai/assistant 0.8.5 → 0.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1171) hide show
  1. package/AGENTS.md +33 -1
  2. package/ARCHITECTURE.md +1 -1
  3. package/Dockerfile +1 -0
  4. package/bun.lock +11 -2
  5. package/bunfig.toml +6 -1
  6. package/docker-entrypoint.sh +8 -6
  7. package/docs/credential-execution-service.md +6 -6
  8. package/docs/plugins.md +67 -31
  9. package/examples/plugins/echo/register.ts +4 -7
  10. package/knip.json +1 -0
  11. package/node_modules/@vellumai/environments/bun.lock +24 -0
  12. package/node_modules/@vellumai/environments/package.json +18 -0
  13. package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +95 -0
  14. package/node_modules/@vellumai/environments/src/index.ts +11 -0
  15. package/node_modules/@vellumai/environments/src/seeds.ts +73 -0
  16. package/node_modules/@vellumai/environments/src/types.ts +70 -0
  17. package/node_modules/@vellumai/environments/tsconfig.json +20 -0
  18. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +11 -0
  19. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +15 -17
  20. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +10 -3
  21. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
  22. package/openapi.yaml +5585 -469
  23. package/package.json +7 -3
  24. package/scripts/generate-openapi.ts +20 -13
  25. package/src/__tests__/actor-token-service.test.ts +3 -2
  26. package/src/__tests__/agent-loop-callsite-precedence.test.ts +42 -80
  27. package/src/__tests__/agent-loop-exit-reason.test.ts +336 -42
  28. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +141 -0
  29. package/src/__tests__/agent-loop-override-profile.test.ts +21 -33
  30. package/src/__tests__/agent-loop-provider-error-recording.test.ts +6 -4
  31. package/src/__tests__/agent-loop-thinking.test.ts +17 -12
  32. package/src/__tests__/agent-loop.test.ts +207 -341
  33. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +5 -2
  34. package/src/__tests__/agent-wake-override-profile.test.ts +23 -40
  35. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  36. package/src/__tests__/annotate-risk-options.test.ts +1 -0
  37. package/src/__tests__/anthropic-provider.test.ts +201 -55
  38. package/src/__tests__/app-builder-skill-instructions.test.ts +22 -0
  39. package/src/__tests__/app-control-flow.test.ts +5 -0
  40. package/src/__tests__/approval-cascade.test.ts +5 -11
  41. package/src/__tests__/approval-routes-http.test.ts +13 -15
  42. package/src/__tests__/assert-not-live-db.ts +79 -0
  43. package/src/__tests__/assistant-event.test.ts +15 -0
  44. package/src/__tests__/assistant-feature-flags-integration.test.ts +11 -27
  45. package/src/__tests__/audit-log-rotation.test.ts +2 -2
  46. package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
  47. package/src/__tests__/avatar-e2e.test.ts +7 -37
  48. package/src/__tests__/avatar-generator.test.ts +12 -42
  49. package/src/__tests__/avatar-identity-sync.test.ts +28 -3
  50. package/src/__tests__/background-shell-bash.test.ts +3 -7
  51. package/src/__tests__/background-workers-disk-pressure.test.ts +5 -8
  52. package/src/__tests__/browser-skill-endstate.test.ts +3 -3
  53. package/src/__tests__/btw-routes.test.ts +10 -14
  54. package/src/__tests__/call-controller.test.ts +3 -2
  55. package/src/__tests__/call-pointer-messages.test.ts +5 -3
  56. package/src/__tests__/call-site-routing-provider.test.ts +22 -40
  57. package/src/__tests__/catalog-files.test.ts +1 -0
  58. package/src/__tests__/channel-approval-routes.test.ts +51 -22
  59. package/src/__tests__/channel-approvals.test.ts +3 -1
  60. package/src/__tests__/channel-guardian.test.ts +3 -2
  61. package/src/__tests__/channel-invite-transport.test.ts +1 -5
  62. package/src/__tests__/channel-readiness-routes.test.ts +0 -4
  63. package/src/__tests__/channel-readiness-slack-remote.test.ts +170 -0
  64. package/src/__tests__/channel-reply-delivery.test.ts +35 -0
  65. package/src/__tests__/channel-retry-sweep.test.ts +388 -79
  66. package/src/__tests__/checker.test.ts +12 -12
  67. package/src/__tests__/circuit-breaker-pipeline.test.ts +3 -3
  68. package/src/__tests__/clawhub-files.test.ts +1 -0
  69. package/src/__tests__/compaction-events.test.ts +6 -17
  70. package/src/__tests__/compaction-pipeline.test.ts +1 -1
  71. package/src/__tests__/compaction-timeout-recovery.test.ts +37 -48
  72. package/src/__tests__/compaction-trail-store.test.ts +186 -0
  73. package/src/__tests__/compactor-call-site-logging.test.ts +1 -0
  74. package/src/__tests__/compactor-image-manifest-trust.test.ts +112 -0
  75. package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
  76. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +7 -5
  77. package/src/__tests__/computer-use-tools.test.ts +14 -16
  78. package/src/__tests__/config-loader-backfill.test.ts +13 -28
  79. package/src/__tests__/config-loader-corrupt.test.ts +5 -5
  80. package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
  81. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
  82. package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
  83. package/src/__tests__/config-schema.test.ts +10 -10
  84. package/src/__tests__/config-watcher.test.ts +28 -0
  85. package/src/__tests__/connection-model-compat.test.ts +83 -0
  86. package/src/__tests__/contacts-tools.test.ts +3 -2
  87. package/src/__tests__/context-search-agent-runner.test.ts +6 -3
  88. package/src/__tests__/context-token-estimator.test.ts +56 -0
  89. package/src/__tests__/context-window-manager-compact-retry.test.ts +291 -0
  90. package/src/__tests__/conversation-abort-tool-results.test.ts +19 -7
  91. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +4 -2
  92. package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
  93. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +13 -27
  94. package/src/__tests__/conversation-agent-loop-overflow.test.ts +464 -90
  95. package/src/__tests__/conversation-agent-loop.test.ts +1069 -64
  96. package/src/__tests__/conversation-analysis-routes.test.ts +2 -3
  97. package/src/__tests__/conversation-app-control-instantiation.test.ts +29 -19
  98. package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -1
  99. package/src/__tests__/conversation-attention-store.test.ts +101 -0
  100. package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
  101. package/src/__tests__/conversation-clear-safety.test.ts +20 -10
  102. package/src/__tests__/conversation-confirmation-signals.test.ts +16 -45
  103. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
  104. package/src/__tests__/conversation-disk-view.test.ts +10 -17
  105. package/src/__tests__/conversation-error.test.ts +30 -0
  106. package/src/__tests__/conversation-fork-crud.test.ts +132 -157
  107. package/src/__tests__/conversation-fork-route.test.ts +19 -16
  108. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  109. package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
  110. package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
  111. package/src/__tests__/conversation-init.benchmark.test.ts +6 -6
  112. package/src/__tests__/conversation-lifecycle.test.ts +4 -2
  113. package/src/__tests__/conversation-list-source.test.ts +3 -2
  114. package/src/__tests__/conversation-load-history-repair.test.ts +5 -3
  115. package/src/__tests__/conversation-load-history-stripped.test.ts +2 -1
  116. package/src/__tests__/conversation-message-sync-tags.test.ts +3 -4
  117. package/src/__tests__/conversation-pairing.test.ts +87 -4
  118. package/src/__tests__/conversation-pre-run-repair.test.ts +1 -1
  119. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +30 -7
  120. package/src/__tests__/conversation-process-callsite.test.ts +28 -30
  121. package/src/__tests__/conversation-provider-retry-repair.test.ts +58 -44
  122. package/src/__tests__/conversation-queue.test.ts +603 -455
  123. package/src/__tests__/conversation-routes-disk-view.test.ts +6 -20
  124. package/src/__tests__/conversation-routes-guardian-reply.test.ts +35 -10
  125. package/src/__tests__/conversation-routes-slash-commands.test.ts +35 -4
  126. package/src/__tests__/conversation-runtime-assembly.test.ts +98 -22
  127. package/src/__tests__/conversation-runtime-workspace.test.ts +19 -1
  128. package/src/__tests__/conversation-skill-tools.test.ts +38 -142
  129. package/src/__tests__/conversation-slash-queue.test.ts +120 -62
  130. package/src/__tests__/conversation-slash-unknown.test.ts +18 -15
  131. package/src/__tests__/conversation-speed-override.test.ts +9 -22
  132. package/src/__tests__/conversation-stream-state.test.ts +484 -0
  133. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +52 -15
  134. package/src/__tests__/conversation-surfaces-app-control.test.ts +32 -4
  135. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
  136. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
  137. package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
  138. package/src/__tests__/conversation-surfaces-state-update.test.ts +8 -5
  139. package/src/__tests__/conversation-surfaces-table-action.test.ts +13 -32
  140. package/src/__tests__/conversation-sync-tags.test.ts +128 -12
  141. package/src/__tests__/conversation-title-service.test.ts +1 -0
  142. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +53 -11
  143. package/src/__tests__/conversation-unread-route.test.ts +14 -2
  144. package/src/__tests__/conversation-usage.test.ts +1 -2
  145. package/src/__tests__/conversation-wipe.test.ts +1 -1
  146. package/src/__tests__/conversation-workspace-cache-state.test.ts +4 -1
  147. package/src/__tests__/conversation-workspace-injection.test.ts +53 -22
  148. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +32 -7
  149. package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
  150. package/src/__tests__/credential-broker-server-use.test.ts +5 -5
  151. package/src/__tests__/credential-execution-client.test.ts +72 -1
  152. package/src/__tests__/credential-execution-feature-gates.test.ts +10 -12
  153. package/src/__tests__/credential-execution-tools.test.ts +1 -2
  154. package/src/__tests__/credential-health-service.test.ts +252 -3
  155. package/src/__tests__/credential-security-invariants.test.ts +5 -6
  156. package/src/__tests__/credential-vault-unit.test.ts +19 -19
  157. package/src/__tests__/credential-vault.test.ts +5 -5
  158. package/src/__tests__/cross-provider-web-search.test.ts +61 -3
  159. package/src/__tests__/cu-unified-flow.test.ts +26 -1
  160. package/src/__tests__/db-connection-isolation.test.ts +7 -6
  161. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
  162. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
  163. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
  164. package/src/__tests__/db-schedule-syntax-migration.test.ts +11 -0
  165. package/src/__tests__/db-test-helpers.ts +58 -0
  166. package/src/__tests__/disk-pressure-guard.test.ts +119 -36
  167. package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
  168. package/src/__tests__/disk-pressure-routes.test.ts +9 -35
  169. package/src/__tests__/disk-pressure-tools.test.ts +0 -4
  170. package/src/__tests__/dm-persistence.test.ts +33 -42
  171. package/src/__tests__/document-create-dedupe.test.ts +189 -0
  172. package/src/__tests__/document-find-replace.test.ts +3 -2
  173. package/src/__tests__/document-tool-security.test.ts +81 -2
  174. package/src/__tests__/dynamic-page-surface.test.ts +68 -0
  175. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
  176. package/src/__tests__/edit-propagation.test.ts +1 -2
  177. package/src/__tests__/empty-response-pipeline.test.ts +127 -5
  178. package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
  179. package/src/__tests__/encrypted-store.test.ts +11 -9
  180. package/src/__tests__/feature-flag-test-helpers.ts +53 -0
  181. package/src/__tests__/filing-service.test.ts +3 -2
  182. package/src/__tests__/first-greeting.test.ts +103 -12
  183. package/src/__tests__/gateway-flag-listener.test.ts +0 -1
  184. package/src/__tests__/gemini-inline-media.test.ts +78 -0
  185. package/src/__tests__/gemini-provider.test.ts +375 -26
  186. package/src/__tests__/guardian-action-sweep.test.ts +3 -2
  187. package/src/__tests__/guardian-outbound-http.test.ts +3 -2
  188. package/src/__tests__/guardian-routing-state.test.ts +60 -71
  189. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
  190. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +10 -7
  191. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  192. package/src/__tests__/heartbeat-service.test.ts +3 -1
  193. package/src/__tests__/helpers/mock-logger.ts +26 -0
  194. package/src/__tests__/history-repair-hook.test.ts +161 -0
  195. package/src/__tests__/history-repair-observability.test.ts +1 -1
  196. package/src/__tests__/history-repair.test.ts +2 -1
  197. package/src/__tests__/host-app-control-proxy.test.ts +2 -0
  198. package/src/__tests__/host-bash-routes.test.ts +1 -0
  199. package/src/__tests__/host-cu-proxy.test.ts +2 -0
  200. package/src/__tests__/host-cu-routes-targeted.test.ts +1 -0
  201. package/src/__tests__/host-file-edit-tool.test.ts +4 -2
  202. package/src/__tests__/host-file-proxy.test.ts +31 -0
  203. package/src/__tests__/host-file-read-tool.test.ts +4 -2
  204. package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
  205. package/src/__tests__/host-file-write-tool.test.ts +9 -3
  206. package/src/__tests__/host-proxy-preactivation.test.ts +53 -14
  207. package/src/__tests__/host-shell-tool.test.ts +11 -5
  208. package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
  209. package/src/__tests__/http-conversation-lineage.test.ts +3 -2
  210. package/src/__tests__/http-user-message-parity.test.ts +31 -9
  211. package/src/__tests__/identity-intro-cache.test.ts +154 -22
  212. package/src/__tests__/inbound-slack-persistence.test.ts +51 -74
  213. package/src/__tests__/inference-profile-reaper.test.ts +3 -2
  214. package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
  215. package/src/__tests__/injector-background-turn.test.ts +1 -1
  216. package/src/__tests__/injector-chain.test.ts +1 -1
  217. package/src/__tests__/injector-disk-pressure.test.ts +4 -18
  218. package/src/__tests__/injector-document-comments.test.ts +1 -1
  219. package/src/__tests__/injector-pkb-v2-silenced.test.ts +1 -1
  220. package/src/__tests__/injector-v3-suppression.test.ts +220 -0
  221. package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
  222. package/src/__tests__/list-messages-attachments.test.ts +7 -8
  223. package/src/__tests__/list-messages-hidden-metadata.test.ts +93 -11
  224. package/src/__tests__/list-messages-page-latest.test.ts +0 -1
  225. package/src/__tests__/list-messages-tool-merge.test.ts +36 -6
  226. package/src/__tests__/llm-call-pipeline.test.ts +21 -15
  227. package/src/__tests__/llm-context-normalization.test.ts +42 -0
  228. package/src/__tests__/llm-request-log-turn-query.test.ts +42 -86
  229. package/src/__tests__/llm-resolver.test.ts +346 -39
  230. package/src/__tests__/llm-schema.test.ts +1 -1
  231. package/src/__tests__/llm-usage-store.test.ts +45 -0
  232. package/src/__tests__/log-export-routes.test.ts +59 -0
  233. package/src/__tests__/managed-skill-lifecycle.test.ts +1 -8
  234. package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
  235. package/src/__tests__/mcp-abort-signal.test.ts +14 -0
  236. package/src/__tests__/mcp-auth-routes.test.ts +15 -10
  237. package/src/__tests__/mcp-client-auth.test.ts +14 -0
  238. package/src/__tests__/mcp-health-check.test.ts +18 -13
  239. package/src/__tests__/memory-retrieval-pipeline.test.ts +1 -1
  240. package/src/__tests__/memory-v2-static-injector.test.ts +1 -1
  241. package/src/__tests__/messaging-send-tool.test.ts +9 -4
  242. package/src/__tests__/migration-export-http.test.ts +12 -12
  243. package/src/__tests__/migration-import-commit-http.test.ts +8 -8
  244. package/src/__tests__/migration-import-from-url.test.ts +3 -3
  245. package/src/__tests__/migration-import-preflight-http.test.ts +7 -7
  246. package/src/__tests__/migration-validate-http.test.ts +3 -3
  247. package/src/__tests__/mock-gateway-ipc.ts +18 -2
  248. package/src/__tests__/model-intents.test.ts +3 -3
  249. package/src/__tests__/native-web-search.test.ts +44 -22
  250. package/src/__tests__/notification-decision-identity.test.ts +9 -18
  251. package/src/__tests__/notification-decision-recipient-context.test.ts +3 -6
  252. package/src/__tests__/notification-deep-link.test.ts +62 -0
  253. package/src/__tests__/oauth-commands-routes.test.ts +38 -1
  254. package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
  255. package/src/__tests__/oauth-store.test.ts +3 -2
  256. package/src/__tests__/onboarding-template-contract.test.ts +13 -2
  257. package/src/__tests__/openai-provider.test.ts +74 -79
  258. package/src/__tests__/openai-responses-provider.test.ts +90 -86
  259. package/src/__tests__/openrouter-provider-only.test.ts +27 -5
  260. package/src/__tests__/outbound-slack-persistence.test.ts +48 -2
  261. package/src/__tests__/overflow-reduce-pipeline.test.ts +2 -4
  262. package/src/__tests__/parallel-tool.benchmark.test.ts +24 -36
  263. package/src/__tests__/persistence-pipeline.test.ts +154 -27
  264. package/src/__tests__/persistence-secret-redaction.test.ts +85 -13
  265. package/src/__tests__/pipeline-runner.test.ts +2 -3
  266. package/src/__tests__/plugin-bootstrap.test.ts +60 -36
  267. package/src/__tests__/plugin-route-contribution.test.ts +6 -16
  268. package/src/__tests__/plugin-skill-contribution.test.ts +7 -17
  269. package/src/__tests__/plugin-tool-contribution.test.ts +51 -64
  270. package/src/__tests__/plugin-types.test.ts +7 -14
  271. package/src/__tests__/prechat-onboarding-contract.test.ts +23 -0
  272. package/src/__tests__/process-message-background-slack.test.ts +38 -32
  273. package/src/__tests__/process-message-display-content.test.ts +49 -64
  274. package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
  275. package/src/__tests__/provider-commit-message-generator.test.ts +19 -14
  276. package/src/__tests__/provider-error-scenarios.test.ts +7 -6
  277. package/src/__tests__/provider-platform-proxy-integration.test.ts +215 -8
  278. package/src/__tests__/provider-registry-ollama.test.ts +45 -22
  279. package/src/__tests__/provider-send-message-override-profile.test.ts +9 -25
  280. package/src/__tests__/provider-streaming.benchmark.test.ts +12 -22
  281. package/src/__tests__/provider-usage-tracking.test.ts +0 -6
  282. package/src/__tests__/ratelimit.test.ts +9 -4
  283. package/src/__tests__/recording-handler.test.ts +1 -0
  284. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  285. package/src/__tests__/registry.test.ts +82 -76
  286. package/src/__tests__/relay-server.test.ts +30 -23
  287. package/src/__tests__/retry-openrouter-only-normalization.test.ts +5 -8
  288. package/src/__tests__/retry-thinking-tool-choice.test.ts +10 -13
  289. package/src/__tests__/retry-verbosity-normalization.test.ts +5 -8
  290. package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
  291. package/src/__tests__/runtime-events-sse-reconnect.test.ts +353 -0
  292. package/src/__tests__/schedule-routes.test.ts +80 -10
  293. package/src/__tests__/schedule-store.test.ts +83 -1
  294. package/src/__tests__/schedule-tools.test.ts +125 -0
  295. package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
  296. package/src/__tests__/secret-ingress-http.test.ts +7 -3
  297. package/src/__tests__/secret-prompt-log-hygiene.test.ts +11 -7
  298. package/src/__tests__/secret-prompter-channel-fallback.test.ts +11 -9
  299. package/src/__tests__/secret-response-routing.test.ts +13 -11
  300. package/src/__tests__/secure-keys.test.ts +3 -3
  301. package/src/__tests__/send-endpoint-busy.test.ts +83 -43
  302. package/src/__tests__/server-history-render.test.ts +4 -1
  303. package/src/__tests__/shell-observability.test.ts +249 -0
  304. package/src/__tests__/skill-feature-flags-integration.test.ts +19 -21
  305. package/src/__tests__/skill-feature-flags.test.ts +20 -22
  306. package/src/__tests__/skill-load-feature-flag.test.ts +15 -15
  307. package/src/__tests__/skill-projection-feature-flag.test.ts +44 -30
  308. package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
  309. package/src/__tests__/skill-tool-factory.test.ts +96 -95
  310. package/src/__tests__/skills-files-catalog-fallback.test.ts +10 -0
  311. package/src/__tests__/skillssh-files.test.ts +1 -0
  312. package/src/__tests__/slack-channel-config.test.ts +3 -3
  313. package/src/__tests__/starter-task-flow.test.ts +6 -6
  314. package/src/__tests__/strip-memory-injections.test.ts +102 -14
  315. package/src/__tests__/subagent-call-site-routing.test.ts +13 -5
  316. package/src/__tests__/subagent-disposal.test.ts +27 -8
  317. package/src/__tests__/subagent-fork-notifications.test.ts +24 -9
  318. package/src/__tests__/subagent-fork-spawn.test.ts +13 -4
  319. package/src/__tests__/subagent-manager-notify.test.ts +20 -8
  320. package/src/__tests__/subagent-notify-parent.test.ts +5 -4
  321. package/src/__tests__/subagent-spawn-tool-fork.test.ts +58 -0
  322. package/src/__tests__/subagent-tools.test.ts +2 -1
  323. package/src/__tests__/suggestion-routes.test.ts +4 -3
  324. package/src/__tests__/sync-message-contract.test.ts +19 -16
  325. package/src/__tests__/system-prompt.test.ts +92 -0
  326. package/src/__tests__/terminal-tools.test.ts +3 -24
  327. package/src/__tests__/test-preload-verifier.ts +68 -0
  328. package/src/__tests__/test-preload.ts +32 -39
  329. package/src/__tests__/thread-backfill.test.ts +4 -9
  330. package/src/__tests__/title-generate-pipeline.test.ts +1 -1
  331. package/src/__tests__/token-estimate-pipeline.test.ts +2 -4
  332. package/src/__tests__/tool-error-pipeline.test.ts +2 -2
  333. package/src/__tests__/tool-execute-pipeline.test.ts +1 -1
  334. package/src/__tests__/tool-executor-lifecycle-events.test.ts +20 -7
  335. package/src/__tests__/tool-executor.test.ts +55 -10
  336. package/src/__tests__/tool-preview-lifecycle.test.ts +14 -11
  337. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  338. package/src/__tests__/tool-result-truncate-pipeline.test.ts +9 -12
  339. package/src/__tests__/tool-result-truncation.test.ts +3 -1
  340. package/src/__tests__/tools-audio-read.test.ts +113 -0
  341. package/src/__tests__/turn-boundary-resolution.test.ts +44 -84
  342. package/src/__tests__/turn-events-store.test.ts +11 -7
  343. package/src/__tests__/twilio-routes.test.ts +3 -2
  344. package/src/__tests__/validate-input.test.ts +381 -0
  345. package/src/__tests__/verification-control-plane-policy.test.ts +1 -0
  346. package/src/__tests__/voice-scoped-grant-consumer.test.ts +10 -7
  347. package/src/__tests__/voice-session-bridge.test.ts +50 -35
  348. package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
  349. package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
  350. package/src/acp/__tests__/prepare-agent-env.test.ts +143 -31
  351. package/src/acp/prepare-agent-env.ts +52 -11
  352. package/src/acp/session-manager.ts +5 -6
  353. package/src/agent/compaction-circuit.ts +140 -0
  354. package/src/agent/loop.ts +489 -85
  355. package/src/api/README.md +126 -2
  356. package/src/api/constants/call-sites.ts +27 -0
  357. package/src/api/constants/tool-execution.ts +21 -0
  358. package/src/api/events/assistant-activity-state.ts +75 -0
  359. package/src/api/events/assistant-outbound-attachment.ts +49 -0
  360. package/src/api/events/assistant-text-delta.ts +30 -0
  361. package/src/api/events/assistant-turn-start.ts +31 -0
  362. package/src/api/events/avatar-updated.ts +24 -0
  363. package/src/api/events/compaction-circuit-closed.ts +26 -0
  364. package/src/api/events/compaction-circuit-open.ts +28 -0
  365. package/src/api/events/confirmation-request.ts +114 -0
  366. package/src/api/events/contact-request.ts +33 -0
  367. package/src/api/events/conversation-error.ts +77 -0
  368. package/src/api/events/conversation-list-invalidated.ts +38 -0
  369. package/src/api/events/conversation-title-updated.ts +24 -0
  370. package/src/api/events/disk-pressure-status-changed.ts +61 -0
  371. package/src/api/events/document-comment-created.ts +44 -0
  372. package/src/api/events/document-comment-deleted.ts +22 -0
  373. package/src/api/events/document-comment-reopened.ts +23 -0
  374. package/src/api/events/document-comment-resolved.ts +25 -0
  375. package/src/api/events/document-editor-update.ts +27 -0
  376. package/src/api/events/error.ts +32 -0
  377. package/src/api/events/generation-cancelled.ts +22 -0
  378. package/src/api/events/generation-handoff.ts +39 -0
  379. package/src/api/events/home-feed-updated.ts +26 -0
  380. package/src/api/events/identity-changed.ts +32 -0
  381. package/src/api/events/interaction-resolved.ts +50 -0
  382. package/src/api/events/message-complete.ts +40 -0
  383. package/src/api/events/message-dequeued.ts +21 -0
  384. package/src/api/events/message-queued-deleted.ts +23 -0
  385. package/src/api/events/message-queued.ts +22 -0
  386. package/src/api/events/message-request-complete.ts +29 -0
  387. package/src/api/events/navigate-settings.ts +20 -0
  388. package/src/api/events/notification-intent.ts +33 -0
  389. package/src/api/events/open-url.ts +28 -0
  390. package/src/api/events/question-request.ts +67 -0
  391. package/src/{events → api/events}/relationship-state-updated.ts +6 -8
  392. package/src/api/events/secret-request.ts +42 -0
  393. package/src/api/events/subagent-event.ts +79 -0
  394. package/src/api/events/subagent-spawned.ts +40 -0
  395. package/src/api/events/subagent-status-changed.ts +65 -0
  396. package/src/api/events/sync-changed.ts +29 -0
  397. package/src/api/events/tool-result.ts +129 -0
  398. package/src/api/events/tool-use-start.ts +30 -0
  399. package/src/api/events/turn-profile-auto-routed.ts +28 -0
  400. package/src/api/events/ui-surface-complete.ts +30 -0
  401. package/src/api/events/ui-surface-dismiss.ts +22 -0
  402. package/src/api/events/ui-surface-show.ts +67 -0
  403. package/src/api/events/ui-surface-update.ts +26 -0
  404. package/src/api/events/usage-update.ts +34 -0
  405. package/src/api/events/user-message-echo.ts +35 -0
  406. package/src/api/index.ts +482 -3
  407. package/src/api/requests/dictation.ts +45 -0
  408. package/src/api/responses/disk-pressure-status.ts +26 -0
  409. package/src/api/responses/home.ts +217 -0
  410. package/src/api/responses/llm-context-response.ts +41 -0
  411. package/src/api/responses/llm-request-log-entry.ts +93 -0
  412. package/src/api/responses/memory-recall-log.ts +65 -0
  413. package/src/api/responses/memory-v2-activation-log.ts +78 -0
  414. package/src/api/responses/memory-v3-selection-log.ts +50 -0
  415. package/src/api/responses/subagent-detail.ts +48 -0
  416. package/src/approvals/guardian-decision-primitive.ts +7 -15
  417. package/src/approvals/guardian-request-resolvers.ts +6 -9
  418. package/src/avatar/__tests__/avatar-manifest.test.ts +236 -0
  419. package/src/avatar/__tests__/avatar-store.test.ts +193 -0
  420. package/src/avatar/avatar-manifest.ts +195 -0
  421. package/src/avatar/avatar-store.ts +113 -0
  422. package/src/avatar/traits-png-sync.ts +8 -2
  423. package/src/background-wake/background-wake-routes.test.ts +687 -52
  424. package/src/background-wake/next-wake.test.ts +31 -1
  425. package/src/background-wake/next-wake.ts +4 -1
  426. package/src/background-wake/platform-client.test.ts +308 -0
  427. package/src/background-wake/platform-client.ts +167 -0
  428. package/src/background-wake/publisher.ts +91 -0
  429. package/src/background-wake/runtime-registry.ts +2 -2
  430. package/src/background-wake/wake-intent-hooks.test.ts +282 -0
  431. package/src/calls/call-conversation-messages.ts +6 -4
  432. package/src/calls/guardian-action-sweep.ts +6 -4
  433. package/src/calls/guardian-dispatch.ts +1 -0
  434. package/src/calls/relay-server.ts +12 -8
  435. package/src/calls/voice-session-bridge.ts +17 -31
  436. package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
  437. package/src/cli/commands/__tests__/memory-v3.test.ts +245 -0
  438. package/src/cli/commands/__tests__/notifications.test.ts +184 -40
  439. package/src/cli/commands/avatar.ts +17 -11
  440. package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
  441. package/src/cli/commands/channels/index.ts +229 -0
  442. package/src/cli/commands/conversations.ts +15 -1
  443. package/src/cli/commands/db/__tests__/repair.test.ts +540 -0
  444. package/src/cli/commands/db/__tests__/status.test.ts +253 -0
  445. package/src/cli/commands/db/format.ts +48 -0
  446. package/src/cli/commands/db/index.ts +29 -0
  447. package/src/cli/commands/db/repair-step-conversation-backfill.ts +345 -0
  448. package/src/cli/commands/db/repair-step-integrity.ts +146 -0
  449. package/src/cli/commands/db/repair-steps.ts +164 -0
  450. package/src/cli/commands/db/repair.ts +141 -0
  451. package/src/cli/commands/db/status.ts +366 -0
  452. package/src/cli/commands/memory-v3.ts +168 -203
  453. package/src/cli/commands/notifications.ts +365 -55
  454. package/src/cli/lib/cli-colors.ts +24 -6
  455. package/src/cli/lib/open-browser.ts +7 -2
  456. package/src/cli/program.ts +6 -5
  457. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  458. package/src/config/assistant-feature-flags.ts +25 -44
  459. package/src/config/bundled-skills/app-builder/SKILL.md +14 -3
  460. package/src/config/bundled-skills/document-editor/SKILL.md +5 -1
  461. package/src/config/bundled-skills/media-processing/services/reduce.ts +6 -9
  462. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +7 -2
  463. package/src/config/bundled-skills/schedule/SKILL.md +2 -2
  464. package/src/config/bundled-skills/schedule/TOOLS.json +10 -2
  465. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
  466. package/src/config/call-site-defaults.ts +3 -8
  467. package/src/config/feature-flag-cache.ts +86 -0
  468. package/src/config/feature-flag-registry.json +42 -26
  469. package/src/config/llm-context-resolution.ts +10 -1
  470. package/src/config/llm-resolver.ts +121 -15
  471. package/src/config/loader.ts +4 -5
  472. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -211
  473. package/src/config/schemas/call-site-catalog.ts +8 -15
  474. package/src/config/schemas/heartbeat.ts +1 -1
  475. package/src/config/schemas/llm.ts +92 -4
  476. package/src/config/schemas/memory-lifecycle.ts +24 -0
  477. package/src/config/schemas/memory-v2.ts +0 -227
  478. package/src/config/schemas/memory-v3.ts +39 -0
  479. package/src/config/schemas/memory.ts +6 -1
  480. package/src/config/schemas/services.ts +6 -2
  481. package/src/config/schemas/timeouts.ts +3 -1
  482. package/src/config/seed-inference-profiles.ts +36 -16
  483. package/src/context/compactor.ts +54 -31
  484. package/src/context/token-estimator.ts +29 -5
  485. package/src/context/tool-result-truncation.ts +1 -43
  486. package/src/context/window-manager.ts +138 -20
  487. package/src/credential-execution/executable-discovery.ts +40 -0
  488. package/src/credential-execution/process-manager.ts +6 -2
  489. package/src/credential-health/credential-health-service.ts +125 -40
  490. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -6
  491. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +15 -17
  492. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -2
  493. package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
  494. package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
  495. package/src/daemon/__tests__/native-web-search-metadata.test.ts +1 -0
  496. package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
  497. package/src/daemon/__tests__/web-search-status-text.test.ts +11 -6
  498. package/src/daemon/approval-generators.ts +4 -4
  499. package/src/daemon/config-watcher.ts +7 -1
  500. package/src/daemon/conversation-agent-loop-handlers.ts +613 -155
  501. package/src/daemon/conversation-agent-loop.ts +409 -605
  502. package/src/daemon/conversation-error.ts +40 -12
  503. package/src/daemon/conversation-history.ts +22 -6
  504. package/src/daemon/conversation-launch.ts +4 -8
  505. package/src/daemon/conversation-lifecycle.ts +10 -38
  506. package/src/daemon/conversation-messaging.ts +83 -44
  507. package/src/daemon/conversation-notifiers.ts +7 -5
  508. package/src/daemon/conversation-process.ts +174 -116
  509. package/src/daemon/conversation-runtime-assembly.ts +76 -30
  510. package/src/daemon/conversation-skill-tools.ts +14 -30
  511. package/src/daemon/conversation-store.ts +6 -5
  512. package/src/daemon/conversation-surfaces.ts +124 -103
  513. package/src/daemon/conversation-tool-setup.ts +36 -48
  514. package/src/daemon/conversation.ts +111 -166
  515. package/src/daemon/daemon-control.ts +1 -1
  516. package/src/daemon/daemon-skill-host.ts +7 -4
  517. package/src/daemon/disk-pressure-guard.ts +54 -50
  518. package/src/daemon/external-plugins-bootstrap.ts +46 -24
  519. package/src/daemon/first-greeting.ts +53 -13
  520. package/src/daemon/guardian-action-generators.ts +2 -2
  521. package/src/daemon/handlers/conversations.ts +6 -22
  522. package/src/daemon/handlers/shared.ts +10 -1
  523. package/src/daemon/handlers/skills.ts +15 -14
  524. package/src/daemon/host-app-control-proxy.ts +54 -1
  525. package/src/daemon/host-cu-proxy.ts +46 -22
  526. package/src/daemon/host-file-proxy.ts +25 -1
  527. package/src/daemon/host-proxy-preactivation.ts +25 -6
  528. package/src/daemon/lifecycle.ts +40 -67
  529. package/src/daemon/mcp-reload-service.ts +1 -1
  530. package/src/daemon/meet-manifest-loader.ts +10 -17
  531. package/src/daemon/message-protocol.ts +2 -3
  532. package/src/daemon/message-provenance.ts +49 -0
  533. package/src/daemon/message-types/contacts.ts +3 -20
  534. package/src/daemon/message-types/conversations.ts +25 -125
  535. package/src/daemon/message-types/document-comments.ts +8 -44
  536. package/src/daemon/message-types/documents.ts +3 -9
  537. package/src/daemon/message-types/home.ts +5 -18
  538. package/src/daemon/message-types/integrations.ts +4 -13
  539. package/src/daemon/message-types/messages.ts +47 -377
  540. package/src/daemon/message-types/notifications.ts +2 -32
  541. package/src/daemon/message-types/settings.ts +3 -8
  542. package/src/daemon/message-types/skills.ts +2 -0
  543. package/src/daemon/message-types/subagents.ts +6 -0
  544. package/src/daemon/message-types/surfaces.ts +2 -0
  545. package/src/daemon/message-types/sync.ts +12 -25
  546. package/src/daemon/message-types/workspace.ts +3 -11
  547. package/src/daemon/process-message.ts +58 -55
  548. package/src/daemon/providers-setup.ts +1 -1
  549. package/src/daemon/server.ts +28 -0
  550. package/src/daemon/switch-inference-profile-tool.ts +13 -3
  551. package/src/daemon/tool-setup-types.ts +0 -6
  552. package/src/daemon/tool-side-effects.ts +10 -7
  553. package/src/daemon/trust-context.ts +13 -0
  554. package/src/daemon/wake-target-adapter.ts +21 -1
  555. package/src/documents/document-store.ts +38 -0
  556. package/src/export/__tests__/transcript-formatter.test.ts +1 -0
  557. package/src/heartbeat/__tests__/heartbeat-service.test.ts +31 -0
  558. package/src/heartbeat/heartbeat-run-store.ts +31 -0
  559. package/src/heartbeat/heartbeat-service.ts +79 -0
  560. package/src/home/__tests__/feed-writer.test.ts +161 -0
  561. package/src/home/__tests__/post-connect-feed.test.ts +1 -0
  562. package/src/home/__tests__/suggested-prompts.test.ts +55 -59
  563. package/src/home/feature-gate.ts +22 -0
  564. package/src/home/feed-types.ts +36 -221
  565. package/src/home/feed-writer.ts +146 -7
  566. package/src/home/suggested-prompts.ts +27 -145
  567. package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
  568. package/src/ipc/__tests__/email-ipc.test.ts +0 -9
  569. package/src/ipc/gateway-client.test.ts +4 -1
  570. package/src/ipc/routes/__tests__/route-adapter.test.ts +244 -0
  571. package/src/ipc/routes/route-adapter.ts +45 -6
  572. package/src/ipc/skill-routes/__tests__/memory.test.ts +19 -9
  573. package/src/ipc/skill-routes/__tests__/providers.test.ts +10 -10
  574. package/src/ipc/skill-routes/__tests__/registries.test.ts +59 -20
  575. package/src/ipc/skill-routes/memory.ts +27 -13
  576. package/src/ipc/skill-routes/providers.ts +5 -6
  577. package/src/ipc/skill-routes/registries.ts +39 -88
  578. package/src/live-voice/__tests__/live-voice-archive.test.ts +24 -11
  579. package/src/memory/__tests__/conversation-queries.test.ts +192 -8
  580. package/src/memory/__tests__/db-maintenance.test.ts +128 -0
  581. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +1 -0
  582. package/src/memory/__tests__/jobs-store-job-classes.test.ts +5 -4
  583. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +26 -5
  584. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
  585. package/src/memory/__tests__/memory-retrospective-job.test.ts +11 -6
  586. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
  587. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
  588. package/src/memory/__tests__/memory-v3-selections-migration.test.ts +103 -0
  589. package/src/memory/context-search/agent-runner.ts +2 -4
  590. package/src/memory/conversation-attention-store.ts +17 -3
  591. package/src/memory/conversation-crud.ts +386 -115
  592. package/src/memory/conversation-queries.ts +78 -22
  593. package/src/memory/db-connection.ts +29 -19
  594. package/src/memory/db-init.ts +12 -0
  595. package/src/memory/db-maintenance.ts +18 -2
  596. package/src/memory/db-singleton.ts +77 -0
  597. package/src/memory/delivery-channels.ts +82 -0
  598. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
  599. package/src/memory/graph/consolidation.ts +8 -11
  600. package/src/memory/graph/conversation-graph-memory.ts +41 -8
  601. package/src/memory/graph/extraction.ts +6 -9
  602. package/src/memory/graph/narrative.ts +2 -2
  603. package/src/memory/graph/pattern-scan.ts +2 -2
  604. package/src/memory/graph/retriever.test.ts +3 -3
  605. package/src/memory/graph/retriever.ts +20 -26
  606. package/src/memory/graph/tools.ts +4 -4
  607. package/src/memory/job-handlers/conversation-starters.ts +32 -32
  608. package/src/memory/job-handlers/embedding.test.ts +3 -2
  609. package/src/memory/job-handlers/summarization.ts +1 -2
  610. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
  611. package/src/memory/jobs-store.ts +3 -1
  612. package/src/memory/jobs-worker.ts +63 -40
  613. package/src/memory/llm-request-log-source-clickhouse.ts +55 -1
  614. package/src/memory/llm-request-log-source-local.ts +13 -0
  615. package/src/memory/llm-request-log-source.ts +21 -6
  616. package/src/memory/llm-request-log-store.ts +147 -3
  617. package/src/memory/llm-usage-store.ts +10 -0
  618. package/src/memory/memory-marker.ts +17 -0
  619. package/src/memory/memory-retrospective-job.ts +6 -2
  620. package/src/memory/memory-v2-activation-log-store.ts +13 -1
  621. package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
  622. package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
  623. package/src/memory/migrations/267-llm-usage-events-add-assistant-version.ts +46 -0
  624. package/src/memory/migrations/268-add-memory-v3-selections.ts +28 -0
  625. package/src/memory/migrations/269-schedule-script-timeout.ts +11 -0
  626. package/src/memory/migrations/270-messages-role-created-at-index.ts +18 -0
  627. package/src/memory/migrations/__tests__/267-llm-usage-events-add-assistant-version.test.ts +117 -0
  628. package/src/memory/migrations/index.ts +6 -0
  629. package/src/memory/schema/conversations.ts +9 -1
  630. package/src/memory/schema/inference.ts +0 -1
  631. package/src/memory/schema/infrastructure.ts +11 -0
  632. package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
  633. package/src/memory/v2/__tests__/consolidation-job.test.ts +124 -0
  634. package/src/memory/v2/__tests__/harness-metrics.test.ts +9 -0
  635. package/src/memory/v2/__tests__/harness-replay-input.test.ts +9 -4
  636. package/src/memory/v2/__tests__/harness-runner.test.ts +26 -0
  637. package/src/memory/v2/__tests__/migration.test.ts +11 -3
  638. package/src/memory/v2/__tests__/page-index.test.ts +37 -1
  639. package/src/memory/v2/__tests__/router.test.ts +14 -4
  640. package/src/memory/v2/__tests__/sweep-job.test.ts +9 -5
  641. package/src/memory/v2/backfill-jobs.ts +6 -0
  642. package/src/memory/v2/consolidation-job.ts +89 -9
  643. package/src/memory/v2/harness/metrics.ts +5 -1
  644. package/src/memory/v2/harness/replay-input.ts +19 -3
  645. package/src/memory/v2/harness/runner.ts +6 -0
  646. package/src/memory/v2/harness/trace.ts +6 -0
  647. package/src/memory/v2/migration.ts +5 -3
  648. package/src/memory/v2/page-index.ts +11 -0
  649. package/src/memory/v2/router.ts +8 -11
  650. package/src/memory/v2/sweep-job.ts +8 -11
  651. package/src/memory/v2/types.ts +1 -0
  652. package/src/memory/v3/__tests__/assign.test.ts +242 -0
  653. package/src/memory/v3/__tests__/capabilities.test.ts +118 -0
  654. package/src/memory/v3/__tests__/core.test.ts +39 -0
  655. package/src/memory/v3/__tests__/fixtures/eval-turns.json +36 -0
  656. package/src/memory/v3/__tests__/fixtures/live-turns.json +37 -0
  657. package/src/memory/v3/__tests__/health.test.ts +203 -0
  658. package/src/memory/v3/__tests__/live-integration.test.ts +330 -0
  659. package/src/memory/v3/__tests__/maintain-job.test.ts +288 -0
  660. package/src/memory/v3/__tests__/needle.test.ts +107 -0
  661. package/src/memory/v3/__tests__/orchestrate.test.ts +400 -0
  662. package/src/memory/v3/__tests__/reconcile.test.ts +274 -0
  663. package/src/memory/v3/__tests__/render-injection.test.ts +61 -0
  664. package/src/memory/v3/__tests__/router.test.ts +260 -0
  665. package/src/memory/v3/__tests__/selection-log-store.test.ts +179 -0
  666. package/src/memory/v3/__tests__/selector.test.ts +404 -0
  667. package/src/memory/v3/__tests__/shadow-plugin.test.ts +414 -0
  668. package/src/memory/v3/__tests__/snapshot.test.ts +168 -0
  669. package/src/memory/v3/__tests__/tree.test.ts +192 -0
  670. package/src/memory/v3/__tests__/types.test.ts +54 -0
  671. package/src/memory/v3/__tests__/working-set-eviction.test.ts +106 -0
  672. package/src/memory/v3/__tests__/working-set-skeleton.test.ts +44 -0
  673. package/src/memory/v3/assign.ts +268 -0
  674. package/src/memory/v3/capabilities.ts +124 -0
  675. package/src/memory/v3/core.ts +26 -0
  676. package/src/memory/v3/data/README.md +84 -0
  677. package/src/memory/v3/data/assignments.json +5 -0
  678. package/src/memory/v3/data/core.json +1 -0
  679. package/src/memory/v3/data/leaves/domain-a/topic-x.md +9 -0
  680. package/src/memory/v3/data/leaves/domain-a/topic-y.md +9 -0
  681. package/src/memory/v3/data/leaves/domain-b/topic-z.md +9 -0
  682. package/src/memory/v3/health.ts +0 -0
  683. package/src/memory/v3/maintain-job.ts +314 -0
  684. package/src/memory/v3/needle.ts +115 -0
  685. package/src/memory/v3/orchestrate.ts +114 -0
  686. package/src/memory/v3/page-content.ts +34 -0
  687. package/src/memory/v3/provider-blocks.ts +16 -0
  688. package/src/memory/v3/reconcile.ts +523 -0
  689. package/src/memory/v3/render-injection.ts +32 -0
  690. package/src/memory/v3/router.ts +184 -0
  691. package/src/memory/v3/selection-log-store.ts +84 -0
  692. package/src/memory/v3/selector.ts +211 -0
  693. package/src/memory/v3/shadow-plugin.ts +379 -0
  694. package/src/memory/v3/snapshot.ts +209 -0
  695. package/src/memory/v3/tree.ts +174 -0
  696. package/src/memory/v3/types.ts +46 -60
  697. package/src/memory/v3/working-set.ts +88 -0
  698. package/src/messaging/providers/slack/render-transcript.test.ts +1 -1
  699. package/src/messaging/providers/slack/render-transcript.ts +2 -2
  700. package/src/messaging/style-analyzer.ts +8 -11
  701. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  702. package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
  703. package/src/notifications/adapters/slack.ts +45 -11
  704. package/src/notifications/broadcaster.ts +114 -63
  705. package/src/notifications/conversation-pairing.ts +30 -8
  706. package/src/notifications/decision-engine.ts +10 -13
  707. package/src/notifications/decisions-store.ts +32 -1
  708. package/src/notifications/deliveries-store.ts +45 -0
  709. package/src/notifications/edit-notification.ts +201 -0
  710. package/src/notifications/emit-signal.ts +11 -1
  711. package/src/notifications/preference-extractor.ts +11 -14
  712. package/src/notifications/signal.ts +10 -0
  713. package/src/notifications/types.ts +37 -0
  714. package/src/oauth/byo-connection.test.ts +67 -3
  715. package/src/oauth/byo-connection.ts +32 -5
  716. package/src/oauth/connect-orchestrator.ts +9 -0
  717. package/src/oauth/connection-resolver.test.ts +76 -0
  718. package/src/oauth/connection-resolver.ts +49 -10
  719. package/src/oauth/manual-token-connection.ts +51 -3
  720. package/src/oauth/seed-providers.ts +3 -0
  721. package/src/permissions/approval-policy.test.ts +19 -5
  722. package/src/permissions/approval-policy.ts +14 -3
  723. package/src/permissions/checker.ts +21 -8
  724. package/src/permissions/prompter.ts +42 -36
  725. package/src/permissions/question-prompter.test.ts +35 -26
  726. package/src/permissions/question-prompter.ts +6 -10
  727. package/src/platform/client.test.ts +24 -1
  728. package/src/platform/client.ts +8 -0
  729. package/src/platform/feature-gate.ts +15 -0
  730. package/src/plugin-api/index.ts +2 -0
  731. package/src/plugin-api/types.ts +25 -3
  732. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +93 -0
  733. package/src/plugins/defaults/circuit-breaker/package.json +15 -0
  734. package/src/plugins/defaults/circuit-breaker/register.ts +39 -0
  735. package/src/plugins/defaults/compaction/middlewares/compaction.ts +25 -0
  736. package/src/plugins/defaults/compaction/package.json +15 -0
  737. package/src/plugins/defaults/compaction/register.ts +35 -0
  738. package/src/plugins/defaults/compaction/terminal.ts +73 -0
  739. package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +22 -0
  740. package/src/plugins/defaults/empty-response/package.json +15 -0
  741. package/src/plugins/defaults/empty-response/register.ts +28 -0
  742. package/src/plugins/defaults/empty-response/terminal.ts +106 -0
  743. package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +35 -0
  744. package/src/plugins/defaults/history-repair/package.json +15 -0
  745. package/src/plugins/defaults/history-repair/register.ts +24 -0
  746. package/src/{daemon/history-repair.ts → plugins/defaults/history-repair/terminal.ts} +48 -35
  747. package/src/plugins/defaults/index.ts +29 -40
  748. package/src/plugins/defaults/injectors/package.json +15 -0
  749. package/src/plugins/defaults/{injectors.ts → injectors/register.ts} +16 -46
  750. package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +17 -0
  751. package/src/plugins/defaults/llm-call/package.json +15 -0
  752. package/src/plugins/defaults/{llm-call.ts → llm-call/register.ts} +6 -38
  753. package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +17 -0
  754. package/src/plugins/defaults/memory-retrieval/package.json +15 -0
  755. package/src/plugins/defaults/{memory-retrieval.ts → memory-retrieval/register.ts} +10 -48
  756. package/src/plugins/defaults/{overflow-reduce.ts → overflow-reduce/middlewares/overflowReduce.ts} +18 -77
  757. package/src/plugins/defaults/overflow-reduce/package.json +15 -0
  758. package/src/plugins/defaults/overflow-reduce/register.ts +42 -0
  759. package/src/plugins/defaults/persistence/middlewares/persistence.ts +19 -0
  760. package/src/plugins/defaults/persistence/package.json +15 -0
  761. package/src/plugins/defaults/persistence/register.ts +38 -0
  762. package/src/plugins/defaults/persistence/terminal.ts +83 -0
  763. package/src/plugins/defaults/title-generate/package.json +15 -0
  764. package/src/plugins/defaults/title-generate/register.ts +35 -0
  765. package/src/plugins/defaults/title-generate/terminal.ts +31 -0
  766. package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +23 -0
  767. package/src/plugins/defaults/token-estimate/package.json +15 -0
  768. package/src/plugins/defaults/token-estimate/register.ts +34 -0
  769. package/src/plugins/defaults/token-estimate/terminal.ts +40 -0
  770. package/src/plugins/defaults/tool-error/middlewares/toolError.ts +21 -0
  771. package/src/plugins/defaults/tool-error/package.json +15 -0
  772. package/src/plugins/defaults/tool-error/register.ts +35 -0
  773. package/src/plugins/defaults/tool-error/terminal.ts +47 -0
  774. package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +23 -0
  775. package/src/plugins/defaults/tool-execute/package.json +15 -0
  776. package/src/plugins/defaults/{tool-execute.ts → tool-execute/register.ts} +8 -46
  777. package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +23 -0
  778. package/src/plugins/defaults/tool-result-truncate/package.json +15 -0
  779. package/src/plugins/defaults/tool-result-truncate/register.ts +35 -0
  780. package/src/plugins/defaults/tool-result-truncate/terminal.ts +113 -0
  781. package/src/plugins/defaults/tool-result-truncate/types.ts +22 -0
  782. package/src/plugins/external-plugin-loader.ts +2 -2
  783. package/src/plugins/pipeline.ts +0 -12
  784. package/src/plugins/types.ts +107 -102
  785. package/src/plugins/user-loader.ts +4 -3
  786. package/src/proactive-artifact/aux-message-injector.ts +0 -1
  787. package/src/proactive-artifact/job.test.ts +21 -8
  788. package/src/proactive-artifact/job.ts +3 -1
  789. package/src/prompts/__tests__/system-prompt.test.ts +4 -4
  790. package/src/prompts/sections.ts +20 -7
  791. package/src/prompts/system-prompt.ts +38 -40
  792. package/src/prompts/template-detection.ts +10 -4
  793. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +2 -2
  794. package/src/prompts/templates/BOOTSTRAP.md +10 -10
  795. package/src/prompts/templates/IDENTITY.md +0 -2
  796. package/src/prompts/templates/system-sections.ts +6 -0
  797. package/src/providers/__tests__/connection-model-compat.test.ts +3 -4
  798. package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
  799. package/src/providers/__tests__/retry-callsite.test.ts +25 -25
  800. package/src/providers/__tests__/satellite-connection-routing.test.ts +7 -21
  801. package/src/providers/anthropic/client.ts +24 -5
  802. package/src/providers/call-site-routing.ts +34 -18
  803. package/src/providers/connection-model-compat.ts +23 -0
  804. package/src/providers/connection-resolution.ts +39 -20
  805. package/src/providers/fireworks/client.ts +1 -0
  806. package/src/providers/gemini/client.ts +176 -37
  807. package/src/providers/gemini/inline-media.ts +74 -0
  808. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
  809. package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
  810. package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
  811. package/src/providers/inference/auth.ts +0 -8
  812. package/src/providers/inference/connections.ts +3 -66
  813. package/src/providers/inference/resolve-auth.ts +2 -3
  814. package/src/providers/model-catalog.ts +35 -1
  815. package/src/providers/model-intents.ts +3 -3
  816. package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
  817. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +157 -7
  818. package/src/providers/openai/chat-completions-provider.ts +111 -16
  819. package/src/providers/openai/codex-models.ts +2 -0
  820. package/src/providers/openai/responses-provider.ts +54 -57
  821. package/src/providers/openrouter/client.ts +14 -14
  822. package/src/providers/provider-send-message.ts +23 -14
  823. package/src/providers/ratelimit.ts +1 -9
  824. package/src/providers/registry.ts +48 -8
  825. package/src/providers/retry.ts +16 -9
  826. package/src/providers/search-provider-catalog.ts +17 -9
  827. package/src/providers/types.ts +20 -2
  828. package/src/providers/usage-tracking.ts +1 -9
  829. package/src/runtime/__tests__/agent-wake.test.ts +132 -26
  830. package/src/runtime/__tests__/background-job-runner.test.ts +2 -3
  831. package/src/runtime/access-request-helper.ts +1 -0
  832. package/src/runtime/agent-wake.ts +93 -18
  833. package/src/runtime/assistant-event-hub.ts +2 -2
  834. package/src/runtime/auth/__tests__/guard-tests.test.ts +75 -109
  835. package/src/runtime/auth/__tests__/route-policy.test.ts +153 -170
  836. package/src/runtime/auth/route-policy.ts +42 -1069
  837. package/src/runtime/background-job-runner.ts +1 -4
  838. package/src/runtime/btw-sidechain.ts +3 -1
  839. package/src/runtime/channel-approvals.ts +3 -14
  840. package/src/runtime/channel-invite-transport.ts +5 -6
  841. package/src/runtime/channel-readiness-service.ts +70 -5
  842. package/src/runtime/channel-reply-delivery.ts +23 -0
  843. package/src/runtime/channel-retry-sweep.ts +59 -30
  844. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  845. package/src/runtime/conversation-stream-state.ts +294 -0
  846. package/src/runtime/http-router.ts +19 -22
  847. package/src/runtime/http-types.ts +12 -6
  848. package/src/runtime/invite-instruction-generator.ts +3 -3
  849. package/src/runtime/migrations/vbundle-builder.ts +3 -2
  850. package/src/runtime/pending-interactions.ts +2 -2
  851. package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +565 -0
  852. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
  853. package/src/runtime/routes/__tests__/content-source-routes.test.ts +4 -4
  854. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +436 -0
  855. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +237 -0
  856. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  857. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  858. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
  859. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +26 -72
  860. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +58 -5
  861. package/src/runtime/routes/__tests__/sanity-routes.test.ts +6 -6
  862. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
  863. package/src/runtime/routes/__tests__/stt-routes.test.ts +3 -3
  864. package/src/runtime/routes/__tests__/suggest-trust-rule-routes.test.ts +5 -2
  865. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +294 -0
  866. package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
  867. package/src/runtime/routes/__tests__/tts-routes.test.ts +3 -3
  868. package/src/runtime/routes/acp-routes-list.test.ts +3 -0
  869. package/src/runtime/routes/acp-routes.test.ts +97 -75
  870. package/src/runtime/routes/acp-routes.ts +29 -6
  871. package/src/runtime/routes/app-management-routes.ts +208 -28
  872. package/src/runtime/routes/app-routes.ts +25 -5
  873. package/src/runtime/routes/approval-routes.ts +16 -4
  874. package/src/runtime/routes/attachment-routes.ts +25 -1
  875. package/src/runtime/routes/audio-routes.ts +1 -0
  876. package/src/runtime/routes/audit-routes.ts +5 -0
  877. package/src/runtime/routes/auth-routes.ts +5 -0
  878. package/src/runtime/routes/avatar-routes.ts +238 -59
  879. package/src/runtime/routes/background-tool-routes.ts +9 -0
  880. package/src/runtime/routes/background-wake-routes.ts +201 -23
  881. package/src/runtime/routes/backup-routes.ts +45 -0
  882. package/src/runtime/routes/bookmark-routes.ts +13 -0
  883. package/src/runtime/routes/brain-graph-routes.ts +9 -0
  884. package/src/runtime/routes/browser-routes.ts +5 -0
  885. package/src/runtime/routes/browser-tabs-routes.ts +5 -0
  886. package/src/runtime/routes/btw-routes.ts +9 -5
  887. package/src/runtime/routes/cache-routes.ts +13 -0
  888. package/src/runtime/routes/call-routes.ts +21 -10
  889. package/src/runtime/routes/channel-availability-routes.ts +5 -1
  890. package/src/runtime/routes/channel-readiness-routes.ts +37 -4
  891. package/src/runtime/routes/channel-route-definitions.ts +21 -0
  892. package/src/runtime/routes/channel-verification-routes.ts +21 -0
  893. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +9 -2
  894. package/src/runtime/routes/client-routes.ts +9 -0
  895. package/src/runtime/routes/consolidation-routes.ts +13 -5
  896. package/src/runtime/routes/contact-prompt-routes.ts +9 -0
  897. package/src/runtime/routes/contact-routes.ts +90 -23
  898. package/src/runtime/routes/content-source-routes.ts +5 -1
  899. package/src/runtime/routes/conversation-analysis-routes.ts +11 -1
  900. package/src/runtime/routes/conversation-attention-routes.ts +5 -0
  901. package/src/runtime/routes/conversation-cli-routes.ts +54 -7
  902. package/src/runtime/routes/conversation-compaction-routes.ts +292 -0
  903. package/src/runtime/routes/conversation-list-routes.ts +225 -9
  904. package/src/runtime/routes/conversation-management-routes.ts +96 -28
  905. package/src/runtime/routes/conversation-query-routes.ts +148 -51
  906. package/src/runtime/routes/conversation-routes.ts +259 -158
  907. package/src/runtime/routes/conversation-starter-routes.ts +22 -13
  908. package/src/runtime/routes/conversations-import-routes.ts +25 -7
  909. package/src/runtime/routes/credential-prompt-routes.ts +5 -0
  910. package/src/runtime/routes/credential-routes.ts +25 -6
  911. package/src/runtime/routes/debug-bash-routes.ts +5 -0
  912. package/src/runtime/routes/debug-routes.ts +11 -2
  913. package/src/runtime/routes/defer-routes.ts +13 -0
  914. package/src/runtime/routes/diagnostics-routes.ts +37 -46
  915. package/src/runtime/routes/disk-pressure-routes.ts +17 -31
  916. package/src/runtime/routes/document-comments-routes.ts +46 -27
  917. package/src/runtime/routes/documents-routes.ts +31 -11
  918. package/src/runtime/routes/domain-routes.ts +61 -28
  919. package/src/runtime/routes/email-routes.ts +33 -0
  920. package/src/runtime/routes/events-routes.ts +114 -9
  921. package/src/runtime/routes/filing-routes.ts +9 -4
  922. package/src/runtime/routes/gateway-log-routes.ts +5 -0
  923. package/src/runtime/routes/global-search-routes.ts +53 -50
  924. package/src/runtime/routes/group-routes.ts +32 -5
  925. package/src/runtime/routes/guardian-action-routes.ts +9 -0
  926. package/src/runtime/routes/guardian-approval-interception.ts +0 -31
  927. package/src/runtime/routes/heartbeat-routes.ts +25 -9
  928. package/src/runtime/routes/home-feed-routes.ts +149 -16
  929. package/src/runtime/routes/home-state-routes.ts +8 -40
  930. package/src/runtime/routes/host-app-control-routes.ts +5 -0
  931. package/src/runtime/routes/host-bash-routes.ts +5 -0
  932. package/src/runtime/routes/host-browser-routes.ts +13 -0
  933. package/src/runtime/routes/host-cu-routes.ts +5 -0
  934. package/src/runtime/routes/host-file-routes.ts +26 -6
  935. package/src/runtime/routes/host-transfer-routes.ts +13 -2
  936. package/src/runtime/routes/http-adapter.ts +1 -2
  937. package/src/runtime/routes/identity-intro-cache.ts +72 -16
  938. package/src/runtime/routes/identity-routes.ts +42 -11
  939. package/src/runtime/routes/image-generation-routes.ts +5 -0
  940. package/src/runtime/routes/inbound-message-handler.ts +15 -11
  941. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +524 -12
  942. package/src/runtime/routes/inbound-stages/background-dispatch.ts +72 -27
  943. package/src/runtime/routes/index.ts +2 -0
  944. package/src/runtime/routes/inference-profile-session-routes.ts +13 -3
  945. package/src/runtime/routes/inference-provider-connection-routes.ts +26 -31
  946. package/src/runtime/routes/inference-send-routes.ts +11 -11
  947. package/src/runtime/routes/integrations/a2a.ts +30 -7
  948. package/src/runtime/routes/integrations/slack/channel.ts +19 -3
  949. package/src/runtime/routes/integrations/slack/share.ts +9 -2
  950. package/src/runtime/routes/integrations/telegram.ts +28 -9
  951. package/src/runtime/routes/integrations/twilio.ts +35 -7
  952. package/src/runtime/routes/integrations/vercel.ts +18 -3
  953. package/src/runtime/routes/internal-oauth-routes.ts +5 -0
  954. package/src/runtime/routes/internal-twilio-routes.ts +13 -0
  955. package/src/runtime/routes/llm-call-sites-routes.ts +39 -4
  956. package/src/runtime/routes/llm-context-normalization.ts +7 -2
  957. package/src/runtime/routes/log-export-routes.ts +28 -10
  958. package/src/runtime/routes/mcp-auth-routes.ts +25 -0
  959. package/src/runtime/routes/memory-item-routes.ts +21 -10
  960. package/src/runtime/routes/memory-v2-routes.ts +90 -36
  961. package/src/runtime/routes/memory-v3-routes.ts +283 -259
  962. package/src/runtime/routes/migration-rollback-routes.ts +5 -1
  963. package/src/runtime/routes/migration-routes.ts +49 -13
  964. package/src/runtime/routes/notification-routes.ts +80 -2
  965. package/src/runtime/routes/oauth-apps.ts +33 -11
  966. package/src/runtime/routes/oauth-commands-routes.ts +43 -15
  967. package/src/runtime/routes/oauth-connect-routes.ts +9 -0
  968. package/src/runtime/routes/oauth-lifecycle-routes.ts +5 -1
  969. package/src/runtime/routes/oauth-providers.ts +35 -10
  970. package/src/runtime/routes/platform-routes.ts +21 -0
  971. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +3 -2
  972. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +37 -16
  973. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +7 -3
  974. package/src/runtime/routes/playground/__tests__/state.test.ts +10 -3
  975. package/src/runtime/routes/playground/force-compact.ts +1 -1
  976. package/src/runtime/routes/playground/helpers.ts +0 -1
  977. package/src/runtime/routes/playground/inject-failures.ts +13 -8
  978. package/src/runtime/routes/playground/reset-circuit.ts +14 -9
  979. package/src/runtime/routes/playground/seed-conversation.ts +1 -1
  980. package/src/runtime/routes/playground/seeded-conversations.ts +3 -3
  981. package/src/runtime/routes/playground/state.ts +4 -3
  982. package/src/runtime/routes/plugins-routes.ts +22 -19
  983. package/src/runtime/routes/profiler-routes.ts +17 -4
  984. package/src/runtime/routes/ps-routes.ts +5 -0
  985. package/src/runtime/routes/publish-routes.ts +13 -3
  986. package/src/runtime/routes/question-routes.ts +5 -0
  987. package/src/runtime/routes/recording-routes.ts +25 -12
  988. package/src/runtime/routes/rename-conversation-routes.ts +5 -0
  989. package/src/runtime/routes/sanity-routes.ts +9 -2
  990. package/src/runtime/routes/schedule-routes.ts +137 -47
  991. package/src/runtime/routes/secret-routes.ts +17 -4
  992. package/src/runtime/routes/sequence-routes.ts +33 -0
  993. package/src/runtime/routes/settings-routes.ts +65 -19
  994. package/src/runtime/routes/skills-routes.ts +133 -69
  995. package/src/runtime/routes/slack-channel-routes.ts +5 -0
  996. package/src/runtime/routes/stt-routes.ts +13 -6
  997. package/src/runtime/routes/subagents-routes.ts +24 -18
  998. package/src/runtime/routes/suggest-trust-rule-routes.ts +7 -2
  999. package/src/runtime/routes/surface-action-routes.ts +10 -38
  1000. package/src/runtime/routes/surface-content-routes.ts +21 -6
  1001. package/src/runtime/routes/surface-conversation-resolver.ts +65 -0
  1002. package/src/runtime/routes/task-routes.ts +37 -0
  1003. package/src/runtime/routes/telemetry-routes.ts +9 -0
  1004. package/src/runtime/routes/trace-event-routes.ts +42 -1
  1005. package/src/runtime/routes/trust-rules-routes.ts +5 -0
  1006. package/src/runtime/routes/tts-routes.ts +13 -6
  1007. package/src/runtime/routes/types.ts +17 -8
  1008. package/src/runtime/routes/ui-request-routes.ts +5 -0
  1009. package/src/runtime/routes/upgrade-broadcast-routes.ts +5 -0
  1010. package/src/runtime/routes/usage-routes.ts +71 -3
  1011. package/src/runtime/routes/user-routes-cli.ts +9 -0
  1012. package/src/runtime/routes/user-routes.ts +5 -1
  1013. package/src/runtime/routes/wake-conversation-routes.ts +5 -0
  1014. package/src/runtime/routes/watcher-routes.ts +21 -0
  1015. package/src/runtime/routes/webhook-routes.ts +9 -0
  1016. package/src/runtime/routes/wipe-conversation-routes.ts +8 -0
  1017. package/src/runtime/routes/work-items-routes.ts +47 -19
  1018. package/src/runtime/routes/workspace-commit-routes.ts +5 -0
  1019. package/src/runtime/routes/workspace-routes.test.ts +42 -0
  1020. package/src/runtime/routes/workspace-routes.ts +120 -9
  1021. package/src/runtime/services/__tests__/analyze-conversation.test.ts +4 -4
  1022. package/src/runtime/services/analyze-conversation.ts +3 -6
  1023. package/src/runtime/services/conversation-serializer.ts +24 -2
  1024. package/src/runtime/slack-dm-text-delivery.ts +177 -0
  1025. package/src/runtime/sync/resource-sync-events.ts +17 -3
  1026. package/src/runtime/sync/sync-publisher.ts +2 -2
  1027. package/src/runtime/tool-grant-request-helper.ts +1 -0
  1028. package/src/schedule/run-script.ts +28 -3
  1029. package/src/schedule/schedule-store.ts +16 -1
  1030. package/src/schedule/scheduler.ts +114 -16
  1031. package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
  1032. package/src/security/encrypted-store.ts +7 -16
  1033. package/src/security/store-path-override.ts +61 -0
  1034. package/src/signals/user-message.ts +10 -16
  1035. package/src/skills/catalog-files.ts +4 -1
  1036. package/src/skills/clawhub-files.ts +2 -0
  1037. package/src/skills/skillssh-files.ts +2 -0
  1038. package/src/skills/validate-input.ts +177 -0
  1039. package/src/subagent/manager.ts +16 -19
  1040. package/src/subagent/types.ts +6 -0
  1041. package/src/tasks/tool-sanitizer.ts +2 -2
  1042. package/src/telemetry/types.ts +26 -0
  1043. package/src/telemetry/usage-telemetry-reporter.test.ts +138 -1
  1044. package/src/telemetry/usage-telemetry-reporter.ts +31 -0
  1045. package/src/tools/acp/spawn.test.ts +88 -38
  1046. package/src/tools/apps/definitions.ts +42 -24
  1047. package/src/tools/ask-question/ask-question-tool.test.ts +120 -105
  1048. package/src/tools/ask-question/ask-question-tool.ts +85 -90
  1049. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
  1050. package/src/tools/computer-use/definitions.ts +295 -289
  1051. package/src/tools/credential-execution/make-authenticated-request.ts +56 -51
  1052. package/src/tools/credential-execution/manage-secure-command-tool.ts +2 -2
  1053. package/src/tools/credential-execution/run-authenticated-command.ts +82 -77
  1054. package/src/tools/credentials/vault.ts +112 -111
  1055. package/src/tools/document/document-tool.ts +131 -8
  1056. package/src/tools/execution-target.ts +3 -6
  1057. package/src/tools/execution-timeout.ts +3 -4
  1058. package/src/tools/executor.ts +18 -55
  1059. package/src/tools/filesystem/edit.ts +45 -42
  1060. package/src/tools/filesystem/list.ts +33 -30
  1061. package/src/tools/filesystem/read.ts +54 -35
  1062. package/src/tools/filesystem/write.ts +34 -31
  1063. package/src/tools/host-filesystem/edit.test.ts +1 -0
  1064. package/src/tools/host-filesystem/edit.ts +44 -42
  1065. package/src/tools/host-filesystem/read.test.ts +1 -0
  1066. package/src/tools/host-filesystem/read.ts +49 -35
  1067. package/src/tools/host-filesystem/transfer.test.ts +31 -6
  1068. package/src/tools/host-filesystem/transfer.ts +121 -108
  1069. package/src/tools/host-filesystem/write.test.ts +1 -0
  1070. package/src/tools/host-filesystem/write.ts +33 -31
  1071. package/src/tools/host-terminal/host-shell.ts +50 -48
  1072. package/src/tools/mcp/mcp-tool-factory.ts +0 -2
  1073. package/src/tools/memory/register.ts +23 -24
  1074. package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
  1075. package/src/tools/network/__tests__/web-search.test.ts +211 -3
  1076. package/src/tools/network/managed-search-proxy.ts +183 -0
  1077. package/src/tools/network/web-fetch.ts +49 -46
  1078. package/src/tools/network/web-search.ts +215 -57
  1079. package/src/tools/policy-context.ts +3 -1
  1080. package/src/tools/registry.ts +184 -118
  1081. package/src/tools/schedule/create.ts +12 -1
  1082. package/src/tools/schedule/update.ts +16 -0
  1083. package/src/tools/shared/filesystem/audio-read.ts +122 -0
  1084. package/src/tools/shared/filesystem/image-read.ts +1 -1
  1085. package/src/tools/skills/execute.ts +34 -31
  1086. package/src/tools/skills/load.ts +29 -23
  1087. package/src/tools/skills/skill-tool-factory.ts +17 -36
  1088. package/src/tools/subagent/notify-parent.ts +35 -32
  1089. package/src/tools/subagent/spawn.ts +3 -0
  1090. package/src/tools/system/avatar-generator.ts +13 -22
  1091. package/src/tools/system/request-permission.ts +30 -27
  1092. package/src/tools/terminal/shell.ts +190 -61
  1093. package/src/tools/tool-approval-handler.ts +10 -4
  1094. package/src/tools/tool-defaults.ts +20 -9
  1095. package/src/tools/tool-manifest.ts +4 -4
  1096. package/src/tools/tool-name-aliases.ts +72 -14
  1097. package/src/tools/types.ts +86 -33
  1098. package/src/tools/ui-surface/definitions.ts +166 -94
  1099. package/src/types/onboarding-context.ts +6 -0
  1100. package/src/usage/attribution.ts +32 -1
  1101. package/src/usage/types.ts +10 -0
  1102. package/src/util/browser.ts +7 -2
  1103. package/src/util/errors.ts +2 -2
  1104. package/src/util/map-limit.ts +27 -0
  1105. package/src/util/platform.ts +15 -12
  1106. package/src/work-items/work-item-runner.ts +7 -2
  1107. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +7 -20
  1108. package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
  1109. package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
  1110. package/src/workspace/migrations/092-backfill-v3-leaves.ts +169 -0
  1111. package/src/workspace/migrations/093-backfill-leaf-ids.ts +144 -0
  1112. package/src/workspace/migrations/094-seed-avatar-manifest.ts +155 -0
  1113. package/src/workspace/migrations/__tests__/094-seed-avatar-manifest.test.ts +136 -0
  1114. package/src/workspace/migrations/__tests__/backfill-leaf-ids.test.ts +175 -0
  1115. package/src/workspace/migrations/__tests__/backfill-v3-leaves.test.ts +124 -0
  1116. package/src/workspace/migrations/registry.ts +10 -0
  1117. package/src/workspace/provider-commit-message-generator.ts +15 -17
  1118. package/tsconfig.json +4 -1
  1119. package/src/__tests__/history-repair-pipeline.test.ts +0 -396
  1120. package/src/cli/commands/__tests__/memory-v3-render.test.ts +0 -340
  1121. package/src/cli/commands/memory-v3-render.ts +0 -344
  1122. package/src/daemon/message-types/disk-pressure.ts +0 -9
  1123. package/src/email/feature-gate.ts +0 -23
  1124. package/src/memory/v3/__tests__/coactivation-store.test.ts +0 -422
  1125. package/src/memory/v3/__tests__/consolidation-job.test.ts +0 -468
  1126. package/src/memory/v3/__tests__/edge-learning-job.test.ts +0 -324
  1127. package/src/memory/v3/__tests__/edges.test.ts +0 -563
  1128. package/src/memory/v3/__tests__/filter.test.ts +0 -512
  1129. package/src/memory/v3/__tests__/gate.test.ts +0 -574
  1130. package/src/memory/v3/__tests__/index-composition.test.ts +0 -233
  1131. package/src/memory/v3/__tests__/loop.test.ts +0 -530
  1132. package/src/memory/v3/__tests__/retriever.test.ts +0 -226
  1133. package/src/memory/v3/__tests__/scouts.test.ts +0 -440
  1134. package/src/memory/v3/__tests__/shadow-middleware.test.ts +0 -312
  1135. package/src/memory/v3/__tests__/system-prompts.test.ts +0 -154
  1136. package/src/memory/v3/__tests__/traversal.test.ts +0 -469
  1137. package/src/memory/v3/__tests__/tree-index.test.ts +0 -280
  1138. package/src/memory/v3/__tests__/tree-store.test.ts +0 -529
  1139. package/src/memory/v3/__tests__/tree-walk.test.ts +0 -707
  1140. package/src/memory/v3/__tests__/validate.test.ts +0 -245
  1141. package/src/memory/v3/auto-edges.ts +0 -223
  1142. package/src/memory/v3/coactivation-store.ts +0 -124
  1143. package/src/memory/v3/consolidation-job.ts +0 -323
  1144. package/src/memory/v3/edge-learning-job.ts +0 -160
  1145. package/src/memory/v3/edges.ts +0 -249
  1146. package/src/memory/v3/filter.ts +0 -281
  1147. package/src/memory/v3/gate.ts +0 -334
  1148. package/src/memory/v3/index-composition.ts +0 -113
  1149. package/src/memory/v3/llm-capture.ts +0 -46
  1150. package/src/memory/v3/loop.ts +0 -382
  1151. package/src/memory/v3/maintenance.ts +0 -144
  1152. package/src/memory/v3/prompt-context.ts +0 -33
  1153. package/src/memory/v3/prompts/consolidation.ts +0 -458
  1154. package/src/memory/v3/prompts/system-prompts.ts +0 -196
  1155. package/src/memory/v3/retriever.ts +0 -33
  1156. package/src/memory/v3/scouts.ts +0 -420
  1157. package/src/memory/v3/shadow-middleware.ts +0 -305
  1158. package/src/memory/v3/traversal.ts +0 -206
  1159. package/src/memory/v3/tree-index.ts +0 -237
  1160. package/src/memory/v3/tree-store.ts +0 -394
  1161. package/src/memory/v3/tree-walk.ts +0 -351
  1162. package/src/memory/v3/validate.ts +0 -300
  1163. package/src/plugins/defaults/circuit-breaker.ts +0 -141
  1164. package/src/plugins/defaults/compaction.ts +0 -141
  1165. package/src/plugins/defaults/empty-response.ts +0 -124
  1166. package/src/plugins/defaults/history-repair.ts +0 -83
  1167. package/src/plugins/defaults/persistence.ts +0 -127
  1168. package/src/plugins/defaults/title-generate.ts +0 -90
  1169. package/src/plugins/defaults/token-estimate.ts +0 -101
  1170. package/src/plugins/defaults/tool-error.ts +0 -119
  1171. package/src/plugins/defaults/tool-result-truncate.ts +0 -84
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import type pino from "pino";
10
+ import { v4 as uuid } from "uuid";
10
11
 
11
12
  import type { AgentEvent } from "../agent/loop.js";
12
13
  import type {
@@ -16,12 +17,16 @@ import type {
16
17
  import { getConfig } from "../config/loader.js";
17
18
  import { recordEstimate } from "../context/estimator-calibration.js";
18
19
  import { getCalibrationProviderKey } from "../context/token-estimator.js";
20
+ import { projectAssistantMessage } from "../memory/conversation-attention-store.js";
19
21
  import {
22
+ deleteMessageById,
20
23
  getConversation,
21
24
  getMessageById,
25
+ messageMetadataSchema,
22
26
  provenanceFromTrustContext,
23
27
  updateMessageContent,
24
28
  } from "../memory/conversation-crud.js";
29
+ import { indexMessageNow } from "../memory/indexer.js";
25
30
  import {
26
31
  backfillMessageIdOnLogs,
27
32
  buildProviderErrorResponsePayload,
@@ -36,19 +41,24 @@ import {
36
41
  type SlackMessageMetadata,
37
42
  writeSlackMetadata,
38
43
  } from "../messaging/providers/slack/message-metadata.js";
39
- import { defaultPersistenceTerminal } from "../plugins/defaults/persistence.js";
44
+ import { defaultPersistenceTerminal } from "../plugins/defaults/persistence/terminal.js";
40
45
  import { DEFAULT_TIMEOUTS, runPipeline } from "../plugins/pipeline.js";
41
46
  import { getMiddlewaresFor } from "../plugins/registry.js";
42
47
  import type {
43
- PersistAddResult,
44
48
  PersistArgs,
49
+ PersistReserveResult,
45
50
  PersistResult,
46
51
  TurnContext,
47
52
  } from "../plugins/types.js";
48
53
  import type { ContentBlock, ImageContent } from "../providers/types.js";
49
54
  import { isContextOverflowError } from "../providers/types.js";
55
+ import { publishSyncInvalidation } from "../runtime/sync/sync-publisher.js";
50
56
  import { redactSecrets } from "../security/secret-scanner.js";
51
57
  import { extractDomain } from "../tools/network/domain-normalize.js";
58
+ import {
59
+ buildPricingUsage,
60
+ resolveStructuredPricing,
61
+ } from "../usage/pricing.js";
52
62
  import { ProviderError } from "../util/errors.js";
53
63
  import { faviconUrlForDomain } from "../util/favicon.js";
54
64
  import { getLogger } from "../util/logger.js";
@@ -57,22 +67,38 @@ import {
57
67
  cleanAssistantContent,
58
68
  drainDirectiveDisplayBuffer,
59
69
  } from "./assistant-attachments.js";
60
- import type { AgentLoopConversationContext } from "./conversation-agent-loop.js";
70
+ import type {
71
+ AgentLoopConversationContext,
72
+ AssistantSurface,
73
+ } from "./conversation-agent-loop.js";
61
74
  import {
62
75
  buildConversationErrorMessage,
63
76
  classifyConversationError,
64
77
  isContextTooLarge,
78
+ maxTokensReachedClassification,
65
79
  } from "./conversation-error.js";
66
80
  import { isProviderOrderingError } from "./conversation-slash.js";
67
81
  import { resolveTurnTimezoneContext } from "./date-context.js";
68
- import type { ServerMessage } from "./message-protocol.js";
82
+ import type {
83
+ CardSurfaceData,
84
+ ServerMessage,
85
+ SurfaceAction,
86
+ UiSurfaceShow,
87
+ } from "./message-protocol.js";
88
+ import { conversationMetadataSyncTag } from "./message-types/sync.js";
69
89
  import type {
70
90
  WebSearchMetadata,
71
91
  WebSearchResultItem,
72
92
  } from "./message-types/web-activity.js";
93
+ import { FALLBACK_TURN_TRUST } from "./trust-context.js";
73
94
 
74
95
  const log = getLogger("agent-loop-handlers");
75
96
 
97
+ // ── Partial-persistence tunables ─────────────────────────────────────
98
+ // Debounce for mid-turn `updateContent` writes from text deltas.
99
+ // Indexer + projector still fire ONLY at `handleMessageComplete`.
100
+ const PARTIAL_PERSIST_DEBOUNCE_MS = 1000;
101
+
76
102
  /**
77
103
  * Build a {@link TurnContext} from the handler's deps for pipeline logging
78
104
  * and plugin attribution.
@@ -93,11 +119,10 @@ function buildHandlerTurnContext(deps: EventHandlerDeps): TurnContext {
93
119
  requestId: deps.reqId,
94
120
  conversationId: deps.ctx.conversationId,
95
121
  turnIndex: deps.ctx.turnCount,
96
- trust: deps.ctx.currentTurnTrustContext ??
97
- deps.ctx.trustContext ?? {
98
- sourceChannel: "vellum",
99
- trustClass: "unknown",
100
- },
122
+ trust:
123
+ deps.ctx.currentTurnTrustContext ??
124
+ deps.ctx.trustContext ??
125
+ FALLBACK_TURN_TRUST,
101
126
  };
102
127
  }
103
128
 
@@ -145,6 +170,21 @@ export interface EventHandlerState {
145
170
  contextTooLargeError: unknown;
146
171
  providerErrorUserMessage: string | null;
147
172
  lastAssistantMessageId: string | undefined;
173
+ /**
174
+ * True when `handleLlmCallStarted` has reserved an empty assistant row
175
+ * that has NOT yet been finalized via `handleMessageComplete`
176
+ * (`op:"updateContent"` + indexing + projection). Used by error/retry
177
+ * paths to detect a stranded reservation that must be cleaned up
178
+ * before the next LLM call reserves a fresh row — without it, every
179
+ * retryable failure (overflow, ordering, image overflow) and every
180
+ * terminal provider rejection would leak an empty assistant bubble
181
+ * into the transcript and mispoint downstream sync/projection.
182
+ *
183
+ * Cleared by `handleMessageComplete` on successful finalize, and by
184
+ * the synthetic-error branch in `conversation-agent-loop.ts` after it
185
+ * absorbs the reserved row into the error message.
186
+ */
187
+ assistantRowAwaitingFinalization: boolean;
148
188
  readonly pendingToolResults: Map<string, PendingToolResult>;
149
189
  readonly persistedToolUseIds: Set<string>;
150
190
  readonly accumulatedDirectives: DirectiveRequest[];
@@ -153,7 +193,6 @@ export interface EventHandlerState {
153
193
  readonly toolContentBlockToolNames: Map<number, string>;
154
194
  readonly directiveWarnings: string[];
155
195
  readonly toolUseIdToName: Map<string, string>;
156
- currentTurnToolNames: string[];
157
196
  /** Sticky for the whole run: this turn created/refreshed an app. */
158
197
  appBuildToolUsedThisRun: boolean;
159
198
  /** Tracks whether the first text delta has been emitted this turn for activity state transitions. */
@@ -207,6 +246,12 @@ export interface EventHandlerState {
207
246
  readonly serverToolStartedAt: Map<string, number>;
208
247
  /** Original input from server_tool_start, keyed by tool_use_id, so the complete handler can read the query. */
209
248
  readonly serverToolInputs: Map<string, Record<string, unknown>>;
249
+ /** Active debounce timer for partial persistence; `undefined` when idle. */
250
+ pendingPartialFlushTimer: ReturnType<typeof setTimeout> | undefined;
251
+ /** In-flight partial flush write awaited at finalize to avoid overwrite races. */
252
+ pendingPartialFlushPromise: Promise<void> | undefined;
253
+ /** Running mirror of the in-flight assistant message's content. */
254
+ currentMessageContent: ContentBlock[];
210
255
  }
211
256
 
212
257
  /** Immutable context shared across event handlers within a single agent loop run. */
@@ -245,6 +290,7 @@ export function createEventHandlerState(): EventHandlerState {
245
290
  contextTooLargeError: null,
246
291
  providerErrorUserMessage: null,
247
292
  lastAssistantMessageId: undefined,
293
+ assistantRowAwaitingFinalization: false,
248
294
  pendingToolResults: new Map(),
249
295
  persistedToolUseIds: new Set(),
250
296
  accumulatedDirectives: [],
@@ -252,7 +298,6 @@ export function createEventHandlerState(): EventHandlerState {
252
298
  toolContentBlockToolNames: new Map(),
253
299
  directiveWarnings: [],
254
300
  toolUseIdToName: new Map(),
255
- currentTurnToolNames: [],
256
301
  appBuildToolUsedThisRun: false,
257
302
  firstTextDeltaEmitted: false,
258
303
  firstThinkingDeltaEmitted: false,
@@ -266,9 +311,119 @@ export function createEventHandlerState(): EventHandlerState {
266
311
  turnStartedAt: Date.now(),
267
312
  serverToolStartedAt: new Map(),
268
313
  serverToolInputs: new Map(),
314
+ pendingPartialFlushTimer: undefined,
315
+ pendingPartialFlushPromise: undefined,
316
+ currentMessageContent: [],
269
317
  };
270
318
  }
271
319
 
320
+ // ── Partial-persistence helpers ──────────────────────────────────────
321
+
322
+ /** Canonical persisted-content build: clean → append surfaces → redact. */
323
+ function buildPersistedAssistantContent(
324
+ rawBlocks: readonly ContentBlock[],
325
+ surfaces: readonly AssistantSurface[],
326
+ ): ContentBlock[] {
327
+ const { cleanedContent } = cleanAssistantContent(rawBlocks);
328
+ const cleaned = cleanedContent as ContentBlock[];
329
+ const withSurfaces: ContentBlock[] = [...cleaned];
330
+ for (const surface of surfaces) {
331
+ withSurfaces.push({
332
+ type: "ui_surface",
333
+ surfaceId: surface.surfaceId,
334
+ surfaceType: surface.surfaceType,
335
+ title: surface.title,
336
+ data: surface.data,
337
+ actions: surface.actions,
338
+ display: surface.display,
339
+ ...(surface.persistent ? { persistent: true } : {}),
340
+ ...(surface.toolCallId ? { toolCallId: surface.toolCallId } : {}),
341
+ } as unknown as ContentBlock);
342
+ }
343
+ return withSurfaces.map((block) => {
344
+ if (block.type === "text") {
345
+ const tb = block as Extract<ContentBlock, { type: "text" }>;
346
+ return { ...tb, text: redactSecrets(tb.text) };
347
+ }
348
+ return block;
349
+ });
350
+ }
351
+
352
+ /** Append a streamed text chunk to `state.currentMessageContent`, fusing into tail text block. */
353
+ function appendTextToCurrentMessage(
354
+ state: EventHandlerState,
355
+ text: string,
356
+ ): void {
357
+ if (text.length === 0) return;
358
+ const tail = state.currentMessageContent.at(-1);
359
+ if (tail && tail.type === "text") {
360
+ tail.text = tail.text + text;
361
+ } else {
362
+ state.currentMessageContent.push({ type: "text", text });
363
+ }
364
+ }
365
+
366
+ /** Reset partial-persist accumulator and any pending flush state. Idempotent. */
367
+ function resetPartialPersistAccumulator(state: EventHandlerState): void {
368
+ if (state.pendingPartialFlushTimer !== undefined) {
369
+ clearTimeout(state.pendingPartialFlushTimer);
370
+ state.pendingPartialFlushTimer = undefined;
371
+ }
372
+ state.currentMessageContent = [];
373
+ state.pendingPartialFlushPromise = undefined;
374
+ }
375
+
376
+ /** Flush `state.currentMessageContent` to the row via the persistence pipeline. */
377
+ async function flushAccumulatedContent(
378
+ state: EventHandlerState,
379
+ deps: EventHandlerDeps,
380
+ ): Promise<void> {
381
+ const messageId = state.lastAssistantMessageId;
382
+ if (messageId === undefined) return;
383
+ if (state.currentMessageContent.length === 0) return;
384
+
385
+ const built = buildPersistedAssistantContent(state.currentMessageContent, []);
386
+ const contentJson = JSON.stringify(built);
387
+
388
+ try {
389
+ await runPipeline<PersistArgs, PersistResult>(
390
+ "persistence",
391
+ getMiddlewaresFor("persistence"),
392
+ defaultPersistenceTerminal,
393
+ {
394
+ op: "updateContent",
395
+ messageId,
396
+ content: contentJson,
397
+ },
398
+ buildHandlerTurnContext(deps),
399
+ DEFAULT_TIMEOUTS.persistence,
400
+ );
401
+ } catch (err) {
402
+ deps.rlog.warn(
403
+ { err, messageId },
404
+ "partial flush of accumulated assistant content failed; finalize at message_complete will recover",
405
+ );
406
+ }
407
+ }
408
+
409
+ /** Schedule a debounced partial flush. First-scheduled wins; no-op when timer pending. */
410
+ function schedulePartialFlush(
411
+ state: EventHandlerState,
412
+ deps: EventHandlerDeps,
413
+ ): void {
414
+ if (state.pendingPartialFlushTimer !== undefined) return;
415
+ state.pendingPartialFlushTimer = setTimeout(() => {
416
+ state.pendingPartialFlushTimer = undefined;
417
+ const flushPromise = flushAccumulatedContent(state, deps);
418
+ state.pendingPartialFlushPromise = flushPromise;
419
+ void flushPromise.finally(() => {
420
+ if (state.pendingPartialFlushPromise === flushPromise) {
421
+ state.pendingPartialFlushPromise = undefined;
422
+ }
423
+ });
424
+ }, PARTIAL_PERSIST_DEBOUNCE_MS);
425
+ }
426
+
272
427
  // ── Shared Helper ────────────────────────────────────────────────────
273
428
 
274
429
  // providerNameOverride should be supplied when the caller already knows the
@@ -307,6 +462,9 @@ function emitLlmCallStartedIfNeeded(
307
462
  // tools the client discards it (extractCodePreview only handles app tools),
308
463
  // so we skip forwarding entirely to avoid transport/decode overhead.
309
464
  const APP_TOOL_NAMES = new Set(["app_create"]);
465
+ const MAX_TOKENS_CONTINUE_PROMPT =
466
+ "Continue from where you stopped. Do not repeat content you've already sent.";
467
+ const MAX_TOKENS_SURFACE_COMPLETION_SUMMARY = "Continue";
310
468
 
311
469
  // ── Friendly Tool Names ──────────────────────────────────────────────
312
470
 
@@ -389,6 +547,139 @@ function resolveAssistantReplyTimestampTimezone(
389
547
  }).effectiveTimezone;
390
548
  }
391
549
 
550
+ /**
551
+ * Assemble the metadata envelope written to the assistant message row.
552
+ *
553
+ * Stamped at reserve time (before `provider.sendMessage`) so the row carries
554
+ * channel provenance from the moment it lands in SQLite, mirroring the
555
+ * snapshot that handleMessageComplete used to compute at end-of-turn. All
556
+ * inputs (channel context, trust context, turnStartedAt) are stable across
557
+ * the LLM call, so building this once at reserve is equivalent to building
558
+ * it at complete. Slack reply rows further stamp a `slackMeta` sub-object —
559
+ * the `channelTs` field stays absent here and is back-filled by
560
+ * `deliverReplyViaCallback` after the gateway returns the ts.
561
+ */
562
+ function buildAssistantChannelMetadata(
563
+ state: EventHandlerState,
564
+ deps: EventHandlerDeps,
565
+ ): Record<string, unknown> {
566
+ const metadata: Record<string, unknown> = {
567
+ ...provenanceFromTrustContext(deps.ctx.trustContext),
568
+ userMessageChannel: deps.turnChannelContext.userMessageChannel,
569
+ assistantMessageChannel: deps.turnChannelContext.assistantMessageChannel,
570
+ userMessageInterface: deps.turnInterfaceContext.userMessageInterface,
571
+ assistantMessageInterface:
572
+ deps.turnInterfaceContext.assistantMessageInterface,
573
+ sentAt: state.turnStartedAt,
574
+ };
575
+
576
+ if (deps.turnChannelContext.assistantMessageChannel === "slack") {
577
+ const channelId = deps.ctx.trustContext?.requesterChatId;
578
+ if (channelId) {
579
+ const threadTs = getThreadTs(deps.ctx.conversationId);
580
+ const timestampTimezone = resolveAssistantReplyTimestampTimezone(
581
+ deps.ctx,
582
+ );
583
+ const timestampTimezoneLabel = formatSlackTimezoneLabel(
584
+ timestampTimezone,
585
+ { nowMs: state.turnStartedAt },
586
+ );
587
+ const partialSlackMeta: Partial<SlackMessageMetadata> = {
588
+ source: "slack",
589
+ eventKind: "message",
590
+ channelId,
591
+ ...(threadTs ? { threadTs } : {}),
592
+ timestampTimezone,
593
+ ...(timestampTimezoneLabel ? { timestampTimezoneLabel } : {}),
594
+ };
595
+ // `channelTs` is filled in by the post-send reconciliation step in
596
+ // `deliverReplyViaCallback`; cast through the Partial to satisfy
597
+ // the writer's type at this pre-send boundary.
598
+ metadata.slackMeta = writeSlackMetadata(
599
+ partialSlackMeta as SlackMessageMetadata,
600
+ );
601
+ }
602
+ }
603
+
604
+ return metadata;
605
+ }
606
+
607
+ /**
608
+ * Reserve an empty assistant row for the LLM call about to begin, stash
609
+ * its id on `state.lastAssistantMessageId`, and announce the boundary on
610
+ * the wire via `assistant_turn_start`.
611
+ *
612
+ * Awaited so the row exists and the client has the anchor id BEFORE any
613
+ * streaming delta arrives — every subsequent `deps.onEvent` in this LLM
614
+ * call stamps `messageId: state.lastAssistantMessageId`, and
615
+ * `handleMessageComplete` flushes the final content to the same row via
616
+ * `op: "updateContent"` instead of inserting a fresh one.
617
+ *
618
+ * Multi-LLM-call agent turns (LLM call → tool execution → LLM call) emit
619
+ * one `llm_call_started` per call, so each LLM call reserves its own row.
620
+ * The read-path `findDisplayTurnEndIndex` collapses consecutive assistant
621
+ * rows for the merged history view, matching today's per-call DB layout.
622
+ */
623
+ export async function handleLlmCallStarted(
624
+ state: EventHandlerState,
625
+ deps: EventHandlerDeps,
626
+ ): Promise<void> {
627
+ // Clean up an orphaned reservation from a previous LLM call in this run
628
+ // that errored before `message_complete` could finalize it. This covers
629
+ // the retryable paths (overflow, ordering, image overflow) where the
630
+ // agent loop re-enters with a fresh `run()` and reserves another row;
631
+ // without this delete the failed-attempt row stays in the transcript as
632
+ // an empty assistant bubble. The finalized-row case is filtered out via
633
+ // the `assistantRowAwaitingFinalization` flag — `handleMessageComplete`
634
+ // clears it after the successful `updateContent`, so the previous call's
635
+ // committed row is never touched here.
636
+ //
637
+ // Direct `deleteMessageById` (not via the `persistence` pipeline) is
638
+ // intentional: a never-finalized reservation has no segments, no
639
+ // attachments, and no observable history — undoing it isn't a real
640
+ // persistence event for plugins to react to, so routing through the
641
+ // pipeline would only widen the mock surface for no observability win.
642
+ if (state.assistantRowAwaitingFinalization && state.lastAssistantMessageId) {
643
+ try {
644
+ deleteMessageById(state.lastAssistantMessageId);
645
+ } catch (err) {
646
+ // Non-fatal: a leaked empty row is preferable to a turn-level throw.
647
+ deps.rlog.warn(
648
+ { err, messageId: state.lastAssistantMessageId },
649
+ "Failed to clean up stranded reserved assistant row before new reservation",
650
+ );
651
+ }
652
+ }
653
+
654
+ const metadata = buildAssistantChannelMetadata(state, deps);
655
+ const reserveResult = (await runPipeline<PersistArgs, PersistResult>(
656
+ "persistence",
657
+ getMiddlewaresFor("persistence"),
658
+ defaultPersistenceTerminal,
659
+ {
660
+ op: "reserve",
661
+ conversationId: deps.ctx.conversationId,
662
+ role: "assistant",
663
+ metadata,
664
+ },
665
+ buildHandlerTurnContext(deps),
666
+ DEFAULT_TIMEOUTS.persistence,
667
+ )) as PersistReserveResult;
668
+ state.lastAssistantMessageId = reserveResult.message.id;
669
+ state.assistantRowAwaitingFinalization = true;
670
+ // Fresh row → fresh accumulator. If an earlier (failed) LLM call
671
+ // within the same run left partial state behind, the
672
+ // `assistantRowAwaitingFinalization` cleanup above already deleted
673
+ // the orphan row, so the accumulator content would point at a
674
+ // non-existent id. Reset here so the new row starts from zero.
675
+ resetPartialPersistAccumulator(state);
676
+ deps.onEvent({
677
+ type: "assistant_turn_start",
678
+ messageId: reserveResult.message.id,
679
+ conversationId: deps.ctx.conversationId,
680
+ });
681
+ }
682
+
392
683
  // ── Individual Handlers ──────────────────────────────────────────────
393
684
 
394
685
  function handleTextDelta(
@@ -405,20 +696,22 @@ function handleTextDelta(
405
696
  if (drained.emitText.length > 0) {
406
697
  if (!state.firstTextDeltaEmitted) {
407
698
  state.firstTextDeltaEmitted = true;
408
- deps.ctx.emitActivityState(
409
- "streaming",
410
- "first_text_delta",
411
- "assistant_turn",
412
- deps.reqId,
413
- "Thinking",
414
- );
699
+ deps.ctx.emitActivityState("streaming", "first_text_delta", {
700
+ requestId: deps.reqId,
701
+ statusText: "Thinking",
702
+ });
415
703
  }
416
704
  deps.onEvent({
417
705
  type: "assistant_text_delta",
418
706
  text: drained.emitText,
419
707
  conversationId: deps.ctx.conversationId,
708
+ messageId: state.lastAssistantMessageId,
420
709
  });
421
710
  if (deps.shouldGenerateTitle) state.firstAssistantText += drained.emitText;
711
+ // Mirror the drained delta into state.currentMessageContent so partial
712
+ // flushes mid-turn see the same content the user is watching live.
713
+ appendTextToCurrentMessage(state, drained.emitText);
714
+ schedulePartialFlush(state, deps);
422
715
  }
423
716
  }
424
717
 
@@ -439,13 +732,10 @@ function handleThinkingDelta(
439
732
  // assistantStatusText for every assistant_activity_state event.
440
733
  if (lastToolName) {
441
734
  const statusText = `Processing ${friendlyToolName(lastToolName)} results`;
442
- deps.ctx.emitActivityState(
443
- "thinking",
444
- "thinking_delta",
445
- "assistant_turn",
446
- deps.reqId,
735
+ deps.ctx.emitActivityState("thinking", "thinking_delta", {
736
+ requestId: deps.reqId,
447
737
  statusText,
448
- );
738
+ });
449
739
  }
450
740
  }
451
741
  if (!deps.ctx.streamThinking) return;
@@ -454,6 +744,7 @@ function handleThinkingDelta(
454
744
  type: "assistant_thinking_delta",
455
745
  thinking: event.thinking,
456
746
  conversationId: deps.ctx.conversationId,
747
+ messageId: state.lastAssistantMessageId,
457
748
  });
458
749
  }
459
750
 
@@ -463,7 +754,6 @@ export function handleToolUse(
463
754
  event: Extract<AgentEvent, { type: "tool_use" }>,
464
755
  ): void {
465
756
  state.toolUseIdToName.set(event.id, event.name);
466
- state.currentTurnToolNames.push(event.name);
467
757
  if (event.name === "app_create" || event.name === "app_refresh") {
468
758
  state.appBuildToolUsedThisRun = true;
469
759
  }
@@ -471,24 +761,22 @@ export function handleToolUse(
471
761
  state.currentToolUseId = event.id;
472
762
  state.currentTurnToolUseIds.push(event.id);
473
763
  const statusText = computeToolUseStatusText(event.name, event.input);
474
- deps.ctx.emitActivityState(
475
- "tool_running",
476
- "tool_use_start",
477
- "assistant_turn",
478
- deps.reqId,
764
+ deps.ctx.emitActivityState("tool_running", "tool_use_start", {
765
+ requestId: deps.reqId,
479
766
  statusText,
480
- );
767
+ });
481
768
  deps.onEvent({
482
769
  type: "tool_use_start",
483
770
  toolName: event.name,
484
771
  input: event.input,
485
772
  conversationId: deps.ctx.conversationId,
486
773
  toolUseId: event.id,
774
+ messageId: state.lastAssistantMessageId,
487
775
  });
488
776
  }
489
777
 
490
778
  export function handleToolUsePreviewStart(
491
- _state: EventHandlerState,
779
+ state: EventHandlerState,
492
780
  deps: EventHandlerDeps,
493
781
  event: Extract<AgentEvent, { type: "tool_use_preview_start" }>,
494
782
  ): void {
@@ -497,19 +785,17 @@ export function handleToolUsePreviewStart(
497
785
  toolUseId: event.toolUseId,
498
786
  toolName: event.toolName,
499
787
  conversationId: deps.ctx.conversationId,
788
+ messageId: state.lastAssistantMessageId,
500
789
  });
501
790
  const statusText = `Preparing ${friendlyToolName(event.toolName)}...`;
502
- deps.ctx.emitActivityState(
503
- "tool_running",
504
- "preview_start",
505
- "assistant_turn",
506
- deps.reqId,
791
+ deps.ctx.emitActivityState("tool_running", "preview_start", {
792
+ requestId: deps.reqId,
507
793
  statusText,
508
- );
794
+ });
509
795
  }
510
796
 
511
797
  function handleToolOutputChunk(
512
- _state: EventHandlerState,
798
+ state: EventHandlerState,
513
799
  deps: EventHandlerDeps,
514
800
  event: Extract<AgentEvent, { type: "tool_output_chunk" }>,
515
801
  ): void {
@@ -567,6 +853,7 @@ function handleToolOutputChunk(
567
853
  chunk: event.chunk,
568
854
  conversationId: deps.ctx.conversationId,
569
855
  toolUseId: event.toolUseId,
856
+ messageId: state.lastAssistantMessageId,
570
857
  subType: structured.subType,
571
858
  subToolName: structured.subToolName,
572
859
  subToolInput: structured.subToolInput,
@@ -579,12 +866,13 @@ function handleToolOutputChunk(
579
866
  chunk: event.chunk,
580
867
  conversationId: deps.ctx.conversationId,
581
868
  toolUseId: event.toolUseId,
869
+ messageId: state.lastAssistantMessageId,
582
870
  });
583
871
  }
584
872
  }
585
873
 
586
874
  export function handleInputJsonDelta(
587
- _state: EventHandlerState,
875
+ state: EventHandlerState,
588
876
  deps: EventHandlerDeps,
589
877
  event: Extract<AgentEvent, { type: "input_json_delta" }>,
590
878
  ): void {
@@ -598,6 +886,7 @@ export function handleInputJsonDelta(
598
886
  content: event.accumulatedJson,
599
887
  conversationId: deps.ctx.conversationId,
600
888
  toolUseId: event.toolUseId,
889
+ messageId: state.lastAssistantMessageId,
601
890
  });
602
891
  }
603
892
 
@@ -690,13 +979,10 @@ export function handleToolResult(
690
979
  const statusText = `Processing ${friendlyToolName(
691
980
  state.lastCompletedToolName ?? "",
692
981
  )} results`;
693
- deps.ctx.emitActivityState(
694
- "thinking",
695
- "tool_result_received",
696
- "assistant_turn",
697
- deps.reqId,
982
+ deps.ctx.emitActivityState("thinking", "tool_result_received", {
983
+ requestId: deps.reqId,
698
984
  statusText,
699
- );
985
+ });
700
986
 
701
987
  // Once all tools for this turn have completed, annotate the persisted
702
988
  // assistant message with timing and confirmation metadata.
@@ -724,6 +1010,7 @@ export function handleToolResult(
724
1010
  diff: event.diff,
725
1011
  status: event.status,
726
1012
  conversationId: deps.ctx.conversationId,
1013
+ messageId: state.lastAssistantMessageId,
727
1014
  imageData: imageDataList?.[0],
728
1015
  imageDataList,
729
1016
  toolUseId: event.toolUseId,
@@ -827,6 +1114,7 @@ function annotatePersistedAssistantMessage(
827
1114
  actions: surface.actions,
828
1115
  display: surface.display,
829
1116
  ...(surface.persistent ? { persistent: true } : {}),
1117
+ ...(surface.toolCallId ? { toolCallId: surface.toolCallId } : {}),
830
1118
  } as unknown as ContentBlock);
831
1119
  }
832
1120
  modified = true;
@@ -903,6 +1191,69 @@ function handleError(
903
1191
  }
904
1192
  }
905
1193
 
1194
+ export function handleMaxTokensReached(
1195
+ _state: EventHandlerState,
1196
+ deps: EventHandlerDeps,
1197
+ event: Extract<AgentEvent, { type: "max_tokens_reached" }>,
1198
+ ): void {
1199
+ const classified = maxTokensReachedClassification();
1200
+ const surfaceId = `max_tokens_${uuid()}`;
1201
+ const data: CardSurfaceData = {
1202
+ title: "Response limit reached",
1203
+ subtitle: "The partial response above was saved.",
1204
+ body: classified.userMessage,
1205
+ };
1206
+ const actions: SurfaceAction[] = [
1207
+ {
1208
+ id: "relay_prompt",
1209
+ label: "Continue",
1210
+ style: "primary",
1211
+ data: {
1212
+ prompt: MAX_TOKENS_CONTINUE_PROMPT,
1213
+ _completeSurface: true,
1214
+ _completionSummary: MAX_TOKENS_SURFACE_COMPLETION_SUMMARY,
1215
+ },
1216
+ },
1217
+ ];
1218
+
1219
+ deps.ctx.surfaceState.set(surfaceId, {
1220
+ surfaceType: "card",
1221
+ title: data.title,
1222
+ data,
1223
+ actions,
1224
+ });
1225
+ deps.ctx.currentTurnSurfaces.push({
1226
+ surfaceId,
1227
+ surfaceType: "card",
1228
+ title: data.title,
1229
+ data,
1230
+ actions,
1231
+ display: "inline",
1232
+ persistent: true,
1233
+ });
1234
+
1235
+ deps.rlog.warn(
1236
+ {
1237
+ conversationId: deps.ctx.conversationId,
1238
+ stopReason: event.stopReason,
1239
+ surfaceId,
1240
+ },
1241
+ "Surfacing max-tokens continuation card",
1242
+ );
1243
+
1244
+ deps.onEvent({
1245
+ type: "ui_surface_show",
1246
+ conversationId: deps.ctx.conversationId,
1247
+ surfaceId,
1248
+ surfaceType: "card",
1249
+ title: data.title,
1250
+ data,
1251
+ actions,
1252
+ display: "inline",
1253
+ persistent: true,
1254
+ } as UiSurfaceShow);
1255
+ }
1256
+
906
1257
  export async function handleMessageComplete(
907
1258
  state: EventHandlerState,
908
1259
  deps: EventHandlerDeps,
@@ -911,12 +1262,36 @@ export async function handleMessageComplete(
911
1262
  // Reset per-turn tool tracking for the new turn.
912
1263
  state.currentTurnToolUseIds = [];
913
1264
 
1265
+ // Cancel any pending debounced partial flush and await an already
1266
+ // in-flight one before the authoritative `updateContent` below.
1267
+ // Without the timer-clear, a timer that fires during this handler
1268
+ // could double-write (idempotent in content but wastes a write) or
1269
+ // race ahead of the indexer/projector and serve a stale snapshot.
1270
+ // Without the await, a partial pipeline call that was dispatched a
1271
+ // moment before this handler can settle AFTER the final write and
1272
+ // overwrite the authoritative row.
1273
+ if (state.pendingPartialFlushTimer !== undefined) {
1274
+ clearTimeout(state.pendingPartialFlushTimer);
1275
+ state.pendingPartialFlushTimer = undefined;
1276
+ }
1277
+ if (state.pendingPartialFlushPromise !== undefined) {
1278
+ try {
1279
+ await state.pendingPartialFlushPromise;
1280
+ } catch {
1281
+ // The partial flush swallows its own pipeline errors via
1282
+ // `rlog.warn`; the `try`/`catch` here is defensive against
1283
+ // future changes that might surface them.
1284
+ }
1285
+ state.pendingPartialFlushPromise = undefined;
1286
+ }
1287
+
914
1288
  // Flush any remaining directive display buffer
915
1289
  if (state.pendingDirectiveDisplayBuffer.length > 0) {
916
1290
  deps.onEvent({
917
1291
  type: "assistant_text_delta",
918
1292
  text: state.pendingDirectiveDisplayBuffer,
919
1293
  conversationId: deps.ctx.conversationId,
1294
+ messageId: state.lastAssistantMessageId,
920
1295
  });
921
1296
  if (deps.shouldGenerateTitle)
922
1297
  state.firstAssistantText += state.pendingDirectiveDisplayBuffer;
@@ -980,13 +1355,14 @@ export async function handleMessageComplete(
980
1355
  state.pendingToolResults.clear();
981
1356
  }
982
1357
 
983
- // Clean assistant content and accumulate directives
984
- const {
985
- cleanedContent,
986
- directives: msgDirectives,
987
- warnings: msgWarnings,
988
- } = cleanAssistantContent(event.message.content);
989
- const cleanedBlocks = cleanedContent as ContentBlock[];
1358
+ // Accumulate directives + warnings from the assistant content for
1359
+ // downstream attachment processing. `cleanAssistantContent` is also
1360
+ // called inside {@link buildPersistedAssistantContent} below; running
1361
+ // it here separately is the cheapest way to keep the directive
1362
+ // side-effects local to this handler while letting the shared helper
1363
+ // own the persisted-content shape.
1364
+ const { directives: msgDirectives, warnings: msgWarnings } =
1365
+ cleanAssistantContent(event.message.content);
990
1366
  state.accumulatedDirectives.push(...msgDirectives);
991
1367
  state.directiveWarnings.push(...msgWarnings);
992
1368
  if (msgDirectives.length > 0) {
@@ -1008,104 +1384,135 @@ export async function handleMessageComplete(
1008
1384
  // are applied in handleToolResult after all tools for the turn complete,
1009
1385
  // then the persisted message is updated via updateMessageContent.
1010
1386
 
1011
- // Build content with UI surfaces
1012
- const contentWithSurfaces: ContentBlock[] = [...cleanedBlocks];
1013
- for (const surface of deps.ctx.currentTurnSurfaces) {
1014
- contentWithSurfaces.push({
1015
- type: "ui_surface",
1016
- surfaceId: surface.surfaceId,
1017
- surfaceType: surface.surfaceType,
1018
- title: surface.title,
1019
- data: surface.data,
1020
- actions: surface.actions,
1021
- display: surface.display,
1022
- ...(surface.persistent ? { persistent: true } : {}),
1023
- } as unknown as ContentBlock);
1024
- }
1025
-
1026
- const assistantChannelMetadata: Record<string, unknown> = {
1027
- ...provenanceFromTrustContext(deps.ctx.trustContext),
1028
- userMessageChannel: deps.turnChannelContext.userMessageChannel,
1029
- assistantMessageChannel: deps.turnChannelContext.assistantMessageChannel,
1030
- userMessageInterface: deps.turnInterfaceContext.userMessageInterface,
1031
- assistantMessageInterface:
1032
- deps.turnInterfaceContext.assistantMessageInterface,
1033
- sentAt: state.turnStartedAt,
1034
- };
1387
+ // Build the canonical persisted content (cleaned + surfaces +
1388
+ // redacted) via the shared helper. The partial-persist flush uses
1389
+ // the same helper with `surfaces=[]` so a mid-turn snapshot lands in
1390
+ // the same shape as the finalize.
1391
+ const contentForPersistence = buildPersistedAssistantContent(
1392
+ event.message.content as ContentBlock[],
1393
+ deps.ctx.currentTurnSurfaces,
1394
+ );
1035
1395
 
1036
- // When the assistant is replying through Slack, stamp a `slackMeta`
1037
- // sub-object so the transcript-rendering / thread-aware-context lookup
1038
- // can identify this row's thread without joining tables.
1039
- // Persistence happens BEFORE the Slack adapter sends the message, so
1040
- // Slack's authoritative `ts` (-> `channelTs`) is not yet known and is
1041
- // intentionally omitted here. The post-send reconciliation step in
1042
- // `deliverReplyViaCallback` writes `channelTs` back into this row once
1043
- // the gateway returns the Slack-assigned ts, restoring a fully-formed
1044
- // metadata envelope before any subsequent turn reads the row.
1045
- if (deps.turnChannelContext.assistantMessageChannel === "slack") {
1046
- const channelId = deps.ctx.trustContext?.requesterChatId;
1047
- if (channelId) {
1048
- const threadTs = getThreadTs(deps.ctx.conversationId);
1049
- const timestampTimezone = resolveAssistantReplyTimestampTimezone(
1050
- deps.ctx,
1051
- );
1052
- const timestampTimezoneLabel = formatSlackTimezoneLabel(
1053
- timestampTimezone,
1054
- { nowMs: state.turnStartedAt },
1055
- );
1056
- const partialSlackMeta: Partial<SlackMessageMetadata> = {
1057
- source: "slack",
1058
- eventKind: "message",
1059
- channelId,
1060
- ...(threadTs ? { threadTs } : {}),
1061
- timestampTimezone,
1062
- ...(timestampTimezoneLabel ? { timestampTimezoneLabel } : {}),
1063
- };
1064
- assistantChannelMetadata.slackMeta = writeSlackMetadata(
1065
- // `channelTs` is filled in by the post-send reconciliation step in
1066
- // `deliverReplyViaCallback`; cast through the Partial to satisfy
1067
- // the writer's type at this pre-send boundary.
1068
- partialSlackMeta as SlackMessageMetadata,
1069
- );
1070
- }
1396
+ // The row was reserved at `llm_call_started` (with channel metadata
1397
+ // stamped at that point) and `state.lastAssistantMessageId` carries its
1398
+ // id. Flush the final content via `updateContent` instead of inserting a
1399
+ // new row. No `syncToDisk` flag here the orchestrator separately
1400
+ // invokes `syncMessageToDisk` on `state.lastAssistantMessageId` after
1401
+ // the loop completes (see
1402
+ // `conversation-agent-loop.ts::syncLastAssistantMessageToDisk`).
1403
+ const assistantMessageId = state.lastAssistantMessageId;
1404
+ if (!assistantMessageId) {
1405
+ throw new Error(
1406
+ "handleMessageComplete fired without a prior llm_call_started reserving an assistant row",
1407
+ );
1071
1408
  }
1072
- // Redact known-pattern secrets from assistant text blocks before they are
1073
- // written to durable storage. Non-text blocks (images, UI surfaces) pass
1074
- // through unchanged. The live model history retains the original values.
1075
- const contentForPersistence = contentWithSurfaces.map((block) => {
1076
- if (block.type === "text") {
1077
- const tb = block as Extract<ContentBlock, { type: "text" }>;
1078
- return { ...tb, text: redactSecrets(tb.text) };
1079
- }
1080
- return block;
1081
- });
1082
-
1083
- // Route the assistant-message persistence through the `persistence`
1084
- // pipeline. No `syncToDisk` here — the orchestrator separately invokes
1085
- // `syncMessageToDisk` on `state.lastAssistantMessageId` after the loop
1086
- // completes (see `conversation-agent-loop.ts::syncLastAssistantMessageToDisk`).
1087
- const assistantPersistResult = (await runPipeline<PersistArgs, PersistResult>(
1409
+ const contentJson = JSON.stringify(contentForPersistence);
1410
+ await runPipeline<PersistArgs, PersistResult>(
1088
1411
  "persistence",
1089
1412
  getMiddlewaresFor("persistence"),
1090
1413
  defaultPersistenceTerminal,
1091
1414
  {
1092
- op: "add",
1093
- conversationId: deps.ctx.conversationId,
1094
- role: "assistant",
1095
- content: JSON.stringify(contentForPersistence),
1096
- metadata: assistantChannelMetadata,
1415
+ op: "updateContent",
1416
+ messageId: assistantMessageId,
1417
+ content: contentJson,
1097
1418
  },
1098
1419
  buildHandlerTurnContext(deps),
1099
1420
  DEFAULT_TIMEOUTS.persistence,
1100
- )) as PersistAddResult;
1101
- const assistantMsg = assistantPersistResult.message;
1102
- state.lastAssistantMessageId = assistantMsg.id;
1421
+ );
1422
+ state.assistantRowAwaitingFinalization = false;
1423
+ // Reset the partial-persist mirror so subsequent calls in this turn
1424
+ // start with an empty running view.
1425
+ state.currentMessageContent = [];
1426
+
1427
+ // ── Indexing + attention projection (restored from the pre-B3 `add` path) ──
1428
+ // `reserveMessage` + `updateMessageContent` are CRUD-only: they don't run
1429
+ // the memory indexer or the attention-cursor projector. The pre-B3 path
1430
+ // wrote the row via `addMessage`, which ran both as side-effects of the
1431
+ // insert. Calling them here keeps the assistant row's external state
1432
+ // (Qdrant segments, conversation attention cursor) in lockstep with the
1433
+ // finalized content. Both are non-fatal — a memory hiccup must not
1434
+ // escalate a successful generation into a turn-level throw. Indexing
1435
+ // intentionally fires AFTER `updateContent` succeeds so we never index
1436
+ // the empty reserved placeholder.
1437
+ const finalizedRow = getMessageById(
1438
+ assistantMessageId,
1439
+ deps.ctx.conversationId,
1440
+ );
1441
+ if (finalizedRow) {
1442
+ let provenanceTrustClass:
1443
+ | "guardian"
1444
+ | "trusted_contact"
1445
+ | "unknown"
1446
+ | undefined;
1447
+ let automated: boolean | undefined;
1448
+ if (finalizedRow.metadata) {
1449
+ try {
1450
+ const parsedMeta = messageMetadataSchema.safeParse(
1451
+ JSON.parse(finalizedRow.metadata),
1452
+ );
1453
+ if (parsedMeta.success) {
1454
+ provenanceTrustClass = parsedMeta.data.provenanceTrustClass;
1455
+ automated = parsedMeta.data.automated;
1456
+ }
1457
+ } catch {
1458
+ // Malformed metadata JSON — fall through with undefined fields,
1459
+ // matching the legacy behavior in `addMessage`.
1460
+ }
1461
+ }
1462
+ try {
1463
+ await indexMessageNow(
1464
+ {
1465
+ messageId: assistantMessageId,
1466
+ conversationId: deps.ctx.conversationId,
1467
+ role: "assistant",
1468
+ content: contentJson,
1469
+ createdAt: finalizedRow.createdAt,
1470
+ scopeId: "default",
1471
+ provenanceTrustClass,
1472
+ automated,
1473
+ },
1474
+ getConfig().memory,
1475
+ );
1476
+ } catch (err) {
1477
+ deps.rlog.warn(
1478
+ {
1479
+ err,
1480
+ conversationId: deps.ctx.conversationId,
1481
+ messageId: assistantMessageId,
1482
+ },
1483
+ "Failed to index assistant message for memory (non-fatal)",
1484
+ );
1485
+ }
1486
+ try {
1487
+ const attentionStateChanged = projectAssistantMessage({
1488
+ conversationId: deps.ctx.conversationId,
1489
+ messageId: assistantMessageId,
1490
+ messageAt: finalizedRow.createdAt,
1491
+ });
1492
+ if (attentionStateChanged) {
1493
+ void publishSyncInvalidation([
1494
+ conversationMetadataSyncTag(deps.ctx.conversationId),
1495
+ ]);
1496
+ }
1497
+ } catch (err) {
1498
+ deps.rlog.warn(
1499
+ {
1500
+ err,
1501
+ conversationId: deps.ctx.conversationId,
1502
+ messageId: assistantMessageId,
1503
+ },
1504
+ "Failed to project assistant message for attention tracking (non-fatal)",
1505
+ );
1506
+ }
1507
+ }
1103
1508
 
1104
1509
  // Backfill message_id on all LLM request logs from this turn.
1105
1510
  // The agent loop is single-threaded per conversation, so all rows with
1106
- // message_id IS NULL belong to the current turn.
1511
+ // message_id IS NULL belong to the current turn. The reserved id was
1512
+ // available before the LLM call ran but the logs are inserted DURING
1513
+ // the call, so the sweep still runs here.
1107
1514
  try {
1108
- backfillMessageIdOnLogs(deps.ctx.conversationId, assistantMsg.id);
1515
+ backfillMessageIdOnLogs(deps.ctx.conversationId, assistantMessageId);
1109
1516
  } catch (err) {
1110
1517
  deps.rlog.warn(
1111
1518
  { err },
@@ -1114,7 +1521,10 @@ export async function handleMessageComplete(
1114
1521
  }
1115
1522
 
1116
1523
  try {
1117
- backfillMemoryRecallLogMessageId(deps.ctx.conversationId, assistantMsg.id);
1524
+ backfillMemoryRecallLogMessageId(
1525
+ deps.ctx.conversationId,
1526
+ assistantMessageId,
1527
+ );
1118
1528
  } catch (err) {
1119
1529
  deps.rlog.warn(
1120
1530
  { err },
@@ -1125,7 +1535,7 @@ export async function handleMessageComplete(
1125
1535
  try {
1126
1536
  backfillMemoryV2ActivationMessageId(
1127
1537
  deps.ctx.conversationId,
1128
- assistantMsg.id,
1538
+ assistantMessageId,
1129
1539
  );
1130
1540
  } catch (err) {
1131
1541
  deps.rlog.warn(
@@ -1136,8 +1546,10 @@ export async function handleMessageComplete(
1136
1546
 
1137
1547
  deps.ctx.currentTurnSurfaces = [];
1138
1548
 
1139
- // Emit trace event
1140
- const charCount = cleanedBlocks
1549
+ // Emit trace event. Char count is computed from the cleaned +
1550
+ // redacted text blocks (UI surface blocks filtered out via the
1551
+ // type guard) — same shape as what was just persisted.
1552
+ const charCount = contentForPersistence
1141
1553
  .filter(
1142
1554
  (b): b is Extract<ContentBlock, { type: "text" }> => b.type === "text",
1143
1555
  )
@@ -1236,6 +1648,36 @@ function handleUsage(
1236
1648
  },
1237
1649
  );
1238
1650
  state.llmCallStartedEmitted = false;
1651
+
1652
+ // Emit a lightweight per-call usage progress event so clients can show
1653
+ // live-updating token/cost metrics. This is a UI hint only — no DB writes.
1654
+ const pricingUsage = buildPricingUsage({
1655
+ providerName,
1656
+ model: event.model,
1657
+ inputTokens: event.inputTokens,
1658
+ outputTokens: event.outputTokens,
1659
+ cacheCreationInputTokens: event.cacheCreationInputTokens,
1660
+ cacheReadInputTokens: event.cacheReadInputTokens,
1661
+ rawResponse: event.rawResponse,
1662
+ });
1663
+ const pricing = resolveStructuredPricing(
1664
+ providerName,
1665
+ event.model,
1666
+ pricingUsage,
1667
+ );
1668
+ const estimatedCost =
1669
+ pricing.pricingStatus === "priced" && pricing.estimatedCostUsd != null
1670
+ ? pricing.estimatedCostUsd
1671
+ : 0;
1672
+
1673
+ deps.onEvent({
1674
+ type: "usage_progress",
1675
+ conversationId: deps.ctx.conversationId,
1676
+ inputTokens: event.inputTokens,
1677
+ outputTokens: event.outputTokens,
1678
+ estimatedCost,
1679
+ model: event.model,
1680
+ });
1239
1681
  }
1240
1682
 
1241
1683
  /**
@@ -1295,6 +1737,9 @@ export async function dispatchAgentEvent(
1295
1737
  ): Promise<void> {
1296
1738
  try {
1297
1739
  switch (event.type) {
1740
+ case "llm_call_started":
1741
+ await handleLlmCallStarted(state, deps);
1742
+ break;
1298
1743
  case "text_delta":
1299
1744
  handleTextDelta(state, deps, event);
1300
1745
  break;
@@ -1320,13 +1765,10 @@ export async function dispatchAgentEvent(
1320
1765
  const query =
1321
1766
  typeof event.input.query === "string" ? event.input.query : "";
1322
1767
  const statusText = formatSearchStatusText(event.name, query);
1323
- deps.ctx.emitActivityState(
1324
- "tool_running",
1325
- "tool_use_start",
1326
- "assistant_turn",
1327
- deps.reqId,
1768
+ deps.ctx.emitActivityState("tool_running", "tool_use_start", {
1769
+ requestId: deps.reqId,
1328
1770
  statusText,
1329
- );
1771
+ });
1330
1772
  state.serverToolStartedAt.set(event.toolUseId, Date.now());
1331
1773
  state.serverToolInputs.set(event.toolUseId, event.input);
1332
1774
  deps.onEvent({
@@ -1335,17 +1777,15 @@ export async function dispatchAgentEvent(
1335
1777
  input: event.input,
1336
1778
  conversationId: deps.ctx.conversationId,
1337
1779
  toolUseId: event.toolUseId,
1780
+ messageId: state.lastAssistantMessageId,
1338
1781
  });
1339
1782
  break;
1340
1783
  }
1341
1784
  case "server_tool_complete": {
1342
- deps.ctx.emitActivityState(
1343
- "streaming",
1344
- "tool_result_received",
1345
- "assistant_turn",
1346
- deps.reqId,
1347
- "Thinking",
1348
- );
1785
+ deps.ctx.emitActivityState("streaming", "tool_result_received", {
1786
+ requestId: deps.reqId,
1787
+ statusText: "Thinking",
1788
+ });
1349
1789
 
1350
1790
  // Prefer `resolvedInput` (Anthropic's accumulated server-tool input,
1351
1791
  // populated on content_block_stop) over the input captured at
@@ -1414,13 +1854,31 @@ export async function dispatchAgentEvent(
1414
1854
  isError: event.isError,
1415
1855
  conversationId: deps.ctx.conversationId,
1416
1856
  toolUseId: event.toolUseId,
1857
+ messageId: state.lastAssistantMessageId,
1417
1858
  ...(metadata ? { activityMetadata: { webSearch: metadata } } : {}),
1418
1859
  });
1419
1860
  break;
1420
1861
  }
1862
+ case "context_compacting":
1863
+ deps.ctx.emitActivityState("thinking", "context_compacting", {
1864
+ requestId: deps.reqId,
1865
+ statusText: "Compacting context",
1866
+ });
1867
+ break;
1868
+ case "compaction_circuit_open":
1869
+ case "compaction_circuit_closed":
1870
+ // Circuit-breaker transitions are already in wire-contract shape
1871
+ // (a subset of ServerMessage), so forward them to the client sink
1872
+ // unchanged. They drive the client's "auto-compaction paused"
1873
+ // banner.
1874
+ deps.onEvent(event);
1875
+ break;
1421
1876
  case "error":
1422
1877
  handleError(state, deps, event);
1423
1878
  break;
1879
+ case "max_tokens_reached":
1880
+ handleMaxTokensReached(state, deps, event);
1881
+ break;
1424
1882
  case "provider_error":
1425
1883
  handleProviderError(deps, event);
1426
1884
  break;