@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
@@ -1,16 +1,17 @@
1
1
  /**
2
- * Verifies that the per-turn `mutableLatestUserMessage` plumbed into
3
- * `AgentLoop.run()` surfaces on every `SendMessageOptions.config` the loop
4
- * emits this is the memory-v3-live cache-anchor signal (the latest user
5
- * message carries a volatile `<memory>` block, so the provider must anchor its
6
- * long-TTL cache breakpoint on the most recent STABLE message instead).
2
+ * Verifies that the `memory-v3-live` cache-anchor signal surfaces on every
3
+ * `SendMessageOptions.config` the loop emits. When the flag is on, the latest
4
+ * user message carries a volatile `<memory>` block, so the loop sets
5
+ * `providerConfig.mutableLatestUserMessage` to tell the provider to anchor its
6
+ * long-TTL cache breakpoint on the most recent STABLE message instead.
7
7
  *
8
- * Default behavior (option unset) must remain unchanged the field is omitted
9
- * from `providerConfig` rather than carrying `undefined`/`false`, so the wire
10
- * is byte-identical to today when the flag is off.
8
+ * The loop reads the flag directly where it assembles `providerConfig`, so the
9
+ * signal is sourced from the flag rather than a run option. When the flag is
10
+ * off the field is omitted (not `false`/`undefined`) so the wire stays
11
+ * byte-identical to today.
11
12
  */
12
13
 
13
- import { describe, expect, mock, test } from "bun:test";
14
+ import { afterEach, describe, expect, mock, test } from "bun:test";
14
15
 
15
16
  import { makeMockLogger } from "./helpers/mock-logger.js";
16
17
 
@@ -19,6 +20,10 @@ mock.module("../util/logger.js", () => ({
19
20
  }));
20
21
 
21
22
  import { AgentLoop } from "../agent/loop.js";
