@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091926.ebb2d62

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 (490) hide show
  1. package/ARCHITECTURE.md +6 -6
  2. package/bun.lock +2 -2
  3. package/docs/activation-funnel-telemetry.md +310 -0
  4. package/examples/plugins/echo/README.md +61 -60
  5. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  6. package/examples/plugins/echo/hooks/stop.ts +16 -0
  7. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  8. package/examples/plugins/echo/package.json +1 -2
  9. package/examples/plugins/echo/src/emit.ts +19 -0
  10. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  11. package/openapi.yaml +273 -121
  12. package/package.json +2 -2
  13. package/scripts/sync-llm-catalog.ts +2 -0
  14. package/src/__tests__/activation-early-marking.test.ts +120 -0
  15. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  16. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  17. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  18. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  19. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  20. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  21. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  22. package/src/__tests__/agent-loop.test.ts +441 -96
  23. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  24. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  25. package/src/__tests__/anthropic-provider.test.ts +23 -8
  26. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  27. package/src/__tests__/app-control-flow.test.ts +1 -1
  28. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  29. package/src/__tests__/app-executors.test.ts +132 -0
  30. package/src/__tests__/approval-cascade.test.ts +6 -5
  31. package/src/__tests__/approval-routes-http.test.ts +4 -1
  32. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  33. package/src/__tests__/build-persisted-content.test.ts +75 -1
  34. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  35. package/src/__tests__/channel-approvals.test.ts +1 -1
  36. package/src/__tests__/compaction-circuit.test.ts +258 -0
  37. package/src/__tests__/compaction-direct.test.ts +146 -0
  38. package/src/__tests__/compaction-events.test.ts +7 -7
  39. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  40. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  41. package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
  42. package/src/__tests__/context-overflow-reducer.test.ts +265 -125
  43. package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
  44. package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
  45. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  46. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  47. package/src/__tests__/conversation-agent-loop-overflow.test.ts +158 -234
  48. package/src/__tests__/conversation-agent-loop.test.ts +342 -623
  49. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  50. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
  51. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  52. package/src/__tests__/conversation-error.test.ts +15 -1
  53. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  54. package/src/__tests__/conversation-media-retry.test.ts +1 -1
  55. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
  56. package/src/__tests__/conversation-process-callsite.test.ts +15 -15
  57. package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
  58. package/src/__tests__/conversation-queue.test.ts +10 -10
  59. package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
  60. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  61. package/src/__tests__/conversation-slash-queue.test.ts +7 -5
  62. package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
  63. package/src/__tests__/conversation-speed-override.test.ts +11 -10
  64. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  65. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
  66. package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
  67. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
  68. package/src/__tests__/conversation-undo.test.ts +2 -2
  69. package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
  70. package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
  71. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
  72. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  73. package/src/__tests__/corrected-target.test.ts +93 -0
  74. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -4
  75. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  76. package/src/__tests__/cu-unified-flow.test.ts +36 -0
  77. package/src/__tests__/db-acp-history.test.ts +101 -0
  78. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  79. package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
  80. package/src/__tests__/empty-response-hook.test.ts +1 -1
  81. package/src/__tests__/file-write-tool.test.ts +63 -0
  82. package/src/__tests__/gateway-only-guard.test.ts +17 -2
  83. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  84. package/src/__tests__/guardian-routing-invariants.test.ts +93 -4
  85. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  86. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  87. package/src/__tests__/heartbeat-service.test.ts +1 -0
  88. package/src/__tests__/history-repair-hook.test.ts +5 -1
  89. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  90. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  91. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  92. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  93. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  94. package/src/__tests__/injector-background-turn.test.ts +13 -23
  95. package/src/__tests__/injector-chain.test.ts +268 -44
  96. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  97. package/src/__tests__/injector-document-comments.test.ts +97 -114
  98. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  99. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  100. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  101. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  102. package/src/__tests__/llm-catalog-parity.test.ts +4 -0
  103. package/src/__tests__/llm-resolver.test.ts +73 -0
  104. package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
  105. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  106. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  107. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  108. package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
  109. package/src/__tests__/persist-unsendable-image.test.ts +97 -1
  110. package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
  111. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  112. package/src/__tests__/plugin-bootstrap.test.ts +91 -81
  113. package/src/__tests__/plugin-registry.test.ts +3 -49
  114. package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
  115. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  116. package/src/__tests__/plugin-types.test.ts +0 -70
  117. package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
  118. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  119. package/src/__tests__/published-app-updater.test.ts +138 -0
  120. package/src/__tests__/reaction-persistence.test.ts +1 -1
  121. package/src/__tests__/retry-thinking-adaptive-only.test.ts +245 -0
  122. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  123. package/src/__tests__/server-history-render.test.ts +39 -0
  124. package/src/__tests__/skill-feature-flags-integration.test.ts +31 -0
  125. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  126. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  127. package/src/__tests__/subagent-detail.test.ts +25 -7
  128. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  129. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  130. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  131. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  132. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  133. package/src/__tests__/title-generate-hook.test.ts +5 -1
  134. package/src/__tests__/tool-error-hook.test.ts +1 -1
  135. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  136. package/src/__tests__/tool-start-timestamp.test.ts +218 -0
  137. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  138. package/src/__tests__/web-fetch.test.ts +45 -0
  139. package/src/acp/__tests__/agent-process.test.ts +161 -0
  140. package/src/acp/__tests__/client-handler.test.ts +40 -0
  141. package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
  142. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  143. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  144. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  145. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  146. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  147. package/src/acp/agent-process.ts +61 -1
  148. package/src/acp/auto-install.test.ts +280 -0
  149. package/src/acp/auto-install.ts +232 -0
  150. package/src/acp/client-handler.ts +31 -0
  151. package/src/acp/prepare-agent-env.ts +80 -27
  152. package/src/acp/resolve-agent.test.ts +188 -28
  153. package/src/acp/resolve-agent.ts +119 -42
  154. package/src/acp/resume-hint.ts +23 -0
  155. package/src/acp/session-manager.ts +507 -73
  156. package/src/agent/compaction-circuit.ts +60 -102
  157. package/src/agent/loop.ts +403 -251
  158. package/src/api/events/tool-result.ts +9 -0
  159. package/src/api/events/tool-use-start.ts +7 -0
  160. package/src/api/responses/conversation-message.ts +72 -13
  161. package/src/api/responses/memory-v3-selection-log.ts +19 -10
  162. package/src/approvals/guardian-request-resolvers.ts +27 -1
  163. package/src/background-wake/next-wake.ts +1 -0
  164. package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
  165. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  166. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  167. package/src/cli/commands/memory-v3.ts +57 -199
  168. package/src/cli/commands/plugins.ts +43 -37
  169. package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
  170. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  171. package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
  172. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
  173. package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
  174. package/src/cli/lib/install-from-github.ts +797 -60
  175. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  176. package/src/cli/lib/plugin-details.ts +416 -0
  177. package/src/cli/lib/plugin-marketplace.ts +215 -0
  178. package/src/cli/lib/search-plugins.ts +201 -29
  179. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  180. package/src/config/acp-defaults.test.ts +10 -0
  181. package/src/config/acp-defaults.ts +9 -3
  182. package/src/config/acp-schema.ts +1 -7
  183. package/src/config/bundled-skills/acp/SKILL.md +80 -41
  184. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  185. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  186. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  187. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  188. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  189. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  190. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  191. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  192. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  193. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  194. package/src/config/bundled-tool-registry.ts +4 -0
  195. package/src/config/call-site-defaults.ts +0 -3
  196. package/src/config/feature-flag-registry.json +10 -56
  197. package/src/config/llm-resolver.ts +39 -7
  198. package/src/config/loader.ts +5 -22
  199. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
  200. package/src/config/schemas/call-site-catalog.ts +0 -21
  201. package/src/config/schemas/heartbeat.ts +9 -0
  202. package/src/config/schemas/llm.ts +17 -3
  203. package/src/config/schemas/memory-v3.ts +58 -8
  204. package/src/config/seed-inference-profiles.ts +18 -0
  205. package/src/context/compactor.ts +22 -4
  206. package/src/context/post-turn-tool-result-truncation.ts +39 -1
  207. package/src/context/strip-injections.ts +8 -2
  208. package/src/daemon/conversation-agent-loop-handlers.ts +120 -42
  209. package/src/daemon/conversation-agent-loop.ts +308 -1297
  210. package/src/daemon/conversation-error.ts +31 -4
  211. package/src/daemon/conversation-history.ts +1 -1
  212. package/src/daemon/conversation-lifecycle.ts +11 -255
  213. package/src/daemon/conversation-media-retry.ts +19 -6
  214. package/src/daemon/conversation-messaging.ts +17 -0
  215. package/src/daemon/conversation-process.ts +22 -141
  216. package/src/daemon/conversation-queue-manager.ts +8 -0
  217. package/src/daemon/conversation-registry.ts +159 -0
  218. package/src/daemon/conversation-runtime-assembly.ts +361 -393
  219. package/src/daemon/conversation-store.ts +9 -90
  220. package/src/daemon/conversation-surfaces.ts +165 -11
  221. package/src/daemon/conversation-workspace.ts +17 -0
  222. package/src/daemon/conversation.ts +444 -62
  223. package/src/daemon/disk-pressure-policy.ts +0 -1
  224. package/src/daemon/external-plugins-bootstrap.ts +36 -46
  225. package/src/daemon/handlers/config-channels.ts +11 -2
  226. package/src/daemon/handlers/conversations.ts +3 -1
  227. package/src/daemon/handlers/shared.ts +9 -1
  228. package/src/daemon/handlers/skills.ts +4 -1
  229. package/src/daemon/host-app-control-proxy.ts +72 -57
  230. package/src/daemon/host-proxy-preactivation.ts +1 -3
  231. package/src/daemon/lifecycle.ts +21 -7
  232. package/src/daemon/persist-unsendable-image.ts +62 -25
  233. package/src/daemon/process-message.ts +1 -1
  234. package/src/daemon/server.ts +2 -0
  235. package/src/daemon/tool-side-effects.ts +15 -0
  236. package/src/daemon/wake-conversation-ops.ts +269 -0
  237. package/src/embedded/plugin-api.ts +2 -2
  238. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  239. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  240. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  241. package/src/heartbeat/heartbeat-service.ts +26 -0
  242. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  243. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  244. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  245. package/src/ipc/skill-routes/memory.ts +4 -2
  246. package/src/memory/__tests__/activation-session-store.test.ts +41 -0
  247. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  248. package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
  249. package/src/memory/activation-session-store.ts +43 -0
  250. package/src/memory/conversation-crud.ts +29 -19
  251. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  252. package/src/memory/db-init.ts +6 -0
  253. package/src/memory/embedding-backend.ts +5 -22
  254. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  255. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  256. package/src/memory/jobs-worker.ts +25 -1
  257. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  258. package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
  259. package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
  260. package/src/memory/migrations/index.ts +3 -0
  261. package/src/memory/onboarding-events-store.ts +66 -18
  262. package/src/memory/schema/acp.ts +4 -0
  263. package/src/memory/schema/infrastructure.ts +13 -0
  264. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
  265. package/src/memory/v2/__tests__/page-store.test.ts +22 -0
  266. package/src/memory/v2/consolidation-job.ts +2 -63
  267. package/src/memory/v2/types.ts +5 -0
  268. package/src/messaging/providers/telegram-bot/api.ts +14 -5
  269. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  270. package/src/notifications/access-request-copy.ts +298 -0
  271. package/src/notifications/adapters/slack.ts +3 -3
  272. package/src/notifications/adapters/telegram.ts +9 -2
  273. package/src/notifications/copy-composer.ts +49 -267
  274. package/src/notifications/decision-engine.ts +16 -35
  275. package/src/notifications/home-feed-side-effect.ts +1 -6
  276. package/src/plugin-api/constants.ts +4 -0
  277. package/src/plugin-api/index.ts +40 -11
  278. package/src/plugin-api/types.ts +108 -0
  279. package/src/plugins/defaults/compaction/compact.ts +106 -0
  280. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
  281. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  282. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  283. package/src/plugins/defaults/compaction/package.json +1 -2
  284. package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
  285. package/src/plugins/defaults/empty-response/package.json +0 -1
  286. package/src/plugins/defaults/history-repair/package.json +0 -1
  287. package/src/plugins/defaults/index.ts +156 -26
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  289. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
  290. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  291. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  292. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  293. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
  297. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
  298. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
  299. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
  300. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
  301. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
  302. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
  303. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
  304. package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
  305. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
  306. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
  307. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
  308. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
  309. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
  310. package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
  311. package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
  312. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  313. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  314. package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
  315. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
  316. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
  317. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  318. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
  319. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
  320. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
  321. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
  322. package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
  323. package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
  324. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
  325. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
  326. package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
  327. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -1
  328. package/src/plugins/defaults/title-generate/package.json +0 -1
  329. package/src/plugins/defaults/tool-error/package.json +0 -1
  330. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  331. package/src/plugins/external-api.ts +12 -2
  332. package/src/plugins/pipeline.ts +6 -293
  333. package/src/plugins/registry.ts +9 -37
  334. package/src/plugins/types.ts +76 -381
  335. package/src/plugins/user-loader.ts +30 -127
  336. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  337. package/src/prompts/system-prompt.ts +61 -10
  338. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
  339. package/src/providers/anthropic/client.ts +9 -10
  340. package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
  341. package/src/providers/inference/logit-bias.ts +55 -0
  342. package/src/providers/model-catalog.ts +57 -0
  343. package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
  344. package/src/providers/openai/chat-completions-provider.ts +44 -1
  345. package/src/providers/retry.ts +57 -7
  346. package/src/providers/types.ts +6 -0
  347. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  348. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  349. package/src/runtime/agent-wake.ts +108 -209
  350. package/src/runtime/assistant-event-hub.ts +1 -1
  351. package/src/runtime/channel-approvals.ts +1 -1
  352. package/src/runtime/interactive-ui.ts +1 -1
  353. package/src/runtime/routes/__tests__/acp-routes.test.ts +466 -55
  354. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  355. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  356. package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
  357. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  358. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  359. package/src/runtime/routes/acp-routes.test.ts +185 -32
  360. package/src/runtime/routes/acp-routes.ts +328 -30
  361. package/src/runtime/routes/app-management-routes.ts +3 -0
  362. package/src/runtime/routes/approval-routes.ts +1 -1
  363. package/src/runtime/routes/browser-routes.ts +1 -1
  364. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  365. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  366. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  367. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  368. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  369. package/src/runtime/routes/conversation-routes.ts +30 -2
  370. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  371. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  372. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  373. package/src/runtime/routes/host-cu-routes.ts +1 -1
  374. package/src/runtime/routes/identity-routes.ts +18 -3
  375. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  376. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  377. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  378. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  379. package/src/runtime/routes/memory-v3-routes.ts +65 -305
  380. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
  381. package/src/runtime/routes/playground/helpers.ts +1 -1
  382. package/src/runtime/routes/plugins-routes.ts +337 -35
  383. package/src/runtime/routes/stt-routes.ts +45 -12
  384. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  385. package/src/runtime/routes/work-items-routes.ts +2 -4
  386. package/src/runtime/routes/workspace-routes.ts +50 -15
  387. package/src/runtime/services/conversation-serializer.ts +1 -1
  388. package/src/runtime/verification-outbound-actions.ts +147 -2
  389. package/src/runtime/verification-templates.ts +29 -3
  390. package/src/services/published-app-updater.ts +30 -8
  391. package/src/signals/cancel.ts +2 -4
  392. package/src/subagent/manager.ts +21 -5
  393. package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
  394. package/src/telemetry/activation-funnel.ts +167 -0
  395. package/src/telemetry/types.ts +13 -0
  396. package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
  397. package/src/telemetry/usage-telemetry-reporter.ts +26 -1
  398. package/src/tools/acp/context.ts +20 -0
  399. package/src/tools/acp/list-agents.test.ts +9 -19
  400. package/src/tools/acp/list-agents.ts +3 -15
  401. package/src/tools/acp/spawn.test.ts +173 -202
  402. package/src/tools/acp/spawn.ts +37 -172
  403. package/src/tools/acp/steer.test.ts +105 -8
  404. package/src/tools/acp/steer.ts +48 -17
  405. package/src/tools/apps/executors.ts +166 -50
  406. package/src/tools/browser/browser-execution.ts +12 -2
  407. package/src/tools/filesystem/write.ts +34 -0
  408. package/src/tools/network/web-fetch.ts +65 -24
  409. package/src/tools/skills/load.ts +1 -1
  410. package/src/tools/subagent/spawn.ts +2 -4
  411. package/src/tools/ui-surface/definitions.ts +32 -5
  412. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  413. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  414. package/docs/plugins.md +0 -832
  415. package/examples/plugins/echo/register.ts +0 -143
  416. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  417. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  418. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  419. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  420. package/src/__tests__/pipeline-runner.test.ts +0 -554
  421. package/src/daemon/wake-target-adapter.ts +0 -253
  422. package/src/memory/v3/__tests__/assign.test.ts +0 -242
  423. package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
  424. package/src/memory/v3/__tests__/core.test.ts +0 -39
  425. package/src/memory/v3/__tests__/health.test.ts +0 -219
  426. package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
  427. package/src/memory/v3/__tests__/needle.test.ts +0 -107
  428. package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
  429. package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
  430. package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
  431. package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
  432. package/src/memory/v3/__tests__/router.test.ts +0 -332
  433. package/src/memory/v3/__tests__/selector.test.ts +0 -470
  434. package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
  435. package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
  436. package/src/memory/v3/__tests__/tree.test.ts +0 -192
  437. package/src/memory/v3/assign.ts +0 -268
  438. package/src/memory/v3/capabilities.ts +0 -124
  439. package/src/memory/v3/core.ts +0 -26
  440. package/src/memory/v3/health.ts +0 -0
  441. package/src/memory/v3/maintain-job.ts +0 -314
  442. package/src/memory/v3/needle.ts +0 -115
  443. package/src/memory/v3/orchestrate.ts +0 -126
  444. package/src/memory/v3/page-content.ts +0 -34
  445. package/src/memory/v3/provider-blocks.ts +0 -26
  446. package/src/memory/v3/reconcile.ts +0 -523
  447. package/src/memory/v3/router.ts +0 -190
  448. package/src/memory/v3/selection-log-store.ts +0 -84
  449. package/src/memory/v3/selector.ts +0 -226
  450. package/src/memory/v3/shadow-plugin.ts +0 -411
  451. package/src/memory/v3/snapshot.ts +0 -209
  452. package/src/memory/v3/tree.ts +0 -174
  453. package/src/memory/v3/types.ts +0 -59
  454. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  455. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  456. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  457. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  458. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  459. package/src/plugins/defaults/compaction/register.ts +0 -35
  460. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  461. package/src/plugins/defaults/empty-response/register.ts +0 -23
  462. package/src/plugins/defaults/history-repair/register.ts +0 -24
  463. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
  464. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  465. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  466. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  467. package/src/plugins/defaults/title-generate/register.ts +0 -35
  468. package/src/plugins/defaults/tool-error/register.ts +0 -23
  469. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  470. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  471. package/src/proactive-artifact/decision.test.ts +0 -226
  472. package/src/proactive-artifact/decision.ts +0 -165
  473. package/src/proactive-artifact/index.ts +0 -7
  474. package/src/proactive-artifact/job.test.ts +0 -962
  475. package/src/proactive-artifact/job.ts +0 -372
  476. package/src/proactive-artifact/message-copy.ts +0 -58
  477. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  478. package/src/proactive-artifact/trigger-state.ts +0 -123
  479. package/src/util/map-limit.ts +0 -27
  480. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  481. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  482. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  483. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  484. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  485. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  486. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  487. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  488. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  489. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  490. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
