@vellumai/assistant 0.8.6 → 0.8.7-dev.202606052118.34cd356

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 (1078) hide show
  1. package/AGENTS.md +4 -4
  2. package/Dockerfile +21 -4
  3. package/bun.lock +13 -4
  4. package/docker-entrypoint.sh +12 -8
  5. package/docker-init-apt-root.sh +3 -1
  6. package/docker-kata-apt-env.sh +3 -1
  7. package/docker-kata-runtime-family.sh +12 -0
  8. package/docs/architecture/memory.md +1 -1
  9. package/docs/plugins.md +110 -83
  10. package/examples/plugins/echo/README.md +13 -12
  11. package/examples/plugins/echo/register.ts +0 -54
  12. package/knip.json +1 -0
  13. package/node_modules/@vellumai/environments/bun.lock +24 -0
  14. package/node_modules/@vellumai/environments/package.json +18 -0
  15. package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +95 -0
  16. package/node_modules/@vellumai/environments/src/index.ts +11 -0
  17. package/node_modules/@vellumai/environments/src/seeds.ts +73 -0
  18. package/node_modules/@vellumai/environments/src/types.ts +70 -0
  19. package/node_modules/@vellumai/environments/tsconfig.json +20 -0
  20. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +11 -0
  21. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +3 -4
  22. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
  23. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +13 -8
  24. package/openapi.yaml +6964 -539
  25. package/package.json +8 -4
  26. package/scripts/generate-openapi.ts +88 -54
  27. package/src/__tests__/agent-loop-callsite-precedence.test.ts +42 -80
  28. package/src/__tests__/agent-loop-exit-reason.test.ts +188 -45
  29. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +141 -0
  30. package/src/__tests__/agent-loop-override-profile.test.ts +19 -32
  31. package/src/__tests__/agent-loop-provider-error-recording.test.ts +7 -5
  32. package/src/__tests__/agent-loop-thinking.test.ts +17 -12
  33. package/src/__tests__/agent-loop.test.ts +238 -422
  34. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +6 -2
  35. package/src/__tests__/agent-wake-override-profile.test.ts +22 -40
  36. package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
  37. package/src/__tests__/annotate-risk-options.test.ts +2 -3
  38. package/src/__tests__/anthropic-provider.test.ts +296 -57
  39. package/src/__tests__/app-builder-skill-instructions.test.ts +22 -0
  40. package/src/__tests__/app-control-flow.test.ts +6 -1
  41. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  42. package/src/__tests__/approval-cascade.test.ts +4 -11
  43. package/src/__tests__/approval-routes-http.test.ts +8 -3
  44. package/src/__tests__/assistant-event-hub.test.ts +25 -0
  45. package/src/__tests__/assistant-event.test.ts +15 -0
  46. package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
  47. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
  48. package/src/__tests__/assistant-stream-state.test.ts +645 -0
  49. package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
  50. package/src/__tests__/avatar-e2e.test.ts +7 -37
  51. package/src/__tests__/avatar-generator.test.ts +12 -42
  52. package/src/__tests__/avatar-identity-sync.test.ts +28 -3
  53. package/src/__tests__/background-shell-bash.test.ts +3 -7
  54. package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
  55. package/src/__tests__/btw-routes.test.ts +69 -15
  56. package/src/__tests__/build-persisted-content.test.ts +184 -0
  57. package/src/__tests__/call-pointer-messages.test.ts +5 -3
  58. package/src/__tests__/call-site-routing-provider.test.ts +22 -40
  59. package/src/__tests__/catalog-files.test.ts +1 -0
  60. package/src/__tests__/channel-approval-routes.test.ts +49 -21
  61. package/src/__tests__/channel-approvals.test.ts +4 -2
  62. package/src/__tests__/channel-invite-transport.test.ts +1 -5
  63. package/src/__tests__/channel-readiness-routes.test.ts +0 -4
  64. package/src/__tests__/channel-readiness-slack-remote.test.ts +2 -7
  65. package/src/__tests__/channel-retry-sweep.test.ts +71 -79
  66. package/src/__tests__/clawhub-files.test.ts +1 -0
  67. package/src/__tests__/compaction-circuit.test.ts +258 -0
  68. package/src/__tests__/compaction-direct.test.ts +132 -0
  69. package/src/__tests__/compaction-events.test.ts +5 -17
  70. package/src/__tests__/compaction-trail-store.test.ts +1 -79
  71. package/src/__tests__/compaction.benchmark.test.ts +0 -30
  72. package/src/__tests__/compactor-image-manifest-trust.test.ts +112 -0
  73. package/src/__tests__/computer-use-tools.test.ts +2 -2
  74. package/src/__tests__/config-watcher.test.ts +28 -0
  75. package/src/__tests__/context-search-agent-runner.test.ts +6 -3
  76. package/src/__tests__/context-token-estimator.test.ts +34 -0
  77. package/src/__tests__/context-window-manager-compact-retry.test.ts +291 -0
  78. package/src/__tests__/conversation-abort-tool-results.test.ts +70 -25
  79. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +9 -7
  80. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -34
  81. package/src/__tests__/conversation-agent-loop-overflow.test.ts +476 -963
  82. package/src/__tests__/conversation-agent-loop.test.ts +823 -1321
  83. package/src/__tests__/conversation-analysis-routes.test.ts +7 -3
  84. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  85. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  86. package/src/__tests__/conversation-clear-safety.test.ts +20 -10
  87. package/src/__tests__/conversation-confirmation-signals.test.ts +15 -45
  88. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
  89. package/src/__tests__/conversation-disk-view.test.ts +10 -17
  90. package/src/__tests__/conversation-fork-crud.test.ts +86 -172
  91. package/src/__tests__/conversation-fork-route.test.ts +16 -14
  92. package/src/__tests__/conversation-history-web-search.test.ts +11 -1
  93. package/src/__tests__/conversation-init.benchmark.test.ts +6 -6
  94. package/src/__tests__/conversation-lifecycle.test.ts +3 -2
  95. package/src/__tests__/conversation-load-history-repair.test.ts +3 -2
  96. package/src/__tests__/conversation-load-history-stripped.test.ts +1 -1
  97. package/src/__tests__/conversation-message-sync-tags.test.ts +3 -4
  98. package/src/__tests__/conversation-pairing.test.ts +10 -7
  99. package/src/__tests__/conversation-pre-run-repair.test.ts +1 -1
  100. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +10 -0
  101. package/src/__tests__/conversation-process-callsite.test.ts +27 -30
  102. package/src/__tests__/conversation-provider-retry-repair.test.ts +80 -51
  103. package/src/__tests__/conversation-queue.test.ts +272 -164
  104. package/src/__tests__/conversation-routes-disk-view.test.ts +6 -2
  105. package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
  106. package/src/__tests__/conversation-routes-slash-commands.test.ts +8 -7
  107. package/src/__tests__/conversation-runtime-assembly.test.ts +317 -313
  108. package/src/__tests__/conversation-runtime-workspace.test.ts +114 -36
  109. package/src/__tests__/conversation-slash-commands.test.ts +8 -42
  110. package/src/__tests__/conversation-slash-queue.test.ts +42 -31
  111. package/src/__tests__/conversation-slash-unknown.test.ts +13 -15
  112. package/src/__tests__/conversation-speed-override.test.ts +8 -22
  113. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  114. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +90 -15
  115. package/src/__tests__/conversation-surfaces-app-control.test.ts +32 -4
  116. package/src/__tests__/conversation-surfaces-state-update.test.ts +5 -2
  117. package/src/__tests__/conversation-surfaces-table-action.test.ts +6 -15
  118. package/src/__tests__/conversation-sync-tags.test.ts +27 -15
  119. package/src/__tests__/conversation-title-service.test.ts +135 -2
  120. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +23 -11
  121. package/src/__tests__/conversation-unread-route.test.ts +14 -2
  122. package/src/__tests__/conversation-usage.test.ts +0 -2
  123. package/src/__tests__/conversation-wipe.test.ts +1 -1
  124. package/src/__tests__/conversation-workspace-cache-state.test.ts +20 -17
  125. package/src/__tests__/conversation-workspace-injection.test.ts +114 -23
  126. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +34 -13
  127. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  128. package/src/__tests__/credential-execution-tools.test.ts +1 -2
  129. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  130. package/src/__tests__/cross-provider-web-search.test.ts +220 -3
  131. package/src/__tests__/cu-unified-flow.test.ts +26 -1
  132. package/src/__tests__/db-acp-history.test.ts +101 -0
  133. package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -0
  134. package/src/__tests__/disk-pressure-guard.test.ts +66 -0
  135. package/src/__tests__/disk-pressure-routes.test.ts +9 -2
  136. package/src/__tests__/dm-persistence.test.ts +12 -3
  137. package/src/__tests__/dynamic-page-surface.test.ts +99 -0
  138. package/src/__tests__/edit-propagation.test.ts +1 -2
  139. package/src/__tests__/empty-response-hook.test.ts +304 -0
  140. package/src/__tests__/feature-flag-test-helpers.ts +2 -2
  141. package/src/__tests__/file-write-tool.test.ts +63 -0
  142. package/src/__tests__/filing-service.test.ts +2 -2
  143. package/src/__tests__/first-greeting.test.ts +55 -14
  144. package/src/__tests__/gemini-image-service.test.ts +13 -0
  145. package/src/__tests__/gemini-inline-media.test.ts +78 -0
  146. package/src/__tests__/gemini-provider.test.ts +351 -28
  147. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  148. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  149. package/src/__tests__/guardian-routing-state.test.ts +60 -71
  150. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +10 -8
  151. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  152. package/src/__tests__/heartbeat-service.test.ts +3 -1
  153. package/src/__tests__/helpers/mock-provider.ts +110 -0
  154. package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
  155. package/src/__tests__/history-repair-hook.test.ts +162 -0
  156. package/src/__tests__/history-repair-observability.test.ts +1 -1
  157. package/src/__tests__/history-repair.test.ts +2 -1
  158. package/src/__tests__/host-app-control-proxy.test.ts +2 -0
  159. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  160. package/src/__tests__/host-cu-proxy.test.ts +2 -0
  161. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  162. package/src/__tests__/host-file-edit-tool.test.ts +4 -2
  163. package/src/__tests__/host-file-proxy.test.ts +31 -0
  164. package/src/__tests__/host-file-read-tool.test.ts +4 -2
  165. package/src/__tests__/host-file-write-tool.test.ts +9 -3
  166. package/src/__tests__/host-proxy-preactivation.test.ts +53 -14
  167. package/src/__tests__/host-shell-tool.test.ts +9 -4
  168. package/src/__tests__/http-user-message-parity.test.ts +2 -2
  169. package/src/__tests__/identity-intro-cache.test.ts +47 -114
  170. package/src/__tests__/identity-routes.test.ts +248 -7
  171. package/src/__tests__/inbound-slack-persistence.test.ts +12 -3
  172. package/src/__tests__/injector-background-turn.test.ts +3 -9
  173. package/src/__tests__/injector-chain.test.ts +139 -275
  174. package/src/__tests__/injector-disk-pressure.test.ts +75 -41
  175. package/src/__tests__/injector-document-comments.test.ts +3 -3
  176. package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
  177. package/src/__tests__/injector-v3-suppression.test.ts +214 -0
  178. package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
  179. package/src/__tests__/list-messages-attachments.test.ts +7 -8
  180. package/src/__tests__/list-messages-hidden-metadata.test.ts +55 -15
  181. package/src/__tests__/list-messages-page-latest.test.ts +60 -1
  182. package/src/__tests__/list-messages-tool-merge.test.ts +56 -6
  183. package/src/__tests__/llm-request-log-turn-query.test.ts +42 -86
  184. package/src/__tests__/llm-resolver.test.ts +23 -47
  185. package/src/__tests__/llm-usage-store.test.ts +268 -1
  186. package/src/__tests__/log-export-routes.test.ts +59 -0
  187. package/src/__tests__/managed-skill-lifecycle.test.ts +1 -8
  188. package/src/__tests__/mcp-auth-routes.test.ts +15 -10
  189. package/src/__tests__/mcp-health-check.test.ts +18 -13
  190. package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
  191. package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
  192. package/src/__tests__/messaging-send-tool.test.ts +8 -4
  193. package/src/__tests__/migration-export-http.test.ts +12 -12
  194. package/src/__tests__/migration-import-commit-http.test.ts +8 -8
  195. package/src/__tests__/migration-import-preflight-http.test.ts +7 -7
  196. package/src/__tests__/migration-validate-http.test.ts +3 -3
  197. package/src/__tests__/native-web-search.test.ts +205 -20
  198. package/src/__tests__/notification-decision-identity.test.ts +9 -18
  199. package/src/__tests__/notification-decision-recipient-context.test.ts +3 -6
  200. package/src/__tests__/oauth-commands-routes.test.ts +1 -1
  201. package/src/__tests__/onboarding-template-contract.test.ts +12 -0
  202. package/src/__tests__/openai-image-service.test.ts +17 -0
  203. package/src/__tests__/openai-provider.test.ts +97 -71
  204. package/src/__tests__/openai-responses-provider.test.ts +21 -77
  205. package/src/__tests__/outbound-slack-persistence.test.ts +2 -1
  206. package/src/__tests__/{overflow-reduce-pipeline.test.ts → overflow-reduction-loop.test.ts} +64 -286
  207. package/src/__tests__/parallel-tool.benchmark.test.ts +24 -36
  208. package/src/__tests__/persist-unsendable-image.test.ts +215 -0
  209. package/src/__tests__/persistence-secret-redaction.test.ts +3 -1
  210. package/src/__tests__/pipeline-runner.test.ts +31 -43
  211. package/src/__tests__/pkb-autoinject.test.ts +2 -5
  212. package/src/__tests__/plugin-bootstrap.test.ts +62 -51
  213. package/src/__tests__/plugin-registry.test.ts +0 -27
  214. package/src/__tests__/plugin-route-contribution.test.ts +6 -16
  215. package/src/__tests__/plugin-skill-contribution.test.ts +7 -17
  216. package/src/__tests__/plugin-tool-contribution.test.ts +10 -26
  217. package/src/__tests__/plugin-types.test.ts +8 -173
  218. package/src/__tests__/prechat-onboarding-contract.test.ts +23 -0
  219. package/src/__tests__/process-message-background-slack.test.ts +17 -16
  220. package/src/__tests__/process-message-display-content.test.ts +36 -44
  221. package/src/__tests__/provider-commit-message-generator.test.ts +19 -14
  222. package/src/__tests__/provider-error-scenarios.test.ts +7 -6
  223. package/src/__tests__/provider-platform-proxy-integration.test.ts +3 -8
  224. package/src/__tests__/provider-send-message-override-profile.test.ts +9 -25
  225. package/src/__tests__/provider-streaming.benchmark.test.ts +12 -22
  226. package/src/__tests__/provider-usage-tracking.test.ts +0 -6
  227. package/src/__tests__/ratelimit.test.ts +9 -4
  228. package/src/__tests__/reaction-persistence.test.ts +1 -1
  229. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
  230. package/src/__tests__/relay-server.test.ts +20 -13
  231. package/src/__tests__/resolve-trust-class.test.ts +4 -4
  232. package/src/__tests__/retry-openrouter-only-normalization.test.ts +5 -8
  233. package/src/__tests__/retry-thinking-tool-choice.test.ts +10 -13
  234. package/src/__tests__/retry-verbosity-normalization.test.ts +5 -8
  235. package/src/__tests__/runtime-events-sse-reconnect.test.ts +390 -0
  236. package/src/__tests__/schedule-routes.test.ts +683 -12
  237. package/src/__tests__/schedule-store.test.ts +108 -0
  238. package/src/__tests__/schedule-tools.test.ts +160 -0
  239. package/src/__tests__/secret-ingress-http.test.ts +2 -2
  240. package/src/__tests__/secret-prompt-log-hygiene.test.ts +11 -7
  241. package/src/__tests__/secret-prompter-channel-fallback.test.ts +11 -9
  242. package/src/__tests__/secret-response-routing.test.ts +13 -11
  243. package/src/__tests__/send-endpoint-busy.test.ts +6 -2
  244. package/src/__tests__/server-history-render.test.ts +314 -1
  245. package/src/__tests__/shell-observability.test.ts +249 -0
  246. package/src/__tests__/skill-feature-flags-integration.test.ts +44 -11
  247. package/src/__tests__/skill-feature-flags.test.ts +6 -6
  248. package/src/__tests__/skill-load-feature-flag.test.ts +10 -10
  249. package/src/__tests__/skills-files-catalog-fallback.test.ts +10 -0
  250. package/src/__tests__/skillssh-files.test.ts +1 -0
  251. package/src/__tests__/starter-task-flow.test.ts +6 -6
  252. package/src/__tests__/strip-memory-injections.test.ts +102 -14
  253. package/src/__tests__/subagent-call-site-routing.test.ts +3 -3
  254. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  255. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  256. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  257. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  258. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  259. package/src/__tests__/suggestion-routes.test.ts +3 -3
  260. package/src/__tests__/sync-message-contract.test.ts +19 -16
  261. package/src/__tests__/system-prompt.test.ts +74 -0
  262. package/src/__tests__/task-scheduler.test.ts +162 -1
  263. package/src/__tests__/terminal-tools.test.ts +9 -25
  264. package/src/__tests__/thread-backfill.test.ts +4 -9
  265. package/src/__tests__/title-generate-hook.test.ts +319 -0
  266. package/src/__tests__/tool-error-hook.test.ts +278 -0
  267. package/src/__tests__/tool-preview-lifecycle.test.ts +481 -16
  268. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  269. package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
  270. package/src/__tests__/tool-result-truncation.test.ts +1 -1
  271. package/src/__tests__/tools-audio-read.test.ts +113 -0
  272. package/src/__tests__/turn-boundary-resolution.test.ts +44 -84
  273. package/src/__tests__/turn-events-store.test.ts +11 -7
  274. package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
  275. package/src/__tests__/ui-work-result-surface.test.ts +159 -0
  276. package/src/__tests__/usage-routes.test.ts +285 -1
  277. package/src/__tests__/user-plugin-loader.test.ts +2 -2
  278. package/src/__tests__/voice-scoped-grant-consumer.test.ts +8 -6
  279. package/src/__tests__/voice-session-bridge.test.ts +19 -10
  280. package/src/__tests__/web-search-backend-failure.test.ts +166 -0
  281. package/src/acp/__tests__/agent-process.test.ts +161 -0
  282. package/src/acp/__tests__/client-handler.test.ts +40 -0
  283. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  284. package/src/acp/__tests__/helpers/exec-file-stub.ts +101 -0
  285. package/src/acp/__tests__/prepare-agent-env.test.ts +143 -31
  286. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  287. package/src/acp/__tests__/session-manager-resume.test.ts +695 -0
  288. package/src/acp/agent-process.ts +61 -1
  289. package/src/acp/auto-install.test.ts +125 -0
  290. package/src/acp/auto-install.ts +174 -0
  291. package/src/acp/client-handler.ts +31 -0
  292. package/src/acp/feature-gate.test.ts +48 -0
  293. package/src/acp/feature-gate.ts +34 -0
  294. package/src/acp/prepare-agent-env.ts +52 -11
  295. package/src/acp/resolve-agent.test.ts +147 -6
  296. package/src/acp/resolve-agent.ts +81 -7
  297. package/src/acp/resume-hint.ts +22 -0
  298. package/src/acp/session-manager.ts +487 -71
  299. package/src/agent/compaction-circuit.ts +98 -0
  300. package/src/agent/loop.ts +651 -450
  301. package/src/api/README.md +19 -17
  302. package/src/api/constants/tool-execution.ts +21 -0
  303. package/src/api/events/assistant-activity-state.ts +75 -0
  304. package/src/api/events/assistant-outbound-attachment.ts +25 -27
  305. package/src/api/events/assistant-text-delta.ts +6 -8
  306. package/src/api/events/assistant-thinking-delta.ts +33 -0
  307. package/src/api/events/assistant-turn-start.ts +5 -7
  308. package/src/api/events/avatar-updated.ts +24 -0
  309. package/src/api/events/compaction-circuit-closed.ts +26 -0
  310. package/src/api/events/compaction-circuit-open.ts +28 -0
  311. package/src/api/events/confirmation-request.ts +114 -0
  312. package/src/api/events/contact-request.ts +33 -0
  313. package/src/api/events/conversation-error.ts +77 -0
  314. package/src/api/events/conversation-list-invalidated.ts +38 -0
  315. package/src/api/events/conversation-title-updated.ts +24 -0
  316. package/src/api/events/disk-pressure-status-changed.ts +61 -0
  317. package/src/api/events/document-comment-created.ts +24 -28
  318. package/src/api/events/document-comment-deleted.ts +6 -8
  319. package/src/api/events/document-comment-reopened.ts +6 -8
  320. package/src/api/events/document-comment-resolved.ts +8 -10
  321. package/src/api/events/document-editor-update.ts +27 -0
  322. package/src/api/events/error.ts +32 -0
  323. package/src/api/events/generation-cancelled.ts +4 -6
  324. package/src/api/events/generation-handoff.ts +13 -15
  325. package/src/api/events/home-feed-updated.ts +26 -0
  326. package/src/api/events/identity-changed.ts +32 -0
  327. package/src/api/events/interaction-resolved.ts +50 -0
  328. package/src/api/events/message-complete.ts +10 -12
  329. package/src/api/events/message-dequeued.ts +21 -0
  330. package/src/api/events/message-queued-deleted.ts +23 -0
  331. package/src/api/events/message-queued.ts +22 -0
  332. package/src/api/events/message-request-complete.ts +29 -0
  333. package/src/api/events/navigate-settings.ts +20 -0
  334. package/src/api/events/notification-intent.ts +33 -0
  335. package/src/api/events/open-url.ts +6 -8
  336. package/src/api/events/question-request.ts +67 -0
  337. package/src/api/events/relationship-state-updated.ts +4 -6
  338. package/src/api/events/secret-request.ts +42 -0
  339. package/src/api/events/subagent-event.ts +79 -0
  340. package/src/api/events/subagent-spawned.ts +40 -0
  341. package/src/api/events/subagent-status-changed.ts +65 -0
  342. package/src/api/events/sync-changed.ts +29 -0
  343. package/src/api/events/tool-output-chunk.ts +45 -0
  344. package/src/api/events/tool-result.ts +129 -0
  345. package/src/api/events/tool-use-preview-start.ts +32 -0
  346. package/src/api/events/tool-use-start.ts +8 -10
  347. package/src/api/events/trace-event.ts +69 -0
  348. package/src/api/events/turn-profile-auto-routed.ts +28 -0
  349. package/src/api/events/ui-surface-complete.ts +30 -0
  350. package/src/api/events/ui-surface-dismiss.ts +22 -0
  351. package/src/api/events/ui-surface-show.ts +67 -0
  352. package/src/api/events/ui-surface-update.ts +26 -0
  353. package/src/api/events/usage-update.ts +34 -0
  354. package/src/api/events/user-message-echo.ts +35 -0
  355. package/src/api/index.ts +389 -0
  356. package/src/api/requests/dictation.ts +45 -0
  357. package/src/api/responses/conversation-message.ts +374 -0
  358. package/src/api/responses/disk-pressure-status.ts +26 -0
  359. package/src/api/responses/home.ts +217 -0
  360. package/src/api/responses/llm-context-response.ts +2 -0
  361. package/src/api/responses/memory-v3-selection-log.ts +50 -0
  362. package/src/api/responses/subagent-detail.ts +48 -0
  363. package/src/approvals/guardian-decision-primitive.ts +7 -15
  364. package/src/approvals/guardian-request-resolvers.ts +7 -10
  365. package/src/avatar/__tests__/avatar-manifest.test.ts +236 -0
  366. package/src/avatar/__tests__/avatar-store.test.ts +198 -0
  367. package/src/avatar/avatar-manifest.ts +195 -0
  368. package/src/avatar/avatar-store.ts +113 -0
  369. package/src/avatar/traits-png-sync.ts +8 -2
  370. package/src/background-wake/next-wake.test.ts +31 -1
  371. package/src/background-wake/next-wake.ts +5 -1
  372. package/src/calls/call-conversation-messages.ts +6 -4
  373. package/src/calls/guardian-action-sweep.ts +6 -4
  374. package/src/calls/relay-server.ts +12 -8
  375. package/src/calls/voice-session-bridge.ts +13 -27
  376. package/src/cli/commands/__tests__/memory-v3.test.ts +245 -0
  377. package/src/cli/commands/__tests__/notifications.test.ts +58 -14
  378. package/src/cli/commands/avatar.ts +17 -11
  379. package/src/cli/commands/conversations.ts +15 -1
  380. package/src/cli/commands/db/__tests__/repair.test.ts +540 -0
  381. package/src/cli/commands/db/__tests__/status.test.ts +253 -0
  382. package/src/cli/commands/db/format.ts +48 -0
  383. package/src/cli/commands/db/index.ts +29 -0
  384. package/src/cli/commands/db/repair-step-conversation-backfill.ts +345 -0
  385. package/src/cli/commands/db/repair-step-integrity.ts +146 -0
  386. package/src/cli/commands/db/repair-steps.ts +164 -0
  387. package/src/cli/commands/db/repair.ts +141 -0
  388. package/src/cli/commands/db/status.ts +366 -0
  389. package/src/cli/commands/memory-v3.ts +159 -445
  390. package/src/cli/commands/notifications.ts +112 -60
  391. package/src/cli/lib/cli-colors.ts +24 -6
  392. package/src/cli/program.ts +4 -5
  393. package/src/config/__tests__/feature-flag-registry-guard.test.ts +4 -4
  394. package/src/config/acp-defaults.test.ts +10 -0
  395. package/src/config/acp-defaults.ts +6 -0
  396. package/src/config/assistant-feature-flags.ts +24 -13
  397. package/src/config/bundled-skills/acp/SKILL.md +64 -30
  398. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  399. package/src/config/bundled-skills/app-builder/SKILL.md +224 -387
  400. package/src/config/bundled-skills/app-builder/TOOLS.json +29 -0
  401. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  402. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  403. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  404. package/src/config/bundled-skills/app-builder/references/examples/README.md +17 -0
  405. package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
  406. package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
  407. package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
  408. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  409. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  410. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  411. package/src/config/bundled-skills/media-processing/services/reduce.ts +6 -9
  412. package/src/config/bundled-skills/messaging/SKILL.md +0 -7
  413. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +7 -2
  414. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  415. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  416. package/src/config/bundled-tool-registry.ts +2 -0
  417. package/src/config/call-site-defaults.ts +2 -7
  418. package/src/config/feature-flag-cache.ts +3 -3
  419. package/src/config/feature-flag-registry.json +68 -12
  420. package/src/config/schemas/__tests__/memory-v2.test.ts +2 -226
  421. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
  422. package/src/config/schemas/call-site-catalog.ts +8 -15
  423. package/src/config/schemas/heartbeat.ts +9 -0
  424. package/src/config/schemas/llm.ts +3 -3
  425. package/src/config/schemas/memory-lifecycle.ts +24 -0
  426. package/src/config/schemas/memory-v2.ts +8 -253
  427. package/src/config/schemas/memory-v3.ts +47 -0
  428. package/src/config/schemas/memory.ts +6 -1
  429. package/src/config/schemas/platform.ts +8 -0
  430. package/src/config/schemas/timeouts.ts +3 -1
  431. package/src/config/seed-inference-profiles.ts +2 -2
  432. package/src/config/skills.ts +13 -0
  433. package/src/context/compactor.ts +55 -32
  434. package/src/context/strip-injections.ts +128 -0
  435. package/src/context/token-estimator.ts +42 -0
  436. package/src/context/tool-result-truncation.ts +1 -66
  437. package/src/context/window-manager.ts +141 -26
  438. package/src/credential-execution/executable-discovery.ts +16 -0
  439. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +6 -0
  440. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +2 -2
  441. package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
  442. package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
  443. package/src/daemon/__tests__/web-search-status-text.test.ts +10 -6
  444. package/src/daemon/approval-generators.ts +4 -4
  445. package/src/daemon/assistant-attachments.ts +1 -1
  446. package/src/daemon/config-watcher.ts +7 -1
  447. package/src/daemon/context-overflow-reducer.ts +0 -1
  448. package/src/daemon/conversation-agent-loop-handlers.ts +793 -215
  449. package/src/daemon/conversation-agent-loop.ts +487 -1478
  450. package/src/daemon/conversation-error.ts +7 -7
  451. package/src/daemon/conversation-history.ts +27 -10
  452. package/src/daemon/conversation-launch.ts +4 -8
  453. package/src/daemon/conversation-lifecycle.ts +13 -42
  454. package/src/daemon/conversation-messaging.ts +8 -9
  455. package/src/daemon/conversation-notifiers.ts +7 -5
  456. package/src/daemon/conversation-process.ts +109 -93
  457. package/src/daemon/conversation-registry.ts +159 -0
  458. package/src/daemon/conversation-runtime-assembly.ts +209 -382
  459. package/src/daemon/conversation-slash.ts +6 -25
  460. package/src/daemon/conversation-store.ts +15 -95
  461. package/src/daemon/conversation-surfaces.ts +277 -73
  462. package/src/daemon/conversation-tool-setup.ts +5 -29
  463. package/src/daemon/conversation-workspace.ts +17 -0
  464. package/src/daemon/conversation.ts +123 -146
  465. package/src/daemon/daemon-skill-host.ts +2 -6
  466. package/src/daemon/disk-pressure-guard.ts +35 -29
  467. package/src/daemon/external-plugins-bootstrap.ts +53 -32
  468. package/src/daemon/first-greeting.ts +26 -4
  469. package/src/daemon/guardian-action-generators.ts +2 -2
  470. package/src/daemon/handlers/config-a2a.ts +51 -36
  471. package/src/daemon/handlers/config-slack-channel.ts +20 -14
  472. package/src/daemon/handlers/config-telegram.ts +16 -2
  473. package/src/daemon/handlers/conversations.ts +9 -23
  474. package/src/daemon/handlers/shared.ts +158 -82
  475. package/src/daemon/handlers/skills.ts +53 -20
  476. package/src/daemon/host-app-control-proxy.ts +54 -1
  477. package/src/daemon/host-cu-proxy.ts +46 -22
  478. package/src/daemon/host-file-proxy.ts +25 -1
  479. package/src/daemon/host-proxy-preactivation.ts +25 -6
  480. package/src/daemon/lifecycle.ts +53 -55
  481. package/src/daemon/message-protocol.ts +2 -3
  482. package/src/daemon/message-provenance.ts +49 -0
  483. package/src/daemon/message-types/apps.ts +1 -29
  484. package/src/daemon/message-types/contacts.ts +3 -20
  485. package/src/daemon/message-types/conversations.ts +13 -111
  486. package/src/daemon/message-types/documents.ts +3 -9
  487. package/src/daemon/message-types/home.ts +4 -17
  488. package/src/daemon/message-types/integrations.ts +2 -6
  489. package/src/daemon/message-types/messages.ts +37 -400
  490. package/src/daemon/message-types/notifications.ts +2 -32
  491. package/src/daemon/message-types/settings.ts +3 -8
  492. package/src/daemon/message-types/skills.ts +4 -0
  493. package/src/daemon/message-types/surfaces.ts +138 -3
  494. package/src/daemon/message-types/sync.ts +12 -25
  495. package/src/daemon/message-types/workspace.ts +3 -11
  496. package/src/daemon/now-scratchpad.ts +21 -0
  497. package/src/daemon/orphan-reaper.test.ts +210 -0
  498. package/src/daemon/orphan-reaper.ts +240 -0
  499. package/src/daemon/overflow-reduction-loop.ts +230 -0
  500. package/src/daemon/persist-unsendable-image.ts +117 -0
  501. package/src/daemon/process-message.ts +50 -49
  502. package/src/daemon/server.ts +14 -0
  503. package/src/daemon/tool-side-effects.ts +10 -7
  504. package/src/daemon/trace-emitter.ts +6 -4
  505. package/src/daemon/trust-context.ts +32 -0
  506. package/src/daemon/wake-target-adapter.ts +14 -2
  507. package/src/heartbeat/__tests__/heartbeat-service.test.ts +6 -1
  508. package/src/heartbeat/heartbeat-run-store.ts +54 -1
  509. package/src/heartbeat/heartbeat-service.ts +42 -0
  510. package/src/home/feed-types.ts +36 -221
  511. package/src/home/home-greeting-cache.ts +24 -1
  512. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  513. package/src/ipc/__tests__/email-ipc.test.ts +0 -9
  514. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  515. package/src/ipc/gateway-client.test.ts +2 -2
  516. package/src/ipc/gateway-client.ts +3 -3
  517. package/src/ipc/routes/__tests__/route-adapter.test.ts +244 -0
  518. package/src/ipc/routes/route-adapter.ts +45 -6
  519. package/src/ipc/skill-routes/__tests__/memory.test.ts +33 -9
  520. package/src/ipc/skill-routes/__tests__/providers.test.ts +10 -10
  521. package/src/ipc/skill-routes/__tests__/registries.test.ts +28 -18
  522. package/src/ipc/skill-routes/memory.ts +29 -14
  523. package/src/ipc/skill-routes/providers.ts +5 -6
  524. package/src/ipc/skill-routes/registries.ts +13 -61
  525. package/src/live-voice/__tests__/live-voice-archive.test.ts +24 -11
  526. package/src/media/gemini-image-service.ts +15 -0
  527. package/src/media/openai-image-service.ts +14 -0
  528. package/src/media/types.ts +34 -0
  529. package/src/memory/__tests__/conversation-queries.test.ts +192 -8
  530. package/src/memory/__tests__/db-maintenance.test.ts +128 -0
  531. package/src/memory/__tests__/jobs-store-job-classes.test.ts +5 -4
  532. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +56 -0
  533. package/src/memory/__tests__/memory-retrospective-job.test.ts +10 -6
  534. package/src/memory/__tests__/memory-v3-selections-migration.test.ts +103 -0
  535. package/src/memory/auth-fallback-events-store.ts +94 -0
  536. package/src/memory/context-search/agent-runner.ts +2 -4
  537. package/src/memory/conversation-crud.ts +39 -8
  538. package/src/memory/conversation-queries.ts +78 -22
  539. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  540. package/src/memory/conversation-title-service.ts +65 -41
  541. package/src/memory/db-init.ts +14 -0
  542. package/src/memory/db-maintenance.ts +18 -2
  543. package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
  544. package/src/memory/graph/consolidation.ts +8 -11
  545. package/src/memory/graph/conversation-graph-memory.ts +106 -8
  546. package/src/memory/graph/extraction.ts +6 -9
  547. package/src/memory/graph/narrative.ts +2 -2
  548. package/src/memory/graph/pattern-scan.ts +2 -2
  549. package/src/memory/graph/retriever.ts +20 -26
  550. package/src/memory/graph/tools.ts +4 -4
  551. package/src/memory/job-handlers/conversation-starters.ts +45 -34
  552. package/src/memory/job-handlers/summarization.ts +1 -2
  553. package/src/memory/jobs-store.ts +36 -1
  554. package/src/memory/jobs-worker.ts +82 -43
  555. package/src/memory/llm-request-log-source-clickhouse.ts +5 -31
  556. package/src/memory/llm-request-log-source-local.ts +0 -11
  557. package/src/memory/llm-request-log-source.ts +9 -25
  558. package/src/memory/llm-request-log-store.ts +0 -41
  559. package/src/memory/llm-usage-store.ts +234 -50
  560. package/src/memory/memory-marker.ts +17 -0
  561. package/src/memory/memory-retrospective-job.ts +6 -2
  562. package/src/memory/memory-v2-activation-log-store.ts +1 -83
  563. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
  564. package/src/memory/migrations/267-llm-usage-events-add-assistant-version.ts +46 -0
  565. package/src/memory/migrations/268-add-memory-v3-selections.ts +28 -0
  566. package/src/memory/migrations/269-schedule-script-timeout.ts +11 -0
  567. package/src/memory/migrations/270-messages-role-created-at-index.ts +18 -0
  568. package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
  569. package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
  570. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  571. package/src/memory/migrations/__tests__/267-llm-usage-events-add-assistant-version.test.ts +117 -0
  572. package/src/memory/migrations/index.ts +7 -0
  573. package/src/memory/pkb/autoinject.ts +61 -0
  574. package/src/memory/pkb/context.ts +50 -0
  575. package/src/memory/pkb/types.ts +14 -0
  576. package/src/memory/schedule-attribution-sql.ts +104 -0
  577. package/src/memory/schema/acp.ts +4 -0
  578. package/src/memory/schema/infrastructure.ts +27 -0
  579. package/src/memory/usage-grouped-buckets.ts +6 -1
  580. package/src/memory/v2/__tests__/consolidation-job.test.ts +125 -1
  581. package/src/memory/v2/__tests__/migration.test.ts +11 -3
  582. package/src/memory/v2/__tests__/page-index.test.ts +37 -1
  583. package/src/memory/v2/__tests__/router.test.ts +14 -4
  584. package/src/memory/v2/__tests__/sweep-job.test.ts +6 -5
  585. package/src/memory/v2/backfill-jobs.ts +6 -0
  586. package/src/memory/v2/consolidation-job.ts +99 -10
  587. package/src/memory/v2/migration.ts +5 -3
  588. package/src/memory/v2/page-index.ts +11 -0
  589. package/src/memory/v2/router.ts +8 -11
  590. package/src/memory/v2/sweep-job.ts +8 -11
  591. package/src/memory/v2/types.ts +1 -0
  592. package/src/messaging/providers/slack/render-transcript.test.ts +1 -1
  593. package/src/messaging/providers/slack/render-transcript.ts +2 -2
  594. package/src/messaging/style-analyzer.ts +8 -11
  595. package/src/notifications/conversation-pairing.ts +8 -13
  596. package/src/notifications/decision-engine.ts +16 -16
  597. package/src/notifications/home-feed-side-effect.ts +12 -1
  598. package/src/notifications/preference-extractor.ts +11 -14
  599. package/src/permissions/prompter.ts +46 -36
  600. package/src/permissions/question-prompter.test.ts +35 -26
  601. package/src/permissions/question-prompter.ts +6 -10
  602. package/src/plugin-api/constants.ts +4 -0
  603. package/src/plugin-api/index.ts +10 -1
  604. package/src/plugin-api/types.ts +176 -4
  605. package/src/plugins/defaults/compaction/compact.ts +59 -0
  606. package/src/plugins/defaults/compaction/package.json +15 -0
  607. package/src/plugins/defaults/compaction/register.ts +24 -0
  608. package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
  609. package/src/plugins/defaults/empty-response/package.json +15 -0
  610. package/src/plugins/defaults/empty-response/register.ts +23 -0
  611. package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +35 -0
  612. package/src/plugins/defaults/history-repair/package.json +15 -0
  613. package/src/plugins/defaults/history-repair/register.ts +24 -0
  614. package/src/{daemon/history-repair.ts → plugins/defaults/history-repair/terminal.ts} +48 -35
  615. package/src/plugins/defaults/index.ts +22 -49
  616. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +95 -0
  617. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
  618. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
  619. package/src/plugins/defaults/{injectors.ts → memory-retrieval/injectors.ts} +295 -112
  620. package/src/plugins/defaults/memory-v3-shadow/__tests__/assign.test.ts +242 -0
  621. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +118 -0
  622. package/src/plugins/defaults/memory-v3-shadow/__tests__/core.test.ts +39 -0
  623. package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/eval-turns.json +36 -0
  624. package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/live-turns.json +37 -0
  625. package/src/plugins/defaults/memory-v3-shadow/__tests__/health.test.ts +219 -0
  626. package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +330 -0
  627. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +288 -0
  628. package/src/plugins/defaults/memory-v3-shadow/__tests__/needle.test.ts +107 -0
  629. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +436 -0
  630. package/src/plugins/defaults/memory-v3-shadow/__tests__/provider-blocks.test.ts +13 -0
  631. package/src/plugins/defaults/memory-v3-shadow/__tests__/reconcile.test.ts +274 -0
  632. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +61 -0
  633. package/src/plugins/defaults/memory-v3-shadow/__tests__/router.test.ts +332 -0
  634. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +179 -0
  635. package/src/plugins/defaults/memory-v3-shadow/__tests__/selector.test.ts +470 -0
  636. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +432 -0
  637. package/src/plugins/defaults/memory-v3-shadow/__tests__/snapshot.test.ts +168 -0
  638. package/src/plugins/defaults/memory-v3-shadow/__tests__/tree.test.ts +192 -0
  639. package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +54 -0
  640. package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-eviction.test.ts +106 -0
  641. package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-skeleton.test.ts +44 -0
  642. package/src/plugins/defaults/memory-v3-shadow/assign.ts +268 -0
  643. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +124 -0
  644. package/src/plugins/defaults/memory-v3-shadow/core.ts +26 -0
  645. package/src/plugins/defaults/memory-v3-shadow/data/README.md +84 -0
  646. package/src/plugins/defaults/memory-v3-shadow/data/assignments.json +5 -0
  647. package/src/plugins/defaults/memory-v3-shadow/data/core.json +1 -0
  648. package/src/plugins/defaults/memory-v3-shadow/data/leaves/domain-a/topic-x.md +9 -0
  649. package/src/plugins/defaults/memory-v3-shadow/data/leaves/domain-a/topic-y.md +9 -0
  650. package/src/plugins/defaults/memory-v3-shadow/data/leaves/domain-b/topic-z.md +9 -0
  651. package/src/plugins/defaults/memory-v3-shadow/health.ts +0 -0
  652. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  653. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  654. package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
  655. package/src/plugins/defaults/memory-v3-shadow/llm-retry.ts +32 -0
  656. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +314 -0
  657. package/src/plugins/defaults/memory-v3-shadow/needle.ts +115 -0
  658. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +126 -0
  659. package/src/plugins/defaults/memory-v3-shadow/package.json +15 -0
  660. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +34 -0
  661. package/src/plugins/defaults/memory-v3-shadow/provider-blocks.ts +26 -0
  662. package/src/plugins/defaults/memory-v3-shadow/reconcile.ts +523 -0
  663. package/src/plugins/defaults/memory-v3-shadow/register.ts +26 -0
  664. package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +32 -0
  665. package/src/plugins/defaults/memory-v3-shadow/router.ts +190 -0
  666. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +84 -0
  667. package/src/plugins/defaults/memory-v3-shadow/selector.ts +226 -0
  668. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +349 -0
  669. package/src/plugins/defaults/memory-v3-shadow/snapshot.ts +209 -0
  670. package/src/plugins/defaults/memory-v3-shadow/tree.ts +174 -0
  671. package/src/plugins/defaults/memory-v3-shadow/types.ts +59 -0
  672. package/src/plugins/defaults/memory-v3-shadow/working-set.ts +88 -0
  673. package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
  674. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
  675. package/src/plugins/defaults/title-generate/package.json +15 -0
  676. package/src/plugins/defaults/title-generate/register.ts +35 -0
  677. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
  678. package/src/plugins/defaults/tool-error/package.json +15 -0
  679. package/src/plugins/defaults/tool-error/register.ts +23 -0
  680. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
  681. package/src/plugins/defaults/tool-result-truncate/package.json +15 -0
  682. package/src/plugins/defaults/tool-result-truncate/register.ts +24 -0
  683. package/src/plugins/defaults/tool-result-truncate/terminal.ts +132 -0
  684. package/src/plugins/external-plugin-loader.ts +2 -2
  685. package/src/plugins/pipeline.ts +8 -35
  686. package/src/plugins/registry.ts +8 -25
  687. package/src/plugins/types.ts +62 -721
  688. package/src/plugins/user-loader.ts +4 -3
  689. package/src/proactive-artifact/aux-message-injector.ts +4 -5
  690. package/src/proactive-artifact/job.test.ts +28 -21
  691. package/src/proactive-artifact/job.ts +3 -1
  692. package/src/prompts/__tests__/system-prompt.test.ts +42 -0
  693. package/src/prompts/sections.ts +20 -7
  694. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +64 -0
  695. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +2 -2
  696. package/src/prompts/templates/BOOTSTRAP.md +7 -3
  697. package/src/prompts/templates/system-sections.ts +21 -0
  698. package/src/providers/__tests__/retry-callsite.test.ts +25 -25
  699. package/src/providers/__tests__/satellite-connection-routing.test.ts +7 -21
  700. package/src/providers/anthropic/client.ts +61 -34
  701. package/src/providers/call-site-routing.ts +1 -9
  702. package/src/providers/gemini/client.ts +152 -34
  703. package/src/providers/gemini/inline-media.ts +74 -0
  704. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -2
  705. package/src/providers/openai/chat-completions-provider.ts +45 -4
  706. package/src/providers/openai/responses-provider.ts +1 -4
  707. package/src/providers/openrouter/client.ts +2 -6
  708. package/src/providers/placeholder-sentinels.ts +35 -0
  709. package/src/providers/provider-send-message.ts +6 -6
  710. package/src/providers/ratelimit.ts +1 -9
  711. package/src/providers/retry.ts +0 -5
  712. package/src/providers/types.ts +11 -2
  713. package/src/providers/usage-tracking.ts +1 -9
  714. package/src/runtime/__tests__/agent-wake.test.ts +141 -32
  715. package/src/runtime/__tests__/background-job-runner.test.ts +1 -3
  716. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  717. package/src/runtime/agent-wake.ts +95 -23
  718. package/src/runtime/assistant-event-hub.ts +38 -8
  719. package/src/runtime/assistant-stream-state.ts +368 -0
  720. package/src/runtime/auth/__tests__/guard-tests.test.ts +75 -109
  721. package/src/runtime/auth/__tests__/route-policy.test.ts +153 -170
  722. package/src/runtime/auth/route-policy.ts +42 -1079
  723. package/src/runtime/background-job-runner.ts +1 -4
  724. package/src/runtime/btw-sidechain.ts +3 -1
  725. package/src/runtime/channel-approvals.ts +4 -15
  726. package/src/runtime/channel-invite-transport.ts +5 -6
  727. package/src/runtime/channel-readiness-service.ts +2 -5
  728. package/src/runtime/channel-retry-sweep.ts +12 -16
  729. package/src/runtime/http-router.ts +35 -43
  730. package/src/runtime/http-types.ts +23 -71
  731. package/src/runtime/interactive-ui.ts +1 -1
  732. package/src/runtime/invite-instruction-generator.ts +3 -3
  733. package/src/runtime/pending-interactions.ts +3 -2
  734. package/src/runtime/routes/__tests__/acp-routes.test.ts +253 -55
  735. package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +565 -0
  736. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
  737. package/src/runtime/routes/__tests__/content-source-routes.test.ts +4 -4
  738. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +62 -32
  739. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +237 -0
  740. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
  741. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -22
  742. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
  743. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +7 -2
  744. package/src/runtime/routes/__tests__/sanity-routes.test.ts +6 -6
  745. package/src/runtime/routes/__tests__/stt-routes.test.ts +3 -3
  746. package/src/runtime/routes/__tests__/suggest-trust-rule-routes.test.ts +5 -2
  747. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  748. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  749. package/src/runtime/routes/__tests__/tts-routes.test.ts +9 -5
  750. package/src/runtime/routes/acp-routes.test.ts +186 -100
  751. package/src/runtime/routes/acp-routes.ts +110 -35
  752. package/src/runtime/routes/app-management-routes.ts +93 -131
  753. package/src/runtime/routes/app-routes.ts +38 -20
  754. package/src/runtime/routes/approval-routes.ts +17 -5
  755. package/src/runtime/routes/attachment-routes.ts +51 -16
  756. package/src/runtime/routes/audio-routes.ts +1 -0
  757. package/src/runtime/routes/audit-routes.ts +5 -0
  758. package/src/runtime/routes/auth-routes.ts +5 -0
  759. package/src/runtime/routes/avatar-routes.ts +264 -59
  760. package/src/runtime/routes/background-tool-routes.ts +9 -0
  761. package/src/runtime/routes/background-wake-routes.ts +13 -3
  762. package/src/runtime/routes/backup-routes.ts +45 -0
  763. package/src/runtime/routes/bookmark-routes.ts +13 -0
  764. package/src/runtime/routes/brain-graph-routes.ts +9 -0
  765. package/src/runtime/routes/browser-routes.ts +6 -1
  766. package/src/runtime/routes/browser-tabs-routes.ts +11 -10
  767. package/src/runtime/routes/btw-routes.ts +34 -24
  768. package/src/runtime/routes/cache-routes.ts +13 -0
  769. package/src/runtime/routes/call-routes.ts +21 -10
  770. package/src/runtime/routes/channel-availability-routes.ts +5 -1
  771. package/src/runtime/routes/channel-readiness-routes.ts +37 -4
  772. package/src/runtime/routes/channel-route-definitions.ts +21 -0
  773. package/src/runtime/routes/channel-verification-routes.ts +21 -0
  774. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +9 -2
  775. package/src/runtime/routes/client-routes.ts +9 -0
  776. package/src/runtime/routes/consolidation-routes.ts +133 -25
  777. package/src/runtime/routes/contact-prompt-routes.ts +9 -0
  778. package/src/runtime/routes/contact-routes.ts +90 -23
  779. package/src/runtime/routes/content-source-routes.ts +5 -1
  780. package/src/runtime/routes/conversation-analysis-routes.ts +5 -1
  781. package/src/runtime/routes/conversation-attention-routes.ts +5 -0
  782. package/src/runtime/routes/conversation-cli-routes.ts +54 -7
  783. package/src/runtime/routes/conversation-compaction-routes.ts +54 -25
  784. package/src/runtime/routes/conversation-list-routes.ts +81 -12
  785. package/src/runtime/routes/conversation-management-routes.ts +57 -14
  786. package/src/runtime/routes/conversation-query-routes.ts +90 -41
  787. package/src/runtime/routes/conversation-routes.ts +446 -204
  788. package/src/runtime/routes/conversation-starter-routes.ts +35 -20
  789. package/src/runtime/routes/conversations-import-routes.ts +30 -8
  790. package/src/runtime/routes/credential-prompt-routes.ts +5 -0
  791. package/src/runtime/routes/credential-routes.ts +25 -6
  792. package/src/runtime/routes/debug-bash-routes.ts +5 -0
  793. package/src/runtime/routes/debug-routes.ts +11 -2
  794. package/src/runtime/routes/defer-routes.ts +13 -0
  795. package/src/runtime/routes/diagnostics-routes.ts +37 -46
  796. package/src/runtime/routes/disk-pressure-routes.ts +17 -31
  797. package/src/runtime/routes/document-comments-routes.ts +46 -27
  798. package/src/runtime/routes/documents-routes.ts +25 -10
  799. package/src/runtime/routes/domain-routes.ts +98 -51
  800. package/src/runtime/routes/email-routes.ts +33 -0
  801. package/src/runtime/routes/epoch-millis-range.ts +34 -0
  802. package/src/runtime/routes/events-routes.ts +107 -8
  803. package/src/runtime/routes/filing-routes.ts +9 -4
  804. package/src/runtime/routes/gateway-log-routes.ts +31 -4
  805. package/src/runtime/routes/global-search-routes.ts +53 -50
  806. package/src/runtime/routes/group-routes.ts +21 -5
  807. package/src/runtime/routes/guardian-action-routes.ts +9 -0
  808. package/src/runtime/routes/guardian-approval-interception.ts +0 -31
  809. package/src/runtime/routes/heartbeat-routes.ts +57 -21
  810. package/src/runtime/routes/home-feed-routes.ts +23 -19
  811. package/src/runtime/routes/home-state-routes.ts +8 -40
  812. package/src/runtime/routes/host-app-control-routes.ts +6 -1
  813. package/src/runtime/routes/host-bash-routes.ts +5 -0
  814. package/src/runtime/routes/host-browser-routes.ts +13 -0
  815. package/src/runtime/routes/host-cu-routes.ts +6 -1
  816. package/src/runtime/routes/host-file-routes.ts +26 -6
  817. package/src/runtime/routes/host-transfer-routes.ts +13 -2
  818. package/src/runtime/routes/http-adapter.ts +1 -2
  819. package/src/runtime/routes/identity-intro-cache.ts +28 -40
  820. package/src/runtime/routes/identity-routes.ts +236 -20
  821. package/src/runtime/routes/image-generation-routes.ts +45 -2
  822. package/src/runtime/routes/inbound-message-handler.ts +16 -12
  823. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +0 -12
  824. package/src/runtime/routes/inbound-stages/background-dispatch.ts +15 -19
  825. package/src/runtime/routes/index.ts +2 -0
  826. package/src/runtime/routes/inference-profile-session-routes.ts +13 -3
  827. package/src/runtime/routes/inference-provider-connection-routes.ts +21 -5
  828. package/src/runtime/routes/inference-send-routes.ts +11 -11
  829. package/src/runtime/routes/integrations/a2a.ts +32 -7
  830. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
  831. package/src/runtime/routes/integrations/slack/channel.ts +23 -3
  832. package/src/runtime/routes/integrations/slack/share.ts +36 -8
  833. package/src/runtime/routes/integrations/telegram.ts +34 -9
  834. package/src/runtime/routes/integrations/twilio.ts +77 -7
  835. package/src/runtime/routes/integrations/vercel.ts +3 -3
  836. package/src/runtime/routes/internal-oauth-routes.ts +5 -0
  837. package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
  838. package/src/runtime/routes/internal-twilio-routes.ts +13 -0
  839. package/src/runtime/routes/llm-call-sites-routes.ts +39 -4
  840. package/src/runtime/routes/log-export-routes.ts +36 -10
  841. package/src/runtime/routes/mcp-auth-routes.ts +25 -0
  842. package/src/runtime/routes/memory-item-routes.ts +21 -10
  843. package/src/runtime/routes/memory-v2-routes.ts +105 -44
  844. package/src/runtime/routes/memory-v3-routes.ts +306 -408
  845. package/src/runtime/routes/migration-rollback-routes.ts +5 -1
  846. package/src/runtime/routes/migration-routes.ts +29 -0
  847. package/src/runtime/routes/notification-routes.ts +17 -1
  848. package/src/runtime/routes/oauth-apps.ts +99 -23
  849. package/src/runtime/routes/oauth-commands-routes.ts +37 -14
  850. package/src/runtime/routes/oauth-connect-routes.ts +9 -0
  851. package/src/runtime/routes/oauth-lifecycle-routes.ts +5 -1
  852. package/src/runtime/routes/oauth-providers.ts +79 -15
  853. package/src/runtime/routes/platform-routes.ts +102 -5
  854. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +9 -6
  855. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +37 -16
  856. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +7 -3
  857. package/src/runtime/routes/playground/__tests__/state.test.ts +10 -3
  858. package/src/runtime/routes/playground/force-compact.ts +2 -2
  859. package/src/runtime/routes/playground/helpers.ts +1 -2
  860. package/src/runtime/routes/playground/inject-failures.ts +13 -8
  861. package/src/runtime/routes/playground/reset-circuit.ts +14 -9
  862. package/src/runtime/routes/playground/seed-conversation.ts +1 -1
  863. package/src/runtime/routes/playground/seeded-conversations.ts +3 -3
  864. package/src/runtime/routes/playground/state.ts +4 -3
  865. package/src/runtime/routes/plugins-routes.ts +22 -19
  866. package/src/runtime/routes/profiler-routes.ts +17 -4
  867. package/src/runtime/routes/ps-routes.ts +5 -0
  868. package/src/runtime/routes/publish-routes.ts +13 -3
  869. package/src/runtime/routes/question-routes.ts +5 -0
  870. package/src/runtime/routes/recording-routes.ts +25 -12
  871. package/src/runtime/routes/rename-conversation-routes.ts +10 -0
  872. package/src/runtime/routes/sanity-routes.ts +9 -2
  873. package/src/runtime/routes/schedule-routes.ts +288 -88
  874. package/src/runtime/routes/secret-routes.ts +31 -6
  875. package/src/runtime/routes/sequence-routes.ts +33 -0
  876. package/src/runtime/routes/settings-routes.ts +65 -19
  877. package/src/runtime/routes/skills-routes.ts +166 -73
  878. package/src/runtime/routes/slack-channel-routes.ts +5 -0
  879. package/src/runtime/routes/stt-routes.ts +13 -6
  880. package/src/runtime/routes/subagents-routes.ts +24 -18
  881. package/src/runtime/routes/suggest-trust-rule-routes.ts +7 -2
  882. package/src/runtime/routes/surface-action-routes.ts +9 -0
  883. package/src/runtime/routes/surface-content-routes.ts +10 -2
  884. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  885. package/src/runtime/routes/task-routes.ts +37 -0
  886. package/src/runtime/routes/telemetry-routes.ts +9 -0
  887. package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
  888. package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
  889. package/src/runtime/routes/trace-event-routes.ts +42 -1
  890. package/src/runtime/routes/trust-rules-routes.ts +31 -2
  891. package/src/runtime/routes/tts-routes.ts +48 -6
  892. package/src/runtime/routes/types.ts +83 -16
  893. package/src/runtime/routes/ui-request-routes.ts +5 -0
  894. package/src/runtime/routes/upgrade-broadcast-routes.ts +5 -0
  895. package/src/runtime/routes/usage-routes.ts +118 -42
  896. package/src/runtime/routes/user-routes-cli.ts +9 -0
  897. package/src/runtime/routes/user-routes.ts +5 -1
  898. package/src/runtime/routes/wake-conversation-routes.ts +5 -0
  899. package/src/runtime/routes/watcher-routes.ts +21 -0
  900. package/src/runtime/routes/webhook-routes.ts +50 -2
  901. package/src/runtime/routes/wipe-conversation-routes.ts +5 -0
  902. package/src/runtime/routes/work-items-routes.ts +49 -23
  903. package/src/runtime/routes/workspace-commit-routes.ts +5 -0
  904. package/src/runtime/routes/workspace-routes.test.ts +42 -0
  905. package/src/runtime/routes/workspace-routes.ts +124 -9
  906. package/src/runtime/services/__tests__/analyze-conversation.test.ts +8 -4
  907. package/src/runtime/services/analyze-conversation.ts +5 -8
  908. package/src/runtime/services/conversation-serializer.ts +24 -2
  909. package/src/runtime/sync/resource-sync-events.ts +16 -2
  910. package/src/runtime/sync/sync-publisher.ts +2 -2
  911. package/src/schedule/run-script.ts +28 -3
  912. package/src/schedule/schedule-store.ts +28 -1
  913. package/src/schedule/schedule-usage-store.ts +83 -0
  914. package/src/schedule/scheduler.ts +15 -6
  915. package/src/signals/cancel.ts +2 -4
  916. package/src/signals/user-message.ts +5 -8
  917. package/src/skills/catalog-files.ts +4 -1
  918. package/src/skills/catalog-install.ts +3 -0
  919. package/src/skills/categories-cache.ts +118 -0
  920. package/src/skills/clawhub-files.ts +1 -0
  921. package/src/skills/skillssh-files.ts +1 -0
  922. package/src/subagent/manager.ts +20 -11
  923. package/src/telemetry/types.ts +55 -1
  924. package/src/telemetry/usage-telemetry-reporter.test.ts +250 -4
  925. package/src/telemetry/usage-telemetry-reporter.ts +88 -2
  926. package/src/tools/acp/context.ts +20 -0
  927. package/src/tools/acp/list-agents.test.ts +7 -1
  928. package/src/tools/acp/spawn.test.ts +198 -93
  929. package/src/tools/acp/spawn.ts +32 -70
  930. package/src/tools/acp/steer.test.ts +105 -8
  931. package/src/tools/acp/steer.ts +48 -17
  932. package/src/tools/apps/definitions.ts +8 -4
  933. package/src/tools/apps/executors.ts +13 -8
  934. package/src/tools/ask-question/ask-question-tool.test.ts +120 -105
  935. package/src/tools/ask-question/ask-question-tool.ts +85 -90
  936. package/src/tools/computer-use/definitions.ts +28 -24
  937. package/src/tools/credential-execution/make-authenticated-request.ts +56 -51
  938. package/src/tools/credential-execution/manage-secure-command-tool.ts +2 -2
  939. package/src/tools/credential-execution/run-authenticated-command.ts +82 -77
  940. package/src/tools/credentials/vault.ts +112 -111
  941. package/src/tools/execution-target.ts +1 -1
  942. package/src/tools/execution-timeout.ts +3 -4
  943. package/src/tools/executor.ts +1 -53
  944. package/src/tools/filesystem/edit.ts +45 -42
  945. package/src/tools/filesystem/list.ts +33 -30
  946. package/src/tools/filesystem/read.ts +54 -35
  947. package/src/tools/filesystem/write.ts +69 -32
  948. package/src/tools/host-filesystem/edit.ts +44 -42
  949. package/src/tools/host-filesystem/read.ts +49 -35
  950. package/src/tools/host-filesystem/transfer.ts +121 -108
  951. package/src/tools/host-filesystem/write.ts +33 -31
  952. package/src/tools/host-terminal/host-shell.ts +50 -48
  953. package/src/tools/memory/register.ts +23 -24
  954. package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
  955. package/src/tools/network/__tests__/web-search.test.ts +11 -3
  956. package/src/tools/network/web-fetch.ts +49 -46
  957. package/src/tools/network/web-search-error.test.ts +248 -0
  958. package/src/tools/network/web-search-error.ts +267 -0
  959. package/src/tools/network/web-search.ts +223 -61
  960. package/src/tools/registry.ts +39 -16
  961. package/src/tools/schedule/create.ts +13 -0
  962. package/src/tools/schedule/update.ts +16 -0
  963. package/src/tools/shared/filesystem/audio-read.ts +122 -0
  964. package/src/tools/shared/filesystem/image-read.ts +1 -1
  965. package/src/tools/skills/execute.ts +34 -31
  966. package/src/tools/skills/load.ts +29 -23
  967. package/src/tools/subagent/notify-parent.ts +35 -32
  968. package/src/tools/subagent/spawn.ts +2 -4
  969. package/src/tools/system/avatar-generator.ts +13 -22
  970. package/src/tools/system/request-permission.ts +30 -27
  971. package/src/tools/terminal/safe-env.ts +10 -1
  972. package/src/tools/terminal/shell.ts +190 -61
  973. package/src/tools/tool-defaults.ts +20 -9
  974. package/src/tools/tool-manifest.ts +4 -4
  975. package/src/tools/types.ts +74 -23
  976. package/src/tools/ui-surface/definitions.ts +99 -10
  977. package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
  978. package/src/tts/provider-catalog.ts +76 -1
  979. package/src/usage/types.ts +10 -0
  980. package/src/util/errors.ts +2 -2
  981. package/src/util/map-limit.ts +27 -0
  982. package/src/util/mutex.ts +47 -0
  983. package/src/util/platform.ts +15 -12
  984. package/src/work-items/work-item-runner.ts +7 -2
  985. package/src/workspace/git-service.ts +1 -42
  986. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +7 -20
  987. package/src/workspace/migrations/092-backfill-v3-leaves.ts +169 -0
  988. package/src/workspace/migrations/093-backfill-leaf-ids.ts +144 -0
  989. package/src/workspace/migrations/094-seed-avatar-manifest.ts +155 -0
  990. package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
  991. package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
  992. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +117 -0
  993. package/src/workspace/migrations/__tests__/094-seed-avatar-manifest.test.ts +136 -0
  994. package/src/workspace/migrations/__tests__/backfill-leaf-ids.test.ts +175 -0
  995. package/src/workspace/migrations/__tests__/backfill-v3-leaves.test.ts +124 -0
  996. package/src/workspace/migrations/registry.ts +12 -0
  997. package/src/workspace/provider-commit-message-generator.ts +15 -17
  998. package/tsconfig.json +4 -1
  999. package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
  1000. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  1001. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  1002. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -262
  1003. package/src/__tests__/empty-response-pipeline.test.ts +0 -301
  1004. package/src/__tests__/history-repair-pipeline.test.ts +0 -396
  1005. package/src/__tests__/llm-call-pipeline.test.ts +0 -281
  1006. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
  1007. package/src/__tests__/persistence-pipeline.test.ts +0 -514
  1008. package/src/__tests__/title-generate-pipeline.test.ts +0 -211
  1009. package/src/__tests__/token-estimate-pipeline.test.ts +0 -481
  1010. package/src/__tests__/tool-error-pipeline.test.ts +0 -241
  1011. package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
  1012. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -344
  1013. package/src/cli/commands/__tests__/memory-v3-render.test.ts +0 -340
  1014. package/src/cli/commands/memory-v3-render.ts +0 -491
  1015. package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
  1016. package/src/daemon/message-types/disk-pressure.ts +0 -9
  1017. package/src/email/feature-gate.ts +0 -23
  1018. package/src/gallery/default-gallery.ts +0 -1359
  1019. package/src/gallery/gallery-manifest.ts +0 -28
  1020. package/src/memory/v3/__tests__/coactivation-store.test.ts +0 -422
  1021. package/src/memory/v3/__tests__/consolidation-job.test.ts +0 -466
  1022. package/src/memory/v3/__tests__/coretrieval-seed.test.ts +0 -270
  1023. package/src/memory/v3/__tests__/edge-learning-job.test.ts +0 -324
  1024. package/src/memory/v3/__tests__/edges.test.ts +0 -706
  1025. package/src/memory/v3/__tests__/filter.test.ts +0 -560
  1026. package/src/memory/v3/__tests__/gate.test.ts +0 -637
  1027. package/src/memory/v3/__tests__/index-composition.test.ts +0 -291
  1028. package/src/memory/v3/__tests__/loop.test.ts +0 -775
  1029. package/src/memory/v3/__tests__/retriever.test.ts +0 -226
  1030. package/src/memory/v3/__tests__/scouts.test.ts +0 -489
  1031. package/src/memory/v3/__tests__/shadow-diff.test.ts +0 -225
  1032. package/src/memory/v3/__tests__/shadow-middleware.test.ts +0 -398
  1033. package/src/memory/v3/__tests__/system-prompts.test.ts +0 -154
  1034. package/src/memory/v3/__tests__/traversal.test.ts +0 -508
  1035. package/src/memory/v3/__tests__/tree-index.test.ts +0 -280
  1036. package/src/memory/v3/__tests__/tree-store.test.ts +0 -529
  1037. package/src/memory/v3/__tests__/tree-walk.test.ts +0 -784
  1038. package/src/memory/v3/__tests__/validate.test.ts +0 -277
  1039. package/src/memory/v3/auto-edges.ts +0 -223
  1040. package/src/memory/v3/coactivation-store.ts +0 -124
  1041. package/src/memory/v3/consolidation-job.ts +0 -323
  1042. package/src/memory/v3/coretrieval-seed.ts +0 -240
  1043. package/src/memory/v3/edge-learning-job.ts +0 -160
  1044. package/src/memory/v3/edges.ts +0 -286
  1045. package/src/memory/v3/filter.ts +0 -286
  1046. package/src/memory/v3/gate.ts +0 -349
  1047. package/src/memory/v3/index-composition.ts +0 -126
  1048. package/src/memory/v3/llm-capture.ts +0 -46
  1049. package/src/memory/v3/loop.ts +0 -430
  1050. package/src/memory/v3/maintenance.ts +0 -144
  1051. package/src/memory/v3/prompt-context.ts +0 -33
  1052. package/src/memory/v3/prompts/consolidation.ts +0 -458
  1053. package/src/memory/v3/prompts/system-prompts.ts +0 -196
  1054. package/src/memory/v3/retriever.ts +0 -33
  1055. package/src/memory/v3/scouts.ts +0 -431
  1056. package/src/memory/v3/shadow-diff.ts +0 -287
  1057. package/src/memory/v3/shadow-middleware.ts +0 -347
  1058. package/src/memory/v3/traversal.ts +0 -211
  1059. package/src/memory/v3/tree-index.ts +0 -237
  1060. package/src/memory/v3/tree-store.ts +0 -394
  1061. package/src/memory/v3/tree-walk.ts +0 -356
  1062. package/src/memory/v3/types.ts +0 -65
  1063. package/src/memory/v3/validate.ts +0 -323
  1064. package/src/plugins/defaults/circuit-breaker.ts +0 -141
  1065. package/src/plugins/defaults/compaction.ts +0 -141
  1066. package/src/plugins/defaults/empty-response.ts +0 -124
  1067. package/src/plugins/defaults/history-repair.ts +0 -83
  1068. package/src/plugins/defaults/llm-call.ts +0 -77
  1069. package/src/plugins/defaults/memory-retrieval.ts +0 -219
  1070. package/src/plugins/defaults/overflow-reduce.ts +0 -185
  1071. package/src/plugins/defaults/persistence.ts +0 -146
  1072. package/src/plugins/defaults/title-generate.ts +0 -90
  1073. package/src/plugins/defaults/token-estimate.ts +0 -101
  1074. package/src/plugins/defaults/tool-error.ts +0 -119
  1075. package/src/plugins/defaults/tool-execute.ts +0 -87
  1076. package/src/plugins/defaults/tool-result-truncate.ts +0 -84
  1077. package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +0 -35
  1078. package/src/skills/category-inference.ts +0 -111
