@vellumai/assistant 0.7.2 → 0.7.3

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 (347) hide show
  1. package/ARCHITECTURE.md +16 -1
  2. package/docs/architecture/memory.md +5 -2
  3. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
  4. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
  5. package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
  6. package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
  7. package/openapi.yaml +449 -22
  8. package/package.json +1 -1
  9. package/src/__tests__/app-control-flow.test.ts +21 -11
  10. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  11. package/src/__tests__/assistant-event.test.ts +0 -10
  12. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
  13. package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
  14. package/src/__tests__/auto-analysis-end-to-end.test.ts +62 -1
  15. package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
  16. package/src/__tests__/call-conversation-messages.test.ts +8 -2
  17. package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
  18. package/src/__tests__/channel-readiness-service.test.ts +4 -2
  19. package/src/__tests__/config-loader-backfill.test.ts +379 -0
  20. package/src/__tests__/config-schema.test.ts +1 -0
  21. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
  22. package/src/__tests__/config-watcher.test.ts +140 -69
  23. package/src/__tests__/context-search-agent-runner.test.ts +61 -3
  24. package/src/__tests__/context-search-conversations-source.test.ts +0 -24
  25. package/src/__tests__/context-search-fanout.test.ts +0 -1
  26. package/src/__tests__/context-search-memory-source.test.ts +3 -7
  27. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
  28. package/src/__tests__/context-search-pkb-source.test.ts +0 -1
  29. package/src/__tests__/context-search-workspace-source.test.ts +0 -1
  30. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
  31. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
  32. package/src/__tests__/conversation-agent-loop.test.ts +454 -5
  33. package/src/__tests__/conversation-error.test.ts +150 -3
  34. package/src/__tests__/conversation-process-callsite.test.ts +43 -0
  35. package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
  36. package/src/__tests__/conversation-runtime-assembly.test.ts +65 -0
  37. package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
  38. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  39. package/src/__tests__/conversation-store.test.ts +0 -18
  40. package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
  41. package/src/__tests__/conversation-surfaces-data-persist.test.ts +404 -0
  42. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +2 -5
  43. package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
  44. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
  45. package/src/__tests__/credentials-cli.test.ts +7 -0
  46. package/src/__tests__/cu-unified-flow.test.ts +176 -10
  47. package/src/__tests__/date-context.test.ts +164 -2
  48. package/src/__tests__/disk-pressure-guard.test.ts +262 -0
  49. package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
  50. package/src/__tests__/disk-pressure-policy.test.ts +241 -0
  51. package/src/__tests__/disk-pressure-routes.test.ts +379 -0
  52. package/src/__tests__/disk-pressure-tools.test.ts +277 -0
  53. package/src/__tests__/disk-usage.test.ts +150 -0
  54. package/src/__tests__/events-client-registration.test.ts +52 -0
  55. package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
  56. package/src/__tests__/file-write-tool.test.ts +4 -10
  57. package/src/__tests__/filing-service.test.ts +3 -4
  58. package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
  59. package/src/__tests__/heartbeat-service.test.ts +260 -11
  60. package/src/__tests__/host-app-control-proxy.test.ts +195 -25
  61. package/src/__tests__/host-bash-proxy.test.ts +227 -34
  62. package/src/__tests__/host-bash-routes.test.ts +178 -13
  63. package/src/__tests__/host-cu-proxy.test.ts +210 -3
  64. package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
  65. package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
  66. package/src/__tests__/host-file-proxy.test.ts +268 -6
  67. package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
  68. package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
  69. package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
  70. package/src/__tests__/http-user-message-parity.test.ts +107 -1
  71. package/src/__tests__/injector-chain.test.ts +18 -6
  72. package/src/__tests__/injector-disk-pressure.test.ts +224 -0
  73. package/src/__tests__/managed-profile-guard.test.ts +18 -0
  74. package/src/__tests__/mcp-abort-signal.test.ts +130 -0
  75. package/src/__tests__/memory-admin-recall.test.ts +3 -11
  76. package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
  77. package/src/__tests__/normalize-onboarding.test.ts +180 -0
  78. package/src/__tests__/oauth-connect-routes.test.ts +316 -0
  79. package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
  80. package/src/__tests__/onboarding-persona-write.test.ts +308 -0
  81. package/src/__tests__/openai-provider.test.ts +45 -8
  82. package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
  83. package/src/__tests__/platform-callback-registration.test.ts +21 -4
  84. package/src/__tests__/platform.test.ts +2 -1
  85. package/src/__tests__/playbook-execution.test.ts +0 -43
  86. package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
  87. package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
  88. package/src/__tests__/provider-tool-name.test.ts +23 -0
  89. package/src/__tests__/relay-server.test.ts +15 -4
  90. package/src/__tests__/runtime-events-sse.test.ts +4 -8
  91. package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
  92. package/src/__tests__/secret-ingress-http.test.ts +0 -1
  93. package/src/__tests__/suggestion-routes.test.ts +46 -0
  94. package/src/__tests__/twilio-validation.test.ts +2 -2
  95. package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
  96. package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
  97. package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
  98. package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +90 -0
  99. package/src/approvals/guardian-decision-primitive.ts +13 -0
  100. package/src/approvals/guardian-request-resolvers.ts +16 -17
  101. package/src/backup/snapshot-lock.ts +2 -27
  102. package/src/bundler/compiler-tools.ts +3 -2
  103. package/src/calls/call-conversation-messages.ts +46 -10
  104. package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
  105. package/src/cli/commands/bash.ts +35 -108
  106. package/src/cli/commands/contacts.ts +64 -25
  107. package/src/cli/commands/credentials.ts +56 -0
  108. package/src/cli/commands/memory-v2.ts +7 -6
  109. package/src/cli/commands/oauth/__tests__/connect.test.ts +437 -1
  110. package/src/cli/commands/oauth/connect.ts +127 -1
  111. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
  112. package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
  113. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  114. package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
  115. package/src/cli/commands/platform/index.ts +16 -7
  116. package/src/cli/commands/status.ts +57 -0
  117. package/src/cli/program.ts +4 -2
  118. package/src/config/assistant-feature-flags.ts +13 -3
  119. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
  120. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
  121. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
  122. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
  123. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
  124. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
  125. package/src/config/env.ts +0 -8
  126. package/src/config/feature-flag-registry.json +27 -3
  127. package/src/config/loader.ts +127 -8
  128. package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
  129. package/src/config/schemas/call-site-catalog.ts +14 -0
  130. package/src/config/schemas/channels.ts +0 -5
  131. package/src/config/schemas/heartbeat.ts +1 -1
  132. package/src/config/schemas/llm.ts +2 -0
  133. package/src/config/schemas/memory-lifecycle.ts +13 -0
  134. package/src/config/schemas/memory-v2.ts +75 -11
  135. package/src/config/schemas/platform.ts +43 -3
  136. package/src/config/schemas/services.ts +28 -0
  137. package/src/config/seed-inference-profiles.ts +230 -33
  138. package/src/contacts/contact-store.ts +0 -25
  139. package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
  140. package/src/daemon/assistant-attachments.ts +4 -4
  141. package/src/daemon/config-watcher.ts +85 -57
  142. package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
  143. package/src/daemon/conversation-agent-loop.ts +170 -33
  144. package/src/daemon/conversation-error.ts +87 -15
  145. package/src/daemon/conversation-lifecycle.ts +1 -3
  146. package/src/daemon/conversation-process.ts +8 -0
  147. package/src/daemon/conversation-runtime-assembly.ts +26 -0
  148. package/src/daemon/conversation-store.ts +2 -2
  149. package/src/daemon/conversation-surfaces.ts +195 -15
  150. package/src/daemon/conversation-tool-setup.ts +57 -14
  151. package/src/daemon/conversation.ts +17 -22
  152. package/src/daemon/date-context.ts +71 -22
  153. package/src/daemon/disk-pressure-background-gate.ts +73 -0
  154. package/src/daemon/disk-pressure-guard.ts +343 -0
  155. package/src/daemon/disk-pressure-policy.ts +163 -0
  156. package/src/daemon/handlers/shared.ts +0 -1
  157. package/src/daemon/handlers/skills.ts +3 -4
  158. package/src/daemon/host-app-control-proxy.ts +137 -41
  159. package/src/daemon/host-bash-proxy.ts +46 -21
  160. package/src/daemon/host-cu-proxy.ts +49 -3
  161. package/src/daemon/host-file-proxy.ts +43 -7
  162. package/src/daemon/host-transfer-proxy.ts +95 -4
  163. package/src/daemon/lifecycle.ts +79 -28
  164. package/src/daemon/meet-host-supervisor.ts +4 -4
  165. package/src/daemon/meet-manifest-loader.ts +0 -1
  166. package/src/daemon/memory-v2-startup.ts +14 -4
  167. package/src/daemon/message-protocol.ts +3 -0
  168. package/src/daemon/message-types/conversations.ts +4 -0
  169. package/src/daemon/message-types/disk-pressure.ts +9 -0
  170. package/src/daemon/message-types/messages.ts +3 -0
  171. package/src/daemon/profiler-run-store.ts +5 -5
  172. package/src/daemon/tool-setup-types.ts +2 -2
  173. package/src/documents/document-store.ts +85 -0
  174. package/src/filing/filing-service.ts +30 -5
  175. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
  176. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
  177. package/src/heartbeat/heartbeat-run-store.ts +13 -0
  178. package/src/heartbeat/heartbeat-service.ts +205 -31
  179. package/src/home/feed-scheduler.ts +18 -0
  180. package/src/inbound/platform-callback-registration.ts +8 -15
  181. package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
  182. package/src/ipc/assistant-server.ts +56 -2
  183. package/src/ipc/gateway-client.ts +37 -3
  184. package/src/live-voice/live-voice-archive.ts +4 -4
  185. package/src/live-voice/protocol.ts +5 -7
  186. package/src/media/image-service.ts +1 -7
  187. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
  188. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +52 -22
  189. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
  190. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
  191. package/src/memory/admin.ts +5 -9
  192. package/src/memory/context-search/agent-runner.ts +19 -2
  193. package/src/memory/context-search/sources/conversations.ts +2 -11
  194. package/src/memory/context-search/sources/memory-v2.ts +5 -4
  195. package/src/memory/context-search/sources/memory.ts +0 -1
  196. package/src/memory/context-search/types.ts +0 -1
  197. package/src/memory/conversation-crud.ts +4 -12
  198. package/src/memory/db-init.ts +2 -0
  199. package/src/memory/embedding-runtime-manager.ts +119 -5
  200. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +32 -21
  201. package/src/memory/graph/conversation-graph-memory.ts +42 -54
  202. package/src/memory/graph/extraction.ts +1 -3
  203. package/src/memory/graph/graph-search.test.ts +10 -67
  204. package/src/memory/graph/graph-search.ts +1 -20
  205. package/src/memory/graph/retriever.test.ts +6 -0
  206. package/src/memory/graph/retriever.ts +6 -10
  207. package/src/memory/indexer.ts +54 -45
  208. package/src/memory/job-handlers/backfill.ts +2 -11
  209. package/src/memory/job-handlers/cleanup.ts +43 -0
  210. package/src/memory/job-handlers/embedding.ts +6 -8
  211. package/src/memory/job-handlers/summarization.ts +2 -7
  212. package/src/memory/jobs-store.ts +48 -0
  213. package/src/memory/jobs-worker.ts +81 -43
  214. package/src/memory/memory-v2-activation-log-store.ts +32 -14
  215. package/src/memory/memory-v2-concept-frequency.ts +169 -0
  216. package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
  217. package/src/memory/migrations/index.ts +1 -0
  218. package/src/memory/pkb/pkb-search.test.ts +6 -0
  219. package/src/memory/qdrant-client.ts +0 -13
  220. package/src/memory/rerank-local.ts +374 -0
  221. package/src/memory/search/semantic.ts +6 -67
  222. package/src/memory/trace-event-store.ts +1 -17
  223. package/src/memory/v2/__tests__/activation.test.ts +311 -250
  224. package/src/memory/v2/__tests__/consolidation-job.test.ts +40 -8
  225. package/src/memory/v2/__tests__/injection.test.ts +157 -167
  226. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
  227. package/src/memory/v2/__tests__/qdrant.test.ts +16 -0
  228. package/src/memory/v2/__tests__/reranker.test.ts +338 -0
  229. package/src/memory/v2/__tests__/sim.test.ts +5 -199
  230. package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
  231. package/src/memory/v2/__tests__/static-context.test.ts +76 -1
  232. package/src/memory/v2/activation.ts +149 -156
  233. package/src/memory/v2/consolidation-job.ts +62 -12
  234. package/src/memory/v2/injection.ts +47 -60
  235. package/src/memory/v2/prompts/consolidation.ts +36 -1
  236. package/src/memory/v2/qdrant.ts +99 -0
  237. package/src/memory/v2/reranker.ts +177 -0
  238. package/src/memory/v2/sim.ts +10 -84
  239. package/src/memory/v2/skill-content.ts +4 -3
  240. package/src/memory/v2/skill-store.ts +82 -59
  241. package/src/memory/v2/static-context.ts +22 -0
  242. package/src/memory/v2/types.ts +10 -10
  243. package/src/notifications/copy-composer.ts +13 -0
  244. package/src/notifications/signal.ts +4 -0
  245. package/src/oauth/AGENTS.md +3 -1
  246. package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
  247. package/src/oauth/connect-orchestrator.ts +2 -0
  248. package/src/oauth/connection-resolver.test.ts +66 -1
  249. package/src/oauth/connection-resolver.ts +55 -1
  250. package/src/oauth/oauth-connect-state.ts +77 -0
  251. package/src/oauth/seed-providers.ts +58 -1
  252. package/src/plugins/defaults/injectors.ts +35 -2
  253. package/src/plugins/defaults/memory-retrieval.ts +5 -6
  254. package/src/plugins/types.ts +7 -0
  255. package/src/proactive-artifact/aux-message-injector.ts +74 -0
  256. package/src/proactive-artifact/decision.test.ts +226 -0
  257. package/src/proactive-artifact/decision.ts +165 -0
  258. package/src/proactive-artifact/index.ts +7 -0
  259. package/src/proactive-artifact/job.test.ts +867 -0
  260. package/src/proactive-artifact/job.ts +352 -0
  261. package/src/proactive-artifact/message-copy.ts +41 -0
  262. package/src/proactive-artifact/trigger-state.test.ts +277 -0
  263. package/src/proactive-artifact/trigger-state.ts +119 -0
  264. package/src/prompts/normalize-onboarding.ts +80 -0
  265. package/src/prompts/persona-resolver.ts +101 -9
  266. package/src/prompts/system-prompt.ts +21 -7
  267. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  268. package/src/providers/__tests__/retry-callsite.test.ts +222 -1
  269. package/src/providers/model-intents.ts +7 -0
  270. package/src/providers/openrouter/client.ts +8 -0
  271. package/src/providers/retry.ts +50 -0
  272. package/src/providers/types.ts +1 -0
  273. package/src/runtime/__tests__/agent-wake.test.ts +456 -3
  274. package/src/runtime/agent-wake.ts +238 -100
  275. package/src/runtime/assistant-event-hub.ts +36 -6
  276. package/src/runtime/assistant-event.ts +0 -1
  277. package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
  278. package/src/runtime/auth/route-policy.ts +14 -1
  279. package/src/runtime/auth/same-actor.ts +216 -0
  280. package/src/runtime/channel-retry-sweep.ts +65 -1
  281. package/src/runtime/guardian-reply-router.ts +10 -0
  282. package/src/runtime/local-actor-identity.ts +52 -11
  283. package/src/runtime/pending-interactions.ts +8 -0
  284. package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
  285. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
  286. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  287. package/src/runtime/routes/client-routes.ts +20 -2
  288. package/src/runtime/routes/contact-routes.ts +0 -25
  289. package/src/runtime/routes/conversation-routes.ts +35 -26
  290. package/src/runtime/routes/debug-bash-routes.ts +163 -0
  291. package/src/runtime/routes/disk-pressure-routes.ts +121 -0
  292. package/src/runtime/routes/document-pdf-renderer.ts +6 -2
  293. package/src/runtime/routes/documents-routes.ts +2 -75
  294. package/src/runtime/routes/events-routes.ts +41 -9
  295. package/src/runtime/routes/host-bash-routes.ts +23 -3
  296. package/src/runtime/routes/host-cu-routes.ts +33 -6
  297. package/src/runtime/routes/host-file-routes.ts +32 -6
  298. package/src/runtime/routes/host-transfer-routes.ts +79 -16
  299. package/src/runtime/routes/identity-routes.ts +7 -138
  300. package/src/runtime/routes/inbound-message-handler.ts +77 -12
  301. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -0
  302. package/src/runtime/routes/index.ts +6 -0
  303. package/src/runtime/routes/memory-item-routes.test.ts +41 -15
  304. package/src/runtime/routes/memory-v2-routes.ts +33 -0
  305. package/src/runtime/routes/oauth-connect-routes.ts +153 -0
  306. package/src/runtime/verification-outbound-actions.ts +4 -4
  307. package/src/schedule/run-script.ts +37 -5
  308. package/src/schedule/scheduler.ts +20 -1
  309. package/src/security/encrypted-store.ts +2 -0
  310. package/src/security/secure-keys.ts +55 -0
  311. package/src/skills/remote-skill-policy.ts +4 -10
  312. package/src/subagent/index.ts +1 -7
  313. package/src/subagent/manager.ts +1 -15
  314. package/src/tasks/task-runner.ts +0 -1
  315. package/src/tasks/task-store.ts +0 -3
  316. package/src/tools/background-tool-registry.ts +17 -3
  317. package/src/tools/host-filesystem/edit.test.ts +151 -0
  318. package/src/tools/host-filesystem/edit.ts +43 -1
  319. package/src/tools/host-filesystem/read.test.ts +129 -0
  320. package/src/tools/host-filesystem/read.ts +43 -1
  321. package/src/tools/host-filesystem/transfer.test.ts +127 -2
  322. package/src/tools/host-filesystem/transfer.ts +56 -11
  323. package/src/tools/host-filesystem/write.test.ts +134 -0
  324. package/src/tools/host-filesystem/write.ts +43 -1
  325. package/src/tools/host-terminal/host-shell.ts +13 -6
  326. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  327. package/src/tools/memory/register.test.ts +12 -9
  328. package/src/tools/memory/register.ts +1 -2
  329. package/src/tools/provider-tool-name.ts +28 -0
  330. package/src/tools/registry.ts +30 -9
  331. package/src/tools/terminal/shell.ts +9 -1
  332. package/src/tools/tool-approval-handler.ts +31 -6
  333. package/src/tools/types.ts +24 -2
  334. package/src/tts/provider-catalog.ts +3 -5
  335. package/src/util/disk-usage.ts +138 -0
  336. package/src/util/platform.ts +21 -11
  337. package/src/util/process-liveness.ts +26 -0
  338. package/src/workspace/heartbeat-service.ts +19 -0
  339. package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
  340. package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
  341. package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +72 -0
  342. package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
  343. package/src/workspace/migrations/registry.ts +8 -0
  344. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
  345. package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
  346. package/src/memory/v2/skill-qdrant.ts +0 -404
  347. package/src/signals/bash.ts +0 -198
