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

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