@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
@@ -0,0 +1,326 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
11
+
12
+ import { memoryRouterCostOptimizedProfileMigration } from "../workspace/migrations/090-memory-router-cost-optimized-profile.js";
13
+
14
+ let workspaceDir: string;
15
+
16
+ function freshWorkspace(): void {
17
+ workspaceDir = join(
18
+ tmpdir(),
19
+ `vellum-migration-090-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
20
+ );
21
+ mkdirSync(workspaceDir, { recursive: true });
22
+ }
23
+
24
+ function writeConfig(data: Record<string, unknown>): void {
25
+ writeFileSync(
26
+ join(workspaceDir, "config.json"),
27
+ JSON.stringify(data, null, 2) + "\n",
28
+ );
29
+ }
30
+
31
+ function readConfig(): Record<string, unknown> {
32
+ return JSON.parse(readFileSync(join(workspaceDir, "config.json"), "utf-8"));
33
+ }
34
+
35
+ function configPath(): string {
36
+ return join(workspaceDir, "config.json");
37
+ }
38
+
39
+ beforeEach(() => {
40
+ freshWorkspace();
41
+ delete process.env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH;
42
+ });
43
+
44
+ afterEach(() => {
45
+ delete process.env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH;
46
+ if (existsSync(workspaceDir)) {
47
+ rmSync(workspaceDir, { recursive: true, force: true });
48
+ }
49
+ });
50
+
51
+ describe("090-memory-router-cost-optimized-profile migration", () => {
52
+ test("has correct migration id", () => {
53
+ expect(memoryRouterCostOptimizedProfileMigration.id).toBe(
54
+ "090-memory-router-cost-optimized-profile",
55
+ );
56
+ });
57
+
58
+ test("flips the 087-seeded balanced profile to cost-optimized with 1M context", () => {
59
+ writeConfig({
60
+ llm: {
61
+ default: { provider: "anthropic" },
62
+ callSites: {
63
+ memoryRouter: { profile: "balanced" },
64
+ },
65
+ },
66
+ });
67
+
68
+ memoryRouterCostOptimizedProfileMigration.run(workspaceDir);
69
+
70
+ const config = readConfig() as {
71
+ llm: { callSites: Record<string, Record<string, unknown>> };
72
+ };
73
+ // Must mirror the shipped default in call-site-defaults.ts: dropping the
74
+ // contextWindow would regress migrated users to the profile's ~200k window
75
+ // because the resolver prefers an explicit callSites entry.
76
+ expect(config.llm.callSites.memoryRouter).toEqual({
77
+ profile: "cost-optimized",
78
+ contextWindow: { maxInputTokens: 1_000_000 },
79
+ });
80
+ });
81
+
82
+ test("flips when default provider is absent (treated as Anthropic)", () => {
83
+ writeConfig({
84
+ llm: {
85
+ callSites: {
86
+ memoryRouter: { profile: "balanced" },
87
+ },
88
+ },
89
+ });
90
+
91
+ memoryRouterCostOptimizedProfileMigration.run(workspaceDir);
92
+
93
+ const config = readConfig() as {
94
+ llm: { callSites: Record<string, Record<string, unknown>> };
95
+ };
96
+ expect(config.llm.callSites.memoryRouter).toEqual({
97
+ profile: "cost-optimized",
98
+ contextWindow: { maxInputTokens: 1_000_000 },
99
+ });
100
+ });
101
+
102
+ test("upgrades a bare cost-optimized entry left by a pre-fix run to add 1M context", () => {
103
+ // An earlier (pre-fix) run of this migration wrote a bare
104
+ // { profile: "cost-optimized" } with no contextWindow. Re-running must
105
+ // recover the 1M window for those users.
106
+ writeConfig({
107
+ llm: {
108
+ default: { provider: "anthropic" },
109
+ callSites: {
110
+ memoryRouter: { profile: "cost-optimized" },
111
+ },
112
+ },
113
+ });
114
+
115
+ memoryRouterCostOptimizedProfileMigration.run(workspaceDir);
116
+
117
+ const config = readConfig() as {
118
+ llm: { callSites: Record<string, Record<string, unknown>> };
119
+ };
120
+ expect(config.llm.callSites.memoryRouter).toEqual({
121
+ profile: "cost-optimized",
122
+ contextWindow: { maxInputTokens: 1_000_000 },
123
+ });
124
+ });
125
+
126
+ test("does not create the call site when missing — shipped default handles it", () => {
127
+ // Fresh installs and BYOK-skipped-by-087 workspaces have no memoryRouter
128
+ // entry. The shipped default in call-site-defaults.ts already supplies
129
+ // cost-optimized + 1M context, so this migration should not write anything.
130
+ writeConfig({
131
+ llm: { default: { provider: "anthropic" } },
132
+ });
133
+
134
+ const before = readFileSync(configPath(), "utf-8");
135
+ memoryRouterCostOptimizedProfileMigration.run(workspaceDir);
136
+ const after = readFileSync(configPath(), "utf-8");
137
+
138
+ expect(after).toBe(before);
139
+ });
140
+
141
+ test("does nothing when config.json is absent", () => {
142
+ memoryRouterCostOptimizedProfileMigration.run(workspaceDir);
143
+ expect(existsSync(configPath())).toBe(false);
144
+ });
145
+
146
+ test("skips BYOK / non-Anthropic workspaces to avoid breaking memoryRouter", () => {
147
+ // `cost-optimized` resolves to a managed connection that BYOK installs
148
+ // disable. Rewriting there would silently disable memory injection.
149
+ writeConfig({
150
+ llm: {
151
+ default: { provider: "openai", model: "gpt-5.4" },
152
+ callSites: {
153
+ memoryRouter: { profile: "balanced" },
154
+ },
155
+ },
156
+ });
157
+
158
+ memoryRouterCostOptimizedProfileMigration.run(workspaceDir);
159
+
160
+ const config = readConfig() as {
161
+ llm: { callSites: Record<string, Record<string, unknown>> };
162
+ };
163
+ expect(config.llm.callSites.memoryRouter).toEqual({ profile: "balanced" });
164
+ });
165
+
166
+ test("skips BYOK workspaces with no existing memoryRouter entry", () => {
167
+ writeConfig({
168
+ llm: { default: { provider: "gemini" } },
169
+ });
170
+
171
+ memoryRouterCostOptimizedProfileMigration.run(workspaceDir);
172
+
173
+ const config = readConfig() as {
174
+ llm: { callSites?: Record<string, Record<string, unknown>> };
175
+ };
176
+ expect(config.llm.callSites?.memoryRouter).toBeUndefined();
177
+ });
178
+
179
+ test("preserves user customizations (model pin)", () => {
180
+ writeConfig({
181
+ llm: {
182
+ default: { provider: "anthropic" },
183
+ callSites: {
184
+ memoryRouter: { model: "claude-sonnet-4-6" },
185
+ },
186
+ },
187
+ });
188
+
189
+ memoryRouterCostOptimizedProfileMigration.run(workspaceDir);
190
+
191
+ const config = readConfig() as {
192
+ llm: { callSites: Record<string, Record<string, unknown>> };
193
+ };
194
+ expect(config.llm.callSites.memoryRouter).toEqual({
195
+ model: "claude-sonnet-4-6",
196
+ });
197
+ });
198
+
199
+ test("preserves user customizations (profile + tuning fields)", () => {
200
+ writeConfig({
201
+ llm: {
202
+ default: { provider: "anthropic" },
203
+ callSites: {
204
+ memoryRouter: { profile: "balanced", effort: "low" },
205
+ },
206
+ },
207
+ });
208
+
209
+ memoryRouterCostOptimizedProfileMigration.run(workspaceDir);
210
+
211
+ const config = readConfig() as {
212
+ llm: { callSites: Record<string, Record<string, unknown>> };
213
+ };
214
+ expect(config.llm.callSites.memoryRouter).toEqual({
215
+ profile: "balanced",
216
+ effort: "low",
217
+ });
218
+ });
219
+
220
+ test("preserves the original 077-seeded shape if still present", () => {
221
+ writeConfig({
222
+ llm: {
223
+ default: { provider: "anthropic" },
224
+ callSites: {
225
+ memoryRouter: {
226
+ model: "claude-sonnet-4-6",
227
+ contextWindow: { maxInputTokens: 1_000_000 },
228
+ },
229
+ },
230
+ },
231
+ });
232
+
233
+ memoryRouterCostOptimizedProfileMigration.run(workspaceDir);
234
+
235
+ const config = readConfig() as {
236
+ llm: { callSites: Record<string, Record<string, unknown>> };
237
+ };
238
+ expect(config.llm.callSites.memoryRouter).toEqual({
239
+ model: "claude-sonnet-4-6",
240
+ contextWindow: { maxInputTokens: 1_000_000 },
241
+ });
242
+ });
243
+
244
+ test("preserves quality-optimized override", () => {
245
+ writeConfig({
246
+ llm: {
247
+ default: { provider: "anthropic" },
248
+ callSites: {
249
+ memoryRouter: { profile: "quality-optimized" },
250
+ },
251
+ },
252
+ });
253
+
254
+ memoryRouterCostOptimizedProfileMigration.run(workspaceDir);
255
+
256
+ const config = readConfig() as {
257
+ llm: { callSites: Record<string, Record<string, unknown>> };
258
+ };
259
+ expect(config.llm.callSites.memoryRouter).toEqual({
260
+ profile: "quality-optimized",
261
+ });
262
+ });
263
+
264
+ test("is idempotent — second run does not change the call site", () => {
265
+ writeConfig({
266
+ llm: {
267
+ default: { provider: "anthropic" },
268
+ callSites: {
269
+ memoryRouter: { profile: "balanced" },
270
+ },
271
+ },
272
+ });
273
+
274
+ memoryRouterCostOptimizedProfileMigration.run(workspaceDir);
275
+ const afterFirst = readFileSync(configPath(), "utf-8");
276
+ memoryRouterCostOptimizedProfileMigration.run(workspaceDir);
277
+ const afterSecond = readFileSync(configPath(), "utf-8");
278
+
279
+ expect(afterSecond).toBe(afterFirst);
280
+ });
281
+
282
+ test("skips when VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH is set", () => {
283
+ process.env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH = "/tmp/overlay.json";
284
+ writeConfig({
285
+ llm: {
286
+ default: { provider: "anthropic" },
287
+ callSites: {
288
+ memoryRouter: { profile: "balanced" },
289
+ },
290
+ },
291
+ });
292
+
293
+ memoryRouterCostOptimizedProfileMigration.run(workspaceDir);
294
+
295
+ const config = readConfig() as {
296
+ llm: { callSites: Record<string, Record<string, unknown>> };
297
+ };
298
+ expect(config.llm.callSites.memoryRouter).toEqual({ profile: "balanced" });
299
+ });
300
+
301
+ test("preserves sibling call-site entries", () => {
302
+ writeConfig({
303
+ llm: {
304
+ default: { provider: "anthropic" },
305
+ callSites: {
306
+ mainAgent: { model: "claude-opus-4-7", maxTokens: 32000 },
307
+ memoryRouter: { profile: "balanced" },
308
+ },
309
+ },
310
+ });
311
+
312
+ memoryRouterCostOptimizedProfileMigration.run(workspaceDir);
313
+
314
+ const config = readConfig() as {
315
+ llm: { callSites: Record<string, Record<string, unknown>> };
316
+ };
317
+ expect(config.llm.callSites.mainAgent).toEqual({
318
+ model: "claude-opus-4-7",
319
+ maxTokens: 32000,
320
+ });
321
+ expect(config.llm.callSites.memoryRouter).toEqual({
322
+ profile: "cost-optimized",
323
+ contextWindow: { maxInputTokens: 1_000_000 },
324
+ });
325
+ });
326
+ });
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Tests for workspace migration `091-retighten-migration-onboarding-thread`.
3
+ *
4
+ * The migration rewrites the assistant-migration onboarding bullet in
5
+ * `memory/threads.md` (seeded by 069) only for newly-created workspaces
6
+ * (`ctx.isNewWorkspace === true`) when the exact old bullet is present. It is a
7
+ * no-op on upgrade, when the old bullet is absent, and when the file is
8
+ * missing. It must be idempotent and compose with 069 (069 seeds the old
9
+ * bullets, then 091 retightens bullet 3).
10
+ */
11
+
12
+ import {
13
+ existsSync,
14
+ mkdirSync,
15
+ mkdtempSync,
16
+ readFileSync,
17
+ rmSync,
18
+ writeFileSync,
19
+ } from "node:fs";
20
+ import { tmpdir } from "node:os";
21
+ import { join } from "node:path";
22
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
23
+
24
+ import { seedOnboardingThreadsMigration } from "../workspace/migrations/069-seed-onboarding-threads.js";
25
+ import { retightenMigrationOnboardingThreadMigration } from "../workspace/migrations/091-retighten-migration-onboarding-thread.js";
26
+ import type { MigrationRunContext } from "../workspace/migrations/types.js";
27
+
28
+ const NEW_WORKSPACE_CTX: MigrationRunContext = { isNewWorkspace: true };
29
+ const UPGRADE_CTX: MigrationRunContext = { isNewWorkspace: false };
30
+
31
+ // Literal copy of the 4-bullet block 069 seeds into memory/threads.md. The
32
+ // third bullet is the one 091 retightens.
33
+ const ONBOARDING_THREADS = `- Figure out what kind of personality would best mesh with your user. Figure out who you are and what your voice should be. Your choice should be DISTINCT and have CHARACTER. Once you've figured this out, rewrite SOUL.md and IDENTITY.md in your own voice to define who you are.
34
+ - Work with your user to set a custom avatar for yourself. This task is done once data/avatar/avatar-image.png exists.
35
+ - Ask your user if they use ChatGPT, Claude or another AI tool and offer to help them import memories from there.
36
+ - Suggest connecting to available messaging services like Slack or Telegram
37
+ `;
38
+
39
+ const OLD_BULLET =
40
+ "- Ask your user if they use ChatGPT, Claude or another AI tool and offer to help them import memories from there.";
41
+
42
+ let workspaceDir: string;
43
+
44
+ beforeEach(() => {
45
+ workspaceDir = mkdtempSync(join(tmpdir(), "vellum-migration-091-test-"));
46
+ });
47
+
48
+ afterEach(() => {
49
+ if (existsSync(workspaceDir)) {
50
+ rmSync(workspaceDir, { recursive: true, force: true });
51
+ }
52
+ });
53
+
54
+ function seedThreads(content: string): void {
55
+ mkdirSync(join(workspaceDir, "memory"), { recursive: true });
56
+ writeFileSync(join(workspaceDir, "memory", "threads.md"), content, "utf-8");
57
+ }
58
+
59
+ function readThreads(): string {
60
+ return readFileSync(join(workspaceDir, "memory", "threads.md"), "utf-8");
61
+ }
62
+
63
+ describe("091-retighten-migration-onboarding-thread migration", () => {
64
+ test("has correct id and description", () => {
65
+ expect(retightenMigrationOnboardingThreadMigration.id).toBe(
66
+ "091-retighten-migration-onboarding-thread",
67
+ );
68
+ expect(retightenMigrationOnboardingThreadMigration.description).toContain(
69
+ "threads.md",
70
+ );
71
+ });
72
+
73
+ test("replaces the old bullet on a new workspace, preserving the others", () => {
74
+ seedThreads(ONBOARDING_THREADS);
75
+
76
+ retightenMigrationOnboardingThreadMigration.run(
77
+ workspaceDir,
78
+ NEW_WORKSPACE_CTX,
79
+ );
80
+
81
+ const content = readThreads();
82
+ expect(content).not.toContain(OLD_BULLET);
83
+ expect(content).toContain("first real task");
84
+ expect(content).toContain("ChatGPT, Claude");
85
+ // The other three onboarding bullets are preserved verbatim.
86
+ expect(content).toContain("Figure out what kind of personality");
87
+ expect(content).toContain("data/avatar/avatar-image.png");
88
+ expect(content).toContain("Slack or Telegram");
89
+ });
90
+
91
+ test("no-op on upgrade even when the old bullet is present", () => {
92
+ seedThreads(ONBOARDING_THREADS);
93
+
94
+ retightenMigrationOnboardingThreadMigration.run(workspaceDir, UPGRADE_CTX);
95
+
96
+ expect(readThreads()).toBe(ONBOARDING_THREADS);
97
+ });
98
+
99
+ test("no-op when the old bullet is absent (user-edited content)", () => {
100
+ const edited = "- A bullet the user wrote themselves.\n";
101
+ seedThreads(edited);
102
+
103
+ retightenMigrationOnboardingThreadMigration.run(
104
+ workspaceDir,
105
+ NEW_WORKSPACE_CTX,
106
+ );
107
+
108
+ expect(readThreads()).toBe(edited);
109
+ });
110
+
111
+ test("no-op when memory/threads.md does not exist", () => {
112
+ retightenMigrationOnboardingThreadMigration.run(
113
+ workspaceDir,
114
+ NEW_WORKSPACE_CTX,
115
+ );
116
+ expect(existsSync(join(workspaceDir, "memory", "threads.md"))).toBe(false);
117
+ });
118
+
119
+ test("idempotent — second run produces identical content", () => {
120
+ seedThreads(ONBOARDING_THREADS);
121
+
122
+ retightenMigrationOnboardingThreadMigration.run(
123
+ workspaceDir,
124
+ NEW_WORKSPACE_CTX,
125
+ );
126
+ const afterFirst = readThreads();
127
+
128
+ retightenMigrationOnboardingThreadMigration.run(
129
+ workspaceDir,
130
+ NEW_WORKSPACE_CTX,
131
+ );
132
+ const afterSecond = readThreads();
133
+
134
+ expect(afterSecond).toBe(afterFirst);
135
+ });
136
+
137
+ test("composes 069 -> 091: fresh empty workspace ends with the new bullet", () => {
138
+ // Mirror how the 069 test sets up: create an empty threads.md first.
139
+ seedThreads("");
140
+
141
+ seedOnboardingThreadsMigration.run(workspaceDir, NEW_WORKSPACE_CTX);
142
+ retightenMigrationOnboardingThreadMigration.run(
143
+ workspaceDir,
144
+ NEW_WORKSPACE_CTX,
145
+ );
146
+
147
+ const content = readThreads();
148
+ expect(content).not.toContain(OLD_BULLET);
149
+ expect(content).toContain("first real task");
150
+ expect(content).toContain("ChatGPT, Claude");
151
+ });
152
+
153
+ test("down() is a no-op — rewritten content remains", () => {
154
+ seedThreads(ONBOARDING_THREADS);
155
+
156
+ retightenMigrationOnboardingThreadMigration.run(
157
+ workspaceDir,
158
+ NEW_WORKSPACE_CTX,
159
+ );
160
+ const rewritten = readThreads();
161
+
162
+ retightenMigrationOnboardingThreadMigration.down(workspaceDir);
163
+
164
+ expect(readThreads()).toBe(rewritten);
165
+ });
166
+ });
@@ -460,14 +460,13 @@ export class AcpSessionManager {
460
460
  current.parentConversationId,
461
461
  );
462
462
  if (parentConversation) {
463
- const enqueueResult = parentConversation.enqueueMessage(
464
- notifyMessage,
465
- [],
466
- );
463
+ const enqueueResult = parentConversation.enqueueMessage({
464
+ content: notifyMessage,
465
+ });
467
466
  if (!enqueueResult.queued && !enqueueResult.rejected) {
468
467
  parentConversation
469
- .persistUserMessage(notifyMessage, [])
470
- .then((messageId) =>
468
+ .persistUserMessage({ content: notifyMessage })
469
+ .then(({ id: messageId }) =>
471
470
  parentConversation.runAgentLoop(notifyMessage, messageId),
472
471
  )
473
472
  .catch((err) => {
package/src/agent/loop.ts CHANGED
@@ -111,6 +111,8 @@ export type AgentLoopExitReason =
111
111
  * leaving `agent_loop_exit_reason` NULL.
112
112
  */
113
113
  | "budget_yield_unrecovered"
114
+ /** Provider stopped because the configured output-token limit was reached. */
115
+ | "max_tokens_reached"
114
116
  /** User cancellation landed after a non-terminal checkpoint yield. */
115
117
  | "aborted_after_checkpoint"
116
118
  /** Signal aborted while the catch handler was synthesizing an error turn. */
@@ -119,9 +121,19 @@ export type AgentLoopExitReason =
119
121
  | "error";
120
122
 
121
123
  export type AgentEvent =
124
+ /**
125
+ * Emitted once per LLM call inside the loop, immediately before the
126
+ * `provider.sendMessage` invocation. Carries the optional `callSite` tag so
127
+ * downstream handlers (the daemon's persistence pipeline) can decide
128
+ * whether to reserve a row for this call. One `llm_call_started` precedes
129
+ * every `message_complete` for the same call; multi-call agent turns emit
130
+ * one pair per call.
131
+ */
132
+ | { type: "llm_call_started"; callSite?: LLMCallSite }
122
133
  | { type: "text_delta"; text: string }
123
134
  | { type: "thinking_delta"; thinking: string }
124
135
  | { type: "message_complete"; message: Message }
136
+ | { type: "max_tokens_reached"; stopReason: string }
125
137
  | {
126
138
  type: "tool_use";
127
139
  id: string;
@@ -256,6 +268,18 @@ const DEFAULT_CONFIG: AgentLoopConfig = {
256
268
 
257
269
  const MAX_CONSECUTIVE_ERROR_NUDGES = 3;
258
270
  const MAX_EMPTY_RESPONSE_RETRIES = 1;
271
+ const MAX_TOKENS_STOP_REASONS = new Set([
272
+ "length",
273
+ "max_output_tokens",
274
+ "max_tokens",
275
+ ]);
276
+
277
+ export function isMaxTokensStopReason(
278
+ stopReason: string | null | undefined,
279
+ ): boolean {
280
+ if (!stopReason) return false;
281
+ return MAX_TOKENS_STOP_REASONS.has(stopReason.trim().toLowerCase());
282
+ }
259
283
 
260
284
  /**
261
285
  * Build a minimal {@link TurnContext} for pipeline invocations inside the
@@ -603,6 +627,16 @@ export class AgentLoop {
603
627
  if (callSite) {
604
628
  providerConfig.callSite = callSite;
605
629
  providerConfig.usageTracking = "manual";
630
+ // Per-conversation seed for deterministic `mix`-profile expansion.
631
+ // Sourced from the orchestrator-supplied turn context's
632
+ // conversationId so every LLM call in a conversation resolves the
633
+ // same mix arm (stable across turns and retries, and across daemon
634
+ // restarts since the seed is the durable conversation id). Absent
635
+ // for standalone `AgentLoop` instances (unit tests / no turnContext)
636
+ // — those fall back to per-call random mix selection.
637
+ if (turnContext?.conversationId) {
638
+ providerConfig.selectionSeed = turnContext.conversationId;
639
+ }
606
640
  }
607
641
 
608
642
  // Per-call inference-profile override. The resolver layers
@@ -751,6 +785,18 @@ export class AgentLoop {
751
785
  toolUseTurns,
752
786
  );
753
787
 
788
+ // Announce the LLM-call boundary so downstream handlers (the
789
+ // daemon's persistence pipeline) can reserve an empty assistant row
790
+ // and stamp the resulting `messageId` onto every streaming event the
791
+ // call emits. Emit as late as possible — after history stripping,
792
+ // arg construction, and turn-context resolution — so the gap
793
+ // between "we said the call started" and the actual provider HTTP
794
+ // call is minimized. Awaited so the row is created and the
795
+ // `assistant_turn_start` wire event reaches the client BEFORE the
796
+ // provider starts streaming deltas — the deltas downstream will
797
+ // carry the freshly-reserved id.
798
+ await onEvent({ type: "llm_call_started", callSite });
799
+
754
800
  // Inner try/catch narrows error-recording scope to the provider
755
801
  // call itself. The outer agent-loop catch (below) wraps the entire
756
802
  // turn body (tool execution, plugin pipelines, checkpoints), so
@@ -862,6 +908,40 @@ export class AgentLoop {
862
908
  "LLM call complete",
863
909
  );
864
910
 
911
+ if (isMaxTokensStopReason(response.stopReason)) {
912
+ const safeContent = response.content.filter(
913
+ (block) =>
914
+ block.type !== "tool_use" &&
915
+ block.type !== "server_tool_use" &&
916
+ block.type !== "web_search_tool_result",
917
+ );
918
+ const safeAssistantMessage: Message = {
919
+ role: "assistant",
920
+ content: safeContent,
921
+ };
922
+ rlog.warn(
923
+ {
924
+ turn: toolUseTurns,
925
+ stopReason: response.stopReason,
926
+ contentBlocks: response.content.length,
927
+ safeContentBlocks: safeContent.length,
928
+ toolUseCount: toolUseBlocks.length,
929
+ },
930
+ "LLM response reached output token limit",
931
+ );
932
+ history.push(safeAssistantMessage);
933
+ await onEvent({
934
+ type: "max_tokens_reached",
935
+ stopReason: response.stopReason,
936
+ });
937
+ await onEvent({
938
+ type: "message_complete",
939
+ message: safeAssistantMessage,
940
+ });
941
+ await emitExit("max_tokens_reached");
942
+ break;
943
+ }
944
+
865
945
  // Detect empty responses: no user-visible text and no tool calls.
866
946
  // This can happen when the model fails to produce output after
867
947
  // receiving a large tool result. Retry once with a nudge before