@@ -1,657 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
-
3
- import { makeMockLogger } from "../../../__tests__/helpers/mock-logger.js";
4
-
5
- mock.module("../../../util/logger.js", () => ({
6
- getLogger: () => makeMockLogger(),
7
- }));
8
-
9
- // Stub getConfig — only the qdrant.url / vectorSize / onDisk fields matter.
10
- mock.module("../../../config/loader.js", () => ({
11
- getConfig: () => ({
12
- memory: {
13
- qdrant: {
14
- url: "http://127.0.0.1:6333",
15
- vectorSize: 384,
16
- onDisk: true,
17
- },
18
- },
19
- }),
20
- }));
21
-
22
- mock.module("../../qdrant-client.js", () => ({
23
- resolveQdrantUrl: () => "http://127.0.0.1:6333",
24
- }));
25
-
26
- // Mock the underlying @qdrant/js-client-rest package. The mock client
27
- // records every call and lets each test program the next response.
28
- type MockPoint = {
29
- id: string;
30
- vector: { dense: number[]; sparse: { indices: number[]; values: number[] } };
31
- payload: {
32
- id: string;
33
- content: string;
34
- updated_at: number;
35
- };
36
- };
37
-
38
- type ScrollPoint = {
39
- id: string | number;
40
- payload: Record<string, unknown>;
41
- };
42
-
43
- const state = {
44
- collectionExistsBeforeCreate: false,
45
- collectionExistsCalls: 0,
46
- createCollectionCalls: 0,
47
- createCollectionParams: null as unknown,
48
- createIndexCalls: [] as Array<{ field_name: string; field_schema: string }>,
49
- upsertCalls: [] as Array<{ wait: boolean; points: MockPoint[] }>,
50
- deleteCalls: [] as Array<{
51
- wait: boolean;
52
- points: Array<string | number>;
53
- }>,
54
- queryCalls: [] as Array<{
55
- using: string;
56
- query: unknown;
57
- limit: number;
58
- with_payload: boolean;
59
- filter?: unknown;
60
- }>,
61
- scrollCalls: [] as Array<{
62
- limit?: number;
63
- offset?: string | number;
64
- with_payload: boolean;
65
- with_vector: boolean;
66
- }>,
67
- // Per-using → response queue. Each entry is consumed in order.
68
- queryResponses: {
69
- dense: [] as Array<{
70
- points: Array<{ score?: number; payload: Record<string, unknown> }>;
71
- }>,
72
- sparse: [] as Array<{
73
- points: Array<{ score?: number; payload: Record<string, unknown> }>;
74
- }>,
75
- },
76
- // Pages of scroll results returned in order; each call shifts one. Each
77
- // page may set `next_page_offset` to keep paginating.
78
- scrollPages: [] as Array<{
79
- points: ScrollPoint[];
80
- next_page_offset?: string | number | null;
81
- }>,
82
- createCollectionThrows: null as Error | null,
83
- };
84
-
85
- class MockQdrantClient {
86
- constructor(_opts: unknown) {}
87
- async collectionExists(_name: string) {
88
- state.collectionExistsCalls++;
89
- return { exists: state.collectionExistsBeforeCreate };
90
- }
91
- async createCollection(_name: string, params: unknown) {
92
- state.createCollectionCalls++;
93
- state.createCollectionParams = params;
94
- if (state.createCollectionThrows) throw state.createCollectionThrows;
95
- state.collectionExistsBeforeCreate = true;
96
- return {};
97
- }
98
- async createPayloadIndex(
99
- _name: string,
100
- params: { field_name: string; field_schema: string },
101
- ) {
102
- state.createIndexCalls.push(params);
103
- return {};
104
- }
105
- async upsert(_name: string, params: { wait: boolean; points: MockPoint[] }) {
106
- state.upsertCalls.push(params);
107
- return {};
108
- }
109
- async delete(
110
- _name: string,
111
- params: { wait: boolean; points: Array<string | number> },
112
- ) {
113
- state.deleteCalls.push(params);
114
- return {};
115
- }
116
- async query(
117
- _name: string,
118
- params: {
119
- using: string;
120
- query: unknown;
121
- limit: number;
122
- with_payload: boolean;
123
- filter?: unknown;
124
- },
125
- ) {
126
- state.queryCalls.push(params);
127
- const queue = state.queryResponses[params.using as "dense" | "sparse"];
128
- return queue.shift() ?? { points: [] };
129
- }
130
- async scroll(
131
- _name: string,
132
- params: {
133
- limit?: number;
134
- offset?: string | number;
135
- with_payload: boolean;
136
- with_vector: boolean;
137
- },
138
- ) {
139
- state.scrollCalls.push(params);
140
- const page = state.scrollPages.shift();
141
- return page ?? { points: [], next_page_offset: null };
142
- }
143
- }
144
-
145
- mock.module("@qdrant/js-client-rest", () => ({
146
- QdrantClient: MockQdrantClient,
147
- }));
148
-
149
- const {
150
- ensureSkillCollection,
151
- upsertSkillEmbedding,
152
- pruneSkillsExcept,
153
- hybridQuerySkills,
154
- MEMORY_V2_SKILLS_COLLECTION,
155
- SKILL_NAMESPACE,
156
- _resetMemoryV2SkillQdrantForTests,
157
- } = await import("../skill-qdrant.js");
158
- const { MEMORY_V2_COLLECTION } = await import("../qdrant.js");
159
-
160
- function resetState(): void {
161
- state.collectionExistsBeforeCreate = false;
162
- state.collectionExistsCalls = 0;
163
- state.createCollectionCalls = 0;
164
- state.createCollectionParams = null;
165
- state.createIndexCalls.length = 0;
166
- state.upsertCalls.length = 0;
167
- state.deleteCalls.length = 0;
168
- state.queryCalls.length = 0;
169
- state.scrollCalls.length = 0;
170
- state.queryResponses.dense.length = 0;
171
- state.queryResponses.sparse.length = 0;
172
- state.scrollPages.length = 0;
173
- state.createCollectionThrows = null;
174
- _resetMemoryV2SkillQdrantForTests();
175
- }
176
-
177
- describe("memory v2 skill qdrant — collection identity", () => {
178
- test("uses the documented collection name", () => {
179
- expect(MEMORY_V2_SKILLS_COLLECTION).toBe("memory_v2_skills");
180
- });
181
-
182
- test("collection name is distinct from the concept-page collection", () => {
183
- expect(MEMORY_V2_SKILLS_COLLECTION).not.toBe(MEMORY_V2_COLLECTION);
184
- });
185
-
186
- test("namespace is a valid UUID distinct from the concept-page namespace", () => {
187
- expect(SKILL_NAMESPACE).toMatch(
188
- /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
189
- );
190
- // SLUG_NAMESPACE in qdrant.ts is "8b9c5d4f-0e1a-4f3b-9c2d-7e8f1a2b3c4d";
191
- // we deliberately picked a different one so an id-vs-slug collision
192
- // across collections doesn't produce the same point ID.
193
- expect(SKILL_NAMESPACE).not.toBe("8b9c5d4f-0e1a-4f3b-9c2d-7e8f1a2b3c4d");
194
- });
195
- });
196
-
197
- describe("memory v2 skill qdrant — collection lifecycle", () => {
198
- beforeEach(resetState);
199
- afterEach(resetState);
200
-
201
- test("creates the collection with named dense + sparse vectors", async () => {
202
- state.collectionExistsBeforeCreate = false;
203
-
204
- await ensureSkillCollection();
205
-
206
- expect(state.createCollectionCalls).toBe(1);
207
- const params = state.createCollectionParams as {
208
- vectors: {
209
- dense: { size: number; distance: string; on_disk: boolean };
210
- };
211
- sparse_vectors: { sparse: Record<string, unknown> };
212
- hnsw_config: { on_disk: boolean; m: number; ef_construct: number };
213
- on_disk_payload: boolean;
214
- };
215
- expect(params.vectors.dense).toEqual({
216
- size: 384,
217
- distance: "Cosine",
218
- on_disk: true,
219
- });
220
- expect(params.sparse_vectors.sparse).toEqual({});
221
- expect(params.hnsw_config).toEqual({
222
- on_disk: true,
223
- m: 16,
224
- ef_construct: 100,
225
- });
226
- expect(params.on_disk_payload).toBe(true);
227
-
228
- // The id payload index is created up front, mirroring how the
229
- // concept-page collection eagerly indexes `slug`.
230
- expect(state.createIndexCalls).toEqual([
231
- { field_name: "id", field_schema: "keyword" },
232
- ]);
233
- });
234
-
235
- test("re-running ensure on an existing collection is a no-op", async () => {
236
- state.collectionExistsBeforeCreate = true;
237
-
238
- await ensureSkillCollection();
239
- await ensureSkillCollection();
240
-
241
- // Existence check fired exactly once thanks to the in-memory readiness
242
- // cache; createCollection / createPayloadIndex never ran.
243
- expect(state.createCollectionCalls).toBe(0);
244
- expect(state.createIndexCalls).toEqual([]);
245
- expect(state.collectionExistsCalls).toBe(1);
246
- });
247
-
248
- test("treats 409-on-create as success (concurrent creation race)", async () => {
249
- state.collectionExistsBeforeCreate = false;
250
- const conflict = Object.assign(new Error("Conflict"), { status: 409 });
251
- state.createCollectionThrows = conflict;
252
-
253
- await ensureSkillCollection();
254
-
255
- // Falls through without throwing — collectionReady gets latched.
256
- expect(state.createCollectionCalls).toBe(1);
257
- // Index creation is skipped on the 409 path because the racing peer is
258
- // expected to have created it (it ran the same code).
259
- expect(state.createIndexCalls).toEqual([]);
260
- });
261
- });
262
-
263
- describe("memory v2 skill qdrant — upsert", () => {
264
- beforeEach(resetState);
265
- afterEach(resetState);
266
-
267
- test("upserts a single point keyed by a deterministic id-derived point id", async () => {
268
- state.collectionExistsBeforeCreate = true;
269
-
270
- await upsertSkillEmbedding({
271
- id: "example-skill-1",
272
- content: "The Example Skill 1 (example-skill-1) is available. ...",
273
- dense: [0.1, 0.2, 0.3],
274
- sparse: { indices: [1, 2], values: [0.5, 0.5] },
275
- updatedAt: 1714000000000,
276
- });
277
-
278
- expect(state.upsertCalls).toHaveLength(1);
279
- const call = state.upsertCalls[0];
280
- expect(call.wait).toBe(true);
281
- expect(call.points).toHaveLength(1);
282
- const [point] = call.points;
283
- expect(point.payload).toEqual({
284
- id: "example-skill-1",
285
- content: "The Example Skill 1 (example-skill-1) is available. ...",
286
- updated_at: 1714000000000,
287
- });
288
- expect(point.vector.dense).toEqual([0.1, 0.2, 0.3]);
289
- expect(point.vector.sparse).toEqual({
290
- indices: [1, 2],
291
- values: [0.5, 0.5],
292
- });
293
- // Point ID is a UUID-shaped string derived from the skill id.
294
- expect(point.id).toMatch(
295
- /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
296
- );
297
- });
298
-
299
- test("two upserts for the same id share the same point id (overwrites in place)", async () => {
300
- state.collectionExistsBeforeCreate = true;
301
-
302
- await upsertSkillEmbedding({
303
- id: "example-skill-1",
304
- content: "first",
305
- dense: [0.1],
306
- sparse: { indices: [1], values: [1] },
307
- updatedAt: 1,
308
- });
309
- await upsertSkillEmbedding({
310
- id: "example-skill-1",
311
- content: "second",
312
- dense: [0.9],
313
- sparse: { indices: [9], values: [0.5] },
314
- updatedAt: 2,
315
- });
316
-
317
- expect(state.upsertCalls).toHaveLength(2);
318
- expect(state.upsertCalls[0].points[0].id).toBe(
319
- state.upsertCalls[1].points[0].id,
320
- );
321
- });
322
-
323
- test("different ids map to different point ids", async () => {
324
- state.collectionExistsBeforeCreate = true;
325
-
326
- await upsertSkillEmbedding({
327
- id: "example-skill-1",
328
- content: "a",
329
- dense: [0.1],
330
- sparse: { indices: [1], values: [1] },
331
- updatedAt: 1,
332
- });
333
- await upsertSkillEmbedding({
334
- id: "example-skill-2",
335
- content: "b",
336
- dense: [0.1],
337
- sparse: { indices: [1], values: [1] },
338
- updatedAt: 1,
339
- });
340
-
341
- expect(state.upsertCalls[0].points[0].id).not.toBe(
342
- state.upsertCalls[1].points[0].id,
343
- );
344
- });
345
- });
346
-
347
- describe("memory v2 skill qdrant — prune", () => {
348
- beforeEach(resetState);
349
- afterEach(resetState);
350
-
351
- test("removes points whose payload.id is not in the active set", async () => {
352
- state.collectionExistsBeforeCreate = true;
353
- state.scrollPages.push({
354
- points: [
355
- {
356
- id: "11111111-1111-1111-1111-111111111111",
357
- payload: { id: "example-skill-1" },
358
- },
359
- {
360
- id: "22222222-2222-2222-2222-222222222222",
361
- payload: { id: "example-skill-2" },
362
- },
363
- ],
364
- next_page_offset: null,
365
- });
366
-
367
- await pruneSkillsExcept(["example-skill-1"]);
368
-
369
- // One scroll, one batch delete carrying only the stale point id.
370
- expect(state.scrollCalls).toHaveLength(1);
371
- expect(state.deleteCalls).toHaveLength(1);
372
- expect(state.deleteCalls[0].wait).toBe(true);
373
- expect(state.deleteCalls[0].points).toEqual([
374
- "22222222-2222-2222-2222-222222222222",
375
- ]);
376
- });
377
-
378
- test("no-op delete when every live point is in the active set", async () => {
379
- state.collectionExistsBeforeCreate = true;
380
- state.scrollPages.push({
381
- points: [
382
- {
383
- id: "11111111-1111-1111-1111-111111111111",
384
- payload: { id: "example-skill-1" },
385
- },
386
- ],
387
- next_page_offset: null,
388
- });
389
-
390
- await pruneSkillsExcept(["example-skill-1"]);
391
-
392
- expect(state.scrollCalls).toHaveLength(1);
393
- expect(state.deleteCalls).toHaveLength(0);
394
- });
395
-
396
- test("paginates via scroll's next_page_offset until exhausted", async () => {
397
- state.collectionExistsBeforeCreate = true;
398
- state.scrollPages.push({
399
- points: [
400
- {
401
- id: "11111111-1111-1111-1111-111111111111",
402
- payload: { id: "example-skill-1" },
403
- },
404
- ],
405
- next_page_offset: "next-token",
406
- });
407
- state.scrollPages.push({
408
- points: [
409
- {
410
- id: "22222222-2222-2222-2222-222222222222",
411
- payload: { id: "example-skill-2" },
412
- },
413
- ],
414
- next_page_offset: null,
415
- });
416
-
417
- await pruneSkillsExcept(["example-skill-1"]);
418
-
419
- // Two scrolls, the second carrying the offset returned by the first.
420
- expect(state.scrollCalls).toHaveLength(2);
421
- expect(state.scrollCalls[0].offset).toBeUndefined();
422
- expect(state.scrollCalls[1].offset).toBe("next-token");
423
- // Only the stale point gets deleted.
424
- expect(state.deleteCalls).toHaveLength(1);
425
- expect(state.deleteCalls[0].points).toEqual([
426
- "22222222-2222-2222-2222-222222222222",
427
- ]);
428
- });
429
-
430
- test("ignores points missing a string payload.id", async () => {
431
- state.collectionExistsBeforeCreate = true;
432
- state.scrollPages.push({
433
- points: [
434
- {
435
- id: "11111111-1111-1111-1111-111111111111",
436
- payload: {}, // no id field at all
437
- },
438
- {
439
- id: "22222222-2222-2222-2222-222222222222",
440
- payload: { id: 42 }, // wrong type
441
- },
442
- {
443
- id: "33333333-3333-3333-3333-333333333333",
444
- payload: { id: "example-skill-2" },
445
- },
446
- ],
447
- next_page_offset: null,
448
- });
449
-
450
- await pruneSkillsExcept(["example-skill-1"]);
451
-
452
- // Only the well-typed stale id is deleted; malformed payloads are
453
- // ignored rather than treated as "stale".
454
- expect(state.deleteCalls).toHaveLength(1);
455
- expect(state.deleteCalls[0].points).toEqual([
456
- "33333333-3333-3333-3333-333333333333",
457
- ]);
458
- });
459
-
460
- test("idempotent across repeated calls with the same active set", async () => {
461
- state.collectionExistsBeforeCreate = true;
462
- state.scrollPages.push({
463
- points: [
464
- {
465
- id: "11111111-1111-1111-1111-111111111111",
466
- payload: { id: "example-skill-1" },
467
- },
468
- ],
469
- next_page_offset: null,
470
- });
471
- state.scrollPages.push({
472
- points: [
473
- {
474
- id: "11111111-1111-1111-1111-111111111111",
475
- payload: { id: "example-skill-1" },
476
- },
477
- ],
478
- next_page_offset: null,
479
- });
480
-
481
- await pruneSkillsExcept(["example-skill-1"]);
482
- await pruneSkillsExcept(["example-skill-1"]);
483
-
484
- // Both runs scrolled; neither deleted because the live set matches.
485
- expect(state.scrollCalls).toHaveLength(2);
486
- expect(state.deleteCalls).toHaveLength(0);
487
- });
488
- });
489
-
490
- describe("memory v2 skill qdrant — hybrid query", () => {
491
- beforeEach(resetState);
492
- afterEach(resetState);
493
-
494
- test("runs both dense and sparse queries and returns per-channel scores", async () => {
495
- state.collectionExistsBeforeCreate = true;
496
- state.queryResponses.dense.push({
497
- points: [
498
- { score: 0.91, payload: { id: "example-skill-1" } },
499
- { score: 0.42, payload: { id: "example-skill-2" } },
500
- ],
501
- });
502
- state.queryResponses.sparse.push({
503
- points: [
504
- { score: 12, payload: { id: "example-skill-1" } },
505
- { score: 3, payload: { id: "example-skill-2" } },
506
- ],
507
- });
508
-
509
- const results = await hybridQuerySkills(
510
- [0.1, 0.2, 0.3],
511
- { indices: [1, 2], values: [0.5, 0.5] },
512
- 5,
513
- );
514
-
515
- // Both queries fired, with the same limit and the right `using`.
516
- expect(state.queryCalls).toHaveLength(2);
517
- const usings = state.queryCalls.map((c) => c.using).sort();
518
- expect(usings).toEqual(["dense", "sparse"]);
519
- expect(state.queryCalls.every((c) => c.limit === 5)).toBe(true);
520
- expect(state.queryCalls.every((c) => c.with_payload === true)).toBe(true);
521
-
522
- // Each id exposes both channel scores.
523
- expect(results).toHaveLength(2);
524
- const skill1 = results.find((r) => r.id === "example-skill-1");
525
- const skill2 = results.find((r) => r.id === "example-skill-2");
526
- expect(skill1).toEqual({
527
- id: "example-skill-1",
528
- denseScore: 0.91,
529
- sparseScore: 12,
530
- });
531
- expect(skill2).toEqual({
532
- id: "example-skill-2",
533
- denseScore: 0.42,
534
- sparseScore: 3,
535
- });
536
- });
537
-
538
- test("dense-only hits leave sparseScore undefined (and vice versa)", async () => {
539
- state.collectionExistsBeforeCreate = true;
540
- state.queryResponses.dense.push({
541
- points: [{ score: 0.7, payload: { id: "example-skill-1" } }],
542
- });
543
- state.queryResponses.sparse.push({
544
- points: [{ score: 2, payload: { id: "example-skill-2" } }],
545
- });
546
-
547
- const results = await hybridQuerySkills(
548
- [0.1],
549
- { indices: [1], values: [1] },
550
- 5,
551
- );
552
-
553
- const denseOnly = results.find((r) => r.id === "example-skill-1");
554
- const sparseOnly = results.find((r) => r.id === "example-skill-2");
555
- expect(denseOnly).toEqual({ id: "example-skill-1", denseScore: 0.7 });
556
- expect(denseOnly?.sparseScore).toBeUndefined();
557
- expect(sparseOnly).toEqual({ id: "example-skill-2", sparseScore: 2 });
558
- expect(sparseOnly?.denseScore).toBeUndefined();
559
- });
560
-
561
- test("does not use Qdrant-side RRF fusion (separate per-channel queries)", async () => {
562
- state.collectionExistsBeforeCreate = true;
563
- state.queryResponses.dense.push({ points: [] });
564
- state.queryResponses.sparse.push({ points: [] });
565
-
566
- await hybridQuerySkills([0.1], { indices: [1], values: [1] }, 5);
567
-
568
- // Each query is a single-channel call (no `prefetch` + `fusion` shape).
569
- for (const call of state.queryCalls) {
570
- expect(call).not.toHaveProperty("prefetch");
571
- const wholeCall = call as unknown as Record<string, unknown>;
572
- expect(wholeCall.fusion).toBeUndefined();
573
- }
574
- });
575
-
576
- test("undefined restrictToIds queries the full catalog (no filter)", async () => {
577
- state.collectionExistsBeforeCreate = true;
578
- state.queryResponses.dense.push({
579
- points: [{ score: 0.5, payload: { id: "example-skill-1" } }],
580
- });
581
- state.queryResponses.sparse.push({
582
- points: [{ score: 1, payload: { id: "example-skill-2" } }],
583
- });
584
-
585
- const results = await hybridQuerySkills(
586
- [0.1],
587
- { indices: [1], values: [1] },
588
- 5,
589
- );
590
-
591
- // Both channels ran without any payload filter.
592
- expect(state.queryCalls).toHaveLength(2);
593
- for (const call of state.queryCalls) {
594
- expect(call.filter).toBeUndefined();
595
- }
596
- // Full results flow through unchanged.
597
- expect(results.map((r) => r.id).sort()).toEqual([
598
- "example-skill-1",
599
- "example-skill-2",
600
- ]);
601
- });
602
-
603
- test("restrictToIds forwards a Qdrant id-IN filter to BOTH channels", async () => {
604
- state.collectionExistsBeforeCreate = true;
605
- state.queryResponses.dense.push({ points: [] });
606
- state.queryResponses.sparse.push({ points: [] });
607
-
608
- await hybridQuerySkills([0.1], { indices: [1], values: [1] }, 5, [
609
- "example-skill-a",
610
- "example-skill-b",
611
- ]);
612
-
613
- expect(state.queryCalls).toHaveLength(2);
614
- const usings = state.queryCalls.map((c) => c.using).sort();
615
- expect(usings).toEqual(["dense", "sparse"]);
616
- for (const call of state.queryCalls) {
617
- // Filter is forwarded to both dense and sparse — without this the
618
- // sparse channel would still grab the global top-K and corrupt
619
- // candidate scoring.
620
- expect(call.filter).toEqual({
621
- must: [
622
- { key: "id", match: { any: ["example-skill-a", "example-skill-b"] } },
623
- ],
624
- });
625
- }
626
- });
627
-
628
- test("empty restrictToIds short-circuits without hitting Qdrant", async () => {
629
- state.collectionExistsBeforeCreate = true;
630
-
631
- const results = await hybridQuerySkills(
632
- [0.1],
633
- { indices: [1], values: [1] },
634
- 5,
635
- [],
636
- );
637
-
638
- expect(results).toEqual([]);
639
- // No Qdrant calls were made — the function returned before
640
- // ensureSkillCollection ran.
641
- expect(state.queryCalls).toHaveLength(0);
642
- expect(state.collectionExistsCalls).toBe(0);
643
- });
644
-
645
- test("empty Qdrant responses yield []", async () => {
646
- state.collectionExistsBeforeCreate = true;
647
- state.queryResponses.dense.push({ points: [] });
648
- state.queryResponses.sparse.push({ points: [] });
649
-
650
- const results = await hybridQuerySkills(
651
- [0.1],
652
- { indices: [1], values: [1] },
653
- 5,
654
- );
655
- expect(results).toEqual([]);
656
- });
657
- });