@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
@@ -1,411 +0,0 @@
1
- /**
2
- * Memory v3 — flag-gated shadow/live injector.
3
- *
4
- * An {@link Injector} that runs the v3 orchestrator each turn and
5
- * records its selection set to `memory_v3_selections`. Two flags gate its
6
- * injection behavior:
7
- *
8
- * - `memory-v3-shadow` (and live OFF): observation-only. `produce()` returns
9
- * `null`, so v2 injection is bit-for-bit identical whether the flag is on
10
- * or off — the only difference is the side-effect telemetry write.
11
- * - `memory-v3-live`: live injection. `produce()` additionally renders the
12
- * working-set selection into a `<memory>` block via {@link renderMemoryBlock}
13
- * and returns it as an injection block at v2's dynamic-memory placement
14
- * (`after-memory-prefix`). Selections are still logged.
15
- * - both OFF: `produce()` returns `null` and skips orchestration entirely.
16
- *
17
- * On each turn (either flag on):
18
- * 1. Lazy-init the v3 lanes ONCE across the whole process (leaf tree, core
19
- * set, BM25 needle, carry-forward working set), memoizing the init
20
- * promise so concurrent first turns share a single build.
21
- * 2. Build a {@link TurnContext} from the conversation's recent messages.
22
- * 3. Run {@link orchestrate} and record its selection set to
23
- * `memory_v3_selections` with a best-effort lane attribution.
24
- *
25
- * Everything after the flag read is wrapped in try/catch — any failure is
26
- * logged and swallowed so it can never affect the live turn. In live mode a
27
- * failure returns `null` (no v3 injection); v2 suppression keys off BOTH the
28
- * flag AND whether `produce()` actually returned a block, so a v3 failure (or
29
- * empty selection) falls back to v2 memory 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 {
41
- type InjectionBlock,
42
- type Injector,
43
- type TurnContext as PluginTurnContext,
44
- } from "../../plugins/types.js";
45
- import { getLogger } from "../../util/logger.js";
46
- import {
47
- getWorkspaceDir,
48
- getWorkspacePromptPath,
49
- } from "../../util/platform.js";
50
- import { stripCommentLines } from "../../util/strip-comment-lines.js";
51
- import { getPageIndex } from "../v2/page-index.js";
52
- import { injectCapabilitiesLeaf, isCapabilitySlug } from "./capabilities.js";
53
- import { loadCore } from "./core.js";
54
- import type { NeedleIndex } from "./needle.js";
55
- import { buildNeedleIndex } from "./needle.js";
56
- import type { OrchestrateResult } from "./orchestrate.js";
57
- import { orchestrate } from "./orchestrate.js";
58
- import { renderV3PageContent } from "./page-content.js";
59
- import { renderMemoryBlock } from "./render-injection.js";
60
- import { coreSlugs, loadLeafTree, resolveDataDir } from "./tree.js";
61
- import {
62
- type LeafPath,
63
- type LeafTree,
64
- MEMORY_V3_BLOCK_ID,
65
- type SelectionSource,
66
- type Slug,
67
- type TurnContext,
68
- } from "./types.js";
69
- import { WorkingSet } from "./working-set.js";
70
-
71
- const MEMORY_V3_SHADOW = "memory-v3-shadow" as const;
72
- const MEMORY_V3_LIVE = "memory-v3-live" as const;
73
-
74
- const log = getLogger("memory-v3-shadow");
75
-
76
- /** How many recent messages to fold into the shadow `recentContext` string. */
77
- const RECENT_CONTEXT_MESSAGES = 6;
78
-
79
- /**
80
- * The lazily-built, process-lifetime v3 lanes. The working set is stateful
81
- * (carry-forward across turns), so it is intentionally shared across every
82
- * shadow turn rather than rebuilt per turn — that is the whole point of the
83
- * carry-forward lane.
84
- */
85
- export interface ShadowLanes {
86
- tree: LeafTree;
87
- core: Set<LeafPath>;
88
- needle: NeedleIndex;
89
- workingSet: WorkingSet;
90
- }
91
-
92
- /**
93
- * Memoized init promise. Caching the PROMISE (not the resolved value) means
94
- * concurrent first turns all await the same build instead of racing several
95
- * `loadLeafTree`/`buildNeedleIndex` passes.
96
- */
97
- let lanesPromise: Promise<ShadowLanes> | null = null;
98
-
99
- /**
100
- * Drop the memoized lanes so the NEXT `getLanes` rebuilds them from scratch
101
- * (fresh topic-tree load + fresh needle index). The rebuild is lazy — this only
102
- * clears the cache, so the cost is paid by the next caller, and concurrent
103
- * first-callers after the invalidation still share a single build via the
104
- * re-memoized promise. Call this whenever the underlying tree or needle sources
105
- * change on disk.
106
- */
107
- export function invalidateLanes(): void {
108
- lanesPromise = null;
109
- }
110
-
111
- /** Test-only alias for {@link invalidateLanes}. */
112
- export function resetShadowLanesForTests(): void {
113
- invalidateLanes();
114
- }
115
-
116
- /**
117
- * Pull a page's summary from the existing v2 page index. Missing summaries
118
- * (and any read failure) degrade to "" — the needle and L2 selector treat an
119
- * empty summary as "no signal" rather than throwing.
120
- */
121
- async function pageSummary(slug: Slug): Promise<string> {
122
- try {
123
- const index = await getPageIndex(getWorkspaceDir());
124
- return index.bySlug.get(slug)?.summary ?? "";
125
- } catch {
126
- return "";
127
- }
128
- }
129
-
130
- async function initLanes(config: AssistantConfig): Promise<ShadowLanes> {
131
- const dataDir = resolveDataDir();
132
- const pageIndex = await getPageIndex(getWorkspaceDir());
133
- const pageLeaves = new Map<Slug, LeafPath[]>();
134
- for (const entry of pageIndex.entries) {
135
- pageLeaves.set(entry.slug, entry.leaves);
136
- }
137
- const tree = await loadLeafTree(dataDir, pageLeaves);
138
- const core = await loadCore(dataDir);
139
-
140
- // Always-on synthetic capabilities leaf: skill + CLI-command rows the page
141
- // index already carries (with summaries). Injecting them as leaf members puts
142
- // them in the needle corpus (`tree.byPage`) and, via `core`, makes L1 always
143
- // open the leaf so L2 selects the relevant subset each turn — matching v2's
144
- // "always in the pool, router-selected" capability surfacing. Done before
145
- // `buildNeedleIndex` so the synthetic members are indexed.
146
- const capabilitySlugs = pageIndex.entries
147
- .map((entry) => entry.slug)
148
- .filter(isCapabilitySlug);
149
- injectCapabilitiesLeaf(tree, core, capabilitySlugs);
150
-
151
- const needle = await buildNeedleIndex(tree, pageSummary);
152
- const workingSet = new WorkingSet(
153
- config.memory.v3.workingSet.maxPages,
154
- config.memory.v3.workingSet.evictWindow,
155
- );
156
-
157
- return { tree, core, needle, workingSet };
158
- }
159
-
160
- /** Lazy, memoized accessor for the shadow lanes. */
161
- function getLanes(config: AssistantConfig): Promise<ShadowLanes> {
162
- if (!lanesPromise) {
163
- lanesPromise = initLanes(config).catch((err) => {
164
- // Reset on failure so a transient init error doesn't permanently wedge
165
- // the shadow lane — the next turn retries.
166
- lanesPromise = null;
167
- throw err;
168
- });
169
- }
170
- return lanesPromise;
171
- }
172
-
173
- /**
174
- * Read the live NOW.md scratchpad (the user's short "what's salient right now"
175
- * file), stripped of its comment lines. Mirrors `readNowScratchpad` but reads
176
- * through the light platform / strip utilities directly, keeping the v3
177
- * plugin's load (and its test) free of heavier module graphs. Returns `null`
178
- * when absent, empty, or unreadable.
179
- */
180
- function readNowContext(): string | null {
181
- const nowPath = getWorkspacePromptPath("NOW.md");
182
- if (!existsSync(nowPath)) return null;
183
- try {
184
- const stripped = stripCommentLines(readFileSync(nowPath, "utf-8")).trim();
185
- return stripped.length > 0 ? stripped : null;
186
- } catch {
187
- return null;
188
- }
189
- }
190
-
191
- /**
192
- * Compose the situational signal threaded into L1 routing and L2 selection: the
193
- * current date plus the live NOW.md scratchpad. The date alone is a weak signal,
194
- * but together with the scratchpad it lets retrieval surface a leaf the message
195
- * never names (e.g. an anniversary that falls today). Always returns at least
196
- * the date line — this mirrors the `c_now`/NOW.md signal the v2 retriever uses.
197
- */
198
- function buildSituationalContext(): string {
199
- const now = readNowContext();
200
- const today = `Today is ${new Date().toDateString()}.`;
201
- return now ? `${today}\n\n${now}` : today;
202
- }
203
-
204
- /**
205
- * Build a v3 {@link TurnContext} from the conversation's persisted messages.
206
- * `currentMessage` is the latest user message; `recentContext` is the tail of
207
- * the recent transcript; `situationalContext` carries the current date and the
208
- * live NOW.md scratchpad. Returns `null` when there is no user message to route
209
- * on (nothing to shadow this turn).
210
- */
211
- function buildShadowTurn(
212
- conversationId: string,
213
- turnIndex: number,
214
- ): TurnContext | null {
215
- const rows = getMessages(conversationId);
216
- if (rows.length === 0) return null;
217
-
218
- let currentMessage = "";
219
- for (let i = rows.length - 1; i >= 0; i--) {
220
- if (rows[i]!.role === "user") {
221
- currentMessage = stringifyMessageContent(rows[i]!.content);
222
- if (currentMessage.length > 0) break;
223
- }
224
- }
225
- if (currentMessage.length === 0) return null;
226
-
227
- const recentContext = rows
228
- .slice(-RECENT_CONTEXT_MESSAGES)
229
- .map((r) => stringifyMessageContent(r.content))
230
- .filter((t) => t.length > 0)
231
- .join("\n");
232
-
233
- return {
234
- conversationId,
235
- turnNumber: turnIndex,
236
- currentMessage,
237
- recentContext,
238
- situationalContext: buildSituationalContext(),
239
- };
240
- }
241
-
242
- interface SelectionRow {
243
- slug: Slug;
244
- source: SelectionSource;
245
- pinned: number;
246
- }
247
-
248
- /**
249
- * Map an orchestrate result onto telemetry rows with best-effort lane
250
- * attribution.
251
- *
252
- * - A current selection whose page belongs to a core leaf → `"core+l2"`,
253
- * otherwise `"l1+l2"`.
254
- * - A slug in `finalInjection` but NOT re-selected this turn → `"carry-forward"`.
255
- *
256
- * Precise needle attribution is a documented follow-up: the needle lane only
257
- * widens the open set, so a needle-surfaced page that survives L2 selection is
258
- * indistinguishable here from an L1-routed one. This coarse mapping is
259
- * acceptable for v0 shadow telemetry.
260
- */
261
- function attributeSelections(
262
- tree: LeafTree,
263
- core: Set<LeafPath>,
264
- result: OrchestrateResult,
265
- ): SelectionRow[] {
266
- const coreOwnedSlugs = coreSlugs(tree, core);
267
-
268
- const rows: SelectionRow[] = [];
269
- const seen = new Set<Slug>();
270
- for (const sel of result.currentSelections) {
271
- seen.add(sel.slug);
272
- rows.push({
273
- slug: sel.slug,
274
- source: coreOwnedSlugs.has(sel.slug) ? "core+l2" : "l1+l2",
275
- pinned: sel.pinned ? 1 : 0,
276
- });
277
- }
278
- for (const slug of result.finalInjection) {
279
- if (seen.has(slug)) continue;
280
- seen.add(slug);
281
- rows.push({ slug, source: "carry-forward", pinned: 0 });
282
- }
283
- return rows;
284
- }
285
-
286
- /** Write the attributed selection rows to `memory_v3_selections`. */
287
- function writeSelections(
288
- conversationId: string,
289
- turn: number,
290
- rows: SelectionRow[],
291
- ): void {
292
- if (rows.length === 0) return;
293
- const raw = getSqliteFrom(getDb());
294
- // PK is (conversation_id, turn, slug); OR REPLACE keeps the write
295
- // idempotent if the same turn is observed twice (e.g. a retried turn).
296
- const stmt = raw.query(/*sql*/ `
297
- INSERT OR REPLACE INTO memory_v3_selections (
298
- conversation_id, turn, slug, source, pinned, created_at
299
- ) VALUES (?, ?, ?, ?, ?, ?)
300
- `);
301
- const now = Date.now();
302
- for (const row of rows) {
303
- stmt.run(conversationId, turn, row.slug, row.source, row.pinned, now);
304
- }
305
- }
306
-
307
- /**
308
- * Run v3 orchestration for one turn and log the selection set, returning the
309
- * orchestrate result so a live caller can render it. Never throws — all
310
- * failures are logged and swallowed (returning `null`) so the live turn is
311
- * unaffected. Returns `null` when there is no user message to route on.
312
- */
313
- async function observeTurn(
314
- conversationId: string,
315
- turnIndex: number,
316
- ): Promise<OrchestrateResult | null> {
317
- try {
318
- const turn = buildShadowTurn(conversationId, turnIndex);
319
- if (!turn) return null;
320
-
321
- const cfg = getConfig();
322
- const lanes = await getLanes(cfg);
323
- const result = await orchestrate(turn, {
324
- tree: lanes.tree,
325
- core: lanes.core,
326
- needle: lanes.needle,
327
- workingSet: lanes.workingSet,
328
- pageSummary,
329
- l2Concurrency: cfg.memory.v3.l2Concurrency,
330
- });
331
-
332
- const rows = attributeSelections(lanes.tree, lanes.core, result);
333
- writeSelections(conversationId, turnIndex, rows);
334
- return result;
335
- } catch (err) {
336
- log.warn(
337
- { err: err instanceof Error ? err.message : String(err), conversationId },
338
- "memory-v3 orchestration failed (non-fatal)",
339
- );
340
- return null;
341
- }
342
- }
343
-
344
- /**
345
- * Test-facing shadow wrapper: gate on the shadow flag, then run v3
346
- * orchestration for one turn and log the selection set. Production injection
347
- * goes through `produce()` → {@link observeTurn} directly (which checks both
348
- * flags); this wrapper exists so tests can drive shadow observation in
349
- * isolation. Never throws — all failures are logged and swallowed so the live
350
- * turn is unaffected.
351
- */
352
- export async function runShadowObservation(
353
- conversationId: string,
354
- turnIndex: number,
355
- ): Promise<void> {
356
- if (!isAssistantFeatureFlagEnabled(MEMORY_V3_SHADOW, getConfig())) return;
357
- await observeTurn(conversationId, turnIndex);
358
- }
359
-
360
- /**
361
- * The v3 injector. Reads both flags:
362
- * - `memory-v3-live` on → orchestrate, log, render the working-set selection
363
- * into a `<memory>` block, and return it at v2's dynamic-memory placement.
364
- * - `memory-v3-shadow` on (live off) → orchestrate + log only, return `null`.
365
- * - both off → return `null` (no orchestration).
366
- *
367
- * Empty selection and any failure return `null` (no v3 injection). v2
368
- * suppression keys off BOTH the flag AND this return value, so a `null` here
369
- * (failure or empty selection) falls back to v2 memory rather than dropping all
370
- * memory.
371
- */
372
- export const memoryV3Injector: Injector = {
373
- name: "memory-v3-shadow",
374
- // High order so it sorts last; the live `<memory>` block uses the
375
- // after-memory-prefix placement so it lands at the memory boundary regardless
376
- // of this sort key, which only orders content-producing injectors.
377
- order: 1000,
378
- async produce(ctx: PluginTurnContext): Promise<InjectionBlock | null> {
379
- const config = getConfig();
380
- const live = isAssistantFeatureFlagEnabled(MEMORY_V3_LIVE, config);
381
- const shadow = isAssistantFeatureFlagEnabled(MEMORY_V3_SHADOW, config);
382
- if (!live && !shadow) return null;
383
-
384
- const result = await observeTurn(ctx.conversationId, ctx.turnIndex);
385
- if (!live || !result) return null;
386
-
387
- try {
388
- // `renderMemoryBlock` returns "" for an empty selection; inject nothing.
389
- const text = await renderMemoryBlock(
390
- result.finalInjection,
391
- renderV3PageContent,
392
- );
393
- if (text.length === 0) return null;
394
- return {
395
- id: MEMORY_V3_BLOCK_ID,
396
- text,
397
- // Mirror v2's dynamic `<memory>` block placement.
398
- placement: "after-memory-prefix",
399
- };
400
- } catch (err) {
401
- log.warn(
402
- {
403
- err: err instanceof Error ? err.message : String(err),
404
- conversationId: ctx.conversationId,
405
- },
406
- "memory-v3 live render failed (non-fatal) — falling back to v2",
407
- );
408
- return null;
409
- }
410
- },
411
- };
@@ -1,209 +0,0 @@
1
- import { promises as fs } from "node:fs";
2
- import path from "node:path";
3
-
4
- /**
5
- * Snapshot / restore for the memory-v3 data directory.
6
- *
7
- * The v3 data dir (`<workspace>/memory/v3/data/`) holds the tree's durable
8
- * state: `leaves/**\/*.md`, `assignments.json`, and `core.json` (see
9
- * `resolveDataDir` in `tree.ts`). A future tree-gardening reconciler mutates
10
- * these in place; this module lets it take a snapshot first and roll back if a
11
- * reconcile pass produces a bad tree.
12
- *
13
- * Page `leaves:` frontmatter lives outside the data dir (in the v2 page store
14
- * at `<workspace>/memory/concepts/`). The reconciler can capture the prior
15
- * page->leaves mapping as `pageRefs` and hand it to {@link snapshotDataDir};
16
- * it is persisted alongside the data snapshot and returned by
17
- * {@link restoreDataDir} so the caller can revert those external frontmatter
18
- * edits too.
19
- */
20
-
21
- /** Files (relative to the data dir) captured by a snapshot. */
22
- const SNAPSHOT_FILES = ["assignments.json", "core.json"] as const;
23
- /** Directories (relative to the data dir) captured recursively. */
24
- const SNAPSHOT_DIRS = ["leaves"] as const;
25
- /** Filename for the persisted page->leaves diff inside a snapshot. */
26
- const PAGE_REFS_FILE = "page-refs.json";
27
- /** Sibling directory (relative to the data dir) that holds snapshots. */
28
- const SNAPSHOTS_DIRNAME = "v3-snapshots";
29
- /** Maximum number of snapshots to retain; older ones are pruned. */
30
- const MAX_RETAINED_SNAPSHOTS = 5;
31
-
32
- function snapshotsRoot(dataDir: string): string {
33
- return path.join(path.dirname(dataDir), SNAPSHOTS_DIRNAME);
34
- }
35
-
36
- async function copyFileIfExists(src: string, dest: string): Promise<void> {
37
- try {
38
- await fs.mkdir(path.dirname(dest), { recursive: true });
39
- await fs.copyFile(src, dest);
40
- } catch (err) {
41
- if ((err as NodeJS.ErrnoException).code === "ENOENT") return;
42
- throw err;
43
- }
44
- }
45
-
46
- /** Copy `src` into `dest` recursively. Returns false if `src` doesn't exist. */
47
- async function copyDirIfExists(src: string, dest: string): Promise<boolean> {
48
- let entries: import("node:fs").Dirent[];
49
- try {
50
- entries = await fs.readdir(src, { withFileTypes: true });
51
- } catch (err) {
52
- if ((err as NodeJS.ErrnoException).code === "ENOENT") return false;
53
- throw err;
54
- }
55
- await fs.mkdir(dest, { recursive: true });
56
- for (const entry of entries) {
57
- const from = path.join(src, entry.name);
58
- const to = path.join(dest, entry.name);
59
- if (entry.isDirectory()) {
60
- await copyDirIfExists(from, to);
61
- } else if (entry.isFile()) {
62
- await fs.copyFile(from, to);
63
- }
64
- }
65
- return true;
66
- }
67
-
68
- async function removeIfExists(target: string): Promise<void> {
69
- await fs.rm(target, { recursive: true, force: true });
70
- }
71
-
72
- function serializePageRefs(pageRefs: Map<string, string[]>): string {
73
- return JSON.stringify(Object.fromEntries(pageRefs), null, 2);
74
- }
75
-
76
- function deserializePageRefs(raw: string): Map<string, string[]> {
77
- const obj = JSON.parse(raw) as Record<string, string[]>;
78
- return new Map(Object.entries(obj));
79
- }
80
-
81
- /**
82
- * Copy the v3 data dir's durable state into a timestamped sibling snapshot
83
- * directory and return its path.
84
- *
85
- * @param dataDir Absolute path to the v3 data dir.
86
- * @param opts.pageRefs Optional slug->prior-leaves map describing page
87
- * frontmatter that lives outside the data dir; persisted so a later restore
88
- * can revert it.
89
- * @param opts.label Snapshot directory name. Defaults to `Date.now()`; pass an
90
- * explicit label to keep tests deterministic.
91
- * @returns Absolute path to the created snapshot directory.
92
- */
93
- export async function snapshotDataDir(
94
- dataDir: string,
95
- opts?: { pageRefs?: Map<string, string[]>; label?: string },
96
- ): Promise<string> {
97
- const label = opts?.label ?? String(Date.now());
98
- const snapshotPath = path.join(snapshotsRoot(dataDir), label);
99
- await fs.mkdir(snapshotPath, { recursive: true });
100
-
101
- for (const file of SNAPSHOT_FILES) {
102
- await copyFileIfExists(
103
- path.join(dataDir, file),
104
- path.join(snapshotPath, file),
105
- );
106
- }
107
- for (const dir of SNAPSHOT_DIRS) {
108
- await copyDirIfExists(
109
- path.join(dataDir, dir),
110
- path.join(snapshotPath, dir),
111
- );
112
- }
113
- if (opts?.pageRefs) {
114
- await fs.writeFile(
115
- path.join(snapshotPath, PAGE_REFS_FILE),
116
- serializePageRefs(opts.pageRefs),
117
- );
118
- }
119
-
120
- await pruneSnapshots(dataDir);
121
- return snapshotPath;
122
- }
123
-
124
- /**
125
- * Atomically replace the v3 data dir's durable state from a snapshot.
126
- *
127
- * Each captured file/dir is staged next to its destination and renamed into
128
- * place, so a crash mid-restore never leaves a half-copied tree.
129
- *
130
- * @param snapshotPath Absolute path to a directory created by
131
- * {@link snapshotDataDir}.
132
- * @param dataDir Absolute path to the v3 data dir to overwrite.
133
- * @returns The persisted `pageRefs` map (if the snapshot captured one) so the
134
- * caller can revert external page frontmatter.
135
- */
136
- export async function restoreDataDir(
137
- snapshotPath: string,
138
- dataDir: string,
139
- ): Promise<{ pageRefs?: Map<string, string[]> }> {
140
- await fs.mkdir(dataDir, { recursive: true });
141
-
142
- for (const file of SNAPSHOT_FILES) {
143
- const src = path.join(snapshotPath, file);
144
- const dest = path.join(dataDir, file);
145
- const tmp = `${dest}.restore-tmp`;
146
- try {
147
- await fs.copyFile(src, tmp);
148
- } catch (err) {
149
- if ((err as NodeJS.ErrnoException).code === "ENOENT") {
150
- // Snapshot didn't capture this file; drop any stale destination copy.
151
- await removeIfExists(dest);
152
- continue;
153
- }
154
- throw err;
155
- }
156
- await fs.rename(tmp, dest);
157
- }
158
-
159
- for (const dir of SNAPSHOT_DIRS) {
160
- const src = path.join(snapshotPath, dir);
161
- const dest = path.join(dataDir, dir);
162
- const tmp = `${dest}.restore-tmp`;
163
- await removeIfExists(tmp);
164
- const captured = await copyDirIfExists(src, tmp);
165
- if (!captured) {
166
- // Snapshot didn't capture this dir; drop any stale destination copy.
167
- await removeIfExists(dest);
168
- continue;
169
- }
170
- await removeIfExists(dest);
171
- await fs.rename(tmp, dest);
172
- }
173
-
174
- const pageRefsPath = path.join(snapshotPath, PAGE_REFS_FILE);
175
- try {
176
- const raw = await fs.readFile(pageRefsPath, "utf8");
177
- return { pageRefs: deserializePageRefs(raw) };
178
- } catch (err) {
179
- if ((err as NodeJS.ErrnoException).code === "ENOENT") return {};
180
- throw err;
181
- }
182
- }
183
-
184
- /**
185
- * Delete all but the most recent {@link MAX_RETAINED_SNAPSHOTS} snapshot
186
- * directories. Snapshots sort lexicographically by their label; numeric
187
- * timestamps and zero-padded labels both order chronologically.
188
- */
189
- async function pruneSnapshots(dataDir: string): Promise<void> {
190
- const root = snapshotsRoot(dataDir);
191
- let entries: import("node:fs").Dirent[];
192
- try {
193
- entries = await fs.readdir(root, { withFileTypes: true });
194
- } catch (err) {
195
- if ((err as NodeJS.ErrnoException).code === "ENOENT") return;
196
- throw err;
197
- }
198
- const dirs = entries
199
- .filter((e) => e.isDirectory())
200
- .map((e) => e.name)
201
- .sort();
202
- const stale = dirs.slice(
203
- 0,
204
- Math.max(0, dirs.length - MAX_RETAINED_SNAPSHOTS),
205
- );
206
- for (const name of stale) {
207
- await removeIfExists(path.join(root, name));
208
- }
209
- }