@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,156 @@
1
+ /**
2
+ * Read-side store for the inspector's Memory V3 section. Reads the persisted
3
+ * `memory_v3_selections` rows for a turn and re-renders the `<memory>` block
4
+ * the v3 working set selected, so the inspector can show what v3 chose (and,
5
+ * in live mode, what it actually injected) without re-running orchestration —
6
+ * which would be wrong anyway, since the working set is stateful (carry-forward
7
+ * across turns) and can't be reproduced after the fact.
8
+ */
9
+
10
+ import type { MemoryV3SelectionLog } from "../../../api/responses/memory-v3-selection-log.js";
11
+ import { isAssistantFeatureFlagEnabled } from "../../../config/assistant-feature-flags.js";
12
+ import { getConfig } from "../../../config/loader.js";
13
+ import { getDb, getSqliteFrom } from "../../../memory/db-connection.js";
14
+ import { renderV3SectionContent } from "./page-content.js";
15
+ import { renderMemoryBlock } from "./render-injection.js";
16
+ import {
17
+ type Section,
18
+ SELECTION_SOURCES,
19
+ type SelectionSource,
20
+ type Slug,
21
+ } from "./types.js";
22
+
23
+ const MEMORY_V3_SHADOW = "memory-v3-shadow" as const;
24
+ const MEMORY_V3_LIVE = "memory-v3-live" as const;
25
+
26
+ interface SelectionRow {
27
+ slug: string;
28
+ source: string;
29
+ pinned: number;
30
+ }
31
+
32
+ function rowsForTurn(conversationId: string, turn: number): SelectionRow[] {
33
+ return getSqliteFrom(getDb())
34
+ .query(
35
+ /*sql*/ `
36
+ SELECT slug, source, pinned FROM memory_v3_selections
37
+ WHERE conversation_id = ? AND turn = ?
38
+ ORDER BY rowid
39
+ `,
40
+ )
41
+ .all(conversationId, turn) as SelectionRow[];
42
+ }
43
+
44
+ /**
45
+ * Build the inspector's v3 selection log for one turn of a conversation.
46
+ *
47
+ * `turn` is the inspected message's turn (the v2-activation log's turn). The
48
+ * selection is returned ONLY for that exact turn; when there are no v3 rows for
49
+ * it — including when `turn` is null (the message has no v2-activation turn) —
50
+ * this returns `null` rather than falling back to a different turn. A fallback
51
+ * would attribute another turn's selection (e.g. a later turn's pages and
52
+ * rendered block) to the inspected message, which corrupts shadow validation.
53
+ *
54
+ * Caveat: v3 rows are keyed by the orchestrator turn counter (`ctx.turnCount`)
55
+ * while `turn` here is the v2 memory-tracker turn. They coincide for normal
56
+ * turns, but if they diverge this simply yields `null` (no section) rather than
57
+ * wrong data. Tying v3 rows to a message id for exact per-message attribution
58
+ * regardless of counter drift is a documented follow-up.
59
+ *
60
+ * Selection rows are stored in `finalInjection` order (this turn's L2
61
+ * selections, then carry-forward), so rendering them in row order reproduces
62
+ * the block v3 would inject.
63
+ */
64
+ export async function getMemoryV3SelectionForInspector(
65
+ conversationId: string,
66
+ turn: number | null | undefined,
67
+ ): Promise<MemoryV3SelectionLog | null> {
68
+ if (turn == null) return null;
69
+
70
+ const rows = rowsForTurn(conversationId, turn);
71
+ if (rows.length === 0) return null;
72
+
73
+ const config = getConfig();
74
+ const selections = rows.map((r) => ({
75
+ slug: r.slug,
76
+ source: r.source,
77
+ pinned: r.pinned === 1,
78
+ }));
79
+ const slugs: Slug[] = selections.map((s) => s.slug);
80
+ // NOTE: `injectedText` is APPROXIMATE — it renders the full/lead page for
81
+ // every slug, not the exact matched section live injection used. The inspector
82
+ // re-renders from persisted rows without re-running orchestration, and the
83
+ // section identity is not persisted (`memory_v3_selections` stores
84
+ // slug/source/pinned, not the section ordinal), so the section map is empty
85
+ // and `renderV3SectionContent` falls back to the full page. Persisting the
86
+ // injected section (a schema migration) is deliberately deferred: the A/B's
87
+ // primary signal is slug-level recall via `summarizeSelections`, which is
88
+ // exact — only this debug-oriented injected text is an approximation.
89
+ const injectedText = await renderMemoryBlock(
90
+ slugs,
91
+ new Map<Slug, Section>(),
92
+ renderV3SectionContent,
93
+ );
94
+
95
+ return {
96
+ turn,
97
+ live: isAssistantFeatureFlagEnabled(MEMORY_V3_LIVE, config),
98
+ shadow: isAssistantFeatureFlagEnabled(MEMORY_V3_SHADOW, config),
99
+ selections,
100
+ injectedText,
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Offline A/B aggregate over a conversation's logged v3 selections. Reads every
106
+ * `memory_v3_selections` row for the conversation (all turns) and rolls them up
107
+ * for shadow-vs-v2 inspection without re-rendering any blocks:
108
+ *
109
+ * - `bySource`: count of selection rows per lane source (`needle` / `dense` /
110
+ * `edge` / `carry-forward`). Every known source is present (zero when
111
+ * unused) so callers can diff two runs without null-guarding; an unknown
112
+ * historical/free-text source is ignored (the column is permissive).
113
+ * - `turns`: number of distinct turns that logged at least one selection.
114
+ * - `distinctSlugs`: number of distinct page slugs selected across all turns —
115
+ * the conversation's working-set footprint.
116
+ *
117
+ * This is read-only telemetry for comparing a shadow run's lane mix against
118
+ * v2's logged selections offline; it never re-runs orchestration.
119
+ */
120
+ export interface SelectionSummary {
121
+ bySource: Record<SelectionSource, number>;
122
+ turns: number;
123
+ distinctSlugs: number;
124
+ }
125
+
126
+ function isSelectionSource(source: string): source is SelectionSource {
127
+ return (SELECTION_SOURCES as readonly string[]).includes(source);
128
+ }
129
+
130
+ export function summarizeSelections(conversationId: string): SelectionSummary {
131
+ const rows = getSqliteFrom(getDb())
132
+ .query(
133
+ /*sql*/ `
134
+ SELECT turn, slug, source FROM memory_v3_selections
135
+ WHERE conversation_id = ?
136
+ `,
137
+ )
138
+ .all(conversationId) as Array<{
139
+ turn: number;
140
+ slug: string;
141
+ source: string;
142
+ }>;
143
+
144
+ const bySource = Object.fromEntries(
145
+ SELECTION_SOURCES.map((source) => [source, 0]),
146
+ ) as Record<SelectionSource, number>;
147
+ const turns = new Set<number>();
148
+ const slugs = new Set<string>();
149
+ for (const row of rows) {
150
+ if (isSelectionSource(row.source)) bySource[row.source] += 1;
151
+ turns.add(row.turn);
152
+ slugs.add(row.slug);
153
+ }
154
+
155
+ return { bySource, turns: turns.size, distinctSlugs: slugs.size };
156
+ }
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Memory v3 — flag-gated shadow/live orchestration engine.
3
+ *
4
+ * Runs the v3 orchestrator each turn and records its selection set to
5
+ * `memory_v3_selections`. Two flags gate behavior:
6
+ *
7
+ * - `memory-v3-shadow` (live OFF): observation-only. {@link observeTurn}
8
+ * orchestrates and logs the selection set; no injection is produced, so v2
9
+ * injection is bit-for-bit identical — the only difference is the
10
+ * side-effect telemetry write.
11
+ * - `memory-v3-live`: live injection. The injector (`memoryV3Injector` in
12
+ * `./injector.ts`) additionally renders the working-set selection into a
13
+ * `<memory>` block and returns it at v2's dynamic-memory placement
14
+ * (`after-memory-prefix`). Selections are still logged.
15
+ * - both OFF: orchestration is skipped entirely.
16
+ *
17
+ * On each turn (either flag on):
18
+ * 1. Lazy-init the v3 lanes ONCE across the whole process (section index,
19
+ * section-grain BM25 needle, dense lane config, link-graph edge graph,
20
+ * carry-forward working set), memoizing the init promise so concurrent
21
+ * first turns share a single build.
22
+ * 2. Build a {@link MemoryRoutingTurn} from the conversation's recent messages.
23
+ * 3. Run {@link orchestrate} and record its selection set to
24
+ * `memory_v3_selections` with a best-effort lane attribution.
25
+ *
26
+ * {@link observeTurn} wraps everything after the flag read in try/catch — any
27
+ * failure is logged and swallowed so it can never affect the live turn. The
28
+ * injector treats a `null`/empty result as "no v3 injection", so v2 memory
29
+ * remains the fallback rather than dropping all memory.
30
+ */
31
+
32
+ import { existsSync, readFileSync } from "node:fs";
33
+
34
+ import { isAssistantFeatureFlagEnabled } from "../../../config/assistant-feature-flags.js";
35
+ import { getConfig } from "../../../config/loader.js";
36
+ import type { AssistantConfig } from "../../../config/schema.js";
37
+ import { getMessages } from "../../../memory/conversation-crud.js";
38
+ import { getDb, getSqliteFrom } from "../../../memory/db-connection.js";
39
+ import { stringifyMessageContent } from "../../../memory/message-content.js";
40
+ import { getPageIndex } from "../../../memory/v2/page-index.js";
41
+ import { readPage, renderPageContent } from "../../../memory/v2/page-store.js";
42
+ import { getLogger } from "../../../util/logger.js";
43
+ import {
44
+ getWorkspaceDir,
45
+ getWorkspacePromptPath,
46
+ } from "../../../util/platform.js";
47
+ import { stripCommentLines } from "../../../util/strip-comment-lines.js";
48
+ import { capabilityOrDiskBody } from "./capabilities.js";
49
+ import type { EdgeGraph } from "./edge.js";
50
+ import { buildEdgeGraph } from "./edge.js";
51
+ import type { OrchestrateResult } from "./orchestrate.js";
52
+ import { orchestrate } from "./orchestrate.js";
53
+ import { ensureSectionCollection } from "./section-dense-store.js";
54
+ import type { SectionNeedle } from "./section-needle.js";
55
+ import { buildSectionNeedle } from "./section-needle.js";
56
+ import { buildSectionIndex } from "./sections.js";
57
+ import {
58
+ type MemoryRoutingTurn,
59
+ type SectionIndex,
60
+ type SelectionSource,
61
+ type Slug,
62
+ } from "./types.js";
63
+ import { WorkingSet } from "./working-set.js";
64
+
65
+ export const MEMORY_V3_SHADOW = "memory-v3-shadow" as const;
66
+ export const MEMORY_V3_LIVE = "memory-v3-live" as const;
67
+
68
+ const log = getLogger("memory-v3-shadow");
69
+
70
+ /** How many recent messages to fold into the shadow `recentContext` string. */
71
+ const RECENT_CONTEXT_MESSAGES = 6;
72
+
73
+ /**
74
+ * The lazily-built, process-lifetime v3 lanes. The working set is stateful
75
+ * (carry-forward across turns), so it is intentionally shared across every
76
+ * shadow turn rather than rebuilt per turn — that is the whole point of the
77
+ * carry-forward lane.
78
+ */
79
+ export interface ShadowLanes {
80
+ sectionIndex: SectionIndex;
81
+ needle: SectionNeedle;
82
+ /** Config the dense lane needs to embed the query + search the section
83
+ * collection. */
84
+ denseConfig: AssistantConfig;
85
+ edgeGraph: EdgeGraph;
86
+ workingSet: WorkingSet;
87
+ }
88
+
89
+ /**
90
+ * Memoized init promise. Caching the PROMISE (not the resolved value) means
91
+ * concurrent first turns all await the same build instead of racing several
92
+ * section-index / needle / edge-graph passes.
93
+ */
94
+ let lanesPromise: Promise<ShadowLanes> | null = null;
95
+
96
+ /**
97
+ * Drop the memoized lanes so the NEXT `getLanes` rebuilds them from scratch
98
+ * (fresh section index + fresh needle + fresh edge graph). The rebuild is lazy
99
+ * — this only clears the cache, so the cost is paid by the next caller, and
100
+ * concurrent first-callers after the invalidation still share a single build via
101
+ * the re-memoized promise. Call this whenever the underlying pages change on
102
+ * disk.
103
+ */
104
+ export function invalidateLanes(): void {
105
+ lanesPromise = null;
106
+ }
107
+
108
+ /** Test-only alias for {@link invalidateLanes}. */
109
+ export function resetShadowLanesForTests(): void {
110
+ invalidateLanes();
111
+ }
112
+
113
+ async function initLanes(config: AssistantConfig): Promise<ShadowLanes> {
114
+ const pageIndex = await getPageIndex(getWorkspaceDir());
115
+ const slugs = pageIndex.entries.map((entry) => entry.slug);
116
+
117
+ // Read each page ONCE and feed BOTH forms downstream: the frontmatter-stripped
118
+ // body to the section index (lexical/dense matching), and the raw page
119
+ // (frontmatter + body) to the edge graph (so the `links:` frontmatter is
120
+ // available). A per-slug cache holds the parsed page so the second consumer
121
+ // reuses the first read.
122
+ const pageCache = new Map<Slug, { body: string; raw: string } | null>();
123
+ async function loadPage(
124
+ slug: Slug,
125
+ ): Promise<{ body: string; raw: string } | null> {
126
+ if (pageCache.has(slug)) return pageCache.get(slug)!;
127
+ let loaded: { body: string; raw: string } | null = null;
128
+ try {
129
+ const page = await readPage(getWorkspaceDir(), slug);
130
+ if (page) loaded = { body: page.body, raw: renderPageContent(page) };
131
+ } catch {
132
+ loaded = null;
133
+ }
134
+ pageCache.set(slug, loaded);
135
+ return loaded;
136
+ }
137
+ // Synthetic capability slugs (skills / CLI commands) have no on-disk page, so
138
+ // they contribute their rendered capability content to the section index —
139
+ // exactly the content `page-content.ts` injects for them. This puts them in
140
+ // the section index, so the needle lane (and, once a backfill embeds them, the
141
+ // dense lane) ranks them by relevance like any other page, instead of being
142
+ // blindly added to the select pool every turn. Real pages read their body
143
+ // through the cached `loadPage`.
144
+ const pageBody = async (slug: Slug): Promise<string> =>
145
+ capabilityOrDiskBody(slug, async (s) => (await loadPage(s))?.body ?? "");
146
+ const pageRaw = async (slug: Slug): Promise<string> => {
147
+ const loaded = await loadPage(slug);
148
+ if (!loaded) throw new Error(`page not found: ${slug}`);
149
+ return loaded.raw;
150
+ };
151
+
152
+ const sectionIndex = await buildSectionIndex(slugs, pageBody);
153
+ const needle = buildSectionNeedle(sectionIndex);
154
+ const edgeGraph = await buildEdgeGraph(pageIndex.entries, pageRaw, {
155
+ hubDegree: config.memory.v3.edge.hubDegree,
156
+ });
157
+ // Ensuring the dense collection is best-effort: the needle + edge lanes and
158
+ // carry-forward are in-memory and independent of Qdrant, so a Qdrant outage
159
+ // must NOT reject lane init (which would return `null` from observeTurn and
160
+ // disable ALL of v3, plus poison the memoized lanes until invalidation). On
161
+ // failure we log and continue with the dense lane degraded — denseLane already
162
+ // returns no hits on a Qdrant error, and maintain/backfill re-ensure the
163
+ // collection once Qdrant recovers.
164
+ try {
165
+ await ensureSectionCollection(config);
166
+ } catch (err) {
167
+ log.warn(
168
+ { err: err instanceof Error ? err.message : String(err) },
169
+ "memory-v3: section collection ensure failed; continuing with the dense lane degraded",
170
+ );
171
+ }
172
+
173
+ const workingSet = new WorkingSet(
174
+ config.memory.v3.workingSet.maxPages,
175
+ config.memory.v3.workingSet.evictWindow,
176
+ );
177
+
178
+ return {
179
+ sectionIndex,
180
+ needle,
181
+ denseConfig: config,
182
+ edgeGraph,
183
+ workingSet,
184
+ };
185
+ }
186
+
187
+ /** Lazy, memoized accessor for the shadow lanes. */
188
+ function getLanes(config: AssistantConfig): Promise<ShadowLanes> {
189
+ if (!lanesPromise) {
190
+ lanesPromise = initLanes(config).catch((err) => {
191
+ // Reset on failure so a transient init error doesn't permanently wedge
192
+ // the shadow lane — the next turn retries.
193
+ lanesPromise = null;
194
+ throw err;
195
+ });
196
+ }
197
+ return lanesPromise;
198
+ }
199
+
200
+ /**
201
+ * Read the live NOW.md scratchpad (the user's short "what's salient right now"
202
+ * file), stripped of its comment lines. Mirrors `readNowScratchpad` but reads
203
+ * through the light platform / strip utilities directly, keeping the v3
204
+ * plugin's load (and its test) free of heavier module graphs. Returns `null`
205
+ * when absent, empty, or unreadable.
206
+ */
207
+ function readNowContext(): string | null {
208
+ const nowPath = getWorkspacePromptPath("NOW.md");
209
+ if (!existsSync(nowPath)) return null;
210
+ try {
211
+ const stripped = stripCommentLines(readFileSync(nowPath, "utf-8")).trim();
212
+ return stripped.length > 0 ? stripped : null;
213
+ } catch {
214
+ return null;
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Compose the situational signal threaded into pool selection: the current date
220
+ * plus the live NOW.md scratchpad. The date alone is a weak signal, but together
221
+ * with the scratchpad it lets retrieval surface a page the message never names
222
+ * (e.g. an anniversary that falls today). Always returns at least the date line
223
+ * — this mirrors the `c_now`/NOW.md signal the v2 retriever uses.
224
+ */
225
+ function buildSituationalContext(): string {
226
+ const now = readNowContext();
227
+ const today = `Today is ${new Date().toDateString()}.`;
228
+ return now ? `${today}\n\n${now}` : today;
229
+ }
230
+
231
+ /**
232
+ * Build a v3 {@link MemoryRoutingTurn} from the conversation's persisted messages.
233
+ * `currentMessage` is the latest user message; `recentContext` is the tail of
234
+ * the recent transcript; `situationalContext` carries the current date and the
235
+ * live NOW.md scratchpad. Returns `null` when there is no user message to route
236
+ * on (nothing to shadow this turn).
237
+ */
238
+ function buildShadowTurn(
239
+ conversationId: string,
240
+ turnIndex: number,
241
+ ): MemoryRoutingTurn | null {
242
+ const rows = getMessages(conversationId);
243
+ if (rows.length === 0) return null;
244
+
245
+ let currentMessage = "";
246
+ for (let i = rows.length - 1; i >= 0; i--) {
247
+ if (rows[i]!.role === "user") {
248
+ currentMessage = stringifyMessageContent(rows[i]!.content);
249
+ if (currentMessage.length > 0) break;
250
+ }
251
+ }
252
+ if (currentMessage.length === 0) return null;
253
+
254
+ const recentContext = rows
255
+ .slice(-RECENT_CONTEXT_MESSAGES)
256
+ .map((r) => stringifyMessageContent(r.content))
257
+ .filter((t) => t.length > 0)
258
+ .join("\n");
259
+
260
+ return {
261
+ conversationId,
262
+ turnNumber: turnIndex,
263
+ currentMessage,
264
+ recentContext,
265
+ situationalContext: buildSituationalContext(),
266
+ };
267
+ }
268
+
269
+ interface SelectionRow {
270
+ slug: Slug;
271
+ source: SelectionSource;
272
+ pinned: number;
273
+ }
274
+
275
+ /**
276
+ * Map an orchestrate result onto telemetry rows with precise per-lane source
277
+ * attribution.
278
+ *
279
+ * - A current selection is tagged with the candgen lane that FIRST surfaced its
280
+ * page this turn — `result.laneBySlug` (`"needle"` / `"dense"` / `"edge"`),
281
+ * recorded at pool-build time. (`"needle"` is the fallback if a selected slug
282
+ * is somehow absent from the lane map, which should not happen since every
283
+ * pooled candidate is recorded there.)
284
+ * - A slug in `finalInjection` but NOT re-selected this turn → `"carry-forward"`.
285
+ */
286
+ export function attributeSelections(result: OrchestrateResult): SelectionRow[] {
287
+ const rows: SelectionRow[] = [];
288
+ const seen = new Set<Slug>();
289
+ for (const sel of result.currentSelections) {
290
+ seen.add(sel.slug);
291
+ rows.push({
292
+ slug: sel.slug,
293
+ source: result.laneBySlug.get(sel.slug) ?? "needle",
294
+ pinned: sel.pinned ? 1 : 0,
295
+ });
296
+ }
297
+ for (const slug of result.finalInjection) {
298
+ if (seen.has(slug)) continue;
299
+ seen.add(slug);
300
+ rows.push({ slug, source: "carry-forward", pinned: 0 });
301
+ }
302
+ return rows;
303
+ }
304
+
305
+ /** Write the attributed selection rows to `memory_v3_selections`. */
306
+ export function writeSelections(
307
+ conversationId: string,
308
+ turn: number,
309
+ rows: SelectionRow[],
310
+ ): void {
311
+ if (rows.length === 0) return;
312
+ const raw = getSqliteFrom(getDb());
313
+ // PK is (conversation_id, turn, slug); OR REPLACE keeps the write
314
+ // idempotent if the same turn is observed twice (e.g. a retried turn).
315
+ const stmt = raw.query(/*sql*/ `
316
+ INSERT OR REPLACE INTO memory_v3_selections (
317
+ conversation_id, turn, slug, source, pinned, created_at
318
+ ) VALUES (?, ?, ?, ?, ?, ?)
319
+ `);
320
+ const now = Date.now();
321
+ for (const row of rows) {
322
+ stmt.run(conversationId, turn, row.slug, row.source, row.pinned, now);
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Run v3 orchestration for one turn and log the selection set, returning the
328
+ * orchestrate result so a live caller can render it. Never throws — all
329
+ * failures are logged and swallowed (returning `null`) so the live turn is
330
+ * unaffected. Returns `null` when there is no user message to route on.
331
+ */
332
+ export async function observeTurn(
333
+ conversationId: string,
334
+ turnIndex: number,
335
+ ): Promise<OrchestrateResult | null> {
336
+ try {
337
+ const turn = buildShadowTurn(conversationId, turnIndex);
338
+ if (!turn) return null;
339
+
340
+ const cfg = getConfig();
341
+ const lanes = await getLanes(cfg);
342
+ const v3 = cfg.memory.v3;
343
+ const result = await orchestrate(turn, {
344
+ sectionIndex: lanes.sectionIndex,
345
+ needle: lanes.needle,
346
+ denseConfig: lanes.denseConfig,
347
+ edgeGraph: lanes.edgeGraph,
348
+ workingSet: lanes.workingSet,
349
+ needleK: v3.needleK,
350
+ denseK: v3.denseK,
351
+ edgeSeeds: v3.edge.seedCount,
352
+ edgePerSeed: v3.edge.perSeed,
353
+ edgeCap: v3.edge.cap,
354
+ });
355
+
356
+ const rows = attributeSelections(result);
357
+ writeSelections(conversationId, turnIndex, rows);
358
+ return result;
359
+ } catch (err) {
360
+ log.warn(
361
+ { err: err instanceof Error ? err.message : String(err), conversationId },
362
+ "memory-v3 orchestration failed (non-fatal)",
363
+ );
364
+ return null;
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Test-facing shadow wrapper: gate on the shadow flag, then run v3
370
+ * orchestration for one turn and log the selection set. Production injection
371
+ * goes through `produce()` → {@link observeTurn} directly (which checks both
372
+ * flags); this wrapper exists so tests can drive shadow observation in
373
+ * isolation. Never throws — all failures are logged and swallowed so the live
374
+ * turn is unaffected.
375
+ */
376
+ export async function runShadowObservation(
377
+ conversationId: string,
378
+ turnIndex: number,
379
+ ): Promise<void> {
380
+ if (!isAssistantFeatureFlagEnabled(MEMORY_V3_SHADOW, getConfig())) return;
381
+ await observeTurn(conversationId, turnIndex);
382
+ }
@@ -0,0 +1,92 @@
1
+ export type Slug = string;
2
+
3
+ /**
4
+ * Injection-block id for the v3 live `<memory>` block. Shared between the
5
+ * producer (the v3 injector in `shadow-plugin.ts`) and the v2-suppression
6
+ * consumer (`conversation-runtime-assembly.ts`), which keys off this id to
7
+ * detect that v3 actually produced a block this turn. Keeping it in one place
8
+ * makes a rename a compile error on both sides instead of a silent
9
+ * suppression bypass.
10
+ */
11
+ export const MEMORY_V3_BLOCK_ID = "memory-v3" as const;
12
+
13
+ /**
14
+ * A single section of a page: the lead (text before the first `## heading`,
15
+ * ordinal 0) or a heading-delimited block. Over-long sections are split into
16
+ * multiple ordered `Section`s, each with its own consecutive `ordinal`, so each
17
+ * fits a typical embedding window. `text` is prefixed with a
18
+ * `${lastSlugSegment} — ${title}` head line for lexical/dense matching.
19
+ */
20
+ export interface Section {
21
+ article: Slug;
22
+ title: string;
23
+ text: string;
24
+ ordinal: number;
25
+ }
26
+
27
+ /**
28
+ * A flat, deterministic index of every page's sections plus an article→section
29
+ * lookup. `byArticle` maps each article slug to the indices (into `sections`)
30
+ * of that article's sections, in order.
31
+ */
32
+ export interface SectionIndex {
33
+ sections: Section[];
34
+ byArticle: Map<Slug, number[]>;
35
+ }
36
+
37
+ /** A page selected from the candidate pool, with whether the turn centers on it. */
38
+ export interface SelectedPage {
39
+ slug: Slug;
40
+ pinned: boolean;
41
+ }
42
+
43
+ export interface WorkingSetEntry {
44
+ slug: Slug;
45
+ selectedAtTurn: number;
46
+ pinned: boolean;
47
+ lastSeenTurn: number;
48
+ }
49
+
50
+ export interface MemoryRoutingTurn {
51
+ conversationId: string;
52
+ turnNumber: number;
53
+ currentMessage: string;
54
+ recentContext: string;
55
+ /**
56
+ * Optional situational signal — the current date plus the live NOW.md
57
+ * scratchpad — so a leaf or page can be routed/selected on a date or
58
+ * live-state cue the message itself never names (e.g. a person whose
59
+ * anniversary is today). Omitted when unavailable; the router and selector
60
+ * render nothing for an undefined value.
61
+ */
62
+ situationalContext?: string;
63
+ }
64
+
65
+ /**
66
+ * Canonical ordered list of the lane sources recorded per selection. The
67
+ * {@link SelectionSource} type is derived from this so a new lane is added in
68
+ * exactly one place and the runtime list (used for telemetry roll-ups and
69
+ * source validation) can never drift from the type.
70
+ *
71
+ * The `memory_v3_selections.source` column is free-text, so tightening this set
72
+ * needs no migration: any historical rows with older labels still read back
73
+ * fine via the permissive `z.string()` row schema.
74
+ */
75
+ export const SELECTION_SOURCES = [
76
+ "needle",
77
+ "dense",
78
+ "edge",
79
+ "carry-forward",
80
+ ] as const;
81
+
82
+ export type SelectionSource = (typeof SELECTION_SOURCES)[number];
83
+
84
+ /**
85
+ * The candidate-generation lanes — the strict subset of {@link SelectionSource}
86
+ * a pooled candidate can be tagged with at pool-build time. (`carry-forward` is
87
+ * the one source assigned later, by the orchestrator's working-set union, not by
88
+ * a candgen lane.) Defined as `Exclude<SelectionSource, "carry-forward">` so it
89
+ * can never drift from {@link SELECTION_SOURCES}: adding a lane there widens this
90
+ * automatically.
91
+ */
92
+ export type CandidateLane = Exclude<SelectionSource, "carry-forward">;
@@ -42,13 +42,16 @@ export class WorkingSet {
42
42
  /**
43
43
  * Prune the working set, in order:
44
44
  * 1. Core slugs never belong here — drop any entry whose slug is core.
45
+ * The section-lane pipeline has no core set, so `coreSlugs` defaults to
46
+ * empty (no-op); the tree pipeline passed its core slugs here. Stale and
47
+ * cap eviction semantics are identical either way.
45
48
  * 2. Pinned entries never evict.
46
49
  * 3. Window eviction: drop a non-pinned entry unseen for more than
47
50
  * `evictWindow` turns.
48
51
  * 4. Cap eviction: while over `maxPages`, evict the lowest-scoring
49
52
  * non-pinned entry first (ties broken deterministically).
50
53
  */
51
- evict(currentTurn: number, coreSlugs: Set<Slug>): void {
54
+ evict(currentTurn: number, coreSlugs: Set<Slug> = new Set()): void {
52
55
  // 1. Core slugs are owned by core, not the working set.
53
56
  for (const slug of this.entries.keys()) {
54
57
  if (coreSlugs.has(slug)) {
@@ -5,7 +5,6 @@
5
5
  "private": true,
6
6
  "license": "MIT",
7
7
  "type": "module",
8
- "main": "./register.ts",
9
8
  "engines": {
10
9
  "node": ">=20.12.0"
11
10
  },
@@ -5,7 +5,6 @@
5
5
  "private": true,
6
6
  "license": "MIT",
7
7
  "type": "module",
8
- "main": "./register.ts",
9
8
  "engines": {
10
9
  "node": ">=20.12.0"
11
10
  },
@@ -5,7 +5,6 @@
5
5
  "private": true,
6
6
  "license": "MIT",
7
7
  "type": "module",
8
- "main": "./register.ts",
9
8
  "engines": {
10
9
  "node": ">=20.12.0"
11
10
  },