@@ -17,6 +17,7 @@ import type {
17
17
  import { getConfig } from "../config/loader.js";
18
18
  import { recordEstimate } from "../context/estimator-calibration.js";
19
19
  import { getCalibrationProviderKey } from "../context/token-estimator.js";
20
+ import type { ContextWindowResult } from "../context/window-manager.js";
20
21
  import { projectAssistantMessage } from "../memory/conversation-attention-store.js";
21
22
  import {
22
23
  deleteMessageById,
@@ -24,8 +25,12 @@ import {
24
25
  getMessageById,
25
26
  messageMetadataSchema,
26
27
  provenanceFromTrustContext,
28
+ reserveMessage,
29
+ setConversationHistoryStrippedAt,
30
+ setLastNotifiedInferenceProfile,
27
31
  updateMessageContent,
28
32
  } from "../memory/conversation-crud.js";
33
+ import { syncMessageToDisk } from "../memory/conversation-disk-view.js";
29
34
  import { indexMessageNow } from "../memory/indexer.js";
30
35
  import {
31
36
  backfillMessageIdOnLogs,
@@ -41,20 +46,24 @@ import {
41
46
  type SlackMessageMetadata,
42
47
  writeSlackMetadata,
43
48
  } from "../messaging/providers/slack/message-metadata.js";
44
- import { defaultPersistenceTerminal } from "../plugins/defaults/persistence.js";
45
- import { DEFAULT_TIMEOUTS, runPipeline } from "../plugins/pipeline.js";
46
- import { getMiddlewaresFor } from "../plugins/registry.js";
47
49
  import type {
48
- PersistArgs,
49
- PersistReserveResult,
50
- PersistResult,
51
- TurnContext,
52
- } from "../plugins/types.js";
53
- import type { ContentBlock, ImageContent } from "../providers/types.js";
50
+ ContentBlock,
51
+ ImageContent,
52
+ Message,
53
+ } from "../providers/types.js";
54
54
  import { isContextOverflowError } from "../providers/types.js";
55
+ import {
56
+ getCurrentSeq,
57
+ recordPersistedSeq,
58
+ } from "../runtime/assistant-stream-state.js";
55
59
  import { publishSyncInvalidation } from "../runtime/sync/sync-publisher.js";
56
60
  import { redactSecrets } from "../security/secret-scanner.js";
57
61
  import { extractDomain } from "../tools/network/domain-normalize.js";
62
+ import {
63
+ classifyWebSearchFailure,
64
+ logWebSearchBackendFailure,
65
+ WEB_SEARCH_BACKEND_FAILURE_MESSAGE,
66
+ } from "../tools/network/web-search-error.js";
58
67
  import {
59
68
  buildPricingUsage,
60
69
  resolveStructuredPricing,
@@ -67,7 +76,10 @@ import {
67
76
  cleanAssistantContent,
68
77
  drainDirectiveDisplayBuffer,
69
78
  } from "./assistant-attachments.js";
70
- import type { AgentLoopConversationContext } from "./conversation-agent-loop.js";
79
+ import type {
80
+ AgentLoopConversationContext,
81
+ AssistantSurface,
82
+ } from "./conversation-agent-loop.js";
71
83
  import {
72
84
  buildConversationErrorMessage,
73
85
  classifyConversationError,
@@ -84,6 +96,7 @@ import type {
84
96
  } from "./message-protocol.js";
85
97
  import { conversationMetadataSyncTag } from "./message-types/sync.js";
86
98
  import type {
99
+ ToolActivityMetadata,
87
100
  WebSearchMetadata,
88
101
  WebSearchResultItem,
89
102
  } from "./message-types/web-activity.js";
@@ -91,33 +104,28 @@ import type {
91
104
  const log = getLogger("agent-loop-handlers");
92
105
 
93
106
  /**
94
- * Build a {@link TurnContext} from the handler's deps for pipeline logging
95
- * and plugin attribution.
96
- *
97
- * Reads `turnIndex` from `deps.ctx.turnCount` the orchestrator-owned
98
- * per-turn counter that is stable for the entire duration of a single
99
- * `runAgentLoopImpl` invocation. The handlers fire after the orchestrator
100
- * has completed its in-turn pipeline work but before `ctx.turnCount++` runs
101
- * in the outer `finally` block, so this value always reflects the turn the
102
- * handler's event belongs to. Trust pulls from the per-turn snapshot first,
103
- * then the conversation-level context, then the canonical `unknown`
104
- * fallback so the required field stays populated for edge cases (fresh
105
- * conversations before the trust resolver runs, heartbeat turns that never
106
- * bind an actor).
107
+ * Persist the history-stripped marker after the loop strips runtime injections
108
+ * for compaction / overflow recovery. The marker is a durability hint, not
109
+ * turn-critical state — a transient SQLite write failure (SQLITE_BUSY,
110
+ * disk-full, read-only FS) must not abort the turn, so failures log a warning
111
+ * and continue.
107
112
  */
108
- function buildHandlerTurnContext(deps: EventHandlerDeps): TurnContext {
109
- return {
110
- requestId: deps.reqId,
111
- conversationId: deps.ctx.conversationId,
112
- turnIndex: deps.ctx.turnCount,
113
- trust: deps.ctx.currentTurnTrustContext ??
114
- deps.ctx.trustContext ?? {
115
- sourceChannel: "vellum",
116
- trustClass: "unknown",
117
- },
118
- };
113
+ export function markHistoryStrippedBestEffort(conversationId: string): void {
114
+ try {
115
+ setConversationHistoryStrippedAt(conversationId, Date.now());
116
+ } catch (err) {
117
+ log.warn(
118
+ { err, conversationId },
119
+ "Failed to persist history-stripped marker after compaction strip (non-fatal)",
120
+ );
121
+ }
119
122
  }
120
123
 
124
+ // ── Partial-persistence tunables ─────────────────────────────────────
125
+ // Debounce for mid-turn `updateContent` writes from text deltas.
126
+ // Indexer + projector still fire ONLY at `handleMessageComplete`.
127
+ const PARTIAL_PERSIST_DEBOUNCE_MS = 1000;
128
+
121
129
  // ── Types ────────────────────────────────────────────────────────────
122
130
 
123
131
  export interface PendingToolResult {
@@ -129,6 +137,16 @@ export interface PendingToolResult {
129
137
  /** Mutable state shared across event handlers within a single agent loop run. */
130
138
  export interface EventHandlerState {
131
139
  llmCallStartedEmitted: boolean;
140
+ /**
141
+ * Profile key whose `model_profile` notice has been assembled into the turn
142
+ * context but not yet marked notified. Set when the turn injects the notice,
143
+ * and consumed the first time the model actually receives that context — i.e.
144
+ * on the first `message_complete`. Persisting on delivery (rather than inline
145
+ * before the provider call) means a cancelled or failed turn re-sends the
146
+ * notice next turn instead of silently marking the profile notified without
147
+ * the model ever having seen it.
148
+ */
149
+ pendingNotifiedInferenceProfile: string | null;
132
150
  pendingDirectiveDisplayBuffer: string;
133
151
  firstAssistantText: string;
134
152
  /** Most recent resolved provider for the current exchange's usage accounting. */
@@ -178,6 +196,15 @@ export interface EventHandlerState {
178
196
  */
179
197
  assistantRowAwaitingFinalization: boolean;
180
198
  readonly pendingToolResults: Map<string, PendingToolResult>;
199
+ /**
200
+ * Reservation of the grouped `user` tool-result row for the current batch,
201
+ * resolving to the row id. Shared across the concurrent `handleToolResult`
202
+ * calls of one parallel-tool batch so they reserve exactly one row and write
203
+ * into it as sibling results land. `undefined` until the first result of a
204
+ * batch triggers a reservation (reset on a failed reservation so the next
205
+ * arrival can retry) and again after the batch is finalized.
206
+ */
207
+ pendingToolResultRowReservation: Promise<string> | undefined;
181
208
  readonly persistedToolUseIds: Set<string>;
182
209
  readonly accumulatedDirectives: DirectiveRequest[];
183
210
  readonly accumulatedToolContentBlocks: ContentBlock[];
@@ -185,7 +212,6 @@ export interface EventHandlerState {
185
212
  readonly toolContentBlockToolNames: Map<number, string>;
186
213
  readonly directiveWarnings: string[];
187
214
  readonly toolUseIdToName: Map<string, string>;
188
- currentTurnToolNames: string[];
189
215
  /** Sticky for the whole run: this turn created/refreshed an app. */
190
216
  appBuildToolUsedThisRun: boolean;
191
217
  /** Tracks whether the first text delta has been emitted this turn for activity state transitions. */
@@ -231,6 +257,13 @@ export interface EventHandlerState {
231
257
  riskDirectoryScopeOptions?: Array<{ scope: string; label: string }>;
232
258
  }
233
259
  >;
260
+ /**
261
+ * Structured tool activity (web_search / web_fetch) keyed by tool_use_id,
262
+ * captured when a result lands so it can be persisted on the tool's content
263
+ * block and survive a history reopen. Populated for both external provider
264
+ * tools (in handleToolResult) and native server tools (server_tool_complete).
265
+ */
266
+ readonly toolActivityMetadata: Map<string, ToolActivityMetadata>;
234
267
  /** tool_use_ids emitted in the current turn (populated in handleToolUse, cleared after annotation). */
235
268
  currentTurnToolUseIds: string[];
236
269
  /** Wall-clock time (ms since epoch) when the agent loop turn started, used as the display timestamp for assistant messages. */
@@ -239,6 +272,38 @@ export interface EventHandlerState {
239
272
  readonly serverToolStartedAt: Map<string, number>;
240
273
  /** Original input from server_tool_start, keyed by tool_use_id, so the complete handler can read the query. */
241
274
  readonly serverToolInputs: Map<string, Record<string, unknown>>;
275
+ /** Request ids for which a user-facing web_search backend-failure notice was already surfaced this turn (dedup noisy repeats). Keyed by request id; each turn has a fresh request id, so this grows at most one entry per turn. */
276
+ readonly webSearchBackendFailureNotified: Set<string>;
277
+ /** Active debounce timer for partial persistence; `undefined` when idle. */
278
+ pendingPartialFlushTimer: ReturnType<typeof setTimeout> | undefined;
279
+ /** In-flight partial flush write awaited at finalize to avoid overwrite races. */
280
+ pendingPartialFlushPromise: Promise<void> | undefined;
281
+ /**
282
+ * Running mirror of the in-flight assistant message's streamed content
283
+ * (text and thinking), flushed to the assistant row on the partial-persist
284
+ * debounce so a mid-turn snapshot reflects what the user is watching live.
285
+ */
286
+ currentMessageContent: ContentBlock[];
287
+ /**
288
+ * `seq` of the most recent streamed content delta mirrored into
289
+ * `currentMessageContent`. Recorded as the conversation's persisted `seq`
290
+ * after each flush commits (the debounced partial flushes and the
291
+ * `message_complete` finalize), so the snapshot's advertised `seq` tracks
292
+ * exactly the streamed content the durable row holds. `undefined` until the
293
+ * first content delta of the in-flight message. Because every streamed
294
+ * content type rides the same mirror-and-flush path, this single field
295
+ * never claims content a flush has not yet written.
296
+ */
297
+ lastPersistedContentSeq: number | undefined;
298
+ /**
299
+ * Whether the reducer has compacted `ctx.messages`, gating the Slack
300
+ * chronological-transcript override on re-injection. The captured
301
+ * transcript is the full persisted history; blindly replaying it after
302
+ * compaction would overwrite the reduced messages and undo compaction, so
303
+ * once this is `true` the override falls back to the reduced
304
+ * `ctx.messages`.
305
+ */
306
+ reducerCompacted: boolean;
242
307
  }
243
308
 
244
309
  /** Immutable context shared across event handlers within a single agent loop run. */
@@ -252,6 +317,18 @@ export interface EventHandlerDeps {
252
317
  readonly rlog: pino.Logger;
253
318
  readonly turnChannelContext: TurnChannelContext;
254
319
  readonly turnInterfaceContext: TurnInterfaceContext;
320
+ /**
321
+ * Commit a successful inline compaction to durable state. Invoked from the
322
+ * `compaction_completed` dispatch case (when `result.compacted`) with the
323
+ * loop's compaction result and the stripped pre-compaction `basis`. Supplied
324
+ * by the orchestrator because the body writes Conversation DB-record fields,
325
+ * projects Slack provenance, and emits transport the loop is intentionally
326
+ * blind to.
327
+ */
328
+ readonly applyCompaction: (
329
+ result: ContextWindowResult,
330
+ basis: Message[],
331
+ ) => Promise<void>;
255
332
  }
256
333
 
257
334
  // ── Factory ──────────────────────────────────────────────────────────
@@ -259,6 +336,7 @@ export interface EventHandlerDeps {
259
336
  export function createEventHandlerState(): EventHandlerState {
260
337
  return {
261
338
  llmCallStartedEmitted: false,
339
+ pendingNotifiedInferenceProfile: null,
262
340
  pendingDirectiveDisplayBuffer: "",
263
341
  firstAssistantText: "",
264
342
  exchangeProviderName: undefined,
@@ -279,13 +357,13 @@ export function createEventHandlerState(): EventHandlerState {
279
357
  lastAssistantMessageId: undefined,
280
358
  assistantRowAwaitingFinalization: false,
281
359
  pendingToolResults: new Map(),
360
+ pendingToolResultRowReservation: undefined,
282
361
  persistedToolUseIds: new Set(),
283
362
  accumulatedDirectives: [],
284
363
  accumulatedToolContentBlocks: [],
285
364
  toolContentBlockToolNames: new Map(),
286
365
  directiveWarnings: [],
287
366
  toolUseIdToName: new Map(),
288
- currentTurnToolNames: [],
289
367
  appBuildToolUsedThisRun: false,
290
368
  firstTextDeltaEmitted: false,
291
369
  firstThinkingDeltaEmitted: false,
@@ -295,13 +373,167 @@ export function createEventHandlerState(): EventHandlerState {
295
373
  requestIdToToolUseId: new Map(),
296
374
  toolConfirmationOutcomes: new Map(),
297
375
  toolRiskOutcomes: new Map(),
376
+ toolActivityMetadata: new Map(),
298
377
  currentTurnToolUseIds: [],
299
378
  turnStartedAt: Date.now(),
300
379
  serverToolStartedAt: new Map(),
301
380
  serverToolInputs: new Map(),
381
+ webSearchBackendFailureNotified: new Set(),
382
+ pendingPartialFlushTimer: undefined,
383
+ pendingPartialFlushPromise: undefined,
384
+ currentMessageContent: [],
385
+ lastPersistedContentSeq: undefined,
386
+ reducerCompacted: false,
302
387
  };
303
388
  }
304
389
 
390
+ // ── Partial-persistence helpers ──────────────────────────────────────
391
+
392
+ /** Canonical persisted-content build: clean → append surfaces → redact. */
393
+ export function buildPersistedAssistantContent(
394
+ rawBlocks: readonly ContentBlock[],
395
+ surfaces: readonly AssistantSurface[],
396
+ activityByToolUseId?: ReadonlyMap<string, ToolActivityMetadata>,
397
+ ): ContentBlock[] {
398
+ const { cleanedContent } = cleanAssistantContent(rawBlocks);
399
+ const cleaned = cleanedContent as ContentBlock[];
400
+ const withSurfaces: ContentBlock[] = [...cleaned];
401
+ for (const surface of surfaces) {
402
+ withSurfaces.push({
403
+ type: "ui_surface",
404
+ surfaceId: surface.surfaceId,
405
+ surfaceType: surface.surfaceType,
406
+ title: surface.title,
407
+ data: surface.data,
408
+ actions: surface.actions,
409
+ display: surface.display,
410
+ ...(surface.persistent ? { persistent: true } : {}),
411
+ ...(surface.toolCallId ? { toolCallId: surface.toolCallId } : {}),
412
+ } as unknown as ContentBlock);
413
+ }
414
+ return withSurfaces.map((block) => {
415
+ if (block.type === "text") {
416
+ const tb = block as Extract<ContentBlock, { type: "text" }>;
417
+ return { ...tb, text: redactSecrets(tb.text) };
418
+ }
419
+ // Native server tools (Anthropic web_search) resolve mid-stream — their
420
+ // `server_tool_complete` fires before `message_complete` — so the captured
421
+ // activity is available at persist time. Stamp it on the server_tool_use
422
+ // block here so the web-search card survives a history reopen. External
423
+ // tool_use activity arrives only with the later tool_result, so it is
424
+ // stamped in `annotatePersistedAssistantMessage` instead.
425
+ if (block.type === "server_tool_use" && activityByToolUseId) {
426
+ const activity = activityByToolUseId.get(block.id);
427
+ if (activity) {
428
+ return { ...block, _activityMetadata: activity } as ContentBlock;
429
+ }
430
+ }
431
+ return block;
432
+ });
433
+ }
434
+
435
+ /** Append a streamed text chunk to `state.currentMessageContent`, fusing into tail text block. */
436
+ function appendTextToCurrentMessage(
437
+ state: EventHandlerState,
438
+ text: string,
439
+ ): void {
440
+ if (text.length === 0) return;
441
+ const tail = state.currentMessageContent.at(-1);
442
+ if (tail && tail.type === "text") {
443
+ tail.text = tail.text + text;
444
+ } else {
445
+ state.currentMessageContent.push({ type: "text", text });
446
+ }
447
+ }
448
+
449
+ /**
450
+ * Append a streamed thinking chunk to `state.currentMessageContent`, fusing
451
+ * into the tail thinking block. The streamed delta carries no provider
452
+ * `signature` (that arrives only when the block closes), so the mirrored block
453
+ * holds an empty one; `message_complete` overwrites the row with the
454
+ * authoritative signed content before it is ever sent back to a provider.
455
+ */
456
+ function appendThinkingToCurrentMessage(
457
+ state: EventHandlerState,
458
+ thinking: string,
459
+ ): void {
460
+ if (thinking.length === 0) return;
461
+ const tail = state.currentMessageContent.at(-1);
462
+ if (tail && tail.type === "thinking") {
463
+ tail.thinking = tail.thinking + thinking;
464
+ } else {
465
+ state.currentMessageContent.push({
466
+ type: "thinking",
467
+ thinking,
468
+ signature: "",
469
+ });
470
+ }
471
+ }
472
+
473
+ /** Reset partial-persist accumulator and any pending flush state. Idempotent. */
474
+ function resetPartialPersistAccumulator(state: EventHandlerState): void {
475
+ if (state.pendingPartialFlushTimer !== undefined) {
476
+ clearTimeout(state.pendingPartialFlushTimer);
477
+ state.pendingPartialFlushTimer = undefined;
478
+ }
479
+ state.currentMessageContent = [];
480
+ state.lastPersistedContentSeq = undefined;
481
+ state.pendingPartialFlushPromise = undefined;
482
+ }
483
+
484
+ /** Flush `state.currentMessageContent` to the persisted assistant row. */
485
+ async function flushAccumulatedContent(
486
+ state: EventHandlerState,
487
+ deps: EventHandlerDeps,
488
+ ): Promise<void> {
489
+ const messageId = state.lastAssistantMessageId;
490
+ if (messageId === undefined) return;
491
+ if (state.currentMessageContent.length === 0) return;
492
+
493
+ const built = buildPersistedAssistantContent(
494
+ state.currentMessageContent,
495
+ [],
496
+ state.toolActivityMetadata,
497
+ );
498
+ const contentJson = JSON.stringify(built);
499
+ // Pair the seq with the exact content snapshot taken above: deltas that
500
+ // arrive while the write is in flight bump `lastPersistedContentSeq`
501
+ // again, but they are not part of this write.
502
+ const flushedSeq = state.lastPersistedContentSeq;
503
+
504
+ try {
505
+ updateMessageContent(messageId, contentJson);
506
+ // Record only after the write commits, so the snapshot seq never
507
+ // claims content that is not yet durable.
508
+ if (flushedSeq != null) {
509
+ recordPersistedSeq(deps.ctx.conversationId, flushedSeq);
510
+ }
511
+ } catch (err) {
512
+ deps.rlog.warn(
513
+ { err, messageId },
514
+ "partial flush of accumulated assistant content failed; finalize at message_complete will recover",
515
+ );
516
+ }
517
+ }
518
+
519
+ /** Schedule a debounced partial flush. First-scheduled wins; no-op when timer pending. */
520
+ function schedulePartialFlush(
521
+ state: EventHandlerState,
522
+ deps: EventHandlerDeps,
523
+ ): void {
524
+ if (state.pendingPartialFlushTimer !== undefined) return;
525
+ state.pendingPartialFlushTimer = setTimeout(() => {
526
+ state.pendingPartialFlushTimer = undefined;
527
+ const flushPromise = flushAccumulatedContent(state, deps);
528
+ state.pendingPartialFlushPromise = flushPromise;
529
+ void flushPromise.finally(() => {
530
+ if (state.pendingPartialFlushPromise === flushPromise) {
531
+ state.pendingPartialFlushPromise = undefined;
532
+ }
533
+ });
534
+ }, PARTIAL_PERSIST_DEBOUNCE_MS);
535
+ }
536
+
305
537
  // ── Shared Helper ────────────────────────────────────────────────────
306
538
 
307
539
  // providerNameOverride should be supplied when the caller already knows the
@@ -511,12 +743,6 @@ export async function handleLlmCallStarted(
511
743
  // the `assistantRowAwaitingFinalization` flag — `handleMessageComplete`
512
744
  // clears it after the successful `updateContent`, so the previous call's
513
745
  // committed row is never touched here.
514
- //
515
- // Direct `deleteMessageById` (not via the `persistence` pipeline) is
516
- // intentional: a never-finalized reservation has no segments, no
517
- // attachments, and no observable history — undoing it isn't a real
518
- // persistence event for plugins to react to, so routing through the
519
- // pipeline would only widen the mock surface for no observability win.
520
746
  if (state.assistantRowAwaitingFinalization && state.lastAssistantMessageId) {
521
747
  try {
522
748
  deleteMessageById(state.lastAssistantMessageId);
@@ -530,24 +756,22 @@ export async function handleLlmCallStarted(
530
756
  }
531
757
 
532
758
  const metadata = buildAssistantChannelMetadata(state, deps);
533
- const reserveResult = (await runPipeline<PersistArgs, PersistResult>(
534
- "persistence",
535
- getMiddlewaresFor("persistence"),
536
- defaultPersistenceTerminal,
537
- {
538
- op: "reserve",
539
- conversationId: deps.ctx.conversationId,
540
- role: "assistant",
541
- metadata,
542
- },
543
- buildHandlerTurnContext(deps),
544
- DEFAULT_TIMEOUTS.persistence,
545
- )) as PersistReserveResult;
546
- state.lastAssistantMessageId = reserveResult.message.id;
759
+ const reservedRow = await reserveMessage(
760
+ deps.ctx.conversationId,
761
+ "assistant",
762
+ metadata,
763
+ );
764
+ state.lastAssistantMessageId = reservedRow.id;
547
765
  state.assistantRowAwaitingFinalization = true;
766
+ // Fresh row → fresh accumulator. If an earlier (failed) LLM call
767
+ // within the same run left partial state behind, the
768
+ // `assistantRowAwaitingFinalization` cleanup above already deleted
769
+ // the orphan row, so the accumulator content would point at a
770
+ // non-existent id. Reset here so the new row starts from zero.
771
+ resetPartialPersistAccumulator(state);
548
772
  deps.onEvent({
549
773
  type: "assistant_turn_start",
550
- messageId: reserveResult.message.id,
774
+ messageId: reservedRow.id,
551
775
  conversationId: deps.ctx.conversationId,
552
776
  });
553
777
  }
@@ -568,13 +792,10 @@ function handleTextDelta(
568
792
  if (drained.emitText.length > 0) {
569
793
  if (!state.firstTextDeltaEmitted) {
570
794
  state.firstTextDeltaEmitted = true;
571
- deps.ctx.emitActivityState(
572
- "streaming",
573
- "first_text_delta",
574
- "assistant_turn",
575
- deps.reqId,
576
- "Thinking",
577
- );
795
+ deps.ctx.emitActivityState("streaming", "first_text_delta", {
796
+ requestId: deps.reqId,
797
+ statusText: "Thinking",
798
+ });
578
799
  }
579
800
  deps.onEvent({
580
801
  type: "assistant_text_delta",
@@ -583,6 +804,15 @@ function handleTextDelta(
583
804
  messageId: state.lastAssistantMessageId,
584
805
  });
585
806
  if (deps.shouldGenerateTitle) state.firstAssistantText += drained.emitText;
807
+ // Mirror the drained delta into state.currentMessageContent so partial
808
+ // flushes mid-turn see the same content the user is watching live.
809
+ appendTextToCurrentMessage(state, drained.emitText);
810
+ // The hub stamps `seq` synchronously on the delta emitted above, so
811
+ // `getCurrentSeq()` here is that delta's seq -- the position the
812
+ // mirrored content now reflects. A partial flush snapshots this to
813
+ // record how far the durable rows track the live stream.
814
+ state.lastPersistedContentSeq = getCurrentSeq();
815
+ schedulePartialFlush(state, deps);
586
816
  }
587
817
  }
588
818
 
@@ -603,13 +833,10 @@ function handleThinkingDelta(
603
833
  // assistantStatusText for every assistant_activity_state event.
604
834
  if (lastToolName) {
605
835
  const statusText = `Processing ${friendlyToolName(lastToolName)} results`;
606
- deps.ctx.emitActivityState(
607
- "thinking",
608
- "thinking_delta",
609
- "assistant_turn",
610
- deps.reqId,
836
+ deps.ctx.emitActivityState("thinking", "thinking_delta", {
837
+ requestId: deps.reqId,
611
838
  statusText,
612
- );
839
+ });
613
840
  }
614
841
  }
615
842
  if (!deps.ctx.streamThinking) return;
@@ -620,6 +847,14 @@ function handleThinkingDelta(
620
847
  conversationId: deps.ctx.conversationId,
621
848
  messageId: state.lastAssistantMessageId,
622
849
  });
850
+ // Mirror thinking into the same running view as text so the debounced
851
+ // partial flush persists it mid-turn -- long reasoning streams survive a
852
+ // refresh that outlives the SSE replay window, exactly as long answers do.
853
+ appendThinkingToCurrentMessage(state, event.thinking);
854
+ // The hub stamps `seq` synchronously on the delta emitted above, so
855
+ // `getCurrentSeq()` is that delta's position in the mirrored content.
856
+ state.lastPersistedContentSeq = getCurrentSeq();
857
+ schedulePartialFlush(state, deps);
623
858
  }
624
859
 
625
860
  export function handleToolUse(
@@ -628,7 +863,6 @@ export function handleToolUse(
628
863
  event: Extract<AgentEvent, { type: "tool_use" }>,
629
864
  ): void {
630
865
  state.toolUseIdToName.set(event.id, event.name);
631
- state.currentTurnToolNames.push(event.name);
632
866
  if (event.name === "app_create" || event.name === "app_refresh") {
633
867
  state.appBuildToolUsedThisRun = true;
634
868
  }
@@ -636,13 +870,10 @@ export function handleToolUse(
636
870
  state.currentToolUseId = event.id;
637
871
  state.currentTurnToolUseIds.push(event.id);
638
872
  const statusText = computeToolUseStatusText(event.name, event.input);
639
- deps.ctx.emitActivityState(
640
- "tool_running",
641
- "tool_use_start",
642
- "assistant_turn",
643
- deps.reqId,
873
+ deps.ctx.emitActivityState("tool_running", "tool_use_start", {
874
+ requestId: deps.reqId,
644
875
  statusText,
645
- );
876
+ });
646
877
  deps.onEvent({
647
878
  type: "tool_use_start",
648
879
  toolName: event.name,
@@ -651,6 +882,14 @@ export function handleToolUse(
651
882
  toolUseId: event.id,
652
883
  messageId: state.lastAssistantMessageId,
653
884
  });
885
+ // `message_complete` always precedes tool events (see handleMessageComplete),
886
+ // so this tool_use block is already durable in the assistant row. The
887
+ // `tool_use_start` emitted just above is therefore the newest stamped event
888
+ // whose content the `/messages` snapshot already reflects -- advance the
889
+ // persisted seq to it. Without this the snapshot would advertise a seq below
890
+ // an event it already incorporates, and a client applying `seq > snapshot.seq`
891
+ // would replay this tool start.
892
+ recordPersistedSeq(deps.ctx.conversationId, getCurrentSeq());
654
893
  }
655
894
 
656
895
  export function handleToolUsePreviewStart(
@@ -666,13 +905,10 @@ export function handleToolUsePreviewStart(
666
905
  messageId: state.lastAssistantMessageId,
667
906
  });
668
907
  const statusText = `Preparing ${friendlyToolName(event.toolName)}...`;
669
- deps.ctx.emitActivityState(
670
- "tool_running",
671
- "preview_start",
672
- "assistant_turn",
673
- deps.reqId,
908
+ deps.ctx.emitActivityState("tool_running", "preview_start", {
909
+ requestId: deps.reqId,
674
910
  statusText,
675
- );
911
+ });
676
912
  }
677
913
 
678
914
  function handleToolOutputChunk(
@@ -771,11 +1007,244 @@ export function handleInputJsonDelta(
771
1007
  });
772
1008
  }
773
1009
 
774
- export function handleToolResult(
1010
+ /**
1011
+ * Build the persisted `tool_result` content blocks for the buffered results,
1012
+ * redacting secrets from both the flat content and any structured blocks. All
1013
+ * results of one assistant turn share a single `user` row (the shape providers
1014
+ * expect for tool_result-in-user-turn).
1015
+ */
1016
+ function buildToolResultBlocks(
1017
+ pending: ReadonlyMap<string, PendingToolResult>,
1018
+ ) {
1019
+ return Array.from(pending.entries()).map(([toolUseId, result]) => ({
1020
+ type: "tool_result",
1021
+ tool_use_id: toolUseId,
1022
+ content: redactSecrets(result.content),
1023
+ is_error: result.isError,
1024
+ ...(result.contentBlocks
1025
+ ? {
1026
+ contentBlocks: result.contentBlocks.map((block) =>
1027
+ block.type === "text"
1028
+ ? { ...block, text: redactSecrets(block.text) }
1029
+ : block,
1030
+ ),
1031
+ }
1032
+ : {}),
1033
+ }));
1034
+ }
1035
+
1036
+ /**
1037
+ * Channel/interface provenance metadata for the grouped tool-result row,
1038
+ * stamped from the turn context so the row carries the same provenance the
1039
+ * snapshot reflects from the moment it lands in SQLite.
1040
+ */
1041
+ function buildToolResultMetadata(
1042
+ deps: EventHandlerDeps,
1043
+ ): Record<string, unknown> {
1044
+ return {
1045
+ ...provenanceFromTrustContext(deps.ctx.trustContext),
1046
+ userMessageChannel: deps.turnChannelContext.userMessageChannel,
1047
+ assistantMessageChannel: deps.turnChannelContext.assistantMessageChannel,
1048
+ userMessageInterface: deps.turnInterfaceContext.userMessageInterface,
1049
+ assistantMessageInterface:
1050
+ deps.turnInterfaceContext.assistantMessageInterface,
1051
+ };
1052
+ }
1053
+
1054
+ /**
1055
+ * Reserve the grouped `user` tool-result row for the current batch exactly
1056
+ * once. Parallel tool results are dispatched without awaiting (`agent/loop.ts`
1057
+ * emits each `tool_result` synchronously), so concurrent `handleToolResult`
1058
+ * calls can reach this before the first reservation resolves; sharing one
1059
+ * in-flight reservation promise keeps the whole batch in a single row. A
1060
+ * failed reservation resets the promise so the next caller can retry rather
1061
+ * than inheriting a settled rejection.
1062
+ */
1063
+ function ensureToolResultRowReserved(
1064
+ state: EventHandlerState,
1065
+ conversationId: string,
1066
+ metadata: Record<string, unknown>,
1067
+ ): Promise<string> {
1068
+ if (state.pendingToolResultRowReservation === undefined) {
1069
+ state.pendingToolResultRowReservation = reserveMessage(
1070
+ conversationId,
1071
+ "user",
1072
+ metadata,
1073
+ )
1074
+ .then((reserved) => reserved.id)
1075
+ .catch((err) => {
1076
+ state.pendingToolResultRowReservation = undefined;
1077
+ throw err;
1078
+ });
1079
+ }
1080
+ return state.pendingToolResultRowReservation;
1081
+ }
1082
+
1083
+ /**
1084
+ * Persist the buffered tool results into their grouped `user` row as each
1085
+ * result arrives, so a long-running tool's output survives a refresh that
1086
+ * outlives the SSE replay window. The row is reserved once per batch and
1087
+ * rewritten in place as sibling parallel results land, keeping all
1088
+ * `tool_result` blocks of one turn in a single message. `seq` is the position
1089
+ * stamped on the triggering `tool_result` event, captured by the caller before
1090
+ * any await so it reflects exactly the content now durable in the row.
1091
+ * Indexing and the buffer drain are deferred to `finalizePendingToolResultRow`.
1092
+ */
1093
+ async function persistPendingToolResultRow(
1094
+ state: EventHandlerState,
1095
+ deps: EventHandlerDeps,
1096
+ seq: number,
1097
+ ): Promise<void> {
1098
+ if (state.pendingToolResults.size === 0) return;
1099
+ const rowId = await ensureToolResultRowReserved(
1100
+ state,
1101
+ deps.ctx.conversationId,
1102
+ buildToolResultMetadata(deps),
1103
+ );
1104
+ // Serialize the content after the reservation resolves so the last of the
1105
+ // concurrent writers reflects the fullest batch.
1106
+ updateMessageContent(
1107
+ rowId,
1108
+ JSON.stringify(buildToolResultBlocks(state.pendingToolResults)),
1109
+ );
1110
+ recordPersistedSeq(deps.ctx.conversationId, seq);
1111
+ const conv = getConversation(deps.ctx.conversationId);
1112
+ if (conv != null) {
1113
+ syncMessageToDisk(deps.ctx.conversationId, rowId, conv.createdAt);
1114
+ }
1115
+ }
1116
+
1117
+ /**
1118
+ * Finalize the grouped tool-result row at a turn/loop boundary: ensure the row
1119
+ * is reserved (a fallback for the case where every on-arrival write failed),
1120
+ * rewrite it to the full batch, sync it to disk, index it for memory recall,
1121
+ * and clear the batch state. Shared by `message_complete` and the orchestrator
1122
+ * loop-exit flush so an aborted or yielded turn finalizes the same reserved row
1123
+ * instead of writing a duplicate.
1124
+ */
1125
+ export async function finalizePendingToolResultRow(
1126
+ state: EventHandlerState,
1127
+ conversationId: string,
1128
+ metadata: Record<string, unknown>,
1129
+ rlog: pino.Logger,
1130
+ ): Promise<void> {
1131
+ if (state.pendingToolResults.size === 0) return;
1132
+ const rowId = await ensureToolResultRowReserved(
1133
+ state,
1134
+ conversationId,
1135
+ metadata,
1136
+ );
1137
+ const contentJson = JSON.stringify(
1138
+ buildToolResultBlocks(state.pendingToolResults),
1139
+ );
1140
+ updateMessageContent(rowId, contentJson);
1141
+ // Sync the row to the JSONL disk view so it stays in lockstep with the DB.
1142
+ // `getConversation` returns `ConversationRow | null`, so `!= null` gates on a
1143
+ // real row (skipping the sync when the conversation was not found rather than
1144
+ // asking the disk-view to resolve a missing id).
1145
+ const conv = getConversation(conversationId);
1146
+ if (conv != null) {
1147
+ syncMessageToDisk(conversationId, rowId, conv.createdAt);
1148
+ }
1149
+ // `reserveMessage` + `updateMessageContent` are CRUD-only, so index the
1150
+ // finalized tool-result content explicitly here (mirroring the assistant-row
1151
+ // finalize) once it is durable. Non-fatal: a memory hiccup must not escalate
1152
+ // a successful turn into a throw.
1153
+ const row = getMessageById(rowId, conversationId);
1154
+ if (row) {
1155
+ let provenanceTrustClass:
1156
+ | "guardian"
1157
+ | "trusted_contact"
1158
+ | "unknown"
1159
+ | undefined;
1160
+ let automated: boolean | undefined;
1161
+ if (row.metadata) {
1162
+ try {
1163
+ const parsedMeta = messageMetadataSchema.safeParse(
1164
+ JSON.parse(row.metadata),
1165
+ );
1166
+ if (parsedMeta.success) {
1167
+ provenanceTrustClass = parsedMeta.data.provenanceTrustClass;
1168
+ automated = parsedMeta.data.automated;
1169
+ }
1170
+ } catch {
1171
+ // Malformed metadata JSON — index with undefined provenance fields.
1172
+ }
1173
+ }
1174
+ try {
1175
+ await indexMessageNow(
1176
+ {
1177
+ messageId: rowId,
1178
+ conversationId,
1179
+ role: "user",
1180
+ content: contentJson,
1181
+ createdAt: row.createdAt,
1182
+ scopeId: "default",
1183
+ provenanceTrustClass,
1184
+ automated,
1185
+ },
1186
+ getConfig().memory,
1187
+ );
1188
+ } catch (err) {
1189
+ rlog.warn(
1190
+ { err, conversationId, messageId: rowId },
1191
+ "Failed to index tool-result message for memory (non-fatal)",
1192
+ );
1193
+ }
1194
+ }
1195
+ for (const id of state.pendingToolResults.keys()) {
1196
+ state.persistedToolUseIds.add(id);
1197
+ }
1198
+ state.pendingToolResults.clear();
1199
+ state.pendingToolResultRowReservation = undefined;
1200
+ }
1201
+
1202
+ export async function handleToolResult(
775
1203
  state: EventHandlerState,
776
1204
  deps: EventHandlerDeps,
777
1205
  event: Extract<AgentEvent, { type: "tool_result" }>,
778
- ): void {
1206
+ ): Promise<void> {
1207
+ // A synthesized cancellation (the tool never executed) is captured for
1208
+ // persistence and forwarded to the client like any result, but skips every
1209
+ // side effect that assumes the tool ran. A real result already captured or
1210
+ // persisted for the same tool wins, so only fill genuine gaps.
1211
+ if (event.cancelled) {
1212
+ if (
1213
+ state.pendingToolResults.has(event.toolUseId) ||
1214
+ state.persistedToolUseIds.has(event.toolUseId)
1215
+ ) {
1216
+ return;
1217
+ }
1218
+ state.pendingToolResults.set(event.toolUseId, {
1219
+ content: event.content,
1220
+ isError: event.isError,
1221
+ });
1222
+ state.currentToolUseId = undefined;
1223
+ deps.onEvent({
1224
+ type: "tool_result",
1225
+ toolName: "",
1226
+ result: event.content,
1227
+ isError: event.isError,
1228
+ conversationId: deps.ctx.conversationId,
1229
+ messageId: state.lastAssistantMessageId,
1230
+ toolUseId: event.toolUseId,
1231
+ });
1232
+ // Capture the seq synchronously (before the persist await) so it reflects
1233
+ // the just-stamped tool_result event, then persist on arrival. A failure
1234
+ // here is non-fatal: the buffered result is still drained at
1235
+ // `message_complete`.
1236
+ const cancelledSeq = getCurrentSeq();
1237
+ try {
1238
+ await persistPendingToolResultRow(state, deps, cancelledSeq);
1239
+ } catch (err) {
1240
+ log.warn(
1241
+ { err, conversationId: deps.ctx.conversationId },
1242
+ "Failed to persist cancelled tool result on arrival (non-fatal; retried at message_complete)",
1243
+ );
1244
+ }
1245
+ return;
1246
+ }
1247
+
779
1248
  const imageBlocks = event.contentBlocks?.filter(
780
1249
  (b): b is ImageContent => b.type === "image",
781
1250
  );
@@ -826,6 +1295,13 @@ export function handleToolResult(
826
1295
  });
827
1296
  }
828
1297
 
1298
+ // Capture tool activity (web_search / web_fetch) so it can be persisted on
1299
+ // the tool_use block and the activity card survives a history reopen,
1300
+ // matching the live tool_result event's activityMetadata.
1301
+ if (event.activityMetadata) {
1302
+ state.toolActivityMetadata.set(event.toolUseId, event.activityMetadata);
1303
+ }
1304
+
829
1305
  const toolName = state.toolUseIdToName.get(event.toolUseId);
830
1306
  if (toolName === "file_write" || toolName === "bash") {
831
1307
  deps.ctx.markWorkspaceTopLevelDirty();
@@ -860,13 +1336,10 @@ export function handleToolResult(
860
1336
  const statusText = `Processing ${friendlyToolName(
861
1337
  state.lastCompletedToolName ?? "",
862
1338
  )} results`;
863
- deps.ctx.emitActivityState(
864
- "thinking",
865
- "tool_result_received",
866
- "assistant_turn",
867
- deps.reqId,
1339
+ deps.ctx.emitActivityState("thinking", "tool_result_received", {
1340
+ requestId: deps.reqId,
868
1341
  statusText,
869
- );
1342
+ });
870
1343
 
871
1344
  // Once all tools for this turn have completed, annotate the persisted
872
1345
  // assistant message with timing and confirmation metadata.
@@ -910,6 +1383,20 @@ export function handleToolResult(
910
1383
  riskThreshold: event.riskThreshold,
911
1384
  activityMetadata: event.activityMetadata,
912
1385
  });
1386
+
1387
+ // Capture the seq synchronously (before the persist await) so it reflects the
1388
+ // just-stamped tool_result event, then persist the grouped row on arrival. A
1389
+ // failure here is non-fatal: the buffered result is still drained at
1390
+ // `message_complete`.
1391
+ const resultSeq = getCurrentSeq();
1392
+ try {
1393
+ await persistPendingToolResultRow(state, deps, resultSeq);
1394
+ } catch (err) {
1395
+ log.warn(
1396
+ { err, conversationId: deps.ctx.conversationId },
1397
+ "Failed to persist tool result on arrival (non-fatal; retried at message_complete)",
1398
+ );
1399
+ }
913
1400
  }
914
1401
 
915
1402
  /**
@@ -981,6 +1468,16 @@ function annotatePersistedAssistantMessage(
981
1468
  rec._riskDirectoryScopeOptions = risk.riskDirectoryScopeOptions;
982
1469
  modified = true;
983
1470
  }
1471
+ // External provider tools (brave/perplexity/tavily) + web_fetch produce
1472
+ // their activity only when the tool_result lands, after message_complete
1473
+ // has already persisted this block — so it is stamped here. Native
1474
+ // server_tool_use activity is stamped earlier, at persist time, in
1475
+ // `buildPersistedAssistantContent`.
1476
+ const activity = state.toolActivityMetadata.get(id);
1477
+ if (activity) {
1478
+ rec._activityMetadata = activity;
1479
+ modified = true;
1480
+ }
984
1481
  }
985
1482
  }
986
1483
 
@@ -998,6 +1495,7 @@ function annotatePersistedAssistantMessage(
998
1495
  actions: surface.actions,
999
1496
  display: surface.display,
1000
1497
  ...(surface.persistent ? { persistent: true } : {}),
1498
+ ...(surface.toolCallId ? { toolCallId: surface.toolCallId } : {}),
1001
1499
  } as unknown as ContentBlock);
1002
1500
  }
1003
1501
  modified = true;
@@ -1142,9 +1640,44 @@ export async function handleMessageComplete(
1142
1640
  deps: EventHandlerDeps,
1143
1641
  event: Extract<AgentEvent, { type: "message_complete" }>,
1144
1642
  ): Promise<void> {
1643
+ // The model has now received the turn context, so persist any pending
1644
+ // inference-profile-change notification. Guarded by the pending slot so it
1645
+ // fires once per turn; a turn that fails before reaching delivery leaves the
1646
+ // slot unconsumed and re-sends the notice next turn.
1647
+ if (state.pendingNotifiedInferenceProfile != null) {
1648
+ setLastNotifiedInferenceProfile(
1649
+ deps.ctx.conversationId,
1650
+ state.pendingNotifiedInferenceProfile,
1651
+ );
1652
+ state.pendingNotifiedInferenceProfile = null;
1653
+ }
1654
+
1145
1655
  // Reset per-turn tool tracking for the new turn.
1146
1656
  state.currentTurnToolUseIds = [];
1147
1657
 
1658
+ // Cancel any pending debounced partial flush and await an already
1659
+ // in-flight one before the authoritative `updateContent` below.
1660
+ // Without the timer-clear, a timer that fires during this handler
1661
+ // could double-write (idempotent in content but wastes a write) or
1662
+ // race ahead of the indexer/projector and serve a stale snapshot.
1663
+ // Without the await, a partial pipeline call that was dispatched a
1664
+ // moment before this handler can settle AFTER the final write and
1665
+ // overwrite the authoritative row.
1666
+ if (state.pendingPartialFlushTimer !== undefined) {
1667
+ clearTimeout(state.pendingPartialFlushTimer);
1668
+ state.pendingPartialFlushTimer = undefined;
1669
+ }
1670
+ if (state.pendingPartialFlushPromise !== undefined) {
1671
+ try {
1672
+ await state.pendingPartialFlushPromise;
1673
+ } catch {
1674
+ // The partial flush swallows its own pipeline errors via
1675
+ // `rlog.warn`; the `try`/`catch` here is defensive against
1676
+ // future changes that might surface them.
1677
+ }
1678
+ state.pendingPartialFlushPromise = undefined;
1679
+ }
1680
+
1148
1681
  // Flush any remaining directive display buffer
1149
1682
  if (state.pendingDirectiveDisplayBuffer.length > 0) {
1150
1683
  deps.onEvent({
@@ -1158,70 +1691,25 @@ export async function handleMessageComplete(
1158
1691
  state.pendingDirectiveDisplayBuffer = "";
1159
1692
  }
1160
1693
 
1161
- // Persist pending tool results
1162
- if (state.pendingToolResults.size > 0) {
1163
- const toolResultBlocks = Array.from(state.pendingToolResults.entries()).map(
1164
- ([toolUseId, result]) => ({
1165
- type: "tool_result",
1166
- tool_use_id: toolUseId,
1167
- content: redactSecrets(result.content),
1168
- is_error: result.isError,
1169
- ...(result.contentBlocks
1170
- ? {
1171
- contentBlocks: result.contentBlocks.map((block) =>
1172
- block.type === "text"
1173
- ? { ...block, text: redactSecrets(block.text) }
1174
- : block,
1175
- ),
1176
- }
1177
- : {}),
1178
- }),
1179
- );
1180
- const toolResultMetadata = {
1181
- ...provenanceFromTrustContext(deps.ctx.trustContext),
1182
- userMessageChannel: deps.turnChannelContext.userMessageChannel,
1183
- assistantMessageChannel: deps.turnChannelContext.assistantMessageChannel,
1184
- userMessageInterface: deps.turnInterfaceContext.userMessageInterface,
1185
- assistantMessageInterface:
1186
- deps.turnInterfaceContext.assistantMessageInterface,
1187
- };
1188
- // Route the add + disk-view sync through the `persistence` pipeline so
1189
- // plugins can observe or override both operations together. The default
1190
- // plugin's terminal performs the add and, when `syncToDisk` is true,
1191
- // immediately calls `syncMessageToDisk` against the just-persisted row.
1192
- // `getConversation` returns `ConversationRow | null`, so `!= null`
1193
- // gates on a real row (skipping the sync when the conversation was
1194
- // not found rather than asking the disk-view to resolve a missing id).
1195
- const convForToolResult = getConversation(deps.ctx.conversationId);
1196
- await runPipeline<PersistArgs, PersistResult>(
1197
- "persistence",
1198
- getMiddlewaresFor("persistence"),
1199
- defaultPersistenceTerminal,
1200
- {
1201
- op: "add",
1202
- conversationId: deps.ctx.conversationId,
1203
- role: "user",
1204
- content: JSON.stringify(toolResultBlocks),
1205
- metadata: toolResultMetadata,
1206
- syncToDisk: convForToolResult != null,
1207
- createdAtMs: convForToolResult?.createdAt,
1208
- },
1209
- buildHandlerTurnContext(deps),
1210
- DEFAULT_TIMEOUTS.persistence,
1211
- );
1212
- for (const id of state.pendingToolResults.keys()) {
1213
- state.persistedToolUseIds.add(id);
1214
- }
1215
- state.pendingToolResults.clear();
1216
- }
1694
+ // Finalize the grouped tool-result row. Each result was persisted into this
1695
+ // row as it arrived (`persistPendingToolResultRow`); this rewrites it to the
1696
+ // full batch (covering the case where a mid-arrival write failed), indexes it
1697
+ // for memory recall, and clears the batch state.
1698
+ await finalizePendingToolResultRow(
1699
+ state,
1700
+ deps.ctx.conversationId,
1701
+ buildToolResultMetadata(deps),
1702
+ deps.rlog,
1703
+ );
1217
1704
 
1218
- // Clean assistant content and accumulate directives
1219
- const {
1220
- cleanedContent,
1221
- directives: msgDirectives,
1222
- warnings: msgWarnings,
1223
- } = cleanAssistantContent(event.message.content);
1224
- const cleanedBlocks = cleanedContent as ContentBlock[];
1705
+ // Accumulate directives + warnings from the assistant content for
1706
+ // downstream attachment processing. `cleanAssistantContent` is also
1707
+ // called inside {@link buildPersistedAssistantContent} below; running
1708
+ // it here separately is the cheapest way to keep the directive
1709
+ // side-effects local to this handler while letting the shared helper
1710
+ // own the persisted-content shape.
1711
+ const { directives: msgDirectives, warnings: msgWarnings } =
1712
+ cleanAssistantContent(event.message.content);
1225
1713
  state.accumulatedDirectives.push(...msgDirectives);
1226
1714
  state.directiveWarnings.push(...msgWarnings);
1227
1715
  if (msgDirectives.length > 0) {
@@ -1243,31 +1731,15 @@ export async function handleMessageComplete(
1243
1731
  // are applied in handleToolResult after all tools for the turn complete,
1244
1732
  // then the persisted message is updated via updateMessageContent.
1245
1733
 
1246
- // Build content with UI surfaces
1247
- const contentWithSurfaces: ContentBlock[] = [...cleanedBlocks];
1248
- for (const surface of deps.ctx.currentTurnSurfaces) {
1249
- contentWithSurfaces.push({
1250
- type: "ui_surface",
1251
- surfaceId: surface.surfaceId,
1252
- surfaceType: surface.surfaceType,
1253
- title: surface.title,
1254
- data: surface.data,
1255
- actions: surface.actions,
1256
- display: surface.display,
1257
- ...(surface.persistent ? { persistent: true } : {}),
1258
- } as unknown as ContentBlock);
1259
- }
1260
-
1261
- // Redact known-pattern secrets from assistant text blocks before they are
1262
- // written to durable storage. Non-text blocks (images, UI surfaces) pass
1263
- // through unchanged. The live model history retains the original values.
1264
- const contentForPersistence = contentWithSurfaces.map((block) => {
1265
- if (block.type === "text") {
1266
- const tb = block as Extract<ContentBlock, { type: "text" }>;
1267
- return { ...tb, text: redactSecrets(tb.text) };
1268
- }
1269
- return block;
1270
- });
1734
+ // Build the canonical persisted content (cleaned + surfaces +
1735
+ // redacted) via the shared helper. The partial-persist flush uses
1736
+ // the same helper with `surfaces=[]` so a mid-turn snapshot lands in
1737
+ // the same shape as the finalize.
1738
+ const contentForPersistence = buildPersistedAssistantContent(
1739
+ event.message.content as ContentBlock[],
1740
+ deps.ctx.currentTurnSurfaces,
1741
+ state.toolActivityMetadata,
1742
+ );
1271
1743
 
1272
1744
  // The row was reserved at `llm_call_started` (with channel metadata
1273
1745
  // stamped at that point) and `state.lastAssistantMessageId` carries its
@@ -1283,25 +1755,30 @@ export async function handleMessageComplete(
1283
1755
  );
1284
1756
  }
1285
1757
  const contentJson = JSON.stringify(contentForPersistence);
1286
- await runPipeline<PersistArgs, PersistResult>(
1287
- "persistence",
1288
- getMiddlewaresFor("persistence"),
1289
- defaultPersistenceTerminal,
1290
- {
1291
- op: "updateContent",
1292
- messageId: assistantMessageId,
1293
- content: contentJson,
1294
- },
1295
- buildHandlerTurnContext(deps),
1296
- DEFAULT_TIMEOUTS.persistence,
1297
- );
1758
+ updateMessageContent(assistantMessageId, contentJson);
1298
1759
  state.assistantRowAwaitingFinalization = false;
1760
+ // The assistant row now holds the authoritative content (text + thinking +
1761
+ // tool_use blocks from `event.message`), and any drained tool-result rows
1762
+ // are durable. `lastPersistedContentSeq` is the last streamed text/thinking
1763
+ // delta's seq -- the highest stamped content event this row reflects -- so
1764
+ // recording it is honest. A drained tool result was stamped earlier in the
1765
+ // turn, so this seq already covers it; a call that streams no content (a
1766
+ // pure tool call) advances instead via `tool_use_start`. `recordPersistedSeq`
1767
+ // clamps monotonically, so a lower value here never regresses the seq.
1768
+ if (state.lastPersistedContentSeq != null) {
1769
+ recordPersistedSeq(deps.ctx.conversationId, state.lastPersistedContentSeq);
1770
+ }
1771
+ // Reset the partial-persist mirror so subsequent calls in this turn
1772
+ // start with an empty running view.
1773
+ state.currentMessageContent = [];
1774
+ state.lastPersistedContentSeq = undefined;
1299
1775
 
1300
- // ── Indexing + attention projection (restored from the pre-B3 `add` path) ──
1776
+ // ── Indexing + attention projection ──
1301
1777
  // `reserveMessage` + `updateMessageContent` are CRUD-only: they don't run
1302
- // the memory indexer or the attention-cursor projector. The pre-B3 path
1303
- // wrote the row via `addMessage`, which ran both as side-effects of the
1304
- // insert. Calling them here keeps the assistant row's external state
1778
+ // the memory indexer or the attention-cursor projector (unlike `addMessage`,
1779
+ // which runs both as side-effects of the insert). Because the assistant row
1780
+ // is reserved empty and finalized via `updateMessageContent`, both must be
1781
+ // invoked explicitly here to keep the assistant row's external state
1305
1782
  // (Qdrant segments, conversation attention cursor) in lockstep with the
1306
1783
  // finalized content. Both are non-fatal — a memory hiccup must not
1307
1784
  // escalate a successful generation into a turn-level throw. Indexing
@@ -1419,8 +1896,10 @@ export async function handleMessageComplete(
1419
1896
 
1420
1897
  deps.ctx.currentTurnSurfaces = [];
1421
1898
 
1422
- // Emit trace event
1423
- const charCount = cleanedBlocks
1899
+ // Emit trace event. Char count is computed from the cleaned +
1900
+ // redacted text blocks (UI surface blocks filtered out via the
1901
+ // type guard) — same shape as what was just persisted.
1902
+ const charCount = contentForPersistence
1424
1903
  .filter(
1425
1904
  (b): b is Extract<ContentBlock, { type: "text" }> => b.type === "text",
1426
1905
  )
@@ -1630,19 +2109,16 @@ export async function dispatchAgentEvent(
1630
2109
  handleInputJsonDelta(state, deps, event);
1631
2110
  break;
1632
2111
  case "tool_result":
1633
- handleToolResult(state, deps, event);
2112
+ await handleToolResult(state, deps, event);
1634
2113
  break;
1635
2114
  case "server_tool_start": {
1636
2115
  const query =
1637
2116
  typeof event.input.query === "string" ? event.input.query : "";
1638
2117
  const statusText = formatSearchStatusText(event.name, query);
1639
- deps.ctx.emitActivityState(
1640
- "tool_running",
1641
- "tool_use_start",
1642
- "assistant_turn",
1643
- deps.reqId,
2118
+ deps.ctx.emitActivityState("tool_running", "tool_use_start", {
2119
+ requestId: deps.reqId,
1644
2120
  statusText,
1645
- );
2121
+ });
1646
2122
  state.serverToolStartedAt.set(event.toolUseId, Date.now());
1647
2123
  state.serverToolInputs.set(event.toolUseId, event.input);
1648
2124
  deps.onEvent({
@@ -1656,13 +2132,10 @@ export async function dispatchAgentEvent(
1656
2132
  break;
1657
2133
  }
1658
2134
  case "server_tool_complete": {
1659
- deps.ctx.emitActivityState(
1660
- "streaming",
1661
- "tool_result_received",
1662
- "assistant_turn",
1663
- deps.reqId,
1664
- "Thinking",
1665
- );
2135
+ deps.ctx.emitActivityState("streaming", "tool_result_received", {
2136
+ requestId: deps.reqId,
2137
+ statusText: "Thinking",
2138
+ });
1666
2139
 
1667
2140
  // Prefer `resolvedInput` (Anthropic's accumulated server-tool input,
1668
2141
  // populated on content_block_stop) over the input captured at
@@ -1705,9 +2178,65 @@ export async function dispatchAgentEvent(
1705
2178
  // for them would mis-label the provider and ship empty results.
1706
2179
  const isAnthropicNative = deps.ctx.provider.name === "anthropic";
1707
2180
 
1708
- const errorMessage = event.isError
1709
- ? (event.errorMessage ?? event.errorCode ?? "Search failed")
1710
- : undefined;
2181
+ // Classify provider failures through the shared normalizer so the same
2182
+ // friendly copy propagates to every client via WebSearchMetadata, while
2183
+ // the raw provider detail stays in telemetry only (ATL-727).
2184
+ const classification = classifyWebSearchFailure({
2185
+ errorCode: event.errorCode,
2186
+ error: event.errorMessage,
2187
+ isError: event.isError,
2188
+ hasResults: results.length > 0,
2189
+ });
2190
+
2191
+ let errorMessage: string | undefined;
2192
+ let fallbackShown = false;
2193
+ if (event.isError) {
2194
+ // A genuine backend failure OR an unclassifiable, message-less native
2195
+ // failure (e.g. `isError:true` with no `error_code`) both surface the
2196
+ // friendly backend copy: a terse "Search failed" placeholder is the
2197
+ // confusing copy this normalization exists to eliminate (ATL-727).
2198
+ // Recoverable categories that carry a real user message
2199
+ // (query_too_long, max_uses_exceeded) keep their own copy.
2200
+ const useBackendCopy =
2201
+ classification.isBackendFailure || !classification.userMessage;
2202
+ if (useBackendCopy) {
2203
+ // Dedup the user-facing friendly notice per turn (request id) so a
2204
+ // burst of failures surfaces at most one full notice. The raw
2205
+ // provider error is preserved on every failure via telemetry below.
2206
+ const alreadyNotified = state.webSearchBackendFailureNotified.has(
2207
+ deps.reqId,
2208
+ );
2209
+ if (alreadyNotified) {
2210
+ errorMessage = "Search is still having trouble.";
2211
+ } else {
2212
+ state.webSearchBackendFailureNotified.add(deps.reqId);
2213
+ errorMessage = WEB_SEARCH_BACKEND_FAILURE_MESSAGE;
2214
+ fallbackShown = true;
2215
+ }
2216
+
2217
+ // Backend-failure telemetry (provider outages / rate limits) must
2218
+ // fire only for genuine backend classifications so it does not
2219
+ // count recoverable input/quota errors — or a message-less unknown
2220
+ // failure that merely borrows the friendly copy — as provider
2221
+ // outages.
2222
+ if (classification.isBackendFailure) {
2223
+ logWebSearchBackendFailure(deps.rlog, {
2224
+ provider: isAnthropicNative
2225
+ ? "anthropic-native"
2226
+ : deps.ctx.provider.name,
2227
+ requestId: deps.reqId,
2228
+ errorCategory: classification.category,
2229
+ rawDetail: classification.rawDetail,
2230
+ fallbackShown,
2231
+ queryLength: query.length,
2232
+ });
2233
+ }
2234
+ } else {
2235
+ // Recoverable, non-backend categories with their own user-facing
2236
+ // copy (query_too_long, max_uses_exceeded) keep that message.
2237
+ errorMessage = classification.userMessage;
2238
+ }
2239
+ }
1711
2240
 
1712
2241
  const metadata: WebSearchMetadata | undefined = isAnthropicNative
1713
2242
  ? {
@@ -1724,6 +2253,14 @@ export async function dispatchAgentEvent(
1724
2253
  .map((r) => `${r.title}\n${r.url}`)
1725
2254
  .join("\n\n");
1726
2255
 
2256
+ // Capture activity so it persists on the server_tool_use block and the
2257
+ // web-search card survives a history reopen, matching the live event.
2258
+ if (metadata) {
2259
+ state.toolActivityMetadata.set(event.toolUseId, {
2260
+ webSearch: metadata,
2261
+ });
2262
+ }
2263
+
1727
2264
  deps.onEvent({
1728
2265
  type: "tool_result",
1729
2266
  toolName: "web_search",
@@ -1736,6 +2273,44 @@ export async function dispatchAgentEvent(
1736
2273
  });
1737
2274
  break;
1738
2275
  }
2276
+ case "context_compacting":
2277
+ deps.ctx.emitActivityState("thinking", "context_compacting", {
2278
+ requestId: deps.reqId,
2279
+ statusText: "Compacting context",
2280
+ });
2281
+ break;
2282
+ case "compaction_circuit_open":
2283
+ case "compaction_circuit_closed":
2284
+ // Circuit-breaker transitions are already in wire-contract shape
2285
+ // (a subset of ServerMessage), so forward them to the client sink
2286
+ // unchanged. They drive the client's "auto-compaction paused"
2287
+ // banner.
2288
+ deps.onEvent(event);
2289
+ break;
2290
+ case "compaction_completed":
2291
+ // Always commit the loop-stripped `basis` as the durable message base
2292
+ // so re-injection re-applies onto the stripped history even when the
2293
+ // pipeline ran but did not compact. When it did compact, commit the
2294
+ // durable result (DB-record fields, Slack provenance, SSE) — which
2295
+ // overwrites `ctx.messages` with the compacted history — and flip the
2296
+ // per-turn re-injection guards the orchestrator reads. This runs
2297
+ // before the loop's `reinject` hook (the loop awaits this dispatch),
2298
+ // so the guards are set in time. A failed durable commit re-throws
2299
+ // below to abort the turn rather than re-injecting against
2300
+ // half-applied state.
2301
+ deps.ctx.messages = event.basis;
2302
+ if (event.result.compacted) {
2303
+ await deps.applyCompaction(event.result, event.basis);
2304
+ state.reducerCompacted = true;
2305
+ }
2306
+ break;
2307
+ case "history_stripped":
2308
+ // Record the history-stripped DB marker right after the loop strips
2309
+ // injections (before the pipeline). Best-effort: a transient marker
2310
+ // write must not abort the turn, so unlike `compaction_completed` this
2311
+ // is not on the re-throw allowlist below.
2312
+ markHistoryStrippedBestEffort(deps.ctx.conversationId);
2313
+ break;
1739
2314
  case "error":
1740
2315
  handleError(state, deps, event);
1741
2316
  break;
@@ -1788,10 +2363,13 @@ export async function dispatchAgentEvent(
1788
2363
  // - message_complete: persists assistant message to DB, sets state flags
1789
2364
  // - error: sets recovery flags (contextTooLargeDetected, orderingErrorDetected)
1790
2365
  // - usage: records token accounting
2366
+ // - compaction_completed: durable compaction commit; aborting the turn is
2367
+ // safer than re-injecting against a half-applied compaction
1791
2368
  if (
1792
2369
  event.type === "message_complete" ||
1793
2370
  event.type === "error" ||
1794
- event.type === "usage"
2371
+ event.type === "usage" ||
2372
+ event.type === "compaction_completed"
1795
2373
  ) {
1796
2374
  throw err;
1797
2375
  }