@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
@@ -47,7 +47,7 @@ mock.module("openai", () => ({
47
47
  lastConstructorOptions = opts;
48
48
  }
49
49
  responses = {
50
- stream: (
50
+ create: async (
51
51
  params: Record<string, unknown>,
52
52
  options?: Record<string, unknown>,
53
53
  ) => {
@@ -165,6 +165,26 @@ function completedEvent(
165
165
  };
166
166
  }
167
167
 
168
+ function incompleteEvent(
169
+ reason: "max_output_tokens" | "content_filter",
170
+ inputTokens: number,
171
+ outputTokens: number,
172
+ ): FakeStreamEvent {
173
+ return {
174
+ type: "response.incomplete",
175
+ response: {
176
+ model: "gpt-5.2",
177
+ status: "incomplete",
178
+ incomplete_details: { reason },
179
+ output: [],
180
+ usage: {
181
+ input_tokens: inputTokens,
182
+ output_tokens: outputTokens,
183
+ },
184
+ },
185
+ };
186
+ }
187
+
168
188
  // ---------------------------------------------------------------------------
169
189
  // Tests
170
190
  // ---------------------------------------------------------------------------
@@ -243,6 +263,21 @@ describe("OpenAIResponsesProvider", () => {
243
263
  expect(result.stopReason).toBe("stop");
244
264
  });
245
265
 
266
+ test("maps response.incomplete max output details to stopReason", async () => {
267
+ fakeStreamEvents = [
268
+ textDeltaEvent("Partial"),
269
+ incompleteEvent("max_output_tokens", 12, 8),
270
+ ];
271
+
272
+ const result = await provider.sendMessage([
273
+ { role: "user", content: [{ type: "text", text: "Hi" }] },
274
+ ]);
275
+
276
+ expect(result.content).toEqual([{ type: "text", text: "Partial" }]);
277
+ expect(result.usage).toEqual({ inputTokens: 12, outputTokens: 8 });
278
+ expect(result.stopReason).toBe("max_output_tokens");
279
+ });
280
+
246
281
  // -----------------------------------------------------------------------
247
282
  // Streaming events
248
283
  // -----------------------------------------------------------------------
@@ -1120,7 +1155,7 @@ describe("OpenAIResponsesProvider", () => {
1120
1155
  "System prompt",
1121
1156
  );
1122
1157
 
1123
- // rawRequest should contain the params sent to responses.stream()
1158
+ // rawRequest should contain the params sent to responses.create()
1124
1159
  const rawReq = result.rawRequest as Record<string, unknown>;
1125
1160
  expect(rawReq.model).toBe("gpt-5.2");
1126
1161
  expect(rawReq.instructions).toBe("System prompt");
@@ -1679,7 +1714,7 @@ describe("OpenAIResponsesProvider — Native Web Search", () => {
1679
1714
  expect(lastStreamParams!.max_output_tokens).toBeUndefined();
1680
1715
  });
1681
1716
 
1682
- test("codexSubscription: strips reasoning param even when effort is set", async () => {
1717
+ test("codexSubscription: forwards reasoning param when effort is set", async () => {
1683
1718
  const codexProvider = new OpenAIResponsesProvider("sk-test", "gpt-5.4", {
1684
1719
  codexSubscription: true,
1685
1720
  });
@@ -1692,10 +1727,10 @@ describe("OpenAIResponsesProvider — Native Web Search", () => {
1692
1727
  { config: { effort: "high" } },
1693
1728
  );
1694
1729
 
1695
- expect(lastStreamParams!.reasoning).toBeUndefined();
1730
+ expect(lastStreamParams!.reasoning).toEqual({ effort: "high" });
1696
1731
  });
1697
1732
 
1698
- test("codexSubscription: strips text.verbosity param", async () => {
1733
+ test("codexSubscription: forwards text.verbosity param", async () => {
1699
1734
  const codexProvider = new OpenAIResponsesProvider("sk-test", "gpt-5.4", {
1700
1735
  codexSubscription: true,
1701
1736
  });
@@ -1708,7 +1743,7 @@ describe("OpenAIResponsesProvider — Native Web Search", () => {
1708
1743
  { config: { verbosity: "low" } },
1709
1744
  );
1710
1745
 
1711
- expect(lastStreamParams!.text).toBeUndefined();
1746
+ expect(lastStreamParams!.text).toEqual({ verbosity: "low" });
1712
1747
  });
1713
1748
 
1714
1749
  test("codexSubscription: uses Codex baseURL", async () => {
@@ -1721,7 +1756,7 @@ describe("OpenAIResponsesProvider — Native Web Search", () => {
1721
1756
  );
1722
1757
  });
1723
1758
 
1724
- test("codexSubscription: strips tools param", async () => {
1759
+ test("codexSubscription: forwards tools param", async () => {
1725
1760
  const codexProvider = new OpenAIResponsesProvider("sk-test", "gpt-5.4", {
1726
1761
  codexSubscription: true,
1727
1762
  });
@@ -1737,7 +1772,32 @@ describe("OpenAIResponsesProvider — Native Web Search", () => {
1737
1772
  [sampleTool],
1738
1773
  );
1739
1774
 
1740
- expect(lastStreamParams!.tools).toBeUndefined();
1775
+ expect(lastStreamParams!.tools).toEqual([
1776
+ {
1777
+ type: "function",
1778
+ name: "test_tool",
1779
+ description: "A test tool",
1780
+ parameters: { type: "object", properties: {} },
1781
+ strict: null,
1782
+ },
1783
+ ]);
1784
+ });
1785
+
1786
+ test("codexSubscription: maps web_search to the Codex native web_search tool", async () => {
1787
+ const codexProvider = new OpenAIResponsesProvider("sk-test", "gpt-5.4", {
1788
+ codexSubscription: true,
1789
+ useNativeWebSearch: true,
1790
+ });
1791
+ fakeStreamEvents = [textDeltaEvent("OK"), completedEvent(10, 2)];
1792
+
1793
+ await codexProvider.sendMessage(
1794
+ [{ role: "user", content: [{ type: "text", text: "Search for cats" }] }],
1795
+ [webSearchTool],
1796
+ );
1797
+
1798
+ expect(lastStreamParams!.tools).toEqual([
1799
+ { type: "web_search", external_web_access: false },
1800
+ ]);
1741
1801
  });
1742
1802
 
1743
1803
  test("codexSubscription: still sends model, input, and instructions", async () => {
@@ -1756,7 +1816,7 @@ describe("OpenAIResponsesProvider — Native Web Search", () => {
1756
1816
  expect(lastStreamParams!.model).toBe("gpt-5.4");
1757
1817
  expect(lastStreamParams!.instructions).toBe("You are helpful.");
1758
1818
  expect(lastStreamParams!.max_output_tokens).toBeUndefined();
1759
- expect(lastStreamParams!.reasoning).toBeUndefined();
1760
- expect(lastStreamParams!.text).toBeUndefined();
1819
+ expect(lastStreamParams!.reasoning).toEqual({ effort: "xhigh" });
1820
+ expect(lastStreamParams!.text).toEqual({ verbosity: "high" });
1761
1821
  });
1762
1822
  });
@@ -101,19 +101,19 @@ describe("OpenRouter provider.only plumbing", () => {
101
101
  config: { openrouter: { only: ["xAI"] } },
102
102
  });
103
103
  expect(extras).toEqual({
104
- reasoning: { enabled: false },
105
104
  provider: { only: ["xAI"] },
106
105
  });
107
106
  });
108
107
 
109
- test("omits provider when openrouter.only is absent", () => {
108
+ test("omits reasoning and provider when config is empty", () => {
110
109
  const provider = new ProbeOpenRouterProvider(
111
110
  "fake-key",
112
111
  "x-ai/grok-4.20-beta",
113
112
  );
114
113
  const extras = provider.probeExtras({ config: {} });
115
- expect(extras).toEqual({ reasoning: { enabled: false } });
114
+ expect(extras).toEqual({});
116
115
  expect(extras.provider).toBe(undefined);
116
+ expect(extras.reasoning).toBe(undefined);
117
117
  });
118
118
 
119
119
  test("enables thinking with default detailed summary alongside provider.only", () => {
@@ -133,7 +133,7 @@ describe("OpenRouter provider.only plumbing", () => {
133
133
  });
134
134
  });
135
135
 
136
- test("disabled thinking keeps reasoning disabled and omits summary", () => {
136
+ test("disabled thinking omits reasoning entirely", () => {
137
137
  const provider = new ProbeOpenRouterProvider(
138
138
  "fake-key",
139
139
  "x-ai/grok-4.20-beta",
@@ -145,9 +145,9 @@ describe("OpenRouter provider.only plumbing", () => {
145
145
  },
146
146
  });
147
147
  expect(extras).toEqual({
148
- reasoning: { enabled: false },
149
148
  provider: { only: ["xAI"] },
150
149
  });
150
+ expect(extras.reasoning).toBe(undefined);
151
151
  });
152
152
 
153
153
  test("nests effort under reasoning and maps `max` to xhigh", () => {
@@ -182,6 +182,28 @@ describe("OpenRouter provider.only plumbing", () => {
182
182
  });
183
183
  });
184
184
 
185
+ test("effort without thinking does not emit reasoning", () => {
186
+ const provider = new ProbeOpenRouterProvider(
187
+ "fake-key",
188
+ "x-ai/grok-4.20-beta",
189
+ );
190
+ const extras = provider.probeExtras({
191
+ config: { thinking: { type: "disabled" }, effort: "low" },
192
+ });
193
+ expect(extras.reasoning).toBe(undefined);
194
+ });
195
+
196
+ test("omitting reasoning avoids 400 from reasoning-only models like DeepSeek R1", () => {
197
+ const provider = new ProbeOpenRouterProvider(
198
+ "fake-key",
199
+ "deepseek/deepseek-r1-0528",
200
+ );
201
+ const extras = provider.probeExtras({
202
+ config: { thinking: { type: "disabled" } },
203
+ });
204
+ expect(extras.reasoning).toBe(undefined);
205
+ });
206
+
185
207
  test("ignores an invalid summary override and falls back to detailed", () => {
186
208
  const provider = new ProbeOpenRouterProvider(
187
209
  "fake-key",
@@ -107,10 +107,47 @@ mock.module("../memory/conversation-crud.js", () => ({
107
107
  : {};
108
108
  row.metadata = JSON.stringify({ ...existing, ...updates });
109
109
  },
110
- updateMessageContent: () => {},
110
+ updateMessageContent: (messageId: string, content: string) => {
111
+ // Mirror updateContent into the same capture array so existing
112
+ // `lastAssistantPersisted()` assertions continue to find the row that
113
+ // was reserved at `llm_call_started` time.
114
+ const row = persistedRows.find((candidate) => candidate.id === messageId);
115
+ if (row) row.content = content;
116
+ const call = addMessageCalls.find((c) => c.id === messageId);
117
+ if (call) call.content = content;
118
+ },
111
119
  // The handler treats provenance as a flat spread; returning {} keeps the
112
120
  // metadata snapshot focused on the fields under test.
113
121
  provenanceFromTrustContext: () => ({}),
122
+ reserveMessage: mock(
123
+ async (
124
+ conversationId: string,
125
+ role: string,
126
+ metadata?: Record<string, unknown>,
127
+ ) => {
128
+ // B3: production code creates the assistant row at `llm_call_started`
129
+ // via `reserveMessage`, stamping channel metadata at reserve time.
130
+ // Mirror that into the addMessage capture array so existing
131
+ // `lastAssistantPersisted()` assertions keep working.
132
+ const id = `mock-msg-${addMessageCalls.length + 1}-reserve`;
133
+ addMessageCalls.push({
134
+ id,
135
+ conversationId,
136
+ role,
137
+ content: "",
138
+ metadata,
139
+ });
140
+ persistedRows.push({
141
+ id,
142
+ conversationId,
143
+ role,
144
+ content: "",
145
+ createdAt: Date.now(),
146
+ metadata: metadata ? JSON.stringify(metadata) : null,
147
+ });
148
+ return { id };
149
+ },
150
+ ),
114
151
  }));
115
152
 
116
153
  mock.module("../memory/llm-request-log-store.js", () => ({
@@ -147,6 +184,7 @@ import type {
147
184
  } from "../daemon/conversation-agent-loop-handlers.js";
148
185
  import {
149
186
  createEventHandlerState,
187
+ handleLlmCallStarted,
150
188
  handleMessageComplete,
151
189
  } from "../daemon/conversation-agent-loop-handlers.js";
152
190
  import type { ServerMessage } from "../daemon/message-protocol.js";
@@ -255,6 +293,7 @@ describe("outbound assistant Slack metadata persistence", () => {
255
293
  assistantMessageChannel: "slack",
256
294
  requesterChatId: channelId,
257
295
  });
296
+ await handleLlmCallStarted(state, deps);
258
297
  await handleMessageComplete(state, deps, makeMessageCompleteEvent("hi"));
259
298
 
260
299
  const persisted = lastAssistantPersisted();
@@ -296,6 +335,7 @@ describe("outbound assistant Slack metadata persistence", () => {
296
335
  requesterTimezoneLabel: "ET",
297
336
  clientTimezone: "America/Los_Angeles",
298
337
  });
338
+ await handleLlmCallStarted(state, deps);
299
339
  await handleMessageComplete(
300
340
  state,
301
341
  deps,
@@ -324,6 +364,7 @@ describe("outbound assistant Slack metadata persistence", () => {
324
364
  requesterChatId: channelId,
325
365
  clientTimezone: "America/Los_Angeles",
326
366
  });
367
+ await handleLlmCallStarted(state, deps);
327
368
  await handleMessageComplete(
328
369
  state,
329
370
  deps,
@@ -351,6 +392,7 @@ describe("outbound assistant Slack metadata persistence", () => {
351
392
  requesterChatId: channelId,
352
393
  requesterTimezoneLabel: "ET",
353
394
  });
395
+ await handleLlmCallStarted(state, deps);
354
396
  await handleMessageComplete(
355
397
  state,
356
398
  deps,
@@ -394,6 +436,7 @@ describe("outbound assistant Slack metadata persistence", () => {
394
436
  assistantMessageChannel: "slack",
395
437
  requesterChatId: channelId,
396
438
  });
439
+ await handleLlmCallStarted(state, deps);
397
440
  await handleMessageComplete(state, deps, makeMessageCompleteEvent("hello"));
398
441
 
399
442
  const persisted = lastAssistantPersisted();
@@ -425,6 +468,7 @@ describe("outbound assistant Slack metadata persistence", () => {
425
468
  assistantMessageChannel: "slack",
426
469
  requesterChatId: channelId,
427
470
  });
471
+ await handleLlmCallStarted(state, deps);
428
472
  await handleMessageComplete(
429
473
  state,
430
474
  deps,
@@ -446,6 +490,7 @@ describe("outbound assistant Slack metadata persistence", () => {
446
490
  const deps = makeDeps(conversationId, {
447
491
  assistantMessageChannel: "vellum",
448
492
  });
493
+ await handleLlmCallStarted(state, deps);
449
494
  await handleMessageComplete(
450
495
  state,
451
496
  deps,
@@ -25,6 +25,8 @@ import {
25
25
  createConversation,
26
26
  getMessageById,
27
27
  getMessages,
28
+ reserveMessage,
29
+ updateMessageContent,
28
30
  updateMessageMetadata,
29
31
  } from "../memory/conversation-crud.js";
30
32
  import { getDb } from "../memory/db-connection.js";
@@ -44,6 +46,7 @@ import type {
44
46
  PersistAddResult,
45
47
  PersistArgs,
46
48
  PersistDeleteResult,
49
+ PersistReserveResult,
47
50
  PersistResult,
48
51
  Plugin,
49
52
  TurnContext,
@@ -215,6 +218,80 @@ describe("persistence pipeline", () => {
215
218
  expect(getMessages(conv.id)).toHaveLength(0);
216
219
  });
217
220
 
221
+ test("default plugin: reserve op pre-allocates an empty assistant row", async () => {
222
+ registerPlugin(defaultPersistencePlugin);
223
+
224
+ const conv = createConversation();
225
+
226
+ const result = (await runPipeline<PersistArgs, PersistResult>(
227
+ "persistence",
228
+ getMiddlewaresFor("persistence"),
229
+ defaultPersistenceTerminal,
230
+ {
231
+ op: "reserve",
232
+ conversationId: conv.id,
233
+ role: "assistant",
234
+ metadata: { reservedFor: "anchor" },
235
+ },
236
+ makeCtx({ conversationId: conv.id }),
237
+ DEFAULT_TIMEOUTS.persistence,
238
+ )) as PersistReserveResult;
239
+
240
+ expect(result.op).toBe("reserve");
241
+ expect(result.message.id).toBeTruthy();
242
+ expect(result.message.role).toBe("assistant");
243
+ // Reserve places an empty JSON array so consumers that parse content
244
+ // observe a no-content payload.
245
+ expect(result.message.content).toBe("[]");
246
+
247
+ // Row must exist with the expected shape and live in the conversation.
248
+ const row = getMessageById(result.message.id, conv.id);
249
+ expect(row).not.toBeNull();
250
+ expect(row?.content).toBe("[]");
251
+ expect(JSON.parse(row!.metadata!)).toEqual({ reservedFor: "anchor" });
252
+ expect(getMessages(conv.id).map((m) => m.id)).toContain(result.message.id);
253
+ });
254
+
255
+ test("default plugin: updateContent op overwrites an existing row's content", async () => {
256
+ registerPlugin(defaultPersistencePlugin);
257
+
258
+ const conv = createConversation();
259
+
260
+ // Reserve first, then overwrite — exactly the B3 sequence consumers will
261
+ // follow.
262
+ const reserved = await reserveMessage(conv.id, "assistant");
263
+ expect(getMessageById(reserved.id, conv.id)?.content).toBe("[]");
264
+
265
+ const result = await runPipeline<PersistArgs, PersistResult>(
266
+ "persistence",
267
+ getMiddlewaresFor("persistence"),
268
+ defaultPersistenceTerminal,
269
+ {
270
+ op: "updateContent",
271
+ messageId: reserved.id,
272
+ content: JSON.stringify([{ type: "text", text: "Hello" }]),
273
+ },
274
+ makeCtx({ conversationId: conv.id }),
275
+ DEFAULT_TIMEOUTS.persistence,
276
+ );
277
+ expect(result).toEqual({ op: "updateContent" });
278
+
279
+ // Content updated in place; row id unchanged.
280
+ const row = getMessageById(reserved.id, conv.id);
281
+ expect(row).not.toBeNull();
282
+ expect(JSON.parse(row!.content)).toEqual([{ type: "text", text: "Hello" }]);
283
+
284
+ // Direct-call parity: a fresh reserve + direct updateMessageContent
285
+ // should land the same content shape.
286
+ const baselineReserved = await reserveMessage(conv.id, "assistant");
287
+ updateMessageContent(
288
+ baselineReserved.id,
289
+ JSON.stringify([{ type: "text", text: "Hello" }]),
290
+ );
291
+ const baselineRow = getMessageById(baselineReserved.id, conv.id);
292
+ expect(baselineRow?.content).toBe(row?.content);
293
+ });
294
+
218
295
  test("custom plugin: short-circuits every op onto a mock in-memory store", async () => {
219
296
  type Stored = {
220
297
  id: string;
@@ -249,6 +326,33 @@ describe("persistence pipeline", () => {
249
326
  },
250
327
  };
251
328
  }
329
+ case "reserve": {
330
+ const id = `mock-${nextId++}`;
331
+ mockStore.set(id, {
332
+ id,
333
+ conversationId: args.conversationId,
334
+ role: args.role,
335
+ content: "[]",
336
+ metadata: { ...(args.metadata ?? {}) },
337
+ });
338
+ return {
339
+ op: "reserve",
340
+ message: {
341
+ id,
342
+ conversationId: args.conversationId,
343
+ role: args.role,
344
+ content: "[]",
345
+ createdAt: 123,
346
+ },
347
+ };
348
+ }
349
+ case "updateContent": {
350
+ const existing = mockStore.get(args.messageId);
351
+ if (existing) {
352
+ existing.content = args.content;
353
+ }
354
+ return { op: "updateContent" };
355
+ }
252
356
  case "update": {
253
357
  const existing = mockStore.get(args.messageId);
254
358
  if (existing) {
@@ -298,6 +402,40 @@ describe("persistence pipeline", () => {
298
402
  expect(addResult.message.id).toBe("mock-1");
299
403
  expect(mockStore.size).toBe(1);
300
404
 
405
+ const reserveResult = (await runPipeline<PersistArgs, PersistResult>(
406
+ "persistence",
407
+ getMiddlewaresFor("persistence"),
408
+ defaultPersistenceTerminal,
409
+ {
410
+ op: "reserve",
411
+ conversationId: conv.id,
412
+ role: "assistant",
413
+ metadata: { reservedFor: "mock-anchor" },
414
+ },
415
+ makeCtx({ conversationId: conv.id }),
416
+ DEFAULT_TIMEOUTS.persistence,
417
+ )) as PersistReserveResult;
418
+ expect(reserveResult.op).toBe("reserve");
419
+ expect(reserveResult.message.id).toBe("mock-2");
420
+ expect(reserveResult.message.content).toBe("[]");
421
+ expect(mockStore.get("mock-2")?.content).toBe("[]");
422
+
423
+ await runPipeline<PersistArgs, PersistResult>(
424
+ "persistence",
425
+ getMiddlewaresFor("persistence"),
426
+ defaultPersistenceTerminal,
427
+ {
428
+ op: "updateContent",
429
+ messageId: "mock-2",
430
+ content: '[{"type":"text","text":"finalized"}]',
431
+ },
432
+ makeCtx({ conversationId: conv.id }),
433
+ DEFAULT_TIMEOUTS.persistence,
434
+ );
435
+ expect(mockStore.get("mock-2")?.content).toBe(
436
+ '[{"type":"text","text":"finalized"}]',
437
+ );
438
+
301
439
  await runPipeline<PersistArgs, PersistResult>(
302
440
  "persistence",
303
441
  getMiddlewaresFor("persistence"),
@@ -326,7 +464,7 @@ describe("persistence pipeline", () => {
326
464
  expect(delResult.op).toBe("delete");
327
465
  expect(mockStore.has("mock-1")).toBe(false);
328
466
 
329
- // The real DB must not have been touched by any of the three ops.
467
+ // The real DB must not have been touched by any of the ops.
330
468
  expect(getMessages(conv.id)).toHaveLength(dbRowsBefore);
331
469
  });
332
470
 
@@ -40,6 +40,7 @@ mock.module("../config/loader.js", () => ({
40
40
  }));
41
41
 
42
42
  interface AddMessageCall {
43
+ id: string;
43
44
  conversationId: string;
44
45
  role: string;
45
46
  content: string;
@@ -53,13 +54,41 @@ mock.module("../memory/conversation-crud.js", () => ({
53
54
  content: string,
54
55
  metadata?: Record<string, unknown>,
55
56
  ) => {
56
- addMessageCalls.push({ conversationId, role, content, metadata });
57
- return { id: `mock-msg-${addMessageCalls.length}` };
57
+ const id = `mock-msg-${addMessageCalls.length + 1}`;
58
+ addMessageCalls.push({ id, conversationId, role, content, metadata });
59
+ return { id };
58
60
  },
59
61
  getConversation: () => null,
60
62
  getMessageById: () => null,
61
- updateMessageContent: () => {},
63
+ updateMessageContent: (messageId: string, content: string) => {
64
+ // Mirror updateContent into the same capture array so existing
65
+ // `lastPersisted("assistant")` assertions continue to find the row that
66
+ // was reserved at `llm_call_started` time.
67
+ const call = addMessageCalls.find((c) => c.id === messageId);
68
+ if (call) call.content = content;
69
+ },
62
70
  provenanceFromTrustContext: () => ({}),
71
+ reserveMessage: mock(
72
+ async (
73
+ conversationId: string,
74
+ role: string,
75
+ metadata?: Record<string, unknown>,
76
+ ) => {
77
+ // B3: production code creates the assistant row at `llm_call_started`
78
+ // via `reserveMessage`, stamping channel metadata at reserve time.
79
+ // Mirror that into the addMessage capture array so existing
80
+ // `lastPersisted("assistant")` assertions keep working.
81
+ const id = `mock-msg-${addMessageCalls.length + 1}-reserve`;
82
+ addMessageCalls.push({
83
+ id,
84
+ conversationId,
85
+ role,
86
+ content: "",
87
+ metadata,
88
+ });
89
+ return { id };
90
+ },
91
+ ),
63
92
  }));
64
93
 
65
94
  mock.module("../memory/llm-request-log-store.js", () => ({
@@ -84,6 +113,7 @@ import type {
84
113
  } from "../daemon/conversation-agent-loop-handlers.js";
85
114
  import {
86
115
  createEventHandlerState,
116
+ handleLlmCallStarted,
87
117
  handleMessageComplete,
88
118
  } from "../daemon/conversation-agent-loop-handlers.js";
89
119
 
@@ -164,7 +194,12 @@ describe("persistence-layer secret redaction", () => {
164
194
  isError: false,
165
195
  });
166
196
 
167
- await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent("done"));
197
+ await handleLlmCallStarted(state, makeDeps());
198
+ await handleMessageComplete(
199
+ state,
200
+ makeDeps(),
201
+ makeMessageCompleteEvent("done"),
202
+ );
168
203
 
169
204
  const persisted = lastPersisted("user");
170
205
  const blocks = JSON.parse(persisted.content) as Array<{
@@ -183,7 +218,12 @@ describe("persistence-layer secret redaction", () => {
183
218
  isError: false,
184
219
  });
185
220
 
186
- await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent("done"));
221
+ await handleLlmCallStarted(state, makeDeps());
222
+ await handleMessageComplete(
223
+ state,
224
+ makeDeps(),
225
+ makeMessageCompleteEvent("done"),
226
+ );
187
227
 
188
228
  const persisted = lastPersisted("user");
189
229
  const blocks = JSON.parse(persisted.content) as Array<{
@@ -195,13 +235,19 @@ describe("persistence-layer secret redaction", () => {
195
235
  });
196
236
 
197
237
  test("does not redact non-secret content (UUID, hex hash) in tool result", async () => {
198
- const safe = "id=550e8400-e29b-41d4-a716-446655440000 sha=a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2";
238
+ const safe =
239
+ "id=550e8400-e29b-41d4-a716-446655440000 sha=a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2";
199
240
  state.pendingToolResults.set("tool-use-3", {
200
241
  content: safe,
201
242
  isError: false,
202
243
  });
203
244
 
204
- await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent("done"));
245
+ await handleLlmCallStarted(state, makeDeps());
246
+ await handleMessageComplete(
247
+ state,
248
+ makeDeps(),
249
+ makeMessageCompleteEvent("done"),
250
+ );
205
251
 
206
252
  const persisted = lastPersisted("user");
207
253
  const blocks = JSON.parse(persisted.content) as Array<{
@@ -224,7 +270,12 @@ describe("persistence-layer secret redaction", () => {
224
270
  // Capture the content before handleMessageComplete clears pendingToolResults
225
271
  const contentSnapshot = state.pendingToolResults.get("tool-use-4")!.content;
226
272
 
227
- await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent("done"));
273
+ await handleLlmCallStarted(state, makeDeps());
274
+ await handleMessageComplete(
275
+ state,
276
+ makeDeps(),
277
+ makeMessageCompleteEvent("done"),
278
+ );
228
279
 
229
280
  // The snapshot taken from live state before the call must be unmodified
230
281
  expect(contentSnapshot).toBe(originalContent);
@@ -239,7 +290,12 @@ describe("persistence-layer secret redaction", () => {
239
290
  "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
240
291
  const text = `Your API key is \`${secret}\`. Keep it safe.`;
241
292
 
242
- await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent(text));
293
+ await handleLlmCallStarted(state, makeDeps());
294
+ await handleMessageComplete(
295
+ state,
296
+ makeDeps(),
297
+ makeMessageCompleteEvent(text),
298
+ );
243
299
 
244
300
  const persisted = lastPersisted("assistant");
245
301
  const blocks = JSON.parse(persisted.content) as Array<{
@@ -256,7 +312,12 @@ describe("persistence-layer secret redaction", () => {
256
312
  const secret = "sk-proj-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrst";
257
313
  const text = `I found this key in the config: ${secret}`;
258
314
 
259
- await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent(text));
315
+ await handleLlmCallStarted(state, makeDeps());
316
+ await handleMessageComplete(
317
+ state,
318
+ makeDeps(),
319
+ makeMessageCompleteEvent(text),
320
+ );
260
321
 
261
322
  const persisted = lastPersisted("assistant");
262
323
  const blocks = JSON.parse(persisted.content) as Array<{
@@ -271,7 +332,12 @@ describe("persistence-layer secret redaction", () => {
271
332
  test("does not redact non-secret text in assistant message", async () => {
272
333
  const safe = "Here is the file list: index.ts, util.ts, main.ts";
273
334
 
274
- await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent(safe));
335
+ await handleLlmCallStarted(state, makeDeps());
336
+ await handleMessageComplete(
337
+ state,
338
+ makeDeps(),
339
+ makeMessageCompleteEvent(safe),
340
+ );
275
341
 
276
342
  const persisted = lastPersisted("assistant");
277
343
  const blocks = JSON.parse(persisted.content) as Array<{
@@ -286,7 +352,12 @@ describe("persistence-layer secret redaction", () => {
286
352
  // High-entropy but no known credential prefix — should NOT be redacted
287
353
  const text = "checksum: 8f14e45fceea167a5a36dedd4bea2543";
288
354
 
289
- await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent(text));
355
+ await handleLlmCallStarted(state, makeDeps());
356
+ await handleMessageComplete(
357
+ state,
358
+ makeDeps(),
359
+ makeMessageCompleteEvent(text),
360
+ );
290
361
 
291
362
  const persisted = lastPersisted("assistant");
292
363
  const blocks = JSON.parse(persisted.content) as Array<{