@vellumai/assistant 0.8.8 → 0.8.9-staging.1

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 (340) hide show
  1. package/bun.lock +2 -2
  2. package/examples/plugins/echo/README.md +61 -60
  3. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  4. package/examples/plugins/echo/hooks/stop.ts +16 -0
  5. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  6. package/examples/plugins/echo/package.json +1 -2
  7. package/examples/plugins/echo/src/emit.ts +19 -0
  8. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  9. package/openapi.yaml +236 -6
  10. package/package.json +2 -2
  11. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  12. package/src/__tests__/agent-loop-exit-reason.test.ts +185 -140
  13. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  14. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  15. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  16. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  17. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  18. package/src/__tests__/agent-loop.test.ts +441 -96
  19. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  20. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  21. package/src/__tests__/anthropic-provider.test.ts +1 -1
  22. package/src/__tests__/app-control-flow.test.ts +1 -1
  23. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  24. package/src/__tests__/approval-cascade.test.ts +5 -4
  25. package/src/__tests__/approval-routes-http.test.ts +4 -1
  26. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  27. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  28. package/src/__tests__/channel-approvals.test.ts +1 -1
  29. package/src/__tests__/compaction-circuit.test.ts +258 -0
  30. package/src/__tests__/compaction-direct.test.ts +132 -0
  31. package/src/__tests__/compaction-events.test.ts +5 -5
  32. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -4
  33. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  34. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +59 -103
  35. package/src/__tests__/conversation-agent-loop-overflow.test.ts +94 -189
  36. package/src/__tests__/conversation-agent-loop.test.ts +249 -490
  37. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  38. package/src/__tests__/conversation-confirmation-signals.test.ts +5 -4
  39. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  40. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -4
  41. package/src/__tests__/conversation-process-callsite.test.ts +14 -14
  42. package/src/__tests__/conversation-provider-retry-repair.test.ts +18 -16
  43. package/src/__tests__/conversation-queue.test.ts +9 -9
  44. package/src/__tests__/conversation-runtime-assembly.test.ts +923 -231
  45. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  46. package/src/__tests__/conversation-slash-queue.test.ts +6 -4
  47. package/src/__tests__/conversation-slash-unknown.test.ts +6 -4
  48. package/src/__tests__/conversation-speed-override.test.ts +10 -9
  49. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  50. package/src/__tests__/conversation-workspace-cache-state.test.ts +23 -20
  51. package/src/__tests__/conversation-workspace-injection.test.ts +68 -6
  52. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +14 -11
  53. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  54. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  55. package/src/__tests__/db-acp-history.test.ts +101 -0
  56. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  57. package/src/__tests__/empty-response-hook.test.ts +1 -1
  58. package/src/__tests__/file-write-tool.test.ts +63 -0
  59. package/src/__tests__/gateway-only-guard.test.ts +12 -2
  60. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  61. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  62. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  63. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  64. package/src/__tests__/heartbeat-service.test.ts +1 -0
  65. package/src/__tests__/history-repair-hook.test.ts +1 -1
  66. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  67. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  68. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  69. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  70. package/src/__tests__/injector-background-turn.test.ts +13 -23
  71. package/src/__tests__/injector-chain.test.ts +268 -44
  72. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  73. package/src/__tests__/injector-document-comments.test.ts +97 -114
  74. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  75. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  76. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  77. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  78. package/src/__tests__/memory-retrieval-hook.test.ts +73 -6
  79. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  80. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  81. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  82. package/src/__tests__/plugin-bootstrap.test.ts +12 -23
  83. package/src/__tests__/plugin-registry.test.ts +3 -49
  84. package/src/__tests__/plugin-types.test.ts +0 -70
  85. package/src/__tests__/reaction-persistence.test.ts +1 -1
  86. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  87. package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
  88. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  89. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  90. package/src/__tests__/subagent-detail.test.ts +25 -7
  91. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  92. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  93. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  94. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  95. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  96. package/src/__tests__/title-generate-hook.test.ts +1 -1
  97. package/src/__tests__/tool-error-hook.test.ts +1 -1
  98. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  99. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  100. package/src/acp/__tests__/agent-process.test.ts +161 -0
  101. package/src/acp/__tests__/client-handler.test.ts +40 -0
  102. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  103. package/src/acp/__tests__/helpers/exec-file-stub.ts +101 -0
  104. package/src/acp/__tests__/prepare-agent-env.test.ts +137 -0
  105. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  106. package/src/acp/__tests__/session-manager-resume.test.ts +736 -0
  107. package/src/acp/agent-process.ts +61 -1
  108. package/src/acp/auto-install.test.ts +196 -0
  109. package/src/acp/auto-install.ts +177 -0
  110. package/src/acp/client-handler.ts +31 -0
  111. package/src/acp/feature-gate.test.ts +48 -0
  112. package/src/acp/feature-gate.ts +34 -0
  113. package/src/acp/prepare-agent-env.ts +83 -29
  114. package/src/acp/resolve-agent.test.ts +320 -7
  115. package/src/acp/resolve-agent.ts +182 -18
  116. package/src/acp/resume-hint.ts +25 -0
  117. package/src/acp/session-manager.ts +495 -73
  118. package/src/acp/types.ts +8 -0
  119. package/src/agent/compaction-circuit.ts +60 -102
  120. package/src/agent/loop.ts +390 -240
  121. package/src/api/responses/conversation-message.ts +14 -1
  122. package/src/approvals/guardian-request-resolvers.ts +1 -1
  123. package/src/background-wake/next-wake.ts +1 -0
  124. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  125. package/src/cli/commands/plugins.ts +43 -37
  126. package/src/cli/lib/__tests__/install-from-github.test.ts +429 -111
  127. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  128. package/src/cli/lib/__tests__/plugin-details.test.ts +372 -0
  129. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +220 -0
  130. package/src/cli/lib/__tests__/search-plugins.test.ts +226 -32
  131. package/src/cli/lib/install-from-github.ts +464 -55
  132. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  133. package/src/cli/lib/plugin-details.ts +409 -0
  134. package/src/cli/lib/plugin-marketplace.ts +197 -0
  135. package/src/cli/lib/search-plugins.ts +195 -29
  136. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  137. package/src/config/acp-defaults.test.ts +10 -0
  138. package/src/config/acp-defaults.ts +6 -0
  139. package/src/config/bundled-skills/acp/SKILL.md +83 -31
  140. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  141. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  142. package/src/config/bundled-skills/app-builder/TOOLS.json +29 -0
  143. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  144. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  145. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  146. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  147. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  148. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  149. package/src/config/bundled-tool-registry.ts +2 -0
  150. package/src/config/feature-flag-registry.json +14 -5
  151. package/src/config/schemas/heartbeat.ts +9 -0
  152. package/src/context/strip-injections.ts +8 -2
  153. package/src/context/window-manager.ts +27 -13
  154. package/src/daemon/conversation-agent-loop-handlers.ts +10 -35
  155. package/src/daemon/conversation-agent-loop.ts +167 -1005
  156. package/src/daemon/conversation-lifecycle.ts +11 -255
  157. package/src/daemon/conversation-process.ts +8 -136
  158. package/src/daemon/conversation-registry.ts +159 -0
  159. package/src/daemon/conversation-runtime-assembly.ts +293 -392
  160. package/src/daemon/conversation-store.ts +9 -90
  161. package/src/daemon/conversation-surfaces.ts +24 -8
  162. package/src/daemon/conversation-workspace.ts +17 -0
  163. package/src/daemon/conversation.ts +383 -56
  164. package/src/daemon/external-plugins-bootstrap.ts +14 -19
  165. package/src/daemon/handlers/conversations.ts +3 -1
  166. package/src/daemon/handlers/skills.ts +4 -1
  167. package/src/daemon/host-proxy-preactivation.ts +1 -3
  168. package/src/daemon/lifecycle.ts +21 -0
  169. package/src/daemon/server.ts +2 -0
  170. package/src/daemon/wake-conversation-ops.ts +269 -0
  171. package/src/embedded/plugin-api.ts +2 -2
  172. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  173. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  174. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  175. package/src/heartbeat/heartbeat-service.ts +26 -0
  176. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  177. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  178. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  179. package/src/ipc/skill-routes/memory.ts +4 -2
  180. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  181. package/src/memory/conversation-crud.ts +29 -19
  182. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  183. package/src/memory/db-init.ts +2 -0
  184. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  185. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  186. package/src/memory/jobs-worker.ts +25 -1
  187. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  188. package/src/memory/migrations/index.ts +1 -0
  189. package/src/memory/schema/acp.ts +4 -0
  190. package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
  191. package/src/memory/v2/consolidation-job.ts +13 -4
  192. package/src/plugin-api/constants.ts +4 -0
  193. package/src/plugin-api/index.ts +6 -5
  194. package/src/plugin-api/types.ts +75 -0
  195. package/src/plugins/defaults/compaction/compact.ts +59 -0
  196. package/src/plugins/defaults/compaction/manager-store.ts +44 -0
  197. package/src/plugins/defaults/compaction/package.json +1 -2
  198. package/src/plugins/defaults/empty-response/package.json +0 -1
  199. package/src/plugins/defaults/history-repair/package.json +0 -1
  200. package/src/plugins/defaults/index.ts +135 -26
  201. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +103 -38
  202. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +167 -56
  203. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  204. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  205. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  206. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
  207. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +9 -6
  208. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
  209. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +8 -5
  210. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
  211. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
  212. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +10 -5
  213. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
  214. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +5 -5
  215. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +16 -17
  216. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -2
  217. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +9 -5
  218. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +5 -2
  219. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  220. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  221. package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
  222. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +15 -8
  223. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +2 -2
  224. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  225. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
  226. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/provider-blocks.ts +1 -1
  227. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +7 -3
  228. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
  229. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +5 -5
  230. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
  231. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +7 -7
  232. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +32 -94
  233. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
  234. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +1 -1
  235. package/src/plugins/defaults/title-generate/package.json +0 -1
  236. package/src/plugins/defaults/tool-error/package.json +0 -1
  237. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  238. package/src/plugins/pipeline.ts +6 -293
  239. package/src/plugins/registry.ts +9 -37
  240. package/src/plugins/types.ts +76 -381
  241. package/src/plugins/user-loader.ts +30 -127
  242. package/src/proactive-artifact/aux-message-injector.ts +1 -1
  243. package/src/proactive-artifact/job.test.ts +1 -1
  244. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  245. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +35 -3
  246. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  247. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  248. package/src/runtime/agent-wake.ts +108 -209
  249. package/src/runtime/assistant-event-hub.ts +1 -1
  250. package/src/runtime/channel-approvals.ts +1 -1
  251. package/src/runtime/interactive-ui.ts +1 -1
  252. package/src/runtime/routes/__tests__/acp-routes.test.ts +283 -55
  253. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  254. package/src/runtime/routes/__tests__/plugins-routes.test.ts +398 -73
  255. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  256. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  257. package/src/runtime/routes/acp-routes.test.ts +89 -25
  258. package/src/runtime/routes/acp-routes.ts +81 -29
  259. package/src/runtime/routes/approval-routes.ts +1 -1
  260. package/src/runtime/routes/browser-routes.ts +1 -1
  261. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  262. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  263. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  264. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  265. package/src/runtime/routes/conversation-routes.ts +28 -2
  266. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  267. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  268. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  269. package/src/runtime/routes/host-cu-routes.ts +1 -1
  270. package/src/runtime/routes/identity-routes.ts +18 -3
  271. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  272. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  273. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  274. package/src/runtime/routes/memory-v3-routes.ts +16 -6
  275. package/src/runtime/routes/playground/helpers.ts +1 -1
  276. package/src/runtime/routes/plugins-routes.ts +336 -35
  277. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  278. package/src/runtime/routes/work-items-routes.ts +2 -4
  279. package/src/runtime/services/conversation-serializer.ts +1 -1
  280. package/src/signals/cancel.ts +2 -4
  281. package/src/subagent/manager.ts +21 -5
  282. package/src/tools/acp/context.ts +20 -0
  283. package/src/tools/acp/list-agents.test.ts +7 -1
  284. package/src/tools/acp/spawn.test.ts +158 -55
  285. package/src/tools/acp/spawn.ts +47 -72
  286. package/src/tools/acp/steer.test.ts +105 -8
  287. package/src/tools/acp/steer.ts +48 -17
  288. package/src/tools/apps/executors.ts +13 -8
  289. package/src/tools/filesystem/write.ts +34 -0
  290. package/src/tools/subagent/spawn.ts +2 -4
  291. package/src/tools/ui-surface/definitions.ts +25 -5
  292. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  293. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  294. package/docs/plugins.md +0 -832
  295. package/examples/plugins/echo/register.ts +0 -143
  296. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  297. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  298. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  299. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  300. package/src/__tests__/pipeline-runner.test.ts +0 -554
  301. package/src/__tests__/plugin-external-api.test.ts +0 -68
  302. package/src/daemon/wake-target-adapter.ts +0 -253
  303. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  304. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  305. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  306. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  307. package/src/plugins/defaults/compaction/register.ts +0 -35
  308. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  309. package/src/plugins/defaults/empty-response/register.ts +0 -23
  310. package/src/plugins/defaults/history-repair/register.ts +0 -24
  311. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  312. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  313. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  314. package/src/plugins/defaults/title-generate/register.ts +0 -35
  315. package/src/plugins/defaults/tool-error/register.ts +0 -23
  316. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  317. package/src/plugins/external-api.ts +0 -104
  318. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
  319. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
  320. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  321. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  322. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +0 -0
  323. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
  324. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/provider-blocks.test.ts +0 -0
  325. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
  326. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
  327. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  328. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  329. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
  330. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  331. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  332. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  333. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  334. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  335. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  336. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
  337. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
  338. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
  339. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
  340. /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,15 @@
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 and `applyRuntimeInjections` so the test doesn't touch
10
+ * the developer's real `~/.vellum`, database, or live conversation state. The
11
+ * memory graph handle is a hand-rolled fake passed on the hook context — the
12
+ * hook only needs `prepareMemory`.
10
13
  */
