@vellumai/assistant 0.8.5 → 0.8.6

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 (544) hide show
  1. package/AGENTS.md +33 -1
  2. package/ARCHITECTURE.md +1 -1
  3. package/bunfig.toml +6 -1
  4. package/docs/credential-execution-service.md +6 -6
  5. package/docs/plugins.md +4 -3
  6. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +12 -13
  7. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +4 -1
  8. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
  9. package/openapi.yaml +1900 -166
  10. package/package.json +1 -1
  11. package/src/__tests__/actor-token-service.test.ts +3 -2
  12. package/src/__tests__/agent-loop-exit-reason.test.ts +102 -9
  13. package/src/__tests__/agent-loop-override-profile.test.ts +2 -1
  14. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
  15. package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
  16. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  17. package/src/__tests__/annotate-risk-options.test.ts +1 -0
  18. package/src/__tests__/approval-cascade.test.ts +1 -0
  19. package/src/__tests__/approval-routes-http.test.ts +9 -13
  20. package/src/__tests__/assert-not-live-db.ts +79 -0
  21. package/src/__tests__/assistant-feature-flags-integration.test.ts +9 -25
  22. package/src/__tests__/audit-log-rotation.test.ts +2 -2
  23. package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
  24. package/src/__tests__/background-workers-disk-pressure.test.ts +5 -8
  25. package/src/__tests__/browser-skill-endstate.test.ts +3 -3
  26. package/src/__tests__/btw-routes.test.ts +3 -2
  27. package/src/__tests__/call-controller.test.ts +3 -2
  28. package/src/__tests__/channel-approval-routes.test.ts +3 -2
  29. package/src/__tests__/channel-guardian.test.ts +3 -2
  30. package/src/__tests__/channel-readiness-slack-remote.test.ts +175 -0
  31. package/src/__tests__/channel-reply-delivery.test.ts +35 -0
  32. package/src/__tests__/channel-retry-sweep.test.ts +320 -3
  33. package/src/__tests__/checker.test.ts +12 -12
  34. package/src/__tests__/compaction-events.test.ts +1 -0
  35. package/src/__tests__/compaction-trail-store.test.ts +264 -0
  36. package/src/__tests__/compactor-call-site-logging.test.ts +1 -0
  37. package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
  38. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +7 -5
  39. package/src/__tests__/computer-use-tools.test.ts +12 -14
  40. package/src/__tests__/config-loader-backfill.test.ts +13 -28
  41. package/src/__tests__/config-loader-corrupt.test.ts +5 -5
  42. package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
  43. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
  44. package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
  45. package/src/__tests__/config-schema.test.ts +10 -10
  46. package/src/__tests__/connection-model-compat.test.ts +83 -0
  47. package/src/__tests__/contacts-tools.test.ts +3 -2
  48. package/src/__tests__/context-token-estimator.test.ts +22 -0
  49. package/src/__tests__/conversation-abort-tool-results.test.ts +5 -0
  50. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
  52. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  53. package/src/__tests__/conversation-agent-loop-overflow.test.ts +34 -0
  54. package/src/__tests__/conversation-agent-loop.test.ts +488 -2
  55. package/src/__tests__/conversation-analysis-routes.test.ts +1 -0
  56. package/src/__tests__/conversation-app-control-instantiation.test.ts +29 -19
  57. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -0
  58. package/src/__tests__/conversation-attention-store.test.ts +101 -0
  59. package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
  61. package/src/__tests__/conversation-error.test.ts +30 -0
  62. package/src/__tests__/conversation-fork-crud.test.ts +69 -8
  63. package/src/__tests__/conversation-fork-route.test.ts +3 -2
  64. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  65. package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
  66. package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
  67. package/src/__tests__/conversation-lifecycle.test.ts +1 -0
  68. package/src/__tests__/conversation-list-source.test.ts +3 -2
  69. package/src/__tests__/conversation-load-history-repair.test.ts +2 -1
  70. package/src/__tests__/conversation-load-history-stripped.test.ts +1 -0
  71. package/src/__tests__/conversation-pairing.test.ts +53 -0
  72. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +26 -7
  73. package/src/__tests__/conversation-process-callsite.test.ts +1 -0
  74. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -0
  75. package/src/__tests__/conversation-queue.test.ts +333 -291
  76. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -18
  77. package/src/__tests__/conversation-routes-guardian-reply.test.ts +33 -8
  78. package/src/__tests__/conversation-routes-slash-commands.test.ts +33 -2
  79. package/src/__tests__/conversation-runtime-assembly.test.ts +78 -0
  80. package/src/__tests__/conversation-skill-tools.test.ts +38 -142
  81. package/src/__tests__/conversation-slash-queue.test.ts +84 -32
  82. package/src/__tests__/conversation-slash-unknown.test.ts +5 -0
  83. package/src/__tests__/conversation-speed-override.test.ts +1 -0
  84. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +46 -0
  85. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
  86. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
  87. package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
  88. package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -3
  89. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -17
  90. package/src/__tests__/conversation-sync-tags.test.ts +128 -12
  91. package/src/__tests__/conversation-title-service.test.ts +1 -0
  92. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +30 -0
  93. package/src/__tests__/conversation-usage.test.ts +1 -0
  94. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  95. package/src/__tests__/conversation-workspace-injection.test.ts +5 -0
  96. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -0
  97. package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
  98. package/src/__tests__/credential-broker-server-use.test.ts +5 -5
  99. package/src/__tests__/credential-execution-client.test.ts +72 -1
  100. package/src/__tests__/credential-execution-feature-gates.test.ts +10 -12
  101. package/src/__tests__/credential-health-service.test.ts +252 -3
  102. package/src/__tests__/credential-security-invariants.test.ts +5 -5
  103. package/src/__tests__/credential-vault-unit.test.ts +19 -19
  104. package/src/__tests__/credential-vault.test.ts +5 -5
  105. package/src/__tests__/cross-provider-web-search.test.ts +56 -2
  106. package/src/__tests__/db-connection-isolation.test.ts +7 -6
  107. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
  108. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
  109. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
  110. package/src/__tests__/db-test-helpers.ts +58 -0
  111. package/src/__tests__/disk-pressure-guard.test.ts +58 -41
  112. package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
  113. package/src/__tests__/disk-pressure-routes.test.ts +0 -33
  114. package/src/__tests__/disk-pressure-tools.test.ts +0 -4
  115. package/src/__tests__/dm-persistence.test.ts +26 -40
  116. package/src/__tests__/document-create-dedupe.test.ts +189 -0
  117. package/src/__tests__/document-find-replace.test.ts +3 -2
  118. package/src/__tests__/document-tool-security.test.ts +81 -2
  119. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
  120. package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
  121. package/src/__tests__/encrypted-store.test.ts +11 -9
  122. package/src/__tests__/feature-flag-test-helpers.ts +53 -0
  123. package/src/__tests__/filing-service.test.ts +1 -0
  124. package/src/__tests__/first-greeting.test.ts +62 -12
  125. package/src/__tests__/gateway-flag-listener.test.ts +0 -1
  126. package/src/__tests__/gemini-provider.test.ts +26 -0
  127. package/src/__tests__/guardian-action-sweep.test.ts +3 -2
  128. package/src/__tests__/guardian-outbound-http.test.ts +3 -2
  129. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
  130. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -0
  131. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  132. package/src/__tests__/heartbeat-service.test.ts +1 -0
  133. package/src/__tests__/helpers/mock-logger.ts +26 -0
  134. package/src/__tests__/host-bash-routes.test.ts +1 -0
  135. package/src/__tests__/host-cu-routes-targeted.test.ts +1 -0
  136. package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
  137. package/src/__tests__/host-shell-tool.test.ts +5 -4
  138. package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
  139. package/src/__tests__/http-conversation-lineage.test.ts +3 -2
  140. package/src/__tests__/http-user-message-parity.test.ts +29 -7
  141. package/src/__tests__/identity-intro-cache.test.ts +133 -22
  142. package/src/__tests__/inbound-slack-persistence.test.ts +44 -72
  143. package/src/__tests__/inference-profile-reaper.test.ts +3 -2
  144. package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
  145. package/src/__tests__/injector-disk-pressure.test.ts +3 -17
  146. package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
  147. package/src/__tests__/list-messages-hidden-metadata.test.ts +80 -0
  148. package/src/__tests__/llm-context-normalization.test.ts +42 -0
  149. package/src/__tests__/llm-resolver.test.ts +331 -0
  150. package/src/__tests__/llm-schema.test.ts +1 -1
  151. package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
  152. package/src/__tests__/mcp-abort-signal.test.ts +14 -0
  153. package/src/__tests__/mcp-client-auth.test.ts +14 -0
  154. package/src/__tests__/messaging-send-tool.test.ts +1 -0
  155. package/src/__tests__/migration-import-from-url.test.ts +3 -3
  156. package/src/__tests__/mock-gateway-ipc.ts +18 -2
  157. package/src/__tests__/model-intents.test.ts +3 -3
  158. package/src/__tests__/native-web-search.test.ts +30 -2
  159. package/src/__tests__/notification-deep-link.test.ts +62 -0
  160. package/src/__tests__/oauth-commands-routes.test.ts +37 -0
  161. package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
  162. package/src/__tests__/oauth-store.test.ts +3 -2
  163. package/src/__tests__/onboarding-template-contract.test.ts +3 -2
  164. package/src/__tests__/openai-provider.test.ts +8 -9
  165. package/src/__tests__/openai-responses-provider.test.ts +70 -10
  166. package/src/__tests__/openrouter-provider-only.test.ts +27 -5
  167. package/src/__tests__/outbound-slack-persistence.test.ts +46 -1
  168. package/src/__tests__/persistence-pipeline.test.ts +139 -1
  169. package/src/__tests__/persistence-secret-redaction.test.ts +83 -12
  170. package/src/__tests__/plugin-bootstrap.test.ts +9 -11
  171. package/src/__tests__/plugin-tool-contribution.test.ts +41 -38
  172. package/src/__tests__/process-message-background-slack.test.ts +21 -16
  173. package/src/__tests__/process-message-display-content.test.ts +19 -22
  174. package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
  175. package/src/__tests__/provider-platform-proxy-integration.test.ts +216 -4
  176. package/src/__tests__/provider-registry-ollama.test.ts +45 -22
  177. package/src/__tests__/recording-handler.test.ts +1 -0
  178. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  179. package/src/__tests__/registry.test.ts +82 -76
  180. package/src/__tests__/relay-server.test.ts +10 -10
  181. package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
  182. package/src/__tests__/schedule-store.test.ts +16 -1
  183. package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
  184. package/src/__tests__/secret-ingress-http.test.ts +5 -1
  185. package/src/__tests__/secure-keys.test.ts +3 -3
  186. package/src/__tests__/send-endpoint-busy.test.ts +81 -42
  187. package/src/__tests__/server-history-render.test.ts +4 -1
  188. package/src/__tests__/skill-feature-flags-integration.test.ts +8 -10
  189. package/src/__tests__/skill-feature-flags.test.ts +14 -16
  190. package/src/__tests__/skill-load-feature-flag.test.ts +5 -5
  191. package/src/__tests__/skill-projection-feature-flag.test.ts +44 -30
  192. package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
  193. package/src/__tests__/skill-tool-factory.test.ts +96 -95
  194. package/src/__tests__/slack-channel-config.test.ts +3 -3
  195. package/src/__tests__/subagent-call-site-routing.test.ts +11 -3
  196. package/src/__tests__/subagent-disposal.test.ts +27 -8
  197. package/src/__tests__/subagent-fork-notifications.test.ts +24 -9
  198. package/src/__tests__/subagent-fork-spawn.test.ts +13 -4
  199. package/src/__tests__/subagent-manager-notify.test.ts +20 -8
  200. package/src/__tests__/subagent-notify-parent.test.ts +5 -4
  201. package/src/__tests__/subagent-spawn-tool-fork.test.ts +58 -0
  202. package/src/__tests__/subagent-tools.test.ts +2 -1
  203. package/src/__tests__/suggestion-routes.test.ts +1 -0
  204. package/src/__tests__/system-prompt.test.ts +38 -0
  205. package/src/__tests__/test-preload-verifier.ts +68 -0
  206. package/src/__tests__/test-preload.ts +32 -39
  207. package/src/__tests__/tool-executor-lifecycle-events.test.ts +20 -7
  208. package/src/__tests__/tool-executor.test.ts +55 -10
  209. package/src/__tests__/tool-preview-lifecycle.test.ts +1 -0
  210. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  211. package/src/__tests__/twilio-routes.test.ts +3 -2
  212. package/src/__tests__/validate-input.test.ts +381 -0
  213. package/src/__tests__/verification-control-plane-policy.test.ts +1 -0
  214. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -1
  215. package/src/__tests__/voice-session-bridge.test.ts +37 -28
  216. package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
  217. package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
  218. package/src/acp/session-manager.ts +5 -6
  219. package/src/agent/loop.ts +80 -0
  220. package/src/api/README.md +124 -2
  221. package/src/api/constants/call-sites.ts +27 -0
  222. package/src/api/events/assistant-outbound-attachment.ts +51 -0
  223. package/src/api/events/assistant-text-delta.ts +32 -0
  224. package/src/api/events/assistant-turn-start.ts +33 -0
  225. package/src/api/events/document-comment-created.ts +48 -0
  226. package/src/api/events/document-comment-deleted.ts +24 -0
  227. package/src/api/events/document-comment-reopened.ts +25 -0
  228. package/src/api/events/document-comment-resolved.ts +27 -0
  229. package/src/api/events/generation-cancelled.ts +24 -0
  230. package/src/api/events/generation-handoff.ts +41 -0
  231. package/src/api/events/message-complete.ts +42 -0
  232. package/src/api/events/open-url.ts +30 -0
  233. package/src/{events → api/events}/relationship-state-updated.ts +3 -3
  234. package/src/api/events/tool-use-start.ts +32 -0
  235. package/src/api/index.ts +128 -3
  236. package/src/api/responses/llm-context-response.ts +39 -0
  237. package/src/api/responses/llm-request-log-entry.ts +93 -0
  238. package/src/api/responses/memory-recall-log.ts +65 -0
  239. package/src/api/responses/memory-v2-activation-log.ts +78 -0
  240. package/src/background-wake/background-wake-routes.test.ts +687 -52
  241. package/src/background-wake/platform-client.test.ts +308 -0
  242. package/src/background-wake/platform-client.ts +167 -0
  243. package/src/background-wake/publisher.ts +91 -0
  244. package/src/background-wake/runtime-registry.ts +2 -2
  245. package/src/background-wake/wake-intent-hooks.test.ts +282 -0
  246. package/src/calls/guardian-dispatch.ts +1 -0
  247. package/src/calls/voice-session-bridge.ts +4 -4
  248. package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
  249. package/src/cli/commands/__tests__/notifications.test.ts +184 -40
  250. package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
  251. package/src/cli/commands/channels/index.ts +229 -0
  252. package/src/cli/commands/memory-v3-render.ts +147 -0
  253. package/src/cli/commands/memory-v3.ts +255 -4
  254. package/src/cli/commands/notifications.ts +365 -55
  255. package/src/cli/lib/open-browser.ts +7 -2
  256. package/src/cli/program.ts +2 -0
  257. package/src/config/assistant-feature-flags.ts +23 -42
  258. package/src/config/bundled-skills/document-editor/SKILL.md +5 -1
  259. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  260. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  261. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
  262. package/src/config/call-site-defaults.ts +1 -1
  263. package/src/config/feature-flag-cache.ts +86 -0
  264. package/src/config/feature-flag-registry.json +17 -17
  265. package/src/config/llm-context-resolution.ts +10 -1
  266. package/src/config/llm-resolver.ts +121 -15
  267. package/src/config/loader.ts +4 -5
  268. package/src/config/schemas/__tests__/memory-v2.test.ts +15 -0
  269. package/src/config/schemas/heartbeat.ts +1 -1
  270. package/src/config/schemas/llm.ts +90 -1
  271. package/src/config/schemas/memory-v2.ts +26 -0
  272. package/src/config/schemas/services.ts +6 -2
  273. package/src/config/seed-inference-profiles.ts +36 -16
  274. package/src/context/token-estimator.ts +10 -5
  275. package/src/credential-execution/executable-discovery.ts +40 -0
  276. package/src/credential-execution/process-manager.ts +6 -2
  277. package/src/credential-health/credential-health-service.ts +125 -40
  278. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -6
  279. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +13 -15
  280. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -2
  281. package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
  282. package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
  283. package/src/daemon/__tests__/native-web-search-metadata.test.ts +1 -0
  284. package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
  285. package/src/daemon/__tests__/web-search-status-text.test.ts +1 -0
  286. package/src/daemon/conversation-agent-loop-handlers.ts +389 -68
  287. package/src/daemon/conversation-agent-loop.ts +132 -28
  288. package/src/daemon/conversation-error.ts +33 -5
  289. package/src/daemon/conversation-messaging.ts +84 -43
  290. package/src/daemon/conversation-process.ts +74 -37
  291. package/src/daemon/conversation-runtime-assembly.ts +29 -9
  292. package/src/daemon/conversation-skill-tools.ts +14 -30
  293. package/src/daemon/conversation-surfaces.ts +69 -34
  294. package/src/daemon/conversation-tool-setup.ts +33 -48
  295. package/src/daemon/conversation.ts +26 -46
  296. package/src/daemon/daemon-control.ts +1 -1
  297. package/src/daemon/daemon-skill-host.ts +9 -2
  298. package/src/daemon/disk-pressure-guard.ts +27 -29
  299. package/src/daemon/first-greeting.ts +31 -13
  300. package/src/daemon/handlers/shared.ts +6 -1
  301. package/src/daemon/lifecycle.ts +12 -12
  302. package/src/daemon/mcp-reload-service.ts +1 -1
  303. package/src/daemon/meet-manifest-loader.ts +10 -17
  304. package/src/daemon/message-types/conversations.ts +20 -22
  305. package/src/daemon/message-types/document-comments.ts +8 -44
  306. package/src/daemon/message-types/home.ts +2 -2
  307. package/src/daemon/message-types/integrations.ts +2 -7
  308. package/src/daemon/message-types/messages.ts +23 -38
  309. package/src/daemon/message-types/subagents.ts +6 -0
  310. package/src/daemon/process-message.ts +9 -9
  311. package/src/daemon/providers-setup.ts +1 -1
  312. package/src/daemon/server.ts +16 -0
  313. package/src/daemon/switch-inference-profile-tool.ts +13 -3
  314. package/src/daemon/tool-setup-types.ts +0 -6
  315. package/src/daemon/wake-target-adapter.ts +10 -0
  316. package/src/documents/document-store.ts +38 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +1 -0
  318. package/src/heartbeat/__tests__/heartbeat-service.test.ts +29 -0
  319. package/src/heartbeat/heartbeat-service.ts +63 -0
  320. package/src/home/__tests__/feed-writer.test.ts +161 -0
  321. package/src/home/__tests__/post-connect-feed.test.ts +1 -0
  322. package/src/home/__tests__/suggested-prompts.test.ts +55 -59
  323. package/src/home/feed-writer.ts +146 -7
  324. package/src/home/suggested-prompts.ts +27 -145
  325. package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
  326. package/src/ipc/gateway-client.test.ts +4 -1
  327. package/src/ipc/skill-routes/__tests__/memory.test.ts +1 -0
  328. package/src/ipc/skill-routes/__tests__/registries.test.ts +36 -7
  329. package/src/ipc/skill-routes/memory.ts +4 -3
  330. package/src/ipc/skill-routes/registries.ts +28 -29
  331. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +1 -0
  332. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +26 -5
  333. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
  334. package/src/memory/__tests__/memory-retrospective-job.test.ts +1 -0
  335. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
  336. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
  337. package/src/memory/conversation-attention-store.ts +17 -3
  338. package/src/memory/conversation-crud.ts +352 -112
  339. package/src/memory/db-connection.ts +29 -19
  340. package/src/memory/db-init.ts +4 -0
  341. package/src/memory/db-singleton.ts +77 -0
  342. package/src/memory/delivery-channels.ts +82 -0
  343. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
  344. package/src/memory/graph/retriever.test.ts +3 -3
  345. package/src/memory/job-handlers/embedding.test.ts +3 -2
  346. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
  347. package/src/memory/jobs-worker.ts +12 -1
  348. package/src/memory/llm-request-log-source-clickhouse.ts +80 -0
  349. package/src/memory/llm-request-log-source-local.ts +24 -0
  350. package/src/memory/llm-request-log-source.ts +31 -0
  351. package/src/memory/llm-request-log-store.ts +188 -3
  352. package/src/memory/memory-v2-activation-log-store.ts +95 -1
  353. package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
  354. package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
  355. package/src/memory/migrations/index.ts +2 -0
  356. package/src/memory/schema/conversations.ts +9 -1
  357. package/src/memory/schema/inference.ts +0 -1
  358. package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
  359. package/src/memory/v2/__tests__/harness-metrics.test.ts +9 -0
  360. package/src/memory/v2/__tests__/harness-replay-input.test.ts +9 -4
  361. package/src/memory/v2/__tests__/harness-runner.test.ts +26 -0
  362. package/src/memory/v2/__tests__/sweep-job.test.ts +6 -3
  363. package/src/memory/v2/harness/metrics.ts +5 -1
  364. package/src/memory/v2/harness/replay-input.ts +19 -3
  365. package/src/memory/v2/harness/runner.ts +6 -0
  366. package/src/memory/v2/harness/trace.ts +6 -0
  367. package/src/memory/v3/__tests__/consolidation-job.test.ts +2 -4
  368. package/src/memory/v3/__tests__/coretrieval-seed.test.ts +270 -0
  369. package/src/memory/v3/__tests__/edges.test.ts +144 -1
  370. package/src/memory/v3/__tests__/filter.test.ts +48 -0
  371. package/src/memory/v3/__tests__/gate.test.ts +96 -33
  372. package/src/memory/v3/__tests__/index-composition.test.ts +58 -0
  373. package/src/memory/v3/__tests__/loop.test.ts +250 -5
  374. package/src/memory/v3/__tests__/scouts.test.ts +49 -0
  375. package/src/memory/v3/__tests__/shadow-diff.test.ts +225 -0
  376. package/src/memory/v3/__tests__/shadow-middleware.test.ts +88 -2
  377. package/src/memory/v3/__tests__/traversal.test.ts +39 -0
  378. package/src/memory/v3/__tests__/tree-walk.test.ts +77 -0
  379. package/src/memory/v3/__tests__/validate.test.ts +32 -0
  380. package/src/memory/v3/coretrieval-seed.ts +240 -0
  381. package/src/memory/v3/edges.ts +58 -21
  382. package/src/memory/v3/filter.ts +27 -22
  383. package/src/memory/v3/gate.ts +51 -36
  384. package/src/memory/v3/index-composition.ts +18 -5
  385. package/src/memory/v3/loop.ts +65 -17
  386. package/src/memory/v3/scouts.ts +15 -4
  387. package/src/memory/v3/shadow-diff.ts +287 -0
  388. package/src/memory/v3/shadow-middleware.ts +44 -2
  389. package/src/memory/v3/traversal.ts +6 -1
  390. package/src/memory/v3/tree-walk.ts +6 -1
  391. package/src/memory/v3/validate.ts +56 -33
  392. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  393. package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
  394. package/src/notifications/adapters/slack.ts +45 -11
  395. package/src/notifications/broadcaster.ts +114 -63
  396. package/src/notifications/conversation-pairing.ts +23 -3
  397. package/src/notifications/decisions-store.ts +32 -1
  398. package/src/notifications/deliveries-store.ts +45 -0
  399. package/src/notifications/edit-notification.ts +201 -0
  400. package/src/notifications/emit-signal.ts +11 -1
  401. package/src/notifications/signal.ts +10 -0
  402. package/src/notifications/types.ts +37 -0
  403. package/src/oauth/byo-connection.test.ts +67 -3
  404. package/src/oauth/byo-connection.ts +32 -5
  405. package/src/oauth/connect-orchestrator.ts +9 -0
  406. package/src/oauth/connection-resolver.test.ts +76 -0
  407. package/src/oauth/connection-resolver.ts +49 -10
  408. package/src/oauth/manual-token-connection.ts +51 -3
  409. package/src/oauth/seed-providers.ts +3 -0
  410. package/src/permissions/approval-policy.test.ts +19 -5
  411. package/src/permissions/approval-policy.ts +14 -3
  412. package/src/permissions/checker.ts +21 -8
  413. package/src/platform/client.test.ts +24 -1
  414. package/src/platform/client.ts +8 -0
  415. package/src/platform/feature-gate.ts +15 -0
  416. package/src/plugins/defaults/injectors.ts +2 -8
  417. package/src/plugins/defaults/persistence.ts +25 -6
  418. package/src/plugins/types.ts +57 -13
  419. package/src/proactive-artifact/job.test.ts +1 -0
  420. package/src/prompts/__tests__/system-prompt.test.ts +4 -4
  421. package/src/prompts/system-prompt.ts +38 -40
  422. package/src/prompts/template-detection.ts +10 -4
  423. package/src/prompts/templates/BOOTSTRAP.md +7 -11
  424. package/src/prompts/templates/IDENTITY.md +0 -2
  425. package/src/providers/__tests__/connection-model-compat.test.ts +3 -4
  426. package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
  427. package/src/providers/call-site-routing.ts +33 -9
  428. package/src/providers/connection-model-compat.ts +23 -0
  429. package/src/providers/connection-resolution.ts +39 -20
  430. package/src/providers/fireworks/client.ts +1 -0
  431. package/src/providers/gemini/client.ts +24 -3
  432. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
  433. package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
  434. package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
  435. package/src/providers/inference/auth.ts +0 -8
  436. package/src/providers/inference/connections.ts +3 -66
  437. package/src/providers/inference/resolve-auth.ts +2 -3
  438. package/src/providers/model-catalog.ts +35 -1
  439. package/src/providers/model-intents.ts +3 -3
  440. package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
  441. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +157 -5
  442. package/src/providers/openai/chat-completions-provider.ts +110 -12
  443. package/src/providers/openai/codex-models.ts +2 -0
  444. package/src/providers/openai/responses-provider.ts +53 -53
  445. package/src/providers/openrouter/client.ts +13 -8
  446. package/src/providers/provider-send-message.ts +18 -9
  447. package/src/providers/registry.ts +48 -8
  448. package/src/providers/retry.ts +16 -4
  449. package/src/providers/search-provider-catalog.ts +17 -9
  450. package/src/providers/types.ts +9 -0
  451. package/src/runtime/__tests__/agent-wake.test.ts +1 -0
  452. package/src/runtime/__tests__/background-job-runner.test.ts +1 -0
  453. package/src/runtime/access-request-helper.ts +1 -0
  454. package/src/runtime/auth/route-policy.ts +10 -0
  455. package/src/runtime/channel-readiness-service.ts +68 -0
  456. package/src/runtime/channel-reply-delivery.ts +23 -0
  457. package/src/runtime/channel-retry-sweep.ts +47 -14
  458. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  459. package/src/runtime/migrations/vbundle-builder.ts +3 -2
  460. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
  461. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +406 -0
  462. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  463. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  464. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
  465. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -50
  466. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +51 -3
  467. package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +35 -0
  468. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
  469. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +294 -0
  470. package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
  471. package/src/runtime/routes/acp-routes-list.test.ts +3 -0
  472. package/src/runtime/routes/app-management-routes.ts +111 -4
  473. package/src/runtime/routes/background-wake-routes.ts +188 -20
  474. package/src/runtime/routes/btw-routes.ts +4 -4
  475. package/src/runtime/routes/conversation-analysis-routes.ts +6 -0
  476. package/src/runtime/routes/conversation-compaction-routes.ts +263 -0
  477. package/src/runtime/routes/conversation-list-routes.ts +147 -0
  478. package/src/runtime/routes/conversation-management-routes.ts +39 -14
  479. package/src/runtime/routes/conversation-query-routes.ts +60 -10
  480. package/src/runtime/routes/conversation-routes.ts +186 -140
  481. package/src/runtime/routes/conversations-import-routes.ts +19 -6
  482. package/src/runtime/routes/documents-routes.ts +10 -1
  483. package/src/runtime/routes/group-routes.ts +11 -0
  484. package/src/runtime/routes/home-feed-routes.ts +129 -0
  485. package/src/runtime/routes/identity-intro-cache.ts +61 -16
  486. package/src/runtime/routes/identity-routes.ts +30 -9
  487. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +530 -6
  488. package/src/runtime/routes/inbound-stages/background-dispatch.ts +57 -8
  489. package/src/runtime/routes/index.ts +2 -0
  490. package/src/runtime/routes/inference-provider-connection-routes.ts +5 -26
  491. package/src/runtime/routes/integrations/vercel.ts +15 -0
  492. package/src/runtime/routes/llm-context-normalization.ts +7 -2
  493. package/src/runtime/routes/memory-v3-routes.ts +160 -2
  494. package/src/runtime/routes/migration-routes.ts +20 -13
  495. package/src/runtime/routes/notification-routes.ts +63 -1
  496. package/src/runtime/routes/oauth-commands-routes.ts +6 -1
  497. package/src/runtime/routes/surface-action-routes.ts +1 -38
  498. package/src/runtime/routes/surface-content-routes.ts +12 -5
  499. package/src/runtime/routes/surface-conversation-resolver.ts +65 -0
  500. package/src/runtime/routes/wipe-conversation-routes.ts +3 -0
  501. package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -0
  502. package/src/runtime/slack-dm-text-delivery.ts +177 -0
  503. package/src/runtime/sync/resource-sync-events.ts +1 -1
  504. package/src/runtime/tool-grant-request-helper.ts +1 -0
  505. package/src/schedule/schedule-store.ts +8 -1
  506. package/src/schedule/scheduler.ts +111 -15
  507. package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
  508. package/src/security/encrypted-store.ts +7 -16
  509. package/src/security/store-path-override.ts +61 -0
  510. package/src/signals/user-message.ts +5 -8
  511. package/src/skills/validate-input.ts +177 -0
  512. package/src/subagent/manager.ts +13 -13
  513. package/src/subagent/types.ts +6 -0
  514. package/src/tasks/tool-sanitizer.ts +2 -2
  515. package/src/tools/apps/definitions.ts +35 -21
  516. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
  517. package/src/tools/computer-use/definitions.ts +268 -266
  518. package/src/tools/document/document-tool.ts +131 -8
  519. package/src/tools/execution-target.ts +2 -5
  520. package/src/tools/executor.ts +18 -55
  521. package/src/tools/host-filesystem/edit.test.ts +1 -0
  522. package/src/tools/host-filesystem/read.test.ts +1 -0
  523. package/src/tools/host-filesystem/transfer.test.ts +31 -6
  524. package/src/tools/host-filesystem/write.test.ts +1 -0
  525. package/src/tools/mcp/mcp-tool-factory.ts +0 -2
  526. package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
  527. package/src/tools/network/__tests__/web-search.test.ts +211 -3
  528. package/src/tools/network/managed-search-proxy.ts +183 -0
  529. package/src/tools/network/web-search.ts +199 -44
  530. package/src/tools/policy-context.ts +3 -1
  531. package/src/tools/registry.ts +146 -103
  532. package/src/tools/schedule/create.ts +1 -1
  533. package/src/tools/skills/skill-tool-factory.ts +17 -36
  534. package/src/tools/subagent/spawn.ts +3 -0
  535. package/src/tools/tool-approval-handler.ts +10 -4
  536. package/src/tools/tool-name-aliases.ts +72 -14
  537. package/src/tools/types.ts +17 -15
  538. package/src/tools/ui-surface/definitions.ts +98 -86
  539. package/src/types/onboarding-context.ts +6 -0
  540. package/src/usage/attribution.ts +32 -1
  541. package/src/util/browser.ts +7 -2
  542. package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
  543. package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
  544. package/src/workspace/migrations/registry.ts +4 -0
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import type pino from "pino";
10
+ import { v4 as uuid } from "uuid";
10
11
 
