@vellumai/assistant 0.8.8 → 0.8.9-staging.2

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 (360) hide show
  1. package/ARCHITECTURE.md +6 -6
  2. package/bun.lock +2 -2
  3. package/examples/plugins/echo/README.md +61 -60
  4. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  5. package/examples/plugins/echo/hooks/stop.ts +16 -0
  6. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  7. package/examples/plugins/echo/package.json +1 -2
  8. package/examples/plugins/echo/src/emit.ts +19 -0
  9. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  10. package/openapi.yaml +235 -6
  11. package/package.json +2 -2
  12. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  13. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  14. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  15. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  16. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  17. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  18. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  19. package/src/__tests__/agent-loop.test.ts +441 -96
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  21. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  22. package/src/__tests__/anthropic-provider.test.ts +1 -1
  23. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  24. package/src/__tests__/app-control-flow.test.ts +1 -1
  25. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  26. package/src/__tests__/app-executors.test.ts +132 -0
  27. package/src/__tests__/approval-cascade.test.ts +5 -4
  28. package/src/__tests__/approval-routes-http.test.ts +4 -1
  29. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  30. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  31. package/src/__tests__/channel-approvals.test.ts +1 -1
  32. package/src/__tests__/compaction-circuit.test.ts +258 -0
  33. package/src/__tests__/compaction-direct.test.ts +132 -0
  34. package/src/__tests__/compaction-events.test.ts +5 -5
  35. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  36. package/src/__tests__/context-overflow-reducer.test.ts +1 -1
  37. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -4
  38. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  39. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  40. package/src/__tests__/conversation-agent-loop-overflow.test.ts +142 -218
  41. package/src/__tests__/conversation-agent-loop.test.ts +297 -586
  42. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  43. package/src/__tests__/conversation-confirmation-signals.test.ts +5 -4
  44. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  45. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  46. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -4
  47. package/src/__tests__/conversation-process-callsite.test.ts +14 -14
  48. package/src/__tests__/conversation-provider-retry-repair.test.ts +70 -65
  49. package/src/__tests__/conversation-queue.test.ts +9 -9
  50. package/src/__tests__/conversation-runtime-assembly.test.ts +923 -231
  51. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  52. package/src/__tests__/conversation-slash-queue.test.ts +6 -4
  53. package/src/__tests__/conversation-slash-unknown.test.ts +6 -4
  54. package/src/__tests__/conversation-speed-override.test.ts +10 -9
  55. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  56. package/src/__tests__/conversation-workspace-cache-state.test.ts +23 -20
  57. package/src/__tests__/conversation-workspace-injection.test.ts +68 -6
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +14 -11
  59. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  60. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  61. package/src/__tests__/db-acp-history.test.ts +101 -0
  62. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  63. package/src/__tests__/empty-response-hook.test.ts +1 -1
  64. package/src/__tests__/file-write-tool.test.ts +63 -0
  65. package/src/__tests__/gateway-only-guard.test.ts +12 -2
  66. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  67. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  68. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  69. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  70. package/src/__tests__/heartbeat-service.test.ts +1 -0
  71. package/src/__tests__/history-repair-hook.test.ts +1 -1
  72. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  73. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  74. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  75. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  76. package/src/__tests__/injector-background-turn.test.ts +13 -23
  77. package/src/__tests__/injector-chain.test.ts +268 -44
  78. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  79. package/src/__tests__/injector-document-comments.test.ts +97 -114
  80. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  81. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  82. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  83. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  84. package/src/__tests__/memory-retrieval-hook.test.ts +131 -26
  85. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  86. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  87. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  88. package/src/__tests__/plugin-bootstrap.test.ts +12 -23
  89. package/src/__tests__/plugin-registry.test.ts +3 -49
  90. package/src/__tests__/plugin-types.test.ts +0 -70
  91. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  92. package/src/__tests__/reaction-persistence.test.ts +1 -1
  93. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  94. package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
  95. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  96. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  97. package/src/__tests__/subagent-detail.test.ts +25 -7
  98. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  99. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  100. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  101. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  102. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  103. package/src/__tests__/title-generate-hook.test.ts +1 -1
  104. package/src/__tests__/tool-error-hook.test.ts +1 -1
  105. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  106. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  107. package/src/acp/__tests__/agent-process.test.ts +161 -0
  108. package/src/acp/__tests__/client-handler.test.ts +40 -0
  109. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  110. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  111. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  112. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  113. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  114. package/src/acp/agent-process.ts +61 -1
  115. package/src/acp/auto-install.test.ts +280 -0
  116. package/src/acp/auto-install.ts +232 -0
  117. package/src/acp/client-handler.ts +31 -0
  118. package/src/acp/feature-gate.test.ts +48 -0
  119. package/src/acp/feature-gate.ts +34 -0
  120. package/src/acp/prepare-agent-env.ts +80 -27
  121. package/src/acp/resolve-agent.test.ts +225 -9
  122. package/src/acp/resolve-agent.ts +122 -17
  123. package/src/acp/resume-hint.ts +23 -0
  124. package/src/acp/session-manager.ts +507 -73
  125. package/src/agent/compaction-circuit.ts +60 -102
  126. package/src/agent/loop.ts +414 -248
  127. package/src/api/responses/conversation-message.ts +14 -1
  128. package/src/approvals/guardian-request-resolvers.ts +1 -1
  129. package/src/background-wake/next-wake.ts +1 -0
  130. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  131. package/src/cli/commands/plugins.ts +43 -37
  132. package/src/cli/lib/__tests__/install-from-github.test.ts +429 -111
  133. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  134. package/src/cli/lib/__tests__/plugin-details.test.ts +372 -0
  135. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +220 -0
  136. package/src/cli/lib/__tests__/search-plugins.test.ts +226 -32
  137. package/src/cli/lib/install-from-github.ts +464 -55
  138. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  139. package/src/cli/lib/plugin-details.ts +409 -0
  140. package/src/cli/lib/plugin-marketplace.ts +197 -0
  141. package/src/cli/lib/search-plugins.ts +195 -29
  142. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  143. package/src/config/acp-defaults.test.ts +10 -0
  144. package/src/config/acp-defaults.ts +6 -0
  145. package/src/config/bundled-skills/acp/SKILL.md +85 -33
  146. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  147. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  148. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  149. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  150. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  151. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  152. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  153. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  154. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  155. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  156. package/src/config/bundled-tool-registry.ts +4 -0
  157. package/src/config/call-site-defaults.ts +0 -2
  158. package/src/config/feature-flag-registry.json +15 -6
  159. package/src/config/schemas/call-site-catalog.ts +0 -14
  160. package/src/config/schemas/heartbeat.ts +9 -0
  161. package/src/config/schemas/llm.ts +0 -2
  162. package/src/context/compactor.ts +22 -4
  163. package/src/context/strip-injections.ts +8 -2
  164. package/src/context/window-manager.ts +27 -13
  165. package/src/daemon/conversation-agent-loop-handlers.ts +10 -35
  166. package/src/daemon/conversation-agent-loop.ts +175 -1055
  167. package/src/daemon/conversation-lifecycle.ts +11 -255
  168. package/src/daemon/conversation-process.ts +8 -136
  169. package/src/daemon/conversation-registry.ts +159 -0
  170. package/src/daemon/conversation-runtime-assembly.ts +293 -392
  171. package/src/daemon/conversation-store.ts +9 -90
  172. package/src/daemon/conversation-surfaces.ts +24 -8
  173. package/src/daemon/conversation-workspace.ts +17 -0
  174. package/src/daemon/conversation.ts +404 -57
  175. package/src/daemon/disk-pressure-policy.ts +0 -1
  176. package/src/daemon/external-plugins-bootstrap.ts +14 -19
  177. package/src/daemon/handlers/conversations.ts +3 -1
  178. package/src/daemon/handlers/skills.ts +4 -1
  179. package/src/daemon/host-proxy-preactivation.ts +1 -3
  180. package/src/daemon/lifecycle.ts +21 -7
  181. package/src/daemon/server.ts +2 -0
  182. package/src/daemon/wake-conversation-ops.ts +269 -0
  183. package/src/embedded/plugin-api.ts +2 -2
  184. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  185. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  186. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  187. package/src/heartbeat/heartbeat-service.ts +26 -0
  188. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  189. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  190. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  191. package/src/ipc/skill-routes/memory.ts +4 -2
  192. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  193. package/src/memory/conversation-crud.ts +29 -19
  194. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  195. package/src/memory/db-init.ts +2 -0
  196. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  197. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  198. package/src/memory/jobs-worker.ts +25 -1
  199. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  200. package/src/memory/migrations/index.ts +1 -0
  201. package/src/memory/schema/acp.ts +4 -0
  202. package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
  203. package/src/memory/v2/consolidation-job.ts +13 -4
  204. package/src/plugin-api/constants.ts +4 -0
  205. package/src/plugin-api/index.ts +6 -5
  206. package/src/plugin-api/types.ts +75 -0
  207. package/src/plugins/defaults/compaction/compact.ts +59 -0
  208. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +7 -7
  209. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  210. package/src/plugins/defaults/compaction/package.json +1 -2
  211. package/src/plugins/defaults/empty-response/package.json +0 -1
  212. package/src/plugins/defaults/history-repair/package.json +0 -1
  213. package/src/plugins/defaults/index.ts +135 -26
  214. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  215. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +184 -74
  216. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  217. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  218. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  219. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
  220. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +9 -6
  221. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
  222. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +8 -5
  223. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
  224. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
  225. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +10 -5
  226. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
  227. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +5 -5
  228. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +16 -17
  229. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -2
  230. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +9 -5
  231. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +5 -2
  232. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  233. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  234. package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
  235. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +15 -8
  236. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +2 -2
  237. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  238. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
  239. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/provider-blocks.ts +1 -1
  240. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +7 -3
  241. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
  242. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +5 -5
  243. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
  244. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +7 -7
  245. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +32 -94
  246. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
  247. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +1 -1
  248. package/src/plugins/defaults/title-generate/package.json +0 -1
  249. package/src/plugins/defaults/tool-error/package.json +0 -1
  250. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  251. package/src/plugins/pipeline.ts +6 -293
  252. package/src/plugins/registry.ts +9 -37
  253. package/src/plugins/types.ts +76 -381
  254. package/src/plugins/user-loader.ts +30 -127
  255. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  256. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +35 -3
  257. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  258. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  259. package/src/runtime/agent-wake.ts +108 -209
  260. package/src/runtime/assistant-event-hub.ts +1 -1
  261. package/src/runtime/channel-approvals.ts +1 -1
  262. package/src/runtime/interactive-ui.ts +1 -1
  263. package/src/runtime/routes/__tests__/acp-routes.test.ts +315 -55
  264. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  265. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  266. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  267. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  268. package/src/runtime/routes/acp-routes.test.ts +89 -25
  269. package/src/runtime/routes/acp-routes.ts +81 -29
  270. package/src/runtime/routes/approval-routes.ts +1 -1
  271. package/src/runtime/routes/browser-routes.ts +1 -1
  272. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  273. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  274. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  275. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  276. package/src/runtime/routes/conversation-routes.ts +28 -2
  277. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  278. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  279. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  280. package/src/runtime/routes/host-cu-routes.ts +1 -1
  281. package/src/runtime/routes/identity-routes.ts +18 -3
  282. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  283. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  284. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  285. package/src/runtime/routes/memory-v3-routes.ts +16 -6
  286. package/src/runtime/routes/playground/helpers.ts +1 -1
  287. package/src/runtime/routes/plugins-routes.ts +337 -35
  288. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  289. package/src/runtime/routes/work-items-routes.ts +2 -4
  290. package/src/runtime/services/conversation-serializer.ts +1 -1
  291. package/src/signals/cancel.ts +2 -4
  292. package/src/subagent/manager.ts +21 -5
  293. package/src/tools/acp/context.ts +20 -0
  294. package/src/tools/acp/list-agents.test.ts +8 -2
  295. package/src/tools/acp/spawn.test.ts +176 -195
  296. package/src/tools/acp/spawn.ts +37 -172
  297. package/src/tools/acp/steer.test.ts +105 -8
  298. package/src/tools/acp/steer.ts +48 -17
  299. package/src/tools/apps/executors.ts +166 -50
  300. package/src/tools/filesystem/write.ts +34 -0
  301. package/src/tools/subagent/spawn.ts +2 -4
  302. package/src/tools/ui-surface/definitions.ts +25 -5
  303. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  304. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  305. package/docs/plugins.md +0 -832
  306. package/examples/plugins/echo/register.ts +0 -143
  307. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  308. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  309. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  310. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  311. package/src/__tests__/pipeline-runner.test.ts +0 -554
  312. package/src/__tests__/plugin-external-api.test.ts +0 -68
  313. package/src/daemon/wake-target-adapter.ts +0 -253
  314. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  315. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  316. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  317. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  318. package/src/plugins/defaults/compaction/register.ts +0 -35
  319. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  320. package/src/plugins/defaults/empty-response/register.ts +0 -23
  321. package/src/plugins/defaults/history-repair/register.ts +0 -24
  322. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  323. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  324. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  325. package/src/plugins/defaults/title-generate/register.ts +0 -35
  326. package/src/plugins/defaults/tool-error/register.ts +0 -23
  327. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  328. package/src/plugins/external-api.ts +0 -104
  329. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  330. package/src/proactive-artifact/decision.test.ts +0 -226
  331. package/src/proactive-artifact/decision.ts +0 -165
  332. package/src/proactive-artifact/index.ts +0 -7
  333. package/src/proactive-artifact/job.test.ts +0 -962
  334. package/src/proactive-artifact/job.ts +0 -372
  335. package/src/proactive-artifact/message-copy.ts +0 -58
  336. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  337. package/src/proactive-artifact/trigger-state.ts +0 -123
  338. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
  339. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
  340. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  341. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  342. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +0 -0
  343. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
  344. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/provider-blocks.test.ts +0 -0
  345. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
  346. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
  347. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  348. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  349. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
  350. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  351. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  352. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  353. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  354. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  355. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  356. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
  357. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
  358. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
  359. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
  360. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