11
14
 
12
15
  import { beforeEach, describe, expect, mock, test } from "bun:test";
@@ -23,6 +26,17 @@ mock.module("../memory/memory-recall-log-store.js", () => ({
23
26
  recordMemoryRecallLog: recordMemoryRecallLogMock,
24
27
  }));
25
28
 
29
+ // Pass-through injection stub: returns the input messages unchanged with no
30
+ // assembled blocks, so existing array-identity assertions hold. Individual
31
+ // tests override it via `mockImplementationOnce` to exercise injected blocks.
32
+ const applyRuntimeInjectionsMock = mock(async (messages: unknown) => ({
33
+ messages,
34
+ blocks: {},
35
+ }));
36
+ mock.module("../daemon/conversation-runtime-assembly.js", () => ({
37
+ applyRuntimeInjections: applyRuntimeInjectionsMock,
38
+ }));
39
+
26
40
  import type { AssistantConfig } from "../config/schema.js";
27
41
  import type { ServerMessage } from "../daemon/message-protocol.js";
28
42
  import type { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
@@ -117,6 +131,11 @@ function makeHookCtx(
117
131
  } as unknown as MemoryRetrievalHookContext["logger"],
118
132
  signal: new AbortController().signal,
119
133
  latestMessages: [],
134
+ requestId: "req-test",
135
+ mode: "full",
136
+ isNonInteractive: false,
137
+ modelProfile: null,
138
+ actorContext: null,
120
139
  ...overrides,
121
140
  };
