@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,266 @@
1
+ /**
2
+ * Memory v3 — orchestrator composing the section-grain lanes into one turn.
3
+ *
4
+ * Each turn runs:
5
+ * 1. Candidate generation over three deterministic lanes:
6
+ * - the section-grain BM25 needle (`SectionNeedle.query`, KB articles),
7
+ * - the dense lane (`denseLane`, KD articles), and
8
+ * - link-graph edge expansion (`edgeExpand`) over the top needle+dense
9
+ * article seeds.
10
+ * Each lane only ever ADDS candidates, so the pool is recall-safe by
11
+ * construction.
12
+ * 2. Build the unified candidate pool of `{ slug, descriptor }`. The
13
+ * descriptor is the matched section's text for needle/dense hits (resolved
14
+ * via the section index); for edge-only pages it is the curated `links`
15
+ * description when the traversed edge carried one, else the page's best
16
+ * section against the query. Edge-only pages record NO matched section, so
17
+ * they inject the FULL page (the lead the query never hit is often empty;
18
+ * the link-relevant content is elsewhere) — the best-section text is only a
19
+ * descriptor fallback. Synthetic capability pages (skills / CLI commands)
20
+ * carry sections in the section index too, so they enter the pool through
21
+ * the lanes like any other page rather than being always-added.
22
+ * 3. A SINGLE forced-tool select (`selectPool`) over the whole pool. The
23
+ * result is this turn's selections.
24
+ * 4. Age the carry-forward working set to this turn (evict stale non-pinned
25
+ * entries, then the cap) and snapshot it — the pages carried in from
26
+ * EARLIER turns.
27
+ * 5. Final injection = unique union of this turn's selected slugs and that
28
+ * carried-forward set, so pages selected on earlier turns carry forward
29
+ * even when this turn does not re-select them.
30
+ * 6. Record this turn's selections into the working set for LATER turns. This
31
+ * runs AFTER the snapshot so the cap is spent on genuinely carried pages,
32
+ * not on this turn's selections (which are injected directly) — otherwise a
33
+ * turn selecting more pages than the cap would evict the entire carry.
34
+ */
35
+
36
+ import type { AssistantConfig } from "../../../config/schema.js";
37
+ import { denseLane } from "./dense.js";
38
+ import type { EdgeGraph } from "./edge.js";
39
+ import { edgeExpand } from "./edge.js";
40
+ import type { PoolCandidate } from "./pool-select.js";
41
+ import { selectPool } from "./pool-select.js";
42
+ import type { SectionNeedle } from "./section-needle.js";
43
+ import type {
44
+ CandidateLane,
45
+ MemoryRoutingTurn,
46
+ Section,
47
+ SectionIndex,
48
+ SelectedPage,
49
+ Slug,
50
+ } from "./types.js";
51
+ import { WorkingSet } from "./working-set.js";
52
+
53
+ /** Default number of BM25 needle articles to fold into the pool. */
54
+ export const DEFAULT_NEEDLE_K = 100;
55
+ /** Default number of dense-lane articles to fold into the pool. */
56
+ export const DEFAULT_DENSE_K = 100;
57
+
58
+ export interface OrchestrateDeps {
59
+ sectionIndex: SectionIndex;
60
+ needle: SectionNeedle;
61
+ /** Config the dense lane needs to embed the query + search the section
62
+ * collection. */
63
+ denseConfig: AssistantConfig;
64
+ edgeGraph: EdgeGraph;
65
+ workingSet: WorkingSet;
66
+ /** Number of BM25 needle articles. Defaults to {@link DEFAULT_NEEDLE_K}. */
67
+ needleK?: number;
68
+ /** Number of dense-lane articles. Defaults to {@link DEFAULT_DENSE_K}. */
69
+ denseK?: number;
70
+ /** Number of top needle+dense seeds expanded. When omitted, the edge lane's
71
+ * own default applies (canonical value: `memory.v3.edge.seedCount`). */
72
+ edgeSeeds?: number;
73
+ /** Neighbours surfaced per expanded edge seed. When omitted, the edge lane's
74
+ * own default applies (canonical value: `memory.v3.edge.perSeed`). */
75
+ edgePerSeed?: number;
76
+ /** Hard cap on total edge-lane surfaced articles. When omitted, the edge
77
+ * lane's own default applies (canonical value: `memory.v3.edge.cap`). */
78
+ edgeCap?: number;
79
+ }
80
+
81
+ export interface OrchestrateResult {
82
+ /** This turn's selections, deduped by slug (pinned flags ORed). */
83
+ currentSelections: SelectedPage[];
84
+ /** The carried-forward set: selections from EARLIER turns, aged to this turn
85
+ * (snapshotted before this turn's selections are recorded). */
86
+ workingSetUnion: Set<Slug>;
87
+ /** Slugs to inject: this turn's selections ∪ the carried-forward working set. */
88
+ finalInjection: Slug[];
89
+ /** The matched `Section` for each pooled slug that had one, keyed by slug.
90
+ * Populated from the lane hits and consumed by the injector to render each
91
+ * selected slug's matched section (progressive disclosure). */
92
+ sectionBySlug: Map<Slug, Section>;
93
+ /** The candgen lane that FIRST surfaced each pooled slug, keyed by slug.
94
+ * Insertion order is needle → dense → edge, so a page surfaced by more than
95
+ * one lane records the highest-precedence one. Consumed by the selection
96
+ * telemetry to attribute each selection to its true lane. */
97
+ laneBySlug: Map<Slug, CandidateLane>;
98
+ }
99
+
100
+ /** Stable-order de-duplication preserving first occurrence. */
101
+ function unique<T>(items: Iterable<T>): T[] {
102
+ return [...new Set(items)];
103
+ }
104
+
105
+ export async function orchestrate(
106
+ turn: MemoryRoutingTurn,
107
+ deps: OrchestrateDeps,
108
+ ): Promise<OrchestrateResult> {
109
+ const needleK = deps.needleK ?? DEFAULT_NEEDLE_K;
110
+ const denseK = deps.denseK ?? DEFAULT_DENSE_K;
111
+ const { sections } = deps.sectionIndex;
112
+
113
+ // Step 1: needle (sync BM25) and dense (async embed + Qdrant) lanes run in
114
+ // parallel. Both return distinct articles each tagged with their best-scoring
115
+ // section index/ordinal.
116
+ const [needled, densed] = await Promise.all([
117
+ Promise.resolve(deps.needle.query(turn.currentMessage, needleK)),
118
+ denseLane(deps.denseConfig, turn.currentMessage, denseK),
119
+ ]);
120
+
121
+ // The pool accumulates one entry per distinct article. `sectionBySlug` records
122
+ // the matched `Section` (when one is known) for downstream injection;
123
+ // `laneBySlug` records the lane that first surfaced each slug for telemetry.
124
+ const poolBySlug = new Map<Slug, PoolCandidate>();
125
+ const sectionBySlug = new Map<Slug, Section>();
126
+ const laneBySlug = new Map<Slug, CandidateLane>();
127
+
128
+ // Add one pool candidate, recording its matched `Section` (when known) for
129
+ // downstream injection and the `lane` that surfaced it for telemetry.
130
+ // `descriptor` overrides the section text when supplied (the edge lane prefers
131
+ // a curated `links` description). The first lane to surface a slug wins the
132
+ // pool entry, the recorded section, AND the recorded lane — so the
133
+ // needle → dense → edge call order encodes lane precedence.
134
+ const addCandidate = (
135
+ slug: Slug,
136
+ section: Section | undefined,
137
+ descriptor: string | undefined,
138
+ lane: CandidateLane,
139
+ ): void => {
140
+ if (section && !sectionBySlug.has(slug)) {
141
+ sectionBySlug.set(slug, section);
142
+ }
143
+ if (poolBySlug.has(slug)) return;
144
+ laneBySlug.set(slug, lane);
145
+ poolBySlug.set(slug, {
146
+ slug,
147
+ descriptor: descriptor ?? section?.text ?? "",
148
+ });
149
+ };
150
+
151
+ // Step 2a: needle hits — descriptor is the matched section's text. `section`
152
+ // is an index into `sections`.
153
+ for (const hit of needled) {
154
+ addCandidate(hit.article, sections[hit.section], undefined, "needle");
155
+ }
156
+
157
+ // Step 2b: dense hits — `section` is the matched ORDINAL; resolve it to the
158
+ // concrete `Section` via the section index. Falls back to undefined (blank
159
+ // descriptor) if the ordinal is not in the in-memory index.
160
+ for (const hit of densed) {
161
+ // A deleted page's points can linger in Qdrant; keep only live-index
162
+ // articles. The section index is rebuilt from `getPageIndex` at `initLanes`,
163
+ // so `byArticle` holds exactly the live pages (synthetic capability slugs
164
+ // included) — only truly-deleted pages are dropped here.
165
+ if (!deps.sectionIndex.byArticle.has(hit.article)) continue;
166
+ addCandidate(
167
+ hit.article,
168
+ sectionByOrdinal(deps.sectionIndex, hit.article, hit.section),
169
+ undefined,
170
+ "dense",
171
+ );
172
+ }
173
+
174
+ // Step 2c: edge expansion over the top needle+dense article seeds. `alive`
175
+ // skips slugs already pooled. An edge-only page was surfaced because its
176
+ // NEIGHBOUR matched — the query did not lexically hit the page itself, so
177
+ // `bestSection` returns its first/lead section on a zero-score match. That
178
+ // lead is often empty for heading-structured pages, and it is the curated
179
+ // `links` description (not the lead) that made the candidate relevant. So we
180
+ // record NO matched section for edge-only pages (pass `undefined`), which
181
+ // makes injection fall back to the FULL page — where the link-relevant content
182
+ // lives. `bestSection`'s text is kept only as the select-pool DESCRIPTOR
183
+ // fallback for when the traversed edge carried no curated `links` description.
184
+ const seeds = unique<Slug>([
185
+ ...needled.map((h) => h.article),
186
+ ...densed.map((h) => h.article),
187
+ ]);
188
+ const surfaced = edgeExpand(deps.edgeGraph, seeds, {
189
+ seedCount: deps.edgeSeeds,
190
+ perSeed: deps.edgePerSeed,
191
+ cap: deps.edgeCap,
192
+ alive: (slug) => !poolBySlug.has(slug),
193
+ });
194
+ for (const neighbor of surfaced) {
195
+ const best = deps.needle.bestSection(neighbor.article, turn.currentMessage);
196
+ const fallbackDescriptor = best >= 0 ? sections[best]?.text : undefined;
197
+ addCandidate(
198
+ neighbor.article,
199
+ undefined,
200
+ neighbor.description ?? fallbackDescriptor,
201
+ "edge",
202
+ );
203
+ }
204
+
205
+ // Step 3: a SINGLE forced-tool select over the unified pool.
206
+ const selected = await selectPool([...poolBySlug.values()], turn);
207
+ const bySlug = new Map<Slug, SelectedPage>();
208
+ for (const page of selected) {
209
+ const existing = bySlug.get(page.slug);
210
+ bySlug.set(page.slug, {
211
+ slug: page.slug,
212
+ pinned: (existing?.pinned ?? false) || page.pinned,
213
+ });
214
+ }
215
+ const currentSelections = [...bySlug.values()];
216
+
217
+ // Step 4: age the carry-forward set to this turn (drop stale non-pinned
218
+ // entries, then the cap) and snapshot it. This is the set carried in from
219
+ // EARLIER turns; recording this turn happens afterward (step 6) so the cap is
220
+ // spent on genuinely carried pages, not on this turn's selections (which are
221
+ // injected directly anyway).
222
+ deps.workingSet.evict(turn.turnNumber);
223
+ const workingSetUnion = deps.workingSet.union();
224
+
225
+ // Step 5: final injection = this turn's selections ∪ the carried-forward set,
226
+ // so pages selected on earlier turns carry forward even when this turn does
227
+ // not re-select them.
228
+ const finalInjection = unique<Slug>([
229
+ ...currentSelections.map((s) => s.slug),
230
+ ...workingSetUnion,
231
+ ]);
232
+
233
+ // Step 6: record this turn's selections so they carry forward to LATER turns.
234
+ for (const sel of currentSelections) {
235
+ deps.workingSet.recordSelection(sel.slug, turn.turnNumber, sel.pinned);
236
+ }
237
+
238
+ return {
239
+ currentSelections,
240
+ workingSetUnion,
241
+ finalInjection,
242
+ sectionBySlug,
243
+ laneBySlug,
244
+ };
245
+ }
246
+
247
+ /**
248
+ * Resolve a dense-lane hit's matched ordinal to the concrete `Section` in the
249
+ * in-memory index. The dense store keys sections by `(article, ordinal)`, so we
250
+ * scan the article's sections for the matching ordinal. Returns `undefined`
251
+ * when the article or ordinal is not in the index (e.g. the dense store is
252
+ * ahead of the in-memory rebuild).
253
+ */
254
+ function sectionByOrdinal(
255
+ index: SectionIndex,
256
+ article: Slug,
257
+ ordinal: number,
258
+ ): Section | undefined {
259
+ const indices = index.byArticle.get(article);
260
+ if (!indices) return undefined;
261
+ for (const i of indices) {
262
+ const section = index.sections[i];
263
+ if (section && section.ordinal === ordinal) return section;
264
+ }
265
+ return undefined;
266
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "default-memory-v3-shadow",
3
+ "version": "1.0.0",
4
+ "description": "First-party default plugin housing the memory-v3 shadow/live orchestration engine and its injector.",
5
+ "private": true,
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "engines": {
9
+ "node": ">=20.12.0"
10
+ },
11
+ "peerDependencies": {
12
+ "@vellumai/plugin-api": "^0.8.0"
13
+ }
14
+ }
@@ -0,0 +1,71 @@
1
+ import { readPage, renderPageContent } from "../../../memory/v2/page-store.js";
2
+ import { getWorkspaceDir } from "../../../util/platform.js";
3
+ import { renderCapabilityContent } from "./capabilities.js";
4
+ import type { Section, Slug } from "./types.js";
5
+
6
+ /**
7
+ * Prefix `body` with the `# memory/concepts/<slug>.md` header marker the v3
8
+ * `<memory>` block uses. Both the full-page and matched-section renderers emit
9
+ * this exact marker, and the v2 stripper recognizes pages by it — so the two
10
+ * call sites MUST stay byte-identical, which is why this lives in one place.
11
+ */
12
+ function withConceptHeader(slug: Slug, body: string): string {
13
+ return `# memory/concepts/${slug}.md\n${body}`;
14
+ }
15
+
16
+ /**
17
+ * Render a selected page's full content for the v3 `<memory>` block. Mirrors
18
+ * the v2 dynamic-memory layout (`# memory/concepts/<slug>.md\n<frontmatter+body>`)
19
+ * so the working-set block reads like v2's. A missing page (or any read
20
+ * failure) degrades to "" — `renderMemoryBlock` still emits a line for the
21
+ * slug, and a blank section is preferable to throwing into the turn.
22
+ *
23
+ * Synthetic capability slugs (skills, `assistant` CLI commands) have no on-disk
24
+ * page; they resolve through {@link renderCapabilityContent} instead of
25
+ * `readPage`. A non-null result (including "") means the slug was a capability
26
+ * slug and was handled here.
27
+ *
28
+ * Shared by the live injector (`shadow-plugin.ts`) and the inspector
29
+ * selection-log store (`selection-log-store.ts`) so the inspector's rendered
30
+ * block is byte-identical to what live injection produces.
31
+ */
32
+ export async function renderV3PageContent(slug: Slug): Promise<string> {
33
+ const capability = renderCapabilityContent(slug);
34
+ if (capability !== null) return capability;
35
+ try {
36
+ const page = await readPage(getWorkspaceDir(), slug);
37
+ if (!page) return "";
38
+ const content = renderPageContent(page).trim();
39
+ if (content.length === 0) return "";
40
+ return withConceptHeader(slug, content);
41
+ } catch {
42
+ return "";
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Render a slug's matched section for the v3 `<memory>` block (progressive
48
+ * disclosure: inject only the section the lanes matched, not the full page).
49
+ * Uses the same `# memory/concepts/<slug>.md` header marker as
50
+ * {@link renderV3PageContent} so the block reads like v2's and the v2 stripper
51
+ * recognizes it.
52
+ *
53
+ * - Capability slugs (skills, `assistant` CLI commands) have no on-disk
54
+ * section, so they always render their capability content via
55
+ * {@link renderCapabilityContent}, never a section.
56
+ * - When `section` is undefined (e.g. a carry-forward page with no current
57
+ * match), fall back to {@link renderV3PageContent} (the full/lead page) so the
58
+ * slug still contributes content.
59
+ */
60
+ export async function renderV3SectionContent(
61
+ slug: Slug,
62
+ section: Section | undefined,
63
+ ): Promise<string> {
64
+ const capability = renderCapabilityContent(slug);
65
+ if (capability !== null) return capability;
66
+ if (!section) return renderV3PageContent(slug);
67
+
68
+ const text = section.text.trim();
69
+ if (text.length === 0) return "";
70
+ return withConceptHeader(slug, text);
71
+ }
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Memory v3 — single pool selector.
3
+ *
4
+ * Runs a SINGLE forced-tool call over one unified candidate pool. Each candidate
5
+ * is a page slug paired with a per-candidate `descriptor` the caller supplies —
6
+ * the matched section text for a needle/dense hit, or a curated link description
7
+ * for an edge page. The caller assembles the pool by unioning the section lanes;
8
+ * the pool selector only decides which of those candidates the reply will draw
9
+ * on.
10
+ *
11
+ * No cache breakpoint. The pool is recomputed from per-turn section matches and
12
+ * is therefore dynamic per turn, so it carries no `cache_control` breakpoint:
13
+ * caching a prefix that changes every turn would never hit. Carry-forward (the
14
+ * working set unioned in by the orchestrator) is the cache mechanism here, not a
15
+ * static prefix breakpoint.
16
+ *
17
+ * Failure handling is recall-safe by construction:
18
+ * - explicit `ids` → select exactly those candidates,
19
+ * - explicit empty `ids: []` → select none (deliberate abstention),
20
+ * - omitted `ids` → keep ALL candidates (the recall-safe "all of these are
21
+ * relevant" signal),
22
+ * - infrastructure failure (provider unavailable, a throw that survived the
23
+ * provider's own retries, no usable `tool_use`, or a schema mismatch) →
24
+ * select nothing after a short re-prompt retry, degrading to the
25
+ * deterministic recall lanes the orchestrator unions in regardless.
26
+ */
27
+
28
+ import { z } from "zod";
29
+
30
+ import {
31
+ extractToolUse,
32
+ getConfiguredProvider,
33
+ } from "../../../providers/provider-send-message.js";
34
+ import type { Message, ToolDefinition } from "../../../providers/types.js";
35
+ import { getLogger } from "../../../util/logger.js";
36
+ import { truncate } from "../../../util/truncate.js";
37
+ import { retryForResult } from "./llm-retry.js";
38
+ import type { MemoryRoutingTurn, SelectedPage, Slug } from "./types.js";
39
+
40
+ const log = getLogger("memory-v3-pool-select");
41
+
42
+ /** A candidate page in the unified pool, with the descriptor that justifies it. */
43
+ export interface PoolCandidate {
44
+ slug: Slug;
45
+ /**
46
+ * The text that justifies this candidate: a matched section for a
47
+ * needle/dense hit, or a curated link description for an edge page. Rendered
48
+ * (truncated) in the numbered candidate list the selector judges against.
49
+ */
50
+ descriptor: string;
51
+ }
52
+
53
+ /** Tool name forced via `tool_choice`. Shared constant so tests can match it. */
54
+ const SELECT_PAGES_TOOL_NAME = "select_pages";
55
+
56
+ /** Descriptors are truncated to keep the candidate list compact. */
57
+ const DESCRIPTOR_MAX_CHARS = 400;
58
+
59
+ const SelectPagesSchema = z.object({
60
+ // Optional: an omitted `ids` field is the recall-safe "keep everything"
61
+ // signal, distinct from an explicit empty array (deliberate abstention).
62
+ ids: z.array(z.number().int()).optional(),
63
+ pinned_ids: z.array(z.number().int()).optional(),
64
+ });
65
+
66
+ const SELECT_PAGES_TOOL: ToolDefinition = {
67
+ name: SELECT_PAGES_TOOL_NAME,
68
+ description:
69
+ "Select the candidate pages whose content the reply would directly draw " +
70
+ "on. Lean inclusive — when in doubt, keep a candidate; for a list or " +
71
+ '"all of X" request keep every candidate that belongs. Pass `pinned_ids` ' +
72
+ "for pages the conversation is centrally about. Omit `ids` only as a " +
73
+ "recall-safe fallback when you cannot judge the pool (keeps every " +
74
+ "candidate); return `[]` when candidates are present but none are " +
75
+ "relevant.",
76
+ input_schema: {
77
+ type: "object",
78
+ properties: {
79
+ ids: {
80
+ type: "array",
81
+ items: { type: "integer" },
82
+ },
83
+ pinned_ids: {
84
+ type: "array",
85
+ items: { type: "integer" },
86
+ },
87
+ },
88
+ },
89
+ };
90
+
91
+ const SYSTEM_PROMPT = `You are given a pool of candidate memory pages, each surfaced because some part of it matched the conversation. Select every page whose specific content the reply to THIS message would directly draw on.
92
+
93
+ Lean inclusive: recall matters more than precision here, so when a candidate could plausibly inform the reply, keep it. For a list or an "all of X" request, keep EVERY candidate that belongs to X rather than guessing a representative subset.
94
+
95
+ A page can be relevant because of the current situation — the date or the live scratchpad — not only the message: keep a page the situation makes pertinent (e.g. a person whose anniversary is today).
96
+
97
+ If the conversation is centrally ABOUT a page (rather than only peripherally relevant to it), mark that page as pinned. Call \`select_pages\` with the chosen IDs. Omit \`ids\` only as a recall-safe fallback when you cannot judge the pool (keeps every candidate); return \`[]\` when candidates are present but none are relevant.`;
98
+
99
+ /** Collapse a descriptor to one line and cap its length for the candidate list. */
100
+ function renderDescriptor(descriptor: string): string {
101
+ return truncate(descriptor.replace(/\s+/g, " ").trim(), DESCRIPTOR_MAX_CHARS);
102
+ }
103
+
104
+ /**
105
+ * Render the numbered candidate list: one `[i] slug — descriptor` line per
106
+ * candidate, descriptors collapsed to one line and length-capped. The pool is
107
+ * dynamic per turn, so this block is NOT cached (see the module doc).
108
+ */
109
+ function renderCandidateList(pool: PoolCandidate[]): string {
110
+ const lines = pool.map(
111
+ (c, i) => `[${i + 1}] ${c.slug} — ${renderDescriptor(c.descriptor)}`,
112
+ );
113
+ return `<candidates>\n${lines.join("\n")}\n</candidates>`;
114
+ }
115
+
116
+ /**
117
+ * Run the single forced-tool selector over the unified candidate pool. Returns
118
+ * the pages to inject.
119
+ *
120
+ * An omitted `ids` keeps ALL candidates (the recall-safe "all of these are
121
+ * relevant" signal); an explicit `[]` keeps none; an infrastructure failure
122
+ * (after a short re-prompt retry) keeps none, degrading to the deterministic
123
+ * recall lanes the orchestrator unions in.
124
+ */
125
+ export async function selectPool(
126
+ pool: PoolCandidate[],
127
+ turn: MemoryRoutingTurn,
128
+ ): Promise<SelectedPage[]> {
129
+ if (pool.length === 0) return [];
130
+
131
+ const keepAll = (): SelectedPage[] =>
132
+ pool.map((c) => ({ slug: c.slug, pinned: false }));
133
+
134
+ const provider = await getConfiguredProvider("memoryV3SelectL2");
135
+ if (!provider) {
136
+ log.warn(
137
+ { candidateCount: pool.length },
138
+ "pool selector provider unavailable; degrading to deterministic lanes",
139
+ );
140
+ return [];
141
+ }
142
+
143
+ // The pool is dynamic per turn, so there is no static cache breakpoint here;
144
+ // carry-forward is the cache mechanism. The candidate list and the per-turn
145
+ // context go in a single plain text block.
146
+ const userMsg: Message = {
147
+ role: "user",
148
+ content: [
149
+ {
150
+ type: "text",
151
+ text:
152
+ `${renderCandidateList(pool)}\n` +
153
+ (turn.situationalContext
154
+ ? `<situation>${turn.situationalContext}</situation>\n`
155
+ : "") +
156
+ `<recent_context>${turn.recentContext}</recent_context>\n` +
157
+ `<current_message>${turn.currentMessage}</current_message>`,
158
+ },
159
+ ],
160
+ };
161
+
162
+ // One forced-tool call, retried a few times so a transient malformed response
163
+ // (no usable tool_use, or tool input that fails the schema) re-prompts before
164
+ // we give up. `null` from an attempt means "unusable, retry"; the provider
165
+ // layer already backs off transient throws, so this loop adds no delay.
166
+ const parsed = await retryForResult(async () => {
167
+ const response = await provider.sendMessage([userMsg], {
168
+ tools: [SELECT_PAGES_TOOL],
169
+ systemPrompt: SYSTEM_PROMPT,
170
+ config: {
171
+ callSite: "memoryV3SelectL2" as const,
172
+ tool_choice: { type: "tool" as const, name: SELECT_PAGES_TOOL_NAME },
173
+ },
174
+ });
175
+ const toolBlock = extractToolUse(response);
176
+ if (!toolBlock || toolBlock.name !== SELECT_PAGES_TOOL_NAME) return null;
177
+ const result = SelectPagesSchema.safeParse(toolBlock.input);
178
+ return result.success ? result.data : null;
179
+ });
180
+
181
+ if (parsed === null) {
182
+ log.warn(
183
+ { candidateCount: pool.length },
184
+ "pool selector could not obtain a selection after retries; degrading to deterministic lanes",
185
+ );
186
+ return [];
187
+ }
188
+
189
+ // Omitted `ids` is the recall-safe "keep all candidates" signal.
190
+ if (parsed.ids === undefined) return keepAll();
191
+
192
+ const pinned = new Set(parsed.pinned_ids ?? []);
193
+
194
+ // Map 1-based IDs back to candidate slugs, dropping out-of-range IDs without
195
+ // throwing. De-duplicate while preserving model-returned order.
196
+ const seen = new Set<number>();
197
+ const selected: SelectedPage[] = [];
198
+ for (const id of parsed.ids) {
199
+ if (id < 1 || id > pool.length || seen.has(id)) continue;
200
+ seen.add(id);
201
+ selected.push({ slug: pool[id - 1]!.slug, pinned: pinned.has(id) });
202
+ }
203
+ return selected;
204
+ }
@@ -1,13 +1,15 @@
1
- import { wrapMemoryBlock } from "../memory-marker.js";
2
- import { Slug } from "./types.js";
1
+ import { wrapMemoryBlock } from "../../../memory/memory-marker.js";
2
+ import { Section, Slug } from "./types.js";
3
3
 
4
4
  /**
5
5
  * Render a v3 working-set selection into a single `<memory>…</memory>` text
6
- * block.
6
+ * block, injecting each slug's matched section (progressive disclosure) rather
7
+ * than its full page body.
7
8
  *
8
9
  * Pure and side-effect-free: all I/O is delegated to the injected
9
- * `pageContent` resolver, which is awaited once per slug in input order. The
10
- * resulting block uses the shared {@link wrapMemoryBlock} marker — the same
10
+ * `pageContent` resolver, which is awaited once per slug in input order with
11
+ * that slug's matched section (`sectionBySlug.get(slug)`, possibly undefined).
12
+ * The resulting block uses the shared {@link wrapMemoryBlock} marker — the same
11
13
  * wrapper the v2 graph-memory path emits — so the existing strip machinery in
12
14
  * `conversation-graph-memory.ts` already recognizes (and evicts) it.
13
15
  *
@@ -16,17 +18,23 @@ import { Slug } from "./types.js";
16
18
  *
17
19
  * @param finalInjection - The working-set selection for the turn, in the order
18
20
  * it should appear in the rendered block.
19
- * @param pageContent - Resolver returning a page's rendered content for a slug.
21
+ * @param sectionBySlug - The matched `Section` per slug; a slug with no entry
22
+ * (e.g. carry-forward with no current match) renders its full/lead page.
23
+ * @param pageContent - Resolver returning a slug's rendered content given its
24
+ * matched section (or undefined).
20
25
  * @returns The wrapped `<memory>` block, or `""` for an empty selection (the
21
26
  * caller injects nothing).
22
27
  */
23
28
  export async function renderMemoryBlock(
24
29
  finalInjection: Slug[],
25
- pageContent: (slug: Slug) => Promise<string>,
30
+ sectionBySlug: Map<Slug, Section>,
31
+ pageContent: (slug: Slug, section: Section | undefined) => Promise<string>,
26
32
  ): Promise<string> {
27
33
  if (finalInjection.length === 0) return "";
28
34
 
29
- const contents = await Promise.all(finalInjection.map(pageContent));
35
+ const contents = await Promise.all(
36
+ finalInjection.map((slug) => pageContent(slug, sectionBySlug.get(slug))),
37
+ );
30
38
 
31
39
  return wrapMemoryBlock(contents.join("\n"));
32
40
  }