@vellumai/assistant 0.8.0 → 0.8.1

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 (692) hide show
  1. package/AGENTS.md +11 -0
  2. package/Dockerfile +5 -4
  3. package/README.md +2 -2
  4. package/docker-entrypoint.sh +16 -0
  5. package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +420 -0
  6. package/eslint-rules/cli-no-daemon-internals.js +283 -0
  7. package/eslint.config.mjs +12 -0
  8. package/knip.json +2 -1
  9. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -1
  10. package/openapi.yaml +4847 -1698
  11. package/package.json +3 -1
  12. package/scripts/generate-openapi.ts +52 -4
  13. package/scripts/sync-llm-catalog.ts +165 -0
  14. package/scripts/sync-web-search-catalog.ts +107 -0
  15. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +169 -0
  16. package/src/__tests__/agent-loop-override-profile.test.ts +26 -1
  17. package/src/__tests__/anthropic-provider.test.ts +92 -2
  18. package/src/__tests__/app-control-flow.test.ts +7 -0
  19. package/src/__tests__/assistant-events-sse-shed.test.ts +232 -0
  20. package/src/__tests__/avatar-identity-sync.test.ts +87 -0
  21. package/src/__tests__/background-workers-disk-pressure.test.ts +11 -22
  22. package/src/__tests__/btw-routes.test.ts +1 -0
  23. package/src/__tests__/call-site-routing-provider.test.ts +172 -45
  24. package/src/__tests__/cancel-resolves-conversation-key.test.ts +44 -3
  25. package/src/__tests__/channel-policy.test.ts +12 -0
  26. package/src/__tests__/checker.test.ts +89 -0
  27. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +35 -7
  28. package/src/__tests__/compact-event-conversation-id-guard.test.ts +33 -5
  29. package/src/__tests__/compaction-strip-metadata-clear.test.ts +26 -1
  30. package/src/__tests__/config-loader-backfill.test.ts +526 -102
  31. package/src/__tests__/config-loader-corrupt.test.ts +68 -0
  32. package/src/__tests__/config-loader-platform-defaults.test.ts +77 -23
  33. package/src/__tests__/config-schema-cmd.test.ts +63 -29
  34. package/src/__tests__/config-schema.test.ts +14 -3
  35. package/src/__tests__/config-set-platform-guard.test.ts +75 -152
  36. package/src/__tests__/config-set-route.test.ts +198 -0
  37. package/src/__tests__/config-watcher.test.ts +6 -0
  38. package/src/__tests__/contacts-tools.test.ts +51 -199
  39. package/src/__tests__/context-search-agent-protocol.test.ts +21 -2
  40. package/src/__tests__/context-search-agent-runner.test.ts +22 -138
  41. package/src/__tests__/context-search-conversations-source.test.ts +42 -16
  42. package/src/__tests__/context-search-fanout.test.ts +20 -157
  43. package/src/__tests__/context-search-memory-v2-source.test.ts +3 -3
  44. package/src/__tests__/context-search-types.test.ts +7 -2
  45. package/src/__tests__/context-window-manager.test.ts +389 -1
  46. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  47. package/src/__tests__/conversation-crud-inference-profile.test.ts +100 -0
  48. package/src/__tests__/conversation-error.test.ts +38 -0
  49. package/src/__tests__/conversation-fork-crud.test.ts +241 -1
  50. package/src/__tests__/conversation-inference-profile-route.test.ts +14 -14
  51. package/src/__tests__/conversation-init.benchmark.test.ts +1 -0
  52. package/src/__tests__/conversation-lifecycle.test.ts +124 -0
  53. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +100 -1
  54. package/src/__tests__/conversation-process-callsite.test.ts +21 -1
  55. package/src/__tests__/conversation-runtime-assembly.test.ts +4 -4
  56. package/src/__tests__/conversation-slash-commands.test.ts +194 -2
  57. package/src/__tests__/conversation-surfaces-app-control.test.ts +323 -3
  58. package/src/__tests__/credential-security-invariants.test.ts +5 -6
  59. package/src/__tests__/daemon-credential-client.test.ts +56 -1
  60. package/src/__tests__/db-activation-state-fk-cascade.test.ts +132 -0
  61. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +37 -0
  62. package/src/__tests__/db-memory-graph-event-date-repair.test.ts +43 -20
  63. package/src/__tests__/db-proxy-transaction.test.ts +206 -0
  64. package/src/__tests__/external-plugin-loader.test.ts +458 -0
  65. package/src/__tests__/filing-service.test.ts +23 -3
  66. package/src/__tests__/fixtures/mock-chrome-extension.ts +5 -0
  67. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  68. package/src/__tests__/graph-extraction-event-date.test.ts +34 -0
  69. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -8
  70. package/src/__tests__/heartbeat-disk-pressure.test.ts +21 -8
  71. package/src/__tests__/heartbeat-service.test.ts +50 -233
  72. package/src/__tests__/history-repair.test.ts +89 -0
  73. package/src/__tests__/host-app-control-proxy.test.ts +109 -1
  74. package/src/__tests__/host-app-control-routes.test.ts +247 -1
  75. package/src/__tests__/host-browser-proxy.test.ts +416 -20
  76. package/src/__tests__/host-browser-routes.test.ts +325 -33
  77. package/src/__tests__/host-proxy-preactivation.test.ts +211 -0
  78. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +246 -0
  79. package/src/__tests__/inference-profile-reaper.test.ts +154 -0
  80. package/src/__tests__/inference-profile-session-handler.test.ts +398 -0
  81. package/src/__tests__/inference-profile-session-ipc.test.ts +236 -0
  82. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -1
  83. package/src/__tests__/install-skill-routing.test.ts +2 -2
  84. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +15 -0
  85. package/src/__tests__/llm-callsite-catalog.test.ts +20 -1
  86. package/src/__tests__/llm-catalog-parity.test.ts +146 -0
  87. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +188 -0
  88. package/src/__tests__/llm-request-log-source-factory.test.ts +124 -0
  89. package/src/__tests__/llm-resolver.test.ts +46 -0
  90. package/src/__tests__/managed-profile-guard.test.ts +131 -2
  91. package/src/__tests__/mcp-auth-routes.test.ts +1 -0
  92. package/src/__tests__/mcp-cli.test.ts +182 -220
  93. package/src/__tests__/mcp-health-check.test.ts +56 -27
  94. package/src/__tests__/memory-jobs-worker-lanes.test.ts +18 -11
  95. package/src/__tests__/message-complete-display-id.test.ts +175 -0
  96. package/src/__tests__/notification-platform-adapter.test.ts +229 -0
  97. package/src/__tests__/oauth-cli.test.ts +38 -2009
  98. package/src/__tests__/oauth-commands-routes.test.ts +711 -0
  99. package/src/__tests__/oauth-connect-routes.test.ts +174 -11
  100. package/src/__tests__/oauth-providers-routes.test.ts +14 -10
  101. package/src/__tests__/openai-responses-cutover-guard.test.ts +33 -12
  102. package/src/__tests__/openai-responses-provider.test.ts +17 -0
  103. package/src/__tests__/plugin-bootstrap.test.ts +31 -2
  104. package/src/__tests__/plugin-route-contribution.test.ts +31 -3
  105. package/src/__tests__/plugin-tool-contribution.test.ts +31 -3
  106. package/src/__tests__/plugin-types.test.ts +13 -11
  107. package/src/__tests__/process-message-background-slack.test.ts +46 -0
  108. package/src/__tests__/profile-entry-status.test.ts +43 -0
  109. package/src/__tests__/provider-managed-proxy-integration.test.ts +12 -4
  110. package/src/__tests__/provider-registry-ollama.test.ts +12 -4
  111. package/src/__tests__/provider-send-message-override-profile.test.ts +10 -4
  112. package/src/__tests__/relay-server.test.ts +118 -0
  113. package/src/__tests__/retry-thinking-tool-choice.test.ts +15 -0
  114. package/src/__tests__/schedule-retry.test.ts +56 -4
  115. package/src/__tests__/schedule-routes.test.ts +104 -0
  116. package/src/__tests__/scheduler-disk-pressure.test.ts +0 -4
  117. package/src/__tests__/scheduler-recurrence.test.ts +87 -34
  118. package/src/__tests__/scheduler-reuse-conversation.test.ts +161 -5
  119. package/src/__tests__/scheduler-wake.test.ts +0 -63
  120. package/src/__tests__/secret-allowlist.test.ts +1 -0
  121. package/src/__tests__/secret-routes-managed-proxy.test.ts +12 -4
  122. package/src/__tests__/shell-credential-ref.test.ts +95 -3
  123. package/src/__tests__/shell-tool-proxy-mode.test.ts +14 -0
  124. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  125. package/src/__tests__/skill-load-tool.test.ts +2 -4
  126. package/src/__tests__/subagent-call-site-routing.test.ts +78 -16
  127. package/src/__tests__/suggestion-routes.test.ts +3 -3
  128. package/src/__tests__/sync-message-contract.test.ts +63 -0
  129. package/src/__tests__/task-scheduler.test.ts +88 -23
  130. package/src/__tests__/update-bulletin-job.test.ts +96 -193
  131. package/src/__tests__/usage-cli.test.ts +11 -73
  132. package/src/__tests__/user-plugin-loader.test.ts +145 -0
  133. package/src/__tests__/vercel-config.test.ts +168 -0
  134. package/src/__tests__/web-search-catalog-parity.test.ts +86 -0
  135. package/src/__tests__/web-search.test.ts +303 -2
  136. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +1 -21
  137. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +58 -0
  138. package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +53 -20
  139. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +191 -0
  140. package/src/__tests__/workspace-migration-076-drop-services-inference-mode.test.ts +211 -0
  141. package/src/__tests__/workspace-migration-077-seed-memory-router-callsite.test.ts +174 -0
  142. package/src/__tests__/workspace-migration-079-home-feed-notification-only.test.ts +323 -0
  143. package/src/__tests__/workspace-migration-080-restrict-vercel-api-token-metadata.test.ts +299 -0
  144. package/src/__tests__/workspace-migration-081-backfill-bash-allowed-tools.test.ts +410 -0
  145. package/src/__tests__/workspace-migration-082-backfill-managed-profile-labels.test.ts +268 -0
  146. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +3 -3
  147. package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +115 -0
  148. package/src/acp/__tests__/helpers/which-stub.ts +4 -2
  149. package/src/acp/resolve-agent.test.ts +25 -0
  150. package/src/acp/resolve-agent.ts +13 -2
  151. package/src/acp/session-manager.ts +14 -0
  152. package/src/approvals/guardian-request-resolvers.ts +32 -87
  153. package/src/calls/relay-server.ts +35 -0
  154. package/src/calls/relay-setup-router.ts +36 -0
  155. package/src/calls/types.ts +1 -0
  156. package/src/calls/voice-session-bridge.ts +23 -4
  157. package/src/channels/config.ts +14 -1
  158. package/src/channels/types.ts +1 -0
  159. package/src/cli/AGENTS.md +164 -4
  160. package/src/cli/__tests__/notifications.test.ts +54 -0
  161. package/src/cli/commands/__tests__/avatar.test.ts +540 -0
  162. package/src/cli/commands/__tests__/backup.test.ts +236 -776
  163. package/src/cli/commands/__tests__/cache.test.ts +1 -1
  164. package/src/cli/commands/__tests__/changelog.test.ts +593 -0
  165. package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +503 -0
  166. package/src/cli/commands/__tests__/conversations-import.test.ts +515 -0
  167. package/src/cli/commands/__tests__/domain-register.test.ts +140 -167
  168. package/src/cli/commands/__tests__/domain-status.test.ts +137 -76
  169. package/src/cli/commands/__tests__/email-attachment.test.ts +314 -337
  170. package/src/cli/commands/__tests__/email-core.test.ts +579 -0
  171. package/src/cli/commands/__tests__/image-generation.test.ts +87 -824
  172. package/src/cli/commands/__tests__/inference-send.test.ts +30 -266
  173. package/src/cli/commands/__tests__/inference-session.test.ts +423 -0
  174. package/src/cli/commands/__tests__/memory-v2.test.ts +81 -110
  175. package/src/cli/commands/__tests__/skills.test.ts +563 -0
  176. package/src/cli/commands/__tests__/status.test.ts +249 -0
  177. package/src/cli/commands/__tests__/stt.test.ts +320 -0
  178. package/src/cli/commands/__tests__/tts-synthesize.test.ts +4 -603
  179. package/src/cli/commands/__tests__/tts.test.ts +321 -0
  180. package/src/cli/commands/__tests__/webhooks.test.ts +86 -511
  181. package/src/cli/commands/attachment.ts +8 -3
  182. package/src/cli/commands/audit.ts +95 -64
  183. package/src/cli/commands/auth.ts +61 -58
  184. package/src/cli/commands/avatar.ts +276 -390
  185. package/src/cli/commands/backup.ts +409 -505
  186. package/src/cli/commands/bash.ts +9 -5
  187. package/src/cli/commands/browser.ts +28 -9
  188. package/src/cli/commands/cache.ts +9 -4
  189. package/src/cli/commands/changelog.ts +414 -0
  190. package/src/cli/commands/channel-verification-sessions.ts +238 -317
  191. package/src/cli/commands/clients.ts +8 -3
  192. package/src/cli/commands/completions.ts +9 -9
  193. package/src/cli/commands/config.ts +102 -72
  194. package/src/cli/commands/contacts.ts +575 -696
  195. package/src/cli/commands/conversations-defer.ts +17 -69
  196. package/src/cli/commands/conversations-import.ts +90 -253
  197. package/src/cli/commands/conversations.ts +346 -436
  198. package/src/cli/commands/credential-execution.ts +9 -6
  199. package/src/cli/commands/credentials.ts +456 -736
  200. package/src/cli/commands/domain.ts +128 -206
  201. package/src/cli/commands/email.ts +606 -794
  202. package/src/cli/commands/gateway.ts +8 -1
  203. package/src/cli/commands/image-generation.ts +157 -205
  204. package/src/cli/commands/inference-providers.ts +352 -0
  205. package/src/cli/commands/inference-session.ts +415 -0
  206. package/src/cli/commands/inference.ts +87 -65
  207. package/src/cli/commands/keys.ts +8 -3
  208. package/src/cli/commands/mcp.ts +103 -287
  209. package/src/cli/commands/memory-v2.ts +162 -516
  210. package/src/cli/commands/notifications.ts +33 -7
  211. package/src/cli/commands/oauth/apps.ts +292 -261
  212. package/src/cli/commands/oauth/connect.ts +176 -297
  213. package/src/cli/commands/oauth/disconnect.ts +16 -215
  214. package/src/cli/commands/oauth/index.ts +49 -45
  215. package/src/cli/commands/oauth/mode.ts +43 -199
  216. package/src/cli/commands/oauth/ping.ts +17 -125
  217. package/src/cli/commands/oauth/providers.ts +732 -921
  218. package/src/cli/commands/oauth/request.ts +60 -350
  219. package/src/cli/commands/oauth/shared.ts +11 -121
  220. package/src/cli/commands/oauth/status.ts +31 -121
  221. package/src/cli/commands/oauth/token.ts +13 -55
  222. package/src/cli/commands/pending.ts +19 -10
  223. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +133 -183
  224. package/src/cli/commands/platform/__tests__/connect.test.ts +66 -181
  225. package/src/cli/commands/platform/__tests__/disconnect.test.ts +71 -227
  226. package/src/cli/commands/platform/__tests__/status.test.ts +169 -287
  227. package/src/cli/commands/platform/connect.ts +16 -80
  228. package/src/cli/commands/platform/disconnect.ts +14 -112
  229. package/src/cli/commands/platform/index.ts +177 -246
  230. package/src/cli/commands/routes.ts +153 -336
  231. package/src/cli/commands/sequence.ts +316 -360
  232. package/src/cli/commands/skills.ts +449 -671
  233. package/src/cli/commands/status.ts +58 -37
  234. package/src/cli/commands/stt.ts +94 -262
  235. package/src/cli/commands/task.ts +14 -40
  236. package/src/cli/commands/trust.ts +8 -3
  237. package/src/cli/commands/tts.ts +162 -167
  238. package/src/cli/commands/ui.ts +35 -42
  239. package/src/cli/commands/usage.ts +188 -126
  240. package/src/cli/commands/watchers.ts +8 -3
  241. package/src/cli/commands/webhooks.ts +99 -193
  242. package/src/cli/lib/__tests__/register-command.test.ts +85 -0
  243. package/src/cli/lib/daemon-credential-client.ts +4 -5
  244. package/src/cli/lib/nested-value.ts +44 -0
  245. package/src/cli/lib/open-browser.ts +36 -0
  246. package/src/cli/lib/register-command.ts +19 -0
  247. package/src/cli/lib/time-ago.ts +34 -0
  248. package/src/cli/program.ts +2 -4
  249. package/src/cli/utils/__tests__/conversation-id.test.ts +66 -0
  250. package/src/cli/utils/__tests__/parse-duration.test.ts +49 -0
  251. package/src/cli/utils/conversation-id.ts +30 -0
  252. package/src/cli/utils/parse-duration.ts +41 -0
  253. package/src/config/acp-defaults.test.ts +5 -1
  254. package/src/config/acp-defaults.ts +11 -4
  255. package/src/config/bundled-skills/acp/TOOLS.json +2 -2
  256. package/src/config/bundled-skills/app-control/TOOLS.json +32 -0
  257. package/src/config/bundled-skills/contacts/SKILL.md +12 -45
  258. package/src/config/bundled-skills/contacts/TOOLS.json +0 -57
  259. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +0 -12
  260. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +0 -58
  261. package/src/config/bundled-tool-registry.ts +0 -2
  262. package/src/config/feature-flag-registry.json +16 -0
  263. package/src/config/llm-resolver.ts +16 -1
  264. package/src/config/loader.ts +76 -14
  265. package/src/config/raw-config-utils.ts +2 -30
  266. package/src/config/schema.ts +4 -0
  267. package/src/config/schemas/__tests__/memory-v2.test.ts +49 -0
  268. package/src/config/schemas/call-site-catalog.ts +29 -7
  269. package/src/config/schemas/llm-request-logs.ts +57 -0
  270. package/src/config/schemas/llm.ts +52 -2
  271. package/src/config/schemas/memory-retrospective.ts +48 -0
  272. package/src/config/schemas/memory-v2.ts +32 -1
  273. package/src/config/schemas/memory.ts +4 -0
  274. package/src/config/schemas/services.ts +15 -12
  275. package/src/config/seed-inference-profiles.ts +195 -134
  276. package/src/contacts/contact-store.ts +0 -61
  277. package/src/context/window-manager.ts +191 -5
  278. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +79 -0
  279. package/src/daemon/__tests__/conversation-tool-setup.test.ts +109 -4
  280. package/src/daemon/__tests__/daemon-skill-host.test.ts +10 -4
  281. package/src/daemon/approval-generators.ts +23 -29
  282. package/src/daemon/config-watcher.ts +2 -0
  283. package/src/daemon/conversation-agent-loop-handlers.ts +24 -0
  284. package/src/daemon/conversation-agent-loop.ts +127 -97
  285. package/src/daemon/conversation-error.ts +21 -0
  286. package/src/daemon/conversation-lifecycle.ts +46 -5
  287. package/src/daemon/conversation-process.ts +36 -19
  288. package/src/daemon/conversation-runtime-assembly.ts +14 -5
  289. package/src/daemon/conversation-slash.ts +175 -23
  290. package/src/daemon/conversation-store.ts +17 -10
  291. package/src/daemon/conversation-surfaces.ts +76 -12
  292. package/src/daemon/conversation-tool-setup.ts +24 -14
  293. package/src/daemon/conversation.ts +48 -9
  294. package/src/daemon/external-plugins-bootstrap.ts +18 -8
  295. package/src/daemon/guardian-action-generators.ts +7 -22
  296. package/src/daemon/handlers/config-model.ts +8 -126
  297. package/src/daemon/handlers/config-slack-channel.ts +10 -7
  298. package/src/daemon/handlers/config-vercel.ts +3 -1
  299. package/src/daemon/handlers/skills.ts +84 -5
  300. package/src/daemon/history-repair.ts +33 -6
  301. package/src/daemon/host-app-control-proxy.ts +44 -19
  302. package/src/daemon/host-bash-proxy.ts +85 -158
  303. package/src/daemon/host-browser-proxy.ts +96 -35
  304. package/src/daemon/host-proxy-base.ts +13 -1
  305. package/src/daemon/host-proxy-preactivation.ts +25 -1
  306. package/src/daemon/identity-helpers.ts +19 -0
  307. package/src/daemon/lifecycle.ts +42 -43
  308. package/src/daemon/meet-host-supervisor.ts +15 -15
  309. package/src/daemon/memory-v2-startup.ts +9 -2
  310. package/src/daemon/message-protocol.ts +6 -0
  311. package/src/daemon/message-types/bookmarks.ts +18 -0
  312. package/src/daemon/message-types/conversations.ts +12 -9
  313. package/src/daemon/message-types/messages.ts +9 -1
  314. package/src/daemon/message-types/sync.ts +60 -0
  315. package/src/daemon/pkb-reminder-builder.test.ts +54 -13
  316. package/src/daemon/pkb-reminder-builder.ts +21 -7
  317. package/src/daemon/process-message.ts +56 -23
  318. package/src/daemon/server.ts +23 -18
  319. package/src/daemon/shutdown-handlers.ts +0 -2
  320. package/src/daemon/tool-setup-types.ts +9 -0
  321. package/src/daemon/tool-side-effects.ts +6 -4
  322. package/src/daemon/wake-target-adapter.ts +11 -0
  323. package/src/export/transcript-formatter.ts +61 -2
  324. package/src/filing/filing-service.ts +40 -53
  325. package/src/heartbeat/__tests__/heartbeat-service.test.ts +359 -0
  326. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  327. package/src/heartbeat/heartbeat-service.ts +148 -127
  328. package/src/home/__tests__/feed-types.test.ts +63 -131
  329. package/src/home/__tests__/feed-writer.test.ts +77 -278
  330. package/src/home/__tests__/post-connect-feed.test.ts +9 -12
  331. package/src/home/feed-types.ts +19 -73
  332. package/src/home/feed-writer.ts +25 -156
  333. package/src/home/post-connect-feed.ts +1 -3
  334. package/src/ipc/__tests__/cli-ipc.test.ts +2 -0
  335. package/src/ipc/__tests__/email-ipc.test.ts +506 -0
  336. package/src/ipc/__tests__/exit-helper.test.ts +104 -0
  337. package/src/ipc/__tests__/streaming-client.test.ts +237 -0
  338. package/src/ipc/__tests__/streaming-framing.test.ts +142 -0
  339. package/src/ipc/assistant-server.ts +55 -6
  340. package/src/ipc/cli-client.ts +370 -50
  341. package/src/ipc/routes/db-proxy-transaction.ts +151 -0
  342. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +60 -0
  343. package/src/ipc/skill-routes/events.ts +30 -3
  344. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +46 -0
  345. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +1 -0
  346. package/src/live-voice/live-voice-session-manager.ts +11 -4
  347. package/src/live-voice/live-voice-session.ts +14 -6
  348. package/src/memory/__tests__/bookmark-crud.test.ts +258 -0
  349. package/src/memory/__tests__/bookmark-schema.test.ts +181 -0
  350. package/src/memory/__tests__/conversation-types.test.ts +36 -0
  351. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +130 -0
  352. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +177 -0
  353. package/src/memory/__tests__/memory-retrospective-job.test.ts +328 -0
  354. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +213 -0
  355. package/src/memory/__tests__/memory-retrospective-trigger-check.test.ts +90 -0
  356. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +69 -0
  357. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +3 -0
  358. package/src/memory/bookmark-crud.ts +179 -0
  359. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +31 -9
  360. package/src/memory/context-search/agent-protocol.ts +5 -1
  361. package/src/memory/context-search/agent-runner.ts +60 -85
  362. package/src/memory/context-search/limits.ts +1 -4
  363. package/src/memory/context-search/search.ts +23 -113
  364. package/src/memory/context-search/sources/conversations.ts +18 -6
  365. package/src/memory/context-search/sources/memory-v2.ts +39 -14
  366. package/src/memory/context-search/sources/memory.ts +7 -0
  367. package/src/memory/context-search/sources/workspace.ts +13 -10
  368. package/src/memory/context-search/types.ts +1 -1
  369. package/src/memory/conversation-bootstrap.ts +11 -0
  370. package/src/memory/conversation-crud.ts +312 -10
  371. package/src/memory/conversation-queries.ts +9 -5
  372. package/src/memory/conversation-title-service.ts +1 -0
  373. package/src/memory/conversation-types.ts +16 -0
  374. package/src/memory/db-init.ts +14 -0
  375. package/src/memory/embedding-backend.ts +2 -1
  376. package/src/memory/embedding-runtime-manager.ts +1 -2
  377. package/src/memory/graph/__tests__/remember-description.test.ts +55 -0
  378. package/src/memory/graph/conversation-graph-memory.ts +76 -5
  379. package/src/memory/graph/extraction.ts +4 -0
  380. package/src/memory/graph/graph-memory-state-store.ts +16 -3
  381. package/src/memory/graph/tool-handlers.ts +17 -7
  382. package/src/memory/graph/tools.ts +44 -5
  383. package/src/memory/indexer.ts +17 -0
  384. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +13 -15
  385. package/src/memory/jobs/embed-concept-page.ts +45 -9
  386. package/src/memory/jobs-store.ts +51 -1
  387. package/src/memory/jobs-worker.ts +52 -3
  388. package/src/memory/llm-request-log-source-clickhouse.ts +317 -0
  389. package/src/memory/llm-request-log-source-local.ts +26 -0
  390. package/src/memory/llm-request-log-source.ts +97 -0
  391. package/src/memory/llm-request-log-store.ts +1 -1
  392. package/src/memory/memory-retrospective-constants.ts +13 -0
  393. package/src/memory/memory-retrospective-enqueue.ts +114 -0
  394. package/src/memory/memory-retrospective-job.ts +351 -0
  395. package/src/memory/memory-retrospective-startup-cleanup.ts +108 -0
  396. package/src/memory/memory-retrospective-state.ts +162 -0
  397. package/src/memory/memory-retrospective-trigger-check.ts +91 -0
  398. package/src/memory/memory-v2-activation-log-store.ts +49 -5
  399. package/src/memory/memory-v2-concept-frequency.ts +4 -0
  400. package/src/memory/message-content.ts +38 -1
  401. package/src/memory/migrations/227-add-conversation-inference-profile.ts +6 -1
  402. package/src/memory/migrations/228-rename-inference-profile-snake-case.ts +20 -7
  403. package/src/memory/migrations/229-delete-private-conversations.test.ts +70 -1
  404. package/src/memory/migrations/229-delete-private-conversations.ts +12 -0
  405. package/src/memory/migrations/231-repair-memory-graph-event-dates.ts +16 -2
  406. package/src/memory/migrations/240-conversation-inference-profile-session.ts +25 -0
  407. package/src/memory/migrations/241-activation-state-fk-cascade.ts +50 -0
  408. package/src/memory/migrations/242-message-bookmarks.ts +38 -0
  409. package/src/memory/migrations/243-provider-connections.ts +68 -0
  410. package/src/memory/migrations/244-provider-connection-status-label.ts +23 -0
  411. package/src/memory/migrations/245-memory-retrospective-state.ts +36 -0
  412. package/src/memory/migrations/246-backfill-provider-connection-label.ts +81 -0
  413. package/src/memory/migrations/__tests__/244-provider-connection-status-label.test.ts +84 -0
  414. package/src/memory/migrations/__tests__/245-memory-retrospective-state.test.ts +125 -0
  415. package/src/memory/migrations/__tests__/246-backfill-provider-connection-label.test.ts +192 -0
  416. package/src/memory/migrations/index.ts +7 -0
  417. package/src/memory/published-pages-store.ts +16 -0
  418. package/src/memory/schema/bookmarks.ts +38 -0
  419. package/src/memory/schema/conversations.ts +2 -0
  420. package/src/memory/schema/index.ts +2 -0
  421. package/src/memory/schema/inference.ts +29 -0
  422. package/src/memory/schema/memory-core.ts +9 -0
  423. package/src/memory/search/semantic.ts +1 -4
  424. package/src/memory/v2/__tests__/__snapshots__/prompts-router.test.ts.snap +27 -0
  425. package/src/memory/v2/__tests__/activation-store.test.ts +5 -5
  426. package/src/memory/v2/__tests__/activation.test.ts +11 -4
  427. package/src/memory/v2/__tests__/backfill-jobs.test.ts +38 -21
  428. package/src/memory/v2/__tests__/consolidation-job.test.ts +123 -135
  429. package/src/memory/v2/__tests__/edge-index.test.ts +1 -1
  430. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +111 -0
  431. package/src/memory/v2/__tests__/injection.test.ts +628 -10
  432. package/src/memory/v2/__tests__/migration.test.ts +7 -3
  433. package/src/memory/v2/__tests__/page-index.test.ts +277 -0
  434. package/src/memory/v2/__tests__/page-store.test.ts +14 -1
  435. package/src/memory/v2/__tests__/prompts-router.test.ts +257 -0
  436. package/src/memory/v2/__tests__/qdrant.test.ts +72 -0
  437. package/src/memory/v2/__tests__/reranker.test.ts +4 -4
  438. package/src/memory/v2/__tests__/router.test.ts +516 -0
  439. package/src/memory/v2/__tests__/sim.test.ts +45 -1
  440. package/src/memory/v2/__tests__/skill-store.test.ts +58 -3
  441. package/src/memory/v2/__tests__/static-context.test.ts +7 -22
  442. package/src/memory/v2/__tests__/sweep-job.test.ts +95 -0
  443. package/src/memory/v2/activation-store.ts +34 -5
  444. package/src/memory/v2/activation.ts +40 -27
  445. package/src/memory/v2/backfill-jobs.ts +17 -84
  446. package/src/memory/v2/consolidation-job.ts +85 -78
  447. package/src/memory/v2/frontmatter-sweep.ts +91 -0
  448. package/src/memory/v2/injection.ts +440 -109
  449. package/src/memory/v2/migration.ts +117 -20
  450. package/src/memory/v2/page-index.ts +191 -0
  451. package/src/memory/v2/page-store.ts +3 -0
  452. package/src/memory/v2/prompts/consolidation.ts +9 -7
  453. package/src/memory/v2/prompts/router.ts +192 -0
  454. package/src/memory/v2/qdrant.ts +100 -87
  455. package/src/memory/v2/reranker.ts +14 -7
  456. package/src/memory/v2/router.ts +322 -0
  457. package/src/memory/v2/sim.ts +25 -12
  458. package/src/memory/v2/skill-store.ts +118 -29
  459. package/src/memory/v2/static-context.ts +16 -9
  460. package/src/memory/v2/sweep-job.ts +122 -96
  461. package/src/memory/v2/types.ts +10 -6
  462. package/src/memory/validation.ts +13 -0
  463. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +182 -0
  464. package/src/notifications/__tests__/home-feed-side-effect.test.ts +199 -0
  465. package/src/notifications/__tests__/signal-registry.test.ts +17 -0
  466. package/src/notifications/adapters/platform.ts +171 -0
  467. package/src/notifications/conversation-pairing.ts +2 -2
  468. package/src/notifications/copy-composer.ts +15 -0
  469. package/src/notifications/destination-resolver.ts +21 -0
  470. package/src/notifications/emit-signal.ts +28 -1
  471. package/src/notifications/home-feed-side-effect.ts +111 -0
  472. package/src/notifications/signal.ts +5 -0
  473. package/src/permissions/checker.ts +12 -0
  474. package/src/permissions/ipc-risk-types.ts +2 -0
  475. package/src/plugin-api/index.ts +13 -0
  476. package/src/plugin-api/package.json +12 -0
  477. package/src/plugin-api/types.ts +62 -0
  478. package/src/plugins/defaults/injectors.ts +19 -3
  479. package/src/plugins/external-plugin-loader.ts +294 -0
  480. package/src/plugins/types.ts +46 -30
  481. package/src/plugins/user-loader.ts +64 -41
  482. package/src/proactive-artifact/job.test.ts +12 -4
  483. package/src/proactive-artifact/job.ts +4 -0
  484. package/src/proactive-artifact/trigger-state.test.ts +9 -0
  485. package/src/proactive-artifact/trigger-state.ts +4 -0
  486. package/src/prompts/__tests__/system-prompt.test.ts +105 -0
  487. package/src/prompts/system-prompt.ts +22 -1
  488. package/src/prompts/update-bulletin-job.ts +61 -73
  489. package/src/providers/__tests__/dispatch-connection-routing.test.ts +279 -0
  490. package/src/providers/__tests__/inference.test.ts +288 -0
  491. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  492. package/src/providers/__tests__/provider-secret-catalog.test.ts +6 -0
  493. package/src/providers/__tests__/retry-callsite.test.ts +14 -32
  494. package/src/providers/__tests__/satellite-connection-routing.test.ts +510 -0
  495. package/src/providers/__tests__/search-provider-catalog.test.ts +80 -0
  496. package/src/providers/anthropic/client.ts +95 -26
  497. package/src/providers/call-site-routing.ts +94 -16
  498. package/src/providers/connection-resolution.ts +163 -0
  499. package/src/providers/inference/__tests__/connections-status-label.test.ts +250 -0
  500. package/src/providers/inference/adapter-factory.ts +173 -0
  501. package/src/providers/inference/auth.ts +112 -0
  502. package/src/providers/inference/backfill.ts +196 -0
  503. package/src/providers/inference/connections.ts +356 -0
  504. package/src/providers/inference/resolve-auth.ts +65 -0
  505. package/src/providers/model-catalog.ts +104 -6
  506. package/src/providers/openai/responses-provider.ts +4 -2
  507. package/src/providers/provider-env-vars.ts +17 -7
  508. package/src/providers/provider-secret-catalog.ts +49 -30
  509. package/src/providers/provider-send-message.ts +41 -20
  510. package/src/providers/registry.ts +143 -159
  511. package/src/providers/retry.ts +18 -10
  512. package/src/providers/search-provider-catalog.ts +121 -0
  513. package/src/runtime/AGENTS.md +18 -5
  514. package/src/runtime/__tests__/background-job-runner.test.ts +357 -0
  515. package/src/runtime/__tests__/pre-first-message-gate.test.ts +82 -0
  516. package/src/runtime/actor-trust-resolver.ts +32 -10
  517. package/src/runtime/agent-wake.ts +35 -6
  518. package/src/runtime/assistant-event-hub.ts +3 -85
  519. package/src/runtime/auth/route-policy.ts +303 -8
  520. package/src/runtime/auth/same-actor.ts +2 -0
  521. package/src/runtime/background-job-runner.ts +339 -0
  522. package/src/runtime/btw-sidechain.ts +1 -0
  523. package/src/runtime/http-router.ts +36 -1
  524. package/src/runtime/http-server.ts +31 -5
  525. package/src/runtime/http-types.ts +2 -0
  526. package/src/runtime/middleware/__tests__/request-logger.test.ts +162 -0
  527. package/src/runtime/middleware/request-logger.ts +62 -1
  528. package/src/runtime/pre-first-message-gate.ts +83 -0
  529. package/src/runtime/routes/__tests__/backup-routes.test.ts +8 -1
  530. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +251 -0
  531. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +142 -0
  532. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +315 -0
  533. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +189 -0
  534. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +15 -136
  535. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +736 -0
  536. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +4 -4
  537. package/src/runtime/routes/__tests__/stt-routes.test.ts +5 -1
  538. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +384 -0
  539. package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
  540. package/src/runtime/routes/acp-routes.ts +10 -8
  541. package/src/runtime/routes/app-management-routes.ts +228 -3
  542. package/src/runtime/routes/approval-routes.ts +0 -18
  543. package/src/runtime/routes/audit-routes.ts +43 -0
  544. package/src/runtime/routes/auth-routes.ts +72 -0
  545. package/src/runtime/routes/avatar-routes.ts +273 -20
  546. package/src/runtime/routes/backup-routes.ts +406 -2
  547. package/src/runtime/routes/bookmark-routes.ts +154 -0
  548. package/src/runtime/routes/channel-verification-routes.ts +2 -1
  549. package/src/runtime/routes/contact-routes.ts +0 -160
  550. package/src/runtime/routes/conversation-cli-routes.ts +192 -0
  551. package/src/runtime/routes/conversation-management-routes.ts +30 -43
  552. package/src/runtime/routes/conversation-query-routes.ts +334 -86
  553. package/src/runtime/routes/conversation-routes.ts +31 -10
  554. package/src/runtime/routes/conversations-import-routes.ts +229 -0
  555. package/src/runtime/routes/credential-routes.ts +540 -0
  556. package/src/runtime/routes/debug-routes.ts +2 -2
  557. package/src/runtime/routes/document-pdf-renderer.ts +5 -1
  558. package/src/runtime/routes/domain-routes.ts +167 -0
  559. package/src/runtime/routes/email-routes.ts +603 -0
  560. package/src/runtime/routes/errors.ts +2 -2
  561. package/src/runtime/routes/events-routes.ts +192 -0
  562. package/src/runtime/routes/home-feed-routes.ts +6 -78
  563. package/src/runtime/routes/host-app-control-routes.ts +44 -2
  564. package/src/runtime/routes/host-browser-routes.ts +103 -22
  565. package/src/runtime/routes/http-adapter.ts +2 -0
  566. package/src/runtime/routes/identity-routes.ts +5 -0
  567. package/src/runtime/routes/image-generation-routes.ts +99 -0
  568. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +137 -1
  569. package/src/runtime/routes/inbound-stages/background-dispatch.ts +87 -7
  570. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +156 -0
  571. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +22 -4
  572. package/src/runtime/routes/index.ts +36 -0
  573. package/src/runtime/routes/inference-profile-session-handler.ts +312 -0
  574. package/src/runtime/routes/inference-profile-session-reaper.ts +98 -0
  575. package/src/runtime/routes/inference-profile-session-routes.ts +146 -0
  576. package/src/runtime/routes/inference-provider-connection-routes.ts +317 -0
  577. package/src/runtime/routes/inference-send-routes.ts +115 -0
  578. package/src/runtime/routes/integrations/twilio.ts +1 -0
  579. package/src/runtime/routes/mcp-auth-routes.ts +283 -9
  580. package/src/runtime/routes/memory-v2-routes.ts +13 -398
  581. package/src/runtime/routes/notification-routes.ts +2 -0
  582. package/src/runtime/routes/oauth-apps.ts +112 -7
  583. package/src/runtime/routes/oauth-commands-routes.ts +1007 -0
  584. package/src/runtime/routes/oauth-connect-routes.ts +67 -5
  585. package/src/runtime/routes/oauth-providers.ts +298 -8
  586. package/src/runtime/routes/platform-routes.ts +336 -0
  587. package/src/runtime/routes/playground/inject-failures.ts +2 -1
  588. package/src/runtime/routes/playground/reset-circuit.ts +2 -1
  589. package/src/runtime/routes/playground/state.ts +2 -1
  590. package/src/runtime/routes/publish-routes.ts +221 -0
  591. package/src/runtime/routes/schedule-routes.ts +82 -0
  592. package/src/runtime/routes/sequence-routes.ts +291 -0
  593. package/src/runtime/routes/settings-routes.ts +2 -10
  594. package/src/runtime/routes/skills-routes.ts +31 -1
  595. package/src/runtime/routes/stt-routes.ts +240 -3
  596. package/src/runtime/routes/surface-action-routes.ts +43 -7
  597. package/src/runtime/routes/tts-routes.ts +67 -0
  598. package/src/runtime/routes/types.ts +32 -0
  599. package/src/runtime/routes/user-routes-cli.ts +243 -0
  600. package/src/runtime/routes/webhook-routes.ts +165 -0
  601. package/src/runtime/sync/resource-sync-events.ts +25 -0
  602. package/src/runtime/sync/sync-publisher.test.ts +105 -0
  603. package/src/runtime/sync/sync-publisher.ts +21 -0
  604. package/src/schedule/scheduler.ts +200 -123
  605. package/src/security/__tests__/provider-key-env-fallback.test.ts +12 -6
  606. package/src/security/secret-patterns.ts +3 -0
  607. package/src/sequence/engine.ts +38 -40
  608. package/src/subagent/manager.ts +20 -15
  609. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +206 -0
  610. package/src/tools/browser/browser-execution.ts +15 -4
  611. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +174 -0
  612. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +16 -13
  613. package/src/tools/browser/cdp-client/extension-cdp-client.ts +24 -1
  614. package/src/tools/browser/cdp-client/factory.ts +66 -5
  615. package/src/tools/browser/runtime-check.ts +77 -0
  616. package/src/tools/memory/register.test.ts +3 -3
  617. package/src/tools/memory/register.ts +9 -1
  618. package/src/tools/network/__tests__/web-search.test.ts +156 -0
  619. package/src/tools/network/web-search.ts +280 -37
  620. package/src/tools/permission-checker.ts +13 -5
  621. package/src/tools/subagent/spawn.ts +3 -3
  622. package/src/tools/terminal/shell.ts +44 -0
  623. package/src/usage/attribution.ts +3 -2
  624. package/src/util/pricing.ts +86 -160
  625. package/src/watcher/__tests__/engine.test.ts +301 -0
  626. package/src/watcher/constants.ts +7 -0
  627. package/src/watcher/engine.ts +90 -90
  628. package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +6 -9
  629. package/src/workspace/migrations/054-seed-recall-callsite.ts +10 -1
  630. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +28 -4
  631. package/src/workspace/migrations/069-seed-onboarding-threads.ts +8 -2
  632. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +104 -0
  633. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +93 -0
  634. package/src/workspace/migrations/074-drop-deprecated-secret-detection-keys.ts +117 -0
  635. package/src/workspace/migrations/075-memory-v2-bm25-b-default-reembed.ts +61 -0
  636. package/src/workspace/migrations/076-drop-services-inference-mode.ts +62 -0
  637. package/src/workspace/migrations/077-seed-memory-router-callsite.ts +89 -0
  638. package/src/workspace/migrations/078-release-notes-tavily-web-search.ts +66 -0
  639. package/src/workspace/migrations/079-home-feed-notification-only.ts +197 -0
  640. package/src/workspace/migrations/080-restrict-vercel-api-token-metadata.ts +182 -0
  641. package/src/workspace/migrations/081-backfill-bash-allowed-tools-for-injection-credentials.ts +160 -0
  642. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +154 -0
  643. package/src/workspace/migrations/registry.ts +22 -0
  644. package/src/workspace/migrations/runner.ts +13 -2
  645. package/src/workspace/migrations/types.ts +13 -3
  646. package/src/workspace/provider-commit-message-generator.ts +3 -2
  647. package/src/__tests__/context-search-pkb-source.test.ts +0 -498
  648. package/src/__tests__/credentials-cli.test.ts +0 -1225
  649. package/src/__tests__/memory-admin-recall.test.ts +0 -213
  650. package/src/approvals/__tests__/guardian-feed-event.test.ts +0 -303
  651. package/src/cli/commands/__tests__/email-download.test.ts +0 -260
  652. package/src/cli/commands/__tests__/email-list.test.ts +0 -216
  653. package/src/cli/commands/__tests__/email-register.test.ts +0 -186
  654. package/src/cli/commands/__tests__/email-send.test.ts +0 -416
  655. package/src/cli/commands/__tests__/email-status.test.ts +0 -185
  656. package/src/cli/commands/__tests__/email-unregister.test.ts +0 -168
  657. package/src/cli/commands/__tests__/routes.test.ts +0 -562
  658. package/src/cli/commands/__tests__/stt-transcribe.test.ts +0 -454
  659. package/src/cli/commands/autonomy.ts +0 -365
  660. package/src/cli/commands/memory.ts +0 -424
  661. package/src/cli/commands/oauth/__tests__/connect.test.ts +0 -947
  662. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +0 -686
  663. package/src/cli/commands/oauth/__tests__/mode.test.ts +0 -632
  664. package/src/cli/commands/oauth/__tests__/ping.test.ts +0 -631
  665. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +0 -573
  666. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +0 -330
  667. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +0 -521
  668. package/src/cli/commands/oauth/__tests__/status.test.ts +0 -551
  669. package/src/cli/commands/oauth/__tests__/token.test.ts +0 -420
  670. package/src/cli/lib/daemon-avatar-client.ts +0 -37
  671. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -87
  672. package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +0 -207
  673. package/src/daemon/__tests__/conversation-feed-event.test.ts +0 -304
  674. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +0 -233
  675. package/src/home/__tests__/assistant-feed-authoring.test.ts +0 -156
  676. package/src/home/__tests__/emit-feed-event.test.ts +0 -169
  677. package/src/home/__tests__/feed-population-integration.test.ts +0 -312
  678. package/src/home/__tests__/feed-scheduler.test.ts +0 -222
  679. package/src/home/__tests__/phase5-exit-criteria.test.ts +0 -229
  680. package/src/home/__tests__/platform-gmail-digest.test.ts +0 -222
  681. package/src/home/__tests__/rollup-producer.test.ts +0 -507
  682. package/src/home/assistant-feed-authoring.ts +0 -135
  683. package/src/home/emit-feed-event.ts +0 -169
  684. package/src/home/feed-scheduler.ts +0 -281
  685. package/src/home/platform-gmail-digest.ts +0 -163
  686. package/src/home/rewrite-command-preview.ts +0 -66
  687. package/src/home/rewrite-feed-title.ts +0 -58
  688. package/src/home/rollup-producer.ts +0 -426
  689. package/src/memory/admin.ts +0 -326
  690. package/src/memory/context-search/sources/pkb.ts +0 -476
  691. package/src/memory/graph/compaction.ts +0 -299
  692. /package/src/cli/{commands → lib}/cache-fs.ts +0 -0
