@vellumai/assistant 0.7.1 → 0.7.2

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 (535) hide show
  1. package/ARCHITECTURE.md +32 -49
  2. package/Dockerfile +1 -0
  3. package/README.md +1 -2
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +9 -3
  5. package/bun.lock +26 -26
  6. package/docs/architecture/security.md +20 -0
  7. package/docs/plugins.md +7 -9
  8. package/knip.json +1 -0
  9. package/node_modules/@vellumai/gateway-client/src/index.ts +1 -0
  10. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +39 -1
  11. package/node_modules/@vellumai/gateway-client/src/types.ts +11 -0
  12. package/node_modules/@vellumai/service-contracts/package.json +2 -0
  13. package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +4 -0
  14. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +107 -0
  15. package/node_modules/@vellumai/service-contracts/src/index.ts +5 -1
  16. package/node_modules/@vellumai/service-contracts/src/ingress.ts +24 -0
  17. package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +84 -0
  18. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +9 -0
  19. package/node_modules/@vellumai/twilio-client/bun.lock +24 -0
  20. package/node_modules/@vellumai/twilio-client/package.json +18 -0
  21. package/node_modules/@vellumai/twilio-client/src/__tests__/twilio-client.test.ts +128 -0
  22. package/node_modules/@vellumai/twilio-client/src/index.ts +179 -0
  23. package/node_modules/@vellumai/twilio-client/tsconfig.json +20 -0
  24. package/openapi.yaml +565 -12
  25. package/package.json +6 -3
  26. package/src/__tests__/app-builder-tool-scripts.test.ts +3 -3
  27. package/src/__tests__/app-bundler.test.ts +170 -1
  28. package/src/__tests__/app-control-flow.test.ts +374 -0
  29. package/src/__tests__/app-control-no-global-cgevent.test.ts +98 -0
  30. package/src/__tests__/app-control-tool-schemas.test.ts +621 -0
  31. package/src/__tests__/app-executors.test.ts +30 -43
  32. package/src/__tests__/approval-routes-http.test.ts +23 -6
  33. package/src/__tests__/assistant-event-hub-machine-name.test.ts +146 -0
  34. package/src/__tests__/assistant-event-hub-targeted.test.ts +257 -0
  35. package/src/__tests__/assistant-event-hub.test.ts +109 -2
  36. package/src/__tests__/assistant-event.test.ts +10 -0
  37. package/src/__tests__/assistant-events-sse-hardening.test.ts +7 -2
  38. package/src/__tests__/assistant-feature-flags-integration.test.ts +11 -7
  39. package/src/__tests__/background-shell-host-bash.test.ts +14 -15
  40. package/src/__tests__/bootstrap-turn-cleanup.test.ts +44 -0
  41. package/src/__tests__/btw-routes.test.ts +13 -4
  42. package/src/__tests__/call-controller.test.ts +49 -1
  43. package/src/__tests__/call-domain.test.ts +0 -2
  44. package/src/__tests__/call-routes-http.test.ts +0 -2
  45. package/src/__tests__/channel-readiness-service.test.ts +59 -1
  46. package/src/__tests__/checker.test.ts +3 -4
  47. package/src/__tests__/config-loader-backfill.test.ts +90 -155
  48. package/src/__tests__/config-loader-platform-defaults.test.ts +196 -0
  49. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  50. package/src/__tests__/config-set-platform-guard.test.ts +48 -4
  51. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +2 -2
  52. package/src/__tests__/config-watcher.test.ts +2 -2
  53. package/src/__tests__/conversation-app-control-instantiation.test.ts +392 -0
  54. package/src/__tests__/conversation-app-control-lifecycle.test.ts +237 -0
  55. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  56. package/src/__tests__/conversation-lifecycle.test.ts +36 -0
  57. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +283 -0
  58. package/src/__tests__/conversation-routes-disk-view.test.ts +6 -0
  59. package/src/__tests__/conversation-routes-guardian-reply.test.ts +120 -72
  60. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  61. package/src/__tests__/conversation-slash-commands.test.ts +0 -4
  62. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +202 -0
  63. package/src/__tests__/conversation-surfaces-app-control.test.ts +317 -0
  64. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -12
  65. package/src/__tests__/credential-execution-managed-contract.test.ts +3 -131
  66. package/src/__tests__/credentials-cli.test.ts +5 -12
  67. package/src/__tests__/cu-unified-flow.test.ts +185 -23
  68. package/src/__tests__/daemon-credential-client.test.ts +101 -19
  69. package/src/__tests__/db-schedule-syntax-migration.test.ts +2 -0
  70. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  71. package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
  72. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -2
  73. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -2
  74. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -1
  75. package/src/__tests__/heartbeat-service.test.ts +718 -1
  76. package/src/__tests__/helpers/call-route-handler.ts +7 -1
  77. package/src/__tests__/host-app-control-proxy.test.ts +602 -0
  78. package/src/__tests__/host-app-control-routes.test.ts +263 -0
  79. package/src/__tests__/host-bash-proxy.test.ts +246 -47
  80. package/src/__tests__/host-bash-routes.test.ts +294 -0
  81. package/src/__tests__/host-browser-proxy.test.ts +24 -22
  82. package/src/__tests__/host-browser-routes.test.ts +39 -13
  83. package/src/__tests__/host-cu-proxy.test.ts +41 -52
  84. package/src/__tests__/host-cu-routes-targeted.test.ts +300 -0
  85. package/src/__tests__/host-file-edit-tool.test.ts +47 -1
  86. package/src/__tests__/host-file-proxy-targeted.test.ts +339 -0
  87. package/src/__tests__/host-file-proxy.test.ts +37 -43
  88. package/src/__tests__/host-file-read-tool.test.ts +17 -0
  89. package/src/__tests__/host-file-routes-targeted.test.ts +262 -0
  90. package/src/__tests__/host-file-write-tool.test.ts +42 -1
  91. package/src/__tests__/host-proxy-base.test.ts +312 -0
  92. package/src/__tests__/host-shell-tool.test.ts +22 -4
  93. package/src/__tests__/host-transfer-proxy-targeted.test.ts +583 -0
  94. package/src/__tests__/host-transfer-proxy.test.ts +121 -22
  95. package/src/__tests__/host-transfer-routes-targeted.test.ts +447 -0
  96. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  97. package/src/__tests__/identity-intro-cache.test.ts +29 -0
  98. package/src/__tests__/identity-routes.test.ts +103 -1
  99. package/src/__tests__/init-feature-flag-overrides.test.ts +26 -3
  100. package/src/__tests__/inline-command-runner.test.ts +0 -1
  101. package/src/__tests__/inline-skill-load-permissions.test.ts +5 -11
  102. package/src/__tests__/integration-status.test.ts +85 -5
  103. package/src/__tests__/intent-routing.test.ts +0 -1
  104. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +95 -5
  105. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +17 -0
  106. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
  107. package/src/__tests__/mcp-auth-routes.test.ts +197 -0
  108. package/src/__tests__/mcp-cli.test.ts +338 -2
  109. package/src/__tests__/memory-jobs-worker-lanes.test.ts +188 -0
  110. package/src/__tests__/migration-import-commit-http.test.ts +108 -2
  111. package/src/__tests__/mock-gateway-ipc.ts +1 -0
  112. package/src/__tests__/oauth-cli.test.ts +0 -2
  113. package/src/__tests__/oauth2-gateway-transport.test.ts +0 -1
  114. package/src/__tests__/persistence-secret-redaction.test.ts +299 -0
  115. package/src/__tests__/platform-bash-auto-approve.test.ts +5 -9
  116. package/src/__tests__/prechat-onboarding-contract.test.ts +3 -1
  117. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  118. package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
  119. package/src/__tests__/public-ingress-urls.test.ts +97 -0
  120. package/src/__tests__/require-fresh-approval.test.ts +0 -1
  121. package/src/__tests__/retry-backoff.test.ts +87 -0
  122. package/src/__tests__/runtime-events-sse.test.ts +10 -6
  123. package/src/__tests__/sanitize-config-for-transfer.test.ts +24 -2
  124. package/src/__tests__/schedule-retry.test.ts +715 -0
  125. package/src/__tests__/script-proxy-mitm-handler.test.ts +1 -1
  126. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  127. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  128. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
  129. package/src/__tests__/skill-feature-flags.test.ts +43 -41
  130. package/src/__tests__/skill-load-feature-flag.test.ts +13 -14
  131. package/src/__tests__/skill-load-inline-command.test.ts +0 -51
  132. package/src/__tests__/skill-load-inline-includes.test.ts +0 -43
  133. package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
  134. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
  135. package/src/__tests__/slack-channel-config.test.ts +9 -14
  136. package/src/__tests__/system-prompt-ask-mode.test.ts +0 -1
  137. package/src/__tests__/system-prompt.test.ts +0 -1
  138. package/src/__tests__/telegram-config.test.ts +0 -1
  139. package/src/__tests__/test-preload.ts +8 -0
  140. package/src/__tests__/tool-approval-handler.test.ts +3 -4
  141. package/src/__tests__/tool-audit-listener.test.ts +48 -0
  142. package/src/__tests__/tool-execute-pipeline.test.ts +0 -1
  143. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
  144. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  145. package/src/__tests__/tool-executor.test.ts +0 -1
  146. package/src/__tests__/twilio-config.test.ts +3 -16
  147. package/src/__tests__/twilio-routes.test.ts +3 -5
  148. package/src/__tests__/twilio-validation.test.ts +93 -0
  149. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -4
  150. package/src/__tests__/verification-control-plane-policy.test.ts +2 -4
  151. package/src/__tests__/voice-ingress-preflight.test.ts +19 -0
  152. package/src/__tests__/workspace-migration-006-services-config.test.ts +3 -2
  153. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +1 -5
  154. package/src/__tests__/workspace-migration-down-functions.test.ts +8 -8
  155. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +10 -6
  156. package/src/backup/__tests__/paths.test.ts +0 -22
  157. package/src/backup/__tests__/restore.test.ts +51 -151
  158. package/src/backup/paths.ts +2 -18
  159. package/src/backup/restore.ts +107 -231
  160. package/src/bundler/app-bundler.ts +51 -3
  161. package/src/calls/relay-server.ts +4 -44
  162. package/src/calls/twilio-config.ts +2 -17
  163. package/src/calls/twilio-rest.ts +33 -105
  164. package/src/calls/twilio-routes.ts +11 -12
  165. package/src/channels/types.ts +8 -7
  166. package/src/cli/commands/__tests__/backup.test.ts +6 -277
  167. package/src/cli/commands/__tests__/gateway.test.ts +288 -0
  168. package/src/cli/commands/__tests__/memory-v2.test.ts +4 -0
  169. package/src/cli/commands/__tests__/webhooks.test.ts +0 -1
  170. package/src/cli/commands/backup.ts +6 -331
  171. package/src/cli/commands/clients.ts +36 -37
  172. package/src/cli/commands/contacts.ts +73 -0
  173. package/src/cli/commands/conversations.ts +2 -5
  174. package/src/cli/commands/credentials.ts +15 -7
  175. package/src/cli/commands/domain.ts +66 -15
  176. package/src/cli/commands/gateway.ts +183 -0
  177. package/src/cli/commands/keys.ts +9 -6
  178. package/src/cli/commands/mcp.ts +116 -156
  179. package/src/cli/commands/memory-v2.ts +296 -1
  180. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
  181. package/src/cli/commands/platform/__tests__/connect.test.ts +0 -2
  182. package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -2
  183. package/src/cli/commands/platform/__tests__/status.test.ts +13 -15
  184. package/src/cli/commands/platform/disconnect.ts +5 -4
  185. package/src/cli/commands/platform/index.ts +0 -18
  186. package/src/cli/lib/daemon-credential-client.ts +110 -28
  187. package/src/cli/program.ts +2 -0
  188. package/src/config/assistant-feature-flags.ts +67 -10
  189. package/src/config/bundled-skills/acp/SKILL.md +6 -0
  190. package/src/config/bundled-skills/acp/TOOLS.json +1 -22
  191. package/src/config/bundled-skills/app-builder/SKILL.md +14 -109
  192. package/src/config/bundled-skills/app-builder/TOOLS.json +1 -28
  193. package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -10
  194. package/src/config/bundled-skills/app-control/SKILL.md +75 -0
  195. package/src/config/bundled-skills/app-control/TOOLS.json +299 -0
  196. package/src/config/bundled-skills/app-control/tools/app-control-click.ts +12 -0
  197. package/src/config/bundled-skills/app-control/tools/app-control-combo.ts +12 -0
  198. package/src/config/bundled-skills/app-control/tools/app-control-drag.ts +12 -0
  199. package/src/config/bundled-skills/app-control/tools/app-control-observe.ts +12 -0
  200. package/src/config/bundled-skills/app-control/tools/app-control-press.ts +12 -0
  201. package/src/config/bundled-skills/app-control/tools/app-control-sequence.ts +12 -0
  202. package/src/config/bundled-skills/app-control/tools/app-control-start.ts +12 -0
  203. package/src/config/bundled-skills/app-control/tools/app-control-stop.ts +12 -0
  204. package/src/config/bundled-skills/app-control/tools/app-control-type.ts +12 -0
  205. package/src/config/bundled-skills/computer-use/SKILL.md +6 -0
  206. package/src/config/bundled-skills/computer-use/TOOLS.json +67 -43
  207. package/src/config/bundled-skills/contacts/TOOLS.json +0 -16
  208. package/src/config/bundled-skills/document/TOOLS.json +0 -8
  209. package/src/config/bundled-skills/followups/TOOLS.json +0 -12
  210. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  211. package/src/config/bundled-skills/image-studio/TOOLS.json +0 -4
  212. package/src/config/bundled-skills/media-processing/TOOLS.json +0 -24
  213. package/src/config/bundled-skills/messaging/TOOLS.json +0 -40
  214. package/src/config/bundled-skills/phone-calls/TOOLS.json +0 -12
  215. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +19 -4
  216. package/src/config/bundled-skills/playbooks/TOOLS.json +0 -16
  217. package/src/config/bundled-skills/schedule/TOOLS.json +14 -14
  218. package/src/config/bundled-skills/sequences/TOOLS.json +0 -36
  219. package/src/config/bundled-skills/settings/SKILL.md +4 -0
  220. package/src/config/bundled-skills/settings/TOOLS.json +0 -12
  221. package/src/config/bundled-skills/skill-management/SKILL.md +6 -0
  222. package/src/config/bundled-skills/skill-management/TOOLS.json +0 -8
  223. package/src/config/bundled-skills/subagent/SKILL.md +6 -2
  224. package/src/config/bundled-skills/subagent/TOOLS.json +0 -20
  225. package/src/config/bundled-skills/transcribe/SKILL.md +4 -0
  226. package/src/config/bundled-skills/transcribe/TOOLS.json +0 -4
  227. package/src/config/bundled-tool-registry.ts +21 -0
  228. package/src/config/env-registry.ts +0 -2
  229. package/src/config/env.ts +19 -12
  230. package/src/config/feature-flag-registry.json +21 -133
  231. package/src/config/loader.ts +73 -99
  232. package/src/config/sanitize-for-transfer.ts +2 -0
  233. package/src/config/schemas/__tests__/memory-lifecycle.test.ts +80 -0
  234. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -4
  235. package/src/config/schemas/calls.ts +0 -9
  236. package/src/config/schemas/heartbeat.ts +63 -0
  237. package/src/config/schemas/ingress.ts +10 -6
  238. package/src/config/schemas/llm.ts +5 -10
  239. package/src/config/schemas/memory-lifecycle.ts +77 -24
  240. package/src/config/schemas/memory-v2.ts +48 -4
  241. package/src/config/schemas/platform.ts +6 -0
  242. package/src/config/schemas/services.ts +1 -15
  243. package/src/config/schemas/skills.ts +0 -6
  244. package/src/config/seed-inference-profiles.ts +1 -1
  245. package/src/contacts/contact-store.ts +0 -30
  246. package/src/contacts/contacts-write.ts +0 -27
  247. package/src/context/window-manager.ts +1 -2
  248. package/src/credential-execution/feature-gates.ts +10 -10
  249. package/src/credential-execution/process-manager.ts +12 -41
  250. package/src/daemon/__tests__/conversation-tool-setup.test.ts +126 -5
  251. package/src/daemon/bootstrap-turn-cleanup.ts +45 -0
  252. package/src/daemon/config-watcher.ts +4 -3
  253. package/src/daemon/conversation-agent-loop-handlers.ts +21 -3
  254. package/src/daemon/conversation-agent-loop.ts +32 -28
  255. package/src/daemon/conversation-lifecycle.ts +8 -1
  256. package/src/daemon/conversation-process.ts +16 -11
  257. package/src/daemon/conversation-runtime-assembly.ts +2 -2
  258. package/src/daemon/conversation-surfaces.ts +125 -4
  259. package/src/daemon/conversation-tool-setup.ts +16 -55
  260. package/src/daemon/conversation.ts +21 -2
  261. package/src/daemon/doordash-steps.ts +1 -1
  262. package/src/daemon/handlers/shared.ts +4 -1
  263. package/src/daemon/host-app-control-proxy.ts +293 -0
  264. package/src/daemon/host-bash-proxy.ts +84 -74
  265. package/src/daemon/host-browser-proxy.ts +67 -82
  266. package/src/daemon/host-cu-proxy.ts +81 -86
  267. package/src/daemon/host-file-proxy.ts +93 -69
  268. package/src/daemon/host-proxy-base.ts +294 -0
  269. package/src/daemon/host-proxy-preactivation.ts +82 -0
  270. package/src/daemon/host-transfer-proxy.ts +247 -129
  271. package/src/daemon/lifecycle.ts +115 -117
  272. package/src/daemon/message-protocol.ts +3 -8
  273. package/src/daemon/message-types/contacts.ts +23 -1
  274. package/src/daemon/message-types/conversations.ts +11 -8
  275. package/src/daemon/message-types/host-app-control.ts +150 -0
  276. package/src/daemon/message-types/host-bash.ts +4 -0
  277. package/src/daemon/message-types/host-cu.ts +2 -0
  278. package/src/daemon/message-types/host-file.ts +4 -0
  279. package/src/daemon/message-types/host-transfer.ts +3 -0
  280. package/src/daemon/message-types/schedules.ts +8 -3
  281. package/src/daemon/message-types/skills.ts +2 -2
  282. package/src/daemon/process-message.ts +18 -1
  283. package/src/daemon/shutdown-handlers.ts +0 -3
  284. package/src/daemon/tool-setup-types.ts +51 -0
  285. package/src/daemon/tool-side-effects.ts +1 -1
  286. package/src/events/tool-audit-listener.ts +2 -1
  287. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +15 -7
  288. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +216 -0
  289. package/src/heartbeat/heartbeat-run-store.ts +236 -0
  290. package/src/heartbeat/heartbeat-service.ts +280 -49
  291. package/src/home/__tests__/post-connect-feed.test.ts +99 -0
  292. package/src/home/__tests__/relationship-state-writer.test.ts +11 -9
  293. package/src/home/__tests__/suggested-prompts.test.ts +89 -0
  294. package/src/home/post-connect-feed.ts +68 -0
  295. package/src/home/relationship-state-writer.ts +17 -92
  296. package/src/home/suggested-prompts.ts +46 -10
  297. package/src/inbound/public-ingress-urls.ts +32 -34
  298. package/src/ipc/__tests__/route-error-envelope.test.ts +80 -0
  299. package/src/ipc/assistant-server.ts +14 -1
  300. package/src/ipc/cli-client.ts +32 -1
  301. package/src/live-voice/live-voice-metrics.ts +10 -10
  302. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +304 -0
  303. package/src/mcp/mcp-auth-orchestrator.ts +213 -0
  304. package/src/mcp/mcp-auth-state.ts +133 -0
  305. package/src/mcp/mcp-oauth-provider.ts +19 -0
  306. package/src/memory/__tests__/jobs-store-job-classes.test.ts +24 -0
  307. package/src/memory/__tests__/qdrant-client-sentinel.test.ts +49 -0
  308. package/src/memory/__tests__/sparse-tokenize.test.ts +66 -0
  309. package/src/memory/anisotropy.test.ts +247 -0
  310. package/src/memory/anisotropy.ts +443 -0
  311. package/src/memory/auto-analysis-constants.ts +17 -0
  312. package/src/memory/auto-analysis-guard.ts +5 -15
  313. package/src/memory/canonical-guardian-store.ts +7 -7
  314. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +122 -0
  315. package/src/memory/context-search/agent-protocol.ts +6 -6
  316. package/src/memory/context-search/agent-runner.ts +32 -7
  317. package/src/memory/context-search/sources/memory-v2.ts +17 -5
  318. package/src/memory/conversation-crud.ts +1 -1
  319. package/src/memory/conversation-key-store.ts +2 -15
  320. package/src/memory/db-init.ts +4 -0
  321. package/src/memory/embedding-backend.ts +9 -21
  322. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +49 -4
  323. package/src/memory/graph/conversation-graph-memory.ts +1 -24
  324. package/src/memory/graph/graph-search.ts +8 -0
  325. package/src/memory/graph/retriever.ts +28 -0
  326. package/src/memory/graph/tools.ts +1 -1
  327. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +8 -2
  328. package/src/memory/jobs/embed-concept-page.ts +28 -2
  329. package/src/memory/jobs/embed-pkb-file.test.ts +2 -2
  330. package/src/memory/jobs-store.ts +66 -22
  331. package/src/memory/jobs-worker.ts +112 -63
  332. package/src/memory/memory-v2-activation-log-store.ts +1 -1
  333. package/src/memory/migrations/237-heartbeat-runs.ts +45 -0
  334. package/src/memory/migrations/238-schedule-retry-policy.ts +20 -0
  335. package/src/memory/migrations/index.ts +5 -0
  336. package/src/memory/migrations/registry.ts +8 -0
  337. package/src/memory/pkb/pkb-search.ts +7 -0
  338. package/src/memory/qdrant-client.ts +50 -20
  339. package/src/memory/schema/infrastructure.ts +15 -0
  340. package/src/memory/search/semantic.ts +7 -0
  341. package/src/memory/sparse-tokenize.ts +49 -0
  342. package/src/memory/v2/__tests__/activation.test.ts +77 -95
  343. package/src/memory/v2/__tests__/injection.test.ts +43 -21
  344. package/src/memory/v2/__tests__/sim.test.ts +166 -6
  345. package/src/memory/v2/__tests__/sparse-bm25.test.ts +292 -0
  346. package/src/memory/v2/__tests__/static-context.test.ts +0 -1
  347. package/src/memory/v2/activation.ts +69 -88
  348. package/src/memory/v2/consolidation-job.ts +3 -5
  349. package/src/memory/v2/constants.ts +7 -0
  350. package/src/memory/v2/injection.ts +86 -53
  351. package/src/memory/v2/prompts/consolidation.ts +312 -91
  352. package/src/memory/v2/qdrant.ts +99 -1
  353. package/src/memory/v2/sim.ts +126 -16
  354. package/src/memory/v2/skill-qdrant.ts +12 -3
  355. package/src/memory/v2/skill-store.ts +16 -1
  356. package/src/memory/v2/sparse-bm25.ts +245 -0
  357. package/src/memory/v2/static-context.ts +6 -5
  358. package/src/messaging/providers/gmail/types.ts +0 -49
  359. package/src/messaging/providers/slack/adapter.ts +1 -31
  360. package/src/messaging/providers/slack/types.ts +0 -32
  361. package/src/notifications/README.md +10 -10
  362. package/src/notifications/broadcaster.ts +1 -1
  363. package/src/notifications/guardian-question-mode.ts +5 -5
  364. package/src/oauth/connect-orchestrator.ts +4 -0
  365. package/src/oauth/credential-token-resolver.ts +1 -3
  366. package/src/oauth/manual-token-connection.ts +0 -4
  367. package/src/outbound-proxy/index.ts +1 -37
  368. package/src/outbound-proxy/logging.ts +1 -1
  369. package/src/outbound-proxy/policy.ts +6 -5
  370. package/src/outbound-proxy/router.ts +2 -1
  371. package/src/permissions/approval-policy.test.ts +6 -275
  372. package/src/permissions/approval-policy.ts +0 -51
  373. package/src/permissions/checker.test.ts +0 -1
  374. package/src/permissions/checker.ts +3 -17
  375. package/src/permissions/gateway-threshold-reader.ts +2 -0
  376. package/src/permissions/prompter.ts +34 -1
  377. package/src/permissions/secret-prompter.ts +6 -2
  378. package/src/prompts/bootstrap-cleanup.ts +27 -0
  379. package/src/prompts/system-prompt.ts +3 -18
  380. package/src/prompts/templates/SOUL.md +13 -1
  381. package/src/providers/speech-to-text/provider-catalog.ts +7 -8
  382. package/src/runtime/assistant-event-hub.ts +118 -96
  383. package/src/runtime/assistant-event.ts +1 -0
  384. package/src/runtime/auth/__tests__/middleware.test.ts +11 -56
  385. package/src/runtime/auth/middleware.ts +0 -96
  386. package/src/runtime/auth/route-policy.ts +19 -0
  387. package/src/runtime/btw-sidechain.ts +2 -3
  388. package/src/runtime/channel-invite-transport.ts +2 -48
  389. package/src/runtime/channel-invite-transports/email.ts +1 -1
  390. package/src/runtime/channel-invite-transports/slack.ts +1 -1
  391. package/src/runtime/channel-invite-transports/telegram.ts +1 -1
  392. package/src/runtime/channel-invite-transports/voice.ts +1 -1
  393. package/src/runtime/channel-invite-transports/whatsapp.ts +1 -1
  394. package/src/runtime/channel-invite-types.ts +54 -0
  395. package/src/runtime/channel-readiness-service.ts +32 -13
  396. package/src/runtime/http-server.ts +3 -329
  397. package/src/runtime/http-types.ts +0 -5
  398. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +413 -0
  399. package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +260 -0
  400. package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +189 -0
  401. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +153 -1
  402. package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +451 -0
  403. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  404. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +515 -0
  405. package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +437 -0
  406. package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +319 -0
  407. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +51 -1
  408. package/src/runtime/migrations/migration-transport.ts +7 -7
  409. package/src/runtime/migrations/vbundle-builder.ts +327 -60
  410. package/src/runtime/migrations/vbundle-import-analyzer.ts +4 -4
  411. package/src/runtime/migrations/vbundle-import-policy.ts +172 -0
  412. package/src/runtime/migrations/vbundle-importer.ts +245 -68
  413. package/src/runtime/migrations/vbundle-streaming-importer.ts +326 -35
  414. package/src/runtime/migrations/vbundle-streaming-validator.ts +157 -4
  415. package/src/runtime/migrations/vbundle-tar-stream.ts +15 -6
  416. package/src/runtime/migrations/vbundle-validator.ts +114 -0
  417. package/src/runtime/pending-interactions.ts +35 -9
  418. package/src/runtime/routes/__tests__/backup-routes.test.ts +22 -150
  419. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  420. package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +242 -0
  421. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +112 -0
  422. package/src/runtime/routes/approval-interception-types.ts +13 -0
  423. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +1 -1
  424. package/src/runtime/routes/backup-routes.ts +15 -38
  425. package/src/runtime/routes/btw-routes.ts +14 -37
  426. package/src/runtime/routes/client-routes.ts +1 -0
  427. package/src/runtime/routes/contact-prompt-routes.ts +183 -0
  428. package/src/runtime/routes/conversation-query-routes.ts +36 -1
  429. package/src/runtime/routes/conversation-routes.ts +30 -13
  430. package/src/runtime/routes/document-pdf-renderer.ts +165 -0
  431. package/src/runtime/routes/documents-routes.ts +30 -0
  432. package/src/runtime/routes/errors.ts +19 -4
  433. package/src/runtime/routes/events-routes.ts +12 -6
  434. package/src/runtime/routes/gateway-log-routes.ts +79 -0
  435. package/src/runtime/routes/guardian-approval-interception.ts +2 -8
  436. package/src/runtime/routes/heartbeat-routes.ts +103 -38
  437. package/src/runtime/routes/host-app-control-routes.ts +134 -0
  438. package/src/runtime/routes/host-bash-routes.ts +36 -6
  439. package/src/runtime/routes/host-browser-routes.ts +108 -13
  440. package/src/runtime/routes/host-cu-routes.ts +44 -14
  441. package/src/runtime/routes/host-file-routes.ts +33 -10
  442. package/src/runtime/routes/host-transfer-routes.ts +64 -24
  443. package/src/runtime/routes/http-adapter.ts +1 -0
  444. package/src/runtime/routes/identity-intro-cache.ts +30 -0
  445. package/src/runtime/routes/identity-routes.ts +15 -43
  446. package/src/runtime/routes/inbound-message-handler.ts +1 -9
  447. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -7
  448. package/src/runtime/routes/inbound-stages/edit-intercept.ts +0 -8
  449. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -20
  450. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +5 -13
  451. package/src/runtime/routes/index.ts +8 -0
  452. package/src/runtime/routes/mcp-auth-routes.ts +132 -0
  453. package/src/runtime/routes/memory-item-routes.ts +10 -12
  454. package/src/runtime/routes/memory-v2-routes.ts +441 -1
  455. package/src/runtime/routes/migration-routes.ts +96 -0
  456. package/src/runtime/routes/schedule-routes.ts +7 -0
  457. package/src/runtime/verification-templates.ts +4 -7
  458. package/src/schedule/integration-status.ts +66 -2
  459. package/src/schedule/recurrence-engine.ts +4 -1
  460. package/src/schedule/retry-backoff.ts +18 -0
  461. package/src/schedule/retry-policy.ts +82 -0
  462. package/src/schedule/schedule-recovery.ts +64 -0
  463. package/src/schedule/schedule-store.ts +106 -2
  464. package/src/schedule/scheduler-types.ts +25 -0
  465. package/src/schedule/scheduler.ts +63 -38
  466. package/src/security/oauth-callback-registry.ts +8 -0
  467. package/src/sequence/analytics.ts +5 -5
  468. package/src/sequence/engine.ts +1 -1
  469. package/src/skills/catalog-files.ts +2 -8
  470. package/src/skills/include-graph.ts +5 -5
  471. package/src/skills/remote-skill-policy.ts +5 -5
  472. package/src/skills/skill-file-provider.ts +1 -1
  473. package/src/skills/skill-file-types.ts +13 -0
  474. package/src/skills/skillssh-audit-types.ts +28 -0
  475. package/src/skills/skillssh-registry.ts +8 -21
  476. package/src/telemetry/types.ts +2 -0
  477. package/src/telemetry/usage-telemetry-reporter.test.ts +21 -0
  478. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  479. package/src/tools/app-control/skill-proxy-bridge.ts +28 -0
  480. package/src/tools/apps/executors.ts +56 -69
  481. package/src/tools/browser/__tests__/browser-status.test.ts +21 -18
  482. package/src/tools/browser/browser-execution.ts +2 -2
  483. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +55 -4
  484. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +12 -6
  485. package/src/tools/browser/cdp-client/factory.ts +23 -24
  486. package/src/tools/browser/cdp-client/index.ts +1 -14
  487. package/src/tools/computer-use/definitions.ts +42 -20
  488. package/src/tools/executor.ts +2 -0
  489. package/src/tools/host-filesystem/edit.ts +26 -0
  490. package/src/tools/host-filesystem/read.ts +26 -0
  491. package/src/tools/host-filesystem/transfer.ts +31 -1
  492. package/src/tools/host-filesystem/write.ts +26 -0
  493. package/src/tools/host-terminal/host-shell.ts +58 -0
  494. package/src/tools/schedule/create.ts +6 -0
  495. package/src/tools/schedule/list.ts +2 -0
  496. package/src/tools/schedule/update.ts +10 -0
  497. package/src/tools/shared/filesystem/file-ops-service.ts +2 -0
  498. package/src/tools/shared/filesystem/path-policy.ts +25 -1
  499. package/src/tools/skills/load.ts +0 -32
  500. package/src/tools/tool-approval-handler.ts +1 -5
  501. package/src/tools/types.ts +4 -0
  502. package/src/usage/pricing.ts +1 -1
  503. package/src/workspace/hatched-date.ts +86 -0
  504. package/src/workspace/migrations/003-seed-device-id.ts +1 -1
  505. package/src/workspace/migrations/006-services-config.ts +8 -5
  506. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +3 -9
  507. package/src/workspace/migrations/021-move-signals-to-workspace.ts +4 -10
  508. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +4 -10
  509. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +4 -11
  510. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +3 -10
  511. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +3 -2
  512. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +2 -1
  513. package/src/workspace/migrations/059-move-pid-to-workspace.ts +3 -8
  514. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +3 -8
  515. package/src/workspace/migrations/AGENTS.md +1 -1
  516. package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -10
  517. package/src/workspace/migrations/utils.ts +21 -0
  518. package/src/__tests__/host-browser-e2e-cloud.test.ts +0 -443
  519. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +0 -226
  520. package/src/__tests__/host-browser-ws-events-e2e.test.ts +0 -427
  521. package/src/__tests__/twilio-rest.test.ts +0 -34
  522. package/src/backup/__tests__/backup-key.test.ts +0 -152
  523. package/src/backup/__tests__/backup-worker.test.ts +0 -782
  524. package/src/backup/__tests__/offsite-writer.test.ts +0 -641
  525. package/src/backup/__tests__/stream-crypt.test.ts +0 -228
  526. package/src/backup/backup-key.ts +0 -137
  527. package/src/backup/backup-worker.ts +0 -472
  528. package/src/backup/offsite-writer.ts +0 -222
  529. package/src/backup/stream-crypt.ts +0 -263
  530. package/src/daemon/message-types/pairing.ts +0 -58
  531. package/src/outbound-proxy/config.ts +0 -20
  532. package/src/outbound-proxy/health.ts +0 -18
  533. package/src/outbound-proxy/types.ts +0 -150
  534. package/src/runtime/capability-tokens.ts +0 -190
  535. package/src/signals/mcp-reload.ts +0 -18
