@vellumai/assistant 0.7.2 → 0.8.0

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 (424) hide show
  1. package/ARCHITECTURE.md +45 -29
  2. package/Dockerfile +1 -0
  3. package/__tests__/permissions/gateway-threshold-reader.test.ts +236 -9
  4. package/bun.lock +3 -0
  5. package/docs/architecture/memory.md +5 -2
  6. package/knip.json +1 -0
  7. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
  8. package/node_modules/@vellumai/ipc-server-utils/bun.lock +24 -0
  9. package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
  10. package/node_modules/@vellumai/ipc-server-utils/src/index.ts +6 -0
  11. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +430 -0
  12. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +221 -0
  13. package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
  14. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
  15. package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
  16. package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
  17. package/openapi.yaml +470 -25
  18. package/package.json +3 -1
  19. package/src/__tests__/annotate-risk-options.test.ts +291 -0
  20. package/src/__tests__/app-control-flow.test.ts +21 -11
  21. package/src/__tests__/approval-cascade.test.ts +8 -16
  22. package/src/__tests__/approval-routes-http.test.ts +6 -0
  23. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  24. package/src/__tests__/assistant-event.test.ts +0 -10
  25. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
  26. package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
  27. package/src/__tests__/auto-analysis-end-to-end.test.ts +48 -0
  28. package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
  29. package/src/__tests__/call-constants.test.ts +10 -1
  30. package/src/__tests__/call-controller.test.ts +127 -0
  31. package/src/__tests__/call-conversation-messages.test.ts +8 -2
  32. package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
  33. package/src/__tests__/channel-readiness-service.test.ts +4 -2
  34. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +58 -28
  35. package/src/__tests__/config-loader-backfill.test.ts +379 -0
  36. package/src/__tests__/config-loader-platform-defaults.test.ts +284 -1
  37. package/src/__tests__/config-schema.test.ts +1 -0
  38. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
  39. package/src/__tests__/config-watcher.test.ts +140 -69
  40. package/src/__tests__/context-search-agent-runner.test.ts +61 -3
  41. package/src/__tests__/context-search-conversations-source.test.ts +0 -24
  42. package/src/__tests__/context-search-fanout.test.ts +0 -1
  43. package/src/__tests__/context-search-memory-source.test.ts +6 -33
  44. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
  45. package/src/__tests__/context-search-pkb-source.test.ts +12 -7
  46. package/src/__tests__/context-search-workspace-source.test.ts +0 -1
  47. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -0
  48. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
  49. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
  50. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  51. package/src/__tests__/conversation-agent-loop.test.ts +457 -8
  52. package/src/__tests__/conversation-confirmation-signals.test.ts +5 -13
  53. package/src/__tests__/conversation-error.test.ts +150 -3
  54. package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
  55. package/src/__tests__/conversation-process-callsite.test.ts +38 -0
  56. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +74 -0
  58. package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
  59. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  60. package/src/__tests__/conversation-store.test.ts +0 -18
  61. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +170 -9
  62. package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
  63. package/src/__tests__/conversation-surfaces-data-persist.test.ts +476 -0
  64. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +61 -5
  65. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  66. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  67. package/src/__tests__/credentials-cli.test.ts +7 -0
  68. package/src/__tests__/cu-unified-flow.test.ts +176 -10
  69. package/src/__tests__/date-context.test.ts +164 -2
  70. package/src/__tests__/disk-pressure-guard.test.ts +262 -0
  71. package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
  72. package/src/__tests__/disk-pressure-policy.test.ts +241 -0
  73. package/src/__tests__/disk-pressure-routes.test.ts +379 -0
  74. package/src/__tests__/disk-pressure-tools.test.ts +277 -0
  75. package/src/__tests__/disk-usage.test.ts +150 -0
  76. package/src/__tests__/events-client-registration.test.ts +52 -0
  77. package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
  78. package/src/__tests__/file-write-tool.test.ts +4 -10
  79. package/src/__tests__/filing-service.test.ts +2 -20
  80. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +10 -26
  81. package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
  82. package/src/__tests__/heartbeat-service.test.ts +260 -11
  83. package/src/__tests__/host-app-control-proxy.test.ts +195 -25
  84. package/src/__tests__/host-bash-proxy.test.ts +227 -34
  85. package/src/__tests__/host-bash-routes.test.ts +178 -13
  86. package/src/__tests__/host-cu-proxy.test.ts +210 -3
  87. package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
  88. package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
  89. package/src/__tests__/host-file-proxy.test.ts +268 -6
  90. package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
  91. package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
  92. package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
  93. package/src/__tests__/http-user-message-parity.test.ts +107 -1
  94. package/src/__tests__/injector-chain.test.ts +36 -16
  95. package/src/__tests__/injector-disk-pressure.test.ts +224 -0
  96. package/src/__tests__/injector-pkb-v2-silenced.test.ts +10 -7
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +154 -67
  98. package/src/__tests__/managed-profile-guard.test.ts +18 -0
  99. package/src/__tests__/mcp-abort-signal.test.ts +130 -0
  100. package/src/__tests__/memory-admin-recall.test.ts +3 -11
  101. package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
  102. package/src/__tests__/normalize-onboarding.test.ts +180 -0
  103. package/src/__tests__/notification-decision-fallback.test.ts +91 -0
  104. package/src/__tests__/notification-decision-strategy.test.ts +22 -0
  105. package/src/__tests__/oauth-cli.test.ts +121 -0
  106. package/src/__tests__/oauth-connect-routes.test.ts +316 -0
  107. package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
  108. package/src/__tests__/onboarding-persona-write.test.ts +308 -0
  109. package/src/__tests__/openai-provider.test.ts +45 -8
  110. package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
  111. package/src/__tests__/platform-callback-registration.test.ts +21 -4
  112. package/src/__tests__/platform.test.ts +2 -1
  113. package/src/__tests__/playbook-execution.test.ts +0 -43
  114. package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
  115. package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
  116. package/src/__tests__/provider-tool-name.test.ts +23 -0
  117. package/src/__tests__/relay-server.test.ts +60 -5
  118. package/src/__tests__/runtime-events-sse.test.ts +4 -8
  119. package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
  120. package/src/__tests__/secret-ingress-http.test.ts +0 -1
  121. package/src/__tests__/secret-prompt-log-hygiene.test.ts +7 -5
  122. package/src/__tests__/secret-prompter-channel-fallback.test.ts +7 -5
  123. package/src/__tests__/secret-response-routing.test.ts +7 -5
  124. package/src/__tests__/server-history-render.test.ts +82 -0
  125. package/src/__tests__/skill-include-graph.test.ts +31 -0
  126. package/src/__tests__/skill-load-tool.test.ts +44 -16
  127. package/src/__tests__/skills.test.ts +39 -0
  128. package/src/__tests__/suggestion-routes.test.ts +46 -0
  129. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -42
  130. package/src/__tests__/tool-executor.test.ts +155 -0
  131. package/src/__tests__/twilio-validation.test.ts +2 -2
  132. package/src/__tests__/voice-session-bridge.test.ts +3 -0
  133. package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
  134. package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
  135. package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
  136. package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +120 -0
  137. package/src/__tests__/workspace-migration-071-remove-safe-storage-release-note.test.ts +206 -0
  138. package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +78 -0
  139. package/src/agent/loop.ts +11 -0
  140. package/src/approvals/guardian-request-resolvers.ts +3 -32
  141. package/src/backup/snapshot-lock.ts +2 -27
  142. package/src/bundler/compiler-tools.ts +3 -2
  143. package/src/calls/call-constants.ts +5 -8
  144. package/src/calls/call-controller.ts +130 -67
  145. package/src/calls/call-conversation-messages.ts +46 -10
  146. package/src/calls/relay-server.ts +7 -1
  147. package/src/calls/voice-session-bridge.ts +1 -1
  148. package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
  149. package/src/cli/commands/bash.ts +35 -108
  150. package/src/cli/commands/contacts.ts +64 -25
  151. package/src/cli/commands/credentials.ts +56 -0
  152. package/src/cli/commands/memory-v2.ts +11 -10
  153. package/src/cli/commands/oauth/__tests__/connect.test.ts +401 -219
  154. package/src/cli/commands/oauth/connect.ts +124 -40
  155. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
  156. package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
  157. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  158. package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
  159. package/src/cli/commands/platform/index.ts +16 -7
  160. package/src/cli/commands/status.ts +57 -0
  161. package/src/cli/program.ts +4 -2
  162. package/src/config/assistant-feature-flags.ts +13 -3
  163. package/src/config/bundled-skills/app-builder/SKILL.md +1 -3
  164. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
  165. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
  166. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
  167. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
  168. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
  169. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
  170. package/src/config/env.ts +0 -8
  171. package/src/config/feature-flag-registry.json +13 -5
  172. package/src/config/loader.ts +199 -27
  173. package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
  174. package/src/config/schemas/call-site-catalog.ts +14 -0
  175. package/src/config/schemas/channels.ts +0 -5
  176. package/src/config/schemas/heartbeat.ts +1 -1
  177. package/src/config/schemas/llm.ts +2 -0
  178. package/src/config/schemas/memory-lifecycle.ts +13 -0
  179. package/src/config/schemas/memory-v2.ts +76 -12
  180. package/src/config/schemas/platform.ts +43 -3
  181. package/src/config/schemas/services.ts +28 -0
  182. package/src/config/seed-inference-profiles.ts +230 -33
  183. package/src/contacts/contact-store.ts +0 -25
  184. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +32 -0
  185. package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
  186. package/src/daemon/assistant-attachments.ts +4 -4
  187. package/src/daemon/config-watcher.ts +85 -57
  188. package/src/daemon/conversation-agent-loop-handlers.ts +38 -0
  189. package/src/daemon/conversation-agent-loop.ts +183 -43
  190. package/src/daemon/conversation-error.ts +87 -15
  191. package/src/daemon/conversation-lifecycle.ts +22 -10
  192. package/src/daemon/conversation-process.ts +8 -0
  193. package/src/daemon/conversation-runtime-assembly.ts +26 -0
  194. package/src/daemon/conversation-store.ts +2 -2
  195. package/src/daemon/conversation-surfaces.ts +211 -29
  196. package/src/daemon/conversation-tool-setup.ts +66 -19
  197. package/src/daemon/conversation.ts +18 -23
  198. package/src/daemon/date-context.ts +71 -22
  199. package/src/daemon/disk-pressure-background-gate.ts +73 -0
  200. package/src/daemon/disk-pressure-guard.ts +343 -0
  201. package/src/daemon/disk-pressure-policy.ts +163 -0
  202. package/src/daemon/handlers/shared.ts +26 -1
  203. package/src/daemon/handlers/skills.ts +3 -4
  204. package/src/daemon/host-app-control-proxy.ts +137 -41
  205. package/src/daemon/host-bash-proxy.ts +47 -22
  206. package/src/daemon/host-browser-proxy.ts +1 -1
  207. package/src/daemon/host-cu-proxy.ts +50 -4
  208. package/src/daemon/host-file-proxy.ts +44 -8
  209. package/src/daemon/host-transfer-proxy.ts +97 -6
  210. package/src/daemon/lifecycle.ts +167 -101
  211. package/src/daemon/meet-host-supervisor.ts +4 -4
  212. package/src/daemon/meet-manifest-loader.ts +0 -1
  213. package/src/daemon/memory-v2-startup.ts +66 -15
  214. package/src/daemon/message-protocol.ts +3 -0
  215. package/src/daemon/message-types/conversations.ts +4 -0
  216. package/src/daemon/message-types/disk-pressure.ts +9 -0
  217. package/src/daemon/message-types/messages.ts +22 -1
  218. package/src/daemon/profiler-run-store.ts +5 -5
  219. package/src/daemon/tool-setup-types.ts +2 -2
  220. package/src/documents/document-store.ts +119 -0
  221. package/src/filing/filing-service.ts +29 -5
  222. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
  223. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
  224. package/src/heartbeat/heartbeat-run-store.ts +13 -0
  225. package/src/heartbeat/heartbeat-service.ts +205 -31
  226. package/src/home/feed-scheduler.ts +18 -0
  227. package/src/inbound/platform-callback-registration.ts +8 -15
  228. package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
  229. package/src/ipc/assistant-server.ts +149 -38
  230. package/src/ipc/gateway-client.ts +37 -3
  231. package/src/ipc/skill-server.ts +99 -42
  232. package/src/live-voice/live-voice-archive.ts +4 -4
  233. package/src/live-voice/protocol.ts +5 -7
  234. package/src/media/image-service.ts +1 -7
  235. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
  236. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +34 -51
  237. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
  238. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
  239. package/src/memory/admin.ts +5 -9
  240. package/src/memory/context-search/agent-runner.ts +19 -2
  241. package/src/memory/context-search/sources/conversations.ts +2 -11
  242. package/src/memory/context-search/sources/memory-v2.ts +1 -16
  243. package/src/memory/context-search/sources/memory.ts +2 -3
  244. package/src/memory/context-search/sources/pkb.ts +2 -3
  245. package/src/memory/context-search/types.ts +0 -1
  246. package/src/memory/conversation-crud.ts +4 -12
  247. package/src/memory/db-init.ts +2 -0
  248. package/src/memory/embedding-runtime-manager.ts +119 -5
  249. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +136 -82
  250. package/src/memory/graph/__tests__/handle-remember-v2.test.ts +11 -26
  251. package/src/memory/graph/conversation-graph-memory.ts +72 -61
  252. package/src/memory/graph/extraction.ts +1 -3
  253. package/src/memory/graph/graph-search.test.ts +11 -67
  254. package/src/memory/graph/graph-search.ts +4 -24
  255. package/src/memory/graph/retriever.test.ts +12 -1
  256. package/src/memory/graph/retriever.ts +10 -15
  257. package/src/memory/graph/tool-handlers.ts +3 -4
  258. package/src/memory/graph/tools.ts +4 -4
  259. package/src/memory/indexer.ts +53 -45
  260. package/src/memory/job-handlers/backfill.ts +2 -11
  261. package/src/memory/job-handlers/cleanup.ts +43 -0
  262. package/src/memory/job-handlers/embedding.ts +6 -8
  263. package/src/memory/job-handlers/summarization.ts +2 -7
  264. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +116 -0
  265. package/src/memory/jobs/embed-concept-page.ts +223 -87
  266. package/src/memory/jobs-store.ts +48 -0
  267. package/src/memory/jobs-worker.ts +85 -43
  268. package/src/memory/memory-v2-activation-log-store.ts +32 -14
  269. package/src/memory/memory-v2-concept-frequency.ts +169 -0
  270. package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
  271. package/src/memory/migrations/index.ts +1 -0
  272. package/src/memory/pkb/pkb-search.test.ts +7 -0
  273. package/src/memory/pkb/pkb-search.ts +4 -5
  274. package/src/memory/qdrant-client.ts +3 -13
  275. package/src/memory/rerank-local.ts +374 -0
  276. package/src/memory/search/semantic.ts +10 -72
  277. package/src/memory/trace-event-store.ts +1 -17
  278. package/src/memory/v2/__tests__/activation.test.ts +346 -255
  279. package/src/memory/v2/__tests__/consolidation-job.test.ts +61 -40
  280. package/src/memory/v2/__tests__/injection.test.ts +297 -190
  281. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
  282. package/src/memory/v2/__tests__/qdrant.test.ts +326 -9
  283. package/src/memory/v2/__tests__/reranker.test.ts +338 -0
  284. package/src/memory/v2/__tests__/sim.test.ts +113 -196
  285. package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
  286. package/src/memory/v2/__tests__/static-context.test.ts +77 -14
  287. package/src/memory/v2/__tests__/sweep-job.test.ts +19 -33
  288. package/src/memory/v2/activation.ts +149 -156
  289. package/src/memory/v2/consolidation-job.ts +69 -20
  290. package/src/memory/v2/injection.ts +75 -68
  291. package/src/memory/v2/page-store.ts +39 -0
  292. package/src/memory/v2/prompts/consolidation.ts +41 -1
  293. package/src/memory/v2/qdrant.ts +306 -46
  294. package/src/memory/v2/reranker.ts +177 -0
  295. package/src/memory/v2/sim.ts +77 -110
  296. package/src/memory/v2/skill-content.ts +4 -3
  297. package/src/memory/v2/skill-store.ts +82 -59
  298. package/src/memory/v2/static-context.ts +26 -8
  299. package/src/memory/v2/sweep-job.ts +5 -6
  300. package/src/memory/v2/types.ts +17 -10
  301. package/src/notifications/copy-composer.ts +47 -0
  302. package/src/notifications/decision-engine.ts +46 -0
  303. package/src/notifications/signal.ts +4 -0
  304. package/src/oauth/AGENTS.md +3 -1
  305. package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
  306. package/src/oauth/connect-orchestrator.ts +2 -0
  307. package/src/oauth/connection-resolver.test.ts +66 -1
  308. package/src/oauth/connection-resolver.ts +55 -1
  309. package/src/oauth/oauth-connect-state.ts +77 -0
  310. package/src/oauth/seed-providers.ts +58 -1
  311. package/src/permissions/gateway-threshold-reader.ts +116 -8
  312. package/src/permissions/prompter.ts +86 -96
  313. package/src/permissions/secret-prompter.ts +31 -31
  314. package/src/plugins/defaults/injectors.ts +36 -4
  315. package/src/plugins/defaults/memory-retrieval.ts +5 -6
  316. package/src/plugins/types.ts +7 -0
  317. package/src/proactive-artifact/aux-message-injector.ts +74 -0
  318. package/src/proactive-artifact/decision.test.ts +226 -0
  319. package/src/proactive-artifact/decision.ts +165 -0
  320. package/src/proactive-artifact/index.ts +7 -0
  321. package/src/proactive-artifact/job.test.ts +914 -0
  322. package/src/proactive-artifact/job.ts +366 -0
  323. package/src/proactive-artifact/message-copy.ts +58 -0
  324. package/src/proactive-artifact/trigger-state.test.ts +277 -0
  325. package/src/proactive-artifact/trigger-state.ts +119 -0
  326. package/src/prompts/normalize-onboarding.ts +80 -0
  327. package/src/prompts/persona-resolver.ts +101 -9
  328. package/src/prompts/system-prompt.ts +21 -7
  329. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  330. package/src/prompts/templates/SOUL.md +13 -28
  331. package/src/providers/__tests__/retry-callsite.test.ts +222 -1
  332. package/src/providers/model-intents.ts +7 -0
  333. package/src/providers/openrouter/client.ts +8 -0
  334. package/src/providers/retry.ts +50 -0
  335. package/src/providers/types.ts +1 -0
  336. package/src/runtime/__tests__/agent-wake.test.ts +456 -3
  337. package/src/runtime/agent-wake.ts +238 -100
  338. package/src/runtime/assistant-event-hub.ts +36 -6
  339. package/src/runtime/assistant-event.ts +0 -1
  340. package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
  341. package/src/runtime/auth/route-policy.ts +15 -1
  342. package/src/runtime/auth/same-actor.ts +216 -0
  343. package/src/runtime/channel-approvals.ts +3 -2
  344. package/src/runtime/channel-retry-sweep.ts +65 -1
  345. package/src/runtime/local-actor-identity.ts +52 -11
  346. package/src/runtime/pending-interactions.ts +27 -15
  347. package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
  348. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
  349. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  350. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +147 -0
  351. package/src/runtime/routes/approval-routes.ts +7 -3
  352. package/src/runtime/routes/client-routes.ts +20 -2
  353. package/src/runtime/routes/consolidation-routes.ts +8 -9
  354. package/src/runtime/routes/contact-routes.ts +0 -25
  355. package/src/runtime/routes/conversation-query-routes.ts +44 -1
  356. package/src/runtime/routes/conversation-routes.ts +35 -26
  357. package/src/runtime/routes/debug-bash-routes.ts +165 -0
  358. package/src/runtime/routes/disk-pressure-routes.ts +121 -0
  359. package/src/runtime/routes/document-pdf-renderer.ts +6 -2
  360. package/src/runtime/routes/documents-routes.ts +2 -75
  361. package/src/runtime/routes/events-routes.ts +41 -9
  362. package/src/runtime/routes/filing-routes.ts +2 -3
  363. package/src/runtime/routes/host-bash-routes.ts +23 -3
  364. package/src/runtime/routes/host-cu-routes.ts +33 -6
  365. package/src/runtime/routes/host-file-routes.ts +32 -6
  366. package/src/runtime/routes/host-transfer-routes.ts +79 -16
  367. package/src/runtime/routes/identity-routes.ts +7 -138
  368. package/src/runtime/routes/inbound-message-handler.ts +77 -12
  369. package/src/runtime/routes/index.ts +6 -0
  370. package/src/runtime/routes/memory-item-routes.test.ts +37 -17
  371. package/src/runtime/routes/memory-item-routes.ts +5 -6
  372. package/src/runtime/routes/memory-v2-routes.ts +136 -17
  373. package/src/runtime/routes/oauth-connect-routes.ts +153 -0
  374. package/src/runtime/verification-outbound-actions.ts +4 -4
  375. package/src/schedule/run-script.ts +37 -5
  376. package/src/schedule/scheduler.ts +20 -1
  377. package/src/security/encrypted-store.ts +2 -0
  378. package/src/security/secure-keys.ts +55 -0
  379. package/src/skills/include-graph.ts +35 -13
  380. package/src/skills/remote-skill-policy.ts +4 -10
  381. package/src/subagent/index.ts +1 -7
  382. package/src/subagent/manager.ts +1 -15
  383. package/src/tasks/task-runner.ts +0 -1
  384. package/src/tasks/task-store.ts +0 -3
  385. package/src/tools/background-tool-registry.ts +17 -3
  386. package/src/tools/document/document-tool.ts +20 -0
  387. package/src/tools/executor.ts +18 -2
  388. package/src/tools/host-filesystem/edit.test.ts +151 -0
  389. package/src/tools/host-filesystem/edit.ts +43 -1
  390. package/src/tools/host-filesystem/read.test.ts +129 -0
  391. package/src/tools/host-filesystem/read.ts +43 -1
  392. package/src/tools/host-filesystem/transfer.test.ts +127 -2
  393. package/src/tools/host-filesystem/transfer.ts +56 -11
  394. package/src/tools/host-filesystem/write.test.ts +134 -0
  395. package/src/tools/host-filesystem/write.ts +43 -1
  396. package/src/tools/host-terminal/host-shell.ts +13 -6
  397. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  398. package/src/tools/memory/register.test.ts +14 -9
  399. package/src/tools/memory/register.ts +1 -2
  400. package/src/tools/permission-checker.ts +15 -0
  401. package/src/tools/provider-tool-name.ts +28 -0
  402. package/src/tools/registry.ts +30 -9
  403. package/src/tools/skills/load.ts +24 -20
  404. package/src/tools/terminal/shell.ts +9 -1
  405. package/src/tools/tool-approval-handler.ts +31 -6
  406. package/src/tools/tool-name-aliases.ts +19 -0
  407. package/src/tools/types.ts +43 -3
  408. package/src/tts/provider-catalog.ts +3 -5
  409. package/src/util/disk-usage.ts +138 -0
  410. package/src/util/platform.ts +21 -11
  411. package/src/util/process-liveness.ts +26 -0
  412. package/src/workspace/heartbeat-service.ts +19 -0
  413. package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
  414. package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
  415. package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +14 -0
  416. package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
  417. package/src/workspace/migrations/069-seed-onboarding-threads.ts +28 -0
  418. package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +31 -0
  419. package/src/workspace/migrations/071-remove-safe-storage-release-note.ts +111 -0
  420. package/src/workspace/migrations/registry.ts +14 -0
  421. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
  422. package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
  423. package/src/memory/v2/skill-qdrant.ts +0 -404
  424. package/src/signals/bash.ts +0 -198