11
12
  import type { AgentEvent } from "../agent/loop.js";
12
13
  import type {
@@ -16,12 +17,16 @@ import type {
16
17
  import { getConfig } from "../config/loader.js";
17
18
  import { recordEstimate } from "../context/estimator-calibration.js";
18
19
  import { getCalibrationProviderKey } from "../context/token-estimator.js";
20
+ import { projectAssistantMessage } from "../memory/conversation-attention-store.js";
19
21
  import {
22
+ deleteMessageById,
20
23
  getConversation,
21
24
  getMessageById,
25
+ messageMetadataSchema,
22
26
  provenanceFromTrustContext,
23
27
  updateMessageContent,
24
28
  } from "../memory/conversation-crud.js";
29
+ import { indexMessageNow } from "../memory/indexer.js";
25
30
  import {
26
31
  backfillMessageIdOnLogs,
27
32
  buildProviderErrorResponsePayload,
@@ -40,15 +45,20 @@ import { defaultPersistenceTerminal } from "../plugins/defaults/persistence.js";
40
45
  import { DEFAULT_TIMEOUTS, runPipeline } from "../plugins/pipeline.js";
41
46
  import { getMiddlewaresFor } from "../plugins/registry.js";
42
47
  import type {
43
- PersistAddResult,
44
48
  PersistArgs,
49
+ PersistReserveResult,
45
50
  PersistResult,
46
51
  TurnContext,
47
52
  } from "../plugins/types.js";
48
53
  import type { ContentBlock, ImageContent } from "../providers/types.js";
49
54
  import { isContextOverflowError } from "../providers/types.js";
55
+ import { publishSyncInvalidation } from "../runtime/sync/sync-publisher.js";
50
56
  import { redactSecrets } from "../security/secret-scanner.js";
51
57
  import { extractDomain } from "../tools/network/domain-normalize.js";
58
+ import {
59
+ buildPricingUsage,
60
+ resolveStructuredPricing,
61
+ } from "../usage/pricing.js";
52
62
  import { ProviderError } from "../util/errors.js";
53
63
  import { faviconUrlForDomain } from "../util/favicon.js";
54
64
  import { getLogger } from "../util/logger.js";
@@ -62,10 +72,17 @@ import {
62
72
  buildConversationErrorMessage,
63
73
  classifyConversationError,
64
74
  isContextTooLarge,
75
+ maxTokensReachedClassification,
65
76
  } from "./conversation-error.js";
66
77
  import { isProviderOrderingError } from "./conversation-slash.js";
67
78
  import { resolveTurnTimezoneContext } from "./date-context.js";
68
- import type { ServerMessage } from "./message-protocol.js";
79
+ import type {
80
+ CardSurfaceData,
81
+ ServerMessage,
82
+ SurfaceAction,
83
+ UiSurfaceShow,
84
+ } from "./message-protocol.js";
85
+ import { conversationMetadataSyncTag } from "./message-types/sync.js";
69
86
  import type {
70
87
  WebSearchMetadata,
71
88
  WebSearchResultItem,
@@ -145,6 +162,21 @@ export interface EventHandlerState {
145
162
  contextTooLargeError: unknown;
146
163
  providerErrorUserMessage: string | null;
147
164
  lastAssistantMessageId: string | undefined;
165
+ /**
166
+ * True when `handleLlmCallStarted` has reserved an empty assistant row
167
+ * that has NOT yet been finalized via `handleMessageComplete`
168
+ * (`op:"updateContent"` + indexing + projection). Used by error/retry
169
+ * paths to detect a stranded reservation that must be cleaned up
170
+ * before the next LLM call reserves a fresh row — without it, every
171
+ * retryable failure (overflow, ordering, image overflow) and every
172
+ * terminal provider rejection would leak an empty assistant bubble
173
+ * into the transcript and mispoint downstream sync/projection.
174
+ *
175
+ * Cleared by `handleMessageComplete` on successful finalize, and by
176
+ * the synthetic-error branch in `conversation-agent-loop.ts` after it
177
+ * absorbs the reserved row into the error message.
178
+ */
179
+ assistantRowAwaitingFinalization: boolean;
148
180
  readonly pendingToolResults: Map<string, PendingToolResult>;
149
181
  readonly persistedToolUseIds: Set<string>;
150
182
  readonly accumulatedDirectives: DirectiveRequest[];
@@ -245,6 +277,7 @@ export function createEventHandlerState(): EventHandlerState {
245
277
  contextTooLargeError: null,
246
278
  providerErrorUserMessage: null,
247
279
  lastAssistantMessageId: undefined,
280
+ assistantRowAwaitingFinalization: false,
248
281
  pendingToolResults: new Map(),
249
282
  persistedToolUseIds: new Set(),
250
283
  accumulatedDirectives: [],
@@ -307,6 +340,9 @@ function emitLlmCallStartedIfNeeded(
307
340
  // tools the client discards it (extractCodePreview only handles app tools),
308
341
  // so we skip forwarding entirely to avoid transport/decode overhead.
309
342
  const APP_TOOL_NAMES = new Set(["app_create"]);
343
+ const MAX_TOKENS_CONTINUE_PROMPT =
344
+ "Continue from where you stopped. Do not repeat content you've already sent.";
345
+ const MAX_TOKENS_SURFACE_COMPLETION_SUMMARY = "Continue";
310
346
 
311
347
  // ── Friendly Tool Names ──────────────────────────────────────────────
312
348
 
@@ -389,6 +425,133 @@ function resolveAssistantReplyTimestampTimezone(
389
425
  }).effectiveTimezone;
390
426
  }
391
427
 
428
+ /**
429
+ * Assemble the metadata envelope written to the assistant message row.
430
+ *
431
+ * Stamped at reserve time (before `provider.sendMessage`) so the row carries
432
+ * channel provenance from the moment it lands in SQLite, mirroring the
433
+ * snapshot that handleMessageComplete used to compute at end-of-turn. All
434
+ * inputs (channel context, trust context, turnStartedAt) are stable across
435
+ * the LLM call, so building this once at reserve is equivalent to building
436
+ * it at complete. Slack reply rows further stamp a `slackMeta` sub-object —
437
+ * the `channelTs` field stays absent here and is back-filled by
438
+ * `deliverReplyViaCallback` after the gateway returns the ts.
439
+ */
440
+ function buildAssistantChannelMetadata(
441
+ state: EventHandlerState,
442
+ deps: EventHandlerDeps,
443
+ ): Record<string, unknown> {
444
+ const metadata: Record<string, unknown> = {
445
+ ...provenanceFromTrustContext(deps.ctx.trustContext),
446
+ userMessageChannel: deps.turnChannelContext.userMessageChannel,
447
+ assistantMessageChannel: deps.turnChannelContext.assistantMessageChannel,
448
+ userMessageInterface: deps.turnInterfaceContext.userMessageInterface,
449
+ assistantMessageInterface:
450
+ deps.turnInterfaceContext.assistantMessageInterface,
451
+ sentAt: state.turnStartedAt,
452
+ };
453
+
454
+ if (deps.turnChannelContext.assistantMessageChannel === "slack") {
455
+ const channelId = deps.ctx.trustContext?.requesterChatId;
456
+ if (channelId) {
457
+ const threadTs = getThreadTs(deps.ctx.conversationId);
458
+ const timestampTimezone = resolveAssistantReplyTimestampTimezone(
459
+ deps.ctx,
460
+ );
461
+ const timestampTimezoneLabel = formatSlackTimezoneLabel(
462
+ timestampTimezone,
463
+ { nowMs: state.turnStartedAt },
464
+ );
465
+ const partialSlackMeta: Partial<SlackMessageMetadata> = {
466
+ source: "slack",
467
+ eventKind: "message",
468
+ channelId,
469
+ ...(threadTs ? { threadTs } : {}),
470
+ timestampTimezone,
471
+ ...(timestampTimezoneLabel ? { timestampTimezoneLabel } : {}),
472
+ };
473
+ // `channelTs` is filled in by the post-send reconciliation step in
474
+ // `deliverReplyViaCallback`; cast through the Partial to satisfy
475
+ // the writer's type at this pre-send boundary.
476
+ metadata.slackMeta = writeSlackMetadata(
477
+ partialSlackMeta as SlackMessageMetadata,
478
+ );
479
+ }
480
+ }
481
+
482
+ return metadata;
483
+ }
484
+
485
+ /**
486
+ * Reserve an empty assistant row for the LLM call about to begin, stash
487
+ * its id on `state.lastAssistantMessageId`, and announce the boundary on
488
+ * the wire via `assistant_turn_start`.
489
+ *
490
+ * Awaited so the row exists and the client has the anchor id BEFORE any
491
+ * streaming delta arrives — every subsequent `deps.onEvent` in this LLM
492
+ * call stamps `messageId: state.lastAssistantMessageId`, and
493
+ * `handleMessageComplete` flushes the final content to the same row via
494
+ * `op: "updateContent"` instead of inserting a fresh one.
495
+ *
496
+ * Multi-LLM-call agent turns (LLM call → tool execution → LLM call) emit
497
+ * one `llm_call_started` per call, so each LLM call reserves its own row.
498
+ * The read-path `findDisplayTurnEndIndex` collapses consecutive assistant
499
+ * rows for the merged history view, matching today's per-call DB layout.
500
+ */
501
+ export async function handleLlmCallStarted(
502
+ state: EventHandlerState,
503
+ deps: EventHandlerDeps,
504
+ ): Promise<void> {
505
+ // Clean up an orphaned reservation from a previous LLM call in this run
506
+ // that errored before `message_complete` could finalize it. This covers
507
+ // the retryable paths (overflow, ordering, image overflow) where the
508
+ // agent loop re-enters with a fresh `run()` and reserves another row;
509
+ // without this delete the failed-attempt row stays in the transcript as
510
+ // an empty assistant bubble. The finalized-row case is filtered out via
511
+ // the `assistantRowAwaitingFinalization` flag — `handleMessageComplete`
512
+ // clears it after the successful `updateContent`, so the previous call's
513
+ // 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
+ if (state.assistantRowAwaitingFinalization && state.lastAssistantMessageId) {
521
+ try {
522
+ deleteMessageById(state.lastAssistantMessageId);
523
+ } catch (err) {
524
+ // Non-fatal: a leaked empty row is preferable to a turn-level throw.
525
+ deps.rlog.warn(
526
+ { err, messageId: state.lastAssistantMessageId },
527
+ "Failed to clean up stranded reserved assistant row before new reservation",
528
+ );
529
+ }
530
+ }
531
+
532
+ 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;
547
+ state.assistantRowAwaitingFinalization = true;
548
+ deps.onEvent({
549
+ type: "assistant_turn_start",
550
+ messageId: reserveResult.message.id,
551
+ conversationId: deps.ctx.conversationId,
552
+ });
553
+ }
554
+
392
555
  // ── Individual Handlers ──────────────────────────────────────────────
393
556
 
394
557
  function handleTextDelta(
@@ -417,6 +580,7 @@ function handleTextDelta(
417
580
  type: "assistant_text_delta",
418
581
  text: drained.emitText,
419
582
  conversationId: deps.ctx.conversationId,
583
+ messageId: state.lastAssistantMessageId,
420
584
  });
421
585
  if (deps.shouldGenerateTitle) state.firstAssistantText += drained.emitText;
422
586
  }
@@ -454,6 +618,7 @@ function handleThinkingDelta(
454
618
  type: "assistant_thinking_delta",
455
619
  thinking: event.thinking,
456
620
  conversationId: deps.ctx.conversationId,
621
+ messageId: state.lastAssistantMessageId,
457
622
  });
458
623
  }
459
624
 
@@ -484,11 +649,12 @@ export function handleToolUse(
484
649
  input: event.input,
485
650
  conversationId: deps.ctx.conversationId,
486
651
  toolUseId: event.id,
652
+ messageId: state.lastAssistantMessageId,
487
653
  });
488
654
  }