@@ -0,0 +1,133 @@
1
+ /**
2
+ * In-memory MCP OAuth flow status map.
3
+ *
4
+ * Tracks the current state of daemon-owned MCP OAuth flows so the CLI can
5
+ * poll for completion via the IPC route.
6
+ */
7
+
8
+ /**
9
+ * Sibling: `assistant/src/security/oauth-callback-registry.ts`. The two look
10
+ * similar (in-memory map, supersede semantics, ~5 min TTL) but live at
11
+ * different layers. The callback registry stores the deferred resolve/reject
12
+ * pair for a single OAuth code arrival, keyed by OAuth `state`. This file
13
+ * stores observable status (pending / complete / error) keyed by serverId so
14
+ * the polling CLI can render progress without holding a long-lived IPC
15
+ * connection.
16
+ */
17
+ type McpAuthState =
18
+ | { status: "pending"; authUrl: string; attemptId: string; expiresAt: number }
19
+ | {
20
+ status: "complete";
21
+ serverId: string;
22
+ attemptId: string;
23
+ completedAt: number;
24
+ }
25
+ | { status: "error"; error: string; attemptId: string; failedAt: number };
26
+
27
+ const activeMcpAuthFlows = new Map<string, McpAuthState>();
28
+
29
+ const PENDING_TTL_MS = 5 * 60 * 1000; // 5 min — matches oauth-callback-registry.ts
30
+ const COMPLETION_GRACE_MS = 60 * 1000; // 60s so the polling CLI gets one final read
31
+
32
+ /**
33
+ * Record that an OAuth flow is pending authorization.
34
+ * Overwrites any prior state for the same serverId (supersede semantics
35
+ * matching registerPendingCallback). The caller must pass an `attemptId`
36
+ * (a unique token per attempt) so that fire-and-forget completion writes
37
+ * can verify they still own the slot before mutating shared state — see
38
+ * `setMcpAuthComplete` / `setMcpAuthError`.
39
+ */
40
+ export function setMcpAuthPending(
41
+ serverId: string,
42
+ authUrl: string,
43
+ attemptId: string,
44
+ ): void {
45
+ activeMcpAuthFlows.set(serverId, {
46
+ status: "pending",
47
+ authUrl,
48
+ attemptId,
49
+ expiresAt: Date.now() + PENDING_TTL_MS,
50
+ });
51
+ }
52
+
53
+ /**
54
+ * Record that an OAuth flow completed successfully. Returns true if the
55
+ * write was applied; false if the attempt has been superseded by a newer
56
+ * one (in which case the caller's tail should silently exit without
57
+ * touching state).
58
+ */
59
+ export function setMcpAuthComplete(
60
+ serverId: string,
61
+ attemptId: string,
62
+ ): boolean {
63
+ const current = activeMcpAuthFlows.get(serverId);
64
+ if (current && current.attemptId !== attemptId) {
65
+ return false; // superseded
66
+ }
67
+ activeMcpAuthFlows.set(serverId, {
68
+ status: "complete",
69
+ serverId,
70
+ attemptId,
71
+ completedAt: Date.now(),
72
+ });
73
+ return true;
74
+ }
75
+
76
+ /**
77
+ * Record that an OAuth flow failed. Returns true if the write was applied;
78
+ * false if the attempt has been superseded.
79
+ */
80
+ export function setMcpAuthError(
81
+ serverId: string,
82
+ error: string,
83
+ attemptId: string,
84
+ ): boolean {
85
+ const current = activeMcpAuthFlows.get(serverId);
86
+ if (current && current.attemptId !== attemptId) {
87
+ return false; // superseded
88
+ }
89
+ activeMcpAuthFlows.set(serverId, {
90
+ status: "error",
91
+ error,
92
+ attemptId,
93
+ failedAt: Date.now(),
94
+ });
95
+ return true;
96
+ }
97
+
98
+ /**
99
+ * Get the current state of an OAuth flow, or null if none exists.
100
+ */
101
+ export function getMcpAuthState(serverId: string): McpAuthState | null {
102
+ clearExpiredMcpAuthStates();
103
+ return activeMcpAuthFlows.get(serverId) ?? null;
104
+ }
105
+
106
+ /**
107
+ * Remove expired entries from the auth flow map.
108
+ */
109
+ export function clearExpiredMcpAuthStates(): void {
110
+ const now = Date.now();
111
+ for (const [serverId, state] of activeMcpAuthFlows) {
112
+ if (state.status === "pending" && now > state.expiresAt) {
113
+ activeMcpAuthFlows.delete(serverId);
114
+ } else if (
115
+ state.status === "complete" &&
116
+ now > state.completedAt + COMPLETION_GRACE_MS
117
+ ) {
118
+ activeMcpAuthFlows.delete(serverId);
119
+ } else if (
120
+ state.status === "error" &&
121
+ now > state.failedAt + COMPLETION_GRACE_MS
122
+ ) {
123
+ activeMcpAuthFlows.delete(serverId);
124
+ }
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Test-only helper — clears all auth flow state for test isolation.
130
+ */
131
+ export function _clearAllMcpAuthStates(): void {
132
+ activeMcpAuthFlows.clear();
133
+ }
@@ -61,6 +61,16 @@ export interface McpOAuthCallbackResult {
61
61
  /** Which callback transport to use for receiving the OAuth redirect. */
62
62
  export type McpOAuthCallbackTransport = "loopback" | "gateway";
63
63
 
64
+ export interface McpOAuthProviderOptions {
65
+ /**
66
+ * If provided, called with the authorization URL during
67
+ * redirectToAuthorization() instead of opening a browser.
68
+ * Used by the daemon-side orchestrator so it can return the
69
+ * URL to the IPC caller (CLI / web client).
70
+ */
71
+ onAuthorizationUrl?: (url: string) => void;
72
+ }
73
+
64
74
  export class McpOAuthProvider implements OAuthClientProvider {
65
75
  private readonly serverId: string;
66
76
  private readonly serverUrl: string;
@@ -75,6 +85,7 @@ export class McpOAuthProvider implements OAuthClientProvider {
75
85
  /** Deferred resolver/rejector for the gateway code promise. */
76
86
  private _gatewayCodeResolve: ((code: string) => void) | undefined;
77
87
  private _gatewayCodeReject: ((err: Error) => void) | undefined;
88
+ private readonly _onAuthorizationUrl: ((url: string) => void) | undefined;
78
89
 
79
90
  /**
80
91
  * @param interactive When true (e.g. `mcp auth` CLI), opens browser for OAuth.
@@ -82,17 +93,20 @@ export class McpOAuthProvider implements OAuthClientProvider {
82
93
  * @param callbackTransport Which transport to use for the OAuth redirect.
83
94
  * - `"loopback"` (default): localhost HTTP server — for desktop clients.
84
95
  * - `"gateway"`: platform ingress + callback registry — for Docker/platform.
96
+ * @param options Additional options for the provider.
85
97
  */
86
98
  constructor(
87
99
  serverId: string,
88
100
  serverUrl: string,
89
101
  interactive = false,
90
102
  callbackTransport: McpOAuthCallbackTransport = "loopback",
103
+ options: McpOAuthProviderOptions = {},
91
104
  ) {
92
105
  this.serverId = serverId;
93
106
  this.serverUrl = serverUrl;
94
107
  this.interactive = interactive;
95
108
  this.callbackTransport = callbackTransport;
109
+ this._onAuthorizationUrl = options.onAuthorizationUrl;
96
110
  }
97
111
 
98
112
  // --- redirectUrl ---
@@ -269,6 +283,11 @@ export class McpOAuthProvider implements OAuthClientProvider {
269
283
  }
270
284
  }
271
285
 
286
+ if (this._onAuthorizationUrl) {
287
+ this._onAuthorizationUrl(url);
288
+ return;
289
+ }
290
+
272
291
  if (!this.interactive) {
273
292
  // Daemon mode — don't open browser, just log guidance
274
293
  log.info(
@@ -0,0 +1,24 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { EMBED_JOB_TYPES, SLOW_LLM_JOB_TYPES } from "../jobs-store.js";
4
+
5
+ describe("memory job classes", () => {
6
+ test("EMBED_JOB_TYPES and SLOW_LLM_JOB_TYPES are disjoint", () => {
7
+ const embedSet = new Set<string>(EMBED_JOB_TYPES);
8
+ const overlap = SLOW_LLM_JOB_TYPES.filter((t) => embedSet.has(t));
9
+ expect(overlap).toEqual([]);
10
+ });
11
+
12
+ test("SLOW_LLM_JOB_TYPES entries are non-empty strings", () => {
13
+ expect(SLOW_LLM_JOB_TYPES.length).toBeGreaterThan(0);
14
+ for (const t of SLOW_LLM_JOB_TYPES) {
15
+ expect(typeof t).toBe("string");
16
+ expect(t.length).toBeGreaterThan(0);
17
+ }
18
+ });
19
+
20
+ test("SLOW_LLM_JOB_TYPES has no duplicate entries", () => {
21
+ const set = new Set(SLOW_LLM_JOB_TYPES);
22
+ expect(set.size).toBe(SLOW_LLM_JOB_TYPES.length);
23
+ });
24
+ });
@@ -0,0 +1,49 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { stripLegacySparseSuffix } from "../qdrant-client.js";
4
+
5
+ describe("stripLegacySparseSuffix", () => {
6
+ test("strips a trailing :sparse-v<digit> suffix", () => {
7
+ expect(
8
+ stripLegacySparseSuffix("gemini:gemini-embedding-2-preview:sparse-v3"),
9
+ ).toBe("gemini:gemini-embedding-2-preview");
10
+ });
11
+
12
+ test("strips multi-digit version suffixes", () => {
13
+ expect(stripLegacySparseSuffix("openai:text-embed-3:sparse-v42")).toBe(
14
+ "openai:text-embed-3",
15
+ );
16
+ });
17
+
18
+ test("returns the input unchanged when no suffix is present", () => {
19
+ expect(stripLegacySparseSuffix("gemini:gemini-embedding-2-preview")).toBe(
20
+ "gemini:gemini-embedding-2-preview",
21
+ );
22
+ });
23
+
24
+ test("does not strip a non-trailing :sparse-v segment", () => {
25
+ expect(stripLegacySparseSuffix("foo:sparse-v3:bar")).toBe(
26
+ "foo:sparse-v3:bar",
27
+ );
28
+ });
29
+
30
+ test("does not strip when the version part is missing", () => {
31
+ expect(stripLegacySparseSuffix("provider:model:sparse-v")).toBe(
32
+ "provider:model:sparse-v",
33
+ );
34
+ });
35
+
36
+ test("normalizes legacy and current sentinels to the same value", () => {
37
+ const legacy = "gemini:gemini-embedding-2-preview:sparse-v2";
38
+ const current = "gemini:gemini-embedding-2-preview";
39
+ expect(stripLegacySparseSuffix(legacy)).toBe(
40
+ stripLegacySparseSuffix(current),
41
+ );
42
+ });
43
+
44
+ test("differentiates sentinels that diverge on the dense identity", () => {
45
+ const a = "gemini:gemini-embedding-2-preview:sparse-v2";
46
+ const b = "openai:text-embedding-3-small:sparse-v2";
47
+ expect(stripLegacySparseSuffix(a)).not.toBe(stripLegacySparseSuffix(b));
48
+ });
49
+ });
@@ -0,0 +1,66 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { tokenize, tokenizeStemmed } from "../sparse-tokenize.js";
4
+
5
+ describe("tokenize", () => {
6
+ test("lowercases and splits on non-alphanumeric runs", () => {
7
+ expect(tokenize("Hello, World!")).toEqual(["hello", "world"]);
8
+ });
9
+
10
+ test("preserves alphanumeric runs as-is (no stemming)", () => {
11
+ expect(tokenize("running supplements taking")).toEqual([
12
+ "running",
13
+ "supplements",
14
+ "taking",
15
+ ]);
16
+ });
17
+
18
+ test("handles Unicode letters and digits", () => {
19
+ expect(tokenize("café-99 naïve")).toEqual(["café", "99", "naïve"]);
20
+ });
21
+
22
+ test("returns empty array for empty / whitespace input", () => {
23
+ expect(tokenize("")).toEqual([]);
24
+ expect(tokenize(" \n\t ")).toEqual([]);
25
+ });
26
+ });
27
+
28
+ describe("tokenizeStemmed", () => {
29
+ test("collapses plurals to their singular stem", () => {
30
+ expect(tokenizeStemmed("supplements")).toEqual(["supplement"]);
31
+ expect(tokenizeStemmed("tests")).toEqual(["test"]);
32
+ });
33
+
34
+ test("collapses verb tenses and gerunds to a shared stem", () => {
35
+ expect(tokenizeStemmed("taking")).toEqual(["take"]);
36
+ expect(tokenizeStemmed("running")).toEqual(["run"]);
37
+ expect(tokenizeStemmed("testing")).toEqual(["test"]);
38
+ });
39
+
40
+ test("singular forms map to themselves (idempotent on stems)", () => {
41
+ expect(tokenizeStemmed("supplement")).toEqual(["supplement"]);
42
+ expect(tokenizeStemmed("run")).toEqual(["run"]);
43
+ });
44
+
45
+ test("query and document forms collapse to identical buckets", () => {
46
+ // Plural query terms must land on the same stem as singular doc terms
47
+ // (and vice versa). This is the property the BM25 sparse channel
48
+ // relies on so doc-side weights and query-side occurrences match.
49
+ const queryTokens = tokenizeStemmed("filing reports");
50
+ const docTokens = tokenizeStemmed("filed a report");
51
+ expect(queryTokens).toContain("file");
52
+ expect(queryTokens).toContain("report");
53
+ expect(docTokens).toContain("file");
54
+ expect(docTokens).toContain("report");
55
+ });
56
+
57
+ test("returns empty array for empty input", () => {
58
+ expect(tokenizeStemmed("")).toEqual([]);
59
+ });
60
+
61
+ test("preserves token order and length (one stem per token)", () => {
62
+ const original = tokenize("the quick brown foxes were jumping");
63
+ const stemmed = tokenizeStemmed("the quick brown foxes were jumping");
64
+ expect(stemmed.length).toBe(original.length);
65
+ });
66
+ });
@@ -0,0 +1,247 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ type AnisotropyCalibration,
5
+ applyAnisotropyCorrection,
6
+ explainedVarianceRatio,
7
+ fitAnisotropyCalibration,
8
+ } from "./anisotropy.js";
9
+
10
+ const META = { provider: "gemini" as const, model: "test-model" };
11
+
12
+ function dot(a: readonly number[], b: readonly number[]): number {
13
+ let s = 0;
14
+ for (let i = 0; i < a.length; i++) s += a[i] * b[i];
15
+ return s;
16
+ }
17
+
18
+ function l2Norm(v: readonly number[]): number {
19
+ return Math.sqrt(dot(v, v));
20
+ }
21
+
22
+ function l2Normalize(v: readonly number[]): number[] {
23
+ const n = l2Norm(v);
24
+ if (n === 0) return [...v];
25
+ return v.map((x) => x / n);
26
+ }
27
+
28
+ /**
29
+ * Build a synthetic anisotropic corpus: every sample lives close to a fixed
30
+ * direction `axis`, with small Gaussian-ish noise injected in the orthogonal
31
+ * subspace. This gives us a known top-1 PC the fit must recover.
32
+ */
33
+ function buildAnisotropicCorpus(
34
+ n: number,
35
+ dim: number,
36
+ axis: readonly number[],
37
+ noiseScale: number,
38
+ ): number[][] {
39
+ const ax = l2Normalize(axis);
40
+ const out: number[][] = [];
41
+ let seed = 42;
42
+ const rand = () => {
43
+ // Tiny deterministic LCG so tests don't depend on Math.random.
44
+ seed = (seed * 1103515245 + 12345) & 0x7fffffff;
45
+ return seed / 0x7fffffff;
46
+ };
47
+ for (let i = 0; i < n; i++) {
48
+ const v = new Array<number>(dim);
49
+ // Strong common direction component, slightly varied per sample.
50
+ const along = 1 + (rand() - 0.5) * 0.1;
51
+ for (let j = 0; j < dim; j++) v[j] = along * ax[j];
52
+ // Orthogonal noise.
53
+ for (let j = 0; j < dim; j++) v[j] += (rand() - 0.5) * noiseScale;
54
+ out.push(v);
55
+ }
56
+ return out;
57
+ }
58
+
59
+ describe("fitAnisotropyCalibration", () => {
60
+ test("recovers the dominant direction of an anisotropic corpus", () => {
61
+ const dim = 16;
62
+ const axis = new Array<number>(dim)
63
+ .fill(0)
64
+ .map((_, i) => (i === 0 ? 1 : 0));
65
+ const corpus = buildAnisotropicCorpus(200, dim, axis, 0.05);
66
+
67
+ const calib = fitAnisotropyCalibration(corpus, 1, META);
68
+
69
+ expect(calib.components.length).toBe(1);
70
+ expect(calib.components[0].length).toBe(dim);
71
+ // PC1 should align with the planted axis (sign-agnostic).
72
+ const alignment = Math.abs(dot(calib.components[0], axis));
73
+ expect(alignment).toBeGreaterThan(0.95);
74
+ });
75
+
76
+ test("captures most variance in PC1 for a strongly anisotropic corpus", () => {
77
+ const dim = 16;
78
+ const axis = new Array<number>(dim)
79
+ .fill(0)
80
+ .map((_, i) => (i === 0 ? 1 : 0));
81
+ const corpus = buildAnisotropicCorpus(200, dim, axis, 0.02);
82
+
83
+ const calib = fitAnisotropyCalibration(corpus, 3, META);
84
+ const ratios = explainedVarianceRatio(calib);
85
+
86
+ // Spectrum is monotonically non-increasing.
87
+ expect(ratios[0]).toBeGreaterThanOrEqual(ratios[1]);
88
+ expect(ratios[1]).toBeGreaterThanOrEqual(ratios[2]);
89
+ // PC1 should dwarf PC2 by an order of magnitude when noise is small.
90
+ expect(ratios[0]).toBeGreaterThan(ratios[1] * 10);
91
+ });
92
+
93
+ test("returns mean and dim metadata", () => {
94
+ const dim = 8;
95
+ const corpus = [
96
+ [1, 1, 0, 0, 0, 0, 0, 0],
97
+ [3, 3, 0, 0, 0, 0, 0, 0],
98
+ ];
99
+ const calib = fitAnisotropyCalibration(corpus, 1, META);
100
+ expect(calib.dim).toBe(dim);
101
+ expect(calib.mean[0]).toBeCloseTo(2);
102
+ expect(calib.mean[1]).toBeCloseTo(2);
103
+ expect(calib.mean[2]).toBeCloseTo(0);
104
+ expect(calib.sampleCount).toBe(2);
105
+ expect(calib.provider).toBe("gemini");
106
+ expect(calib.model).toBe("test-model");
107
+ });
108
+
109
+ test("rejects empty input, bad k, and ragged rows", () => {
110
+ expect(() => fitAnisotropyCalibration([], 1, META)).toThrow(/no vectors/);
111
+ expect(() => fitAnisotropyCalibration([[1, 2, 3]], 0, META)).toThrow(
112
+ /positive integer/,
113
+ );
114
+ expect(() =>
115
+ fitAnisotropyCalibration(
116
+ [
117
+ [1, 2],
118
+ [3, 4, 5],
119
+ ],
120
+ 1,
121
+ META,
122
+ ),
123
+ ).toThrow(/dim/);
124
+ expect(() => fitAnisotropyCalibration([[1, 2]], 5, META)).toThrow(
125
+ /exceeds/,
126
+ );
127
+ });
128
+ });
129
+
130
+ describe("applyAnisotropyCorrection", () => {
131
+ test("output has unit L2 norm", () => {
132
+ const corpus = buildAnisotropicCorpus(
133
+ 100,
134
+ 8,
135
+ [1, 0, 0, 0, 0, 0, 0, 0],
136
+ 0.05,
137
+ );
138
+ const calib = fitAnisotropyCalibration(corpus, 1, META);
139
+
140
+ const corrected = applyAnisotropyCorrection(corpus[0], calib);
141
+ expect(l2Norm(corrected)).toBeCloseTo(1, 6);
142
+ });
143
+
144
+ test("removes projection onto the dominant direction", () => {
145
+ const dim = 8;
146
+ const axis = [1, 0, 0, 0, 0, 0, 0, 0];
147
+ const corpus = buildAnisotropicCorpus(200, dim, axis, 0.02);
148
+ const calib = fitAnisotropyCalibration(corpus, 1, META);
149
+
150
+ // Pick a vector that points strongly along the planted axis.
151
+ const sample = [...axis];
152
+ const corrected = applyAnisotropyCorrection(sample, calib);
153
+
154
+ // After removing PC1 (which is ~axis), the projection back onto axis
155
+ // should be nearly zero — the sample is in the deflated subspace.
156
+ expect(Math.abs(dot(corrected, axis))).toBeLessThan(0.05);
157
+ });
158
+
159
+ test("spreads cosine similarities for vectors in the cone", () => {
160
+ const dim = 16;
161
+ const axis = new Array<number>(dim)
162
+ .fill(0)
163
+ .map((_, i) => (i === 0 ? 1 : 0));
164
+ const corpus = buildAnisotropicCorpus(300, dim, axis, 0.1);
165
+ const calib = fitAnisotropyCalibration(corpus, 1, META);
166
+
167
+ // Compute pairwise cosines on raw vs corrected vectors. The corrected
168
+ // distribution should have noticeably larger spread (max - min) — the
169
+ // whole point of the correction.
170
+ function spread(vectors: number[][]): number {
171
+ const sims: number[] = [];
172
+ for (let i = 0; i < vectors.length; i++) {
173
+ const a = l2Normalize(vectors[i]);
174
+ for (let j = i + 1; j < vectors.length; j++) {
175
+ const b = l2Normalize(vectors[j]);
176
+ sims.push(dot(a, b));
177
+ }
178
+ }
179
+ let min = Infinity;
180
+ let max = -Infinity;
181
+ for (const s of sims) {
182
+ if (s < min) min = s;
183
+ if (s > max) max = s;
184
+ }
185
+ return max - min;
186
+ }
187
+
188
+ const rawSpread = spread(corpus.slice(0, 30));
189
+ const correctedSpread = spread(
190
+ corpus.slice(0, 30).map((v) => applyAnisotropyCorrection(v, calib)),
191
+ );
192
+
193
+ expect(correctedSpread).toBeGreaterThan(rawSpread * 2);
194
+ });
195
+
196
+ test("rejects mismatched dim", () => {
197
+ const calib: AnisotropyCalibration = {
198
+ provider: "gemini",
199
+ model: "x",
200
+ dim: 4,
201
+ mean: [0, 0, 0, 0],
202
+ components: [[1, 0, 0, 0]],
203
+ componentVariance: [1],
204
+ totalVariance: 1,
205
+ sampleCount: 1,
206
+ fitAt: 0,
207
+ };
208
+ expect(() => applyAnisotropyCorrection([1, 2, 3], calib)).toThrow(/dim/);
209
+ });
210
+ });
211
+
212
+ describe("explainedVarianceRatio", () => {
213
+ test("returns zeros when totalVariance is zero", () => {
214
+ const calib: AnisotropyCalibration = {
215
+ provider: "gemini",
216
+ model: "x",
217
+ dim: 2,
218
+ mean: [0, 0],
219
+ components: [[1, 0]],
220
+ componentVariance: [0],
221
+ totalVariance: 0,
222
+ sampleCount: 1,
223
+ fitAt: 0,
224
+ };
225
+ expect(explainedVarianceRatio(calib)).toEqual([0]);
226
+ });
227
+
228
+ test("each ratio is variance/total", () => {
229
+ const calib: AnisotropyCalibration = {
230
+ provider: "gemini",
231
+ model: "x",
232
+ dim: 2,
233
+ mean: [0, 0],
234
+ components: [
235
+ [1, 0],
236
+ [0, 1],
237
+ ],
238
+ componentVariance: [3, 1],
239
+ totalVariance: 4,
240
+ sampleCount: 100,
241
+ fitAt: 0,
242
+ };
243
+ const ratios = explainedVarianceRatio(calib);
244
+ expect(ratios[0]).toBeCloseTo(0.75);
245
+ expect(ratios[1]).toBeCloseTo(0.25);
246
+ });
247
+ });