@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091853.fbaa2ae

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 (475) 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 +263 -121
  12. package/package.json +2 -2
  13. package/src/__tests__/activation-early-marking.test.ts +120 -0
  14. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  15. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  16. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  17. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  18. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  19. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  20. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  21. package/src/__tests__/agent-loop.test.ts +441 -96
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  23. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  24. package/src/__tests__/anthropic-provider.test.ts +23 -8
  25. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  26. package/src/__tests__/app-control-flow.test.ts +1 -1
  27. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  28. package/src/__tests__/app-executors.test.ts +132 -0
  29. package/src/__tests__/approval-cascade.test.ts +6 -5
  30. package/src/__tests__/approval-routes-http.test.ts +4 -1
  31. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  32. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  33. package/src/__tests__/channel-approvals.test.ts +1 -1
  34. package/src/__tests__/compaction-circuit.test.ts +258 -0
  35. package/src/__tests__/compaction-direct.test.ts +146 -0
  36. package/src/__tests__/compaction-events.test.ts +7 -7
  37. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  38. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  39. package/src/__tests__/context-overflow-reducer.test.ts +265 -125
  40. package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
  41. package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
  42. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  43. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  44. package/src/__tests__/conversation-agent-loop-overflow.test.ts +158 -234
  45. package/src/__tests__/conversation-agent-loop.test.ts +342 -623
  46. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  47. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
  48. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  49. package/src/__tests__/conversation-error.test.ts +15 -1
  50. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  51. package/src/__tests__/conversation-media-retry.test.ts +1 -1
  52. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
  53. package/src/__tests__/conversation-process-callsite.test.ts +15 -15
  54. package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
  55. package/src/__tests__/conversation-queue.test.ts +10 -10
  56. package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
  57. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  58. package/src/__tests__/conversation-slash-queue.test.ts +7 -5
  59. package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
  60. package/src/__tests__/conversation-speed-override.test.ts +11 -10
  61. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  62. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
  63. package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
  64. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
  65. package/src/__tests__/conversation-undo.test.ts +2 -2
  66. package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
  67. package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
  68. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
  69. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  70. package/src/__tests__/corrected-target.test.ts +93 -0
  71. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  72. package/src/__tests__/cu-unified-flow.test.ts +36 -0
  73. package/src/__tests__/db-acp-history.test.ts +101 -0
  74. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  75. package/src/__tests__/empty-response-hook.test.ts +1 -1
  76. package/src/__tests__/file-write-tool.test.ts +63 -0
  77. package/src/__tests__/gateway-only-guard.test.ts +17 -2
  78. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  79. package/src/__tests__/guardian-routing-invariants.test.ts +93 -4
  80. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  81. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  82. package/src/__tests__/heartbeat-service.test.ts +1 -0
  83. package/src/__tests__/history-repair-hook.test.ts +5 -1
  84. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  85. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  86. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  87. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  88. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  89. package/src/__tests__/injector-background-turn.test.ts +13 -23
  90. package/src/__tests__/injector-chain.test.ts +268 -44
  91. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  92. package/src/__tests__/injector-document-comments.test.ts +97 -114
  93. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  94. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  95. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  96. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  97. package/src/__tests__/llm-resolver.test.ts +73 -0
  98. package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
  99. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  100. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  101. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  102. package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
  103. package/src/__tests__/persist-unsendable-image.test.ts +97 -1
  104. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  105. package/src/__tests__/plugin-bootstrap.test.ts +91 -81
  106. package/src/__tests__/plugin-registry.test.ts +3 -49
  107. package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
  108. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  109. package/src/__tests__/plugin-types.test.ts +0 -70
  110. package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
  111. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  112. package/src/__tests__/published-app-updater.test.ts +138 -0
  113. package/src/__tests__/reaction-persistence.test.ts +1 -1
  114. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  115. package/src/__tests__/skill-feature-flags-integration.test.ts +31 -0
  116. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  117. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  118. package/src/__tests__/subagent-detail.test.ts +25 -7
  119. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  120. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  121. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  122. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  123. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  124. package/src/__tests__/title-generate-hook.test.ts +5 -1
  125. package/src/__tests__/tool-error-hook.test.ts +1 -1
  126. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  127. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  128. package/src/__tests__/web-fetch.test.ts +45 -0
  129. package/src/acp/__tests__/agent-process.test.ts +161 -0
  130. package/src/acp/__tests__/client-handler.test.ts +40 -0
  131. package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
  132. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  133. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  134. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  135. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  136. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  137. package/src/acp/agent-process.ts +61 -1
  138. package/src/acp/auto-install.test.ts +280 -0
  139. package/src/acp/auto-install.ts +232 -0
  140. package/src/acp/client-handler.ts +31 -0
  141. package/src/acp/prepare-agent-env.ts +80 -27
  142. package/src/acp/resolve-agent.test.ts +188 -28
  143. package/src/acp/resolve-agent.ts +119 -42
  144. package/src/acp/resume-hint.ts +23 -0
  145. package/src/acp/session-manager.ts +507 -73
  146. package/src/agent/compaction-circuit.ts +60 -102
  147. package/src/agent/loop.ts +403 -251
  148. package/src/api/responses/conversation-message.ts +22 -2
  149. package/src/api/responses/memory-v3-selection-log.ts +19 -10
  150. package/src/approvals/guardian-request-resolvers.ts +27 -1
  151. package/src/background-wake/next-wake.ts +1 -0
  152. package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
  153. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  154. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  155. package/src/cli/commands/memory-v3.ts +57 -199
  156. package/src/cli/commands/plugins.ts +43 -37
  157. package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
  158. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  159. package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
  160. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
  161. package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
  162. package/src/cli/lib/install-from-github.ts +797 -60
  163. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  164. package/src/cli/lib/plugin-details.ts +416 -0
  165. package/src/cli/lib/plugin-marketplace.ts +215 -0
  166. package/src/cli/lib/search-plugins.ts +201 -29
  167. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  168. package/src/config/acp-defaults.test.ts +10 -0
  169. package/src/config/acp-defaults.ts +9 -3
  170. package/src/config/acp-schema.ts +1 -7
  171. package/src/config/bundled-skills/acp/SKILL.md +80 -41
  172. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  173. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  174. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  175. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  176. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  177. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  178. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  179. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  180. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  181. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  182. package/src/config/bundled-tool-registry.ts +4 -0
  183. package/src/config/call-site-defaults.ts +0 -3
  184. package/src/config/feature-flag-registry.json +10 -16
  185. package/src/config/llm-resolver.ts +39 -7
  186. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
  187. package/src/config/schemas/call-site-catalog.ts +0 -21
  188. package/src/config/schemas/heartbeat.ts +9 -0
  189. package/src/config/schemas/llm.ts +17 -3
  190. package/src/config/schemas/memory-v3.ts +58 -8
  191. package/src/config/seed-inference-profiles.ts +18 -0
  192. package/src/context/compactor.ts +22 -4
  193. package/src/context/post-turn-tool-result-truncation.ts +39 -1
  194. package/src/context/strip-injections.ts +8 -2
  195. package/src/daemon/conversation-agent-loop-handlers.ts +18 -36
  196. package/src/daemon/conversation-agent-loop.ts +308 -1297
  197. package/src/daemon/conversation-error.ts +31 -4
  198. package/src/daemon/conversation-history.ts +1 -1
  199. package/src/daemon/conversation-lifecycle.ts +11 -255
  200. package/src/daemon/conversation-media-retry.ts +19 -6
  201. package/src/daemon/conversation-messaging.ts +17 -0
  202. package/src/daemon/conversation-process.ts +22 -141
  203. package/src/daemon/conversation-queue-manager.ts +8 -0
  204. package/src/daemon/conversation-registry.ts +159 -0
  205. package/src/daemon/conversation-runtime-assembly.ts +361 -393
  206. package/src/daemon/conversation-store.ts +9 -90
  207. package/src/daemon/conversation-surfaces.ts +165 -11
  208. package/src/daemon/conversation-workspace.ts +17 -0
  209. package/src/daemon/conversation.ts +444 -62
  210. package/src/daemon/disk-pressure-policy.ts +0 -1
  211. package/src/daemon/external-plugins-bootstrap.ts +36 -46
  212. package/src/daemon/handlers/config-channels.ts +11 -2
  213. package/src/daemon/handlers/conversations.ts +3 -1
  214. package/src/daemon/handlers/skills.ts +4 -1
  215. package/src/daemon/host-app-control-proxy.ts +72 -57
  216. package/src/daemon/host-proxy-preactivation.ts +1 -3
  217. package/src/daemon/lifecycle.ts +21 -7
  218. package/src/daemon/persist-unsendable-image.ts +62 -25
  219. package/src/daemon/process-message.ts +1 -1
  220. package/src/daemon/server.ts +2 -0
  221. package/src/daemon/tool-side-effects.ts +15 -0
  222. package/src/daemon/wake-conversation-ops.ts +269 -0
  223. package/src/embedded/plugin-api.ts +2 -2
  224. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  225. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  226. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  227. package/src/heartbeat/heartbeat-service.ts +26 -0
  228. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  229. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  230. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  231. package/src/ipc/skill-routes/memory.ts +4 -2
  232. package/src/memory/__tests__/activation-session-store.test.ts +41 -0
  233. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  234. package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
  235. package/src/memory/activation-session-store.ts +43 -0
  236. package/src/memory/conversation-crud.ts +29 -19
  237. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  238. package/src/memory/db-init.ts +6 -0
  239. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  240. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  241. package/src/memory/jobs-worker.ts +25 -1
  242. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  243. package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
  244. package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
  245. package/src/memory/migrations/index.ts +3 -0
  246. package/src/memory/onboarding-events-store.ts +66 -18
  247. package/src/memory/schema/acp.ts +4 -0
  248. package/src/memory/schema/infrastructure.ts +13 -0
  249. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
  250. package/src/memory/v2/__tests__/page-store.test.ts +22 -0
  251. package/src/memory/v2/consolidation-job.ts +2 -63
  252. package/src/memory/v2/types.ts +5 -0
  253. package/src/messaging/providers/telegram-bot/api.ts +14 -5
  254. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  255. package/src/notifications/access-request-copy.ts +298 -0
  256. package/src/notifications/adapters/slack.ts +3 -3
  257. package/src/notifications/adapters/telegram.ts +9 -2
  258. package/src/notifications/copy-composer.ts +49 -267
  259. package/src/notifications/decision-engine.ts +16 -35
  260. package/src/notifications/home-feed-side-effect.ts +1 -6
  261. package/src/plugin-api/constants.ts +4 -0
  262. package/src/plugin-api/index.ts +40 -11
  263. package/src/plugin-api/types.ts +108 -0
  264. package/src/plugins/defaults/compaction/compact.ts +106 -0
  265. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
  266. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  267. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  268. package/src/plugins/defaults/compaction/package.json +1 -2
  269. package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
  270. package/src/plugins/defaults/empty-response/package.json +0 -1
  271. package/src/plugins/defaults/history-repair/package.json +0 -1
  272. package/src/plugins/defaults/index.ts +156 -26
  273. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  274. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
  275. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  276. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  277. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  278. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  279. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
  280. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
  281. package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
  282. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
  283. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
  284. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
  285. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
  286. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
  287. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
  288. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
  289. package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
  290. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
  291. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
  292. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
  293. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
  294. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
  295. package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
  296. package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
  297. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  298. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  299. package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
  300. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
  301. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
  302. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  303. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
  304. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
  305. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
  306. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
  307. package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
  308. package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
  309. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
  310. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
  311. package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
  312. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -1
  313. package/src/plugins/defaults/title-generate/package.json +0 -1
  314. package/src/plugins/defaults/tool-error/package.json +0 -1
  315. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  316. package/src/plugins/external-api.ts +12 -2
  317. package/src/plugins/pipeline.ts +6 -293
  318. package/src/plugins/registry.ts +9 -37
  319. package/src/plugins/types.ts +76 -381
  320. package/src/plugins/user-loader.ts +30 -127
  321. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  322. package/src/prompts/system-prompt.ts +61 -10
  323. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
  324. package/src/providers/anthropic/client.ts +9 -10
  325. package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
  326. package/src/providers/inference/logit-bias.ts +55 -0
  327. package/src/providers/model-catalog.ts +34 -0
  328. package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
  329. package/src/providers/openai/chat-completions-provider.ts +44 -1
  330. package/src/providers/retry.ts +22 -0
  331. package/src/providers/types.ts +6 -0
  332. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  333. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  334. package/src/runtime/agent-wake.ts +108 -209
  335. package/src/runtime/assistant-event-hub.ts +1 -1
  336. package/src/runtime/channel-approvals.ts +1 -1
  337. package/src/runtime/interactive-ui.ts +1 -1
  338. package/src/runtime/routes/__tests__/acp-routes.test.ts +466 -55
  339. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  340. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  341. package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
  342. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  343. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  344. package/src/runtime/routes/acp-routes.test.ts +185 -32
  345. package/src/runtime/routes/acp-routes.ts +328 -30
  346. package/src/runtime/routes/app-management-routes.ts +3 -0
  347. package/src/runtime/routes/approval-routes.ts +1 -1
  348. package/src/runtime/routes/browser-routes.ts +1 -1
  349. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  350. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  351. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  352. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  353. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  354. package/src/runtime/routes/conversation-routes.ts +30 -2
  355. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  356. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  357. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  358. package/src/runtime/routes/host-cu-routes.ts +1 -1
  359. package/src/runtime/routes/identity-routes.ts +18 -3
  360. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  361. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  362. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  363. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  364. package/src/runtime/routes/memory-v3-routes.ts +65 -305
  365. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
  366. package/src/runtime/routes/playground/helpers.ts +1 -1
  367. package/src/runtime/routes/plugins-routes.ts +337 -35
  368. package/src/runtime/routes/stt-routes.ts +45 -12
  369. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  370. package/src/runtime/routes/work-items-routes.ts +2 -4
  371. package/src/runtime/routes/workspace-routes.ts +50 -15
  372. package/src/runtime/services/conversation-serializer.ts +1 -1
  373. package/src/runtime/verification-outbound-actions.ts +147 -2
  374. package/src/runtime/verification-templates.ts +29 -3
  375. package/src/services/published-app-updater.ts +30 -8
  376. package/src/signals/cancel.ts +2 -4
  377. package/src/subagent/manager.ts +21 -5
  378. package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
  379. package/src/telemetry/activation-funnel.ts +167 -0
  380. package/src/telemetry/types.ts +13 -0
  381. package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
  382. package/src/telemetry/usage-telemetry-reporter.ts +26 -1
  383. package/src/tools/acp/context.ts +20 -0
  384. package/src/tools/acp/list-agents.test.ts +9 -19
  385. package/src/tools/acp/list-agents.ts +3 -15
  386. package/src/tools/acp/spawn.test.ts +173 -202
  387. package/src/tools/acp/spawn.ts +37 -172
  388. package/src/tools/acp/steer.test.ts +105 -8
  389. package/src/tools/acp/steer.ts +48 -17
  390. package/src/tools/apps/executors.ts +166 -50
  391. package/src/tools/browser/browser-execution.ts +12 -2
  392. package/src/tools/filesystem/write.ts +34 -0
  393. package/src/tools/network/web-fetch.ts +65 -24
  394. package/src/tools/skills/load.ts +1 -1
  395. package/src/tools/subagent/spawn.ts +2 -4
  396. package/src/tools/ui-surface/definitions.ts +32 -5
  397. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  398. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  399. package/docs/plugins.md +0 -832
  400. package/examples/plugins/echo/register.ts +0 -143
  401. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  402. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  403. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  404. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  405. package/src/__tests__/pipeline-runner.test.ts +0 -554
  406. package/src/daemon/wake-target-adapter.ts +0 -253
  407. package/src/memory/v3/__tests__/assign.test.ts +0 -242
  408. package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
  409. package/src/memory/v3/__tests__/core.test.ts +0 -39
  410. package/src/memory/v3/__tests__/health.test.ts +0 -219
  411. package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
  412. package/src/memory/v3/__tests__/needle.test.ts +0 -107
  413. package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
  414. package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
  415. package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
  416. package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
  417. package/src/memory/v3/__tests__/router.test.ts +0 -332
  418. package/src/memory/v3/__tests__/selector.test.ts +0 -470
  419. package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
  420. package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
  421. package/src/memory/v3/__tests__/tree.test.ts +0 -192
  422. package/src/memory/v3/assign.ts +0 -268
  423. package/src/memory/v3/capabilities.ts +0 -124
  424. package/src/memory/v3/core.ts +0 -26
  425. package/src/memory/v3/health.ts +0 -0
  426. package/src/memory/v3/maintain-job.ts +0 -314
  427. package/src/memory/v3/needle.ts +0 -115
  428. package/src/memory/v3/orchestrate.ts +0 -126
  429. package/src/memory/v3/page-content.ts +0 -34
  430. package/src/memory/v3/provider-blocks.ts +0 -26
  431. package/src/memory/v3/reconcile.ts +0 -523
  432. package/src/memory/v3/router.ts +0 -190
  433. package/src/memory/v3/selection-log-store.ts +0 -84
  434. package/src/memory/v3/selector.ts +0 -226
  435. package/src/memory/v3/shadow-plugin.ts +0 -411
  436. package/src/memory/v3/snapshot.ts +0 -209
  437. package/src/memory/v3/tree.ts +0 -174
  438. package/src/memory/v3/types.ts +0 -59
  439. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  440. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  441. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  442. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  443. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  444. package/src/plugins/defaults/compaction/register.ts +0 -35
  445. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  446. package/src/plugins/defaults/empty-response/register.ts +0 -23
  447. package/src/plugins/defaults/history-repair/register.ts +0 -24
  448. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
  449. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  450. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  451. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  452. package/src/plugins/defaults/title-generate/register.ts +0 -35
  453. package/src/plugins/defaults/tool-error/register.ts +0 -23
  454. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  455. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  456. package/src/proactive-artifact/decision.test.ts +0 -226
  457. package/src/proactive-artifact/decision.ts +0 -165
  458. package/src/proactive-artifact/index.ts +0 -7
  459. package/src/proactive-artifact/job.test.ts +0 -962
  460. package/src/proactive-artifact/job.ts +0 -372
  461. package/src/proactive-artifact/message-copy.ts +0 -58
  462. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  463. package/src/proactive-artifact/trigger-state.ts +0 -123
  464. package/src/util/map-limit.ts +0 -27
  465. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  466. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  467. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  468. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  469. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  470. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  471. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  472. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  473. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  474. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  475. /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");