489
655
 
490
656
  export function handleToolUsePreviewStart(
491
- _state: EventHandlerState,
657
+ state: EventHandlerState,
492
658
  deps: EventHandlerDeps,
493
659
  event: Extract<AgentEvent, { type: "tool_use_preview_start" }>,
494
660
  ): void {
@@ -497,6 +663,7 @@ export function handleToolUsePreviewStart(
497
663
  toolUseId: event.toolUseId,
498
664
  toolName: event.toolName,
499
665
  conversationId: deps.ctx.conversationId,
666
+ messageId: state.lastAssistantMessageId,
500
667
  });
501
668
  const statusText = `Preparing ${friendlyToolName(event.toolName)}...`;
502
669
  deps.ctx.emitActivityState(
@@ -509,7 +676,7 @@ export function handleToolUsePreviewStart(
509
676
  }
510
677
 
511
678
  function handleToolOutputChunk(
512
- _state: EventHandlerState,
679
+ state: EventHandlerState,
513
680
  deps: EventHandlerDeps,
514
681
  event: Extract<AgentEvent, { type: "tool_output_chunk" }>,
515
682
  ): void {
@@ -567,6 +734,7 @@ function handleToolOutputChunk(
567
734
  chunk: event.chunk,
568
735
  conversationId: deps.ctx.conversationId,
569
736
  toolUseId: event.toolUseId,
737
+ messageId: state.lastAssistantMessageId,
570
738
  subType: structured.subType,
571
739
  subToolName: structured.subToolName,
572
740
  subToolInput: structured.subToolInput,
@@ -579,12 +747,13 @@ function handleToolOutputChunk(
579
747
  chunk: event.chunk,
580
748
  conversationId: deps.ctx.conversationId,
581
749
  toolUseId: event.toolUseId,
750
+ messageId: state.lastAssistantMessageId,
582
751
  });
583
752
  }
584
753
  }