122
141
  }
@@ -124,6 +143,7 @@ function makeHookCtx(
124
143
  beforeEach(() => {
125
144
  updateMessageMetadataMock.mockReset();
126
145
  recordMemoryRecallLogMock.mockReset();
146
+ applyRuntimeInjectionsMock.mockClear();
127
147
  });
128
148
 
129
149
  describe("user-prompt-submit-temp hook (memory retrieval)", () => {
@@ -177,6 +197,53 @@ describe("user-prompt-submit-temp hook (memory retrieval)", () => {
177
197
  );
178
198
  });
179
199
 
200
+ test("applies runtime injection on the retrieved history and persists the assembled blocks", async () => {
201
+ const retrieved: Message[] = [
202
+ { role: "user", content: [{ type: "text", text: "retrieved" }] },
203
+ ];
204
+ const injected: Message[] = [
205
+ { role: "user", content: [{ type: "text", text: "injected" }] },
206
+ ];
207
+ const { memory } = makeFakeGraphMemory({ messages: retrieved });
208
+ applyRuntimeInjectionsMock.mockImplementationOnce(async () => ({
209
+ messages: injected,
210
+ blocks: { unifiedTurnContext: "tc-block" },
211
+ }));
212
+ const ctx = makeHookCtx({ graphMemory: memory, userMessageId: "msg-77" });
213
+
214
+ await userPromptSubmitMemoryRetrieval(ctx);
215
+
216
+ // Injection runs on the retrieved history; its result becomes the working
217
+ // list the loop reads back.
218
+ expect(applyRuntimeInjectionsMock).toHaveBeenCalledTimes(1);
219
+ expect(applyRuntimeInjectionsMock.mock.calls[0]?.[0]).toBe(retrieved);
220
+ expect(ctx.latestMessages).toBe(injected);
221
+ // The assembled blocks are persisted onto the user message metadata.
222
+ expect(updateMessageMetadataMock).toHaveBeenCalledWith("msg-77", {
223
+ turnContextBlock: "tc-block",
224
+ });
225
+ });
226
+
227
+ test("applies runtime injection for untrusted actors despite skipping retrieval", async () => {
228
+ const { memory, prepareMemoryMock } = makeFakeGraphMemory();
229
+ const seeded: Message[] = [
230
+ { role: "user", content: [{ type: "text", text: "seeded" }] },
231
+ ];
232
+ const ctx = makeHookCtx({
233
+ graphMemory: memory,
234
+ isTrustedActor: false,
235
+ latestMessages: seeded,
236
+ });
237
+
238
+ await userPromptSubmitMemoryRetrieval(ctx);
239
+
240
+ // The memory-graph step is gated on trust, but injection runs for everyone
241
+ // — on the seeded history, since no retrieval replaced it.
242
+ expect(prepareMemoryMock).not.toHaveBeenCalled();
243
+ expect(applyRuntimeInjectionsMock).toHaveBeenCalledTimes(1);
244
+ expect(applyRuntimeInjectionsMock.mock.calls[0]?.[0]).toBe(seeded);
245
+ });
246
+
180
247
  test("skips graph retrieval and side effects for untrusted actors", async () => {
181
248
  const { memory, prepareMemoryMock, recordPkbQueryVectorsMock } =
182
249
  makeFakeGraphMemory();
@@ -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;
@@ -34,22 +34,19 @@ describe("buildShimSource", () => {
34
34
  test("emits a globalThis trampoline + one binding per export", () => {
35
35
  const source = buildShimSource(
36
36
  ["foo", "bar"],
37
- Symbol.for("vellum.plugin-api.v1"),
37
+ Symbol.for("vellum.plugin-api"),
38
38
  );
39
39
  expect(source).toBe(
40
- `const api = globalThis[Symbol.for("vellum.plugin-api.v1")];\n` +
40
+ `const api = globalThis[Symbol.for("vellum.plugin-api")];\n` +
41
41
  `export const foo = api.foo;\n` +
42
42
  `export const bar = api.bar;\n`,
43
43
  );
44
44
  });
45
45
 
46
46
  test("handles an empty export list (today's types-only surface)", () => {
47
- const source = buildShimSource(
48
- [],
49
- Symbol.for("vellum.plugin-api.v1"),
50
- );
47
+ const source = buildShimSource([], Symbol.for("vellum.plugin-api"));
51
48
  expect(source).toBe(
52
- `const api = globalThis[Symbol.for("vellum.plugin-api.v1")];\n`,
49
+ `const api = globalThis[Symbol.for("vellum.plugin-api")];\n`,
53
50
  );
54
51
  });
55
52
  });
@@ -109,7 +106,7 @@ describe("ensurePluginApiShim", () => {
109
106
  const pluginDir = join(workspaceDir, "plugins", "fake-plugin");
110
107
  await mkdir(pluginDir, { recursive: true });
111
108
  await writeFile(
112
- join(pluginDir, "register.js"),
109
+ join(pluginDir, "probe.js"),
113
110
  `import * as api from "@vellumai/plugin-api";\nexport { api };\n`,
114
111
  );
115
112
 
@@ -118,7 +115,7 @@ describe("ensurePluginApiShim", () => {
118
115
  // → plugin-api namespace. If any link in that chain is broken, this
119
116
  // import throws.
120
117
  const mod: { api: Record<string, unknown> } = await import(
121
- join(pluginDir, "register.js")
118
+ join(pluginDir, "probe.js")
122
119
  );
123
120
  expect(mod.api).toBeDefined();
124
121
  });
@@ -38,13 +38,11 @@ import { RiskLevel } from "../permissions/types.js";
38
38
  import { registerDefaultPlugins } from "../plugins/defaults/index.js";
39
39
  import {
40
40
  closeRegistration,
41
- getMiddlewaresFor,
42
41
  getRegisteredPlugins,
43
42
  registerPlugin,
44
43
  resetPluginRegistryForTests,
45
44
  } from "../plugins/registry.js";
46
45
  import {
47
- type PipelineMiddlewareMap,
48
46
  type Plugin,
49
47
  PluginExecutionError,
50
48
  type PluginInitContext,
@@ -485,33 +483,24 @@ describe("plugin bootstrap", () => {
485
483
  expect(initFired).toBe(false);
486
484
  });
487
485
 
488
- test("requiresFlag disabled: plugin middleware is dropped from the registry", async () => {
489
- // Regression: prior to the unregisterPlugin() call on the flag-gated skip
490
- // path, `getMiddlewaresFor()` iterated over every entry in
491
- // `registeredPlugins` — so a gated-off plugin's middleware still ran on
492
- // every pipeline invocation even though `init()` had never fired to set up
493
- // the state it depended on.
494
- setOverridesForTesting({ "plugin-middleware-disabled": false });
495
-
496
- const gatedMiddleware: PipelineMiddlewareMap["compaction"] = async (
497
- args,
498
- next,
499
- ) => next(args);
486
+ test("requiresFlag disabled: the skipped plugin is removed from the registry", async () => {
487
+ // Regression: a flag-gated skip must call `unregisterPlugin()` so the
488
+ // gated-off plugin does not linger in `registeredPlugins` with its
489
+ // `init()` never having fired to set up the state it depends on.
490
+ setOverridesForTesting({ "plugin-registry-disabled": false });
491
+
500
492
  const plugin = buildPlugin(
501
- "gated-middleware",
502
- {
503
- middleware: { compaction: gatedMiddleware },
504
- },
505
- { requiresFlag: ["plugin-middleware-disabled"] },
493
+ "gated-registry",
494
+ {},
495
+ { requiresFlag: ["plugin-registry-disabled"] },
506
496
  );
507
497
  registerPlugin(plugin);
508
498
 
509
499
  await bootstrapPlugins();
510
500
 
511
- // The middleware slot must not expose the flag-gated plugin's contribution.
512
- // The default plugins also contribute compaction middleware, so we key on
513
- // identity rather than asserting an empty list.
514
- expect(getMiddlewaresFor("compaction")).not.toContain(gatedMiddleware);
501
+ // The gated-off plugin must not survive in the registry snapshot.
502
+ const names = getRegisteredPlugins().map((p) => p.manifest.name);
503
+ expect(names).not.toContain("gated-registry");
515
504
  });
516
505
 
517
506
  test("requiresFlag disabled: no shutdown hook entry installed for the skipped plugin", async () => {