@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
@@ -20,14 +20,14 @@ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
20
20
 
21
21
  import { drizzle } from "drizzle-orm/bun-sqlite";
22
22
 
23
- import { migrateAddMemoryV3Selections } from "../../migrations/268-add-memory-v3-selections.js";
24
- import * as schema from "../../schema.js";
23
+ import { migrateAddMemoryV3Selections } from "../../../../memory/migrations/268-add-memory-v3-selections.js";
24
+ import * as schema from "../../../../memory/schema.js";
25
25
 
26
26
  const realFlags = {
27
- ...(await import("../../../config/assistant-feature-flags.js")),
27
+ ...(await import("../../../../config/assistant-feature-flags.js")),
28
28
  };
29
- const realLoader = { ...(await import("../../../config/loader.js")) };
30
- const realDb = { ...(await import("../../db-connection.js")) };
29
+ const realLoader = { ...(await import("../../../../config/loader.js")) };
30
+ const realDb = { ...(await import("../../../../memory/db-connection.js")) };
31
31
  const realPageContent = { ...(await import("../page-content.js")) };
32
32
 
33
33
  let storeMockActive = false;
@@ -64,7 +64,7 @@ function seed(
64
64
  }
65
65
  }
66
66
 
67
- mock.module("../../../config/assistant-feature-flags.js", () => ({
67
+ mock.module("../../../../config/assistant-feature-flags.js", () => ({
68
68
  ...realFlags,
69
69
  isAssistantFeatureFlagEnabled: (key: string, config: unknown) =>
70
70
  storeMockActive
@@ -81,12 +81,12 @@ mock.module("../../../config/assistant-feature-flags.js", () => ({
81
81
  ),
82
82
  }));
83
83
 
84
- mock.module("../../../config/loader.js", () => ({
84
+ mock.module("../../../../config/loader.js", () => ({
85
85
  ...realLoader,
86
86
  getConfig: () => (storeMockActive ? {} : realLoader.getConfig()),
87
87
  }));
88
88
 
89
- mock.module("../../db-connection.js", () => ({
89
+ mock.module("../../../../memory/db-connection.js", () => ({
90
90
  ...realDb,
91
91
  getDb: () => (storeMockActive ? testDb : realDb.getDb()),
92
92
  getSqliteFrom: (db: unknown) =>
@@ -97,13 +97,16 @@ mock.module("../../db-connection.js", () => ({
97
97
 
98
98
  mock.module("../page-content.js", () => ({
99
99
  ...realPageContent,
100
- renderV3PageContent: async (slug: string) =>
100
+ // The inspector store renders via `renderV3SectionContent` with an empty
101
+ // section map (sections are unavailable post-orchestration), so it falls back
102
+ // to the full page. The mock stands in for that full-page render.
103
+ renderV3SectionContent: async (slug: string) =>
101
104
  storeMockActive
102
105
  ? `body for ${slug}`
103
- : realPageContent.renderV3PageContent(slug),
106
+ : realPageContent.renderV3SectionContent(slug, undefined),
104
107
  }));
105
108
 
106
- const { getMemoryV3SelectionForInspector } =
109
+ const { getMemoryV3SelectionForInspector, summarizeSelections } =
107
110
  await import("../selection-log-store.js");
108
111
 
109
112
  beforeEach(() => {
@@ -119,7 +122,7 @@ afterAll(() => {
119
122
 
120
123
  describe("getMemoryV3SelectionForInspector", () => {
121
124
  test("returns null for a null/undefined turn", async () => {
122
- seed("conv-x", 3, [{ slug: "domain-a/page-1", source: "l1+l2" }]);
125
+ seed("conv-x", 3, [{ slug: "domain-a/page-1", source: "needle" }]);
123
126
  expect(await getMemoryV3SelectionForInspector("conv-x", null)).toBeNull();
124
127
  expect(
125
128
  await getMemoryV3SelectionForInspector("conv-x", undefined),
@@ -127,8 +130,8 @@ describe("getMemoryV3SelectionForInspector", () => {
127
130
  });
128
131
 
129
132
  test("returns the selection for the exact turn", async () => {
130
- seed("conv-2", 2, [{ slug: "domain-a/page-1", source: "l1+l2" }]);
131
- seed("conv-2", 7, [{ slug: "domain-b/page-9", source: "l1+l2" }]);
133
+ seed("conv-2", 2, [{ slug: "domain-a/page-1", source: "needle" }]);
134
+ seed("conv-2", 7, [{ slug: "domain-b/page-9", source: "needle" }]);
132
135
 
133
136
  const log = await getMemoryV3SelectionForInspector("conv-2", 2);
134
137
  expect(log?.turn).toBe(2);
@@ -136,26 +139,26 @@ describe("getMemoryV3SelectionForInspector", () => {
136
139
  });
137
140
 
138
141
  test("returns null when the turn has no v3 rows", async () => {
139
- seed("conv-1", 3, [{ slug: "domain-a/page-1", source: "l1+l2" }]);
142
+ seed("conv-1", 3, [{ slug: "domain-a/page-1", source: "needle" }]);
140
143
  expect(await getMemoryV3SelectionForInspector("conv-1", 4)).toBeNull();
141
144
  });
142
145
 
143
146
  test("does NOT fall back to another turn for an unmatched lookup", async () => {
144
147
  // Turn 5 has rows, but inspecting turn 3 (no rows) must return null —
145
148
  // never turn 5's selection, which would misattribute it to turn 3.
146
- seed("conv-3", 5, [{ slug: "domain-a/page-1", source: "l1+l2" }]);
149
+ seed("conv-3", 5, [{ slug: "domain-a/page-1", source: "needle" }]);
147
150
  expect(await getMemoryV3SelectionForInspector("conv-3", 3)).toBeNull();
148
151
  });
149
152
 
150
153
  test("maps source/pinned and renders the <memory> block", async () => {
151
154
  seed("conv-4", 1, [
152
- { slug: "domain-a/page-1", source: "core+l2", pinned: true },
155
+ { slug: "domain-a/page-1", source: "edge", pinned: true },
153
156
  { slug: "domain-b/page-2", source: "carry-forward", pinned: false },
154
157
  ]);
155
158
 
156
159
  const log = await getMemoryV3SelectionForInspector("conv-4", 1);
157
160
  expect(log?.selections).toEqual([
158
- { slug: "domain-a/page-1", source: "core+l2", pinned: true },
161
+ { slug: "domain-a/page-1", source: "edge", pinned: true },
159
162
  { slug: "domain-b/page-2", source: "carry-forward", pinned: false },
160
163
  ]);
161
164
  expect(log?.injectedText).toContain("<memory>");
@@ -164,7 +167,7 @@ describe("getMemoryV3SelectionForInspector", () => {
164
167
  });
165
168
 
166
169
  test("live/shadow reflect the flag resolver", async () => {
167
- seed("conv-5", 1, [{ slug: "domain-a/page-1", source: "l1+l2" }]);
170
+ seed("conv-5", 1, [{ slug: "domain-a/page-1", source: "needle" }]);
168
171
 
169
172
  const off = await getMemoryV3SelectionForInspector("conv-5", 1);
170
173
  expect(off?.live).toBe(false);
@@ -177,3 +180,55 @@ describe("getMemoryV3SelectionForInspector", () => {
177
180
  expect(on?.shadow).toBe(true);
178
181
  });
179
182
  });
183
+
184
+ describe("summarizeSelections", () => {
185
+ test("aggregates per-source counts, turn count, and distinct slugs", () => {
186
+ // Turn 1: a needle + an edge selection.
187
+ seed("conv-a", 1, [
188
+ { slug: "domain-a/page-1", source: "needle" },
189
+ { slug: "domain-b/page-2", source: "edge" },
190
+ ]);
191
+ // Turn 2: page-1 re-selected (needle) + page-2 carried forward.
192
+ seed("conv-a", 2, [
193
+ { slug: "domain-a/page-1", source: "needle", pinned: true },
194
+ { slug: "domain-b/page-2", source: "carry-forward" },
195
+ ]);
196
+ // A different conversation must not bleed into the aggregate.
197
+ seed("conv-b", 1, [{ slug: "domain-c/page-9", source: "dense" }]);
198
+
199
+ const summary = summarizeSelections("conv-a");
200
+ expect(summary.bySource).toEqual({
201
+ needle: 2,
202
+ dense: 0,
203
+ edge: 1,
204
+ "carry-forward": 1,
205
+ });
206
+ expect(summary.turns).toBe(2);
207
+ // page-1 and page-2 — distinct across the two turns.
208
+ expect(summary.distinctSlugs).toBe(2);
209
+ });
210
+
211
+ test("returns zeroed counts for a conversation with no rows", () => {
212
+ expect(summarizeSelections("conv-none")).toEqual({
213
+ bySource: { needle: 0, dense: 0, edge: 0, "carry-forward": 0 },
214
+ turns: 0,
215
+ distinctSlugs: 0,
216
+ });
217
+ });
218
+
219
+ test("ignores unknown/free-text historical source labels in bySource but counts the turn", () => {
220
+ // A pre-lane historical row with a legacy label (column is free-text).
221
+ seed("conv-c", 1, [
222
+ { slug: "domain-a/page-1", source: "l1+l2" },
223
+ { slug: "domain-a/page-2", source: "needle" },
224
+ ]);
225
+ const summary = summarizeSelections("conv-c");
226
+ expect(summary.bySource.needle).toBe(1);
227
+ // The unknown label is not counted in any known bucket.
228
+ const total = Object.values(summary.bySource).reduce((a, b) => a + b, 0);
229
+ expect(total).toBe(1);
230
+ // But the turn and both distinct slugs are still reflected.
231
+ expect(summary.turns).toBe(1);
232
+ expect(summary.distinctSlugs).toBe(2);
233
+ });
234
+ });
@@ -0,0 +1,446 @@
1
+ /**
2
+ * Shadow-mode end-to-end integration test for the memory-v3 section-lane
3
+ * pipeline.
4
+ *
5
+ * SCOPE / ALTITUDE. A full daemon-assembly run (plugin registry → flag read →
6
+ * runtime assembly → provider call → DB write) is too heavy and too
7
+ * mock-fragile for a unit test. Instead this composes the REAL shadow units
8
+ * with a mocked select provider, a stubbed dense lane, an in-memory selections
9
+ * DB, and synthetic fixtures, driving them over a MULTI-TURN sequence:
10
+ *
11
+ * orchestrate (needle ∪ dense ∪ edge → selectPool over a shared WorkingSet
12
+ * → carry-forward)
13
+ * → attribute selections to lane sources (the shadow plugin's REAL
14
+ * `attributeSelections`, reading `result.laneBySlug`)
15
+ * → write to `memory_v3_selections` (the shadow plugin's REAL
16
+ * `writeSelections`)
17
+ * → summarizeSelections (the offline A/B readout)
18
+ *
19
+ * This is exactly the side-effect contract shadow mode observes each turn: the
20
+ * candidate pool is the union of the lanes — synthetic capability pages are
21
+ * indexed like any other page, so they enter through the needle lane rather than
22
+ * being always-added — a SINGLE select runs per turn, the working set carries
23
+ * selections forward across turns, and each selection is logged tagged with its
24
+ * lane source. None of this changes live injection — shadow mode is
25
+ * observation-only; cutover is the `memory-v3-live` flag flip.
26
+ *
27
+ * Slugs are generic placeholders (`page-a`, `topic-x`, `page-b`, …) — this is a
28
+ * public repo.
29
+ */
30
+
31
+ import { Database } from "bun:sqlite";
32
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
33
+
34
+ import { drizzle } from "drizzle-orm/bun-sqlite";
35
+
36
+ import { migrateAddMemoryV3Selections } from "../../../../memory/migrations/268-add-memory-v3-selections.js";
37
+ import * as schema from "../../../../memory/schema.js";
38
+ import type { PageIndexEntry } from "../../../../memory/v2/page-index.js";
39
+ import type {
40
+ Message,
41
+ Provider,
42
+ ProviderResponse,
43
+ } from "../../../../providers/types.js";
44
+ import type { EdgeGraph } from "../edge.js";
45
+ import { buildEdgeGraph } from "../edge.js";
46
+ import type { OrchestrateResult } from "../orchestrate.js";
47
+ import { buildSectionNeedle } from "../section-needle.js";
48
+ import { buildSectionIndex } from "../sections.js";
49
+ import type { MemoryRoutingTurn, SectionIndex, Slug } from "../types.js";
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Module stubs installed BEFORE the orchestrator / store imports so they
53
+ // observe them at load time. Every stub DELEGATES to the real implementation
54
+ // unless this file's tests are running, so the process-global `mock.module`
55
+ // cannot leak fake behavior into sibling test files.
56
+ // ---------------------------------------------------------------------------
57
+
58
+ let mockActive = false;
59
+
60
+ let providerStub: Provider | null = null;
61
+
62
+ // Spread the real provider module so unrelated exports (e.g. `createTimeout`,
63
+ // pulled in transitively via the selection-log-store render path) stay present;
64
+ // override only the two entry points the select pool uses, and only while this
65
+ // file is active so the stub cannot leak into sibling tests.
66
+ const realProvider = {
67
+ ...(await import("../../../../providers/provider-send-message.js")),
68
+ };
69
+ mock.module("../../../../providers/provider-send-message.js", () => ({
70
+ ...realProvider,
71
+ getConfiguredProvider: async (
72
+ ...args: Parameters<typeof realProvider.getConfiguredProvider>
73
+ ) =>
74
+ mockActive ? providerStub : realProvider.getConfiguredProvider(...args),
75
+ extractToolUse: (response: ProviderResponse) =>
76
+ response.content.find((b) => b.type === "tool_use"),
77
+ }));
78
+
79
+ mock.module("../../../../util/logger.js", () => ({
80
+ getLogger: () =>
81
+ new Proxy({} as Record<string, unknown>, {
82
+ get: (_t, prop) => (prop === "child" ? () => ({}) : () => {}),
83
+ }),
84
+ }));
85
+
86
+ // The dense lane is stubbed to a controllable hit list; it DELEGATES to the
87
+ // real `denseLane` unless this file is running so dense.test.ts still exercises
88
+ // the real lane.
89
+ const realDense = { ...(await import("../dense.js")) };
90
+ let denseHits: Array<{ article: Slug; section: number }> = [];
91
+ mock.module("../dense.js", () => ({
92
+ ...realDense,
93
+ denseLane: async (...args: Parameters<typeof realDense.denseLane>) =>
94
+ mockActive ? denseHits : realDense.denseLane(...args),
95
+ }));
96
+
97
+ // In-memory selections DB. `summarizeSelections` reads via getDb/getSqliteFrom;
98
+ // the writer below writes through the same handles.
99
+ const realDb = { ...(await import("../../../../memory/db-connection.js")) };
100
+ let testSqlite: Database;
101
+ let testDb = makeDb();
102
+ function makeDb() {
103
+ testSqlite = new Database(":memory:");
104
+ testSqlite.exec("PRAGMA journal_mode=WAL");
105
+ const db = drizzle(testSqlite, { schema });
106
+ migrateAddMemoryV3Selections(db);
107
+ return db;
108
+ }
109
+ mock.module("../../../../memory/db-connection.js", () => ({
110
+ ...realDb,
111
+ getDb: () => (mockActive ? testDb : realDb.getDb()),
112
+ getSqliteFrom: (db: unknown) =>
113
+ mockActive
114
+ ? testSqlite
115
+ : realDb.getSqliteFrom(db as Parameters<typeof realDb.getSqliteFrom>[0]),
116
+ }));
117
+
118
+ const { orchestrate } = await import("../orchestrate.js");
119
+ const { WorkingSet } = await import("../working-set.js");
120
+ const { summarizeSelections } = await import("../selection-log-store.js");
121
+ // The REAL attribution + writer from the shadow plugin, imported AFTER the
122
+ // db-connection mock so `writeSelections` binds to the in-memory test DB. Using
123
+ // the production code (rather than a local copy) means this test exercises the
124
+ // real `result.laneBySlug` attribution — the only thing that can emit "dense".
125
+ const { attributeSelections, writeSelections } =
126
+ await import("../shadow-plugin.js");
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // Fixtures: a tiny corpus. `page-a` carries a curated link to `topic-x` so the
130
+ // edge lane surfaces it; each page has a distinctive section term so a query
131
+ // selects exactly that page via the needle.
132
+ // ---------------------------------------------------------------------------
133
+
134
+ const PAGES: Record<Slug, string> = {
135
+ "page-a": "lead a\n## Body\napple content for page a",
136
+ "page-b": "lead b\n## Body\nbanana content for page b",
137
+ "topic-x": "lead x\n## Body\ncherry content for topic x",
138
+ };
139
+
140
+ const RAW: Record<Slug, string> = {
141
+ "page-a": `---\nlinks:\n - "topic-x — the curated edge from a to x"\n---\n${PAGES["page-a"]}`,
142
+ "page-b": `---\nedges: []\n---\n${PAGES["page-b"]}`,
143
+ "topic-x": `---\nedges: []\n---\n${PAGES["topic-x"]}`,
144
+ };
145
+
146
+ const SLUGS = Object.keys(PAGES);
147
+
148
+ // A synthetic capability page (skill). Its content carries a distinctive term
149
+ // ("durian") so the real needle ranks it — synthetic pages are indexed and
150
+ // lane-ranked like any other page, not always-added to the pool.
151
+ const CAPABILITY_SLUG = "skills/example";
152
+ const CAPABILITY_CONTENT =
153
+ "# Skill: example\ndurian content for the example skill";
154
+ const INDEX_SLUGS = [...SLUGS, CAPABILITY_SLUG];
155
+
156
+ /** Page-body resolver mirroring `initLanes`: capability slug → its rendered
157
+ * content, on-disk slug → its page body. */
158
+ function bodyOf(slug: Slug): string {
159
+ return slug === CAPABILITY_SLUG ? CAPABILITY_CONTENT : PAGES[slug]!;
160
+ }
161
+
162
+ function makeEntries(): PageIndexEntry[] {
163
+ return INDEX_SLUGS.map((slug, i) => ({
164
+ id: i + 1,
165
+ slug,
166
+ summary: `summary of ${slug}`,
167
+ edges: [],
168
+ leaves: [],
169
+ modifiedAt: 0,
170
+ }));
171
+ }
172
+
173
+ async function buildLanes(): Promise<{
174
+ sectionIndex: SectionIndex;
175
+ needle: ReturnType<typeof buildSectionNeedle>;
176
+ edgeGraph: EdgeGraph;
177
+ }> {
178
+ const sectionIndex = await buildSectionIndex(INDEX_SLUGS, async (s) =>
179
+ bodyOf(s),
180
+ );
181
+ const needle = buildSectionNeedle(sectionIndex);
182
+ // The capability slug has no `links:` frontmatter, so the edge graph reads
183
+ // through `RAW` for the on-disk pages and its raw content otherwise.
184
+ const edgeGraph = await buildEdgeGraph(makeEntries(), async (s) =>
185
+ s === CAPABILITY_SLUG ? CAPABILITY_CONTENT : RAW[s]!,
186
+ );
187
+ return { sectionIndex, needle, edgeGraph };
188
+ }
189
+
190
+ const config = {} as never;
191
+ const CONV = "conv-xyz";
192
+
193
+ function makeTurn(
194
+ turnNumber: number,
195
+ currentMessage: string,
196
+ ): MemoryRoutingTurn {
197
+ return {
198
+ conversationId: CONV,
199
+ turnNumber,
200
+ currentMessage,
201
+ recentContext: "prior context",
202
+ };
203
+ }
204
+
205
+ function toolUseResponse(input: Record<string, unknown>): ProviderResponse {
206
+ return {
207
+ model: "stub-model",
208
+ stopReason: "tool_use",
209
+ usage: { inputTokens: 0, outputTokens: 0 },
210
+ content: [{ type: "tool_use", id: "tu-1", name: "select_pages", input }],
211
+ };
212
+ }
213
+
214
+ /** Parse the numbered `<candidates>` block back into an ordered slug list. */
215
+ function candidateSlugs(messages: Message[]): Slug[] {
216
+ for (const msg of messages) {
217
+ for (const block of msg.content) {
218
+ if (block.type !== "text") continue;
219
+ const m = /<candidates>\n([\s\S]*?)\n<\/candidates>/.exec(block.text);
220
+ if (!m) continue;
221
+ return m[1]
222
+ .split("\n")
223
+ .map((line) => /^\[\d+\] (\S+) —/.exec(line)?.[1])
224
+ .filter((s): s is string => !!s);
225
+ }
226
+ }
227
+ return [];
228
+ }
229
+
230
+ /**
231
+ * Provider that selects the pooled candidates in `keep` (mapping each back to
232
+ * its 1-based id), pinning those in `pin`. Records the rendered pool and counts
233
+ * the select calls so the test can assert one select per turn over the union.
234
+ */
235
+ let lastPool: Slug[] = [];
236
+ let selectCalls = 0;
237
+ function selectProvider(keep: Slug[], pin: Slug[] = []): Provider {
238
+ return {
239
+ name: "stub",
240
+ sendMessage: async (messages) => {
241
+ selectCalls++;
242
+ const pool = candidateSlugs(messages);
243
+ lastPool = pool;
244
+ const ids: number[] = [];
245
+ const pinned_ids: number[] = [];
246
+ pool.forEach((slug, i) => {
247
+ if (keep.includes(slug)) ids.push(i + 1);
248
+ if (pin.includes(slug)) pinned_ids.push(i + 1);
249
+ });
250
+ return toolUseResponse({ ids, pinned_ids });
251
+ },
252
+ };
253
+ }
254
+
255
+ // ---------------------------------------------------------------------------
256
+ // Selection read-back. Attribution (`attributeSelections`) and the write
257
+ // (`writeSelections`) are the shadow plugin's REAL functions, imported above —
258
+ // so this test exercises the production `result.laneBySlug` attribution rather
259
+ // than a local copy. The db-connection mock routes `writeSelections` at the
260
+ // in-memory test DB.
261
+ // ---------------------------------------------------------------------------
262
+
263
+ /** Read back every logged selection source for a turn, in row order. */
264
+ function loggedSources(turn: number): Array<{ slug: Slug; source: string }> {
265
+ return testSqlite
266
+ .query(
267
+ `SELECT slug, source FROM memory_v3_selections
268
+ WHERE conversation_id = ? AND turn = ? ORDER BY rowid`,
269
+ )
270
+ .all(CONV, turn) as Array<{ slug: Slug; source: string }>;
271
+ }
272
+
273
+ /** Drive one turn end-to-end: orchestrate → attribute → write. */
274
+ async function runTurn(
275
+ turnNumber: number,
276
+ query: string,
277
+ keep: Slug[],
278
+ pin: Slug[],
279
+ deps: {
280
+ lanes: Awaited<ReturnType<typeof buildLanes>>;
281
+ workingSet: InstanceType<typeof WorkingSet>;
282
+ },
283
+ ): Promise<OrchestrateResult> {
284
+ providerStub = selectProvider(keep, pin);
285
+ const result = await orchestrate(makeTurn(turnNumber, query), {
286
+ sectionIndex: deps.lanes.sectionIndex,
287
+ needle: deps.lanes.needle,
288
+ denseConfig: config,
289
+ edgeGraph: deps.lanes.edgeGraph,
290
+ workingSet: deps.workingSet,
291
+ });
292
+ writeSelections(CONV, turnNumber, attributeSelections(result));
293
+ return result;
294
+ }
295
+
296
+ beforeEach(() => {
297
+ mockActive = true;
298
+ providerStub = null;
299
+ denseHits = [];
300
+ lastPool = [];
301
+ selectCalls = 0;
302
+ testDb = makeDb();
303
+ });
304
+
305
+ afterAll(() => {
306
+ mockActive = false;
307
+ });
308
+
309
+ // ---------------------------------------------------------------------------
310
+ // Pool composition: the candidate pool is the union of the lanes, and exactly
311
+ // one select runs per turn. Synthetic capability pages are indexed like any
312
+ // other page, so they enter through the needle lane when the query matches
313
+ // their content — not by always being appended.
314
+ // ---------------------------------------------------------------------------
315
+
316
+ describe("memory-v3 shadow integration — candidate pool", () => {
317
+ test("pool unions needle ∪ dense ∪ edge; one select per turn", async () => {
318
+ const lanes = await buildLanes();
319
+ // "apple" hits page-a (needle). Dense returns page-b. page-a links to
320
+ // topic-x (edge). "apple" does NOT match the capability page, so it is not
321
+ // pooled this turn — capability pages are lane-ranked, not always-added.
322
+ denseHits = [{ article: "page-b", section: 0 }];
323
+ await runTurn(1, "apple", [], [], { lanes, workingSet: new WorkingSet() });
324
+
325
+ expect(selectCalls).toBe(1);
326
+ expect(new Set(lastPool)).toEqual(new Set(["page-a", "page-b", "topic-x"]));
327
+ });
328
+
329
+ test("a synthetic capability page enters the pool via the needle lane", async () => {
330
+ const lanes = await buildLanes();
331
+ // "durian" is the distinctive term in the capability page's content, so the
332
+ // real needle ranks it and folds it into the pool.
333
+ await runTurn(1, "durian", [], [], { lanes, workingSet: new WorkingSet() });
334
+
335
+ expect(selectCalls).toBe(1);
336
+ expect(lastPool).toContain(CAPABILITY_SLUG);
337
+ });
338
+ });
339
+
340
+ // ---------------------------------------------------------------------------
341
+ // Carry-forward across turns: a page selected on turn 1 stays in turn 2's
342
+ // final injection without being re-selected (net-new shadow behavior), and is
343
+ // logged as `carry-forward` on the later turn.
344
+ // ---------------------------------------------------------------------------
345
+
346
+ describe("memory-v3 shadow integration — carry-forward", () => {
347
+ test("a turn-1 selection accumulates into turn 2's injection and logs as carry-forward", async () => {
348
+ const lanes = await buildLanes();
349
+ const workingSet = new WorkingSet();
350
+
351
+ // Turn 1 selects+pins page-a (needle-sourced).
352
+ const t1 = await runTurn(1, "apple", ["page-a"], ["page-a"], {
353
+ lanes,
354
+ workingSet,
355
+ });
356
+ expect(t1.currentSelections.map((s) => s.slug)).toContain("page-a");
357
+ expect(loggedSources(1)).toEqual([{ slug: "page-a", source: "needle" }]);
358
+
359
+ // Turn 2 selects a DIFFERENT page; page-a carries forward un-re-selected.
360
+ denseHits = [{ article: "topic-x", section: 0 }];
361
+ const t2 = await runTurn(2, "cherry", ["topic-x"], [], {
362
+ lanes,
363
+ workingSet,
364
+ });
365
+ expect(t2.currentSelections.map((s) => s.slug)).not.toContain("page-a");
366
+ expect(t2.finalInjection).toContain("page-a");
367
+
368
+ // The carried page is logged with the carry-forward source tag.
369
+ const t2Rows = loggedSources(2);
370
+ expect(t2Rows).toContainEqual({ slug: "topic-x", source: "needle" });
371
+ expect(t2Rows).toContainEqual({ slug: "page-a", source: "carry-forward" });
372
+
373
+ // Working set accumulates both pages across the two turns.
374
+ expect(workingSet.union()).toEqual(new Set(["page-a", "topic-x"]));
375
+ });
376
+ });
377
+
378
+ // ---------------------------------------------------------------------------
379
+ // Lane-source attribution: a synthetic capability page now carries a section
380
+ // (its rendered content), so when the needle ranks it the selection has a
381
+ // matched section and is attributed `needle` — exactly like a concept page.
382
+ // ---------------------------------------------------------------------------
383
+
384
+ describe("memory-v3 shadow integration — lane-source attribution", () => {
385
+ test("a needle-ranked capability selection is logged with the needle source", async () => {
386
+ const lanes = await buildLanes();
387
+ // "durian" matches the capability page's content section, so selecting it
388
+ // records a `sectionBySlug` entry and the coarse mapping attributes it
389
+ // `needle` (capabilities are indexed pages now, not sectionless add-ins).
390
+ const result = await runTurn(1, "durian", [CAPABILITY_SLUG], [], {
391
+ lanes,
392
+ workingSet: new WorkingSet(),
393
+ });
394
+ expect(result.currentSelections.map((s) => s.slug)).toEqual([
395
+ CAPABILITY_SLUG,
396
+ ]);
397
+ expect(loggedSources(1)).toEqual([
398
+ { slug: CAPABILITY_SLUG, source: "needle" },
399
+ ]);
400
+ });
401
+ });
402
+
403
+ // ---------------------------------------------------------------------------
404
+ // A/B readout: summarizeSelections aggregates the logged shadow run by lane
405
+ // source, and reports turn count + distinct-slug (working-set) footprint.
406
+ // ---------------------------------------------------------------------------
407
+
408
+ describe("memory-v3 shadow integration — selection-log readout", () => {
409
+ test("summarizeSelections aggregates a multi-turn run by source", async () => {
410
+ const lanes = await buildLanes();
411
+ const workingSet = new WorkingSet();
412
+
413
+ // Turn 1: needle selects page-a (matched "apple").
414
+ await runTurn(1, "apple", ["page-a"], [], { lanes, workingSet });
415
+ // Turn 2: needle selects page-b (matched "banana"); page-a carries forward.
416
+ denseHits = [{ article: "page-b", section: 0 }];
417
+ await runTurn(2, "banana", ["page-b"], [], { lanes, workingSet });
418
+ // Turn 3: needle selects the capability page (matched "durian" in its
419
+ // content); page-a and page-b carry forward.
420
+ denseHits = [];
421
+ await runTurn(3, "durian", [CAPABILITY_SLUG], [], { lanes, workingSet });
422
+
423
+ const summary = summarizeSelections(CONV);
424
+ // needle: page-a (t1) + page-b (t2) + capability page (t3) = 3.
425
+ expect(summary.bySource.needle).toBe(3);
426
+ // No selection was attributed to the edge lane in this run.
427
+ expect(summary.bySource.edge).toBe(0);
428
+ // carry-forward: page-a (t2) + page-a & page-b (t3) = 3.
429
+ expect(summary.bySource["carry-forward"]).toBe(3);
430
+ // dense lane surfaced candidates but none were selected.
431
+ expect(summary.bySource.dense).toBe(0);
432
+ // Three turns logged selections.
433
+ expect(summary.turns).toBe(3);
434
+ // Distinct slugs across the run: page-a, page-b, the capability page.
435
+ expect(summary.distinctSlugs).toBe(3);
436
+ });
437
+
438
+ test("summarizeSelections reports zeros for a conversation with no rows", () => {
439
+ const summary = summarizeSelections("conv-empty");
440
+ expect(summary).toEqual({
441
+ bySource: { needle: 0, dense: 0, edge: 0, "carry-forward": 0 },
442
+ turns: 0,
443
+ distinctSlugs: 0,
444
+ });
445
+ });
446
+ });