585
754
 
586
755
  export function handleInputJsonDelta(
587
- _state: EventHandlerState,
756
+ state: EventHandlerState,
588
757
  deps: EventHandlerDeps,
589
758
  event: Extract<AgentEvent, { type: "input_json_delta" }>,
590
759
  ): void {
@@ -598,6 +767,7 @@ export function handleInputJsonDelta(
598
767
  content: event.accumulatedJson,
599
768
  conversationId: deps.ctx.conversationId,
600
769
  toolUseId: event.toolUseId,
770
+ messageId: state.lastAssistantMessageId,
601
771
  });
602
772
  }
603
773
 
@@ -724,6 +894,7 @@ export function handleToolResult(
724
894
  diff: event.diff,
725
895
  status: event.status,
726
896
  conversationId: deps.ctx.conversationId,
897
+ messageId: state.lastAssistantMessageId,
727
898
  imageData: imageDataList?.[0],
728
899
  imageDataList,
729
900
  toolUseId: event.toolUseId,
@@ -903,6 +1074,69 @@ function handleError(
903
1074
  }
904
1075
  }
905
1076
 
1077
+ export function handleMaxTokensReached(
1078
+ _state: EventHandlerState,
1079
+ deps: EventHandlerDeps,
1080
+ event: Extract<AgentEvent, { type: "max_tokens_reached" }>,
1081
+ ): void {
1082
+ const classified = maxTokensReachedClassification();
1083
+ const surfaceId = `max_tokens_${uuid()}`;
1084
+ const data: CardSurfaceData = {
1085
+ title: "Response limit reached",
1086
+ subtitle: "The partial response above was saved.",
1087
+ body: classified.userMessage,
1088
+ };
1089
+ const actions: SurfaceAction[] = [
1090
+ {
1091
+ id: "relay_prompt",
1092
+ label: "Continue",
1093
+ style: "primary",
1094
+ data: {
1095
+ prompt: MAX_TOKENS_CONTINUE_PROMPT,
1096
+ _completeSurface: true,
1097
+ _completionSummary: MAX_TOKENS_SURFACE_COMPLETION_SUMMARY,
1098
+ },
1099
+ },
1100
+ ];
1101
+
1102
+ deps.ctx.surfaceState.set(surfaceId, {
1103
+ surfaceType: "card",
1104
+ title: data.title,
1105
+ data,
1106
+ actions,
1107
+ });
1108
+ deps.ctx.currentTurnSurfaces.push({
1109
+ surfaceId,
1110
+ surfaceType: "card",
1111
+ title: data.title,
1112
+ data,
1113
+ actions,
1114
+ display: "inline",
1115
+ persistent: true,
1116
+ });
1117
+
1118
+ deps.rlog.warn(
1119
+ {
1120
+ conversationId: deps.ctx.conversationId,
1121
+ stopReason: event.stopReason,
1122
+ surfaceId,
1123
+ },
1124
+ "Surfacing max-tokens continuation card",
1125
+ );
1126
+
1127
+ deps.onEvent({
1128
+ type: "ui_surface_show",
1129
+ conversationId: deps.ctx.conversationId,
1130
+ surfaceId,
1131
+ surfaceType: "card",
1132
+ title: data.title,
1133
+ data,
1134
+ actions,
1135
+ display: "inline",
1136
+ persistent: true,
1137
+ } as UiSurfaceShow);
1138
+ }
1139
+
906
1140
  export async function handleMessageComplete(
907
1141
  state: EventHandlerState,
908
1142
  deps: EventHandlerDeps,
@@ -917,6 +1151,7 @@ export async function handleMessageComplete(
917
1151
  type: "assistant_text_delta",
918
1152
  text: state.pendingDirectiveDisplayBuffer,
919
1153
  conversationId: deps.ctx.conversationId,
1154
+ messageId: state.lastAssistantMessageId,
920
1155
  });
