@vellumai/assistant 0.10.1 → 0.10.2-dev.202606241651.2d2b40d

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 (367) hide show
  1. package/docs/workspace-tools.md +42 -33
  2. package/eslint-rules/cli-no-daemon-internals.js +6 -0
  3. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
  5. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
  6. package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
  7. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
  8. package/openapi.yaml +74 -1
  9. package/package.json +1 -1
  10. package/scripts/test.sh +36 -15
  11. package/src/__tests__/actor-token-service.test.ts +36 -14
  12. package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
  13. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  14. package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
  15. package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
  16. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  17. package/src/__tests__/approval-cascade.test.ts +2 -0
  18. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
  19. package/src/__tests__/btw-routes.test.ts +2 -0
  20. package/src/__tests__/build-persisted-content.test.ts +2 -0
  21. package/src/__tests__/call-controller.test.ts +19 -0
  22. package/src/__tests__/channel-guardian.test.ts +94 -58
  23. package/src/__tests__/channel-reply-delivery.test.ts +2 -0
  24. package/src/__tests__/compaction-events.test.ts +2 -0
  25. package/src/__tests__/compaction.benchmark.test.ts +2 -0
  26. package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
  27. package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
  28. package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
  29. package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
  30. package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
  31. package/src/__tests__/computer-use-tools.test.ts +13 -0
  32. package/src/__tests__/config-loader-backfill.test.ts +5 -1
  33. package/src/__tests__/config-schema.test.ts +1 -0
  34. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
  35. package/src/__tests__/contacts-relay-reads.test.ts +13 -15
  36. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  37. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
  38. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  39. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
  40. package/src/__tests__/conversation-agent-loop.test.ts +7 -0
  41. package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
  42. package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
  43. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
  44. package/src/__tests__/conversation-history-web-search.test.ts +2 -0
  45. package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
  46. package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
  47. package/src/__tests__/conversation-pairing.test.ts +2 -0
  48. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  49. package/src/__tests__/conversation-process-callsite.test.ts +2 -0
  50. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  51. package/src/__tests__/conversation-queue.test.ts +91 -0
  52. package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
  53. package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
  54. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  55. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  56. package/src/__tests__/conversation-speed-override.test.ts +2 -0
  57. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +65 -0
  58. package/src/__tests__/conversation-title-service.test.ts +2 -0
  59. package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
  60. package/src/__tests__/conversation-usage.test.ts +2 -0
  61. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  64. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  65. package/src/__tests__/db-migration-rollback.test.ts +205 -171
  66. package/src/__tests__/db-test-helpers.ts +5 -4
  67. package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
  68. package/src/__tests__/disk-pressure-guard.test.ts +41 -0
  69. package/src/__tests__/dm-persistence.test.ts +2 -0
  70. package/src/__tests__/emit-signal-routing-intent.test.ts +10 -5
  71. package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
  72. package/src/__tests__/filing-service.test.ts +2 -0
  73. package/src/__tests__/guardian-binding-drift-heal.test.ts +75 -10
  74. package/src/__tests__/guardian-dispatch.test.ts +95 -1
  75. package/src/__tests__/guardian-outbound-http.test.ts +13 -0
  76. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  77. package/src/__tests__/heartbeat-service.test.ts +2 -0
  78. package/src/__tests__/helpers/channel-test-adapter.ts +1 -7
  79. package/src/__tests__/host-app-control-routes.test.ts +24 -30
  80. package/src/__tests__/host-bash-routes.test.ts +31 -41
  81. package/src/__tests__/host-browser-routes.test.ts +26 -32
  82. package/src/__tests__/host-cu-proxy.test.ts +299 -0
  83. package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
  84. package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
  85. package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
  86. package/src/__tests__/http-user-message-parity.test.ts +167 -8
  87. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  88. package/src/__tests__/invite-redemption-service.test.ts +43 -0
  89. package/src/__tests__/llm-context-normalization.test.ts +105 -0
  90. package/src/__tests__/llm-usage-store.test.ts +25 -0
  91. package/src/__tests__/media-stream-server-integration.test.ts +127 -0
  92. package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
  93. package/src/__tests__/messaging-send-tool.test.ts +2 -0
  94. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  95. package/src/__tests__/native-web-search.test.ts +2 -0
  96. package/src/__tests__/non-member-access-request.test.ts +189 -17
  97. package/src/__tests__/notification-broadcaster.test.ts +4 -0
  98. package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
  99. package/src/__tests__/notification-deep-link.test.ts +6 -0
  100. package/src/__tests__/notification-guardian-path.test.ts +19 -0
  101. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  102. package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
  103. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  104. package/src/__tests__/plugin-bootstrap.test.ts +3 -73
  105. package/src/__tests__/plugin-route-contribution.test.ts +4 -17
  106. package/src/__tests__/plugin-tool-contribution.test.ts +3 -18
  107. package/src/__tests__/plugin-types.test.ts +0 -2
  108. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  109. package/src/__tests__/process-message-display-content.test.ts +2 -0
  110. package/src/__tests__/provider-usage-tracking.test.ts +39 -0
  111. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
  112. package/src/__tests__/registry.test.ts +3 -0
  113. package/src/__tests__/relay-server.test.ts +694 -25
  114. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  115. package/src/__tests__/secret-ingress-http.test.ts +14 -0
  116. package/src/__tests__/send-endpoint-busy.test.ts +30 -8
  117. package/src/__tests__/skills.test.ts +44 -0
  118. package/src/__tests__/slack-inbound-verification.test.ts +47 -2
  119. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +102 -0
  120. package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
  121. package/src/__tests__/stt-hints.test.ts +44 -13
  122. package/src/__tests__/subagent-detail.test.ts +27 -0
  123. package/src/__tests__/subagent-disposal.test.ts +65 -0
  124. package/src/__tests__/subagent-notify-parent.test.ts +2 -0
  125. package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
  126. package/src/__tests__/subagent-tools.test.ts +2 -0
  127. package/src/__tests__/suggestion-routes.test.ts +2 -0
  128. package/src/__tests__/title-generate-hook.test.ts +2 -0
  129. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
  130. package/src/__tests__/tool-executor.test.ts +16 -11
  131. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  132. package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
  133. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  134. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +10 -10
  135. package/src/__tests__/twilio-routes.test.ts +96 -0
  136. package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
  137. package/src/__tests__/web-search-backend-failure.test.ts +2 -0
  138. package/src/__tests__/workspace-tool-loader.test.ts +195 -2
  139. package/src/agent/loop-exclusive-tool.test.ts +150 -0
  140. package/src/agent/loop.ts +56 -0
  141. package/src/api/constants/sse-replay.ts +41 -0
  142. package/src/api/index.ts +6 -0
  143. package/src/api/responses/llm-request-log-entry.ts +25 -0
  144. package/src/api/responses/subagent-detail.ts +17 -0
  145. package/src/calls/__tests__/relay-setup-router.test.ts +262 -4
  146. package/src/calls/call-domain.ts +3 -3
  147. package/src/calls/guardian-dispatch.ts +10 -8
  148. package/src/calls/inbound-trust-reader.ts +17 -1
  149. package/src/calls/media-stream-server.ts +21 -0
  150. package/src/calls/relay-server.ts +167 -50
  151. package/src/calls/relay-setup-router.ts +37 -7
  152. package/src/calls/relay-verification.ts +4 -4
  153. package/src/calls/stt-hints.ts +9 -12
  154. package/src/calls/twilio-routes.ts +14 -4
  155. package/src/cli/commands/__tests__/cache.test.ts +8 -1
  156. package/src/cli/commands/cache.ts +194 -181
  157. package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
  158. package/src/cli/commands/db/status.ts +37 -1
  159. package/src/cli/commands/mcp.ts +252 -218
  160. package/src/cli/commands/memory/__tests__/worker.test.ts +302 -0
  161. package/src/cli/commands/memory/index.ts +2 -0
  162. package/src/cli/commands/memory/worker.ts +175 -0
  163. package/src/cli/commands/plugins.ts +75 -3
  164. package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
  165. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
  166. package/src/cli/lib/list-installed-plugins.ts +179 -1
  167. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
  168. package/src/config/bundled-skills/computer-use/TOOLS.json +6 -1
  169. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
  170. package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
  171. package/src/config/feature-flag-registry.json +0 -8
  172. package/src/config/loader.ts +36 -5
  173. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  174. package/src/config/schemas/memory-lifecycle.ts +12 -0
  175. package/src/config/schemas/memory-v3.ts +7 -0
  176. package/src/config/schemas/memory.ts +4 -0
  177. package/src/config/schemas/timeouts.ts +8 -0
  178. package/src/config/seed-inference-profiles.ts +14 -5
  179. package/src/config/skills.ts +27 -5
  180. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
  181. package/src/contacts/contacts-write.ts +3 -0
  182. package/src/contacts/guardian-delivery-reader.ts +223 -0
  183. package/src/daemon/conversation-agent-loop.ts +9 -0
  184. package/src/daemon/conversation-process.ts +39 -17
  185. package/src/daemon/conversation-surfaces.ts +8 -0
  186. package/src/daemon/conversation-tool-setup.ts +49 -16
  187. package/src/daemon/conversation.ts +21 -2
  188. package/src/daemon/disk-pressure-guard.ts +12 -2
  189. package/src/daemon/event-loop-watchdog.ts +28 -1
  190. package/src/daemon/external-plugins-bootstrap.ts +4 -34
  191. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -0
  192. package/src/daemon/handlers/__tests__/config-channels.test.ts +225 -0
  193. package/src/daemon/handlers/config-a2a.ts +6 -14
  194. package/src/daemon/handlers/config-channels.ts +78 -22
  195. package/src/daemon/handlers/conversations.ts +77 -0
  196. package/src/daemon/host-cu-proxy.ts +102 -11
  197. package/src/daemon/lifecycle.ts +4 -0
  198. package/src/daemon/memory-v2-startup.test.ts +72 -0
  199. package/src/daemon/memory-v2-startup.ts +87 -19
  200. package/src/daemon/server.ts +0 -4
  201. package/src/daemon/shutdown-handlers.ts +20 -0
  202. package/src/daemon/tool-setup-types.ts +9 -0
  203. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  204. package/src/ipc/assistant-server.ts +2 -2
  205. package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
  206. package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
  207. package/src/memory/__tests__/prompt-override.test.ts +192 -0
  208. package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
  209. package/src/memory/conversation-crud.ts +38 -0
  210. package/src/memory/db-connection.ts +22 -3
  211. package/src/memory/db-init.ts +36 -502
  212. package/src/memory/db-singleton.ts +6 -4
  213. package/src/memory/jobs-worker.ts +58 -0
  214. package/src/memory/llm-usage-store.ts +48 -20
  215. package/src/memory/memory-retrospective-job.ts +9 -8
  216. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +13 -3
  217. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -27
  218. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +130 -56
  219. package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
  220. package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
  221. package/src/memory/migrations/__tests__/014-backfill-inbox-thread-state.test.ts +108 -0
  222. package/src/memory/migrations/__tests__/136-drop-assistant-id-columns.test.ts +82 -0
  223. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +224 -0
  224. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  225. package/src/memory/migrations/run-migrations.ts +90 -6
  226. package/src/memory/migrations/schema-introspection.ts +14 -0
  227. package/src/memory/migrations/validate-migration-state.ts +101 -66
  228. package/src/memory/prompt-override.ts +129 -0
  229. package/src/memory/schema/conversations.ts +9 -0
  230. package/src/memory/schema/infrastructure.ts +20 -0
  231. package/src/memory/steps.ts +573 -0
  232. package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
  233. package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
  234. package/src/memory/v2/cli-command-store.ts +75 -38
  235. package/src/memory/v2/prompts/consolidation.ts +13 -82
  236. package/src/memory/v2/prompts/router.ts +21 -93
  237. package/src/memory/v2/skill-store.ts +68 -31
  238. package/src/memory/watchdog-events-store.ts +87 -0
  239. package/src/memory/worker-control.ts +118 -0
  240. package/src/memory/worker-process.ts +72 -0
  241. package/src/notifications/__tests__/broadcaster.test.ts +16 -8
  242. package/src/notifications/__tests__/connected-channels.test.ts +114 -0
  243. package/src/notifications/__tests__/decision-engine.test.ts +78 -9
  244. package/src/notifications/__tests__/destination-resolver.test.ts +256 -0
  245. package/src/notifications/broadcaster.ts +8 -1
  246. package/src/notifications/decision-engine.ts +15 -7
  247. package/src/notifications/destination-resolver.ts +68 -24
  248. package/src/notifications/emit-signal.ts +39 -14
  249. package/src/onboarding/checkin-event.test.ts +220 -0
  250. package/src/onboarding/checkin-event.ts +321 -0
  251. package/src/onboarding/schedule-checkin.ts +190 -0
  252. package/src/permissions/question-prompter.test.ts +1 -1
  253. package/src/permissions/question-prompter.ts +7 -4
  254. package/src/plugin-api/index.ts +6 -6
  255. package/src/plugin-api/types.ts +3 -5
  256. package/src/plugin-api/vision-support.test.ts +28 -4
  257. package/src/plugin-api/vision-support.ts +66 -31
  258. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +161 -0
  259. package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +106 -0
  260. package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +60 -0
  261. package/src/plugins/defaults/advisor/consult.ts +110 -6
  262. package/src/plugins/defaults/advisor/context-pack.ts +288 -0
  263. package/src/plugins/defaults/advisor/steering.ts +14 -2
  264. package/src/plugins/defaults/advisor/tools/advisor.ts +32 -5
  265. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
  266. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +10 -11
  267. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +12 -20
  268. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
  269. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
  270. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
  271. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +29 -1
  272. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +8 -1
  273. package/src/plugins/mtime-cache.ts +7 -2
  274. package/src/plugins/types.ts +0 -2
  275. package/src/providers/anthropic/client.ts +5 -0
  276. package/src/providers/call-site-routing.ts +4 -0
  277. package/src/providers/model-catalog.ts +16 -0
  278. package/src/providers/openai/responses-provider.ts +5 -0
  279. package/src/providers/openrouter/client.ts +5 -0
  280. package/src/providers/provider-send-message.ts +4 -0
  281. package/src/providers/ratelimit.ts +4 -0
  282. package/src/providers/retry.ts +4 -0
  283. package/src/providers/types.ts +9 -0
  284. package/src/providers/usage-tracking.ts +4 -0
  285. package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
  286. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +181 -0
  287. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
  288. package/src/runtime/__tests__/local-principal-trust.test.ts +164 -0
  289. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +335 -3
  290. package/src/runtime/access-request-helper.ts +19 -39
  291. package/src/runtime/actor-trust-resolver.ts +2 -2
  292. package/src/runtime/anchored-guardian.test.ts +156 -0
  293. package/src/runtime/anchored-guardian.ts +135 -0
  294. package/src/runtime/assistant-event-hub.ts +1 -1
  295. package/src/runtime/assistant-stream-state.ts +9 -2
  296. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
  297. package/src/runtime/auth/require-bound-guardian.ts +21 -11
  298. package/src/runtime/channel-verification-service.ts +56 -31
  299. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  300. package/src/runtime/guardian-vellum-migration.ts +66 -7
  301. package/src/runtime/invite-redemption-service.ts +50 -18
  302. package/src/runtime/local-actor-identity.ts +76 -11
  303. package/src/runtime/local-principal-trust.ts +52 -0
  304. package/src/runtime/pending-interactions.ts +11 -1
  305. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
  306. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  307. package/src/runtime/routes/__tests__/contact-routes.test.ts +212 -0
  308. package/src/runtime/routes/__tests__/global-search-routes.test.ts +93 -0
  309. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +215 -1
  310. package/src/runtime/routes/browser-routes.ts +1 -1
  311. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  312. package/src/runtime/routes/contact-routes.ts +8 -32
  313. package/src/runtime/routes/conversation-cli-routes.ts +4 -5
  314. package/src/runtime/routes/conversation-list-routes.ts +4 -7
  315. package/src/runtime/routes/conversation-routes.ts +74 -81
  316. package/src/runtime/routes/events-routes.ts +2 -2
  317. package/src/runtime/routes/global-search-routes.ts +3 -1
  318. package/src/runtime/routes/guardian-action-routes.ts +4 -5
  319. package/src/runtime/routes/host-app-control-routes.ts +5 -4
  320. package/src/runtime/routes/host-bash-routes.ts +5 -4
  321. package/src/runtime/routes/host-browser-routes.ts +9 -11
  322. package/src/runtime/routes/host-cu-routes.ts +5 -4
  323. package/src/runtime/routes/host-file-routes.ts +5 -4
  324. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  325. package/src/runtime/routes/http-adapter.ts +1 -1
  326. package/src/runtime/routes/identity-routes.ts +3 -2
  327. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  328. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
  329. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -49
  330. package/src/runtime/routes/inbound-stages/background-dispatch.ts +16 -4
  331. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  332. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
  333. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
  334. package/src/runtime/routes/index.ts +2 -0
  335. package/src/runtime/routes/llm-context-normalization.ts +71 -0
  336. package/src/runtime/routes/mcp-auth-routes.ts +38 -15
  337. package/src/runtime/routes/migration-rollback-routes.ts +4 -3
  338. package/src/runtime/routes/migration-routes.ts +4 -1
  339. package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
  340. package/src/runtime/routes/subagents-routes.ts +5 -0
  341. package/src/runtime/routes/surface-action-routes.ts +51 -55
  342. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  343. package/src/runtime/services/conversation-serializer.ts +7 -9
  344. package/src/runtime/tool-grant-request-helper.ts +3 -3
  345. package/src/runtime/trust-verdict-consumer.ts +85 -9
  346. package/src/runtime/verification-outbound-actions.ts +18 -18
  347. package/src/signals/user-message.ts +16 -0
  348. package/src/subagent/manager.ts +9 -0
  349. package/src/telemetry/types.ts +34 -1
  350. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  351. package/src/telemetry/usage-telemetry-reporter.ts +87 -3
  352. package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
  353. package/src/tools/ask-question/ask-question-tool.ts +13 -0
  354. package/src/tools/computer-use/definitions.ts +8 -2
  355. package/src/tools/executor.ts +4 -4
  356. package/src/tools/registry.ts +18 -0
  357. package/src/tools/tool-approval-handler.ts +1 -1
  358. package/src/tools/tool-defaults.ts +9 -2
  359. package/src/tools/types.ts +17 -2
  360. package/src/tools/workspace-tools/loader.ts +348 -244
  361. package/src/util/platform.ts +5 -0
  362. package/src/util/telemetry-db-path.ts +24 -0
  363. package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
  364. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
  365. package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
  366. package/src/daemon/workspace-tools-watcher.ts +0 -328
  367. package/src/memory/migrations/registry.ts +0 -573
