@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
@@ -100,7 +100,7 @@ Use `notify` for simple reminders ("remind me to take medicine at 9am"), `execut
100
100
 
101
101
  ## Conversation Reuse
102
102
 
103
- By default, each schedule run creates a new conversation. For recurring schedules that benefit from accumulating context across runs (e.g. polling-style jobs, daily digests that reference prior results), set `reuse_conversation: true`. When enabled, subsequent runs reuse the conversation from the last successful run instead of creating a new one.
103
+ Recurring schedules reuse the same conversation across runs by default subsequent runs continue the conversation from the last successful run, preserving context and channel thread continuity. Set `reuse_conversation: false` explicitly if each run should start with a fresh conversation (e.g. independent reports that shouldn't accumulate prior context). One-shot schedules always create a fresh conversation.
104
104
 
105
105
  - Only applies to **recurring** schedules; ignored for one-shot schedules.
106
106
  - If the prior conversation has been deleted, a new one is created automatically.
@@ -62,7 +62,7 @@
62
62
  },
63
63
  "reuse_conversation": {
64
64
  "type": "boolean",
65
- "description": "When true, reuse the same conversation across recurring schedule runs instead of creating a new one each time. Useful for polling-style schedules that accumulate context over time. Ignored for one-shot schedules. Defaults to false."
65
+ "description": "When true, reuse the same conversation across recurring schedule runs instead of creating a new one each time. Defaults to true for recurring schedules, false for one-shot. Set to false explicitly if each run should start fresh."
66
66
  },
67
67
  "max_retries": {
68
68
  "type": "integer",
@@ -161,7 +161,7 @@
161
161
  },
162
162
  "reuse_conversation": {
163
163
  "type": "boolean",
164
- "description": "When true, reuse the same conversation across recurring schedule runs instead of creating a new one each time. Useful for polling-style schedules that accumulate context over time. Ignored for one-shot schedules."
164
+ "description": "When true, reuse the same conversation across recurring schedule runs instead of creating a new one each time. Defaults to true for recurring schedules. Set to false explicitly if each run should start fresh."
165
165
  },
166
166
  "max_retries": {
167
167
  "type": "integer",
@@ -42,6 +42,7 @@ export async function run(
42
42
  context.sendToClient({
43
43
  type: "open_url",
44
44
  url: meta.url,
45
+ conversationId: context.conversationId,
45
46
  });
46
47
  }
47
48
 
@@ -23,7 +23,7 @@ export const CALL_SITE_DEFAULTS: Record<LLMCallSite, CallSiteDefaultConfig> = {
23
23
  emptyStateGreeting: { profile: "balanced" },
24
24
 
25
25
  memoryRouter: {
26
- profile: "balanced",
26
+ profile: "cost-optimized",
27
27
  contextWindow: { maxInputTokens: 1000000 },
28
28
  },
29
29
  recall: {
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Module-level cache for resolved feature flag override values.
3
+ *
4
+ * Lives in its own module (rather than alongside the resolver in
5
+ * `assistant-feature-flags.ts`) so test code can read/write the cache
6
+ * without going through `assistant-feature-flags.ts` — which transitively
7
+ * pulls `util/logger.js` (pino) and the gateway IPC client. Stdlib-only
8
+ * by design: this file must remain safe to import from the test
9
+ * preload's load-time chain, where a broken `node_modules` symlink has
10
+ * historically tripped the env override (see DB ghost #3,
11
+ * /workspace/journal/2026-05-25-db-ghost-3-recovery.md).
12
+ *
13
+ * State is held on `globalThis.vellumAssistant.featureFlagCache` so test
14
+ * helpers in `__tests__/` can read/write it WITHOUT importing this
15
+ * module — they declare the same slot shape locally and access the
16
+ * globalThis namespace directly. See
17
+ * `__tests__/feature-flag-test-helpers.ts` for the test-side mirror;
18
+ * the slot shape MUST stay in sync between the two.
19
+ *
20
+ * Both `overrides` and `fromGateway` were previously module-level `let`
21
+ * bindings inside `assistant-feature-flags.ts`. The semantics are
22
+ * preserved exactly: `overrides === null` means "no fetch has populated
23
+ * the cache yet"; `fromGateway === true` means "the cache is
24
+ * authoritative — `initFeatureFlagOverrides()` should not clobber it".
25
+ *
26
+ * Consumers:
27
+ * - `assistant-feature-flags.ts` (resolver — reads/writes via gateway fetch)
28
+ * - `__tests__/feature-flag-test-helpers.ts` (seeds for tests, via globalThis)
29
+ */
30
+
31
+ type FlagSlot = {
32
+ overrides: Record<string, boolean> | null;
33
+ fromGateway: boolean;
34
+ };
35
+
36
+ type VellumAssistantNamespace = {
37
+ featureFlagCache?: FlagSlot;
38
+ };
39
+
40
+ function slot(): FlagSlot {
41
+ const g = globalThis as { vellumAssistant?: VellumAssistantNamespace };
42
+ const ns = (g.vellumAssistant ??= {});
43
+ return (ns.featureFlagCache ??= { overrides: null, fromGateway: false });
44
+ }
45
+
46
+ /** Read the current override cache. `null` means not yet populated. */
47
+ export function getCachedOverrides(): Record<string, boolean> | null {
48
+ return slot().overrides;
49
+ }
50
+
51
+ /**
52
+ * True when the cache was populated by either a gateway IPC fetch or by a
53
+ * test helper. Used by `initFeatureFlagOverrides()` to short-circuit a
54
+ * second fetch (e.g. when a CLI entry point runs after the daemon has
55
+ * already initialized) and by tests to prevent the retry loop from
56
+ * clobbering preseeded state.
57
+ */
58
+ export function isCachedFromGateway(): boolean {
59
+ return slot().fromGateway;
60
+ }
61
+
62
+ /**
63
+ * Replace the cache with a clone of `overrides`. The `fromGateway` flag
64
+ * is set by the caller — production callers pass `true` after a
65
+ * successful gateway fetch; test helpers also pass `true` so subsequent
66
+ * `initFeatureFlagOverrides()` calls are no-ops.
67
+ */
68
+ export function setCachedOverrides(
69
+ overrides: Record<string, boolean>,
70
+ options: { fromGateway: boolean },
71
+ ): void {
72
+ const s = slot();
73
+ s.overrides = { ...overrides };
74
+ s.fromGateway = options.fromGateway;
75
+ }
76
+
77
+ /**
78
+ * Drop the cache. The next `loadOverrides()` returns an empty record (so
79
+ * flag checks fall through to registry defaults) and the next
80
+ * `initFeatureFlagOverrides()` re-fetches from the gateway.
81
+ */
82
+ export function clearCachedOverrides(): void {
83
+ const s = slot();
84
+ s.overrides = null;
85
+ s.fromGateway = false;
86
+ }
@@ -25,6 +25,14 @@
25
25
  "description": "Enable user-hosted onboarding flow",
26
26
  "defaultEnabled": false
27
27
  },
28
+ {
29
+ "id": "prechat-onboarding-condensed-flow",
30
+ "scope": "client",
31
+ "key": "prechat-onboarding-condensed-flow",
32
+ "label": "Condensed Pre-chat Onboarding",
33
+ "description": "Enable the condensed pre-chat onboarding flow for a standard LaunchDarkly percentage rollout.",
34
+ "defaultEnabled": false
35
+ },
28
36
  {
29
37
  "id": "local-docker-enabled",
30
38
  "scope": "client",
@@ -190,7 +198,7 @@
190
198
  "scope": "assistant",
191
199
  "key": "fast-mode",
192
200
  "label": "Fast Mode",
193
- "description": "Enable Anthropic fast mode for Opus models (4.6, 4.7), delivering up to 2.5x higher output tokens per second at premium pricing",
201
+ "description": "Enable Anthropic fast mode for Opus models (4.6, 4.7, 4.8), delivering up to 2.5x higher output tokens per second at premium pricing",
194
202
  "defaultEnabled": false
195
203
  },
196
204
  {
@@ -281,14 +289,6 @@
281
289
  "description": "Expose the developer-only Compaction Playground tab in macOS Settings and enable the /playground/* HTTP endpoints for exercising compaction conditions. Dev-only; default off.",
282
290
  "defaultEnabled": false
283
291
  },
284
- {
285
- "id": "safe-storage-limits",
286
- "scope": "assistant",
287
- "key": "safe-storage-limits",
288
- "label": "Safe Storage Limits",
289
- "description": "Enable disk pressure protection flows that block background work and remote actors while storage is critically low.",
290
- "defaultEnabled": false
291
- },
292
292
  {
293
293
  "id": "account-deletion",
294
294
  "scope": "assistant",
@@ -313,14 +313,6 @@
313
313
  "description": "Show the 'Analyze' / 'Analyze conversation' option in conversation context menus and the conversation title actions dropdown.",
314
314
  "defaultEnabled": false
315
315
  },
316
- {
317
- "id": "pro-plan-adjust",
318
- "scope": "client",
319
- "key": "pro-plan-adjust",
320
- "label": "Pro Plan Adjust",
321
- "description": "Show the rich Plan card (current plan, features, Manage/Upgrade CTA) at the top of the macOS Settings \u2192 Billing tab.",
322
- "defaultEnabled": false
323
- },
324
316
  {
325
317
  "id": "external-plugins",
326
318
  "scope": "assistant",
@@ -440,6 +432,14 @@
440
432
  "label": "Memory Router Playground",
441
433
  "description": "Expose the developer-only Memory Router Playground tab in macOS Settings and the /assistant/memory-router-playground web page for dry-running v4 router config overrides against the live page index. Dev-only; default off.",
442
434
  "defaultEnabled": false
435
+ },
436
+ {
437
+ "id": "platform-features-in-local-mode",
438
+ "scope": "assistant",
439
+ "key": "platform-features-in-local-mode",
440
+ "label": "Platform Features in Local Mode",
441
+ "description": "When enabled, the assistant can call the Vellum platform API from local mode. When disabled, all platform API clients in the daemon, gateway, CES, and web UI no-op with a debug log instead of making outbound requests.",
442
+ "defaultEnabled": true
443
443
  }
444
444
  ]
445
445
  }
@@ -27,12 +27,21 @@ export function resolveEffectiveContextWindow({
27
27
  llm,
28
28
  callSite,
29
29
  overrideProfile,
30
+ selectionSeed,
30
31
  }: {
31
32
  llm: LLMConfig;
32
33
  callSite: LLMCallSite;
33
34
  overrideProfile?: string;
35
+ /**
36
+ * Per-conversation mix seed (the conversation id). Threaded so context-window
37
+ * sizing for a mix profile reflects the same arm the dispatch path picks.
38
+ */
39
+ selectionSeed?: string;
34
40
  }): EffectiveContextWindow {
35
- const resolved = resolveCallSiteConfig(callSite, llm, { overrideProfile });
41
+ const resolved = resolveCallSiteConfig(callSite, llm, {
42
+ overrideProfile,
43
+ selectionSeed,
44
+ });
36
45
  const catalogModel = PROVIDER_CATALOG.find(
37
46
  (provider) => provider.id === resolved.provider,
38
47
  )?.models.find((model) => model.id === resolved.model);
@@ -42,33 +42,60 @@ import {
42
42
  * resolver stays pure; schema validation in `LLMSchema.superRefine` catches
43
43
  * unknown `activeProfile` references at config-load time.
44
44
  *
45
- * Pure & synchronous: no I/O, no async work.
45
+ * A profile reference that points at a "mix" profile is expanded to one of its
46
+ * constituent profiles by a seeded weighted pick (see `resolveProfileFragment`
47
+ * and `opts.selectionSeed`). Expansion happens uniformly at every dereference
48
+ * spot, so a mix works as `activeProfile`, `overrideProfile`, or a call-site
49
+ * `profile`.
50
+ *
51
+ * Pure & synchronous: no I/O, no async work. (Random selection only occurs for
52
+ * mix profiles when no `selectionSeed` is supplied; with a seed the pick is
53
+ * deterministic.)
46
54
  */
55
+ export interface ResolveCallSiteOpts {
56
+ overrideProfile?: string;
57
+ /**
58
+ * Per-conversation seed for expanding `mix` profiles. The chosen constituent
59
+ * is a deterministic function of `selectionSeed` + the mix profile's own
60
+ * name, so every `resolveCallSiteConfig` call for the same conversation picks
61
+ * the SAME arm (stable across turns, retries, and restarts). Pass the
62
+ * conversation id. When absent, the resolver falls back to a fresh random
63
+ * pick per call — acceptable only for one-shot/background call sites that
64
+ * resolve config exactly once per invocation.
65
+ */
66
+ selectionSeed?: string;
67
+ /**
68
+ * Invoked once for each mix profile the resolver expands, reporting which
69
+ * constituent was chosen. Used by A/B-eval recording (usage attribution).
70
+ */
71
+ onMixSelected?: (info: { mixProfile: string; chosenProfile: string }) => void;
72
+ }
73
+
47
74
  export function resolveCallSiteConfig(
48
75
  callSite: LLMCallSite,
49
76
  llm: z.infer<typeof LLMSchema>,
50
- opts: { overrideProfile?: string } = {},
77
+ opts: ResolveCallSiteOpts = {},
51
78
  ): z.infer<typeof LLMConfigBase> {
52
79
  const layers: Mergeable[] = [llm.default as Mergeable];
53
80
 
54
- const activeFragment =
55
- llm.activeProfile != null ? llm.profiles?.[llm.activeProfile] : undefined;
56
- const overrideFragment =
57
- opts.overrideProfile != null
58
- ? llm.profiles?.[opts.overrideProfile]
59
- : undefined;
81
+ const activeFragment = resolveProfileFragment(llm.activeProfile, llm, opts);
82
+ const overrideFragment = resolveProfileFragment(
83
+ opts.overrideProfile,
84
+ llm,
85
+ opts,
86
+ );
60
87
  const site =
61
88
  llm.callSites?.[callSite] ??
62
89
  effectiveDefault(callSite, llm, opts.overrideProfile != null);
63
90
 
64
91
  if (callSite === "mainAgent") {
65
- appendCallSiteLayers(layers, callSite, llm, site);
92
+ appendCallSiteLayers(layers, callSite, llm, site, opts);
66
93
  appendProfileLayer(layers, activeFragment);
67
94
  appendProfileLayer(layers, overrideFragment);
68
95
  } else {
69
96
  appendProfileLayer(layers, activeFragment);
70
97
  appendProfileLayer(layers, overrideFragment);
71
- appendCallSiteLayers(layers, callSite, llm, site);
98
+ appendCallSiteLayers(layers, callSite, llm, site, opts);
72
99
  }
73
100
 
74
101
  return finalize(deepMerge(...layers.map(withImpliedProviderForKnownModel)));
@@ -80,6 +107,81 @@ export function resolveCallSiteConfig(
80
107
 
81
108
  type Mergeable = Record<string, unknown>;
82
109
 
110
+ /**
111
+ * FNV-1a 32-bit string hash → unit float in [0, 1). Deterministic and stable
112
+ * across runtimes — the mix-pick contract depends on identical output for
113
+ * identical input forever, so the constants must never change. (Mirrors the
114
+ * private hash in `memory/v2/page-index.ts`; intentionally re-declared here so
115
+ * the resolver's determinism contract is self-contained and cannot be broken
116
+ * by an unrelated edit to that module.)
117
+ */
118
+ function seededUnitFloat(seed: string): number {
119
+ let h = 0x811c9dc5;
120
+ for (let i = 0; i < seed.length; i++) {
121
+ h ^= seed.charCodeAt(i);
122
+ h = Math.imul(h, 0x01000193);
123
+ }
124
+ // `>>> 0` → unsigned 32-bit; divide by 2^32 to land in [0, 1).
125
+ return (h >>> 0) / 0x100000000;
126
+ }
127
+
128
+ /**
129
+ * Pick one entry from a weighted list given a unit float in [0, 1). Weights
130
+ * are relative and normalized by their sum. Assumes `entries` is non-empty
131
+ * with positive weights (guaranteed by `MixSchema`: `.min(2)` + positive).
132
+ */
133
+ function weightedPick<T extends { weight: number }>(
134
+ entries: readonly T[],
135
+ unit: number,
136
+ ): T {
137
+ const total = entries.reduce((sum, e) => sum + e.weight, 0);
138
+ // Defensive: a degenerate total (unreachable post-schema) → first arm.
139
+ if (!(total > 0)) return entries[0];
140
+ let threshold = unit * total;
141
+ for (const entry of entries) {
142
+ threshold -= entry.weight;
143
+ if (threshold < 0) return entry;
144
+ }
145
+ // Floating-point fall-through (unit ≈ 1): return the last arm.
146
+ return entries[entries.length - 1];
147
+ }
148
+
149
+ /**
150
+ * Dereference a profile name to its concrete `ProfileEntry`, expanding a mix
151
+ * profile by a seeded weighted pick. Returns `undefined` when the name is
152
+ * unknown (parity with the silent fall-through callers already rely on).
153
+ *
154
+ * Mix expansion is one level only — `LLMSchema.superRefine` guarantees arms
155
+ * are standard (non-mix) profiles, so this never recurses unboundedly. A
156
+ * chosen arm pointing at a missing profile (only reachable in hand-crafted,
157
+ * unparsed configs) falls through to `undefined`.
158
+ *
159
+ * Pure & synchronous (the only impurity is `Math.random()` in the no-seed
160
+ * fallback path).
161
+ */
162
+ function resolveProfileFragment(
163
+ name: string | undefined,
164
+ llm: z.infer<typeof LLMSchema>,
165
+ opts: ResolveCallSiteOpts,
166
+ ): ProfileEntry | undefined {
167
+ if (name == null) return undefined;
168
+ const entry = llm.profiles?.[name];
169
+ if (entry?.mix == null) return entry;
170
+
171
+ // Mix: pick one constituent. Seed by per-conversation seed + the mix's own
172
+ // name so two different mixes in the same conversation pick independently,
173
+ // but the same mix always resolves to the same arm within the conversation.
174
+ const unit =
175
+ opts.selectionSeed != null
176
+ ? seededUnitFloat(`${opts.selectionSeed}\u0000${name}`)
177
+ : Math.random();
178
+ const chosen = weightedPick(entry.mix, unit);
179
+ opts.onMixSelected?.({ mixProfile: name, chosenProfile: chosen.profile });
180
+
181
+ // The chosen arm must be a standard profile (enforced by superRefine).
182
+ return llm.profiles?.[chosen.profile];
183
+ }
184
+
83
185
  /**
84
186
  * Returns the effective default profile key the resolver would actually
85
187
  * select for a call site when no per-turn `overrideProfile` is supplied.
@@ -170,16 +272,16 @@ function appendCallSiteLayers(
170
272
  callSite: LLMCallSite,
171
273
  llm: z.infer<typeof LLMSchema>,
172
274
  site: z.infer<typeof LLMSchema>["callSites"][LLMCallSite] | undefined,
275
+ opts: ResolveCallSiteOpts,
173
276
  ): void {
174
277
  if (site != null) {
175
278
  if (site.profile != null) {
176
- const profileFragment: ProfileEntry | undefined =
177
- llm.profiles?.[site.profile];
279
+ const profileFragment = resolveProfileFragment(site.profile, llm, opts);
178
280
  if (profileFragment == null) {
179
281
  // Defensive: `LLMSchema.superRefine` already rejects unknown profile
180
- // references at config load, so this branch is unreachable for any
181
- // config that survived schema validation. Throw a clear error in case
182
- // a hand-crafted (un-parsed) config slips through.
282
+ // references (and unknown mix arms) at config load, so this branch is
283
+ // unreachable for any config that survived schema validation. Throw a
284
+ // clear error in case a hand-crafted (un-parsed) config slips through.
183
285
  throw new Error(
184
286
  `LLM call site "${callSite}" references undefined profile "${site.profile}"`,
185
287
  );
@@ -198,6 +300,10 @@ function profileConfigFragment(profile: ProfileEntry): Mergeable {
198
300
  source: _source,
199
301
  label: _label,
200
302
  description: _description,
303
+ // `mix` never reaches here in practice (a mix expands to a standard
304
+ // profile before this point), but strip it defensively so it can never
305
+ // leak into the merged `LLMConfigBase`.
306
+ mix: _mix,
201
307
  ...config
202
308
  } = profile;
203
309
  return config as Mergeable;
@@ -122,6 +122,9 @@ export function getDeploymentContextDefaults(): Record<string, unknown> {
122
122
  if (process.env.IS_PLATFORM !== "true" && process.env.IS_PLATFORM !== "1") {
123
123
  return {};
124
124
  }
125
+ // `web-search.mode = managed` enables platform-backed app-executed search
126
+ // for non-native inference providers while preserving provider-native hosted
127
+ // search for providers/models that support it.
125
128
  const managed = { mode: "managed" as const };
126
129
  return {
127
130
  services: {
@@ -161,11 +164,7 @@ export function fillContextDefaultsForMissingKeys(
161
164
  ): void {
162
165
  for (const [key, value] of Object.entries(contextDefaults)) {
163
166
  const fileVal = fileConfig[key];
164
- if (
165
- value !== null &&
166
- typeof value === "object" &&
167
- !Array.isArray(value)
168
- ) {
167
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
169
168
  const targetChild = readPlainObject(target[key]);
170
169
  const fileChild = readPlainObject(fileVal);
171
170
  if (targetChild) {
@@ -224,6 +224,7 @@ describe("MemoryV3ConfigSchema", () => {
224
224
  denseQuota: { activeDomain: 30, offDomain: 8 },
225
225
  hotLimit: 50,
226
226
  lanes: { hot: true, sparse: true, dense: true, tree: true, edges: true },
227
+ edges: { learnedAdjacencyThreshold: 0, maxPulls: 400 },
227
228
  ks: [5, 10, 25, 50],
228
229
  write: {
229
230
  enabled: false,
@@ -272,6 +273,20 @@ describe("MemoryV3ConfigSchema", () => {
272
273
  expect(parsed.denseQuota).toEqual({ activeDomain: 50, offDomain: 12 });
273
274
  });
274
275
 
276
+ test("accepts a partial denseQuota override and defaults the rest", () => {
277
+ // Overriding only one leaf must merge with defaults, not fail validation
278
+ // (which would discard the entire user config).
279
+ const onlyActive = MemoryV3ConfigSchema.parse({
280
+ denseQuota: { activeDomain: 50 },
281
+ });
282
+ expect(onlyActive.denseQuota).toEqual({ activeDomain: 50, offDomain: 8 });
283
+
284
+ const onlyOff = MemoryV3ConfigSchema.parse({
285
+ denseQuota: { offDomain: 12 },
286
+ });
287
+ expect(onlyOff.denseQuota).toEqual({ activeDomain: 30, offDomain: 12 });
288
+ });
289
+
275
290
  test("accepts a partial lanes override and defaults the rest", () => {
276
291
  const parsed = MemoryV3ConfigSchema.parse({ lanes: { dense: false } });
277
292
  expect(parsed.lanes).toEqual({
@@ -11,7 +11,7 @@ export const HeartbeatConfigSchema = z
11
11
  .number({ error: "heartbeat.intervalMs must be a number" })
12
12
  .int("heartbeat.intervalMs must be an integer")
13
13
  .positive("heartbeat.intervalMs must be a positive integer")
14
- .default(30 * 60_000)
14
+ .default(60 * 60_000)
15
15
  .describe("Time between heartbeat checks in milliseconds"),
16
16
  cronExpression: z
17
17
  .string()
@@ -309,7 +309,7 @@ export const LLMConfigBase = z.object({
309
309
  * naturally — the underlying profile-level field is on `ProfileEntry`.
310
310
  */
311
311
  provider_connection: z.string().min(1).optional(),
312
- model: ModelSchema.default("claude-opus-4-7"),
312
+ model: ModelSchema.default("claude-opus-4-8"),
313
313
  maxTokens: MaxTokensSchema.default(64000),
314
314
  effort: EffortEnum.default("max"),
315
315
  speed: SpeedEnum.default("standard"),
@@ -344,6 +344,27 @@ type LLMConfigFragment = z.infer<typeof LLMConfigFragment>;
344
344
  export const ProfileStatusSchema = z.enum(["active", "disabled"]);
345
345
  export type ProfileStatus = z.infer<typeof ProfileStatusSchema>;
346
346
 
347
+ // ---------------------------------------------------------------------------
348
+ // Mix profiles
349
+ //
350
+ // A "mix" profile carries no model config of its own. Instead it references a
351
+ // weighted list of other (standard) profiles; at resolve time exactly one
352
+ // constituent is chosen by weight. The pick is a deterministic function of a
353
+ // per-conversation seed (the conversation id — see `resolveCallSiteConfig`'s
354
+ // `selectionSeed`), so a conversation always lands on the same arm across all
355
+ // its turns, retries, and even daemon restarts, while different conversations
356
+ // split according to the weights — and the chosen arm is recordable for A/B
357
+ // evaluation. Weights are relative (normalized by their sum at pick time), so
358
+ // `[{weight:80},{weight:20}]` and `[{weight:4},{weight:1}]` are equivalent.
359
+ // ---------------------------------------------------------------------------
360
+ const MixArmSchema = z.object({
361
+ profile: z.string().min(1),
362
+ weight: z.number().finite().positive(),
363
+ });
364
+ export type MixArm = z.infer<typeof MixArmSchema>;
365
+
366
+ const MixSchema = z.array(MixArmSchema).min(2);
367
+
347
368
  /**
348
369
  * A named profile entry: an `LLMConfigFragment` augmented with
349
370
  * presentation/ownership metadata. These fields are intentionally kept off
@@ -379,6 +400,17 @@ export const ProfileEntry = LLMConfigFragment.extend({
379
400
  * #30362 even though the schema didn't accept it until now.
380
401
  */
381
402
  status: ProfileStatusSchema.nullable().optional(),
403
+ /**
404
+ * When present, this profile is a "mix": it carries no model config and
405
+ * instead references a weighted list of standard profiles. The resolver
406
+ * expands a mix by a seeded weighted pick (see `resolveCallSiteConfig`).
407
+ * `LLMSchema.superRefine` enforces that (a) every referenced profile exists,
408
+ * (b) no referenced profile is itself a mix (no nesting), (c) no arm
409
+ * references the mix itself, and (d) a mix carries no `LLMConfigFragment`
410
+ * config field — only metadata (`label`, `description`, `status`, `source`)
411
+ * may accompany `mix`.
412
+ */
413
+ mix: MixSchema.optional(),
382
414
  });
383
415
  export type ProfileEntry = z.infer<typeof ProfileEntry>;
384
416
 
@@ -444,6 +476,63 @@ export const LLMSchema = z
444
476
  message: `Profile "${config.activeProfile}" referenced by llm.activeProfile is not defined in llm.profiles`,
445
477
  });
446
478
  }
479
+
480
+ // --- Mix profile validation --------------------------------------------
481
+ // Config keys a mix profile must NOT also set (a mix only references other
482
+ // profiles + metadata). Derived from the fragment shape plus the
483
+ // ProfileEntry-only `provider_connection` so it can't drift if a new config
484
+ // field is added to `LLMConfigFragment`.
485
+ const MIX_DISALLOWED_CONFIG_KEYS = [
486
+ ...Object.keys(LLMConfigFragment.shape),
487
+ "provider_connection",
488
+ ];
489
+ const mixProfileNames = new Set(
490
+ Object.entries(config.profiles ?? {})
491
+ .filter(([, profile]) => profile?.mix != null)
492
+ .map(([name]) => name),
493
+ );
494
+ for (const [name, profile] of Object.entries(config.profiles ?? {})) {
495
+ if (profile?.mix == null) continue;
496
+ // (d) A mix must not also carry model config — the resolved config comes
497
+ // entirely from the chosen constituent.
498
+ for (const key of MIX_DISALLOWED_CONFIG_KEYS) {
499
+ if ((profile as Record<string, unknown>)[key] !== undefined) {
500
+ ctx.addIssue({
501
+ code: "custom",
502
+ path: ["profiles", name, key],
503
+ message: `Mix profile "${name}" cannot also set "${key}" — a mix only references other profiles plus metadata (label, description, status).`,
504
+ });
505
+ }
506
+ }
507
+ for (const [index, arm] of profile.mix.entries()) {
508
+ // (c) No self-reference.
509
+ if (arm.profile === name) {
510
+ ctx.addIssue({
511
+ code: "custom",
512
+ path: ["profiles", name, "mix", index, "profile"],
513
+ message: `Mix profile "${name}" cannot reference itself.`,
514
+ });
515
+ continue;
516
+ }
517
+ // (a) Referenced profile must exist.
518
+ if (!profileNames.has(arm.profile)) {
519
+ ctx.addIssue({
520
+ code: "custom",
521
+ path: ["profiles", name, "mix", index, "profile"],
522
+ message: `Mix profile "${name}" references profile "${arm.profile}" which is not defined in llm.profiles.`,
523
+ });
524
+ continue;
525
+ }
526
+ // (b) No nesting — a mix arm must be a standard (non-mix) profile.
527
+ if (mixProfileNames.has(arm.profile)) {
528
+ ctx.addIssue({
529
+ code: "custom",
530
+ path: ["profiles", name, "mix", index, "profile"],
531
+ message: `Mix profile "${name}" references another mix profile "${arm.profile}" — mixes cannot be nested; constituents must be standard profiles.`,
532
+ });
533
+ }
534
+ }
535
+ }
447
536
  });
448
537
 
449
538
  export type LLMConfig = z.infer<typeof LLMSchema>;
@@ -466,11 +466,13 @@ export const MemoryV3ConfigSchema = z
466
466
  .number({
467
467
  error: "memory.v3.denseQuota.activeDomain must be a number",
468
468
  })
469
+ .default(30)
469
470
  .describe(
470
471
  "Dense-lane candidate quota allocated to the conversation's active domain.",
471
472
  ),
472
473
  offDomain: z
473
474
  .number({ error: "memory.v3.denseQuota.offDomain must be a number" })
475
+ .default(8)
474
476
  .describe(
475
477
  "Dense-lane candidate quota allocated to off-domain (exploratory) retrieval.",
476
478
  ),
@@ -520,6 +522,29 @@ export const MemoryV3ConfigSchema = z
520
522
  .describe(
521
523
  "Per-lane on/off toggles for the v3 multi-lane retrieval fanout. All lanes on by default.",
522
524
  ),
525
+ edges: z
526
+ .object({
527
+ learnedAdjacencyThreshold: z
528
+ .number({
529
+ error: "memory.v3.edges.learnedAdjacencyThreshold must be a number",
530
+ })
531
+ .min(0, "memory.v3.edges.learnedAdjacencyThreshold must be >= 0")
532
+ .default(0)
533
+ .describe(
534
+ "Association-strength cutoff for merging the learned co-retrieval graph (memory_v3_auto_edges) into the edge-expansion lane. Seeded edges are weighted seedWeight × NPMI, so this is effectively a minimum-NPMI gate: NPMI ≥ threshold / seedWeight (e.g. with the default seedWeight 2.0, threshold 1.0 ≈ NPMI ≥ 0.5, 1.2 ≈ NPMI ≥ 0.6). When > 0, edges at or above this weight are read via aboveThreshold() and merged with the curated frontmatter graph as expandEdges' extraAdjacency. 0 (default) = OFF: the edge lane uses curated edges only and behavior is unchanged. Seed the learned graph with `assistant memory v3 seed-edges`.",
535
+ ),
536
+ maxPulls: z
537
+ .number({ error: "memory.v3.edges.maxPulls must be a number" })
538
+ .min(0, "memory.v3.edges.maxPulls must be >= 0")
539
+ .default(400)
540
+ .describe(
541
+ "Hard cap on the edge lane's contribution to the gate's candidate pool (the unioned 1–2 hop curated∪learned neighborhood). The shipped default 400 lets a dense curated graph dominate the pool and drown the gate's recall of high-precision hits; lower values (~40) keep the lane focused. 0 effectively disables edge pulls.",
542
+ ),
543
+ })
544
+ .default({ learnedAdjacencyThreshold: 0, maxPulls: 400 })
545
+ .describe(
546
+ "Edge-expansion lane configuration. Gates whether the learned co-retrieval graph augments the curated edge graph.",
547
+ ),
523
548
  ks: z
524
549
  .array(z.number({ error: "memory.v3.ks entries must be numbers" }))
525
550
  .default([5, 10, 25, 50])
@@ -597,6 +622,7 @@ export const MemoryV3ConfigSchema = z
597
622
  denseQuota: { activeDomain: 30, offDomain: 8 },
598
623
  hotLimit: 50,
599
624
  lanes: { hot: true, sparse: true, dense: true, tree: true, edges: true },
625
+ edges: { learnedAdjacencyThreshold: 0, maxPulls: 400 },
600
626
  ks: [5, 10, 25, 50],
601
627
  write: {
602
628
  enabled: false,