@vellumai/assistant 0.8.4 → 0.8.5

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 (438) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/docs/browser-use-architecture-phase2.md +1 -1
  3. package/knip.json +2 -1
  4. package/openapi.yaml +809 -11
  5. package/package.json +1 -1
  6. package/src/__tests__/anthropic-provider.test.ts +34 -37
  7. package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +293 -0
  8. package/src/__tests__/assistant-feature-flags-integration.test.ts +3 -3
  9. package/src/__tests__/audit-log-rotation.test.ts +70 -16
  10. package/src/__tests__/background-workers-disk-pressure.test.ts +3 -3
  11. package/src/__tests__/btw-routes.test.ts +2 -3
  12. package/src/__tests__/call-controller.test.ts +0 -1
  13. package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
  14. package/src/__tests__/channel-guardian.test.ts +3 -3
  15. package/src/__tests__/checker.test.ts +6 -15
  16. package/src/__tests__/compaction-events.test.ts +1 -0
  17. package/src/__tests__/compactor-call-site-logging.test.ts +214 -0
  18. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +5 -11
  19. package/src/__tests__/computer-use-tools.test.ts +2 -4
  20. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  21. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -1
  22. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
  23. package/src/__tests__/conversation-agent-loop-overflow.test.ts +197 -2
  24. package/src/__tests__/conversation-agent-loop.test.ts +163 -122
  25. package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -5
  26. package/src/__tests__/conversation-clear-safety.test.ts +25 -25
  27. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +1 -1
  28. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
  29. package/src/__tests__/conversation-error.test.ts +31 -0
  30. package/src/__tests__/conversation-fork-crud.test.ts +178 -15
  31. package/src/__tests__/conversation-lifecycle.test.ts +52 -11
  32. package/src/__tests__/{conversation-load-cleaned-at.test.ts → conversation-load-history-stripped.test.ts} +13 -13
  33. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
  34. package/src/__tests__/conversation-routes-disk-view.test.ts +109 -0
  35. package/src/__tests__/conversation-routes-slash-commands.test.ts +35 -0
  36. package/src/__tests__/conversation-skill-tools.test.ts +2 -5
  37. package/src/__tests__/conversation-store.test.ts +1 -1
  38. package/src/__tests__/conversation-sync-tags.test.ts +99 -32
  39. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  40. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  41. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  42. package/src/__tests__/credential-execution-feature-gates.test.ts +9 -7
  43. package/src/__tests__/credential-execution-tools.test.ts +6 -6
  44. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  45. package/src/__tests__/credential-vault-unit.test.ts +2 -2
  46. package/src/__tests__/dynamic-page-surface.test.ts +2 -2
  47. package/src/__tests__/email-html-renderer.test.ts +12 -0
  48. package/src/__tests__/gateway-flag-listener.test.ts +237 -0
  49. package/src/__tests__/gemini-provider.test.ts +78 -0
  50. package/src/__tests__/guardian-dispatch.test.ts +0 -1
  51. package/src/__tests__/guardian-outbound-http.test.ts +7 -5
  52. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  53. package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
  54. package/src/__tests__/heartbeat-service.test.ts +4 -0
  55. package/src/__tests__/host-shell-tool.test.ts +1 -1
  56. package/src/__tests__/init-feature-flag-overrides.test.ts +5 -6
  57. package/src/__tests__/list-messages-tool-merge.test.ts +70 -11
  58. package/src/__tests__/llm-request-log-call-site.test.ts +136 -0
  59. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +26 -0
  60. package/src/__tests__/llm-resolver.test.ts +77 -9
  61. package/src/__tests__/llm-usage-store.test.ts +66 -0
  62. package/src/__tests__/logger.test.ts +89 -0
  63. package/src/__tests__/mcp-abort-signal.test.ts +2 -2
  64. package/src/__tests__/media-generate-image.test.ts +31 -0
  65. package/src/__tests__/memory-v2-static-injector.test.ts +7 -7
  66. package/src/__tests__/model-intents.test.ts +2 -4
  67. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  68. package/src/__tests__/onboarding-template-contract.test.ts +1 -1
  69. package/src/__tests__/openai-provider.test.ts +46 -0
  70. package/src/__tests__/openai-responses-provider.test.ts +114 -12
  71. package/src/__tests__/pending-interactions-resolved-event.test.ts +0 -1
  72. package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
  73. package/src/__tests__/platform.test.ts +2 -2
  74. package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
  75. package/src/__tests__/plugin-bootstrap.test.ts +2 -2
  76. package/src/__tests__/plugin-tool-contribution.test.ts +13 -6
  77. package/src/__tests__/plugin-types.test.ts +3 -2
  78. package/src/__tests__/prechat-onboarding-contract.test.ts +131 -98
  79. package/src/__tests__/pricing.test.ts +12 -0
  80. package/src/__tests__/prune-jobs-changes-parser.test.ts +61 -0
  81. package/src/__tests__/registry.test.ts +2 -8
  82. package/src/__tests__/require-fresh-approval.test.ts +2 -2
  83. package/src/__tests__/runtime-events-sse-bilingual.test.ts +154 -0
  84. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
  85. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  86. package/src/__tests__/skill-projection-feature-flag.test.ts +4 -7
  87. package/src/__tests__/skill-projection.benchmark.test.ts +2 -6
  88. package/src/__tests__/skill-tool-factory.test.ts +1 -1
  89. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  90. package/src/__tests__/suggestion-routes.test.ts +1 -0
  91. package/src/__tests__/sync-message-contract.test.ts +59 -0
  92. package/src/__tests__/system-prompt.test.ts +145 -131
  93. package/src/__tests__/terminal-tools.test.ts +1 -1
  94. package/src/__tests__/tool-approval-handler.test.ts +1 -5
  95. package/src/__tests__/tool-execute-pipeline.test.ts +2 -2
  96. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -5
  97. package/src/__tests__/tool-executor-lifecycle-events.test.ts +15 -5
  98. package/src/__tests__/tool-executor.test.ts +9 -62
  99. package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
  100. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  101. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -6
  102. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
  103. package/src/__tests__/ui-file-upload-surface.test.ts +2 -2
  104. package/src/__tests__/usage-routes.test.ts +3 -0
  105. package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
  106. package/src/__tests__/workspace-git-service.test.ts +6 -5
  107. package/src/__tests__/workspace-migration-089-move-memory-tree-out-of-v3.test.ts +86 -0
  108. package/src/acp/__tests__/prepare-agent-env.test.ts +146 -0
  109. package/src/acp/prepare-agent-env.ts +78 -0
  110. package/src/acp/session-manager.ts +1 -1
  111. package/src/agent/loop.ts +8 -0
  112. package/src/api/README.md +5 -0
  113. package/src/api/index.ts +4 -0
  114. package/src/api/package.json +10 -0
  115. package/src/background-wake/background-wake-routes.test.ts +233 -0
  116. package/src/background-wake/runtime-registry.ts +24 -0
  117. package/src/cli/commands/__tests__/browser.test.ts +23 -5
  118. package/src/cli/commands/__tests__/domain-register.test.ts +110 -0
  119. package/src/cli/commands/__tests__/domain-status.test.ts +33 -33
  120. package/src/cli/commands/__tests__/inference-send.test.ts +108 -5
  121. package/src/cli/commands/__tests__/memory-v2-compare-render.test.ts +98 -0
  122. package/src/cli/commands/__tests__/memory-v2.test.ts +1 -0
  123. package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
  124. package/src/cli/commands/browser.ts +247 -0
  125. package/src/cli/commands/domain.ts +91 -41
  126. package/src/cli/commands/inference.ts +93 -40
  127. package/src/cli/commands/memory-v2-compare-render.ts +115 -0
  128. package/src/cli/commands/memory-v2.ts +176 -1
  129. package/src/cli/commands/memory-v3-render.ts +344 -0
  130. package/src/cli/commands/memory-v3.ts +316 -0
  131. package/src/cli/program.ts +2 -0
  132. package/src/config/assistant-feature-flags.ts +21 -9
  133. package/src/config/bundled-skills/document-editor/SKILL.md +11 -2
  134. package/src/config/bundled-skills/document-editor/TOOLS.json +18 -0
  135. package/src/config/bundled-skills/document-editor/tools/document-open.ts +12 -0
  136. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  137. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
  138. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +13 -8
  139. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +10 -3
  140. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +16 -14
  141. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +7 -2
  142. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -2
  143. package/src/config/bundled-tool-registry.ts +2 -0
  144. package/src/config/call-site-defaults.ts +7 -6
  145. package/src/config/feature-flag-registry.json +16 -0
  146. package/src/config/schemas/__tests__/memory-v2.test.ts +213 -1
  147. package/src/config/schemas/call-site-catalog.ts +21 -7
  148. package/src/config/schemas/llm.ts +12 -1
  149. package/src/config/schemas/memory-v2.ts +246 -0
  150. package/src/config/schemas/memory.ts +2 -1
  151. package/src/context/compactor.ts +52 -0
  152. package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
  153. package/src/conversations/message-consolidation.ts +404 -0
  154. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -1
  155. package/src/daemon/__tests__/meet-manifest-loader.test.ts +1 -1
  156. package/src/daemon/conversation-agent-loop-handlers.ts +2 -13
  157. package/src/daemon/conversation-agent-loop.ts +126 -76
  158. package/src/daemon/conversation-error.ts +31 -1
  159. package/src/daemon/conversation-lifecycle.ts +27 -22
  160. package/src/daemon/conversation-runtime-assembly.ts +10 -9
  161. package/src/daemon/conversation-tool-setup.ts +63 -3
  162. package/src/daemon/conversation-usage.ts +2 -0
  163. package/src/daemon/conversation.ts +14 -29
  164. package/src/daemon/disk-pressure-guard.ts +14 -2
  165. package/src/daemon/handlers/config-model.test.ts +1 -0
  166. package/src/daemon/handlers/conversations.ts +11 -3
  167. package/src/daemon/host-browser-proxy.ts +5 -5
  168. package/src/daemon/host-cu-proxy.ts +4 -4
  169. package/src/daemon/host-file-proxy.ts +4 -4
  170. package/src/daemon/host-proxy-base.ts +4 -4
  171. package/src/daemon/host-transfer-proxy.ts +10 -10
  172. package/src/daemon/lifecycle.ts +23 -20
  173. package/src/daemon/meet-manifest-loader.ts +1 -7
  174. package/src/daemon/message-types/conversations.ts +6 -9
  175. package/src/daemon/message-types/home.ts +1 -13
  176. package/src/daemon/message-types/messages.ts +6 -14
  177. package/src/daemon/message-types/sync.ts +14 -0
  178. package/src/daemon/shutdown-handlers.ts +24 -5
  179. package/src/daemon/switch-inference-profile-tool.ts +52 -0
  180. package/src/daemon/tool-setup-types.ts +13 -0
  181. package/src/events/relationship-state-updated.ts +25 -0
  182. package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
  183. package/src/home/home-greeting.ts +0 -9
  184. package/src/home/suggested-prompts.ts +0 -9
  185. package/src/ipc/gateway-flag-listener.ts +123 -0
  186. package/src/ipc/skill-routes/registries.ts +8 -12
  187. package/src/memory/__tests__/db-async-query.test.ts +165 -0
  188. package/src/memory/__tests__/db-maintenance.test.ts +115 -0
  189. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +241 -0
  190. package/src/memory/__tests__/jobs-store-job-classes.test.ts +28 -1
  191. package/src/memory/__tests__/memory-retrospective-job.test.ts +7 -0
  192. package/src/memory/auto-analysis-enqueue.ts +5 -1
  193. package/src/memory/conversation-crud.ts +71 -70
  194. package/src/memory/conversation-starters-cadence.ts +3 -1
  195. package/src/memory/conversation-title-service.ts +19 -3
  196. package/src/memory/db-async-query.ts +214 -0
  197. package/src/memory/db-init.ts +10 -0
  198. package/src/memory/db-maintenance.ts +30 -21
  199. package/src/memory/graph/bootstrap.ts +8 -1
  200. package/src/memory/graph/capability-seed.ts +7 -3
  201. package/src/memory/graph/conversation-graph-memory.ts +100 -17
  202. package/src/memory/graph/extraction.ts +1 -5
  203. package/src/memory/graph/graph-search.ts +7 -1
  204. package/src/memory/indexer.ts +28 -18
  205. package/src/memory/job-handlers/cleanup.ts +76 -18
  206. package/src/memory/job-handlers/conversation-starters.ts +1 -4
  207. package/src/memory/jobs/embed-pkb-file.ts +6 -1
  208. package/src/memory/jobs-store.ts +14 -0
  209. package/src/memory/jobs-worker.ts +55 -22
  210. package/src/memory/llm-request-log-source-clickhouse.ts +42 -2
  211. package/src/memory/llm-request-log-source-local.ts +7 -0
  212. package/src/memory/llm-request-log-source.ts +9 -2
  213. package/src/memory/llm-request-log-store.ts +43 -1
  214. package/src/memory/llm-usage-store.ts +24 -0
  215. package/src/memory/memory-retrospective-enqueue.ts +8 -1
  216. package/src/memory/memory-retrospective-job.ts +5 -0
  217. package/src/memory/memory-v2-activation-log-store.ts +15 -6
  218. package/src/memory/migrations/260-rename-cleaned-at.ts +44 -0
  219. package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +36 -0
  220. package/src/memory/migrations/262-memory-v3-coactivation.ts +57 -0
  221. package/src/memory/migrations/263-memory-v3-auto-edges.ts +50 -0
  222. package/src/memory/migrations/264-llm-request-log-call-site.ts +29 -0
  223. package/src/memory/migrations/index.ts +17 -0
  224. package/src/memory/migrations/registry.ts +33 -0
  225. package/src/memory/schema/conversations.ts +1 -1
  226. package/src/memory/schema/infrastructure.ts +21 -0
  227. package/src/memory/tool-usage-store.ts +36 -8
  228. package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -0
  229. package/src/memory/v2/__tests__/harness-compare.test.ts +186 -0
  230. package/src/memory/v2/__tests__/harness-metrics.test.ts +74 -0
  231. package/src/memory/v2/__tests__/harness-oracle.test.ts +257 -0
  232. package/src/memory/v2/__tests__/harness-replay-input.test.ts +225 -0
  233. package/src/memory/v2/__tests__/harness-runner.test.ts +109 -0
  234. package/src/memory/v2/__tests__/injection.test.ts +127 -98
  235. package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
  236. package/src/memory/v2/__tests__/router.test.ts +171 -3
  237. package/src/memory/v2/harness/compare.ts +57 -0
  238. package/src/memory/v2/harness/metrics.ts +124 -0
  239. package/src/memory/v2/harness/oracle.ts +145 -0
  240. package/src/memory/v2/harness/replay-input.ts +224 -0
  241. package/src/memory/v2/harness/retriever.ts +74 -0
  242. package/src/memory/v2/harness/router-retriever.ts +43 -0
  243. package/src/memory/v2/harness/runner.ts +106 -0
  244. package/src/memory/v2/harness/trace.ts +58 -0
  245. package/src/memory/v2/injection.ts +21 -15
  246. package/src/memory/v2/prompts/router.ts +26 -1
  247. package/src/memory/v2/qdrant.ts +14 -2
  248. package/src/memory/v2/router.ts +171 -18
  249. package/src/memory/v3/__tests__/coactivation-store.test.ts +422 -0
  250. package/src/memory/v3/__tests__/consolidation-job.test.ts +468 -0
  251. package/src/memory/v3/__tests__/edge-learning-job.test.ts +324 -0
  252. package/src/memory/v3/__tests__/edges.test.ts +563 -0
  253. package/src/memory/v3/__tests__/filter.test.ts +512 -0
  254. package/src/memory/v3/__tests__/gate.test.ts +574 -0
  255. package/src/memory/v3/__tests__/index-composition.test.ts +233 -0
  256. package/src/memory/v3/__tests__/loop.test.ts +530 -0
  257. package/src/memory/v3/__tests__/retriever.test.ts +226 -0
  258. package/src/memory/v3/__tests__/scouts.test.ts +440 -0
  259. package/src/memory/v3/__tests__/shadow-middleware.test.ts +312 -0
  260. package/src/memory/v3/__tests__/system-prompts.test.ts +154 -0
  261. package/src/memory/v3/__tests__/traversal.test.ts +469 -0
  262. package/src/memory/v3/__tests__/tree-index.test.ts +280 -0
  263. package/src/memory/v3/__tests__/tree-store.test.ts +529 -0
  264. package/src/memory/v3/__tests__/tree-walk.test.ts +707 -0
  265. package/src/memory/v3/__tests__/validate.test.ts +245 -0
  266. package/src/memory/v3/auto-edges.ts +223 -0
  267. package/src/memory/v3/coactivation-store.ts +124 -0
  268. package/src/memory/v3/consolidation-job.ts +323 -0
  269. package/src/memory/v3/edge-learning-job.ts +160 -0
  270. package/src/memory/v3/edges.ts +249 -0
  271. package/src/memory/v3/filter.ts +281 -0
  272. package/src/memory/v3/gate.ts +334 -0
  273. package/src/memory/v3/index-composition.ts +113 -0
  274. package/src/memory/v3/llm-capture.ts +46 -0
  275. package/src/memory/v3/loop.ts +382 -0
  276. package/src/memory/v3/maintenance.ts +144 -0
  277. package/src/memory/v3/prompt-context.ts +33 -0
  278. package/src/memory/v3/prompts/consolidation.ts +458 -0
  279. package/src/memory/v3/prompts/system-prompts.ts +196 -0
  280. package/src/memory/v3/retriever.ts +33 -0
  281. package/src/memory/v3/scouts.ts +420 -0
  282. package/src/memory/v3/shadow-middleware.ts +305 -0
  283. package/src/memory/v3/traversal.ts +206 -0
  284. package/src/memory/v3/tree-index.ts +237 -0
  285. package/src/memory/v3/tree-store.ts +394 -0
  286. package/src/memory/v3/tree-walk.ts +351 -0
  287. package/src/memory/v3/types.ts +65 -0
  288. package/src/memory/v3/validate.ts +300 -0
  289. package/src/notifications/adapters/macos.ts +18 -1
  290. package/src/notifications/adapters/platform.ts +1 -1
  291. package/src/notifications/decision-engine.ts +1 -4
  292. package/src/notifications/emit-signal.ts +29 -49
  293. package/src/permissions/prompter.ts +3 -3
  294. package/src/permissions/question-prompter.ts +5 -2
  295. package/src/permissions/secret-prompter.ts +2 -2
  296. package/src/plugin-api/index.ts +4 -0
  297. package/src/plugin-api/types.ts +7 -33
  298. package/src/plugins/defaults/index.ts +6 -0
  299. package/src/plugins/defaults/injectors.ts +18 -11
  300. package/src/plugins/external-plugin-loader.ts +5 -68
  301. package/src/plugins/types.ts +11 -16
  302. package/src/proactive-artifact/aux-message-injector.ts +17 -4
  303. package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
  304. package/src/prompts/persona-resolver.ts +36 -21
  305. package/src/prompts/sections.ts +39 -7
  306. package/src/prompts/system-prompt.ts +50 -185
  307. package/src/prompts/templates/BOOTSTRAP.md +2 -2
  308. package/src/prompts/templates/system-sections.ts +230 -8
  309. package/src/providers/__tests__/connection-model-compat.test.ts +234 -0
  310. package/src/providers/__tests__/retry-callsite.test.ts +85 -5
  311. package/src/providers/anthropic/client.ts +32 -66
  312. package/src/providers/call-site-routing.ts +14 -2
  313. package/src/providers/connection-model-compat.ts +38 -0
  314. package/src/providers/connection-resolution.ts +16 -2
  315. package/src/providers/gemini/client.ts +49 -6
  316. package/src/providers/inference/adapter-factory.ts +3 -0
  317. package/src/providers/minimax/client.ts +106 -0
  318. package/src/providers/model-catalog.ts +43 -0
  319. package/src/providers/model-intents.ts +1 -1
  320. package/src/providers/openai/chat-completions-provider.ts +6 -3
  321. package/src/providers/openai/codex-models.ts +18 -0
  322. package/src/providers/openai/responses-provider.ts +78 -21
  323. package/src/providers/provider-send-message.ts +7 -1
  324. package/src/providers/retry.ts +34 -3
  325. package/src/providers/thinking-config.ts +26 -1
  326. package/src/providers/usage-tracking.ts +2 -0
  327. package/src/runtime/AGENTS.md +2 -2
  328. package/src/runtime/agent-wake.ts +1 -0
  329. package/src/runtime/assistant-event-hub.ts +76 -6
  330. package/src/runtime/auth/route-policy.ts +36 -0
  331. package/src/runtime/btw-sidechain.ts +0 -6
  332. package/src/runtime/http-types.ts +0 -2
  333. package/src/runtime/migrations/vbundle-builder.ts +10 -3
  334. package/src/runtime/pending-interactions.ts +0 -1
  335. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +106 -0
  336. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +25 -6
  337. package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -0
  338. package/src/runtime/routes/acp-routes.test.ts +255 -6
  339. package/src/runtime/routes/acp-routes.ts +8 -1
  340. package/src/runtime/routes/avatar-routes.ts +10 -10
  341. package/src/runtime/routes/background-wake-routes.ts +188 -0
  342. package/src/runtime/routes/browser-tabs-routes.ts +200 -0
  343. package/src/runtime/routes/btw-routes.ts +0 -6
  344. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  345. package/src/runtime/routes/conversation-list-routes.ts +12 -4
  346. package/src/runtime/routes/conversation-management-routes.ts +77 -20
  347. package/src/runtime/routes/conversation-query-routes.ts +142 -36
  348. package/src/runtime/routes/conversation-routes.ts +252 -410
  349. package/src/runtime/routes/conversation-starter-routes.ts +6 -3
  350. package/src/runtime/routes/disk-pressure-routes.ts +1 -1
  351. package/src/runtime/routes/domain-routes.ts +60 -10
  352. package/src/runtime/routes/email-routes.ts +5 -2
  353. package/src/runtime/routes/events-routes.ts +54 -10
  354. package/src/runtime/routes/group-routes.ts +24 -8
  355. package/src/runtime/routes/host-browser-routes.ts +10 -2
  356. package/src/runtime/routes/host-cu-routes.ts +2 -2
  357. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +96 -3
  358. package/src/runtime/routes/index.ts +8 -0
  359. package/src/runtime/routes/inference-profile-session-handler.ts +22 -12
  360. package/src/runtime/routes/inference-profile-session-routes.ts +7 -1
  361. package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
  362. package/src/runtime/routes/memory-item-routes.ts +8 -3
  363. package/src/runtime/routes/memory-v2-routes.ts +215 -5
  364. package/src/runtime/routes/memory-v3-routes.ts +316 -0
  365. package/src/runtime/routes/migration-routes.ts +21 -24
  366. package/src/runtime/routes/plugins-routes.ts +337 -0
  367. package/src/runtime/routes/rename-conversation-routes.ts +6 -2
  368. package/src/runtime/routes/secret-routes.ts +25 -5
  369. package/src/runtime/routes/settings-routes.ts +12 -11
  370. package/src/runtime/routes/slack-channel-routes.ts +5 -4
  371. package/src/runtime/routes/workspace-routes.ts +25 -10
  372. package/src/runtime/sync/resource-sync-events.ts +106 -38
  373. package/src/runtime/sync/sync-publisher.test.ts +49 -0
  374. package/src/runtime/sync/sync-publisher.ts +2 -1
  375. package/src/runtime/verification-outbound-actions.ts +73 -1
  376. package/src/telemetry/types.ts +12 -0
  377. package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
  378. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  379. package/src/tools/acp/spawn.test.ts +119 -0
  380. package/src/tools/acp/spawn.ts +15 -2
  381. package/src/tools/apps/definitions.ts +2 -8
  382. package/src/tools/ask-question/ask-question-tool.test.ts +3 -3
  383. package/src/tools/ask-question/ask-question-tool.ts +38 -45
  384. package/src/tools/browser/__tests__/pinned-tabs.test.ts +70 -0
  385. package/src/tools/browser/browser-execution.ts +16 -3
  386. package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
  387. package/src/tools/browser/cdp-client/__tests__/types.test.ts +3 -0
  388. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +12 -0
  389. package/src/tools/browser/cdp-client/extension-cdp-client.ts +27 -1
  390. package/src/tools/browser/cdp-client/factory.ts +100 -17
  391. package/src/tools/browser/cdp-client/local-cdp-client.ts +12 -0
  392. package/src/tools/browser/cdp-client/types.ts +65 -0
  393. package/src/tools/browser/pinned-tabs.ts +96 -40
  394. package/src/tools/computer-use/definitions.ts +22 -78
  395. package/src/tools/credential-execution/make-authenticated-request.ts +3 -9
  396. package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -9
  397. package/src/tools/credential-execution/run-authenticated-command.ts +3 -9
  398. package/src/tools/credentials/vault.ts +3 -9
  399. package/src/tools/document/document-tool.ts +59 -0
  400. package/src/tools/execution-target.ts +21 -23
  401. package/src/tools/executor.ts +6 -1
  402. package/src/tools/filesystem/edit.ts +3 -9
  403. package/src/tools/filesystem/list.ts +3 -9
  404. package/src/tools/filesystem/read.ts +3 -9
  405. package/src/tools/filesystem/write.ts +3 -9
  406. package/src/tools/host-filesystem/edit.ts +3 -9
  407. package/src/tools/host-filesystem/read.ts +3 -9
  408. package/src/tools/host-filesystem/transfer.ts +3 -9
  409. package/src/tools/host-filesystem/write.ts +3 -9
  410. package/src/tools/host-terminal/host-shell.ts +3 -9
  411. package/src/tools/mcp/mcp-tool-factory.ts +1 -8
  412. package/src/tools/memory/register.test.ts +1 -1
  413. package/src/tools/memory/register.ts +4 -9
  414. package/src/tools/network/web-fetch.ts +3 -9
  415. package/src/tools/network/web-search.ts +25 -32
  416. package/src/tools/registry.ts +7 -23
  417. package/src/tools/schema-transforms.ts +1 -1
  418. package/src/tools/skills/execute.ts +3 -9
  419. package/src/tools/skills/load.ts +3 -9
  420. package/src/tools/skills/skill-tool-factory.ts +1 -8
  421. package/src/tools/subagent/notify-parent.ts +3 -9
  422. package/src/tools/system/request-permission.ts +3 -9
  423. package/src/tools/terminal/shell.ts +3 -9
  424. package/src/tools/tool-defaults.ts +94 -0
  425. package/src/tools/types.ts +27 -98
  426. package/src/tools/ui-surface/definitions.ts +6 -22
  427. package/src/usage/pricing.ts +23 -0
  428. package/src/usage/types.ts +12 -0
  429. package/src/util/logger.ts +16 -7
  430. package/src/util/platform.ts +7 -2
  431. package/src/util/sqlite3-runtime.ts +65 -0
  432. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
  433. package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
  434. package/src/workspace/migrations/registry.ts +2 -0
  435. package/src/__tests__/compaction-strip-metadata-clear.test.ts +0 -206
  436. package/src/__tests__/message-complete-display-id.test.ts +0 -175
  437. package/src/daemon/query-complexity-router.ts +0 -75
  438. package/src/prompts/cache-boundary.ts +0 -8