921
1156
  if (deps.shouldGenerateTitle)
922
1157
  state.firstAssistantText += state.pendingDirectiveDisplayBuffer;
@@ -1023,52 +1258,6 @@ export async function handleMessageComplete(
1023
1258
  } as unknown as ContentBlock);
1024
1259
  }
1025
1260
 
1026
- const assistantChannelMetadata: Record<string, unknown> = {
1027
- ...provenanceFromTrustContext(deps.ctx.trustContext),
1028
- userMessageChannel: deps.turnChannelContext.userMessageChannel,
1029
- assistantMessageChannel: deps.turnChannelContext.assistantMessageChannel,
1030
- userMessageInterface: deps.turnInterfaceContext.userMessageInterface,
1031
- assistantMessageInterface:
1032
- deps.turnInterfaceContext.assistantMessageInterface,
1033
- sentAt: state.turnStartedAt,
1034
- };
1035
-
1036
- // When the assistant is replying through Slack, stamp a `slackMeta`
1037
- // sub-object so the transcript-rendering / thread-aware-context lookup
1038
- // can identify this row's thread without joining tables.
1039
- // Persistence happens BEFORE the Slack adapter sends the message, so
1040
- // Slack's authoritative `ts` (-> `channelTs`) is not yet known and is
1041
- // intentionally omitted here. The post-send reconciliation step in
1042
- // `deliverReplyViaCallback` writes `channelTs` back into this row once
1043
- // the gateway returns the Slack-assigned ts, restoring a fully-formed
1044
- // metadata envelope before any subsequent turn reads the row.
1045
- if (deps.turnChannelContext.assistantMessageChannel === "slack") {
1046
- const channelId = deps.ctx.trustContext?.requesterChatId;
1047
- if (channelId) {
1048
- const threadTs = getThreadTs(deps.ctx.conversationId);
1049
- const timestampTimezone = resolveAssistantReplyTimestampTimezone(
1050
- deps.ctx,
1051
- );
1052
- const timestampTimezoneLabel = formatSlackTimezoneLabel(
1053
- timestampTimezone,
1054
- { nowMs: state.turnStartedAt },
1055
- );
1056
- const partialSlackMeta: Partial<SlackMessageMetadata> = {
1057
- source: "slack",
1058
- eventKind: "message",
1059
- channelId,
1060
- ...(threadTs ? { threadTs } : {}),
1061
- timestampTimezone,
1062
- ...(timestampTimezoneLabel ? { timestampTimezoneLabel } : {}),
1063
- };
1064
- assistantChannelMetadata.slackMeta = writeSlackMetadata(
1065
- // `channelTs` is filled in by the post-send reconciliation step in
1066
- // `deliverReplyViaCallback`; cast through the Partial to satisfy
1067
- // the writer's type at this pre-send boundary.
1068
- partialSlackMeta as SlackMessageMetadata,
1069
- );
1070
- }
1071
- }
1072
1261
  // Redact known-pattern secrets from assistant text blocks before they are