@@ -36,17 +36,36 @@ mock.module("../prompts/system-prompt.js", () => ({
36
36
  buildCoreIdentityContext: () => null,
37
37
  }));
38
38
 
39
- // ── Guardian contact mock ────────────────────────────────────────────
39
+ // ── Guardian binding + contact notes mocks ───────────────────────────
40
40
 
41
- let mockGuardianResult: {
42
- contact: { notes: string | null };
43
- channels: Record<string, unknown>[];
44
- } | null = null;
41
+ // Guardian binding (ACL) is resolved via the gateway pull; notes (INFO) are
42
+ // joined locally by contactId. Tests drive both via mutable slots.
43
+ let guardianDeliveryFixture: Array<{ contactId: string }> = [];
44
+ let contactInfoFixture: Record<string, { notes: string | null } | null> = {};
45
+
46
+ mock.module("../contacts/guardian-delivery-reader.js", () => ({
47
+ getGuardianDelivery: async () => guardianDeliveryFixture,
48
+ anyGuardian: (list: Array<{ contactId: string }>) => list[0],
49
+ }));
45
50
 
46
51
  mock.module("../contacts/contact-store.js", () => ({
47
- listGuardianChannels: () => mockGuardianResult,
52
+ findContactInfoById: (contactId: string) =>
53
+ contactInfoFixture[contactId] ?? null,
48
54
  }));