23
+ import {
24
+ clearCachedOverrides,
25
+ setCachedOverrides,
26
+ } from "../config/feature-flag-cache.js";
22
27
  import type {
23
28
  Message,
24
29
  Provider,
@@ -75,13 +80,20 @@ function makeRecordingProvider(responses: ProviderResponse[]): {
75
80
  return { provider, configs: () => configs };
76
81
  }
77
82
 
78
- describe("AgentLoop.run — mutableLatestUserMessage plumbing", () => {
79
- test("forwards mutableLatestUserMessage=true to providerConfig on every LLM call (multi-turn)", async () => {
83
+ describe("AgentLoop.run — mutableLatestUserMessage from memory-v3-live", () => {
84
+ afterEach(() => {
85
+ clearCachedOverrides();
86
+ });
87
+
88
+ test("sets mutableLatestUserMessage on every LLM call when memory-v3-live is on (multi-turn)", async () => {
89
+ // GIVEN memory-v3-live is enabled
90
+ setCachedOverrides({ "memory-v3-live": true }, { fromGateway: true });
91
+
92
+ // AND a provider that records the config of each LLM call across a tool round-trip
80
93
  const { provider, configs } = makeRecordingProvider([
81
94
  toolUseResponse("t1", "echo", { value: "first" }),
82
95
  textResponse("done"),
83
96
  ]);
84
-
85
97
  const dummyTools: ToolDefinition[] = [
86
98
  {
87
99
  name: "echo",
@@ -92,50 +104,53 @@ describe("AgentLoop.run — mutableLatestUserMessage plumbing", () => {
92
104
  },
93
105
  },
94
106
  ];
95
-
96
- const toolExecutor = async () => ({ content: "ok", isError: false });
97
-
98
- const loop = new AgentLoop(provider, "system", {
107
+ const loop = new AgentLoop({
108
+ provider: provider,
109
+ systemPrompt: "system",
110
+ conversationId: "test-conversation",
99
111
  config: { maxTokens: 1024 },
100
112
  tools: dummyTools,
101
- toolExecutor: toolExecutor,
113
+ toolExecutor: async () => ({ content: "ok", isError: false }),
102
114
  });
103
115
 
104
- await loop.run([userMessage], () => {}, {
116
+ // WHEN the loop runs
117
+ await loop.run({
118
+ messages: [userMessage],
119
+ onEvent: () => {},
120
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
105
121
  callSite: "mainAgent",
106
- mutableLatestUserMessage: true,
107
122
  });
108
123
 
109
- // Two sends initial + one tool round-trip. Every send carries the flag.
124
+ // THEN every send (initial + tool round-trip) carries the cache-anchor signal
110
125
  expect(configs()).toHaveLength(2);
111
126
  for (const cfg of configs()) {
112
127
  expect(cfg?.mutableLatestUserMessage).toBe(true);
113
128
  }
114
129
  });
115
130
 
116
- test("omits mutableLatestUserMessage when unset (default behavior unchanged)", async () => {
117
- const { provider, configs } = makeRecordingProvider([textResponse("hi")]);
118
- const loop = new AgentLoop(provider, "system", {
119
- config: { maxTokens: 1024 },
120
- });
121
-
122
- await loop.run([userMessage], () => {});
131
+ test("omits mutableLatestUserMessage when memory-v3-live is off (flag-off byte-identity)", async () => {
132
+ // GIVEN memory-v3-live is off (no override; registry default is false)
133
+ clearCachedOverrides();
123
134
 
124
- expect(configs()).toHaveLength(1);
125
- expect("mutableLatestUserMessage" in (configs()[0] ?? {})).toBe(false);
126
- });
127
-
128
- test("omits mutableLatestUserMessage when explicitly false (flag-off byte-identity)", async () => {
135
+ // AND a provider that records the config of each LLM call
129
136
  const { provider, configs } = makeRecordingProvider([textResponse("hi")]);
130
- const loop = new AgentLoop(provider, "system", {
137
+ const loop = new AgentLoop({
138
+ provider: provider,
139
+ systemPrompt: "system",
140
+ conversationId: "test-conversation",
131
141
  config: { maxTokens: 1024 },
132
142
  });
133
143
 
134
- await loop.run([userMessage], () => {}, {
144
+ // WHEN the loop runs
145
+ await loop.run({
146
+ messages: [userMessage],
147
+ onEvent: () => {},
148
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
135
149
  callSite: "mainAgent",
136
- mutableLatestUserMessage: false,
137
150
  });
138
151
 
152
+ // THEN the field is omitted entirely, not carried as false/undefined
153
+ expect(configs()).toHaveLength(1);
139
154
  expect("mutableLatestUserMessage" in (configs()[0] ?? {})).toBe(false);
140
155
  });
141
156
  });
@@ -0,0 +1,357 @@
1
+ /**
2
+ * Tests for the `pre-model-call` and `assistant-message` plugin hooks: a plugin
3
+ * can edit the outbound request, transform the finalized assistant message
4
+ * (persisted + streamed), and defer the live stream so the transformed text is
5
+ * emitted once. All hooks here use neutral transforms (redaction / uppercasing).
6
+ */
7
+ import { beforeEach, describe, expect, test } from "bun:test";
8
+
9
+ import type { AgentEvent } from "../agent/loop.js";
10
+ import { AgentLoop } from "../agent/loop.js";
11
+ import type {
12
+ AssistantMessageContext,
13
+ PreModelCallContext,
14
+ } from "../plugin-api/types.js";
15
+ import { resetPluginRegistryAndRegisterDefaults } from "../plugins/defaults/index.js";
16
+ import { registerPlugin } from "../plugins/registry.js";
17
+ import type {
18
+ ContentBlock,
19
+ Message,
20
+ ProviderResponse,
21
+ } from "../providers/types.js";
22
+ import { createMockProvider, textResponse } from "./helpers/mock-provider.js";
23
+
24
+ const userMessage: Message = {
25
+ role: "user",
26
+ content: [{ type: "text", text: "Hello" }],
27
+ };
28
+
29
+ function collect(events: AgentEvent[]): (event: AgentEvent) => void {
30
+ return (event) => events.push(event);
31
+ }
32
+
33
+ function streamedText(events: AgentEvent[]): string {
34
+ return events
35
+ .filter(
36
+ (e): e is Extract<AgentEvent, { type: "text_delta" }> =>
37
+ e.type === "text_delta",
38
+ )
39
+ .map((e) => e.text)
40
+ .join("");
41
+ }
42
+
43
+ function textOf(content: ReadonlyArray<ContentBlock>): string {
44
+ let out = "";
45
+ for (const block of content) if (block.type === "text") out += block.text;
46
+ return out;
47
+ }
48
+
49
+ function lastAssistant(history: Message[]): Message {
50
+ for (let i = history.length - 1; i >= 0; i--) {
51
+ if (history[i].role === "assistant") return history[i];
52
+ }
53
+ throw new Error("no assistant message in history");
54
+ }
55
+
56
+ function registerOutputHookPlugin(hooks: {
57
+ preModelCall?: (ctx: PreModelCallContext) => void;
58
+ assistantMessage?: (ctx: AssistantMessageContext) => void;
59
+ }): void {
60
+ registerPlugin({
61
+ manifest: { name: "test-output-hooks", version: "0.0.0" },
62
+ hooks: {
63
+ ...(hooks.preModelCall
64
+ ? {
65
+ "pre-model-call": async (ctx: PreModelCallContext) => {
66
+ hooks.preModelCall!(ctx);
67
+ },
68
+ }
69
+ : {}),
70
+ ...(hooks.assistantMessage
71
+ ? {
72
+ "assistant-message": async (ctx: AssistantMessageContext) => {
73
+ hooks.assistantMessage!(ctx);
74
+ },
75
+ }
76
+ : {}),
77
+ },
78
+ });
79
+ }
80
+
81
+ describe("agent loop output hooks", () => {
82
+ beforeEach(() => {
83
+ resetPluginRegistryAndRegisterDefaults();
84
+ });
85
+
86
+ test("assistant-message transforms the persisted message content", async () => {
87
+ registerOutputHookPlugin({
88
+ assistantMessage: (ctx) => {
89
+ ctx.content = ctx.content.map((b) =>
90
+ b.type === "text"
91
+ ? { type: "text", text: b.text.replace("secret", "[redacted]") }
92
+ : b,
93
+ );
94
+ },
95
+ });
96
+ const { provider } = createMockProvider([textResponse("my secret value")]);
97
+ const loop = new AgentLoop({
98
+ provider: provider,
99
+ systemPrompt: "system",
100
+ conversationId: "test-conversation",
101
+ });
102
+ const events: AgentEvent[] = [];
103
+ const { history } = await loop.run({
104
+ messages: [userMessage],
105
+ onEvent: collect(events),
106
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
107
+ });
108
+ expect(textOf(lastAssistant(history).content)).toBe("my [redacted] value");
109
+ });
110
+
111
+ test("deferred output: the real stream is suppressed and the transformed text is emitted once", async () => {
112
+ registerOutputHookPlugin({
113
+ preModelCall: (ctx) => {
114
+ ctx.deferAssistantOutput = true;
115
+ },
116
+ assistantMessage: (ctx) => {
117
+ ctx.content = [{ type: "text", text: "[filtered]" }];
118
+ },
119
+ });
120
+ const { provider } = createMockProvider([textResponse("my secret value")]);
121
+ const loop = new AgentLoop({
122
+ provider: provider,
123
+ systemPrompt: "system",
124
+ conversationId: "test-conversation",
125
+ });
126
+ const events: AgentEvent[] = [];
127
+ const { history } = await loop.run({
128
+ messages: [userMessage],
129
+ onEvent: collect(events),
130
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
131
+ });
132
+ // Her real words never streamed; only the transformed text did, once.
133
+ expect(streamedText(events)).not.toContain("secret");
134
+ expect(streamedText(events)).toBe("[filtered]");
135
+ expect(textOf(lastAssistant(history).content)).toBe("[filtered]");
136
+ });
137
+
138
+ test("without defer, the real text still streams while storage is transformed", async () => {
139
+ registerOutputHookPlugin({
140
+ assistantMessage: (ctx) => {
141
+ ctx.content = [{ type: "text", text: "[stored]" }];
142
+ },
143
+ });
144
+ const { provider } = createMockProvider([textResponse("live text")]);
145
+ const loop = new AgentLoop({
146
+ provider: provider,
147
+ systemPrompt: "system",
148
+ conversationId: "test-conversation",
149
+ });
150
+ const events: AgentEvent[] = [];
151
+ const { history } = await loop.run({
152
+ messages: [userMessage],
153
+ onEvent: collect(events),
154
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
155
+ });
156
+ expect(streamedText(events)).toBe("live text"); // streamed live, untransformed
157
+ expect(textOf(lastAssistant(history).content)).toBe("[stored]"); // storage transformed
158
+ });
159
+
160
+ test("transforms text but leaves tool_use blocks intact", async () => {
161
+ const toolAndText: ProviderResponse = {
162
+ content: [
163
+ { type: "text", text: "calling" },
164
+ { type: "tool_use", id: "t1", name: "noop", input: {} },
165
+ ],
166
+ model: "mock-model",
167
+ usage: { inputTokens: 1, outputTokens: 1 },
168
+ stopReason: "tool_use",
169
+ };
170
+ registerOutputHookPlugin({
171
+ assistantMessage: (ctx) => {
172
+ ctx.content = ctx.content.map((b) =>
173
+ b.type === "text" ? { type: "text", text: b.text.toUpperCase() } : b,
174
+ );
175
+ },
176
+ });
177
+ const { provider } = createMockProvider([
178
+ toolAndText,
179
+ textResponse("done"),
180
+ ]);
181
+ const loop = new AgentLoop({
182
+ provider: provider,
183
+ systemPrompt: "system",
184
+ conversationId: "test-conversation",
185
+ tools: [
186
+ {
187
+ name: "noop",
188
+ description: "",
189
+ input_schema: { type: "object", properties: {} },
190
+ },
191
+ ],
192
+ toolExecutor: async () => ({ content: "ok", isError: false }),
193
+ });
194
+ const { history } = await loop.run({
195
+ messages: [userMessage],
196
+ onEvent: collect([]),
197
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
198
+ });
199
+ const toolTurn = history.find(
200
+ (m) =>
201
+ m.role === "assistant" && m.content.some((b) => b.type === "tool_use"),
202
+ )!;
203
+ expect(toolTurn.content.find((b) => b.type === "text")).toEqual({
204
+ type: "text",
205
+ text: "CALLING",
206
+ });
207
+ expect(toolTurn.content.find((b) => b.type === "tool_use")).toMatchObject({
208
+ type: "tool_use",
209
+ id: "t1",
210
+ name: "noop",
211
+ });
212
+ });
213
+
214
+ test("pre-model-call can edit the outbound system prompt", async () => {
215
+ registerOutputHookPlugin({
216
+ preModelCall: (ctx) => {
217
+ ctx.systemPrompt = `${ctx.systemPrompt ?? ""} [EDITED]`;
218
+ },
219
+ });
220
+ const { provider, calls } = createMockProvider([textResponse("hi")]);
221
+ const loop = new AgentLoop({
222
+ provider: provider,
223
+ systemPrompt: "base prompt",
224
+ conversationId: "test-conversation",
225
+ });
226
+ await loop.run({
227
+ messages: [userMessage],
228
+ onEvent: collect([]),
229
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
230
+ });
231
+ expect(calls[0].systemPrompt).toContain("[EDITED]");
232
+ });
233
+
234
+ test("fail-open: a hook that mutates in place AND then throws cannot corrupt the persisted content", async () => {
235
+ // The hook mutates the array it receives before throwing. If the loop
236
+ // handed the hook the real `assistantMessage.content` array, the
237
+ // mid-mutation would survive into history. The loop must clone the
238
+ // content before invoking the hook.
239
+ registerOutputHookPlugin({
240
+ assistantMessage: (ctx) => {
241
+ ctx.content.push({ type: "text", text: "[INJECTED]" });
242
+ throw new Error("boom");
243
+ },
244
+ });
245
+ const { provider } = createMockProvider([textResponse("untouched")]);
246
+ const loop = new AgentLoop({
247
+ provider: provider,
248
+ systemPrompt: "system",
249
+ conversationId: "test-conversation",
250
+ });
251
+ const { history } = await loop.run({
252
+ messages: [userMessage],
253
+ onEvent: collect([]),
254
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
255
+ });
256
+ const finalContent = lastAssistant(history).content;
257
+ expect(textOf(finalContent)).toBe("untouched");
258
+ expect(
259
+ finalContent.some((b) => b.type === "text" && b.text === "[INJECTED]"),
260
+ ).toBe(false);
261
+ });
262
+
263
+ test("max_tokens turn: the hook fires and the deferred final text is emitted", async () => {
264
+ // Without this, an output-filter plugin misses the truncated reply; with
265
+ // defer set, the live stream was suppressed and the client would see
266
+ // nothing at all.
267
+ const seen: { calls: number } = { calls: 0 };
268
+ registerOutputHookPlugin({
269
+ preModelCall: (ctx) => {
270
+ ctx.deferAssistantOutput = true;
271
+ },
272
+ assistantMessage: (ctx) => {
273
+ seen.calls += 1;
274
+ ctx.content = ctx.content.map((b) =>
275
+ b.type === "text" ? { type: "text", text: b.text.toUpperCase() } : b,
276
+ );
277
+ },
278
+ });
279
+ const truncated: ProviderResponse = {
280
+ content: [{ type: "text", text: "partial answer" }],
281
+ model: "mock-model",
282
+ usage: { inputTokens: 1, outputTokens: 1 },
283
+ stopReason: "max_tokens",
284
+ };
285
+ const { provider } = createMockProvider([truncated]);
286
+ const loop = new AgentLoop({
287
+ provider: provider,
288
+ systemPrompt: "system",
289
+ conversationId: "test-conversation",
290
+ });
291
+ const events: AgentEvent[] = [];
292
+ const { history } = await loop.run({
293
+ messages: [userMessage],
294
+ onEvent: collect(events),
295
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
296
+ });
297
+ expect(seen.calls).toBe(1);
298
+ expect(textOf(lastAssistant(history).content)).toBe("PARTIAL ANSWER");
299
+ // Real stream suppressed; transformed final text emitted once.
300
+ expect(streamedText(events)).toBe("PARTIAL ANSWER");
301
+ });
302
+
303
+ test("deferred final text passes through sensitive-output substitution", async () => {
304
+ // A tool returns a sensitive binding; the next assistant turn uses the
305
+ // placeholder (the persisted message must keep it — model never sees real
306
+ // values on reload), but the deferred final stream must show the real
307
+ // value, just like the normal live stream would.
308
+ registerOutputHookPlugin({
309
+ preModelCall: (ctx) => {
310
+ ctx.deferAssistantOutput = true;
311
+ },
312
+ });
313
+ const placeholder = "VELLUM_ASSISTANT_INVITE_CODE_TESTXXXX";
314
+ const real = "real-invite-code-xyz";
315
+ const toolThenText = [
316
+ {
317
+ content: [{ type: "tool_use", id: "t1", name: "issue", input: {} }],
318
+ model: "mock-model",
319
+ usage: { inputTokens: 1, outputTokens: 1 },
320
+ stopReason: "tool_use",
321
+ } as ProviderResponse,
322
+ textResponse(`Your code is ${placeholder}.`),
323
+ ];
324
+ const { provider } = createMockProvider(toolThenText);
325
+ const loop = new AgentLoop({
326
+ provider: provider,
327
+ systemPrompt: "system",
328
+ conversationId: "test-conversation",
329
+ tools: [
330
+ {
331
+ name: "issue",
332
+ description: "",
333
+ input_schema: { type: "object", properties: {} },
334
+ },
335
+ ],
336
+ toolExecutor: async () => ({
337
+ content: `code=${placeholder}`,
338
+ isError: false,
339
+ sensitiveBindings: [{ kind: "invite_code", placeholder, value: real }],
340
+ }),
341
+ });
342
+ const events: AgentEvent[] = [];
343
+ const { history } = await loop.run({
344
+ messages: [userMessage],
345
+ onEvent: collect(events),
346
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
347
+ });
348
+ // Persisted message keeps the placeholder — model must never see real
349
+ // values on reload.
350
+ expect(textOf(lastAssistant(history).content)).toContain(placeholder);
351
+ expect(textOf(lastAssistant(history).content)).not.toContain(real);
352
+ // Streamed final text shows the substituted real value, matching the
353
+ // behavior of the normal live stream.
354
+ expect(streamedText(events)).toContain(real);
355
+ expect(streamedText(events)).not.toContain(placeholder);
356
+ });
357
+ });
@@ -110,13 +110,19 @@ describe("AgentLoop.run — overrideProfile plumbing", () => {
110
110
  _input: Record<string, unknown>,
111
111
  ) => ({ content: "ok", isError: false });
112
112
 
113
- const loop = new AgentLoop(provider, "system", {
113
+ const loop = new AgentLoop({
114
+ provider: provider,
115
+ systemPrompt: "system",
116
+ conversationId: "test-conversation",
114
117
  config: { maxTokens: 1024 },
115
118
  tools: dummyTools,
116
119
  toolExecutor: toolExecutor,
117
120
  });
118
121
 
119
- await loop.run([userMessage], () => {}, {
122
+ await loop.run({
123
+ messages: [userMessage],
124
+ onEvent: () => {},
125
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
120
126
  callSite: "mainAgent",
121
127
  overrideProfile: "fast",
122
128
  });
@@ -130,11 +136,18 @@ describe("AgentLoop.run — overrideProfile plumbing", () => {
130
136
 
131
137
  test("omits overrideProfile from providerConfig when unset (default behavior unchanged)", async () => {
132
138
  const { provider, configs } = makeRecordingProvider([textResponse("hi")]);
133
- const loop = new AgentLoop(provider, "system", {
139
+ const loop = new AgentLoop({
140
+ provider: provider,
141
+ systemPrompt: "system",
142
+ conversationId: "test-conversation",
134
143
  config: { maxTokens: 1024 },
135
144
  });
136
145
 
137
- await loop.run([userMessage], () => {});
146
+ await loop.run({
147
+ messages: [userMessage],
148
+ onEvent: () => {},
149
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
150
+ });
138
151
 
139
152
  // Single send, no overrideProfile field at all.
140
153
  expect(configs()).toHaveLength(1);
@@ -148,11 +161,17 @@ describe("AgentLoop.run — overrideProfile plumbing", () => {
148
161
  // receives so a non-existent profile silently falls back at the
149
162
  // provider layer (covered by provider-send-message-override-profile.test.ts).
150
163
  const { provider, configs } = makeRecordingProvider([textResponse("hi")]);
151
- const loop = new AgentLoop(provider, "system", {
164
+ const loop = new AgentLoop({
165
+ provider: provider,
166
+ systemPrompt: "system",
167
+ conversationId: "test-conversation",
152
168
  config: { maxTokens: 1024 },
153
169
  });
154
170
 
155
- await loop.run([userMessage], () => {}, {
171
+ await loop.run({
172
+ messages: [userMessage],
173
+ onEvent: () => {},
174
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
156
175
  callSite: "mainAgent",
157
176
  overrideProfile: "does-not-exist",
158
177
  });
@@ -83,14 +83,19 @@ describe("AgentLoop provider_error event emission", () => {
83
83
  const { provider, calls } = makeThrowingProvider("anthropic", () => thrown);
84
84
 
85
85
  const events: AgentEvent[] = [];
86
- const loop = new AgentLoop(provider, "you are a helpful assistant");
86
+ const loop = new AgentLoop({
87
+ provider: provider,
88
+ systemPrompt: "you are a helpful assistant",
89
+ conversationId: "test-conversation",
90
+ });
87
91
 
88
- await loop.run(
89
- [{ role: "user", content: [{ type: "text", text: "hi" }] }],
90
- (e) => {
92
+ await loop.run({
93
+ messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
94
+ onEvent: (e) => {
91
95
  events.push(e);
92
96
  },
93
- );
97
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
98
+ });
94
99
 
95
100
  expect(calls).toHaveLength(1);
96
101
 
@@ -122,14 +127,19 @@ describe("AgentLoop provider_error event emission", () => {
122
127
  const { provider } = makeThrowingProvider("gemini", () => thrown);
123
128
 
124
129
  const events: AgentEvent[] = [];
125
- const loop = new AgentLoop(provider, "system");
130
+ const loop = new AgentLoop({
131
+ provider: provider,
132
+ systemPrompt: "system",
133
+ conversationId: "test-conversation",
134
+ });
126
135
 
127
- await loop.run(
128
- [{ role: "user", content: [{ type: "text", text: "hi" }] }],
129
- (e) => {
136
+ await loop.run({
137
+ messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
138
+ onEvent: (e) => {
130
139
  events.push(e);
131
140
  },
132
- );
141
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
142
+ });
133
143
 
134
144
  const providerErrorIdx = events.findIndex(
135
145
  (e) => e.type === "provider_error",
@@ -148,14 +158,19 @@ describe("AgentLoop provider_error event emission", () => {
148
158
  const { provider } = makeThrowingProvider("openai", () => thrown);
149
159
 
150
160
  const events: AgentEvent[] = [];
151
- const loop = new AgentLoop(provider, "system");
161
+ const loop = new AgentLoop({
162
+ provider: provider,
163
+ systemPrompt: "system",
164
+ conversationId: "test-conversation",
165
+ });
152
166
 
153
- await loop.run(
154
- [{ role: "user", content: [{ type: "text", text: "hi" }] }],
155
- (e) => {
167
+ await loop.run({
168
+ messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
169
+ onEvent: (e) => {
156
170
  events.push(e);
157
171
  },
158
- );
172
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
173
+ });
159
174
 
160
175
  const providerErrorEvent = events.find((e) => e.type === "provider_error");
161
176
  expect(providerErrorEvent).toBeDefined();
@@ -179,15 +194,20 @@ describe("AgentLoop provider_error event emission", () => {
179
194
  });
180
195
 
181
196
  const events: AgentEvent[] = [];
182
- const loop = new AgentLoop(provider, "system");
197
+ const loop = new AgentLoop({
198
+ provider: provider,
199
+ systemPrompt: "system",
200
+ conversationId: "test-conversation",
201
+ });
183
202
 
184
- await loop.run(
185
- [{ role: "user", content: [{ type: "text", text: "hi" }] }],
186
- (e) => {
203
+ await loop.run({
204
+ messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
205
+ onEvent: (e) => {
187
206
  events.push(e);
188
207
  },
189
- { signal: controller.signal },
190
- );
208
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
209
+ signal: controller.signal,
210
+ });
191
211
 
192
212
  const providerErrorEvent = events.find((e) => e.type === "provider_error");
193
213
  // Cancellation should never produce a recording row — there's no