1073
1262
  // written to durable storage. Non-text blocks (images, UI surfaces) pass
1074
1263
  // through unchanged. The live model history retains the original values.
@@ -1080,32 +1269,123 @@ export async function handleMessageComplete(
1080
1269
  return block;
1081
1270
  });
1082
1271
 
1083
- // Route the assistant-message persistence through the `persistence`
1084
- // pipeline. No `syncToDisk` here the orchestrator separately invokes
1085
- // `syncMessageToDisk` on `state.lastAssistantMessageId` after the loop
1086
- // completes (see `conversation-agent-loop.ts::syncLastAssistantMessageToDisk`).
1087
- const assistantPersistResult = (await runPipeline<PersistArgs, PersistResult>(
1272
+ // The row was reserved at `llm_call_started` (with channel metadata
1273
+ // stamped at that point) and `state.lastAssistantMessageId` carries its
1274
+ // id. Flush the final content via `updateContent` instead of inserting a
1275
+ // new row. No `syncToDisk` flag here — the orchestrator separately
1276
+ // invokes `syncMessageToDisk` on `state.lastAssistantMessageId` after
1277
+ // the loop completes (see
1278
+ // `conversation-agent-loop.ts::syncLastAssistantMessageToDisk`).
1279
+ const assistantMessageId = state.lastAssistantMessageId;
1280
+ if (!assistantMessageId) {
1281
+ throw new Error(
1282
+ "handleMessageComplete fired without a prior llm_call_started reserving an assistant row",
1283
+ );
1284
+ }
1285
+ const contentJson = JSON.stringify(contentForPersistence);
1286
+ await runPipeline<PersistArgs, PersistResult>(
1088
1287
  "persistence",
1089
1288
  getMiddlewaresFor("persistence"),
1090
1289
  defaultPersistenceTerminal,
1091
1290
  {
1092
- op: "add",
1093
- conversationId: deps.ctx.conversationId,
1094
- role: "assistant",
1095
- content: JSON.stringify(contentForPersistence),
1096
- metadata: assistantChannelMetadata,
1291
+ op: "updateContent",
1292
+ messageId: assistantMessageId,
1293
+ content: contentJson,
1097
1294
  },
1098
1295
  buildHandlerTurnContext(deps),
1099
1296
  DEFAULT_TIMEOUTS.persistence,
1100
- )) as PersistAddResult;
1101
- const assistantMsg = assistantPersistResult.message;
1102
- state.lastAssistantMessageId = assistantMsg.id;
1297
+ );
1298
+ state.assistantRowAwaitingFinalization = false;
1299
+
1300
+ // ── Indexing + attention projection (restored from the pre-B3 `add` path) ──
1301
+ // `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
1305
+ // (Qdrant segments, conversation attention cursor) in lockstep with the
1306
+ // finalized content. Both are non-fatal — a memory hiccup must not
1307
+ // escalate a successful generation into a turn-level throw. Indexing
1308
+ // intentionally fires AFTER `updateContent` succeeds so we never index
1309
+ // the empty reserved placeholder.
1310
+ const finalizedRow = getMessageById(
1311
+ assistantMessageId,
1312
+ deps.ctx.conversationId,
1313
+ );
1314
+ if (finalizedRow) {
1315
+ let provenanceTrustClass:
1316
+ | "guardian"
1317
+ | "trusted_contact"
1318
+ | "unknown"
1319
+ | undefined;
1320
+ let automated: boolean | undefined;
1321
+ if (finalizedRow.metadata) {
1322
+ try {
1323
+ const parsedMeta = messageMetadataSchema.safeParse(
1324
+ JSON.parse(finalizedRow.metadata),
1325
+ );
1326
+ if (parsedMeta.success) {
1327
+ provenanceTrustClass = parsedMeta.data.provenanceTrustClass;
1328
+ automated = parsedMeta.data.automated;
1329
+ }
1330
+ } catch {
1331
+ // Malformed metadata JSON — fall through with undefined fields,
1332
+ // matching the legacy behavior in `addMessage`.
1333
+ }
1334
+ }
1335
+ try {
1336
+ await indexMessageNow(
1337
+ {
1338
+ messageId: assistantMessageId,
1339
+ conversationId: deps.ctx.conversationId,
1340
+ role: "assistant",
1341
+ content: contentJson,
1342
+ createdAt: finalizedRow.createdAt,
1343
+ scopeId: "default",
1344
+ provenanceTrustClass,
1345
+ automated,
1346
+ },
1347
+ getConfig().memory,
1348
+ );
1349
+ } catch (err) {
1350
+ deps.rlog.warn(
1351
+ {
1352
+ err,
1353
+ conversationId: deps.ctx.conversationId,
1354
+ messageId: assistantMessageId,
1355
+ },
1356
+ "Failed to index assistant message for memory (non-fatal)",
1357
+ );
1358
+ }
1359
+ try {
1360
+ const attentionStateChanged = projectAssistantMessage({
1361
+ conversationId: deps.ctx.conversationId,
1362
+ messageId: assistantMessageId,
1363
+ messageAt: finalizedRow.createdAt,
1364
+ });
1365
+ if (attentionStateChanged) {
1366
+ void publishSyncInvalidation([
1367
+ conversationMetadataSyncTag(deps.ctx.conversationId),
1368
+ ]);
1369
+ }
1370
+ } catch (err) {
1371
+ deps.rlog.warn(
1372
+ {
1373
+ err,
1374
+ conversationId: deps.ctx.conversationId,
1375
+ messageId: assistantMessageId,
1376
+ },
1377
+ "Failed to project assistant message for attention tracking (non-fatal)",
1378
+ );
1379
+ }
1380
+ }
1103
1381
 