@@ -0,0 +1,592 @@
1
+ /**
2
+ * Tests for `shadow-plugin.ts` (section-lane pipeline).
3
+ *
4
+ * The v3 plugin is flag-gated. These tests assert:
5
+ * - both flags OFF → orchestrate is never called and no DB rows are written;
6
+ * - either flag ON → orchestrate runs and selection rows land in
7
+ * `memory_v3_selections` with the new lane source tags;
8
+ * - shadow-only (live off) → the injector returns `null` (never mutates the
9
+ * turn) but still logs;
10
+ * - live on → the injector returns the rendered `<memory>` block;
11
+ * - an empty selection under live → `null`;
12
+ * - lazy-init runs the lane builders only once across multiple turns, and
13
+ * `invalidateLanes` forces exactly one rebuild;
14
+ * - `initLanes` feeds synthetic capability pages (skills / CLI commands) into
15
+ * the section index via `renderCapabilityContent`, so the needle lane ranks
16
+ * them like any other page (they are no longer always-added to the pool).
17
+ *
18
+ * All heavy dependencies (config, flag resolver, conversation reads, v2 page
19
+ * index + page store, the lane builders, orchestrate) are mocked BEFORE
20
+ * importing the plugin so the module observes them at load time. A real
21
+ * in-memory SQLite DB backs the write assertions via the
22
+ * `memory_v3_selections` migration.
23
+ */
24
+
25
+ import { Database } from "bun:sqlite";
26
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
27
+
28
+ import { drizzle } from "drizzle-orm/bun-sqlite";
29
+
30
+ import { migrateAddMemoryV3Selections } from "../../../../memory/migrations/268-add-memory-v3-selections.js";
31
+ import * as schema from "../../../../memory/schema.js";
32
+ import type { CandidateLane, SectionIndex, SelectionSource } from "../types.js";
33
+
34
+ // `mock.module` is process-global and, in Bun, neither `mock.restore()` nor a
35
+ // re-mock in `afterAll` reverts it for files that load LATER in the same
36
+ // `bun test src/plugins/defaults/memory-v3-shadow/` run. Sibling files import
37
+ // these same modules for real, so an unconditional stub here would leak in and
38
+ // break them. Instead each stub below DELEGATES to the real implementation
39
+ // unless this test is actively running (`shadowMockActive`, toggled in
40
+ // beforeEach/afterAll).
41
+ //
42
+ // Snapshot the real exports into plain objects NOW: a module namespace object
43
+ // is a live view, so reading the real export *after* the stub is installed
44
+ // would resolve back to the stub (infinite recursion).
45
+ const realSections = { ...(await import("../sections.js")) };
46
+ const realSectionNeedle = { ...(await import("../section-needle.js")) };
47
+ const realEdge = { ...(await import("../edge.js")) };
48
+ const realSectionDenseStore = {
49
+ ...(await import("../section-dense-store.js")),
50
+ };
51
+ const realOrchestrate = { ...(await import("../orchestrate.js")) };
52
+ const realPlatform = { ...(await import("../../../../util/platform.js")) };
53
+ const realPageStore = {
54
+ ...(await import("../../../../memory/v2/page-store.js")),
55
+ };
56
+ const realConversationCrud = {
57
+ ...(await import("../../../../memory/conversation-crud.js")),
58
+ };
59
+ const realSkillStore = {
60
+ ...(await import("../../../../memory/v2/skill-store.js")),
61
+ };
62
+ const realCliCommandStore = {
63
+ ...(await import("../../../../memory/v2/cli-command-store.js")),
64
+ };
65
+
66
+ let shadowMockActive = false;
67
+
68
+ // ─── mutable test state, read by the mocks below ────────────────────────────
69
+
70
+ let liveEnabled = false;
71
+ let shadowEnabled = false;
72
+ let messages: Array<{ role: string; content: string }> = [];
73
+
74
+ // A synthetic skill capability slug the page index carries. Its rendered
75
+ // content holds a distinctive term ("kumquat") so the real needle, built over
76
+ // the section index `initLanes` feeds, ranks it. Generic placeholder only.
77
+ const CAPABILITY_SLUG = "skills/example";
78
+ const CAPABILITY_CONTENT = "use the kumquat skill to do the thing";
79
+
80
+ // The orchestrate result the spy returns. `laneBySlug` records the lane that
81
+ // surfaced each pooled slug: page-1 → "needle", page-2 → "dense", page-3 →
82
+ // "edge"; `attributeSelections` reads it directly. `carried` is a carry-forward
83
+ // slug not re-selected this turn. `sectionBySlug` carries the matched section
84
+ // for the slugs that had one (page-1/page-2) — consumed by the live injector's
85
+ // progressive disclosure, independent of source attribution.
86
+ const orchestrateSpy = mock(async () => ({
87
+ currentSelections: [
88
+ { slug: "page-1", pinned: true },
89
+ { slug: "page-2", pinned: false },
90
+ { slug: "page-3", pinned: false },
91
+ ],
92
+ workingSetUnion: new Set<string>(["page-1", "page-2", "page-3"]),
93
+ finalInjection: ["page-1", "page-2", "page-3", "carried"],
94
+ sectionBySlug: new Map([
95
+ ["page-1", { article: "page-1", title: "", text: "x", ordinal: 0 }],
96
+ ["page-2", { article: "page-2", title: "", text: "y", ordinal: 0 }],
97
+ ]),
98
+ laneBySlug: new Map<string, CandidateLane>([
99
+ ["page-1", "needle"],
100
+ ["page-2", "dense"],
101
+ ["page-3", "edge"],
102
+ ]),
103
+ }));
104
+
105
+ let sectionBuilds = 0;
106
+ let needleBuilds = 0;
107
+ let edgeBuilds = 0;
108
+ let ensureCollectionCalls = 0;
109
+ let ensureCollectionThrows = false;
110
+
111
+ // The `pageBody` resolver `initLanes` passes to `buildSectionIndex` (its second
112
+ // arg), captured by the stub below so a test can drive it directly: a capability
113
+ // slug must resolve to its rendered capability content, an on-disk slug to its
114
+ // page body.
115
+ let capturedPageBody: ((slug: string) => Promise<string>) | null = null;
116
+
117
+ // Shared in-memory DB so writes are observable from the test.
118
+ let testSqlite: Database;
119
+ let testDb = makeDb();
120
+ function makeDb() {
121
+ testSqlite = new Database(":memory:");
122
+ testSqlite.exec("PRAGMA journal_mode=WAL");
123
+ const db = drizzle(testSqlite, { schema });
124
+ migrateAddMemoryV3Selections(db);
125
+ return db;
126
+ }
127
+
128
+ const FAKE_SECTION_INDEX: SectionIndex = {
129
+ sections: [],
130
+ byArticle: new Map(),
131
+ };
132
+
133
+ // ─── module mocks (installed before the plugin import) ──────────────────────
134
+
135
+ mock.module("../../../../config/assistant-feature-flags.js", () => ({
136
+ isAssistantFeatureFlagEnabled: (key: string) =>
137
+ key === "memory-v3-live"
138
+ ? liveEnabled
139
+ : key === "memory-v3-shadow"
140
+ ? shadowEnabled
141
+ : false,
142
+ }));
143
+
144
+ mock.module("../../../../config/loader.js", () => ({
145
+ getConfig: () => ({
146
+ memory: {
147
+ v3: {
148
+ workingSet: { maxPages: 150, evictWindow: 5 },
149
+ needleK: 100,
150
+ denseK: 100,
151
+ edge: { hubDegree: 30, seedCount: 18, perSeed: 6, cap: 45 },
152
+ },
153
+ qdrant: { vectorSize: 8, onDisk: false },
154
+ },
155
+ }),
156
+ }));
157
+
158
+ // Spread the real module so every export the live path transitively imports
159
+ // stays present; only `getMessages` is overridden.
160
+ mock.module("../../../../memory/conversation-crud.js", () => ({
161
+ ...realConversationCrud,
162
+ getMessages: () => messages.map((m, i) => ({ ...m, id: `m${i}` })),
163
+ }));
164
+
165
+ mock.module("../../../../memory/db-connection.js", () => ({
166
+ getDb: () => testDb,
167
+ getSqliteFrom: () => testSqlite,
168
+ }));
169
+
170
+ mock.module("../../../../memory/v2/page-index.js", () => ({
171
+ getPageIndex: async () => ({
172
+ entries: [
173
+ {
174
+ slug: "page-1",
175
+ id: 1,
176
+ summary: "",
177
+ edges: [],
178
+ leaves: [],
179
+ modifiedAt: 0,
180
+ },
181
+ {
182
+ slug: "page-2",
183
+ id: 2,
184
+ summary: "",
185
+ edges: [],
186
+ leaves: [],
187
+ modifiedAt: 0,
188
+ },
189
+ // A synthetic capability row — same shape the v2 page index appends for
190
+ // skills/CLI commands. `initLanes` must route it through the capability
191
+ // resolver, not an on-disk read.
192
+ {
193
+ slug: CAPABILITY_SLUG,
194
+ id: 3,
195
+ summary: "",
196
+ edges: [],
197
+ leaves: [],
198
+ modifiedAt: 0,
199
+ },
200
+ ],
201
+ bySlug: new Map(),
202
+ }),
203
+ }));
204
+
205
+ // `pageContent` (live mode) reads the full page via `readPage`/`renderPageContent`.
206
+ mock.module("../../../../memory/v2/page-store.js", () => ({
207
+ ...realPageStore,
208
+ readPage: async (workspaceDir: string, slug: string) =>
209
+ shadowMockActive
210
+ ? { slug, frontmatter: {}, body: `body for ${slug}` }
211
+ : realPageStore.readPage(workspaceDir, slug),
212
+ renderPageContent: (page: { slug: string; body: string }) =>
213
+ shadowMockActive
214
+ ? page.body
215
+ : realPageStore.renderPageContent(
216
+ page as Parameters<typeof realPageStore.renderPageContent>[0],
217
+ ),
218
+ }));
219
+
220
+ mock.module("../../../../util/platform.js", () => ({
221
+ ...realPlatform,
222
+ getWorkspaceDir: () =>
223
+ shadowMockActive
224
+ ? "/tmp/shadow-test-workspace"
225
+ : realPlatform.getWorkspaceDir(),
226
+ }));
227
+
228
+ // Capability stores: `renderCapabilityContent` (reached from `initLanes`' pageBody
229
+ // and from the live injector) resolves synthetic slugs through these. Spread the
230
+ // real module so the prefix predicates (`isSkillSlug`/`isCliCommandSlug`) stay
231
+ // intact; override only the content lookup so the capability slug resolves.
232
+ mock.module("../../../../memory/v2/skill-store.js", () => ({
233
+ ...realSkillStore,
234
+ getSkillCapability: (idOrSlug: string) =>
235
+ shadowMockActive
236
+ ? idOrSlug === CAPABILITY_SLUG
237
+ ? { id: "example", content: CAPABILITY_CONTENT }
238
+ : null
239
+ : realSkillStore.getSkillCapability(idOrSlug),
240
+ }));
241
+
242
+ mock.module("../../../../memory/v2/cli-command-store.js", () => ({
243
+ ...realCliCommandStore,
244
+ getCliCommandCapability: (idOrSlug: string) =>
245
+ shadowMockActive
246
+ ? null
247
+ : realCliCommandStore.getCliCommandCapability(idOrSlug),
248
+ }));
249
+
250
+ mock.module("../sections.js", () => ({
251
+ ...realSections,
252
+ buildSectionIndex: async (
253
+ ...args: Parameters<typeof realSections.buildSectionIndex>
254
+ ) => {
255
+ if (!shadowMockActive) return realSections.buildSectionIndex(...args);
256
+ sectionBuilds++;
257
+ // Capture the `pageBody` resolver so a test can exercise the capability
258
+ // branch directly. Returning the FAKE index keeps the other tests cheap.
259
+ capturedPageBody = args[1];
260
+ return FAKE_SECTION_INDEX;
261
+ },
262
+ }));
263
+
264
+ mock.module("../section-needle.js", () => ({
265
+ ...realSectionNeedle,
266
+ buildSectionNeedle: (
267
+ ...args: Parameters<typeof realSectionNeedle.buildSectionNeedle>
268
+ ) => {
269
+ if (!shadowMockActive) return realSectionNeedle.buildSectionNeedle(...args);
270
+ needleBuilds++;
271
+ return { query: () => [], bestSection: () => -1 };
272
+ },
273
+ }));
274
+
275
+ mock.module("../edge.js", () => ({
276
+ ...realEdge,
277
+ buildEdgeGraph: async (
278
+ ...args: Parameters<typeof realEdge.buildEdgeGraph>
279
+ ) => {
280
+ if (!shadowMockActive) return realEdge.buildEdgeGraph(...args);
281
+ edgeBuilds++;
282
+ return { adjacency: new Map(), hubs: new Set(), slugs: new Set() };
283
+ },
284
+ }));
285
+
286
+ mock.module("../section-dense-store.js", () => ({
287
+ ...realSectionDenseStore,
288
+ ensureSectionCollection: async (
289
+ ...args: Parameters<typeof realSectionDenseStore.ensureSectionCollection>
290
+ ) => {
291
+ if (!shadowMockActive) {
292
+ return realSectionDenseStore.ensureSectionCollection(...args);
293
+ }
294
+ ensureCollectionCalls++;
295
+ if (ensureCollectionThrows) throw new Error("qdrant unavailable");
296
+ },
297
+ }));
298
+
299
+ mock.module("../orchestrate.js", () => ({
300
+ ...realOrchestrate,
301
+ orchestrate: (...args: Parameters<typeof realOrchestrate.orchestrate>) =>
302
+ shadowMockActive
303
+ ? orchestrateSpy(...(args as unknown as []))
304
+ : realOrchestrate.orchestrate(...args),
305
+ }));
306
+
307
+ // Import AFTER mocks so the plugin binds to them.
308
+ const { runShadowObservation, resetShadowLanesForTests, invalidateLanes } =
309
+ await import("../shadow-plugin.js");
310
+ const { memoryV3Injector } = await import("../injector.js");
311
+
312
+ afterAll(() => {
313
+ shadowMockActive = false;
314
+ });
315
+
316
+ function readRows() {
317
+ return testSqlite
318
+ .query(
319
+ `SELECT slug, source, pinned FROM memory_v3_selections ORDER BY slug`,
320
+ )
321
+ .all() as Array<{ slug: string; source: SelectionSource; pinned: number }>;
322
+ }
323
+
324
+ beforeEach(() => {
325
+ shadowMockActive = true;
326
+ liveEnabled = false;
327
+ shadowEnabled = false;
328
+ messages = [
329
+ {
330
+ role: "user",
331
+ content: JSON.stringify([{ type: "text", text: "hello world" }]),
332
+ },
333
+ ];
334
+ orchestrateSpy.mockClear();
335
+ sectionBuilds = 0;
336
+ needleBuilds = 0;
337
+ edgeBuilds = 0;
338
+ ensureCollectionCalls = 0;
339
+ ensureCollectionThrows = false;
340
+ capturedPageBody = null;
341
+ testDb = makeDb();
342
+ resetShadowLanesForTests();
343
+ });
344
+
345
+ /** Invoke the memory-v3 injector's `produce()` for a turn. */
346
+ function produce(conversationId: string, turnIndex: number) {
347
+ return memoryV3Injector.produce({
348
+ requestId: "r1",
349
+ conversationId,
350
+ turnIndex,
351
+ trust: {} as never,
352
+ });
353
+ }
354
+
355
+ describe("memory-v3 shadow plugin", () => {
356
+ test("shadow flag OFF → orchestrate not called, no DB writes", async () => {
357
+ shadowEnabled = false;
358
+ await runShadowObservation("conv-1", 0);
359
+ expect(orchestrateSpy).not.toHaveBeenCalled();
360
+ expect(sectionBuilds).toBe(0);
361
+ expect(readRows()).toHaveLength(0);
362
+ });
363
+
364
+ test("shadow flag ON → orchestrate runs and rows are written with per-lane sources", async () => {
365
+ shadowEnabled = true;
366
+ await runShadowObservation("conv-1", 2);
367
+
368
+ expect(orchestrateSpy).toHaveBeenCalledTimes(1);
369
+ const rows = readRows();
370
+ // Each selection is attributed to the lane that surfaced it
371
+ // (`result.laneBySlug`), not re-derived from section presence — so the
372
+ // dense-only page-2 logs "dense", not "needle".
373
+ expect(rows).toEqual([
374
+ { slug: "carried", source: "carry-forward", pinned: 0 },
375
+ // page-1 was surfaced by the needle lane → "needle", pinned.
376
+ { slug: "page-1", source: "needle", pinned: 1 },
377
+ // page-2 was surfaced by the dense lane → "dense".
378
+ { slug: "page-2", source: "dense", pinned: 0 },
379
+ // page-3 was surfaced by the edge lane → "edge".
380
+ { slug: "page-3", source: "edge", pinned: 0 },
381
+ ]);
382
+ });
383
+
384
+ test("the turn passed to orchestrate carries the latest user message", async () => {
385
+ shadowEnabled = true;
386
+ await runShadowObservation("conv-1", 0);
387
+ const turn = (
388
+ orchestrateSpy.mock.calls as unknown as unknown[][]
389
+ )[0]![0] as {
390
+ conversationId: string;
391
+ turnNumber: number;
392
+ currentMessage: string;
393
+ };
394
+ expect(turn.conversationId).toBe("conv-1");
395
+ expect(turn.turnNumber).toBe(0);
396
+ expect(turn.currentMessage).toBe("hello world");
397
+ });
398
+
399
+ test("orchestrate receives the lane deps", async () => {
400
+ shadowEnabled = true;
401
+ await runShadowObservation("conv-1", 0);
402
+ const deps = (
403
+ orchestrateSpy.mock.calls as unknown as unknown[][]
404
+ )[0]![1] as {
405
+ sectionIndex?: unknown;
406
+ needle?: unknown;
407
+ edgeGraph?: unknown;
408
+ workingSet?: unknown;
409
+ };
410
+ expect(deps.sectionIndex).toBeDefined();
411
+ expect(deps.needle).toBeDefined();
412
+ expect(deps.edgeGraph).toBeDefined();
413
+ expect(deps.workingSet).toBeDefined();
414
+ });
415
+
416
+ test("both flags OFF → produce returns null, no orchestrate, no writes", async () => {
417
+ liveEnabled = false;
418
+ shadowEnabled = false;
419
+ const block = await produce("conv-1", 0);
420
+ expect(block).toBeNull();
421
+ expect(orchestrateSpy).not.toHaveBeenCalled();
422
+ expect(readRows()).toHaveLength(0);
423
+ });
424
+
425
+ test("shadow-only (live off) → produce returns null but still logs", async () => {
426
+ liveEnabled = false;
427
+ shadowEnabled = true;
428
+ const block = await produce("conv-1", 0);
429
+ expect(block).toBeNull();
430
+ expect(orchestrateSpy).toHaveBeenCalledTimes(1);
431
+ expect(readRows().length).toBeGreaterThan(0);
432
+ });
433
+
434
+ test("live on → produce returns the rendered <memory> block and logs", async () => {
435
+ liveEnabled = true;
436
+ shadowEnabled = false;
437
+ const block = await produce("conv-1", 0);
438
+ expect(block).not.toBeNull();
439
+ expect(block!.placement).toBe("after-memory-prefix");
440
+ expect(block!.text.startsWith("<memory>\n")).toBe(true);
441
+ expect(block!.text.endsWith("\n</memory>")).toBe(true);
442
+ // Progressive disclosure: a slug with a matched section renders that
443
+ // section's text (page-1 → "x"), NOT the full page body.
444
+ expect(block!.text).toContain("# memory/concepts/page-1.md\nx");
445
+ expect(block!.text).not.toContain("body for page-1");
446
+ // A slug with no matched section (carry-forward) falls back to the full body.
447
+ expect(block!.text).toContain("body for carried");
448
+ // Selections are still logged in live mode.
449
+ expect(readRows().length).toBeGreaterThan(0);
450
+ });
451
+
452
+ test("live on but empty selection → produce returns null", async () => {
453
+ liveEnabled = true;
454
+ shadowEnabled = false;
455
+ orchestrateSpy.mockImplementationOnce(async () => ({
456
+ currentSelections: [],
457
+ workingSetUnion: new Set<string>(),
458
+ finalInjection: [],
459
+ sectionBySlug: new Map(),
460
+ laneBySlug: new Map(),
461
+ }));
462
+ const block = await produce("conv-1", 0);
463
+ expect(block).toBeNull();
464
+ expect(orchestrateSpy).toHaveBeenCalledTimes(1);
465
+ expect(readRows()).toHaveLength(0);
466
+ });
467
+
468
+ test("lazy-init runs the lane builders only once across turns", async () => {
469
+ shadowEnabled = true;
470
+ await runShadowObservation("conv-1", 0);
471
+ await runShadowObservation("conv-1", 1);
472
+ await runShadowObservation("conv-1", 2);
473
+ expect(sectionBuilds).toBe(1);
474
+ expect(needleBuilds).toBe(1);
475
+ expect(edgeBuilds).toBe(1);
476
+ expect(ensureCollectionCalls).toBe(1);
477
+ expect(orchestrateSpy).toHaveBeenCalledTimes(3);
478
+ });
479
+
480
+ test("a Qdrant section-collection failure does not disable the in-memory lanes", async () => {
481
+ shadowEnabled = true;
482
+ ensureCollectionThrows = true;
483
+ // initLanes still builds the needle + edge lanes and orchestrate runs — a
484
+ // Qdrant outage degrades only the dense lane, it does not take down all of v3
485
+ // (and does not poison the memoized lanes by rejecting init).
486
+ await runShadowObservation("conv-1", 0);
487
+ expect(needleBuilds).toBe(1);
488
+ expect(edgeBuilds).toBe(1);
489
+ expect(ensureCollectionCalls).toBe(1);
490
+ expect(orchestrateSpy).toHaveBeenCalledTimes(1);
491
+ });
492
+
493
+ test("invalidateLanes forces a one-time rebuild on the next turn", async () => {
494
+ shadowEnabled = true;
495
+ await runShadowObservation("conv-1", 0);
496
+ await runShadowObservation("conv-1", 1);
497
+ expect(sectionBuilds).toBe(1);
498
+ expect(needleBuilds).toBe(1);
499
+
500
+ invalidateLanes();
501
+
502
+ await runShadowObservation("conv-1", 2);
503
+ expect(sectionBuilds).toBe(2);
504
+ expect(needleBuilds).toBe(2);
505
+ expect(edgeBuilds).toBe(2);
506
+
507
+ // ...and the rebuild is memoized again — no further builds until the next
508
+ // invalidation.
509
+ await runShadowObservation("conv-1", 3);
510
+ expect(sectionBuilds).toBe(2);
511
+ expect(needleBuilds).toBe(2);
512
+ });
513
+
514
+ test("resetShadowLanesForTests invalidates like invalidateLanes", async () => {
515
+ shadowEnabled = true;
516
+ await runShadowObservation("conv-1", 0);
517
+ expect(sectionBuilds).toBe(1);
518
+
519
+ resetShadowLanesForTests();
520
+
521
+ await runShadowObservation("conv-1", 1);
522
+ expect(sectionBuilds).toBe(2);
523
+ });
524
+
525
+ test("concurrent first turns after invalidation share a single build", async () => {
526
+ shadowEnabled = true;
527
+ await runShadowObservation("conv-1", 0);
528
+ expect(sectionBuilds).toBe(1);
529
+
530
+ invalidateLanes();
531
+
532
+ await Promise.all([
533
+ runShadowObservation("conv-1", 1),
534
+ runShadowObservation("conv-1", 2),
535
+ ]);
536
+ expect(sectionBuilds).toBe(2);
537
+ expect(needleBuilds).toBe(2);
538
+ });
539
+
540
+ test("no user message → no orchestrate, no writes", async () => {
541
+ shadowEnabled = true;
542
+ messages = [
543
+ {
544
+ role: "assistant",
545
+ content: JSON.stringify([{ type: "text", text: "hi" }]),
546
+ },
547
+ ];
548
+ await runShadowObservation("conv-1", 0);
549
+ expect(orchestrateSpy).not.toHaveBeenCalled();
550
+ expect(readRows()).toHaveLength(0);
551
+ });
552
+
553
+ describe("initLanes feeds synthetic capability pages into the section index", () => {
554
+ test("the pageBody resolver returns capability content for a synthetic slug and disk body otherwise", async () => {
555
+ shadowEnabled = true;
556
+ await runShadowObservation("conv-1", 0);
557
+
558
+ // `initLanes` ran the real pageBody-building closure and handed it to
559
+ // `buildSectionIndex`; the stub captured it.
560
+ expect(capturedPageBody).not.toBeNull();
561
+ const pageBody = capturedPageBody!;
562
+
563
+ // A capability slug resolves to its rendered capability content (NOT an
564
+ // on-disk read, which would miss), so the needle can rank it.
565
+ const capBody = await pageBody(CAPABILITY_SLUG);
566
+ expect(capBody).toContain("kumquat");
567
+ // On-disk slugs still resolve to their page body.
568
+ expect(await pageBody("page-1")).toBe("body for page-1");
569
+ });
570
+
571
+ test("a synthetic slug's capability content yields a section the needle ranks", async () => {
572
+ shadowEnabled = true;
573
+ await runShadowObservation("conv-1", 0);
574
+ const pageBody = capturedPageBody!;
575
+
576
+ // Feed the captured resolver through the REAL section builder + needle:
577
+ // the capability slug yields ≥1 section and is ranked on a term from its
578
+ // capability content — the path that makes synthetic pages lane-rankable.
579
+ const index = await realSections.buildSectionIndex(
580
+ [CAPABILITY_SLUG],
581
+ pageBody,
582
+ );
583
+ expect(index.byArticle.get(CAPABILITY_SLUG)?.length ?? 0).toBeGreaterThan(
584
+ 0,
585
+ );
586
+
587
+ const needle = realSectionNeedle.buildSectionNeedle(index);
588
+ const hits = needle.query("kumquat", 5);
589
+ expect(hits.map((h) => h.article)).toContain(CAPABILITY_SLUG);
590
+ });
591
+ });
592
+ });
@@ -1,35 +1,15 @@
1
1
  import { expect, test } from "bun:test";
