@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
@@ -9,7 +9,7 @@ import type {
9
9
  RecallSearchContext,
10
10
  RecallSearchResult,
11
11
  } from "../types.js";
12
- import { isMemoryV2ReadActive, searchMemoryV2Source } from "./memory-v2.js";
12
+ import { searchMemoryV2Source } from "./memory-v2.js";
13
13
 
14
14
  const log = getLogger("context-search-memory-source");
15
15
 
@@ -23,7 +23,7 @@ export async function searchMemorySource(
23
23
  return { evidence: [] };
24
24
  }
25
25
 
26
- if (isMemoryV2ReadActive(context.config)) {
26
+ if (context.config.memory.v2.enabled) {
27
27
  return searchMemoryV2Source(query, context, normalizedLimit);
28
28
  }
29
29
 
@@ -47,7 +47,6 @@ export async function searchMemorySource(
47
47
  const searchResults = await searchGraphNodes(
48
48
  queryVector,
49
49
  normalizedLimit,
50
- [context.memoryScopeId],
51
50
  sparseVector,
52
51
  );
53
52
 
@@ -15,7 +15,6 @@ import type {
15
15
  RecallSearchContext,
16
16
  RecallSearchResult,
17
17
  } from "../types.js";
18
- import { isMemoryV2ReadActive } from "./memory-v2.js";
19
18
 
20
19
  const log = getLogger("context-search-pkb-source");
21
20
 
@@ -76,7 +75,7 @@ export async function searchPkbSource(
76
75
  context: RecallSearchContext,
77
76
  limit: number,
78
77
  ): Promise<RecallSearchResult> {
79
- if (isMemoryV2ReadActive(context.config)) {
78
+ if (context.config.memory.v2.enabled) {
80
79
  return { evidence: [] };
81
80
  }
82
81
 
@@ -139,7 +138,7 @@ export async function searchPkbSource(
139
138
  export function readPkbContextEvidence(
140
139
  context: RecallSearchContext,
141
140
  ): RecallEvidence[] {
142
- if (isMemoryV2ReadActive(context.config)) {
141
+ if (context.config.memory.v2.enabled) {
143
142
  return [];
144
143
  }
145
144
 
@@ -24,7 +24,6 @@ export interface RecallEvidence {
24
24
 
25
25
  export interface RecallSearchContext {
26
26
  workingDir: string;
27
- memoryScopeId: string;
28
27
  conversationId: string;
29
28
  config: AssistantConfig;
30
29
  signal?: AbortSignal;
@@ -115,8 +115,6 @@ export const messageMetadataSchema = z
115
115
  })
116
116
  .passthrough();
117
117
 
118
- export type MessageMetadata = z.infer<typeof messageMetadataSchema>;
119
-
120
118
  function cloneForkMessageMetadata(
121
119
  metadata: string | null,
122
120
  sourceMessageId: string,
@@ -444,11 +442,6 @@ export function getConversationSource(conversationId: string): string | null {
444
442
  return row?.source ?? null;
445
443
  }
446
444
 
447
- export function getConversationMemoryScopeId(conversationId: string): string {
448
- const conv = getConversation(conversationId);
449
- return conv?.memoryScopeId ?? "default";
450
- }
451
-
452
445
  /**
453
446
  * Fetch group_id for a conversation via raw SQL. group_id is NOT in the
454
447
  * Drizzle schema (raw-query-only pattern), so ConversationRow doesn't
@@ -912,7 +905,6 @@ export async function addMessage(
912
905
  if (!opts?.skipIndexing) {
913
906
  try {
914
907
  const config = getConfig();
915
- const scopeId = getConversationMemoryScopeId(conversationId);
916
908
  const parsed = metadata
917
909
  ? messageMetadataSchema.safeParse(metadata)
918
910
  : null;
@@ -927,7 +919,7 @@ export async function addMessage(
927
919
  role: message.role,
928
920
  content: message.content,
929
921
  createdAt: message.createdAt,
930
- scopeId,
922
+ scopeId: "default",
931
923
  provenanceTrustClass,
932
924
  automated,
933
925
  },
@@ -1027,7 +1019,7 @@ export function hasMessages(conversationId: string): boolean {
1027
1019
  return row !== undefined;
1028
1020
  }
1029
1021
 
1030
- export interface PaginatedMessagesResult {
1022
+ interface PaginatedMessagesResult {
1031
1023
  messages: MessageRow[];
1032
1024
  hasMore: boolean;
1033
1025
  }
@@ -1441,12 +1433,12 @@ export function deleteLastExchange(conversationId: string): number {
1441
1433
  * Callers must delete these from the Qdrant collection after the
1442
1434
  * SQLite transaction commits.
1443
1435
  */
1444
- export interface DeletedMemoryIds {
1436
+ interface DeletedMemoryIds {
1445
1437
  segmentIds: string[];
1446
1438
  deletedSummaryIds: string[];
1447
1439
  }
1448
1440
 
1449
- export interface WipeConversationResult extends DeletedMemoryIds {
1441
+ interface WipeConversationResult extends DeletedMemoryIds {
1450
1442
  cancelledJobCount: number;
1451
1443
  }
1452
1444
 
@@ -165,6 +165,7 @@ import {
165
165
  migrateStripPlaceholderSentinelsFromMessages,
166
166
  migrateStripThinkingFromConsolidated,
167
167
  migrateToolInvocationsMatchedRuleId,
168
+ migrateTraceEventsCreatedAtIndex,
168
169
  migrateUsageDashboardIndexes,
169
170
  migrateUsageLlmCallCount,
170
171
  migrateVoiceInviteColumns,
@@ -408,6 +409,7 @@ export function initializeDb(): void {
408
409
  backfillAppConversationIds();
409
410
  },
410
411
  migrateScheduleRetryPolicy,
412
+ migrateTraceEventsCreatedAtIndex,
411
413
  ];
412
414
 
413
415
  // Run each migration step, catching and logging individual failures so one
@@ -38,10 +38,16 @@ const ONNXRUNTIME_COMMON_VERSION = "1.21.0";
38
38
  const TRANSFORMERS_VERSION = "3.8.1";
39
39
  const JINJA_VERSION = "0.5.5";
40
40
 
41
- /** Composite version string for cache invalidation. */
42
- const RUNTIME_VERSION = `ort-${ONNXRUNTIME_NODE_VERSION}_hf-${TRANSFORMERS_VERSION}_jinja-${JINJA_VERSION}`;
41
+ /**
42
+ * Composite version string for cache invalidation. Bumping the trailing
43
+ * `_workers-vN` suffix forces existing installs to regenerate the worker
44
+ * scripts when the worker IPC contract or spawn-args list changes (without
45
+ * requiring an `@huggingface/transformers` version bump).
46
+ */
47
+ const RUNTIME_VERSION = `ort-${ONNXRUNTIME_NODE_VERSION}_hf-${TRANSFORMERS_VERSION}_jinja-${JINJA_VERSION}_workers-v2`;
43
48
 
44
49
  const WORKER_FILENAME = "embed-worker.mjs";
50
+ const RERANK_WORKER_FILENAME = "rerank-worker.mjs";
45
51
 
46
52
  /** Module-level guard so concurrent in-process calls share one download. */
47
53
  const installGuard = new PromiseGuard<void>();
@@ -171,6 +177,101 @@ process.stdin.on('end', () => process.exit(0));
171
177
  `;
172
178
  }
173
179
 
180
+ function generateRerankWorkerScript(): string {
181
+ // Cross-encoder rerank worker. Loads a sequence-classification model and
182
+ // scores paired (queries[i], passages[i]) tuples in one batched ONNX
183
+ // inference call. Mirrors the embed worker's lifecycle (ready signal,
184
+ // JSON-lines IPC, sequential queue) so LocalRerankBackend can reuse the
185
+ // same supervisor pattern.
186
+ //
187
+ // Request shape: { id, queries: string[], passages: string[] } with
188
+ // queries.length === passages.length. Each pair is one (query, passage)
189
+ // tuple; multiple distinct queries can ride in a single batch so the
190
+ // activation pipeline can score the user-channel and assistant-channel
191
+ // queries against a shared candidate set in one tokenizer + ONNX call.
192
+ return `\
193
+ // rerank-worker.mjs — Auto-generated by EmbeddingRuntimeManager
194
+ // Runs in a separate bun process, communicates via JSON-lines over stdin/stdout.
195
+ process.title = 'rerank-worker';
196
+ import {
197
+ AutoModelForSequenceClassification,
198
+ AutoTokenizer,
199
+ env,
200
+ } from '@huggingface/transformers';
201
+
202
+ const model = process.argv[2];
203
+ const cacheDir = process.argv[3];
204
+ const dtype = process.argv[4] || 'q8';
205
+ if (cacheDir && env) env.cacheDir = cacheDir;
206
+
207
+ let tokenizer;
208
+ let session;
209
+ try {
210
+ tokenizer = await AutoTokenizer.from_pretrained(model);
211
+ session = await AutoModelForSequenceClassification.from_pretrained(model, { dtype });
212
+ process.stdout.write(JSON.stringify({ type: 'ready' }) + '\\n');
213
+ } catch (err) {
214
+ process.stdout.write(JSON.stringify({ type: 'error', error: err.message || String(err) }) + '\\n');
215
+ process.exit(1);
216
+ }
217
+
218
+ const sigmoid = (x) => 1 / (1 + Math.exp(-x));
219
+
220
+ const decoder = new TextDecoder();
221
+ let buffer = '';
222
+ let processing = false;
223
+ const queue = [];
224
+
225
+ process.stdin.on('data', (chunk) => {
226
+ buffer += typeof chunk === 'string' ? chunk : decoder.decode(chunk, { stream: true });
227
+ let idx;
228
+ while ((idx = buffer.indexOf('\\n')) !== -1) {
229
+ const line = buffer.slice(0, idx);
230
+ buffer = buffer.slice(idx + 1);
231
+ if (line.trim()) queue.push(line);
232
+ }
233
+ if (!processing) processQueue();
234
+ });
235
+
236
+ async function processQueue() {
237
+ processing = true;
238
+ while (queue.length > 0) {
239
+ const line = queue.shift();
240
+ let req;
241
+ try { req = JSON.parse(line); } catch { continue; }
242
+ try {
243
+ const { id, queries, passages } = req;
244
+ if (
245
+ !Array.isArray(queries) || !Array.isArray(passages) ||
246
+ queries.length !== passages.length || passages.length === 0
247
+ ) {
248
+ process.stdout.write(JSON.stringify({ id, scores: [] }) + '\\n');
249
+ continue;
250
+ }
251
+ const inputs = await tokenizer(queries, {
252
+ text_pair: passages,
253
+ padding: true,
254
+ truncation: true,
255
+ return_tensors: 'pt',
256
+ });
257
+ const out = await session(inputs);
258
+ const logits = out.logits.data;
259
+ const scores = new Array(passages.length);
260
+ for (let i = 0; i < passages.length; i++) {
261
+ scores[i] = sigmoid(Number(logits[i]));
262
+ }
263
+ process.stdout.write(JSON.stringify({ id, scores }) + '\\n');
264
+ } catch (err) {
265
+ process.stdout.write(JSON.stringify({ id: req?.id, error: err.message || String(err) }) + '\\n');
266
+ }
267
+ }
268
+ processing = false;
269
+ }
270
+
271
+ process.stdin.on('end', () => process.exit(0));
272
+ `;
273
+ }
274
+
174
275
  // ── Main manager ────────────────────────────────────────────────────
175
276
 
176
277
  export class EmbeddingRuntimeManager {
@@ -186,8 +287,12 @@ export class EmbeddingRuntimeManager {
186
287
  if (!manifest) return false;
187
288
  if (manifest.runtimeVersion !== RUNTIME_VERSION) return false;
188
289
 
189
- // Verify the worker script exists and a bun binary is available
190
- return existsSync(this.getWorkerPath()) && this.getBunPath() !== undefined;
290
+ // Verify both worker scripts exist and a bun binary is available
291
+ return (
292
+ existsSync(this.getWorkerPath()) &&
293
+ existsSync(this.getRerankWorkerPath()) &&
294
+ this.getBunPath() !== undefined
295
+ );
191
296
  }
192
297
 
193
298
  /** Path to the embed worker script. */
@@ -195,6 +300,11 @@ export class EmbeddingRuntimeManager {
195
300
  return join(this.baseDir, WORKER_FILENAME);
196
301
  }
197
302
 
303
+ /** Path to the rerank worker script. */
304
+ getRerankWorkerPath(): string {
305
+ return join(this.baseDir, RERANK_WORKER_FILENAME);
306
+ }
307
+
198
308
  /**
199
309
  * Find a usable bun binary.
200
310
  * Delegates to the shared bun-runtime helper, also checking
@@ -375,8 +485,12 @@ export class EmbeddingRuntimeManager {
375
485
  ].join("\n"),
376
486
  );
377
487
 
378
- // Step 4: Write embed worker script
488
+ // Step 4: Write embed + rerank worker scripts
379
489
  writeFileSync(join(tmpDir, WORKER_FILENAME), generateWorkerScript());
490
+ writeFileSync(
491
+ join(tmpDir, RERANK_WORKER_FILENAME),
492
+ generateRerankWorkerScript(),
493
+ );
380
494
 
381
495
  // Step 5: Write version manifest
382
496
  const manifest: VersionManifest = {
@@ -1,18 +1,16 @@
1
1
  /**
2
2
  * Tests for the v2 routing wired into `ConversationGraphMemory.prepareMemory`.
3
3
  *
4
- * The wiring layer at `conversation-graph-memory.ts` reads two signals to
5
- * decide whether to swap v1's injection step for the v2 activation pipeline:
4
+ * The wiring layer at `conversation-graph-memory.ts` reads
5
+ * `config.memory.v2.enabled` to decide whether to swap v1's injection step
6
+ * for the v2 activation pipeline.
6
7
  *
7
- * 1. The `memory-v2-enabled` feature flag (`isAssistantFeatureFlagEnabled`).
8
- * 2. The workspace config value `memory.v2.enabled`.
9
- *
10
- * Both must be true for v2 to take over. This file uses the *real*
11
- * `injectMemoryV2Block` and stubs only the lower-level deps (Qdrant client,
12
- * embedding backend) the way `memory/v2/__tests__/injection.test.ts` does
13
- * mocking `injection.js` itself would clobber that sibling test when both
14
- * files run in the same `bun test` invocation, since `mock.module` is
15
- * process-global. Avoiding the mock keeps the suite hermetic in either order.
8
+ * This file uses the *real* `injectMemoryV2Block` and stubs only the
9
+ * lower-level deps (Qdrant client, embedding backend) the way
10
+ * `memory/v2/__tests__/injection.test.ts` does — mocking `injection.js`
11
+ * itself would clobber that sibling test when both files run in the same
12
+ * `bun test` invocation, since `mock.module` is process-global. Avoiding
13
+ * the mock keeps the suite hermetic in either order.
16
14
  */
17
15
  import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
18
16
  import { tmpdir } from "node:os";
@@ -20,7 +18,6 @@ import { join } from "node:path";
20
18
  import { Database } from "bun:sqlite";
21
19
  import {
22
20
  afterAll,
23
- afterEach,
24
21
  beforeAll,
25
22
  beforeEach,
26
23
  describe,
@@ -45,25 +42,28 @@ mock.module("../../../util/logger.js", () => ({
45
42
 
46
43
  // Stub the v1 retriever so we don't reach Qdrant. Both modes return zero
47
44
  // nodes — the v1 injection branch becomes a no-op, isolating the assertion
48
- // to "did the v2 routing fire?".
45
+ // to "did the v2 routing fire?". Tracked via `mock()` so tests can also
46
+ // assert that v1 retrieval is *not* called when v2 is enabled.
47
+ const loadContextMemoryMock = mock(async () => ({
48
+ nodes: [],
49
+ serendipityNodes: [],
50
+ latencyMs: 1,
51
+ metrics: null,
52
+ queryVector: undefined,
53
+ sparseVector: undefined,
54
+ userQueryVector: undefined,
55
+ userQuerySparseVector: undefined,
56
+ }));
57
+ const retrieveForTurnMock = mock(async () => ({
58
+ nodes: [],
59
+ latencyMs: 1,
60
+ metrics: null,
61
+ queryVector: undefined,
62
+ sparseVector: undefined,
63
+ }));
49
64
  mock.module("../retriever.js", () => ({
50
- loadContextMemory: async () => ({
51
- nodes: [],
52
- serendipityNodes: [],
53
- latencyMs: 1,
54
- metrics: null,
55
- queryVector: undefined,
56
- sparseVector: undefined,
57
- userQueryVector: undefined,
58
- userQuerySparseVector: undefined,
59
- }),
60
- retrieveForTurn: async () => ({
61
- nodes: [],
62
- latencyMs: 1,
63
- metrics: null,
64
- queryVector: undefined,
65
- sparseVector: undefined,
66
- }),
65
+ loadContextMemory: loadContextMemoryMock,
66
+ retrieveForTurn: retrieveForTurnMock,
67
67
  }));
68
68
 
69
69
  // Programmable embedding + Qdrant state. Mirrors the pattern in
@@ -95,9 +95,11 @@ class MockQdrantClient {
95
95
  _name: string,
96
96
  params: { using: string; limit: number; filter?: unknown },
97
97
  ) {
98
- const queue =
99
- qdrantState.queryResponses[params.using as "dense" | "sparse"];
100
- return queue.shift() ?? { points: [] };
98
+ // The four-channel hybrid query fires body-dense, body-sparse,
99
+ // summary-dense, summary-sparse in order; both dense channels share
100
+ // the dense queue and both sparse channels share the sparse queue.
101
+ const channel = params.using.endsWith("sparse") ? "sparse" : "dense";
102
+ return qdrantState.queryResponses[channel].shift() ?? { points: [] };
101
103
  }
102
104
  }
103
105
 
@@ -167,14 +169,14 @@ import type { DrizzleDb } from "../../db-connection.js";
167
169
 
168
170
  const { ConversationGraphMemory } =
169
171
  await import("../conversation-graph-memory.js");
170
- const { _setOverridesForTesting } =
171
- await import("../../../config/assistant-feature-flags.js");
172
172
  const { applyNestedDefaults } = await import("../../../config/loader.js");
173
173
  const { getSqliteFrom } = await import("../../db-connection.js");
174
174
  const { migrateActivationState } =
175
175
  await import("../../migrations/232-activation-state.js");
176
176
  const schema = await import("../../schema.js");
177
177
  const { _resetMemoryV2QdrantForTests } = await import("../../v2/qdrant.js");
178
+ const { hydrate: hydrateActivationState } =
179
+ await import("../../v2/activation-store.js");
178
180
 
179
181
  // The wiring layer calls `getDb()` to fetch the SQLite handle. We mock
180
182
  // only that one export and spread the real module so unrelated callers
@@ -232,15 +234,26 @@ function makeMemory(): InstanceType<typeof ConversationGraphMemory> {
232
234
  // `initialized = true` skips the context-load branch and the
233
235
  // `fetchRecentSummaries` DB read it depends on, isolating the per-turn path
234
236
  // for these unit tests. Context-load is covered by its own block below.
235
- const m = new ConversationGraphMemory("scope-1", "conv-test-1");
237
+ const m = new ConversationGraphMemory("conv-test-1");
236
238
  (m as unknown as { initialized: boolean }).initialized = true;
237
239
  return m;
238
240
  }
239
241
 
240
- /** Stage one set of dense/sparse hits for each channel of the activation
241
- * pipeline (1 candidate query + 3 simBatch channels). */
242
+ /** Stage one set of body and summary dense/sparse hits for each channel of
243
+ * the activation pipeline (1 candidate query + 3 simBatch channels). Each
244
+ * `hybridQueryConceptPages` call now fires four sub-queries (body-dense,
245
+ * body-sparse, summary-dense, summary-sparse) so we push four entries per
246
+ * channel iteration. Hits without `summary*Score` set produce empty point
247
+ * lists for the summary channels — fine for tests that only care about body
248
+ * scoring. */
242
249
  function stageTurn(
243
- hits: Array<{ slug: string; denseScore?: number; sparseScore?: number }>,
250
+ hits: Array<{
251
+ slug: string;
252
+ denseScore?: number;
253
+ sparseScore?: number;
254
+ summaryDenseScore?: number;
255
+ summarySparseScore?: number;
256
+ }>,
244
257
  ): void {
245
258
  for (let i = 0; i < 4; i++) {
246
259
  qdrantState.queryResponses.dense.push({
@@ -253,6 +266,22 @@ function stageTurn(
253
266
  .filter((h) => h.sparseScore !== undefined)
254
267
  .map((h) => ({ score: h.sparseScore, payload: { slug: h.slug } })),
255
268
  });
269
+ qdrantState.queryResponses.dense.push({
270
+ points: hits
271
+ .filter((h) => h.summaryDenseScore !== undefined)
272
+ .map((h) => ({
273
+ score: h.summaryDenseScore,
274
+ payload: { slug: h.slug },
275
+ })),
276
+ });
277
+ qdrantState.queryResponses.sparse.push({
278
+ points: hits
279
+ .filter((h) => h.summarySparseScore !== undefined)
280
+ .map((h) => ({
281
+ score: h.summarySparseScore,
282
+ payload: { slug: h.slug },
283
+ })),
284
+ });
256
285
  }
257
286
  }
258
287
 
@@ -262,42 +291,17 @@ beforeEach(() => {
262
291
  testDbHandle = createTestDb();
263
292
  qdrantState.queryResponses.dense.length = 0;
264
293
  qdrantState.queryResponses.sparse.length = 0;
294
+ loadContextMemoryMock.mockClear();
295
+ retrieveForTurnMock.mockClear();
265
296
  _resetMemoryV2QdrantForTests();
266
297
  });
267
298
 
268
- afterEach(() => {
269
- _setOverridesForTesting({});
270
- });
271
-
272
299
  // ---------------------------------------------------------------------------
273
300
  // Tests
274
301
  // ---------------------------------------------------------------------------
275
302
 
276
303
  describe("ConversationGraphMemory.prepareMemory — v2 routing (per-turn path)", () => {
277
- test("flag off → v2 not run, messages unchanged", async () => {
278
- _setOverridesForTesting({ "memory-v2-enabled": false });
279
- stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
280
-
281
- const memory = makeMemory();
282
- const config = makeConfig(true);
283
- const messages = makeMessages();
284
-
285
- const result = await memory.prepareMemory(
286
- messages,
287
- config,
288
- new AbortController().signal,
289
- noopEvent,
290
- );
291
-
292
- expect(result.mode).toBe("per-turn");
293
- expect(result.injectedBlockText).toBeNull();
294
- // No v2 block prepended — the v1 retriever returned zero nodes so the
295
- // user message is exactly the input.
296
- expect(result.runMessages).toEqual(messages);
297
- });
298
-
299
- test("flag on + config off → v2 not run, messages unchanged", async () => {
300
- _setOverridesForTesting({ "memory-v2-enabled": true });
304
+ test("config off → v2 not run, messages unchanged", async () => {
301
305
  stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
302
306
 
303
307
  const memory = makeMemory();
@@ -316,8 +320,7 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (per-turn path)",
316
320
  expect(result.runMessages).toEqual(messages);
317
321
  });
318
322
 
319
- test("flag on + config on → v2 block prepended, mode is per-turn", async () => {
320
- _setOverridesForTesting({ "memory-v2-enabled": true });
323
+ test("config on → v2 block prepended, mode is per-turn", async () => {
321
324
  stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
322
325
 
323
326
  const memory = makeMemory();
@@ -334,7 +337,9 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (per-turn path)",
334
337
  expect(result.mode).toBe("per-turn");
335
338
  expect(result.injectedBlockText).not.toBeNull();
336
339
  expect(result.injectedBlockText).not.toContain("<memory>");
337
- expect(result.injectedBlockText).toContain("### alice-vscode");
340
+ expect(result.injectedBlockText).toContain(
341
+ "# memory/concepts/alice-vscode.md",
342
+ );
338
343
 
339
344
  // The leading content block on the user message is the v2 block,
340
345
  // wrapped exactly once.
@@ -347,13 +352,15 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (per-turn path)",
347
352
  expect(firstBlock.text.endsWith("\n</memory>")).toBe(true);
348
353
  // No nested wrapper.
349
354
  expect(firstBlock.text.match(/<memory>/g)?.length).toBe(1);
355
+
356
+ // v1 retrieval is fully bypassed when v2 is enabled.
357
+ expect(retrieveForTurnMock).not.toHaveBeenCalled();
350
358
  });
351
359
 
352
360
  test("reinjectCachedMemory after v2 injection wraps exactly once (no double-wrap)", async () => {
353
361
  // Regression for the double-wrap bug: v2 cached `lastInjectedBlock`
354
362
  // already wrapped, then `reinjectCachedMemory` re-wrapped via
355
363
  // `injectTextBlock`, producing `<memory>\n<memory>\n...\n</memory>\n</memory>`.
356
- _setOverridesForTesting({ "memory-v2-enabled": true });
357
364
  stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
358
365
 
359
366
  const memory = makeMemory();
@@ -380,11 +387,10 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (per-turn path)",
380
387
  expect(firstBlock.text.endsWith("\n</memory>")).toBe(true);
381
388
  expect(firstBlock.text.match(/<memory>/g)?.length).toBe(1);
382
389
  expect(firstBlock.text.match(/<\/memory>/g)?.length).toBe(1);
383
- expect(firstBlock.text).toContain("### alice-vscode");
390
+ expect(firstBlock.text).toContain("# memory/concepts/alice-vscode.md");
384
391
  });
385
392
 
386
- test("flag on + config on with empty Qdrant hits → no v2 block, v1 fallback skipped", async () => {
387
- _setOverridesForTesting({ "memory-v2-enabled": true });
393
+ test("config on with empty Qdrant hits → no v2 block, v1 fallback skipped", async () => {
388
394
  // No `stageTurn` call — every channel returns `{ points: [] }` so the
389
395
  // candidate set is empty and `injectMemoryV2Block` returns block=null.
390
396
  const memory = makeMemory();
@@ -404,12 +410,11 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (per-turn path)",
404
410
  });
405
411
 
406
412
  describe("ConversationGraphMemory.prepareMemory — v2 routing (context-load path)", () => {
407
- test("flag on + config on → v2 fires with mode=context-load", async () => {
408
- _setOverridesForTesting({ "memory-v2-enabled": true });
413
+ test("config on → v2 fires with mode=context-load", async () => {
409
414
  stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
410
415
 
411
416
  // Fresh memory → initialized=false → runContextLoad branch.
412
- const memory = new ConversationGraphMemory("scope-1", "conv-test-cl");
417
+ const memory = new ConversationGraphMemory("conv-test-cl");
413
418
  const config = makeConfig(true);
414
419
  const messages = makeMessages("first message of the conversation here");
415
420
 
@@ -422,7 +427,9 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (context-load pat
422
427
 
423
428
  expect(result.mode).toBe("context-load");
424
429
  expect(result.injectedBlockText).not.toBeNull();
425
- expect(result.injectedBlockText).toContain("### alice-vscode");
430
+ expect(result.injectedBlockText).toContain(
431
+ "# memory/concepts/alice-vscode.md",
432
+ );
426
433
  // injectedBlockText is the unwrapped inner content; the wrapper is
427
434
  // applied at injection time on the run message.
428
435
  expect(result.injectedBlockText).not.toContain("<memory>");
@@ -430,14 +437,16 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (context-load pat
430
437
  const firstBlock = lastMsg?.content[0];
431
438
  if (firstBlock?.type !== "text") throw new Error("unexpected block type");
432
439
  expect(firstBlock.text.match(/<memory>/g)?.length).toBe(1);
440
+
441
+ // v1 retrieval is fully bypassed when v2 is enabled.
442
+ expect(loadContextMemoryMock).not.toHaveBeenCalled();
433
443
  });
434
444
 
435
- test("flag off → v2 not run on first turn either", async () => {
436
- _setOverridesForTesting({ "memory-v2-enabled": false });
445
+ test("config off → v2 not run on first turn either", async () => {
437
446
  stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
438
447
 
439
- const memory = new ConversationGraphMemory("scope-1", "conv-test-cl-off");
440
- const config = makeConfig(true);
448
+ const memory = new ConversationGraphMemory("conv-test-cl-off");
449
+ const config = makeConfig(false);
441
450
  const messages = makeMessages("first message of the conversation here");
442
451
 
443
452
  const result = await memory.prepareMemory(
@@ -451,3 +460,48 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (context-load pat
451
460
  expect(result.injectedBlockText).toBeNull();
452
461
  });
453
462
  });
463
+
464
+ describe("ConversationGraphMemory.onCompacted — v2 activation eviction", () => {
465
+ test("clears everInjected so a previously-injected slug can re-attach", async () => {
466
+ // Without this wiring, `selectInjections` keeps subtracting the slug from
467
+ // every per-turn delta even though compaction discarded the cached
468
+ // `<memory>` attachment that previously made it visible.
469
+ const conversationId = "conv-test-evict";
470
+ const memory = new ConversationGraphMemory(conversationId);
471
+ const config = makeConfig(true);
472
+
473
+ // Turn 1 — context-load fires (initialized=false), injecting alice-vscode.
474
+ stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
475
+ const initial = await memory.prepareMemory(
476
+ makeMessages("Tell me about Alice's editor preferences"),
477
+ config,
478
+ new AbortController().signal,
479
+ noopEvent,
480
+ );
481
+ expect(initial.injectedBlockText).toContain(
482
+ "# memory/concepts/alice-vscode.md",
483
+ );
484
+
485
+ const before = await hydrateActivationState(testDbHandle!, conversationId);
486
+ expect(before?.everInjected.map((e) => e.slug)).toContain("alice-vscode");
487
+
488
+ await memory.onCompacted(1);
489
+
490
+ const after = await hydrateActivationState(testDbHandle!, conversationId);
491
+ expect(after?.everInjected).toEqual([]);
492
+
493
+ // Turn 2 — same Qdrant relevance. With everInjected cleared the slug
494
+ // should appear again in the injection block (re-attached on the new
495
+ // user message after compaction).
496
+ stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
497
+ const next = await memory.prepareMemory(
498
+ makeMessages("And what about Alice's editor again?"),
499
+ config,
500
+ new AbortController().signal,
501
+ noopEvent,
502
+ );
503
+ expect(next.injectedBlockText).toContain(
504
+ "# memory/concepts/alice-vscode.md",
505
+ );
506
+ });
507
+ });