@@ -1,19 +1,14 @@
1
1
  /**
2
- * Tests for `handleRemember` flag-routing between v1 (PKB) and v2 (memory/).
2
+ * Tests for `handleRemember` routing between v1 (PKB) and v2 (memory/).
3
3
  *
4
- * Verifies:
5
- * - Flag on → writes go to `memory/buffer.md` + `memory/archive/<today>.md`
6
- * and NO PKB re-index job is enqueued.
7
- * - Flag off → existing PKB path is unchanged (writes to `pkb/buffer.md`
8
- * + `pkb/archive/<today>.md`, both files are re-indexed).
9
- * - Archive filename uses today's local date.
4
+ * Routing follows `config.memory.v2.enabled`: when true, writes go to
5
+ * memory/; otherwise they fall back to v1 PKB.
10
6
  */
11
7
  import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
12
8
  import { tmpdir } from "node:os";
13
9
  import { join } from "node:path";
14
10
  import {
15
11
  afterAll,
16
- afterEach,
17
12
  beforeAll,
18
13
  beforeEach,
19
14
  describe,
@@ -62,11 +57,13 @@ afterAll(() => {
62
57
  // Imports are deferred to after the env var is set so any internal use of
63
58
  // `getWorkspaceDir()` resolves to the tmpdir.
64
59
  const { handleRemember } = await import("../tool-handlers.js");
65
- const { _setOverridesForTesting } =
66
- await import("../../../config/assistant-feature-flags.js");
67
60
  const { applyNestedDefaults } = await import("../../../config/loader.js");
68
61
 
69
62
  const CONFIG = applyNestedDefaults({});
63
+ const CONFIG_V2_OFF = {
64
+ ...CONFIG,
65
+ memory: { ...CONFIG.memory, v2: { ...CONFIG.memory.v2, enabled: false } },
66
+ };
70
67
 
71
68
  beforeEach(() => {
72
69
  enqueueCalls.length = 0;
@@ -76,10 +73,6 @@ beforeEach(() => {
76
73
  rmSync(join(tmpWorkspace, "memory"), { recursive: true, force: true });
77
74
  });
78
75
 
79
- afterEach(() => {
80
- _setOverridesForTesting({});
81
- });
82
-
83
76
  function todaysArchiveBasename(now: Date = new Date()): string {
84
77
  const yyyy = now.getFullYear();
85
78
  const mm = String(now.getMonth() + 1).padStart(2, "0");
@@ -87,11 +80,7 @@ function todaysArchiveBasename(now: Date = new Date()): string {
87
80
  return `${yyyy}-${mm}-${dd}.md`;
88
81
  }
89
82
 
90
- describe("handleRemember — memory-v2 flag on", () => {
91
- beforeEach(() => {
92
- _setOverridesForTesting({ "memory-v2-enabled": true });
93
- });
94
-
83
+ describe("handleRemember — memory.v2.enabled on", () => {
95
84
  test("writes to memory/buffer.md and memory/archive/<today>.md", () => {
96
85
  const result = handleRemember(
97
86
  { content: "Alice prefers VS Code over Vim" },
@@ -175,17 +164,13 @@ describe("handleRemember — memory-v2 flag on", () => {
175
164
  });
176
165
  });
177
166
 
178
- describe("handleRemember — memory-v2 flag off (v1 PKB path)", () => {
179
- beforeEach(() => {
180
- _setOverridesForTesting({ "memory-v2-enabled": false });
181
- });
182
-
167
+ describe("handleRemember — memory.v2.enabled off (v1 PKB path)", () => {
183
168
  test("writes to pkb/buffer.md and pkb/archive/<today>.md", () => {
184
169
  const result = handleRemember(
185
170
  { content: "v1 path still works" },
186
171
  "conv-v1-1",
187
172
  "default",
188
- CONFIG,
173
+ CONFIG_V2_OFF,
189
174
  );
190
175
 
191
176
  expect(result.success).toBe(true);
@@ -206,7 +191,7 @@ describe("handleRemember — memory-v2 flag off (v1 PKB path)", () => {
206
191
  { content: "index me" },
207
192
  "conv-v1-2",
208
193
  "default",
209
- CONFIG,
194
+ CONFIG_V2_OFF,
210
195
  );
211
196
 
212
197
  expect(result.success).toBe(true);
@@ -8,7 +8,6 @@
8
8
 
9
9
  import { and, desc, eq, inArray, ne, notInArray } from "drizzle-orm";
10
10
 
11
- import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
12
11
  import type { AssistantConfig } from "../../config/types.js";
13
12
  import { estimateTextTokens } from "../../context/token-estimator.js";
14
13
  import type { ServerMessage } from "../../daemon/message-protocol.js";
@@ -23,6 +22,11 @@ import { getDb } from "../db-connection.js";
23
22
  import type { QdrantSparseVector } from "../qdrant-client.js";
24
23
  import { memorySummaries } from "../schema.js";
25
24
  import { conversations } from "../schema/conversations.js";
25
+ import {
26
+ evictCompactedTurns as evictCompactedTurnsV2,
27
+ hydrate as hydrateV2State,
28
+ save as saveV2State,
29
+ } from "../v2/activation-store.js";
26
30
  import {
27
31
  injectMemoryV2Block,
28
32
  type InjectMemoryV2Mode,
@@ -62,14 +66,12 @@ export class ConversationGraphMemory {
62
66
  private initialized = false;
63
67
  private needsReload = false;
64
68
  private stateRestored = false;
65
- private scopeId: string;
66
69
  private conversationId: string;
67
70
  private lastInjectedBlock: string | null = null;
68
71
  private lastInjectedNodeIds: string[] = [];
69
72
  private lastInjectedImages: Map<string, ResolvedImage> = new Map();
70
73
 
71
- constructor(scopeId: string, conversationId: string) {
72
- this.scopeId = scopeId;
74
+ constructor(conversationId: string) {
73
75
  this.conversationId = conversationId;
74
76
  }
75
77
 
@@ -147,7 +149,6 @@ export class ConversationGraphMemory {
147
149
  const db = getDb();
148
150
  const baseWhere = and(
149
151
  eq(memorySummaries.scope, "conversation"),
150
- eq(memorySummaries.scopeId, this.scopeId),
151
152
  ne(memorySummaries.scopeKey, this.conversationId),
152
153
  );
153
154
 
@@ -209,11 +210,33 @@ export class ConversationGraphMemory {
209
210
  * Notify that context compaction just happened.
210
211
  * On the next turn, we'll re-run full context load.
211
212
  */
212
- onCompacted(compactedMessageCount: number): void {
213
+ async onCompacted(compactedMessageCount: number): Promise<void> {
213
214
  // Evict everything — compaction summarized all prior turns.
214
215
  // The tracker can't know exactly which turns were compacted,
215
216
  // so we conservatively clear everything and reload.
216
- this.tracker.evictCompactedTurns(this.tracker.getTurn());
217
+ const upToTurn = this.tracker.getTurn();
218
+ this.tracker.evictCompactedTurns(upToTurn);
219
+
220
+ // Mirror the eviction on the v2 activation row: the cached `<memory>`
221
+ // attachments those slugs lived on are gone, but `everInjected` would
222
+ // otherwise keep them deduped from per-turn deltas forever.
223
+ try {
224
+ const db = getDb();
225
+ const state = await hydrateV2State(db, this.conversationId);
226
+ if (state) {
227
+ await saveV2State(
228
+ db,
229
+ this.conversationId,
230
+ evictCompactedTurnsV2(state, upToTurn),
231
+ );
232
+ }
233
+ } catch (err) {
234
+ log.warn(
235
+ { err: err instanceof Error ? err.message : String(err) },
236
+ "Failed to evict v2 activation state on compaction (non-fatal)",
237
+ );
238
+ }
239
+
217
240
  this.needsReload = true;
218
241
  log.info(
219
242
  { compactedMessageCount },
@@ -385,29 +408,11 @@ export class ConversationGraphMemory {
385
408
  signal: AbortSignal,
386
409
  onEvent: (msg: ServerMessage) => void,
387
410
  ) {
388
- const result = await loadContextMemory({
389
- scopeId: this.scopeId,
390
- recentSummaries,
391
- userQuery,
392
- config,
393
- signal,
394
- });
395
-
396
- this.initialized = true;
397
- this.needsReload = false;
398
-
399
- // v2 routing: when the feature flag and workspace config are both on,
400
- // replace v1's injection with the activation-pipeline output. v1
401
- // retrieval still runs above so its tracker stays warm — keeps the
402
- // off→on→off flag flip cheap and avoids invalidating cached metrics.
403
- // assistantMessage is empty: context-load fires on turn 1 / post-
404
- // compaction, so there is no immediately-prior assistant turn to
405
- // weight the activation against.
406
- //
407
411
  // Use the raw user text (no >10-char filter) so even short greetings
408
412
  // ("hi") get a fresh top-K activation dump on the first user message.
409
- // The activation pipeline is robust to weak ANN signal — it still falls
410
- // back to spreading + nowText to surface candidates.
413
+ // The activation pipeline is robust to weak ANN signal — it falls back
414
+ // to spreading + nowText to surface candidates.
415
+ const startedAt = Date.now();
411
416
  const rawUserText = readRawUserText(messages[messages.length - 1]);
412
417
  const v2 = await this.maybeRouteV2Injection(
413
418
  messages,
@@ -415,7 +420,11 @@ export class ConversationGraphMemory {
415
420
  "context-load",
416
421
  rawUserText ?? userQuery ?? "",
417
422
  "",
423
+ signal,
418
424
  );
425
+ this.initialized = true;
426
+ this.needsReload = false;
427
+
419
428
  if (v2.routed) {
420
429
  this.lastInjectedBlock = v2.injectedBlockText;
421
430
  this.lastInjectedNodeIds = [];
@@ -425,17 +434,22 @@ export class ConversationGraphMemory {
425
434
  injectedTokens: v2.injectedBlockText
426
435
  ? estimateTextTokens(v2.injectedBlockText)
427
436
  : 0,
428
- latencyMs: result.latencyMs,
437
+ latencyMs: Date.now() - startedAt,
429
438
  mode: "context-load" as const,
430
439
  injectedBlockText: v2.injectedBlockText,
431
- metrics: result.metrics,
432
- queryVector: result.queryVector,
433
- sparseVector: result.sparseVector,
434
- userQueryVector: result.userQueryVector,
435
- userQuerySparseVector: result.userQuerySparseVector,
440
+ metrics: null,
436
441
  };
437
442
  }
438
443
 
444
+ // v1 fallback — only reached when the v2 flag or workspace config is off.
445
+ const result = await loadContextMemory({
446
+ scopeId: "default",
447
+ recentSummaries,
448
+ userQuery,
449
+ config,
450
+ signal,
451
+ });
452
+
439
453
  if (result.nodes.length === 0) {
440
454
  this.lastInjectedBlock = null;
441
455
  this.lastInjectedNodeIds = [];
@@ -543,27 +557,16 @@ export class ConversationGraphMemory {
543
557
  if (userLastBlocks.length > 0 && assistantLast) break;
544
558
  }
545
559
 
546
- const result = await retrieveForTurn({
547
- assistantLastMessage: assistantLast,
548
- userLastMessage: userLast,
549
- userLastMessageBlocks: userLastBlocks,
550
- scopeId: this.scopeId,
551
- config,
552
- tracker: this.tracker,
553
- signal,
554
- });
555
-
556
- // v2 routing: same gating as `runContextLoad` — when the flag and config
557
- // are both on, the v2 activation pipeline produces the injection block
558
- // (or `null` for the cache-stable empty path). v1 retrieval above runs
559
- // unconditionally so the tracker stays in sync with the v1 nodes —
560
- // cheap insurance for an off→on→off flag flip mid-conversation.
560
+ // v2 path skip v1 retrieval entirely when v2 is enabled. See the
561
+ // matching comment in `runContextLoad` for rationale.
562
+ const startedAt = Date.now();
561
563
  const v2 = await this.maybeRouteV2Injection(
562
564
  messages,
563
565
  config,
564
566
  "per-turn",
565
567
  userLast,
566
568
  assistantLast,
569
+ signal,
567
570
  );
568
571
  if (v2.routed) {
569
572
  this.lastInjectedBlock = v2.injectedBlockText;
@@ -574,15 +577,24 @@ export class ConversationGraphMemory {
574
577
  injectedTokens: v2.injectedBlockText
575
578
  ? estimateTextTokens(v2.injectedBlockText)
576
579
  : 0,
577
- latencyMs: result.latencyMs,
580
+ latencyMs: Date.now() - startedAt,
578
581
  mode: "per-turn" as const,
579
582
  injectedBlockText: v2.injectedBlockText,
580
- metrics: result.metrics,
581
- queryVector: result.queryVector,
582
- sparseVector: result.sparseVector,
583
+ metrics: null,
583
584
  };
584
585
  }
585
586
 
587
+ // v1 path (only reached when the v2 flag or workspace config is off).
588
+ const result = await retrieveForTurn({
589
+ assistantLastMessage: assistantLast,
590
+ userLastMessage: userLast,
591
+ userLastMessageBlocks: userLastBlocks,
592
+ scopeId: "default",
593
+ config,
594
+ tracker: this.tracker,
595
+ signal,
596
+ });
597
+
586
598
  if (result.nodes.length === 0) {
587
599
  this.lastInjectedBlock = null;
588
600
  this.lastInjectedNodeIds = [];
@@ -641,12 +653,12 @@ export class ConversationGraphMemory {
641
653
  }
642
654
 
643
655
  /**
644
- * Route the v1 retrieval's injection step through the v2 activation
645
- * pipeline when the `memory-v2-enabled` feature flag *and* the workspace
646
- * config (`memory.v2.enabled`) are both on.
656
+ * Run the v2 activation pipeline when the workspace config
657
+ * (`memory.v2.enabled`) is on.
647
658
  *
648
659
  * The two outcomes the caller distinguishes via `routed`:
649
- * - `routed: false` — v2 disabled; caller runs the existing v1 injection.
660
+ * - `routed: false` — v2 disabled; caller falls through to the legacy v1
661
+ * retrieval path.
650
662
  * - `routed: true` — v2 ran. `runMessages` is either the v2-prepended
651
663
  * message list (block was non-null) or the input
652
664
  * messages unchanged (cache-stable empty path).
@@ -658,15 +670,13 @@ export class ConversationGraphMemory {
658
670
  mode: InjectMemoryV2Mode,
659
671
  userMessage: string,
660
672
  assistantMessage: string,
673
+ signal: AbortSignal,
661
674
  ): Promise<{
662
675
  routed: boolean;
663
676
  runMessages: Message[];
664
677
  injectedBlockText: string | null;
665
678
  }> {
666
- if (
667
- !isAssistantFeatureFlagEnabled("memory-v2-enabled", config) ||
668
- !config.memory.v2.enabled
669
- ) {
679
+ if (!config.memory.v2.enabled) {
670
680
  return { routed: false, runMessages: messages, injectedBlockText: null };
671
681
  }
672
682
 
@@ -683,6 +693,7 @@ export class ConversationGraphMemory {
683
693
  messageId: `${this.conversationId}:turn:${currentTurn}`,
684
694
  mode,
685
695
  config,
696
+ signal,
686
697
  });
687
698
 
688
699
  if (!result.block) {
@@ -1443,9 +1443,7 @@ async function findCandidateNodes(
1443
1443
  const embedding = await embedWithRetry(config, [searchText]);
1444
1444
  const queryVector = embedding.vectors[0];
1445
1445
  if (queryVector) {
1446
- const searchResults = await searchGraphNodes(queryVector, 100, [
1447
- scopeId,
1448
- ]);
1446
+ const searchResults = await searchGraphNodes(queryVector, 100);
1449
1447
  for (const r of searchResults) allNodeIds.add(r.nodeId);
1450
1448
  }
1451
1449
  } catch (err) {
@@ -2,6 +2,13 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
3
  import { makeMockLogger } from "../../__tests__/helpers/mock-logger.js";
4
4
 
5
+ // This test exercises the v1 graph search path. `config.memory.v2.enabled`
6
+ // (default `true`) makes graph-search short-circuit to keep traffic off
7
+ // the legacy collection — force it off so the v1 path stays under test.
8
+ mock.module("../../config/loader.js", () => ({
9
+ getConfig: () => ({ memory: { v2: { enabled: false } } }),
10
+ }));
11
+
5
12
  mock.module("../../util/logger.js", () => ({
6
13
  getLogger: () => makeMockLogger(),
7
14
  }));
@@ -64,7 +71,7 @@ describe("searchGraphNodes — _meta filter parity", () => {
64
71
  });
65
72
 
66
73
  test("hybrid path excludes _meta sentinel points", async () => {
67
- await searchGraphNodes([0.1], 5, ["default"], {
74
+ await searchGraphNodes([0.1], 5, {
68
75
  indices: [1],
69
76
  values: [1],
70
77
  });
@@ -80,7 +87,7 @@ describe("searchGraphNodes — _meta filter parity", () => {
80
87
  });
81
88
 
82
89
  test("dense-only path also excludes _meta sentinel points", async () => {
83
- await searchGraphNodes([0.1], 5, ["default"]);
90
+ await searchGraphNodes([0.1], 5);
84
91
 
85
92
  expect(searchCalls).toHaveLength(1);
86
93
  const filter = searchCalls[0]?.filter as {
@@ -93,69 +100,6 @@ describe("searchGraphNodes — _meta filter parity", () => {
93
100
  });
94
101
  });
95
102
 
96
- describe("searchGraphNodes — excludeScopeIds", () => {
97
- beforeEach(() => {
98
- breakerOpen = false;
99
- hybridSearchCalls.length = 0;
100
- searchCalls.length = 0;
101
- });
102
-
103
- test("hybrid path adds memory_scope_id must_not when excludeScopeIds provided", async () => {
104
- await searchGraphNodes(
105
- [0.1],
106
- 5,
107
- undefined,
108
- { indices: [1], values: [1] },
109
- undefined,
110
- ["scope:abc", "scope:xyz"],
111
- );
112
-
113
- expect(hybridSearchCalls).toHaveLength(1);
114
- const filter = hybridSearchCalls[0]?.filter as {
115
- must_not: Array<Record<string, unknown>>;
116
- };
117
- const scopeExclude = filter.must_not.find(
118
- (c) => c.key === "memory_scope_id",
119
- ) as { match: { any: string[] } } | undefined;
120
- expect(scopeExclude?.match.any).toEqual(["scope:abc", "scope:xyz"]);
121
- });
122
-
123
- test("dense-only path adds memory_scope_id must_not when excludeScopeIds provided", async () => {
124
- await searchGraphNodes([0.1], 5, undefined, undefined, undefined, [
125
- "scope:abc",
126
- ]);
127
-
128
- expect(searchCalls).toHaveLength(1);
129
- const filter = searchCalls[0]?.filter as {
130
- must_not: Array<Record<string, unknown>>;
131
- };
132
- const scopeExclude = filter.must_not.find(
133
- (c) => c.key === "memory_scope_id",
134
- ) as { match: { any: string[] } } | undefined;
135
- expect(scopeExclude?.match.any).toEqual(["scope:abc"]);
136
- });
137
-
138
- test("hybrid path omits memory_scope_id must_not when excludeScopeIds is empty", async () => {
139
- await searchGraphNodes(
140
- [0.1],
141
- 5,
142
- undefined,
143
- { indices: [1], values: [1] },
144
- undefined,
145
- [],
146
- );
147
-
148
- expect(hybridSearchCalls).toHaveLength(1);
149
- const filter = hybridSearchCalls[0]?.filter as {
150
- must_not: Array<Record<string, unknown>>;
151
- };
152
- const scopeExclude = filter.must_not.find(
153
- (c) => c.key === "memory_scope_id",
154
- );
155
- expect(scopeExclude).toBeUndefined();
156
- });
157
- });
158
-
159
103
  describe("searchGraphNodes — prefetch floor", () => {
160
104
  beforeEach(() => {
161
105
  breakerOpen = false;
@@ -164,7 +108,7 @@ describe("searchGraphNodes — prefetch floor", () => {
164
108
  });
165
109
 
166
110
  test("hybrid prefetchLimit floors at 200 for small limits", async () => {
167
- await searchGraphNodes([0.1], 10, ["default"], {
111
+ await searchGraphNodes([0.1], 10, {
168
112
  indices: [1],
169
113
  values: [1],
170
114
  });
@@ -174,7 +118,7 @@ describe("searchGraphNodes — prefetch floor", () => {
174
118
  });
175
119
 
176
120
  test("hybrid prefetchLimit scales with limit when limit*10 exceeds floor", async () => {
177
- await searchGraphNodes([0.1], 50, ["default"], {
121
+ await searchGraphNodes([0.1], 50, {
178
122
  indices: [1],
179
123
  values: [1],
180
124
  });
@@ -5,7 +5,6 @@
5
5
  import { getConfig } from "../../config/loader.js";
6
6
  import type { AssistantConfig } from "../../config/types.js";
7
7
  import { getLogger } from "../../util/logger.js";
8
- import { isMemoryV2ReadActive } from "../context-search/sources/memory-v2.js";
9
8
  import { selectedBackendSupportsMultimodal } from "../embedding-backend.js";
10
9
  import type { EmbeddingInput } from "../embedding-types.js";
11
10
  import { embedAndUpsert } from "../job-utils.js";
@@ -38,23 +37,19 @@ export interface GraphSearchResult {
38
37
  * Semantic search across graph nodes in Qdrant. Returns scored node IDs
39
38
  * that the caller can hydrate from the graph store.
40
39
  *
41
- * Filters to `target_type: "graph_node"` and optionally by scope.
42
- * `excludeScopeIds` adds a `must_not` against `memory_scope_id` for callers
43
- * that need to omit specific scopes from a broader recall search.
40
+ * Filters to `target_type: "graph_node"`.
44
41
  */
45
42
  export async function searchGraphNodes(
46
43
  queryVector: number[],
47
44
  limit: number,
48
- scopeIds?: string[],
49
45
  sparseVector?: QdrantSparseVector,
50
46
  dateRange?: { afterMs?: number; beforeMs?: number },
51
- excludeScopeIds?: string[],
52
47
  ): Promise<GraphSearchResult[]> {
53
- // v2 owns the read path when both gates are on. The v1 `memory` collection
54
- // is in active retirement and a corrupted sparse segment can OOM-crash the
48
+ // v2 owns the read path when enabled. The v1 `memory` collection is in
49
+ // active retirement and a corrupted sparse segment can OOM-crash the
55
50
  // shared Qdrant process — short-circuiting here keeps v1 background work
56
51
  // and stale callers from taking v2 down with them.
57
- if (isMemoryV2ReadActive(getConfig())) return [];
52
+ if (getConfig().memory.v2.enabled) return [];
58
53
 
59
54
  if (isQdrantBreakerOpen()) {
60
55
  log.warn("Qdrant circuit breaker open, skipping graph search");
@@ -66,21 +61,12 @@ export async function searchGraphNodes(
66
61
  const mustNot: Record<string, unknown>[] = [
67
62
  { key: "_meta", match: { value: true } },
68
63
  ];
69
- if (excludeScopeIds && excludeScopeIds.length > 0) {
70
- mustNot.push({
71
- key: "memory_scope_id",
72
- match: { any: excludeScopeIds },
73
- });
74
- }
75
64
 
76
65
  // Use hybrid search (dense + sparse with RRF fusion) when a non-empty
77
66
  // sparse vector is available; otherwise fall back to dense-only search.
78
67
  if (sparseVector && sparseVector.indices.length > 0) {
79
68
  const must: Record<string, unknown>[] = [
80
69
  { key: "target_type", match: { value: "graph_node" } },
81
- ...(scopeIds && scopeIds.length > 0
82
- ? [{ key: "memory_scope_id", match: { any: scopeIds } }]
83
- : []),
84
70
  ];
85
71
  if (dateRange?.afterMs != null) {
86
72
  must.push({ key: "created_at", range: { gte: dateRange.afterMs } });
@@ -120,12 +106,6 @@ export async function searchGraphNodes(
120
106
  },
121
107
  ];
122
108
 
123
- if (scopeIds && scopeIds.length > 0) {
124
- denseMusts.push({
125
- key: "memory_scope_id",
126
- match: { any: scopeIds },
127
- });
128
- }
129
109
  if (dateRange?.afterMs != null) {
130
110
  denseMusts.push({ key: "created_at", range: { gte: dateRange.afterMs } });
131
111
  }
@@ -81,7 +81,16 @@ import { loadContextMemory, retrieveForTurn } from "./retriever.js";
81
81
  import { createNode } from "./store.js";
82
82
  import type { NewNode } from "./types.js";
83
83
 
84
- const TEST_CONFIG: AssistantConfig = { ...DEFAULT_CONFIG };
84
+ // These tests exercise v1 retrieval. `memory.v2.enabled` (default `true`)
85
+ // makes `loadContextMemory` short-circuit, so disable it here to keep the
86
+ // v1 path under test.
87
+ const TEST_CONFIG: AssistantConfig = {
88
+ ...DEFAULT_CONFIG,
89
+ memory: {
90
+ ...DEFAULT_CONFIG.memory,
91
+ v2: { ...DEFAULT_CONFIG.memory.v2, enabled: false },
92
+ },
93
+ };
85
94
 
86
95
  function makeCapabilityNode(content: string, capId: string): NewNode {
87
96
  const now = Date.now();
@@ -401,8 +410,10 @@ describe("loadContextMemory — dual-query capability ranking", () => {
401
410
 
402
411
  // Build a config where capabilityReserve=1 so the ranking code actually
403
412
  // prunes (it only prunes when capabilityNodes.length > capabilityReserve).
413
+ // memory.v2.enabled=false to keep the v1 retrieval path under test.
404
414
  const DUAL_QUERY_CONFIG: AssistantConfig = structuredClone(DEFAULT_CONFIG);
405
415
  DUAL_QUERY_CONFIG.memory.retrieval.injection.contextLoad.capabilityReserve = 1;
416
+ DUAL_QUERY_CONFIG.memory.v2.enabled = false;
406
417
 
407
418
  // Keyword-routed embed: any text that contains a topic keyword returns a
408
419
  // one-hot vector identifying that topic. Anything else falls back to a
@@ -14,7 +14,6 @@ import {
14
14
  } from "../../providers/provider-send-message.js";
15
15
  import type { ContentBlock, ImageContent } from "../../providers/types.js";
16
16
  import { getLogger } from "../../util/logger.js";
17
- import { isMemoryV2ReadActive } from "../context-search/sources/memory-v2.js";
18
17
  import { embedWithRetry } from "../embed.js";
19
18
  import {
20
19
  generateSparseEmbedding,
@@ -351,7 +350,7 @@ async function dedupCrossCategory(
351
350
  // Context load — conversation start
352
351
  // ---------------------------------------------------------------------------
353
352
 
354
- export interface ContextLoadOpts {
353
+ interface ContextLoadOpts {
355
354
  /** Scope for memory isolation. */
356
355
  scopeId: string;
357
356
  /** Recent conversation summaries (used as retrieval queries). */
@@ -373,7 +372,7 @@ export interface ContextLoadOpts {
373
372
  userQuery?: string;
374
373
  }
375
374
 
376
- export interface ContextLoadResult {
375
+ interface ContextLoadResult {
377
376
  nodes: ScoredNode[];
378
377
  serendipityNodes: ScoredNode[];
379
378
  triggeredNodes: TriggeredResult[];
@@ -426,12 +425,12 @@ export interface ContextLoadResult {
426
425
  export async function loadContextMemory(
427
426
  opts: ContextLoadOpts,
428
427
  ): Promise<ContextLoadResult> {
429
- // v2 owns the read path when both gates are on. The v1 collection is in
430
- // active retirement and querying it can OOM-crash Qdrant via a corrupted
431
- // sparse segment, so we skip the embedding work and downstream searches
428
+ // v2 owns the read path when enabled. The v1 collection is in active
429
+ // retirement and querying it can OOM-crash Qdrant via a corrupted sparse
430
+ // segment, so we skip the embedding work and downstream searches
432
431
  // entirely. Caller (`runContextLoad`) sees zero nodes and routes to the
433
432
  // v2 activation pipeline.
434
- if (isMemoryV2ReadActive(opts.config)) {
433
+ if (opts.config.memory.v2.enabled) {
435
434
  return {
436
435
  nodes: [],
437
436
  serendipityNodes: [],
@@ -525,7 +524,6 @@ export async function loadContextMemory(
525
524
  const results = await searchGraphNodes(
526
525
  queryVector,
527
526
  maxNodes * 3,
528
- [opts.scopeId],
529
527
  sparseVector,
530
528
  );
531
529
  for (const r of results) {
@@ -548,7 +546,6 @@ export async function loadContextMemory(
548
546
  const results = await searchGraphNodes(
549
547
  userQueryVector,
550
548
  maxNodes * 3,
551
- [opts.scopeId],
552
549
  undefined,
553
550
  );
554
551
  for (const r of results) {
@@ -873,7 +870,7 @@ export async function loadContextMemory(
873
870
  // Per-turn retrieval — mid-conversation injection
874
871
  // ---------------------------------------------------------------------------
875
872
 
876
- export interface TurnRetrievalOpts {
873
+ interface TurnRetrievalOpts {
877
874
  /** The assistant's last message content. */
878
875
  assistantLastMessage: string;
879
876
  /** The user's last message content. */
@@ -886,7 +883,7 @@ export interface TurnRetrievalOpts {
886
883
  signal?: AbortSignal;
887
884
  }
888
885
 
889
- export interface TurnRetrievalResult {
886
+ interface TurnRetrievalResult {
890
887
  /** New nodes to inject (not already in context). */
891
888
  nodes: ScoredNode[];
892
889
  /** Serendipity picks included in nodes. */
@@ -1007,9 +1004,7 @@ export async function retrieveForTurn(
1007
1004
  }
1008
1005
  const imgVector = imgResult.vectors[0];
1009
1006
  if (imgVector) {
1010
- const imgResults = await searchGraphNodes(imgVector, 40, [
1011
- opts.scopeId,
1012
- ]);
1007
+ const imgResults = await searchGraphNodes(imgVector, 40);
1013
1008
  for (const r of imgResults) {
1014
1009
  const current = allCandidateIds.get(r.nodeId) ?? 0;
1015
1010
  allCandidateIds.set(r.nodeId, Math.max(current, r.score));
@@ -1097,7 +1092,7 @@ export async function retrieveForTurn(
1097
1092
  queryEmbeddings = embedResults.vectors;
1098
1093
 
1099
1094
  const searchPromises = queryEmbeddings.map((vec) =>
1100
- searchGraphNodes(vec, 40, [opts.scopeId]),
1095
+ searchGraphNodes(vec, 40),
1101
1096
  );
1102
1097
  const searchResults = await Promise.all(searchPromises);
1103
1098