@@ -215,8 +215,8 @@ describe("rerankCandidates", () => {
215
215
  expect(backendState.calls).toHaveLength(1);
216
216
  });
217
217
 
218
- test("passage construction caps at 240 chars after slug newline", async () => {
219
- const longBody = "a".repeat(500);
218
+ test("passage construction caps at 1500 chars after slug newline", async () => {
219
+ const longBody = "a".repeat(2000);
220
220
  pageState.pages.set("slug", { body: longBody });
221
221
  backendState.scores = [0.5];
222
222
 
@@ -224,9 +224,9 @@ describe("rerankCandidates", () => {
224
224
 
225
225
  expect(backendState.calls).toHaveLength(1);
226
226
  const passage = backendState.calls[0].passages[0];
227
- // "slug\n" prefix + 240 chars of body
227
+ // "slug\n" prefix + 1500 chars of body
228
228
  expect(passage.startsWith("slug\n")).toBe(true);
229
- expect(passage.length).toBeLessThanOrEqual(5 + 240);
229
+ expect(passage.length).toBeLessThanOrEqual(5 + 1500);
230
230
  });
231
231
 
232
232
  test("first paragraph is taken (body truncated at blank line)", async () => {
@@ -0,0 +1,516 @@
1
+ /**
2
+ * Tests for `assistant/src/memory/v2/router.ts`.
3
+ *
4
+ * Coverage matrix:
5
+ * - Empty workspace (zero pages, zero skills) → `empty_index` short-circuit.
6
+ * - No configured provider → `no_provider`.
7
+ * - Successful tool-use → IDs map to slugs, ordered as the model returned.
8
+ * - Empty `page_ids` array → success with empty selection (abstention).
9
+ * - Missing tool_use block → `tool_use_missing`.
10
+ * - Tool input failing Zod → `schema_mismatch`.
11
+ * - IDs outside `[1, N]` filtered with warn.
12
+ * - More than `max_page_ids` returned → truncated with warn.
13
+ * - Provider throw → `api_error`.
14
+ * - Abort signal forwarded to the provider call.
15
+ * - Request shape: system prompt carries page index; user message has the
16
+ * two text blocks; the NOW block has explicit `cache_control`; tool
17
+ * choice forces `select_pages_to_inject`.
18
+ *
19
+ * Workspace lives in a `mkdtemp` directory per test; `~/.vellum/` is never
20
+ * touched. The provider is stubbed so no network calls fire.
21
+ */
22
+
23
+ import { mkdtempSync, rmSync } from "node:fs";
24
+ import { tmpdir } from "node:os";
25
+ import { join } from "node:path";
26
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
27
+
28
+ import type {
29
+ Message,
30
+ Provider,
31
+ ProviderResponse,
32
+ SendMessageOptions,
33
+ ToolDefinition,
34
+ ToolUseContent,
35
+ } from "../../../providers/types.js";
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Mocks installed BEFORE the router import so the module observes them at
39
+ // load time. The page-index reads concept pages from disk and the skill
40
+ // store via `listSkillEntries()` — we mock the skill store here so each
41
+ // test starts with a clean (empty by default) skill list and can opt in.
42
+ // ---------------------------------------------------------------------------
43
+
44
+ const skillState: { entries: { id: string; content: string }[] } = {
45
+ entries: [],
46
+ };
47
+ const warnLogs: Array<{ args: unknown[] }> = [];
48
+
49
+ // Recursive proxy so `log.<any>()` / `log.child({...}).<any>()` are safe
50
+ // no-ops, but `log.warn(...)` records its args for assertion. Mirrors the
51
+ // shape of the shared `makeMockLogger` helper so tests in the same run
52
+ // can't observe a foreign mock from a sibling file.
53
+ function makeRecordingLogger(): unknown {
54
+ return new Proxy({} as Record<string, unknown>, {
55
+ get: (_target, prop) => {
56
+ if (prop === "child") return makeRecordingLogger;
57
+ if (prop === "warn") {
58
+ return (...args: unknown[]) => {
59
+ warnLogs.push({ args });
60
+ };
61
+ }
62
+ return () => {};
63
+ },
64
+ });
65
+ }
66
+
67
+ mock.module("../../../util/logger.js", () => ({
68
+ getLogger: () => makeRecordingLogger(),
69
+ }));
70
+
71
+ mock.module("../skill-store.js", () => ({
72
+ SKILL_SLUG_PREFIX: "skills/",
73
+ listSkillEntries: () => skillState.entries,
74
+ }));
75
+
76
+ // Provider stub. Each test sets `providerStub` to control the response;
77
+ // `null` simulates "no configured provider available".
78
+ let providerStub: Provider | null = null;
79
+
80
+ interface ProviderCall {
81
+ messages: Message[];
82
+ tools: ToolDefinition[] | undefined;
83
+ systemPrompt: string | undefined;
84
+ options: SendMessageOptions | undefined;
85
+ }
86
+ const providerCalls: ProviderCall[] = [];
87
+
88
+ mock.module("../../../providers/provider-send-message.js", () => ({
89
+ getConfiguredProvider: async () => providerStub,
90
+ extractToolUse: (response: ProviderResponse) =>
91
+ response.content.find((b): b is ToolUseContent => b.type === "tool_use"),
92
+ }));
93
+
94
+ // IDENTITY.md / users/default.md aren't required for these tests — the
95
+ // router falls back to neutral labels when missing, and we don't assert on
96
+ // them. No mock needed for `daemon/identity-helpers.js`; it tolerates a
97
+ // missing IDENTITY.md by returning null.
98
+
99
+ const { runRouter } = await import("../router.js");
100
+ const { getPageIndex, invalidatePageIndex } = await import("../page-index.js");
101
+ const { writePage } = await import("../page-store.js");
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Per-test workspace + reset hooks.
105
+ // ---------------------------------------------------------------------------
106
+
107
+ let workspaceDir: string;
108
+
109
+ beforeEach(() => {
110
+ workspaceDir = mkdtempSync(join(tmpdir(), "memory-v2-router-test-"));
111
+ skillState.entries = [];
112
+ providerStub = null;
113
+ providerCalls.length = 0;
114
+ warnLogs.length = 0;
115
+ invalidatePageIndex();
116
+ });
117
+
118
+ afterEach(() => {
119
+ invalidatePageIndex();
120
+ rmSync(workspaceDir, { recursive: true, force: true });
121
+ });
122
+
123
+ // ---------------------------------------------------------------------------
124
+ // Helpers.
125
+ // ---------------------------------------------------------------------------
126
+
127
+ function makeProvider(response: ProviderResponse): Provider {
128
+ return {
129
+ name: "stub",
130
+ sendMessage: async (messages, tools, systemPrompt, options) => {
131
+ providerCalls.push({ messages, tools, systemPrompt, options });
132
+ // Honor abort like a real provider would — if the signal already
133
+ // aborted, throw the canonical AbortError so callers can assert that
134
+ // signal forwarding actually has teeth.
135
+ if (options?.signal?.aborted) {
136
+ const err = new Error("aborted");
137
+ err.name = "AbortError";
138
+ throw err;
139
+ }
140
+ return response;
141
+ },
142
+ };
143
+ }
144
+
145
+ function toolUseResponse(pageIds: number[]): ProviderResponse {
146
+ return {
147
+ model: "stub-model",
148
+ stopReason: "tool_use",
149
+ usage: { inputTokens: 0, outputTokens: 0 },
150
+ content: [
151
+ {
152
+ type: "tool_use",
153
+ id: "tu-1",
154
+ name: "select_pages_to_inject",
155
+ input: { page_ids: pageIds },
156
+ },
157
+ ],
158
+ };
159
+ }
160
+
161
+ function badShapeResponse(input: Record<string, unknown>): ProviderResponse {
162
+ return {
163
+ model: "stub-model",
164
+ stopReason: "tool_use",
165
+ usage: { inputTokens: 0, outputTokens: 0 },
166
+ content: [
167
+ {
168
+ type: "tool_use",
169
+ id: "tu-1",
170
+ name: "select_pages_to_inject",
171
+ input,
172
+ },
173
+ ],
174
+ };
175
+ }
176
+
177
+ function makePage(
178
+ slug: string,
179
+ opts: { summary?: string; edges?: string[] } = {},
180
+ ) {
181
+ return {
182
+ slug,
183
+ frontmatter: {
184
+ edges: opts.edges ?? [],
185
+ ref_files: [],
186
+ ref_urls: [],
187
+ ...(opts.summary !== undefined ? { summary: opts.summary } : {}),
188
+ },
189
+ body: "",
190
+ };
191
+ }
192
+
193
+ // Default config object — mirrors the schema defaults but trimmed to the
194
+ // fields the router actually reads. Cast through `as unknown` because the
195
+ // production type is a heavy nested schema; we only exercise the v2.router
196
+ // branch in this test file.
197
+ function makeConfig(overrides?: { maxPageIds?: number }) {
198
+ return {
199
+ memory: {
200
+ v2: {
201
+ enabled: true,
202
+ router: {
203
+ enabled: true,
204
+ max_page_ids: overrides?.maxPageIds ?? 25,
205
+ },
206
+ },
207
+ },
208
+ } as unknown as Parameters<typeof runRouter>[0]["config"];
209
+ }
210
+
211
+ const COMMON_PARAMS = {
212
+ userMessage: "What's on my plate today?",
213
+ assistantMessage: "Let me check your plan.",
214
+ nowText: "2026-05-10 14:00 PT",
215
+ priorEverInjected: [] as { slug: string; turn: number }[],
216
+ };
217
+
218
+ // ---------------------------------------------------------------------------
219
+ // Tests.
220
+ // ---------------------------------------------------------------------------
221
+
222
+ describe("runRouter — early bails", () => {
223
+ test("returns empty_index when the workspace has no pages and no skills", async () => {
224
+ providerStub = makeProvider(toolUseResponse([1]));
225
+
226
+ const result = await runRouter({
227
+ workspaceDir,
228
+ ...COMMON_PARAMS,
229
+ config: makeConfig(),
230
+ });
231
+
232
+ expect(result).toEqual({
233
+ selectedSlugs: [],
234
+ failureReason: "empty_index",
235
+ });
236
+ // Provider must NOT be invoked when there is nothing to route.
237
+ expect(providerCalls).toHaveLength(0);
238
+ });
239
+
240
+ test("returns no_provider when getConfiguredProvider yields null", async () => {
241
+ await writePage(workspaceDir, makePage("alice", { summary: "Alice" }));
242
+ providerStub = null;
243
+
244
+ const result = await runRouter({
245
+ workspaceDir,
246
+ ...COMMON_PARAMS,
247
+ config: makeConfig(),
248
+ });
249
+
250
+ expect(result.failureReason).toBe("no_provider");
251
+ expect(result.selectedSlugs).toEqual([]);
252
+ expect(providerCalls).toHaveLength(0);
253
+ });
254
+ });
255
+
256
+ describe("runRouter — successful tool_use", () => {
257
+ beforeEach(async () => {
258
+ // Build a 3-page workspace. Sorted by slug → [alpha, bravo, charlie] →
259
+ // IDs [1, 2, 3].
260
+ await writePage(workspaceDir, makePage("alpha", { summary: "A" }));
261
+ await writePage(workspaceDir, makePage("bravo", { summary: "B" }));
262
+ await writePage(workspaceDir, makePage("charlie", { summary: "C" }));
263
+ });
264
+
265
+ test("maps returned IDs to slugs in model-returned order", async () => {
266
+ providerStub = makeProvider(toolUseResponse([3, 1]));
267
+
268
+ const result = await runRouter({
269
+ workspaceDir,
270
+ ...COMMON_PARAMS,
271
+ config: makeConfig(),
272
+ });
273
+
274
+ expect(result.failureReason).toBeNull();
275
+ expect(result.selectedSlugs).toEqual(["charlie", "alpha"]);
276
+ });
277
+
278
+ test("empty page_ids is the abstention path — success with empty selection", async () => {
279
+ providerStub = makeProvider(toolUseResponse([]));
280
+
281
+ const result = await runRouter({
282
+ workspaceDir,
283
+ ...COMMON_PARAMS,
284
+ config: makeConfig(),
285
+ });
286
+
287
+ expect(result).toEqual({
288
+ selectedSlugs: [],
289
+ failureReason: null,
290
+ });
291
+ });
292
+
293
+ test("forces tool_choice to select_pages_to_inject", async () => {
294
+ providerStub = makeProvider(toolUseResponse([1]));
295
+
296
+ await runRouter({
297
+ workspaceDir,
298
+ ...COMMON_PARAMS,
299
+ config: makeConfig(),
300
+ });
301
+
302
+ expect(providerCalls).toHaveLength(1);
303
+ const [call] = providerCalls;
304
+ const callConfig = call.options?.config as Record<string, unknown>;
305
+ expect(callConfig?.callSite).toBe("memoryRouter");
306
+ expect(callConfig?.tool_choice).toEqual({
307
+ type: "tool",
308
+ name: "select_pages_to_inject",
309
+ });
310
+ expect(call.tools).toHaveLength(1);
311
+ expect(call.tools?.[0].name).toBe("select_pages_to_inject");
312
+ });
313
+
314
+ test("tool maxItems reflects configured max_page_ids", async () => {
315
+ providerStub = makeProvider(toolUseResponse([1]));
316
+
317
+ await runRouter({
318
+ workspaceDir,
319
+ ...COMMON_PARAMS,
320
+ config: makeConfig({ maxPageIds: 50 }),
321
+ });
322
+
323
+ const [call] = providerCalls;
324
+ const schema = call.tools?.[0].input_schema as {
325
+ properties: { page_ids: { maxItems: number } };
326
+ };
327
+ expect(schema.properties.page_ids.maxItems).toBe(50);
328
+ expect(call.tools?.[0].description).toContain("up to 50");
329
+ });
330
+
331
+ test("system prompt carries the rendered page index", async () => {
332
+ providerStub = makeProvider(toolUseResponse([1]));
333
+
334
+ await runRouter({
335
+ workspaceDir,
336
+ ...COMMON_PARAMS,
337
+ config: makeConfig(),
338
+ });
339
+
340
+ const idx = await getPageIndex(workspaceDir);
341
+ const sys = providerCalls[0].systemPrompt;
342
+ expect(sys).toBeTruthy();
343
+ // Each entry's rendered line should appear verbatim.
344
+ for (const entry of idx.entries) {
345
+ expect(sys).toContain(`[${entry.id}] ${entry.slug}`);
346
+ }
347
+ });
348
+
349
+ test("user message has two text blocks: <now> and <last_turn>+already_injected", async () => {
350
+ providerStub = makeProvider(toolUseResponse([1]));
351
+
352
+ await runRouter({
353
+ workspaceDir,
354
+ ...COMMON_PARAMS,
355
+ priorEverInjected: [{ slug: "alpha", turn: 1 }],
356
+ config: makeConfig(),
357
+ });
358
+
359
+ const [call] = providerCalls;
360
+ expect(call.messages).toHaveLength(1);
361
+ const userMsg = call.messages[0];
362
+ expect(userMsg.role).toBe("user");
363
+ expect(userMsg.content).toHaveLength(2);
364
+
365
+ const [blockA, blockB] = userMsg.content as Array<{
366
+ type: string;
367
+ text: string;
368
+ cache_control?: { type: string; ttl?: string };
369
+ }>;
370
+
371
+ // Block A — NOW with explicit ephemeral cache breakpoint at 1h TTL
372
+ // (matches the provider's auto-applied breakpoints; the default 5m
373
+ // would force re-creation across most turns since `<now>` is stable).
374
+ expect(blockA.type).toBe("text");
375
+ expect(blockA.text).toContain("<now>");
376
+ expect(blockA.text).toContain("2026-05-10 14:00 PT");
377
+ expect(blockA.text).toContain("</now>");
378
+ expect(blockA.cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
379
+
380
+ // Block B — already-injected IDs + last turn, NO cache_control.
381
+ expect(blockB.type).toBe("text");
382
+ expect(blockB.text).toContain("<already_injected_ids>");
383
+ expect(blockB.text).toContain("1"); // alpha → id 1
384
+ expect(blockB.text).toContain("<last_turn>");
385
+ expect(blockB.text).toContain("[user]: What's on my plate today?");
386
+ expect(blockB.text).toContain("[assistant]: Let me check your plan.");
387
+ expect(blockB.cache_control).toBeUndefined();
388
+ });
389
+
390
+ test("de-duplicates repeated IDs from the model while preserving order", async () => {
391
+ providerStub = makeProvider(toolUseResponse([2, 1, 2]));
392
+
393
+ const result = await runRouter({
394
+ workspaceDir,
395
+ ...COMMON_PARAMS,
396
+ config: makeConfig(),
397
+ });
398
+
399
+ expect(result.selectedSlugs).toEqual(["bravo", "alpha"]);
400
+ });
401
+ });
402
+
403
+ describe("runRouter — failure modes", () => {
404
+ beforeEach(async () => {
405
+ await writePage(workspaceDir, makePage("alpha", { summary: "A" }));
406
+ await writePage(workspaceDir, makePage("bravo", { summary: "B" }));
407
+ await writePage(workspaceDir, makePage("charlie", { summary: "C" }));
408
+ });
409
+
410
+ test("missing tool_use block → tool_use_missing", async () => {
411
+ providerStub = makeProvider({
412
+ model: "stub-model",
413
+ stopReason: "end_turn",
414
+ usage: { inputTokens: 0, outputTokens: 0 },
415
+ content: [{ type: "text", text: "I have nothing to add." }],
416
+ });
417
+
418
+ const result = await runRouter({
419
+ workspaceDir,
420
+ ...COMMON_PARAMS,
421
+ config: makeConfig(),
422
+ });
423
+
424
+ expect(result.failureReason).toBe("tool_use_missing");
425
+ expect(result.selectedSlugs).toEqual([]);
426
+ });
427
+
428
+ test("tool input failing Zod → schema_mismatch with warn log", async () => {
429
+ providerStub = makeProvider(badShapeResponse({ wrong_key: [1, 2] }));
430
+
431
+ const result = await runRouter({
432
+ workspaceDir,
433
+ ...COMMON_PARAMS,
434
+ config: makeConfig(),
435
+ });
436
+
437
+ expect(result.failureReason).toBe("schema_mismatch");
438
+ // At least one warn log was emitted with a Zod-shaped error.
439
+ const warnSeen = warnLogs.some((l) =>
440
+ JSON.stringify(l.args).includes("schema"),
441
+ );
442
+ expect(warnSeen).toBe(true);
443
+ });
444
+
445
+ test("IDs outside [1, N] are filtered with warn", async () => {
446
+ // N = 3. Returning [2, 99, 0, -1] should keep only [2].
447
+ providerStub = makeProvider(toolUseResponse([2, 99, 0, -1]));
448
+
449
+ const result = await runRouter({
450
+ workspaceDir,
451
+ ...COMMON_PARAMS,
452
+ config: makeConfig(),
453
+ });
454
+
455
+ expect(result.failureReason).toBeNull();
456
+ expect(result.selectedSlugs).toEqual(["bravo"]);
457
+ const warnSeen = warnLogs.some((l) =>
458
+ JSON.stringify(l.args).includes("outside the valid range"),
459
+ );
460
+ expect(warnSeen).toBe(true);
461
+ });
462
+
463
+ test("more than max_page_ids → truncated with warn", async () => {
464
+ providerStub = makeProvider(toolUseResponse([1, 2, 3]));
465
+
466
+ const result = await runRouter({
467
+ workspaceDir,
468
+ ...COMMON_PARAMS,
469
+ config: makeConfig({ maxPageIds: 2 }),
470
+ });
471
+
472
+ expect(result.failureReason).toBeNull();
473
+ expect(result.selectedSlugs).toEqual(["alpha", "bravo"]);
474
+ const warnSeen = warnLogs.some((l) =>
475
+ JSON.stringify(l.args).includes("more page IDs than max_page_ids"),
476
+ );
477
+ expect(warnSeen).toBe(true);
478
+ });
479
+
480
+ test("provider throw → api_error", async () => {
481
+ providerStub = {
482
+ name: "throwing",
483
+ sendMessage: async () => {
484
+ throw new Error("boom");
485
+ },
486
+ };
487
+
488
+ const result = await runRouter({
489
+ workspaceDir,
490
+ ...COMMON_PARAMS,
491
+ config: makeConfig(),
492
+ });
493
+
494
+ expect(result.failureReason).toBe("api_error");
495
+ expect(result.selectedSlugs).toEqual([]);
496
+ });
497
+
498
+ test("aborted signal propagates as api_error (provider throw caught)", async () => {
499
+ providerStub = makeProvider(toolUseResponse([1]));
500
+
501
+ const controller = new AbortController();
502
+ controller.abort();
503
+
504
+ const result = await runRouter({
505
+ workspaceDir,
506
+ ...COMMON_PARAMS,
507
+ config: makeConfig(),
508
+ signal: controller.signal,
509
+ });
510
+
511
+ expect(result.failureReason).toBe("api_error");
512
+ expect(providerCalls).toHaveLength(1);
513
+ // Signal must be forwarded — otherwise the stub's aborted-check wouldn't fire.
514
+ expect(providerCalls[0].options?.signal).toBe(controller.signal);
515
+ });
516
+ });
@@ -433,7 +433,7 @@ describe("simBatch", () => {
433
433
 
434
434
  const out = await simBatch("query", ["dense-only-page"], config);
435
435
 
436
- // 0.7 * 0.5 + 0.3 * 0 = 0.35
436
+ // cosine 0.5 (positive, passes through); 0.7 * 0.5 + 0.3 * 0 = 0.35
437
437
  expect(out.get("dense-only-page")).toBeCloseTo(0.35, 6);
438
438
  });
439
439
 
@@ -475,6 +475,7 @@ describe("simBatch", () => {
475
475
 
476
476
  const out = await simBatch("query", ["alice", "bob"], config);
477
477
 
478
+ // Positive cosines pass through unchanged.
478
479
  // alice: 0.4 * 0.5 + 0.6 * 1.0 = 0.8
479
480
  // bob: 0.4 * 0.25 + 0.6 * 0.5 = 0.4
480
481
  expect(out.get("alice")).toBeCloseTo(0.8, 6);
@@ -537,6 +538,7 @@ describe("simBatch", () => {
537
538
 
538
539
  const out = await simBatch("query", ["alice"], config);
539
540
 
541
+ // Positive cosines pass through unchanged: max(0.3, 0.7) = 0.7.
540
542
  expect(out.get("alice")).toBeCloseTo(0.7, 6);
541
543
  });
542
544
 
@@ -553,6 +555,7 @@ describe("simBatch", () => {
553
555
 
554
556
  const out = await simBatch("query", ["alice"], config);
555
557
 
558
+ // Positive cosines pass through unchanged: max(0.9, 0.4) = 0.9.
556
559
  expect(out.get("alice")).toBeCloseTo(0.9, 6);
557
560
  });
558
561
 
@@ -570,6 +573,7 @@ describe("simBatch", () => {
570
573
 
571
574
  const out = await simBatch("query", ["legacy-page"], config);
572
575
 
576
+ // Positive cosine 0.6 passes through unchanged.
573
577
  expect(out.get("legacy-page")).toBeCloseTo(0.6, 6);
574
578
  });
575
579
 
@@ -607,6 +611,46 @@ describe("simBatch", () => {
607
611
  expect(out.get("bob")).toBeCloseTo(1.0, 6);
608
612
  });
609
613
 
614
+ test("dense-favored config keeps the dense-strong candidate above the sparse-strong one", async () => {
615
+ // Regression guard: an earlier fix mapped cosines into [0, 1] via
616
+ // `(cos + 1) / 2` before fusion, which halved every pairwise dense
617
+ // difference and silently shifted ranking toward sparse. With dense
618
+ // weighted higher than sparse, the dense-only hit must outrank the
619
+ // sparse-only hit even though sparse normalizes to 1.0.
620
+ const config = configWithWeights(0.7, 0.3);
621
+ stageHybridResponse([
622
+ { slug: "dense-strong", denseScore: 0.5 /* sparseScore omitted */ },
623
+ { slug: "sparse-strong", denseScore: 0.0, sparseScore: 1 },
624
+ ]);
625
+
626
+ const out = await simBatch(
627
+ "query",
628
+ ["dense-strong", "sparse-strong"],
629
+ config,
630
+ );
631
+
632
+ // dense-strong: 0.7 * max(0, 0.5) + 0.3 * 0 = 0.35
633
+ // sparse-strong: 0.7 * max(0, 0.0) + 0.3 * 1.0 = 0.30
634
+ expect(out.get("dense-strong")).toBeCloseTo(0.35, 6);
635
+ expect(out.get("sparse-strong")).toBeCloseTo(0.3, 6);
636
+ expect(out.get("dense-strong")!).toBeGreaterThan(out.get("sparse-strong")!);
637
+ });
638
+
639
+ test("negative cosine maps to a non-negative dense contribution", async () => {
640
+ // Qdrant cosine search returns scores in [-1, 1]. A near-zero or
641
+ // negative cosine must not yield a negative dense contribution that
642
+ // depresses the fused score below the sparse-only floor.
643
+ const config = configWithWeights(0.7, 0.3);
644
+ stageHybridResponse([
645
+ { slug: "anti-match", denseScore: -1.0, sparseScore: 1 },
646
+ ]);
647
+
648
+ const out = await simBatch("query", ["anti-match"], config);
649
+
650
+ // cosine -1 → clamped to 0; sparse-norm = 1.0; fused = 0.7*0 + 0.3*1 = 0.3.
651
+ expect(out.get("anti-match")).toBeCloseTo(0.3, 6);
652
+ });
653
+
610
654
  test("returned scores are always in [0, 1] for arbitrary inputs", async () => {
611
655
  const config = configWithWeights(0.7, 0.3);
612
656
  stageHybridResponse([