49
55
 
56
+ const GUARDIAN_CONTACT_ID = "guardian-contact-1";
57
+
58
+ /** Bind a guardian with the given notes (or no guardian when notes is null). */
59
+ function setGuardian(notes: string | null | undefined): void {
60
+ if (notes === undefined) {
61
+ guardianDeliveryFixture = [];
62
+ contactInfoFixture = {};
63
+ return;
64
+ }
65
+ guardianDeliveryFixture = [{ contactId: GUARDIAN_CONTACT_ID }];
66
+ contactInfoFixture = { [GUARDIAN_CONTACT_ID]: { notes } };
67
+ }
68
+
50
69
  // ── Provider mock with system prompt capture ──────────────────────────
51
70
 
52
71
  let configuredProvider: {
@@ -134,15 +153,12 @@ describe("recipient context in notification decision engine", () => {
134
153
  beforeEach(() => {
135
154
  configuredProvider = null;
136
155
  extractedToolUse = null;
137
- mockGuardianResult = null;
156
+ setGuardian(undefined);
138
157
  capturedSystemPrompt = undefined;
139
158
  });
140
159
 
141
160
  test("guardian contact notes appear in system prompt as <recipient-context>", async () => {
142
- mockGuardianResult = {
143
- contact: { notes: "Prefers formal tone. Address as Dr. Smith." },
144
- channels: [{ type: "vellum" }],
145
- };
161
+ setGuardian("Prefers formal tone. Address as Dr. Smith.");
146
162
  setupLLMProvider();
147
163
 
148
164
  const signal = makeSignal();
@@ -157,7 +173,7 @@ describe("recipient context in notification decision engine", () => {
157
173
  });
158
174
 
159
175
  test("recipient-context is omitted when no guardian exists", async () => {
160
- mockGuardianResult = null;
176
+ setGuardian(undefined);
161
177
  setupLLMProvider();
162
178
 
163
179
  const signal = makeSignal();
@@ -169,10 +185,7 @@ describe("recipient context in notification decision engine", () => {
169
185
  });
170
186
 
171
187
  test("recipient-context is omitted when guardian notes are null", async () => {
172
- mockGuardianResult = {
173
- contact: { notes: null },
174
- channels: [{ type: "vellum" }],
175
- };
188
+ setGuardian(null);
176
189
  setupLLMProvider();
177
190
 
178
191
  const signal = makeSignal();
@@ -184,10 +197,7 @@ describe("recipient context in notification decision engine", () => {
184
197
  });
185
198
 
186
199
  test("recipient-context is omitted when guardian notes are empty string", async () => {
187
- mockGuardianResult = {
188
- contact: { notes: "" },
189
- channels: [{ type: "vellum" }],
190
- };
200
+ setGuardian("");
191
201
  setupLLMProvider();
192
202
 
193
203
  const signal = makeSignal();
@@ -199,10 +209,7 @@ describe("recipient context in notification decision engine", () => {
199
209
  });
200
210
 
201
211
  test("large guardian notes are truncated to prevent oversized prompts", async () => {
202
- mockGuardianResult = {
203
- contact: { notes: "N".repeat(3000) },
204
- channels: [{ type: "vellum" }],
205
- };
212
+ setGuardian("N".repeat(3000));
206
213
  setupLLMProvider();
207
214
 
208
215
  const signal = makeSignal();
@@ -226,10 +233,7 @@ describe("recipient context in notification decision engine", () => {
226
233
  });
227
234
 
228
235
  test("fallback path works correctly without recipient context", async () => {
229
- mockGuardianResult = {
230
- contact: { notes: "Prefers formal tone." },
231
- channels: [{ type: "vellum" }],
232
- };
236
+ setGuardian("Prefers formal tone.");
233
237
  // null provider forces fallback path
234
238
  configuredProvider = null;
235
239
 
@@ -247,10 +251,7 @@ describe("recipient context in notification decision engine", () => {
247
251
  });
248
252
 
249
253
  test("recipient-context appears after user-preferences in prompt", async () => {
250
- mockGuardianResult = {
251
- contact: { notes: "Prefers brief updates." },
252
- channels: [{ type: "vellum" }],
253
- };
254
+ setGuardian("Prefers brief updates.");
254
255
  setupLLMProvider();
255
256
 
256
257
  const signal = makeSignal();
@@ -20,6 +20,10 @@ mock.module("../util/logger.js", () => ({
20
20
  }),
21
21
  }));
22
22
 
23
+ mock.module("../contacts/guardian-delivery-reader.js", () => ({
24
+ getGuardianDelivery: async () => null,
25
+ }));
26
+
23
27
  // Mock destination-resolver for broadcaster tests
24
28
  mock.module("../notifications/destination-resolver.js", () => ({
25
29
  resolveDestinations: (channels: string[]) => {
@@ -42,6 +46,8 @@ mock.module("../notifications/deliveries-store.js", () => ({
42
46
  // can be driven from tests without DB access.
43
47
  let mockExistingConversations: Record<string, { id: string }> = {};
44
48
  mock.module("../memory/conversation-crud.js", () => ({
49
+ setConversationProcessingStartedAt: () => {},
50
+ isConversationProcessing: () => false,
45
51
  getConversation: (id: string) => mockExistingConversations[id] ?? null,
46
52
  }));
47
53
 
@@ -68,6 +68,25 @@ mock.module("../notifications/emit-signal.js", () => ({
68
68
  },
69
69
  }));
70
70
 
71
+ // Guardian principalId is resolved from the gateway binding reader. Mirror the
72
+ // seeded vellum binding so dispatch can resolve the principal.
73
+ mock.module("../contacts/guardian-delivery-reader.js", () => ({
74
+ getGuardianDelivery: async () => [
75
+ {
76
+ channelType: "vellum",
77
+ contactId: "guardian-vellum",
78
+ principalId: "test-principal-id",
79
+ address: "local",
80
+ status: "active",
81
+ },
82
+ ],
83
+ guardianForChannel: (
84
+ list: Array<{ channelType: string; status: string }>,
85
+ channelType: string,
86
+ ) => list.find((g) => g.channelType === channelType && g.status === "active"),
87
+ anyGuardian: (list: unknown[]) => list[0],
88
+ }));
89
+
71
90
  import {
72
91
  createCallSession,
73
92
  createPendingQuestion,
@@ -72,6 +72,8 @@ const persistedRows: Array<{
72
72
  metadata: string | null;
73
73
  }> = [];
74
74
  mock.module("../memory/conversation-crud.js", () => ({
75
+ setConversationProcessingStartedAt: () => {},
76
+ isConversationProcessing: () => false,
75
77
  addMessage: (
76
78
  conversationId: string,
77
79
  role: string,
@@ -155,7 +155,7 @@ describe("pendingInteractions.resolve emits interaction_resolved", () => {
155
155
  });
156
156
 
157
157
  describe("removeByConversation emits interaction_resolved per entry", () => {
158
- test("emits superseded for every non-host interaction in the conversation", () => {
158
+ test("emits superseded for every confirmation/secret interaction in the conversation", () => {
159
159
  pendingInteractions.register("conf-1", {
160
160
  conversationId: "conv-x",
161
161
  kind: "confirmation",
@@ -182,13 +182,16 @@ describe("removeByConversation emits interaction_resolved per entry", () => {
182
182
  const events = publishedMessages.filter(
183
183
  (m) => m.type === "interaction_resolved",
184
184
  ) as Extract<ServerMessage, { type: "interaction_resolved" }>[];
185
- expect(events).toHaveLength(3);
185
+ expect(events).toHaveLength(2);
186
186
  expect(events.every((e) => e.state === "superseded")).toBe(true);
187
187
  const requestIds = new Set(events.map((e) => e.requestId));
188
- expect(requestIds).toEqual(new Set(["conf-1", "secret-1", "question-1"]));
188
+ expect(requestIds).toEqual(new Set(["conf-1", "secret-1"]));
189
189
 
190
- // host_bash entries survive auto-deny — no event for them.
190
+ // host_bash and question entries both survive auto-deny — no event for
191
+ // either. host proxies are settled by their result POST; questions by the
192
+ // enqueue steer's turn abort (see removeByConversation's skip list).
191
193
  expect(pendingInteractions.get("host-bash-1")).toBeDefined();
194
+ expect(pendingInteractions.get("question-1")).toBeDefined();
192
195
  // Unrelated conversation is untouched.
193
196
  expect(pendingInteractions.get("conf-other")).toBeDefined();
194
197
  });
@@ -48,6 +48,8 @@ interface AddMessageCall {
48
48
  }
49
49
  const addMessageCalls: AddMessageCall[] = [];
50
50
  mock.module("../memory/conversation-crud.js", () => ({
51
+ setConversationProcessingStartedAt: () => {},
52
+ isConversationProcessing: () => false,
51
53
  addMessage: (
52
54
  conversationId: string,
53
55
  role: string,
@@ -4,32 +4,19 @@
4
4
  * Covers:
5
5
  * - A noop `init()` fires with a valid `PluginInitContext` that exposes every
6
6
  * documented field.
7
- * - `requiresCredential` entries are resolved through the credential store
8
- * helper and arrive in `ctx.credentials`.
9
7
  * - Version-mismatch registration fails with an error that names the plugin
10
8
  * (the registry enforces this at `registerPlugin` time, so bootstrap never
11
9
  * sees the malformed plugin).
12
10
  * - Shutdown hook walks plugins in reverse registration order.
13
11
  *
14
- * Uses `mock.module` to stub `security/secure-keys.js` so credential
15
- * resolution doesn't hit the real backend. `resetPluginRegistryForTests()`
16
- * isolates registry state between cases.
12
+ * `resetPluginRegistryForTests()` isolates registry state between cases.
17
13
  */
18
14
 
19
15
  import { existsSync } from "node:fs";
20
16
  import { rm } from "node:fs/promises";
21
17
  import { tmpdir } from "node:os";
22
18
  import { join } from "node:path";
23
- import { beforeEach, describe, expect, mock, test } from "bun:test";
24
-
25
- // Mock credential store before importing the bootstrap module so the
26
- // module-under-test captures the stubbed binding.
27
- const getSecureKeyAsyncMock = mock(
28
- async (_account: string): Promise<string | undefined> => undefined,
29
- );
30
- mock.module("../security/secure-keys.js", () => ({
31
- getSecureKeyAsync: getSecureKeyAsyncMock,
32
- }));
19
+ import { beforeEach, describe, expect, test } from "bun:test";
33
20
 
34
21
  import { clearFeatureFlagOverridesCache } from "../config/assistant-feature-flags.js";
35
22
  import { bootstrapPlugins } from "../daemon/external-plugins-bootstrap.js";
@@ -68,7 +55,6 @@ function buildPlugin(
68
55
  onShutdown?: () => Promise<void>;
69
56
  } = {},
70
57
  options: {
71
- requiresCredential?: string[];
72
58
  requiresFlag?: string[];
73
59
  } = {},
74
60
  ): Plugin {
@@ -94,9 +80,6 @@ function buildPlugin(
94
80
  manifest: {
95
81
  name,
96
82
  version: "0.0.1",
97
- ...(options.requiresCredential
98
- ? { requiresCredential: options.requiresCredential }
99
- : {}),
100
83
  ...(options.requiresFlag ? { requiresFlag: options.requiresFlag } : {}),
101
84
  },
102
85
  ...rest,
@@ -107,8 +90,6 @@ function buildPlugin(
107
90
  describe("plugin bootstrap", () => {
108
91
  beforeEach(async () => {
109
92
  resetPluginRegistryForTests();
110
- getSecureKeyAsyncMock.mockReset();
111
- getSecureKeyAsyncMock.mockImplementation(async () => undefined);
112
93
  // Reset feature-flag cache so tests start from a known state. Individual
113
94
  // tests that exercise `requiresFlag` use `setOverridesForTesting(...)`
114
95
  // to install their own overrides.
@@ -133,7 +114,6 @@ describe("plugin bootstrap", () => {
133
114
 
134
115
  // Every documented field must be present on the context passed to init.
135
116
  expect(ctx.config).toBeUndefined(); // no `plugins.alpha` block in fake config
136
- expect(ctx.credentials).toEqual({});
137
117
  expect(ctx.logger).toBeDefined();
138
118
  expect(typeof (ctx.logger as { info: unknown }).info).toBe("function");
139
119
  // Storage dir lives under getWorkspaceDir()/plugins-data/<name> and must have
@@ -145,52 +125,6 @@ describe("plugin bootstrap", () => {
145
125
  expect(ctx.assistantVersion).toBe(APP_VERSION);
146
126
  });
147
127
 
148
- test("credential resolution: init receives the resolved value under credentials[key]", async () => {
149
- getSecureKeyAsyncMock.mockImplementation(async (account: string) => {
150
- if (account === "some-key") return "super-secret-value";
151
- return undefined;
152
- });
153
-
154
- let received: PluginInitContext | undefined;
155
- const plugin = buildPlugin(
156
- "credentialed",
157
- {
158
- async init(ctx) {
159
- received = ctx;
160
- },
161
- },
162
- { requiresCredential: ["some-key"] },
163
- );
164
- registerPlugin(plugin);
165
-
166
- await bootstrapPlugins();
167
-
168
- expect(getSecureKeyAsyncMock).toHaveBeenCalledTimes(1);
169
- expect(getSecureKeyAsyncMock).toHaveBeenCalledWith("some-key");
170
- expect(received?.credentials).toEqual({ "some-key": "super-secret-value" });
171
- });
172
-
173
- test("credential resolution: a plugin whose required credential is missing is skipped, not fatal", async () => {
174
- // GIVEN a plugin that requires a credential the store cannot resolve
175
- getSecureKeyAsyncMock.mockImplementation(async () => undefined);
176
- registerPlugin(
177
- buildPlugin(
178
- "missing-cred",
179
- { async init() {} },
180
- { requiresCredential: ["absent-key"] },
181
- ),
182
- );
183
-
184
- // WHEN bootstrap runs
185
- // THEN it completes without throwing — the unresolvable credential is
186
- // contained to that plugin (same per-plugin isolation as an init throw)
187
- await bootstrapPlugins();
188
-
189
- // AND the plugin is dropped from the registry
190
- const names = getRegisteredPlugins().map((p) => p.manifest.name);
191
- expect(names).not.toContain("missing-cred");
192
- });
193
-
194
128
  test("version mismatch: external plugin loader rejects when peerDependency unsatisfied", async () => {
195
129
  // Host-compat negotiation lives in the external-plugin loader against
196
130
  // `peerDependencies["@vellumai/plugin-api"]`. The registry no longer
@@ -555,11 +489,7 @@ describe("plugin bootstrap", () => {
555
489
  registerPlugin(plugin);
556
490
 
557
491
  // Create the .disabled sentinel in the workspace plugins dir.
558
- const sentinelDir = join(
559
- TEST_WORKSPACE_DIR,
560
- "plugins",
561
- "sentinel-off",
562
- );
492
+ const sentinelDir = join(TEST_WORKSPACE_DIR, "plugins", "sentinel-off");
563
493
  const { mkdir, writeFile } = await import("node:fs/promises");
564
494
  await mkdir(sentinelDir, { recursive: true });
565
495
  await writeFile(join(sentinelDir, ".disabled"), "");
@@ -22,26 +22,15 @@
22
22
  * each plugin's shutdown only removes its own route — the other plugin's
23
23
  * route stays live until its own teardown runs.
24
24
  *
25
- * Uses `mock.module` to stub credential resolution — bootstrap otherwise
26
- * tries to hit the real secure-key backend. `resetPluginRegistryForTests()`
27
- * isolates plugin-registry state and `resetSkillRoutesForTests()` isolates
28
- * skill-route-registry state between cases.
25
+ * `resetPluginRegistryForTests()` isolates plugin-registry state and
26
+ * `resetSkillRoutesForTests()` isolates skill-route-registry state between
27
+ * cases.
29
28
  */
30
29
 
31
30
  import { rm } from "node:fs/promises";
32
31
  import { tmpdir } from "node:os";
33
32
  import { join } from "node:path";
34
- import { beforeEach, describe, expect, mock, test } from "bun:test";
35
-
36
- // Stub the credential store before importing bootstrap so the module binds to
37
- // the mock. Plugins in these tests don't declare `requiresCredential`, but
38
- // the mock keeps the test hermetic regardless of what the backend would do.
39
- const getSecureKeyAsyncMock = mock(
40
- async (_account: string): Promise<string | undefined> => undefined,
41
- );
42
- mock.module("../security/secure-keys.js", () => ({
43
- getSecureKeyAsync: getSecureKeyAsyncMock,
44
- }));
33
+ import { beforeEach, describe, expect, test } from "bun:test";
45
34
 
46
35
  import { bootstrapPlugins } from "../daemon/external-plugins-bootstrap.js";
47
36
  import { runShutdownHooks } from "../daemon/shutdown-registry.js";
@@ -113,8 +102,6 @@ describe("plugin route contributions", () => {
113
102
  beforeEach(async () => {
114
103
  resetPluginRegistryForTests();
115
104
  resetSkillRoutesForTests();
116
- getSecureKeyAsyncMock.mockReset();
117
- getSecureKeyAsyncMock.mockImplementation(async () => undefined);
118
105
  await rm(TEST_WORKSPACE_DIR, { recursive: true, force: true });
119
106
  });
120
107
 
@@ -16,11 +16,9 @@
16
16
  * - Direct `registerPluginTools` / `unregisterPluginTools` semantics,
17
17
  * including the plugin-scoped ref count.
18
18
  *
19
- * Uses `mock.module` to stub the credential store so bootstrap doesn't hit
20
- * the real backend. `resetPluginRegistryForTests()` and
21
- * `__clearRegistryForTesting()` isolate registry state between cases so
22
- * this file can run alongside other plugin/tool-registry tests without
23
- * cross-contamination.
19
+ * `resetPluginRegistryForTests()` and `__clearRegistryForTesting()` isolate
20
+ * registry state between cases so this file can run alongside other
21
+ * plugin/tool-registry tests without cross-contamination.
24
22
  */
25
23
 
26
24
  import { rm } from "node:fs/promises";
@@ -28,17 +26,6 @@ import { tmpdir } from "node:os";
28
26
  import { join } from "node:path";
29
27
  import { beforeEach, describe, expect, mock, test } from "bun:test";
30
28
 
31
- // Mock the credential store before importing the bootstrap so the module
32
- // under test captures the stubbed binding. Bootstrap only calls this for
33
- // plugins that declare `requiresCredential`; the tests in this file don't,
34
- // so the stub simply returns undefined.
35
- const getSecureKeyAsyncMock = mock(
36
- async (_account: string): Promise<string | undefined> => undefined,
37
- );
38
- mock.module("../security/secure-keys.js", () => ({
39
- getSecureKeyAsync: getSecureKeyAsyncMock,
40
- }));
41
-
42
29
  import { bootstrapPlugins } from "../daemon/external-plugins-bootstrap.js";
43
30
  import { runShutdownHooks } from "../daemon/shutdown-registry.js";
44
31
  import { RiskLevel } from "../permissions/types.js";
@@ -134,8 +121,6 @@ describe("plugin tool contributions", () => {
134
121
  // assertions about which tools are present. We don't need any of the
135
122
  // eager/host tools for these tests.
136
123
  __clearRegistryForTesting();
137
- getSecureKeyAsyncMock.mockReset();
138
- getSecureKeyAsyncMock.mockImplementation(async () => undefined);
139
124
  await rm(TEST_WORKSPACE_DIR, { recursive: true, force: true });
140
125
  });
141
126
 
@@ -38,7 +38,6 @@ describe("plugin core types", () => {
38
38
  const manifest: PluginManifest = {
39
39
  name: "sample-plugin",
40
40
  version: "0.1.0",
41
- requiresCredential: ["SAMPLE_API_KEY"],
42
41
  requiresFlag: ["sample-feature"],
43
42
  config: { parse: (input: unknown) => input },
44
43
  };
@@ -61,7 +60,6 @@ describe("plugin core types", () => {
61
60
  async init(ctx: PluginInitContext) {
62
61
  // Touch every field so refactors that rename any of them break here.
63
62
  void ctx.config;
64
- void ctx.credentials;
65
63
  void ctx.logger;
66
64
  void ctx.pluginStorageDir;
67
65
  void ctx.assistantVersion;
@@ -19,6 +19,8 @@ mock.module("../memory/canonical-guardian-store.js", () => ({
19
19
  }));
20
20
 
21
21
  mock.module("../memory/conversation-crud.js", () => ({
22
+ setConversationProcessingStartedAt: () => {},
23
+ isConversationProcessing: () => false,
22
24
  addMessage: async () => ({ id: "message-id" }),
23
25
  getConversation: () => null,
24
26
  provenanceFromTrustContext: () => ({}),
@@ -37,6 +37,8 @@ mock.module("../memory/attachments-store.js", () => ({
37
37
  }));
38
38
 
39
39
  mock.module("../memory/conversation-crud.js", () => ({
40
+ setConversationProcessingStartedAt: () => {},
41
+ isConversationProcessing: () => false,
40
42
  addMessage: async (
41
43
  conversationId: string,
42
44
  role: string,
@@ -200,3 +200,42 @@ describe("UsageTrackingProvider", () => {
200
200
  });
201
201
  });
202
202
  });
203
+
204
+ describe("native web-search capability survives the wrapper chain", () => {
205
+ function leaf(supports: boolean | undefined): Provider {
206
+ return {
207
+ name: "anthropic",
208
+ ...(supports === undefined ? {} : { supportsNativeWebSearch: supports }),
209
+ async sendMessage(): Promise<ProviderResponse> {
210
+ return {
211
+ content: [{ type: "text", text: "" }],
212
+ model: "m",
213
+ usage: { inputTokens: 0, outputTokens: 0 },
214
+ stopReason: "end_turn",
215
+ };
216
+ },
217
+ };
218
+ }
219
+
220
+ test("UsageTrackingProvider forwards supportsNativeWebSearch", () => {
221
+ expect(new UsageTrackingProvider(leaf(true)).supportsNativeWebSearch).toBe(
222
+ true,
223
+ );
224
+ expect(new UsageTrackingProvider(leaf(false)).supportsNativeWebSearch).toBe(
225
+ false,
226
+ );
227
+ expect(
228
+ new UsageTrackingProvider(leaf(undefined)).supportsNativeWebSearch,
229
+ ).toBeUndefined();
230
+ });
231
+
232
+ test("CallSiteConfiguredProvider forwards it through a nested wrapper", () => {
233
+ // The exact chain getConfiguredProvider returns: CallSiteConfigured →
234
+ // UsageTracking → leaf. The advisor consult reads the flag off the top.
235
+ const wrapped = new CallSiteConfiguredProvider(
236
+ new UsageTrackingProvider(leaf(true)),
237
+ "advisor",
238
+ );
239
+ expect(wrapped.supportsNativeWebSearch).toBe(true);
240
+ });
241
+ });
@@ -23,6 +23,8 @@ let dbMessages: Array<{
23
23
  }> = [];
24
24
 
25
25
  mock.module("../memory/conversation-crud.js", () => ({
26
+ setConversationProcessingStartedAt: () => {},
27
+ isConversationProcessing: () => false,
26
28
  getMessages: (conversationId: string) =>
27
29
  dbMessages.filter((m) => m.conversationId === conversationId),
28
30
  deleteMessageById: (messageId: string) => {
@@ -36,6 +36,9 @@ function makeFakeTool(name: string): Tool {
36
36
  category: "test",
37
37
  defaultRiskLevel: RiskLevel.Low,
38
38
  executionTarget: "sandbox",
39
+ // Match the finalized shape the registry stores, so identity comparisons
40
+ // (`getTool(name)` toEqual coreTool) hold after registration fills defaults.
41
+ exclusive: false,
39
42
  input_schema: { type: "object", properties: {}, required: [] },
40
43
  async execute(
41
44
  _input: Record<string, unknown>,