@@ -0,0 +1,512 @@
1
+ /**
2
+ * Tests for `assistant/src/memory/v3/filter.ts`.
3
+ *
4
+ * Coverage matrix:
5
+ * - keep-subset → kept = bypass ∪ judged-kept; dropped = judged minus kept;
6
+ * bypass slugs are never judged.
7
+ * - model keeping a slug outside the judged set → dropped.
8
+ * - empty dense → no LLM call, kept = bypass-relevant only.
9
+ * - dense entirely covered by bypass → no LLM call (nothing to judge).
10
+ * - provider === null (no provider configured) → fail-open: keep all dense,
11
+ * failureReason = "no_provider".
12
+ * - provider throws → fail-open (keep all, failureReason = "api_error").
13
+ * - missing tool_use block → fail-open (failureReason = "tool_use_missing").
14
+ * - tool input failing schema → fail-open (failureReason = "schema_mismatch").
15
+ * - request shape: forced tool_choice on `filter_dense_hits`, judged set in
16
+ * the user message, abort signal forwarded.
17
+ *
18
+ * The provider is injected via `filterDenseHits({ provider })` — no real LLM,
19
+ * no network, no `mock.module`. `~/.vellum/` is never touched.
20
+ */
21
+
22
+ import { describe, expect, test } from "bun:test";
23
+
24
+ import type {
25
+ Message,
26
+ Provider,
27
+ ProviderResponse,
28
+ SendMessageOptions,
29
+ ToolDefinition,
30
+ } from "../../../providers/types.js";
31
+ import type { RetrievalInput } from "../../v2/harness/retriever.js";
32
+ import type { ScoutResult } from "../../v2/harness/trace.js";
33
+ import { filterDenseHits } from "../filter.js";
34
+ import type { LlmCallRecord } from "../llm-capture.js";
35
+ import { FILTER_SYSTEM_PROMPT } from "../prompts/system-prompts.js";
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Helpers.
39
+ // ---------------------------------------------------------------------------
40
+
41
+ interface ProviderCall {
42
+ messages: Message[];
43
+ tools: ToolDefinition[] | undefined;
44
+ systemPrompt: string | undefined;
45
+ options: SendMessageOptions | undefined;
46
+ }
47
+
48
+ /**
49
+ * A stub provider that records its calls and returns a fixed response.
50
+ * Honors an already-aborted signal by throwing an AbortError so signal
51
+ * forwarding can be asserted.
52
+ */
53
+ function makeProvider(
54
+ response: ProviderResponse,
55
+ calls: ProviderCall[],
56
+ ): Provider {
57
+ return {
58
+ name: "stub",
59
+ sendMessage: async (messages, tools, systemPrompt, options) => {
60
+ calls.push({ messages, tools, systemPrompt, options });
61
+ if (options?.signal?.aborted) {
62
+ const err = new Error("aborted");
63
+ err.name = "AbortError";
64
+ throw err;
65
+ }
66
+ return response;
67
+ },
68
+ };
69
+ }
70
+
71
+ /** A provider whose sendMessage always throws. */
72
+ function makeThrowingProvider(): Provider {
73
+ return {
74
+ name: "throwing-stub",
75
+ sendMessage: async () => {
76
+ throw new Error("boom");
77
+ },
78
+ };
79
+ }
80
+
81
+ /** A provider that must never be called (asserts no LLM round-trip happens). */
82
+ function makeNeverCalledProvider(): Provider {
83
+ return {
84
+ name: "never-called-stub",
85
+ sendMessage: async () => {
86
+ throw new Error("provider should not be called");
87
+ },
88
+ };
89
+ }
90
+
91
+ function filterToolResponse(input: Record<string, unknown>): ProviderResponse {
92
+ return {
93
+ model: "stub-model",
94
+ stopReason: "tool_use",
95
+ usage: { inputTokens: 0, outputTokens: 0 },
96
+ content: [
97
+ { type: "tool_use", id: "tu-1", name: "filter_dense_hits", input },
98
+ ],
99
+ };
100
+ }
101
+
102
+ /** A response with no tool_use block (e.g. the model emitted only text). */
103
+ function textOnlyResponse(): ProviderResponse {
104
+ return {
105
+ model: "stub-model",
106
+ stopReason: "end_turn",
107
+ usage: { inputTokens: 0, outputTokens: 0 },
108
+ content: [{ type: "text", text: "no tool here" }],
109
+ };
110
+ }
111
+
112
+ /** Minimal `RetrievalInput` — the filter only reads `nowText` and `signal`. */
113
+ function makeInput(overrides?: Partial<RetrievalInput>): RetrievalInput {
114
+ return {
115
+ workspaceDir: "/tmp/does-not-matter",
116
+ recentTurnPairs: [],
117
+ nowText: "2026-05-25 10:00 PT",
118
+ priorEverInjected: [],
119
+ config: {} as unknown as RetrievalInput["config"],
120
+ ...overrides,
121
+ };
122
+ }
123
+
124
+ function denseResult(slugs: string[]): ScoutResult {
125
+ return { lane: "dense", slugs };
126
+ }
127
+
128
+ /**
129
+ * Build a `RetrievalInput["config"]` carrying a `memory.v3.prompts.filter`
130
+ * inline override. The cast mirrors `makeInput`'s — the filter only reads the
131
+ * prompts path on config.
132
+ */
133
+ function configWithFilterOverride(override: string): RetrievalInput["config"] {
134
+ return {
135
+ memory: { v3: { prompts: { filter: { override, path: null } } } },
136
+ } as unknown as RetrievalInput["config"];
137
+ }
138
+
139
+ // ---------------------------------------------------------------------------
140
+ // Tests.
141
+ // ---------------------------------------------------------------------------
142
+
143
+ describe("filterDenseHits — judged keep/drop", () => {
144
+ test("kept = bypass ∪ judged-kept; bypass slugs are never judged", async () => {
145
+ const calls: ProviderCall[] = [];
146
+ // Dense surfaces a, b, c, plus bypass slug `x`. Model keeps a, c; drops b.
147
+ const provider = makeProvider(
148
+ filterToolResponse({ keep_slugs: ["c", "a"] }),
149
+ calls,
150
+ );
151
+
152
+ const result = await filterDenseHits({
153
+ input: makeInput(),
154
+ dense: denseResult(["a", "b", "c", "x"]),
155
+ sticky: new Set(["x"]),
156
+ bypass: new Set(["x"]),
157
+ provider,
158
+ });
159
+
160
+ // bypass first (x), then judged-kept in model order (c, a).
161
+ expect(result.kept).toEqual(["x", "c", "a"]);
162
+ // Only the non-bypass slugs are judged; b was dropped.
163
+ expect(result.trace.judged).toEqual(["a", "b", "c"]);
164
+ expect(result.trace.dropped).toEqual(["b"]);
165
+ expect(result.failureReason).toBeUndefined();
166
+ expect(calls).toHaveLength(1);
167
+ // The bypass slug `x` was never shown to the model.
168
+ const userText = calls[0].messages[0].content
169
+ .map((b) => (b.type === "text" ? b.text : ""))
170
+ .join("\n");
171
+ expect(userText).not.toContain("x");
172
+ });
173
+
174
+ test("forces tool_choice on filter_dense_hits and surfaces judged candidates", async () => {
175
+ const calls: ProviderCall[] = [];
176
+ const provider = makeProvider(
177
+ filterToolResponse({ keep_slugs: ["a"] }),
178
+ calls,
179
+ );
180
+
181
+ await filterDenseHits({
182
+ input: makeInput({ nowText: "NOW-MARKER" }),
183
+ dense: denseResult(["a", "b"]),
184
+ sticky: new Set(),
185
+ bypass: new Set(),
186
+ provider,
187
+ });
188
+
189
+ const call = calls[0];
190
+ expect(call.options?.config?.tool_choice).toEqual({
191
+ type: "tool",
192
+ name: "filter_dense_hits",
193
+ });
194
+ expect(call.options?.config?.callSite).toBe("memoryV3Filter");
195
+ expect(call.tools?.[0].name).toBe("filter_dense_hits");
196
+ const userText = call.messages[0].content
197
+ .map((b) => (b.type === "text" ? b.text : ""))
198
+ .join("\n");
199
+ expect(userText).toContain("NOW-MARKER");
200
+ expect(userText).toContain("a");
201
+ expect(userText).toContain("b");
202
+ });
203
+
204
+ test("includes the just-arrived turn so the filter judges query-aware", async () => {
205
+ const calls: ProviderCall[] = [];
206
+ const provider = makeProvider(
207
+ filterToolResponse({ keep_slugs: ["a"] }),
208
+ calls,
209
+ );
210
+
211
+ await filterDenseHits({
212
+ input: makeInput({
213
+ recentTurnPairs: [
214
+ {
215
+ assistantMessage: "an earlier reply",
216
+ userMessage: "tell me about the people you know",
217
+ },
218
+ ],
219
+ }),
220
+ dense: denseResult(["a", "b"]),
221
+ sticky: new Set(),
222
+ bypass: new Set(),
223
+ provider,
224
+ });
225
+
226
+ const userText = calls[0].messages[0].content
227
+ .map((b) => (b.type === "text" ? b.text : ""))
228
+ .join("\n");
229
+ // The filter must judge against the user's actual question, not just NOW.
230
+ expect(userText).toContain("<last_turn>");
231
+ expect(userText).toContain("tell me about the people you know");
232
+ expect(userText).toContain("an earlier reply");
233
+ });
234
+
235
+ test("drops a model-kept slug outside the judged set", async () => {
236
+ const calls: ProviderCall[] = [];
237
+ const provider = makeProvider(
238
+ filterToolResponse({ keep_slugs: ["a", "ghost"] }),
239
+ calls,
240
+ );
241
+
242
+ const result = await filterDenseHits({
243
+ input: makeInput(),
244
+ dense: denseResult(["a", "b"]),
245
+ sticky: new Set(),
246
+ bypass: new Set(),
247
+ provider,
248
+ });
249
+
250
+ expect(result.kept).toEqual(["a"]);
251
+ expect(result.trace.dropped).toEqual(["b"]);
252
+ });
253
+
254
+ test("forwards an abort signal to the provider call", async () => {
255
+ const calls: ProviderCall[] = [];
256
+ const controller = new AbortController();
257
+ controller.abort();
258
+ const provider = makeProvider(
259
+ filterToolResponse({ keep_slugs: ["a"] }),
260
+ calls,
261
+ );
262
+
263
+ // Aborted signal makes the stub throw → filter fails open (keep all).
264
+ const result = await filterDenseHits({
265
+ input: makeInput({ signal: controller.signal }),
266
+ dense: denseResult(["a", "b"]),
267
+ sticky: new Set(),
268
+ bypass: new Set(),
269
+ provider,
270
+ });
271
+
272
+ expect(calls[0].options?.signal).toBe(controller.signal);
273
+ expect([...result.kept].sort()).toEqual(["a", "b"]);
274
+ expect(result.failureReason).toBe("api_error");
275
+ });
276
+ });
277
+
278
+ describe("filterDenseHits — no LLM call", () => {
279
+ test("empty dense → no call, kept = bypass-relevant only", async () => {
280
+ const provider = makeNeverCalledProvider();
281
+
282
+ const result = await filterDenseHits({
283
+ input: makeInput(),
284
+ dense: denseResult([]),
285
+ sticky: new Set(["x"]),
286
+ bypass: new Set(["x"]),
287
+ provider,
288
+ });
289
+
290
+ expect(result.kept).toEqual(["x"]);
291
+ expect(result.trace).toEqual({ judged: [], dropped: [] });
292
+ expect(result.failureReason).toBeUndefined();
293
+ });
294
+
295
+ test("dense fully covered by bypass → no call (nothing to judge)", async () => {
296
+ const provider = makeNeverCalledProvider();
297
+
298
+ const result = await filterDenseHits({
299
+ input: makeInput(),
300
+ dense: denseResult(["x", "y"]),
301
+ sticky: new Set(["x", "y"]),
302
+ bypass: new Set(["x", "y"]),
303
+ provider,
304
+ });
305
+
306
+ expect([...result.kept].sort()).toEqual(["x", "y"]);
307
+ expect(result.trace).toEqual({ judged: [], dropped: [] });
308
+ });
309
+ });
310
+
311
+ describe("filterDenseHits — system prompt", () => {
312
+ test("uses the bundled default when no override is configured", async () => {
313
+ const calls: ProviderCall[] = [];
314
+ const provider = makeProvider(
315
+ filterToolResponse({ keep_slugs: ["a"] }),
316
+ calls,
317
+ );
318
+
319
+ await filterDenseHits({
320
+ input: makeInput(),
321
+ dense: denseResult(["a", "b"]),
322
+ sticky: new Set(),
323
+ bypass: new Set(),
324
+ provider,
325
+ });
326
+
327
+ expect(calls[0].systemPrompt).toBe(FILTER_SYSTEM_PROMPT);
328
+ });
329
+
330
+ test("uses the configured inline override as the system prompt", async () => {
331
+ const calls: ProviderCall[] = [];
332
+ const provider = makeProvider(
333
+ filterToolResponse({ keep_slugs: ["a"] }),
334
+ calls,
335
+ );
336
+
337
+ const override = "CUSTOM FILTER PROMPT — keep only the obvious matches.";
338
+ await filterDenseHits({
339
+ input: makeInput({ config: configWithFilterOverride(override) }),
340
+ dense: denseResult(["a", "b"]),
341
+ sticky: new Set(),
342
+ bypass: new Set(),
343
+ provider,
344
+ });
345
+
346
+ expect(calls[0].systemPrompt).toBe(override);
347
+ expect(calls[0].systemPrompt).not.toBe(FILTER_SYSTEM_PROMPT);
348
+ });
349
+ });
350
+
351
+ describe("filterDenseHits — fail-open", () => {
352
+ test("provider === null keeps all dense with failureReason no_provider", async () => {
353
+ const result = await filterDenseHits({
354
+ input: makeInput(),
355
+ dense: denseResult(["a", "b", "c"]),
356
+ sticky: new Set(),
357
+ bypass: new Set(),
358
+ provider: null,
359
+ });
360
+
361
+ expect([...result.kept].sort()).toEqual(["a", "b", "c"]);
362
+ expect(result.trace.judged).toEqual(["a", "b", "c"]);
363
+ expect(result.trace.dropped).toEqual([]);
364
+ expect(result.failureReason).toBe("no_provider");
365
+ });
366
+
367
+ test("fail-open still unions bypass slugs into kept", async () => {
368
+ const result = await filterDenseHits({
369
+ input: makeInput(),
370
+ dense: denseResult(["a", "b", "x"]),
371
+ sticky: new Set(["x"]),
372
+ bypass: new Set(["x"]),
373
+ provider: null,
374
+ });
375
+
376
+ // bypass `x` first, then the judged-but-kept-by-fail-open slugs a, b.
377
+ expect(result.kept).toEqual(["x", "a", "b"]);
378
+ expect(result.trace.judged).toEqual(["a", "b"]);
379
+ });
380
+
381
+ test("provider throw keeps all dense (failureReason api_error)", async () => {
382
+ const result = await filterDenseHits({
383
+ input: makeInput(),
384
+ dense: denseResult(["a", "b"]),
385
+ sticky: new Set(),
386
+ bypass: new Set(),
387
+ provider: makeThrowingProvider(),
388
+ });
389
+
390
+ expect([...result.kept].sort()).toEqual(["a", "b"]);
391
+ expect(result.failureReason).toBe("api_error");
392
+ });
393
+
394
+ test("missing tool_use block keeps all dense (failureReason tool_use_missing)", async () => {
395
+ const calls: ProviderCall[] = [];
396
+ const result = await filterDenseHits({
397
+ input: makeInput(),
398
+ dense: denseResult(["a", "b"]),
399
+ sticky: new Set(),
400
+ bypass: new Set(),
401
+ provider: makeProvider(textOnlyResponse(), calls),
402
+ });
403
+
404
+ expect([...result.kept].sort()).toEqual(["a", "b"]);
405
+ expect(result.failureReason).toBe("tool_use_missing");
406
+ });
407
+
408
+ test("schema-mismatched tool input keeps all dense (failureReason schema_mismatch)", async () => {
409
+ const calls: ProviderCall[] = [];
410
+ const result = await filterDenseHits({
411
+ input: makeInput(),
412
+ dense: denseResult(["a", "b"]),
413
+ sticky: new Set(),
414
+ bypass: new Set(),
415
+ // `keep_slugs` is required; missing it fails the Zod schema.
416
+ provider: makeProvider(filterToolResponse({ wrong_key: ["a"] }), calls),
417
+ });
418
+
419
+ expect([...result.kept].sort()).toEqual(["a", "b"]);
420
+ expect(result.failureReason).toBe("schema_mismatch");
421
+ });
422
+ });
423
+
424
+ describe("filterDenseHits — capture", () => {
425
+ test("emits one record with the filter's input + raw response", async () => {
426
+ const calls: ProviderCall[] = [];
427
+ const captured: Omit<LlmCallRecord, "pass">[] = [];
428
+ const provider = makeProvider(
429
+ filterToolResponse({ keep_slugs: ["a"] }),
430
+ calls,
431
+ );
432
+
433
+ await filterDenseHits({
434
+ input: makeInput(),
435
+ dense: denseResult(["a", "b"]),
436
+ sticky: new Set(),
437
+ bypass: new Set(),
438
+ provider,
439
+ capture: (record) => captured.push(record),
440
+ });
441
+
442
+ expect(captured).toHaveLength(1);
443
+ const rec = captured[0]!;
444
+ expect(rec.lane).toBe("filter");
445
+ expect(rec.callSite).toBe("memoryV3Filter");
446
+ expect(rec.request.tools[0]!.name).toBe("filter_dense_hits");
447
+ expect(rec.response.stopReason).toBe("tool_use");
448
+ });
449
+
450
+ test("emits nothing when there is nothing to judge (no LLM call)", async () => {
451
+ const captured: Omit<LlmCallRecord, "pass">[] = [];
452
+ await filterDenseHits({
453
+ input: makeInput(),
454
+ dense: denseResult([]),
455
+ sticky: new Set(),
456
+ bypass: new Set(),
457
+ capture: (record) => captured.push(record),
458
+ });
459
+ expect(captured).toHaveLength(0);
460
+ });
461
+ });
462
+
463
+ describe("filterDenseHits — reasoning field", () => {
464
+ test("exposes an optional reasoning property in the forced tool schema", async () => {
465
+ const calls: ProviderCall[] = [];
466
+ const provider = makeProvider(
467
+ filterToolResponse({ keep_slugs: ["a"] }),
468
+ calls,
469
+ );
470
+
471
+ await filterDenseHits({
472
+ input: makeInput(),
473
+ dense: denseResult(["a", "b"]),
474
+ sticky: new Set(),
475
+ bypass: new Set(),
476
+ provider,
477
+ });
478
+
479
+ const schema = calls[0].tools![0].input_schema as {
480
+ properties: Record<string, { type?: string }>;
481
+ required?: string[];
482
+ };
483
+ expect(schema.properties.reasoning?.type).toBe("string");
484
+ // Reasoning is purely additive — the model may omit it.
485
+ expect(schema.required ?? []).not.toContain("reasoning");
486
+ });
487
+
488
+ test("accepts model-supplied reasoning without altering the kept set", async () => {
489
+ const calls: ProviderCall[] = [];
490
+ const provider = makeProvider(
491
+ filterToolResponse({
492
+ keep_slugs: ["c", "a"],
493
+ reasoning:
494
+ "kept the cross-domain associations, dropped the near-duplicate",
495
+ }),
496
+ calls,
497
+ );
498
+
499
+ const result = await filterDenseHits({
500
+ input: makeInput(),
501
+ dense: denseResult(["a", "b", "c"]),
502
+ sticky: new Set(),
503
+ bypass: new Set(),
504
+ provider,
505
+ });
506
+
507
+ // Reasoning is ignored by control flow: kept/dropped unchanged.
508
+ expect(result.kept).toEqual(["c", "a"]);
509
+ expect(result.trace.dropped).toEqual(["b"]);
510
+ expect(result.failureReason).toBeUndefined();
511
+ });
512
+ });