1104
1382
  // Backfill message_id on all LLM request logs from this turn.
1105
1383
  // The agent loop is single-threaded per conversation, so all rows with
1106
- // message_id IS NULL belong to the current turn.
1384
+ // message_id IS NULL belong to the current turn. The reserved id was
1385
+ // available before the LLM call ran but the logs are inserted DURING
1386
+ // the call, so the sweep still runs here.
1107
1387
  try {
1108
- backfillMessageIdOnLogs(deps.ctx.conversationId, assistantMsg.id);
1388
+ backfillMessageIdOnLogs(deps.ctx.conversationId, assistantMessageId);
1109
1389
  } catch (err) {
1110
1390
  deps.rlog.warn(
1111
1391
  { err },
@@ -1114,7 +1394,10 @@ export async function handleMessageComplete(
1114
1394
  }
1115
1395
 
1116
1396
  try {
1117
- backfillMemoryRecallLogMessageId(deps.ctx.conversationId, assistantMsg.id);
1397
+ backfillMemoryRecallLogMessageId(
1398
+ deps.ctx.conversationId,
1399
+ assistantMessageId,
1400
+ );
1118
1401
  } catch (err) {
1119
1402
  deps.rlog.warn(
1120
1403
  { err },
@@ -1125,7 +1408,7 @@ export async function handleMessageComplete(
1125
1408
  try {
1126
1409
  backfillMemoryV2ActivationMessageId(
1127
1410
  deps.ctx.conversationId,
1128
- assistantMsg.id,
1411
+ assistantMessageId,
1129
1412
  );
1130
1413
  } catch (err) {
1131
1414
  deps.rlog.warn(
@@ -1236,6 +1519,36 @@ function handleUsage(
1236
1519
  },
1237
1520
  );
1238
1521
  state.llmCallStartedEmitted = false;
1522
+
1523
+ // Emit a lightweight per-call usage progress event so clients can show
1524
+ // live-updating token/cost metrics. This is a UI hint only — no DB writes.
1525
+ const pricingUsage = buildPricingUsage({
1526
+ providerName,
1527
+ model: event.model,
1528
+ inputTokens: event.inputTokens,
1529
+ outputTokens: event.outputTokens,
1530
+ cacheCreationInputTokens: event.cacheCreationInputTokens,
1531
+ cacheReadInputTokens: event.cacheReadInputTokens,
1532
+ rawResponse: event.rawResponse,
1533
+ });
1534
+ const pricing = resolveStructuredPricing(
1535
+ providerName,
1536
+ event.model,
1537
+ pricingUsage,
1538
+ );
1539
+ const estimatedCost =
1540
+ pricing.pricingStatus === "priced" && pricing.estimatedCostUsd != null
1541
+ ? pricing.estimatedCostUsd
1542
+ : 0;
1543
+
1544
+ deps.onEvent({
1545
+ type: "usage_progress",
1546
+ conversationId: deps.ctx.conversationId,
1547
+ inputTokens: event.inputTokens,
1548
+ outputTokens: event.outputTokens,
1549
+ estimatedCost,
1550
+ model: event.model,
1551
+ });
1239
1552
  }
1240
1553
 
1241
1554
  /**
@@ -1295,6 +1608,9 @@ export async function dispatchAgentEvent(
1295
1608
  ): Promise<void> {
1296
1609
  try {
1297
1610
  switch (event.type) {
1611
+ case "llm_call_started":
1612
+ await handleLlmCallStarted(state, deps);
1613
+ break;
1298
1614
  case "text_delta":
1299
1615
  handleTextDelta(state, deps, event);
1300
1616
  break;
@@ -1335,6 +1651,7 @@ export async function dispatchAgentEvent(
1335
1651
  input: event.input,
1336
1652
  conversationId: deps.ctx.conversationId,
1337
1653
  toolUseId: event.toolUseId,
1654
+ messageId: state.lastAssistantMessageId,
1338
1655
  });
1339
1656
  break;
1340
1657
  }
@@ -1414,6 +1731,7 @@ export async function dispatchAgentEvent(
1414
1731
  isError: event.isError,
1415
1732
  conversationId: deps.ctx.conversationId,
1416
1733
  toolUseId: event.toolUseId,
1734
+ messageId: state.lastAssistantMessageId,
1417
1735
  ...(metadata ? { activityMetadata: { webSearch: metadata } } : {}),
1418
1736
  });
1419
1737
  break;
@@ -1421,6 +1739,9 @@ export async function dispatchAgentEvent(
1421
1739
  case "error":
1422
1740
  handleError(state, deps, event);
1423
1741
  break;
1742
+ case "max_tokens_reached":
1743
+ handleMaxTokensReached(state, deps, event);
1744
+ break;
1424
1745
  case "provider_error":
1425
1746
  handleProviderError(deps, event);
1426
1747
  break;