@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
@@ -136,13 +136,9 @@ function makeCompletingConversation(): Conversation {
136
136
  const messages: unknown[] = [];
137
137
  return {
138
138
  isProcessing: () => processing,
139
- persistUserMessage: (
140
- _content: string,
141
- _attachments: unknown[],
142
- requestId?: string,
143
- ) => {
139
+ persistUserMessage: (options: { requestId?: string }) => {
144
140
  processing = true;
145
- return requestId ?? "msg-1";
141
+ return { id: options.requestId ?? "msg-1", deduplicated: false };
146
142
  },
147
143
  memoryPolicy: {
148
144
  scopeId: "default",
@@ -188,18 +184,14 @@ function makeHangingConversation(): Conversation {
188
184
  const messages: unknown[] = [];
189
185
  const enqueuedMessages: Array<{
190
186
  content: string;
191
- onEvent: (msg: ServerMessage) => void;
192
- requestId: string;
187
+ onEvent?: (msg: ServerMessage) => void;
188
+ requestId?: string;
193
189
  }> = [];
194
190
  return {
195
191
  isProcessing: () => processing,
196
- persistUserMessage: (
197
- _content: string,
198
- _attachments: unknown[],
199
- requestId?: string,
200
- ) => {
192
+ persistUserMessage: (options: { requestId?: string }) => {
201
193
  processing = true;
202
- return requestId ?? "msg-1";
194
+ return { id: options.requestId ?? "msg-1", deduplicated: false };
203
195
  },
204
196
  memoryPolicy: {
205
197
  scopeId: "default",
@@ -223,14 +215,20 @@ function makeHangingConversation(): Conversation {
223
215
  hasPendingConfirmation: () => false,
224
216
  denyAllPendingConfirmations: () => {},
225
217
  getQueueDepth: () => enqueuedMessages.length,
226
- enqueueMessage: (
227
- content: string,
228
- _attachments: unknown[],
229
- onEvent: (msg: ServerMessage) => void,
230
- requestId: string,
231
- ) => {
232
- enqueuedMessages.push({ content, onEvent, requestId });
233
- return { queued: true, requestId };
218
+ enqueueMessage: (options: {
219
+ content: string;
220
+ onEvent?: (msg: ServerMessage) => void;
221
+ requestId?: string;
222
+ }) => {
223
+ enqueuedMessages.push({
224
+ content: options.content,
225
+ onEvent: options.onEvent,
226
+ requestId: options.requestId,
227
+ });
228
+ return {
229
+ queued: true,
230
+ requestId: options.requestId ?? "hanging-req",
231
+ };
234
232
  },
235
233
  runAgentLoop: async () => {
236
234
  // Hang forever
@@ -259,14 +257,9 @@ function makePendingApprovalConversation(
259
257
  const messages: unknown[] = [];
260
258
  const runAgentLoopMock = mock(async () => {});
261
259
  const enqueueMessageMock = mock(
262
- (
263
- _content: string,
264
- _attachments: unknown[],
265
- _onEvent: (msg: ServerMessage) => void,
266
- queuedRequestId: string,
267
- ) => ({
260
+ (options: { content: string; requestId?: string }) => ({
268
261
  queued: true,
269
- requestId: queuedRequestId,
262
+ requestId: options.requestId ?? "queued-req",
270
263
  }),
271
264
  );
272
265
  const denyAllPendingConfirmationsMock = mock(() => {
@@ -278,11 +271,10 @@ function makePendingApprovalConversation(
278
271
 
279
272
  const conversation = {
280
273
  isProcessing: () => processing,
281
- persistUserMessage: (
282
- _content: string,
283
- _attachments: unknown[],
284
- reqId?: string,
285
- ) => reqId ?? "msg-1",
274
+ persistUserMessage: (options: { requestId?: string }) => ({
275
+ id: options.requestId ?? "msg-1",
276
+ deduplicated: false,
277
+ }),
286
278
  memoryPolicy: {
287
279
  scopeId: "default",
288
280
  includeDefaultFallback: false,
@@ -895,7 +887,7 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
895
887
  await stopServer();
896
888
  });
897
889
 
898
- test("accepts message when conversationKey is omitted (defaults to stable channel key)", async () => {
890
+ test("accepts message when conversationKey is omitted (vellum channel mints fresh)", async () => {
899
891
  await startServer(() => makeCompletingConversation());
900
892
 
901
893
  const res = await fetch(messagesUrl(), {
@@ -908,14 +900,24 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
908
900
  }),
909
901
  });
910
902
  expect(res.status).toBe(202);
903
+ const body = (await res.json()) as {
904
+ accepted: boolean;
905
+ conversationId: string;
906
+ };
907
+ expect(body.accepted).toBe(true);
908
+ expect(body.conversationId).toBeTruthy();
909
+
910
+ // The vellum channel never falls through to the shared
911
+ // `default:vellum:<interface>` thread: each empty-handed send mints
912
+ // a fresh conversation so the first-message id surfaces to the client.
913
+ expect(getConversationByKey("default:vellum:macos")).toBeNull();
911
914
 
912
915
  await stopServer();
913
916
  });
914
917
 
915
- test("two calls without conversationKey use the same conversation", async () => {
918
+ test("two empty-handed vellum sends each mint distinct conversations", async () => {
916
919
  await startServer(() => makeCompletingConversation());
917
920
 
918
- // First message — no conversationKey
919
921
  const res1 = await fetch(messagesUrl(), {
920
922
  method: "POST",
921
923
  headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
@@ -926,8 +928,8 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
926
928
  }),
927
929
  });
928
930
  expect(res1.status).toBe(202);
931
+ const body1 = (await res1.json()) as { conversationId: string };
929
932
 
930
- // Second message — same channel/interface, still no conversationKey
931
933
  const res2 = await fetch(messagesUrl(), {
932
934
  method: "POST",
933
935
  headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
@@ -938,12 +940,49 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
938
940
  }),
939
941
  });
940
942
  expect(res2.status).toBe(202);
943
+ const body2 = (await res2.json()) as { conversationId: string };
944
+
945
+ expect(body1.conversationId).toBeTruthy();
946
+ expect(body2.conversationId).toBeTruthy();
947
+ expect(body1.conversationId).not.toBe(body2.conversationId);
948
+
949
+ await stopServer();
950
+ });
951
+
952
+ test("two empty-handed phone sends share the default channel thread", async () => {
953
+ await startServer(() => makeCompletingConversation());
954
+
955
+ const res1 = await fetch(messagesUrl(), {
956
+ method: "POST",
957
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
958
+ body: JSON.stringify({
959
+ content: "First",
960
+ sourceChannel: "phone",
961
+ interface: "phone",
962
+ }),
963
+ });
964
+ expect(res1.status).toBe(202);
965
+ const body1 = (await res1.json()) as { conversationId: string };
966
+
967
+ const res2 = await fetch(messagesUrl(), {
968
+ method: "POST",
969
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
970
+ body: JSON.stringify({
971
+ content: "Second",
972
+ sourceChannel: "phone",
973
+ interface: "phone",
974
+ }),
975
+ });
976
+ expect(res2.status).toBe(202);
977
+ const body2 = (await res2.json()) as { conversationId: string };
941
978
 
942
- // Both should have resolved to the same default conversation key
943
- // ("default:vellum:macos"), which maps to the same conversationId.
944
- const mapping = getConversationByKey("default:vellum:macos");
979
+ // Non-vellum channels keep the legacy `default:<channel>:<interface>`
980
+ // co-location so repeated inbound messages from the same external
981
+ // channel/interface land on a single thread.
982
+ expect(body1.conversationId).toBe(body2.conversationId);
983
+ const mapping = getConversationByKey("default:phone:phone");
945
984
  expect(mapping).not.toBeNull();
946
- expect(mapping!.conversationId).toBeTruthy();
985
+ expect(mapping!.conversationId).toBe(body1.conversationId);
947
986
 
948
987
  await stopServer();
949
988
  });
@@ -578,7 +578,10 @@ describe("getAttachmentsForMessage", () => {
578
578
  db.run("DELETE FROM conversations");
579
579
  });
580
580
 
581
- async function createMessage(role: string, content: string): Promise<string> {
581
+ async function createMessage(
582
+ role: "user" | "assistant" | "system",
583
+ content: string,
584
+ ): Promise<string> {
582
585
  const conv = createConversation("test");
583
586
  const msg = await addMessage(conv.id, role, content);
584
587
  return msg.id;
@@ -7,17 +7,15 @@
7
7
  */
8
8
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
9
9
 
10
- import {
11
- _setOverridesForTesting,
12
- isAssistantFeatureFlagEnabled,
13
- } from "../config/assistant-feature-flags.js";
10
+ import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
11
+ import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
14
12
 
15
13
  beforeEach(() => {
16
- _setOverridesForTesting({});
14
+ setOverridesForTesting({});
17
15
  });
18
16
 
19
17
  afterEach(() => {
20
- _setOverridesForTesting({});
18
+ setOverridesForTesting({});
21
19
  });
22
20
  import type { AssistantConfig } from "../config/schema.js";
23
21
  import { resolveSkillStates, skillFlagKey } from "../config/skill-state.js";
@@ -150,7 +148,7 @@ describe("frontmatter feature-flag integration", () => {
150
148
  });
151
149
 
152
150
  test("resolveSkillStates includes skill with featureFlag when flag is ON", () => {
153
- _setOverridesForTesting({
151
+ setOverridesForTesting({
154
152
  "email-channel": true,
155
153
  });
156
154
  const skill = buildSkillSummary("email-setup", SKILL_MD_WITH_FLAG)!;
@@ -174,7 +172,7 @@ describe("frontmatter feature-flag integration", () => {
174
172
  test("resolveSkillStates never gates skill without featureFlag", () => {
175
173
  const skill = buildSkillSummary("plain-skill", SKILL_MD_WITHOUT_FLAG)!;
176
174
  // Even with an explicit false override for this skill ID, it should pass through
177
- _setOverridesForTesting({
175
+ setOverridesForTesting({
178
176
  "plain-skill": false,
179
177
  });
180
178
  const config = makeConfig();
@@ -210,7 +208,7 @@ describe("frontmatter feature-flag integration", () => {
210
208
  expect(resolvedDefault.length).toBe(0);
211
209
 
212
210
  // Step 6: With override enabled, skill passes through
213
- _setOverridesForTesting({ [key!]: true });
211
+ setOverridesForTesting({ [key!]: true });
214
212
  const configOn = makeConfig();
215
213
  expect(isAssistantFeatureFlagEnabled(key!, configOn)).toBe(true);
216
214
 
@@ -219,7 +217,7 @@ describe("frontmatter feature-flag integration", () => {
219
217
  expect(resolvedOn[0].summary.id).toBe("email-setup");
220
218
 
221
219
  // Step 7: With override disabled, skill is filtered out
222
- _setOverridesForTesting({ [key!]: false });
220
+ setOverridesForTesting({ [key!]: false });
223
221
  const configOff = makeConfig();
224
222
  expect(isAssistantFeatureFlagEnabled(key!, configOff)).toBe(false);
225
223
 
@@ -1,19 +1,17 @@
1
1
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
2
 
3
- import {
4
- _setOverridesForTesting,
5
- isAssistantFeatureFlagEnabled,
6
- } from "../config/assistant-feature-flags.js";
3
+ import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
7
4
  import type { AssistantConfig } from "../config/schema.js";
8
5
  import { resolveSkillStates, skillFlagKey } from "../config/skill-state.js";
9
6
  import type { SkillSummary } from "../config/skills.js";
7
+ import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
10
8
 
11
9
  beforeEach(() => {
12
- _setOverridesForTesting({});
10
+ setOverridesForTesting({});
13
11
  });
14
12
 
15
13
  afterEach(() => {
16
- _setOverridesForTesting({});
14
+ setOverridesForTesting({});
17
15
  });
18
16
 
19
17
  const DECLARED_FLAG_ID = "email-channel";
@@ -103,7 +101,7 @@ describe("isAssistantFeatureFlagEnabled with skillFlagKey", () => {
103
101
  });
104
102
 
105
103
  test("returns true when skill key is explicitly true", () => {
106
- _setOverridesForTesting({ [DECLARED_FLAG_KEY]: true });
104
+ setOverridesForTesting({ [DECLARED_FLAG_KEY]: true });
107
105
  const config = makeConfig();
108
106
  expect(
109
107
  isAssistantFeatureFlagEnabled(
@@ -114,7 +112,7 @@ describe("isAssistantFeatureFlagEnabled with skillFlagKey", () => {
114
112
  });
115
113
 
116
114
  test("returns false when skill key is explicitly false", () => {
117
- _setOverridesForTesting({ [DECLARED_FLAG_KEY]: false });
115
+ setOverridesForTesting({ [DECLARED_FLAG_KEY]: false });
118
116
  const config = makeConfig();
119
117
  expect(
120
118
  isAssistantFeatureFlagEnabled(
@@ -136,7 +134,7 @@ describe("isAssistantFeatureFlagEnabled", () => {
136
134
  });
137
135
 
138
136
  test("file-based override overrides registry default", () => {
139
- _setOverridesForTesting({ [DECLARED_FLAG_KEY]: false });
137
+ setOverridesForTesting({ [DECLARED_FLAG_KEY]: false });
140
138
  const config = makeConfig();
141
139
  expect(isAssistantFeatureFlagEnabled(DECLARED_FLAG_KEY, config)).toBe(
142
140
  false,
@@ -152,7 +150,7 @@ describe("isAssistantFeatureFlagEnabled", () => {
152
150
  });
153
151
 
154
152
  test("respects persisted overrides for undeclared keys", () => {
155
- _setOverridesForTesting({ "some-undeclared-flag": false });
153
+ setOverridesForTesting({ "some-undeclared-flag": false });
156
154
  const config = makeConfig();
157
155
  expect(isAssistantFeatureFlagEnabled("some-undeclared-flag", config)).toBe(
158
156
  false,
@@ -166,7 +164,7 @@ describe("isAssistantFeatureFlagEnabled", () => {
166
164
 
167
165
  describe("resolveSkillStates with feature flags", () => {
168
166
  test("flag OFF skill does not appear in resolved list", () => {
169
- _setOverridesForTesting({
167
+ setOverridesForTesting({
170
168
  [DECLARED_FLAG_KEY]: false,
171
169
  [ENABLED_UNDECLARED_FLAG_KEY]: true,
172
170
  });
@@ -188,7 +186,7 @@ describe("resolveSkillStates with feature flags", () => {
188
186
  });
189
187
 
190
188
  test("flag ON skill appears normally", () => {
191
- _setOverridesForTesting({
189
+ setOverridesForTesting({
192
190
  [DECLARED_FLAG_KEY]: true,
193
191
  [ENABLED_UNDECLARED_FLAG_KEY]: true,
194
192
  });
@@ -230,7 +228,7 @@ describe("resolveSkillStates with feature flags", () => {
230
228
  });
231
229
 
232
230
  test("feature flag OFF takes precedence over user-enabled config entry", () => {
233
- _setOverridesForTesting({ [DECLARED_FLAG_KEY]: false });
231
+ setOverridesForTesting({ [DECLARED_FLAG_KEY]: false });
234
232
  const catalog = [makeSkill(DECLARED_SKILL_ID, "bundled", DECLARED_FLAG_ID)];
235
233
  const config = makeConfig({
236
234
  skills: {
@@ -256,7 +254,7 @@ describe("resolveSkillStates with feature flags", () => {
256
254
  });
257
255
 
258
256
  test("multiple skills with mixed flags — persisted overrides respected", () => {
259
- _setOverridesForTesting({
257
+ setOverridesForTesting({
260
258
  [DECLARED_FLAG_KEY]: false,
261
259
  [ENABLED_UNDECLARED_FLAG_KEY]: true,
262
260
  deploy: false,
@@ -296,7 +294,7 @@ describe("resolveSkillStates with frontmatter featureFlag", () => {
296
294
  });
297
295
 
298
296
  test("skill with featureFlag is included when override enables it", () => {
299
- _setOverridesForTesting({ [DECLARED_FLAG_KEY]: true });
297
+ setOverridesForTesting({ [DECLARED_FLAG_KEY]: true });
300
298
  const catalog = [makeSkill(DECLARED_SKILL_ID, "bundled", DECLARED_FLAG_ID)];
301
299
  const config = makeConfig();
302
300
 
@@ -320,7 +318,7 @@ describe("resolveSkillStates with frontmatter featureFlag", () => {
320
318
  // This proves the implicit skillId→flag mapping is gone:
321
319
  // setting feature_flags.my-skill.enabled = false has no effect
322
320
  // when the skill itself does not declare a featureFlag.
323
- _setOverridesForTesting({
321
+ setOverridesForTesting({
324
322
  "my-skill": false,
325
323
  });
326
324
  const catalog = [makeSkill("my-skill")];
@@ -6,7 +6,7 @@ import { mkdirSync, writeFileSync } from "node:fs";
6
6
  import { join } from "node:path";
7
7
  import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
8
8
 
9
- import { _setOverridesForTesting } from "../config/assistant-feature-flags.js";
9
+ import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
10
10
 
11
11
  const TEST_DIR = process.env.VELLUM_WORKSPACE_DIR!;
12
12
 
@@ -89,11 +89,11 @@ describe("skill_load feature flag enforcement", () => {
89
89
  beforeEach(() => {
90
90
  mkdirSync(join(TEST_DIR, "skills"), { recursive: true });
91
91
  currentConfig = {};
92
- _setOverridesForTesting({});
92
+ setOverridesForTesting({});
93
93
  });
94
94
 
95
95
  afterEach(() => {
96
- _setOverridesForTesting({});
96
+ setOverridesForTesting({});
97
97
  });
98
98
 
99
99
  test("returns deterministic error for flag OFF skill", async () => {
@@ -104,7 +104,7 @@ describe("skill_load feature flag enforcement", () => {
104
104
  "Use the feature.",
105
105
  );
106
106
 
107
- _setOverridesForTesting({ [DECLARED_FLAG_KEY]: false });
107
+ setOverridesForTesting({ [DECLARED_FLAG_KEY]: false });
108
108
 
109
109
  const result = await executeSkillLoad({ skill: DECLARED_SKILL_ID });
110
110
 
@@ -121,7 +121,7 @@ describe("skill_load feature flag enforcement", () => {
121
121
  "Use the feature.",
122
122
  );
123
123
 
124
- _setOverridesForTesting({ [DECLARED_FLAG_KEY]: true });
124
+ setOverridesForTesting({ [DECLARED_FLAG_KEY]: true });
125
125
 
126
126
  const result = await executeSkillLoad({ skill: DECLARED_SKILL_ID });
127
127
 
@@ -45,6 +45,10 @@ mock.module("../config/skill-state.js", () => ({
45
45
 
46
46
  // Mock assistant-feature-flags to avoid loading the real module (which
47
47
  // triggers file I/O and env-registry imports that hang in test context).
48
+ // The seed-state backdoor is the test-only helper module — we mirror
49
+ // production's design: tests reach into `feature-flag-cache.ts` (or its
50
+ // test-helper wrapper) to seed cached overrides, never through the
51
+ // resolver module itself.
48
52
  let _mockOverrides: Record<string, boolean> = {};
49
53
  mock.module("../config/assistant-feature-flags.js", () => ({
50
54
  isAssistantFeatureFlagEnabled: (key: string, _config: unknown): boolean => {
@@ -55,10 +59,12 @@ mock.module("../config/assistant-feature-flags.js", () => ({
55
59
  clearFeatureFlagOverridesCache: () => {
56
60
  _mockOverrides = {};
57
61
  },
58
- _setOverridesForTesting: (overrides: Record<string, boolean>) => {
62
+ getAssistantFeatureFlagDefaults: () => ({}),
63
+ }));
64
+ mock.module("./feature-flag-test-helpers.js", () => ({
65
+ setOverridesForTesting: (overrides: Record<string, boolean>) => {
59
66
  _mockOverrides = { ...overrides };
60
67
  },
61
- getAssistantFeatureFlagDefaults: () => ({}),
62
68
  }));
63
69
 
64
70
  mock.module("../skills/active-skill-tools.js", () => {
@@ -109,12 +115,13 @@ mock.module("../skills/tool-manifest.js", () => ({
109
115
  }));
110
116
 
111
117
  mock.module("../tools/skills/skill-tool-factory.js", () => ({
118
+ // Mirrors the real factory: no skillId in/out — ownership is recorded by
119
+ // the registry at `registerSkillTools(skillId, tools)` time.
112
120
  createSkillToolsFromManifest: (
113
121
  entries: SkillToolManifest["tools"],
114
- skillId: string,
115
122
  _skillDir: string,
116
- versionHash: string,
117
- bundled?: boolean,
123
+ _versionHash: string,
124
+ _bundled?: boolean,
118
125
  ): Tool[] => {
119
126
  return entries.map((entry) => ({
120
127
  name: entry.name,
@@ -122,10 +129,6 @@ mock.module("../tools/skills/skill-tool-factory.js", () => ({
122
129
  category: entry.category,
123
130
  defaultRiskLevel: RiskLevel.Medium,
124
131
  executionTarget: "sandbox" as const,
125
- origin: "skill" as const,
126
- ownerSkillId: skillId,
127
- ownerSkillVersionHash: versionHash,
128
- ownerSkillBundled: bundled ?? undefined,
129
132
  input_schema: entry.input_schema as object,
130
133
  execute: async () => ({ content: "", isError: false }),
131
134
  }));
@@ -133,18 +136,14 @@ mock.module("../tools/skills/skill-tool-factory.js", () => ({
133
136
  }));
134
137
 
135
138
  mock.module("../tools/registry.js", () => ({
136
- registerSkillTools: (tools: Tool[]) => {
137
- const skillIds = new Set<string>();
138
- for (const tool of tools) {
139
- const skillId = tool.ownerSkillId!;
140
- skillIds.add(skillId);
141
- const existing = mockRegisteredTools.get(skillId) ?? [];
142
- existing.push(tool);
143
- mockRegisteredTools.set(skillId, existing);
144
- }
145
- for (const id of skillIds) {
146
- mockSkillRefCount.set(id, (mockSkillRefCount.get(id) ?? 0) + 1);
147
- }
139
+ // Matches the new signature: `registerSkillTools(skillId, tools)`. The
140
+ // skillId comes from the caller (conversation-skill-tools) and is the
141
+ // sole source of truth for ownership.
142
+ registerSkillTools: (skillId: string, tools: Tool[]) => {
143
+ const existing = mockRegisteredTools.get(skillId) ?? [];
144
+ existing.push(...tools);
145
+ mockRegisteredTools.set(skillId, existing);
146
+ mockSkillRefCount.set(skillId, (mockSkillRefCount.get(skillId) ?? 0) + 1);
148
147
  return tools;
149
148
  },
150
149
  unregisterSkillTools: (skillId: string) => {
@@ -166,6 +165,23 @@ mock.module("../tools/registry.js", () => ({
166
165
  }
167
166
  return found;
168
167
  },
168
+ // Mirrors the registry's `ownersByName` accessor: derives the owning
169
+ // skillId from `mockRegisteredTools` keying so the production
170
+ // `getToolOwner(name)` call in `conversation-skill-tools.ts` resolves to
171
+ // the same shape the real registry would return.
172
+ getToolOwner: (
173
+ name: string,
174
+ ): { kind: "skill" | "plugin" | "mcp"; id: string } | undefined => {
175
+ let ownerSkillId: string | undefined;
176
+ for (const [skillId, tools] of mockRegisteredTools.entries()) {
177
+ for (const tool of tools) {
178
+ if (tool.name === name) ownerSkillId = skillId;
179
+ }
180
+ }
181
+ return ownerSkillId === undefined
182
+ ? undefined
183
+ : { kind: "skill", id: ownerSkillId };
184
+ },
169
185
  getSkillToolNames: () => {
170
186
  const names: string[] = [];
171
187
  for (const tools of mockRegisteredTools.values()) {
@@ -212,10 +228,8 @@ mock.module("../util/logger.js", () => ({
212
228
 
213
229
  const { projectSkillTools, resetSkillToolProjection } =
214
230
  await import("../daemon/conversation-skill-tools.js");
215
- const { _setOverridesForTesting } =
216
- (await import("../config/assistant-feature-flags.js")) as {
217
- _setOverridesForTesting: (o: Record<string, boolean>) => void;
218
- };
231
+ const { setOverridesForTesting } =
232
+ await import("./feature-flag-test-helpers.js");
219
233
 
220
234
  // ---------------------------------------------------------------------------
221
235
  // Helpers
@@ -289,12 +303,12 @@ describe("projectSkillTools feature flag enforcement", () => {
289
303
  mockUnregisteredSkillIds = [];
290
304
  mockSkillRefCount = new Map();
291
305
  currentConfig = {};
292
- _setOverridesForTesting({});
306
+ setOverridesForTesting({});
293
307
  resetSkillToolProjection();
294
308
  });
295
309
 
296
310
  afterEach(() => {
297
- _setOverridesForTesting({});
311
+ setOverridesForTesting({});
298
312
  });
299
313
 
300
314
  test("no skill tools projected for flag OFF skill even with old markers", () => {
@@ -308,7 +322,7 @@ describe("projectSkillTools feature flag enforcement", () => {
308
322
  const prevActive = new Map<string, string>();
309
323
 
310
324
  // Feature flag is OFF — use protected directory override
311
- _setOverridesForTesting({ [DECLARED_FLAG_KEY]: false });
325
+ setOverridesForTesting({ [DECLARED_FLAG_KEY]: false });
312
326
 
313
327
  const result = projectSkillTools(history, {
314
328
  previouslyActiveSkillIds: prevActive,
@@ -329,7 +343,7 @@ describe("projectSkillTools feature flag enforcement", () => {
329
343
  const prevActive = new Map<string, string>();
330
344
 
331
345
  // Feature flag is ON — use protected directory override
332
- _setOverridesForTesting({ [DECLARED_FLAG_KEY]: true });
346
+ setOverridesForTesting({ [DECLARED_FLAG_KEY]: true });
333
347
 
334
348
  const result = projectSkillTools(history, {
335
349
  previouslyActiveSkillIds: prevActive,
@@ -420,7 +434,7 @@ describe("projectSkillTools feature flag enforcement", () => {
420
434
  const prevActive = new Map<string, string>();
421
435
 
422
436
  // Declared skill is OFF; plain-skill has no featureFlag so remains ON.
423
- _setOverridesForTesting({ [DECLARED_FLAG_KEY]: false });
437
+ setOverridesForTesting({ [DECLARED_FLAG_KEY]: false });
424
438
 
425
439
  const result = projectSkillTools(history, {
426
440
  previouslyActiveSkillIds: prevActive,
@@ -102,18 +102,15 @@ mock.module("../tools/skills/skill-tool-factory.js", () => ({
102
102
  entries: Array<{ name: string; description: string; input_schema: object }>,
103
103
  skillId: string,
104
104
  _skillDir: string,
105
- versionHash: string,
106
- bundled?: boolean,
105
+ _versionHash: string,
106
+ _bundled?: boolean,
107
107
  ) =>
108
108
  entries.map((e) => ({
109
109
  name: e.name,
110
110
  description: e.description,
111
111
  category: "skill",
112
112
  defaultRiskLevel: "low",
113
- origin: "skill" as const,
114
- ownerSkillId: skillId,
115
- ownerSkillVersionHash: versionHash,
116
- ownerSkillBundled: bundled,
113
+ owner: { kind: "skill" as const, id: skillId },
117
114
  input_schema: e.input_schema,
118
115
  execute: async () => ({ content: "", isError: false }),
119
116
  })),
@@ -166,7 +163,8 @@ mock.module("../tools/registry.js", () => ({
166
163
  },
167
164
  unregisterSkillTools: (skillId: string) => {
168
165
  for (const [name, t] of benchmarkRegistry) {
169
- if ((t as { ownerSkillId?: string }).ownerSkillId === skillId)
166
+ const owner = (t as { owner?: { kind: string; id: string } }).owner;
167
+ if (owner?.kind === "skill" && owner.id === skillId)
170
168
  benchmarkRegistry.delete(name);
171
169
  }
172
170
  },