2
2
 
3
3
  import type {
4
- LeafFrontmatter,
5
- LeafNode,
6
- LeafPath,
7
- LeafTree,
4
+ MemoryRoutingTurn,
8
5
  SelectionSource,
9
6
  Slug,
10
- TurnContext,
11
7
  WorkingSetEntry,
12
8
  } from "../types.js";
13
9
 
14
10
  test("v3 core types instantiate", () => {
15
- const path: LeafPath = "domain/topic/leaf";
16
11
  const slug: Slug = "page-123";
17
12
 
18
- const frontmatter: LeafFrontmatter = { path, in_core: true };
19
-
20
- const node: LeafNode = {
21
- path,
22
- frontmatter,
23
- description: "an example leaf node",
24
- members: [slug],
25
- domain: "example",
26
- };
27
-
28
- const tree: LeafTree = {
29
- leaves: new Map<LeafPath, LeafNode>([[path, node]]),
30
- byPage: new Map<Slug, LeafPath[]>([[slug, [path]]]),
31
- };
32
-
33
13
  const entry: WorkingSetEntry = {
34
14
  slug,
35
15
  selectedAtTurn: 1,
@@ -37,7 +17,7 @@ test("v3 core types instantiate", () => {
37
17
  lastSeenTurn: 2,
38
18
  };
39
19
 
40
- const turnContext: TurnContext = {
20
+ const turnContext: MemoryRoutingTurn = {
41
21
  conversationId: "conv-xyz",
42
22
  turnNumber: 3,
43
23
  currentMessage: "hello",
@@ -46,8 +26,6 @@ test("v3 core types instantiate", () => {
46
26
 
47
27
  const source: SelectionSource = "carry-forward";
48
28
 
49
- expect(tree.leaves.get(path)).toBe(node);
50
- expect(tree.byPage.get(slug)).toEqual([path]);
51
29
  expect(entry.slug).toBe(slug);
52
30
  expect(turnContext.turnNumber).toBe(3);
53
31
  expect(source).toBe("carry-forward");