@@ -110,7 +110,7 @@ describe("memory-v3-live v2 suppression", () => {
110
110
  ];
111
111
 
112
112
  const result = await applyRuntimeInjections(runMessages, {
113
- turnContext: makeTurnContext(),
113
+ ...makeTurnContext(),
114
114
  });
115
115
 
116
116
  // Exactly one <memory> source across the WHOLE assembled context.
@@ -149,7 +149,7 @@ describe("memory-v3-live v2 suppression", () => {
149
149
  ];
150
150
 
151
151
  const result = await applyRuntimeInjections(runMessages, {
152
- turnContext: makeTurnContext(),
152
+ ...makeTurnContext(),
153
153
  });
154
154
 
155
155
  // v2's block survives — the turn still ships memory.
@@ -177,7 +177,7 @@ describe("memory-v3-live v2 suppression", () => {
177
177
  // captures the exact pre-flag assembly behavior: v2 prefix stays, v3
178
178
  // splices after it.
179
179
  const offResult = await applyRuntimeInjections(runMessages, {
180
- turnContext: makeTurnContext(),
180
+ ...makeTurnContext(),
181
181
  });
182
182
 
183
183
  // The tail keeps v2's block AND gains v3's (the historical double-injection
@@ -206,7 +206,7 @@ describe("memory-v3-live v2 suppression", () => {
206
206
  ];
207
207
 
208
208
  const result = await applyRuntimeInjections(runMessages, {
209
- turnContext: makeTurnContext(),
209
+ ...makeTurnContext(),
210
210
  });
211
211
 
212
212
  expect(result.messages).toEqual(runMessages);
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Tests for handleListMessages clientMessageId projection.
3
+ *
4
+ * Verifies that the persisted idempotency nonce is echoed back on the
5
+ * messages snapshot row so a client can correlate its optimistic row with
6
+ * the confirmed server row by identity instead of matching message text.
7
+ */
8
+
9
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
10
+
11
+ mock.module("../util/logger.js", () => ({
12
+ getLogger: () =>
13
+ new Proxy({} as Record<string, unknown>, {
14
+ get: () => () => {},
15
+ }),
16
+ }));
17
+
18
+ mock.module("../config/loader.js", () => ({
19
+ getConfig: () => ({
20
+ ui: {},
21
+ model: "test",
22
+ provider: "test",
23
+ memory: { enabled: false },
24
+ rateLimit: { maxRequestsPerMinute: 0 },
25
+ }),
26
+ }));
27
+
28
+ import { addMessage, createConversation } from "../memory/conversation-crud.js";
29
+ import { getDb } from "../memory/db-connection.js";
30
+ import { initializeDb } from "../memory/db-init.js";
31
+ import { handleListMessages } from "../runtime/routes/conversation-routes.js";
32
+
33
+ initializeDb();
34
+
35
+ function resetTables() {
36
+ const db = getDb();
37
+ db.run("DELETE FROM messages");
38
+ db.run("DELETE FROM conversations");
39
+ }
40
+
41
+ interface MessagePayload {
42
+ role: string;
43
+ clientMessageId?: string;
44
+ }
45
+
46
+ describe("handleListMessages clientMessageId", () => {
47
+ beforeEach(resetTables);
48
+
49
+ test("echoes the persisted clientMessageId onto the user row", async () => {
50
+ // GIVEN a user message persisted with a client-generated idempotency nonce
51
+ const conv = createConversation();
52
+ await addMessage(
53
+ conv.id,
54
+ "user",
55
+ JSON.stringify([{ type: "text", text: "hello" }]),
56
+ { clientMessageId: "nonce-abc", skipIndexing: true },
57
+ );
58
+
59
+ // WHEN the messages snapshot is built
60
+ const response = handleListMessages({
61
+ queryParams: { conversationId: conv.id },
62
+ });
63
+ const body = response as { messages: MessagePayload[] };
64
+
65
+ // THEN the snapshot row carries the nonce back for id-based correlation
66
+ expect(body.messages).toHaveLength(1);
67
+ expect(body.messages[0].role).toBe("user");
68
+ expect(body.messages[0].clientMessageId).toBe("nonce-abc");
69
+ });
70
+
71
+ test("omits clientMessageId when the row was persisted without one", async () => {
72
+ // GIVEN a user message persisted without a client nonce (e.g. a channel send)
73
+ const conv = createConversation();
74
+ await addMessage(
75
+ conv.id,
76
+ "user",
77
+ JSON.stringify([{ type: "text", text: "hello" }]),
78
+ { skipIndexing: true },
79
+ );
80
+
81
+ // WHEN the messages snapshot is built
82
+ const response = handleListMessages({
83
+ queryParams: { conversationId: conv.id },
84
+ });
85
+ const body = response as { messages: MessagePayload[] };
86
+
87
+ // THEN the field is absent rather than emitted as null/empty
88
+ expect(body.messages).toHaveLength(1);
89
+ expect(body.messages[0].clientMessageId).toBeUndefined();
90
+ });
91
+ });
@@ -96,6 +96,44 @@ describe("handleListMessages metadata.hidden filtering", () => {
96
96
  expect(llmRows[1].metadata).toContain('"hidden":true');
97
97
  });
98
98
 
99
+ test("UI serializer omits system rows but LLM-side getMessages includes them", async () => {
100
+ // GIVEN a conversation with a system row sandwiched between two
101
+ // renderable turns (e.g. a skill-authored context message)
102
+ const conv = createConversation();
103
+ await addMessage(
104
+ conv.id,
105
+ "user",
106
+ JSON.stringify([{ type: "text", text: "first visible" }]),
107
+ );
108
+ await addMessage(
109
+ conv.id,
110
+ "system",
111
+ JSON.stringify([{ type: "text", text: "system scaffolding" }]),
112
+ );
113
+ await addMessage(
114
+ conv.id,
115
+ "assistant",
116
+ JSON.stringify([{ type: "text", text: "second visible" }]),
117
+ );
118
+
119
+ // WHEN the UI history list is serialized
120
+ const response = handleListMessages({
121
+ queryParams: { conversationId: conv.id },
122
+ });
123
+ const body = response as { messages: MessagePayload[] };
124
+
125
+ // THEN only the user/assistant turns are returned, never the system row
126
+ expect(body.messages.map((m) => m.role)).toEqual(["user", "assistant"]);
127
+ expect(
128
+ body.messages.some((m) => plainText(m).includes("system scaffolding")),
129
+ ).toBe(false);
130
+
131
+ // AND the LLM-side loader still includes the system row for agent context
132
+ const llmRows = getMessages(conv.id);
133
+ expect(llmRows).toHaveLength(3);
134
+ expect(llmRows[1].role).toBe("system");
135
+ });
136
+
99
137
  test("messages without metadata or with hidden=false are returned", async () => {
100
138
  const conv = createConversation();
101
139
  await addMessage(
@@ -1,12 +1,16 @@
1
1
  /**
2
- * Tests for the default `user-prompt-submit-temp` hook (memory retrieval).
2
+ * Tests for the default `user-prompt-submit-temp` hook (memory retrieval +
3
+ * runtime injection).
3
4
  *
4
5
  * Covers the retrieval behavior, the side effects the hook owns (injected-block
5
- * metadata, recall log, `memory_recalled` event), trust gating, error
6
- * propagation, and abort-signal forwarding. Uses `mock.module` to stub the
7
- * persistence helpers so the test doesn't touch the developer's real
8
- * `~/.vellum` or database. The memory graph handle is a hand-rolled fake
9
- * passed on the hook context the hook only needs `prepareMemory`.
6
+ * metadata, recall log, `memory_recalled` event), trust gating, the runtime
7
+ * injection the hook applies for every actor (and the assembled-block persist),
8
+ * error propagation, and abort-signal forwarding. Uses `mock.module` to stub
9
+ * the persistence helpers, `applyRuntimeInjections`, and the conversation
10
+ * registry / trust resolver so the test doesn't touch the developer's real
11
+ * `~/.vellum`, database, or live conversation state. The memory graph handle,
12
+ * abort signal, and trust class are self-resolved by the hook from a fake
13
+ * conversation installed in the registry mock.
10
14
  */
11
15
 
12
16
  import { beforeEach, describe, expect, mock, test } from "bun:test";
@@ -23,7 +27,36 @@ mock.module("../memory/memory-recall-log-store.js", () => ({
23
27
  recordMemoryRecallLog: recordMemoryRecallLogMock,
24
28
  }));
25
29
 
30
+ // Pass-through injection stub: returns the input messages unchanged with no
31
+ // assembled blocks, so existing array-identity assertions hold. Individual
32
+ // tests override it via `mockImplementationOnce` to exercise injected blocks.
33
+ const applyRuntimeInjectionsMock = mock(async (messages: unknown) => ({
34
+ messages,
35
+ blocks: {},
36
+ }));
37
+ mock.module("../daemon/conversation-runtime-assembly.js", () => ({
38
+ applyRuntimeInjections: applyRuntimeInjectionsMock,
39
+ }));
40
+
41
+ // The hook self-resolves the live conversation and its trust class; both are
42
+ // driven from these refs so each test controls what the hook sees.
43
+ let currentConversation: Conversation | undefined;
44
+ let currentTrustClass: "guardian" | "unknown" = "guardian";
45
+ const findConversationOrSubagentMock = mock(
46
+ (_conversationId?: string) => currentConversation,
47
+ );
48
+ mock.module("../daemon/conversation-registry.js", () => ({
49
+ findConversationOrSubagent: findConversationOrSubagentMock,
50
+ }));
51
+ mock.module("../daemon/trust-context.js", () => ({
52
+ resolveTrustClass: () => currentTrustClass,
53
+ }));
54
+ mock.module("../config/loader.js", () => ({
55
+ getConfig: () => ({}) as AssistantConfig,
56
+ }));
57
+
26
58
  import type { AssistantConfig } from "../config/schema.js";
59
+ import type { Conversation } from "../daemon/conversation.js";
27
60
  import type { ServerMessage } from "../daemon/message-protocol.js";
28
61
  import type { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
29
62
  import type { QdrantSparseVector } from "../memory/qdrant-client.js";
@@ -104,26 +137,47 @@ function makeFakeGraphMemory(overrides?: {
104
137
  function makeHookCtx(
105
138
  overrides: Partial<MemoryRetrievalHookContext> = {},
106
139
  ): MemoryRetrievalHookContext {
107
- const { memory } = makeFakeGraphMemory();
108
140
  return {
109
- graphMemory: memory,
110
- config: {} as AssistantConfig,
111
141
  onEvent: () => {},
112
- isTrustedActor: true,
113
142
  conversationId: "conv-test",
114
143
  userMessageId: "msg-test",
115
144
  logger: {
116
145
  warn: () => {},
117
146
  } as unknown as MemoryRetrievalHookContext["logger"],
118
- signal: new AbortController().signal,
119
147
  latestMessages: [],
148
+ requestId: "req-test",
149
+ mode: "full",
150
+ isNonInteractive: false,
151
+ modelProfile: null,
152
+ actorContext: null,
120
153
  ...overrides,
121
154
  };
122
155
  }
123
156
 
157
+ /**
158
+ * Install a fake live conversation for the hook to self-resolve by id: the
159
+ * graph handle, abort signal, and trust class all come from here rather than
160
+ * the hook context.
161
+ */
162
+ function installConversation(
163
+ graphMemory: ConversationGraphMemory,
164
+ opts?: { trusted?: boolean; signal?: AbortSignal },
165
+ ): void {
166
+ currentTrustClass = opts?.trusted === false ? "unknown" : "guardian";
167
+ currentConversation = {
168
+ graphMemory,
169
+ trustContext: undefined,
170
+ abortController: { signal: opts?.signal ?? new AbortController().signal },
171
+ } as unknown as Conversation;
172
+ }
173
+
124
174
  beforeEach(() => {
125
175
  updateMessageMetadataMock.mockReset();
126
176
  recordMemoryRecallLogMock.mockReset();
177
+ applyRuntimeInjectionsMock.mockClear();
178
+ findConversationOrSubagentMock.mockClear();
179
+ currentConversation = undefined;
180
+ currentTrustClass = "guardian";
127
181
  });
128
182
 
129
183
  describe("user-prompt-submit-temp hook (memory retrieval)", () => {
@@ -134,7 +188,8 @@ describe("user-prompt-submit-temp hook (memory retrieval)", () => {
134
188
  const { memory, prepareMemoryMock } = makeFakeGraphMemory({
135
189
  messages: injected,
136
190
  });
137
- const ctx = makeHookCtx({ graphMemory: memory, isTrustedActor: true });
191
+ installConversation(memory, { trusted: true });
192
+ const ctx = makeHookCtx();
138
193
 
139
194
  await userPromptSubmitMemoryRetrieval(ctx);
140
195
 
@@ -156,7 +211,8 @@ describe("user-prompt-submit-temp hook (memory retrieval)", () => {
156
211
  userQueryVector: userDense,
157
212
  userQuerySparseVector: userSparse,
158
213
  });
159
- const userCtx = makeHookCtx({ graphMemory: withUserQuery.memory });
214
+ installConversation(withUserQuery.memory, { trusted: true });
215
+ const userCtx = makeHookCtx();
160
216
  await userPromptSubmitMemoryRetrieval(userCtx);
161
217
  // User-query pair wins — never crossed with the summary signal — and is
162
218
  // recorded back onto the graph handle for the PKB-reminder injector.
@@ -169,7 +225,8 @@ describe("user-prompt-submit-temp hook (memory retrieval)", () => {
169
225
  queryVector: summaryDense,
170
226
  sparseVector: summarySparse,
171
227
  });
172
- const summaryCtx = makeHookCtx({ graphMemory: summaryOnly.memory });
228
+ installConversation(summaryOnly.memory, { trusted: true });
229
+ const summaryCtx = makeHookCtx();
173
230
  await userPromptSubmitMemoryRetrieval(summaryCtx);
174
231
  expect(summaryOnly.recordPkbQueryVectorsMock).toHaveBeenCalledWith(
175
232
  summaryDense,
@@ -177,17 +234,59 @@ describe("user-prompt-submit-temp hook (memory retrieval)", () => {
177
234
  );
178
235
  });
179
236
 
237
+ test("applies runtime injection on the retrieved history and persists the assembled blocks", async () => {
238
+ const retrieved: Message[] = [
239
+ { role: "user", content: [{ type: "text", text: "retrieved" }] },
240
+ ];
241
+ const injected: Message[] = [
242
+ { role: "user", content: [{ type: "text", text: "injected" }] },
243
+ ];
244
+ const { memory } = makeFakeGraphMemory({ messages: retrieved });
245
+ applyRuntimeInjectionsMock.mockImplementationOnce(async () => ({
246
+ messages: injected,
247
+ blocks: { unifiedTurnContext: "tc-block" },
248
+ }));
249
+ installConversation(memory, { trusted: true });
250
+ const ctx = makeHookCtx({ userMessageId: "msg-77" });
251
+
252
+ await userPromptSubmitMemoryRetrieval(ctx);
253
+
254
+ // Injection runs on the retrieved history; its result becomes the working
255
+ // list the loop reads back.
256
+ expect(applyRuntimeInjectionsMock).toHaveBeenCalledTimes(1);
257
+ expect(applyRuntimeInjectionsMock.mock.calls[0]?.[0]).toBe(retrieved);
258
+ expect(ctx.latestMessages).toBe(injected);
259
+ // The assembled blocks are persisted onto the user message metadata.
260
+ expect(updateMessageMetadataMock).toHaveBeenCalledWith("msg-77", {
261
+ turnContextBlock: "tc-block",
262
+ });
263
+ });
264
+
265
+ test("applies runtime injection for untrusted actors despite skipping retrieval", async () => {
266
+ const { memory, prepareMemoryMock } = makeFakeGraphMemory();
267
+ const seeded: Message[] = [
268
+ { role: "user", content: [{ type: "text", text: "seeded" }] },
269
+ ];
270
+ installConversation(memory, { trusted: false });
271
+ const ctx = makeHookCtx({ latestMessages: seeded });
272
+
273
+ await userPromptSubmitMemoryRetrieval(ctx);
274
+
275
+ // The memory-graph step is gated on trust, but injection runs for everyone
276
+ // — on the seeded history, since no retrieval replaced it.
277
+ expect(prepareMemoryMock).not.toHaveBeenCalled();
278
+ expect(applyRuntimeInjectionsMock).toHaveBeenCalledTimes(1);
279
+ expect(applyRuntimeInjectionsMock.mock.calls[0]?.[0]).toBe(seeded);
280
+ });
281
+
180
282
  test("skips graph retrieval and side effects for untrusted actors", async () => {
181
283
  const { memory, prepareMemoryMock, recordPkbQueryVectorsMock } =
182
284
  makeFakeGraphMemory();
183
285
  const seeded: Message[] = [
184
286
  { role: "user", content: [{ type: "text", text: "seeded" }] },
185
287
  ];
186
- const ctx = makeHookCtx({
187
- graphMemory: memory,
188
- isTrustedActor: false,
189
- latestMessages: seeded,
190
- });
288
+ installConversation(memory, { trusted: false });
289
+ const ctx = makeHookCtx({ latestMessages: seeded });
191
290
 
192
291
  await userPromptSubmitMemoryRetrieval(ctx);
193
292
 
@@ -206,8 +305,8 @@ describe("user-prompt-submit-temp hook (memory retrieval)", () => {
206
305
  injectedBlockText: "injected-block",
207
306
  metrics: makeMetrics(),
208
307
  });
308
+ installConversation(memory, { trusted: true });
209
309
  const ctx = makeHookCtx({
210
- graphMemory: memory,
211
310
  onEvent: (msg) => received.push(msg),
212
311
  userMessageId: "msg-42",
213
312
  conversationId: "conv-42",
@@ -231,7 +330,8 @@ describe("user-prompt-submit-temp hook (memory retrieval)", () => {
231
330
 
232
331
  test("skips metadata persist when no block text is injected", async () => {
233
332
  const { memory } = makeFakeGraphMemory({ injectedBlockText: null });
234
- const ctx = makeHookCtx({ graphMemory: memory });
333
+ installConversation(memory, { trusted: true });
334
+ const ctx = makeHookCtx();
235
335
 
236
336
  await userPromptSubmitMemoryRetrieval(ctx);
237
337
 
@@ -254,16 +354,17 @@ describe("user-prompt-submit-temp hook (memory retrieval)", () => {
254
354
  const graphMemory = {
255
355
  prepareMemory: failingPrepare,
256
356
  } as unknown as ConversationGraphMemory;
257
- const ctx = makeHookCtx({ graphMemory, isTrustedActor: true });
357
+ installConversation(graphMemory, { trusted: true });
358
+ const ctx = makeHookCtx();
258
359
 
259
360
  await expect(userPromptSubmitMemoryRetrieval(ctx)).rejects.toThrow(
260
361
  "retrieval failed",
261
362
  );
262
363
  });
263
364
 
264
- test("forwards the context abort signal into prepareMemory", async () => {
265
- // The hook hands its `ctx.signal` straight to `prepareMemory` so an
266
- // external cancel aborts the underlying retrieval.
365
+ test("forwards the conversation abort signal into prepareMemory", async () => {
366
+ // The hook hands the live conversation's abort signal straight to
367
+ // `prepareMemory` so an external cancel aborts the underlying retrieval.
267
368
  let capturedSignal: AbortSignal | undefined;
268
369
  const prepareMemoryMock = mock(
269
370
  async (
@@ -288,7 +389,11 @@ describe("user-prompt-submit-temp hook (memory retrieval)", () => {
288
389
  recordPkbQueryVectors: mock(() => {}),
289
390
  } as unknown as ConversationGraphMemory;
290
391
  const controller = new AbortController();
291
- const ctx = makeHookCtx({ graphMemory, signal: controller.signal });
392
+ installConversation(graphMemory, {
393
+ trusted: true,
394
+ signal: controller.signal,
395
+ });
396
+ const ctx = makeHookCtx();
292
397
 
293
398
  await userPromptSubmitMemoryRetrieval(ctx);
294
399
 
@@ -11,6 +11,9 @@
11
11
  * - Skips (re)injection when the `<info>` block is already present in the
12
12
  * turn's working messages (presence detection — the block persists in
13
13
  * history between compactions).
14
+ * - Still injects when only a dynamic `<memory>` activation block is present
15
+ * (that wrapper must not be mistaken for the static block) — both on a
16
+ * normal turn and after compaction strips the prior `<info>` block.
14
17
  *
15
18
  * The injector sources its content itself via `readMemoryV2StaticContent()`
16
19
  * behind the personal-memory trust gate, so each test seeds the workspace
@@ -35,7 +38,7 @@ mock.module("../config/loader.js", () => ({
35
38
  }));
36
39
 
37
40
  const { defaultInjectors } =
38
- await import("../plugins/defaults/injectors/register.js");
41
+ await import("../plugins/defaults/memory-retrieval/injectors.js");
39
42
  import type { Injector, TurnContext } from "../plugins/types.js";
40
43
  import type { Message } from "../providers/types.js";
41
44
  import { getWorkspacePromptPath } from "../util/platform.js";
@@ -54,7 +57,6 @@ function makeContext(overrides: Partial<TurnContext> = {}): TurnContext {
54
57
  conversationId: "conv-test",
55
58
  turnIndex: 0,
56
59
  trust: { sourceChannel: "vellum", trustClass: "guardian" },
57
- injectionInputs: {},
58
60
  ...overrides,
59
61
  };
60
62
  }
@@ -96,7 +98,7 @@ describe("memory-v2-static injector", () => {
96
98
 
97
99
  test("returns null in minimal mode even with content", async () => {
98
100
  seedEssentials("Alice prefers VS Code.");
99
- const ctx = makeContext({ injectionInputs: { mode: "minimal" } });
101
+ const ctx = makeContext({ mode: "minimal" });
100
102
  expect(await memoryV2StaticInjector.produce(ctx)).toBeNull();
101
103
  });
102
104
 
@@ -140,10 +142,12 @@ describe("memory-v2-static injector", () => {
140
142
  expect(await memoryV2StaticInjector.produce(ctx, runMessages)).toBeNull();
141
143
  });
142
144
 
143
- test("skips (re)injection when a legacy <memory>-wrapped static block is present", async () => {
144
- // Rows persisted before the `<info>` switch rehydrate the static block as
145
- // `<memory>…</memory>`. Re-injecting a fresh `<info>` copy alongside it
146
- // would duplicate the content until the next compaction.
145
+ test("injects the <info> block even when a dynamic <memory> activation block is present", async () => {
146
+ // Regression (#33612): the v2 *dynamic* activation block uses the
147
+ // `<memory>…</memory>` wrapper and `prepareMemory` prepends it to the tail
148
+ // user message every turn, before this injector runs. The static injector
149
+ // must NOT treat that as its own `<info>` block — doing so suppressed the
150
+ // static block on essentially every turn.
147
151
  seedEssentials("Alice prefers VS Code.");
148
152
  const ctx = makeContext();
149
153
  const runMessages: Message[] = [
@@ -152,12 +156,86 @@ describe("memory-v2-static injector", () => {
152
156
  content: [
153
157
  {
154
158
  type: "text",
155
- text: "<memory>\n## Essentials\n\nAlice prefers VS Code.\n</memory>",
159
+ text: "<memory>\n# memory/concepts/foo.md\nactivated page summary\n</memory>",
156
160
  },
157
161
  { type: "text", text: "What next?" },
158
162
  ],
159
163
  },
160
164
  ];
165
+ const block = await memoryV2StaticInjector.produce(ctx, runMessages);
166
+ expect(block).not.toBeNull();
167
+ expect(block!.id).toBe("memory-v2-static");
168
+ expect(block!.text).toBe(
169
+ "<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
170
+ );
171
+ });
172
+
173
+ test("reinjects the <info> block after compaction strips it, alongside a fresh <memory> block", async () => {
174
+ // Post-compaction state: `stripInjectionsForCompaction` removed the prior
175
+ // `<info>` block, and the next turn's `prepareMemory` re-added a fresh
176
+ // dynamic `<memory>` block ahead of the chain. With no `<info>` present, the
177
+ // injector must reinject it rather than skip on the `<memory>` block.
178
+ seedEssentials("Alice prefers VS Code.");
179
+ const ctx = makeContext();
180
+ const postCompactMessages: Message[] = [
181
+ {
182
+ role: "user",
183
+ content: [
184
+ { type: "text", text: "<memory>\nactivated page summary\n</memory>" },
185
+ { type: "text", text: "Continue please." },
186
+ ],
187
+ },
188
+ ];
189
+ const block = await memoryV2StaticInjector.produce(
190
+ ctx,
191
+ postCompactMessages,
192
+ );
193
+ expect(block).not.toBeNull();
194
+ expect(block!.id).toBe("memory-v2-static");
195
+ expect(block!.text).toContain("## Essentials");
196
+ });
197
+
198
+ test("skips (re)injection when the <info> block is carried in a flattened history block", async () => {
199
+ // Regression: when a historical user message's separate injection content
200
+ // blocks get collapsed into one text block (observed after a daemon restart
201
+ // / plugin hot-reload rebuilt the rendered history mid-conversation — the
202
+ // blocks are joined with "\n"), the `<info>` wrapper is no longer its own
203
+ // block but a newline-delimited section inside a larger block. Presence
204
+ // detection must still recognize it; otherwise the injector re-injects a
205
+ // duplicate `<info>` onto the new tail (the double-injection bug).
206
+ seedEssentials("Alice prefers VS Code.");
207
+ const ctx = makeContext();
208
+ const flattenedFirstUser = [
209
+ "<workspace>\nRoot: /ws\nFiles: a.md\n</workspace>",
210
+ "<turn_context>\ncurrent_time: now\n</turn_context>",
211
+ "<memory>\nactivated page summary\n</memory>",
212
+ "<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
213
+ "what should we do next?",
214
+ ].join("\n");
215
+ const runMessages: Message[] = [
216
+ { role: "user", content: [{ type: "text", text: flattenedFirstUser }] },
217
+ { role: "assistant", content: [{ type: "text", text: "On it." }] },
218
+ { role: "user", content: [{ type: "text", text: "great, continue" }] },
219
+ ];
161
220
  expect(await memoryV2StaticInjector.produce(ctx, runMessages)).toBeNull();
162
221
  });
222
+
223
+ test("still injects when a user message opens an <info> tag without closing it", async () => {
224
+ // False-positive guard: the wrapper matcher requires BOTH the opening and
225
+ // closing tags, so user prose that merely opens an injection-like tag must
226
+ // not be mistaken for a carried block and suppress injection.
227
+ seedEssentials("Alice prefers VS Code.");
228
+ const ctx = makeContext();
229
+ const runMessages: Message[] = [
230
+ {
231
+ role: "user",
232
+ content: [
233
+ { type: "text", text: "<info>\n is this how the tag works?" },
234
+ ],
235
+ },
236
+ ];
237
+ const block = await memoryV2StaticInjector.produce(ctx, runMessages);
238
+ expect(block).not.toBeNull();
239
+ expect(block!.id).toBe("memory-v2-static");
240
+ });
163
241
  });
@@ -128,12 +128,19 @@ describe("Parallel tool execution benchmarks", () => {
128
128
  return { content: "ok", isError: false };
129
129
  };
130
130
 
131
- const loop = new AgentLoop(provider, "system", {
131
+ const loop = new AgentLoop({
132
+ provider: provider,
133
+ systemPrompt: "system",
134
+ conversationId: "test-conversation",
132
135
  tools: dummyTools,
133
136
  toolExecutor: toolExecutor,
134
137
  });
135
138
  const start = Date.now();
136
- await loop.run([userMessage], () => {});
139
+ await loop.run({
140
+ messages: [userMessage],
141
+ onEvent: () => {},
142
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
143
+ });
137
144
  const elapsed = Date.now() - start;
138
145
 
139
146
  // Parallel: ~50ms + overhead. Sequential would be ~250ms.
@@ -169,12 +176,19 @@ describe("Parallel tool execution benchmarks", () => {
169
176
  return { content: "ok", isError: false };
170
177
  };
171
178
 
172
- const loop = new AgentLoop(provider, "system", {
179
+ const loop = new AgentLoop({
180
+ provider: provider,
181
+ systemPrompt: "system",
182
+ conversationId: "test-conversation",
173
183
  tools: dummyTools,
174
184
  toolExecutor: toolExecutor,
175
185
  });
176
186
  const start = Date.now();
177
- await loop.run([userMessage], () => {});
187
+ await loop.run({
188
+ messages: [userMessage],
189
+ onEvent: () => {},
190
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
191
+ });
178
192
  const elapsed = Date.now() - start;
179
193
 
180
194
  // All 10 tools should have executed
@@ -222,13 +236,20 @@ describe("Parallel tool execution benchmarks", () => {
222
236
  return { content: "ok", isError: false };
223
237
  };
224
238
 
225
- const loop = new AgentLoop(provider, "system", {
239
+ const loop = new AgentLoop({
240
+ provider: provider,
241
+ systemPrompt: "system",
242
+ conversationId: "test-conversation",
226
243
  tools: dummyTools,
227
244
  toolExecutor: toolExecutor,
228
245
  });
229
246
  const events: AgentEvent[] = [];
230
247
  const start = Date.now();
231
- await loop.run([userMessage], collectEvents(events));
248
+ await loop.run({
249
+ messages: [userMessage],
250
+ onEvent: collectEvents(events),
251
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
252
+ });
232
253
  const elapsed = Date.now() - start;
233
254
 
234
255
  // Parallel: ~2000ms (dominated by slow tool). Sequential: ~2400ms (2000 + 4*100).
@@ -289,12 +310,18 @@ describe("Parallel tool execution benchmarks", () => {
289
310
  return { content: "should not return", isError: false };
290
311
  };
291
312
 
292
- const loop = new AgentLoop(provider, "system", {
313
+ const loop = new AgentLoop({
314
+ provider: provider,
315
+ systemPrompt: "system",
316
+ conversationId: "test-conversation",
293
317
  tools: dummyTools,
294
318
  toolExecutor: toolExecutor,
295
319
  });
296
320
  const start = Date.now();
297
- const { history } = await loop.run([userMessage], () => {}, {
321
+ const { history } = await loop.run({
322
+ messages: [userMessage],
323
+ onEvent: () => {},
324
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
298
325
  signal: controller.signal,
299
326
  });
300
327
  const elapsed = Date.now() - start;