@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
@@ -13,17 +13,17 @@
13
13
  * Sites not exercised here (`aborted_via_error`) require deeper provider
14
14
  * fakery and are best covered by integration tests.
15
15
  */
16
- import { describe, expect, spyOn, test } from "bun:test";
16
+ import { describe, expect, mock, test } from "bun:test";
17
17
 
18
18
  import type {
19
19
  AgentEvent,
20
+ AgentLoopRunOptions,
20
21
  CheckpointDecision,
21
22
  CheckpointInfo,
22
- MidLoopCompaction,
23
23
  } from "../agent/loop.js";
24
24
  import { AgentLoop, isMaxTokensStopReason } from "../agent/loop.js";
25
- import type { TurnContext } from "../plugins/types.js";
26
- import { PluginTimeoutError } from "../plugins/types.js";
25
+ import type { TrustContext } from "../daemon/trust-context.js";
26
+ import type { PostCompactContext } from "../plugins/defaults/memory-retrieval/hooks/post-compact.js";
27
27
  import type {
28
28
  Message,
29
29
  Provider,
@@ -32,6 +32,23 @@ import type {
32
32
  ToolDefinition,
33
33
  } from "../providers/types.js";
34
34
 
35
+ // The agent loop invokes the default post-compaction re-injection hook directly
36
+ // when it compacts in place. Stub it so these unit tests can drive the
37
+ // re-injection result without the daemon-level injector chain. Tests assign
38
+ // `postCompactImpl` to observe the call or force a failure; when unset
39
+ // the hook is a no-op that returns the history it was handed.
40
+ let postCompactImpl:
41
+ | ((input: PostCompactContext) => Promise<Message[]>)
42
+ | null = null;
43
+ mock.module(
44
+ "../plugins/defaults/memory-retrieval/hooks/post-compact.js",
45
+ () => ({
46
+ default: async (input: PostCompactContext) => ({
47
+ messages: postCompactImpl ? await postCompactImpl(input) : input.history,
48
+ }),
49
+ }),
50
+ );
51
+
35
52
  // ---------------------------------------------------------------------------
36
53
  // Helpers (mirrored from agent-loop.test.ts so this file is self-contained)
37
54
  // ---------------------------------------------------------------------------
@@ -101,38 +118,20 @@ const userMessage: Message = {
101
118
  content: [{ type: "text", text: "Hello" }],
102
119
  };
103
120
 
104
- // A turn context whose `contextWindowManager.maybeCompact` returns a canned
105
- // result, so the loop's native compaction pipeline runs without the real
106
- // orchestrator machinery.
107
- function fakeCompactionTurnContext(result: {
108
- compacted: boolean;
109
- exhausted: boolean;
110
- }): TurnContext {
121
+ // A trust snapshot plus a context-window manager whose `maybeCompact` returns a
122
+ // canned result, so the loop's compaction call runs without the real
123
+ // orchestrator machinery. Both are supplied to the loop as dedicated run
124
+ // options.
125
+ function fakeCompaction(result: { compacted: boolean; exhausted: boolean }): {
126
+ trust: TrustContext;
127
+ contextWindowManager: AgentLoopRunOptions["contextWindowManager"];
128
+ } {
111
129
  return {
112
- requestId: "req-compact",
113
- conversationId: "conv-compact",
114
- turnIndex: 0,
115
130
  trust: { sourceChannel: "vellum", trustClass: "unknown" },
116
131
  contextWindowManager: {
117
132
  maybeCompact: async () => result,
118
- },
119
- } as unknown as TurnContext;
120
- }
121
-
122
- // A turn context whose compaction call times out, exercising the loop's
123
- // PluginTimeoutError handling.
124
- function timeoutCompactionTurnContext(): TurnContext {
125
- return {
126
- requestId: "req-compact",
127
- conversationId: "conv-compact",
128
- turnIndex: 0,
129
- trust: { sourceChannel: "vellum", trustClass: "unknown" },
130
- contextWindowManager: {
131
- maybeCompact: async () => {
132
- throw new PluginTimeoutError("compaction", "default-compaction", 1);
133
- },
134
- },
135
- } as unknown as TurnContext;
133
+ } as unknown as AgentLoopRunOptions["contextWindowManager"],
134
+ };
136
135
  }
137
136
 
138
137
  function lastExitEvent(
@@ -164,11 +163,19 @@ describe("AgentLoop exit-reason instrumentation", () => {
164
163
 
165
164
  test("emits exit event exactly once with 'no_tool_calls' on plain text response", async () => {
166
165
  const { provider } = createMockProvider([textResponse("Hi there!")]);
167
- const loop = new AgentLoop(provider, "system prompt");
166
+ const loop = new AgentLoop({
167
+ provider: provider,
168
+ systemPrompt: "system prompt",
169
+ conversationId: "test-conversation",
170
+ });
168
171
 
169
172
  const events: AgentEvent[] = [];
170
- await loop.run([userMessage], (e) => {
171
- events.push(e);
173
+ await loop.run({
174
+ messages: [userMessage],
175
+ onEvent: (e) => {
176
+ events.push(e);
177
+ },
178
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
172
179
  });
173
180
 
174
181
  expect(countExitEvents(events)).toBe(1);
@@ -178,11 +185,19 @@ describe("AgentLoop exit-reason instrumentation", () => {
178
185
 
179
186
  test("agent_loop_exit is the last event emitted", async () => {
180
187
  const { provider } = createMockProvider([textResponse("Hi there!")]);
181
- const loop = new AgentLoop(provider, "system prompt");
188
+ const loop = new AgentLoop({
189
+ provider: provider,
190
+ systemPrompt: "system prompt",
191
+ conversationId: "test-conversation",
192
+ });
182
193
 
183
194
  const events: AgentEvent[] = [];
184
- await loop.run([userMessage], (e) => {
185
- events.push(e);
195
+ await loop.run({
196
+ messages: [userMessage],
197
+ onEvent: (e) => {
198
+ events.push(e);
199
+ },
200
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
186
201
  });
187
202
 
188
203
  expect(events.length).toBeGreaterThan(0);
@@ -193,11 +208,19 @@ describe("AgentLoop exit-reason instrumentation", () => {
193
208
  const { provider } = createMockProvider([
194
209
  maxTokensResponse("Partial answer"),
195
210
  ]);
196
- const loop = new AgentLoop(provider, "system prompt");
211
+ const loop = new AgentLoop({
212
+ provider: provider,
213
+ systemPrompt: "system prompt",
214
+ conversationId: "test-conversation",
215
+ });
197
216
 
198
217
  const events: AgentEvent[] = [];
199
- await loop.run([userMessage], (e) => {
200
- events.push(e);
218
+ await loop.run({
219
+ messages: [userMessage],
220
+ onEvent: (e) => {
221
+ events.push(e);
222
+ },
223
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
201
224
  });
202
225
 
203
226
  expect(events.map((e) => e.type)).toEqual([
@@ -228,13 +251,20 @@ describe("AgentLoop exit-reason instrumentation", () => {
228
251
  stopReason: "max_tokens",
229
252
  },
230
253
  ]);
231
- const loop = new AgentLoop(provider, "system prompt", {
254
+ const loop = new AgentLoop({
255
+ provider: provider,
256
+ systemPrompt: "system prompt",
257
+ conversationId: "test-conversation",
232
258
  tools: dummyTools,
233
259
  });
234
260
 
235
261
  const events: AgentEvent[] = [];
236
- const { history: result } = await loop.run([userMessage], (e) => {
237
- events.push(e);
262
+ const { history: result } = await loop.run({
263
+ messages: [userMessage],
264
+ onEvent: (e) => {
265
+ events.push(e);
266
+ },
267
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
238
268
  });
239
269
 
240
270
  expect(events.some((e) => e.type === "tool_use")).toBe(false);
@@ -246,19 +276,24 @@ describe("AgentLoop exit-reason instrumentation", () => {
246
276
 
247
277
  test("emits 'aborted_pre_call' when signal is already aborted at run start", async () => {
248
278
  const { provider } = createMockProvider([textResponse("never sent")]);
249
- const loop = new AgentLoop(provider, "system prompt");
279
+ const loop = new AgentLoop({
280
+ provider: provider,
281
+ systemPrompt: "system prompt",
282
+ conversationId: "test-conversation",
283
+ });
250
284
 
251
285
  const controller = new AbortController();
252
286
  controller.abort();
253
287
 
254
288
  const events: AgentEvent[] = [];
255
- await loop.run(
256
- [userMessage],
257
- (e) => {
289
+ await loop.run({
290
+ messages: [userMessage],
291
+ onEvent: (e) => {
258
292
  events.push(e);
259
293
  },
260
- { signal: controller.signal },
261
- );
294
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
295
+ signal: controller.signal,
296
+ });
262
297
 
263
298
  expect(countExitEvents(events)).toBe(1);
264
299
  expect(lastExitEvent(events)?.reason).toBe("aborted_pre_call");
@@ -273,14 +308,21 @@ describe("AgentLoop exit-reason instrumentation", () => {
273
308
  isError: false,
274
309
  yieldToUser: true,
275
310
  });
276
- const loop = new AgentLoop(provider, "system", {
311
+ const loop = new AgentLoop({
312
+ provider: provider,
313
+ systemPrompt: "system",
314
+ conversationId: "test-conversation",
277
315
  tools: dummyTools,
278
316
  toolExecutor: toolExecutor,
279
317
  });
280
318
 
281
319
  const events: AgentEvent[] = [];
282
- await loop.run([userMessage], (e) => {
283
- events.push(e);
320
+ await loop.run({
321
+ messages: [userMessage],
322
+ onEvent: (e) => {
323
+ events.push(e);
324
+ },
325
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
284
326
  });
285
327
 
286
328
  expect(countExitEvents(events)).toBe(1);
@@ -293,7 +335,10 @@ describe("AgentLoop exit-reason instrumentation", () => {
293
335
  textResponse("never reached"),
294
336
  ]);
295
337
  const toolExecutor = async () => ({ content: "ok", isError: false });
296
- const loop = new AgentLoop(provider, "system", {
338
+ const loop = new AgentLoop({
339
+ provider: provider,
340
+ systemPrompt: "system",
341
+ conversationId: "test-conversation",
297
342
  tools: dummyTools,
298
343
  toolExecutor: toolExecutor,
299
344
  });
@@ -302,13 +347,14 @@ describe("AgentLoop exit-reason instrumentation", () => {
302
347
  "budget";
303
348
 
304
349
  const events: AgentEvent[] = [];
305
- await loop.run(
306
- [userMessage],
307
- (e) => {
350
+ await loop.run({
351
+ messages: [userMessage],
352
+ onEvent: (e) => {
308
353
  events.push(e);
309
354
  },
310
- { onCheckpoint },
311
- );
355
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
356
+ onCheckpoint,
357
+ });
312
358
 
313
359
  expect(countExitEvents(events)).toBe(0);
314
360
  });
@@ -321,7 +367,10 @@ describe("AgentLoop exit-reason instrumentation", () => {
321
367
  textResponse("never reached"),
322
368
  ]);
323
369
  const toolExecutor = async () => ({ content: "ok", isError: false });
324
- const loop = new AgentLoop(provider, "system", {
370
+ const loop = new AgentLoop({
371
+ provider: provider,
372
+ systemPrompt: "system",
373
+ conversationId: "test-conversation",
325
374
  tools: dummyTools,
326
375
  toolExecutor: toolExecutor,
327
376
  });
@@ -329,7 +378,10 @@ describe("AgentLoop exit-reason instrumentation", () => {
329
378
  // AND an effective context window so small that any real token estimate
330
379
  // of the running history exceeds the mid-loop threshold.
331
380
  // WHEN the loop checkpoints after the tool results land
332
- const result = await loop.run([userMessage], () => {}, {
381
+ const result = await loop.run({
382
+ messages: [userMessage],
383
+ onEvent: () => {},
384
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
333
385
  resolveContextWindow: () => ({
334
386
  maxInputTokens: 10,
335
387
  overflowRecovery: { enabled: true, safetyMarginRatio: 0 },
@@ -348,13 +400,19 @@ describe("AgentLoop exit-reason instrumentation", () => {
348
400
  textResponse("done"),
349
401
  ]);
350
402
  const toolExecutor = async () => ({ content: "ok", isError: false });
351
- const loop = new AgentLoop(provider, "system", {
403
+ const loop = new AgentLoop({
404
+ provider: provider,
405
+ systemPrompt: "system",
406
+ conversationId: "test-conversation",
352
407
  tools: dummyTools,
353
408
  toolExecutor: toolExecutor,
354
409
  });
355
410
 
356
411
  // WHEN the loop runs to completion
357
- const result = await loop.run([userMessage], () => {}, {
412
+ const result = await loop.run({
413
+ messages: [userMessage],
414
+ onEvent: () => {},
415
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
358
416
  resolveContextWindow: () => ({
359
417
  maxInputTokens: 10,
360
418
  overflowRecovery: { enabled: false, safetyMarginRatio: 0 },
@@ -373,38 +431,37 @@ describe("AgentLoop exit-reason instrumentation", () => {
373
431
  textResponse("done after compaction"),
374
432
  ]);
375
433
  const toolExecutor = async () => ({ content: "ok", isError: false });
376
- const loop = new AgentLoop(provider, "system", {
434
+ const loop = new AgentLoop({
435
+ provider: provider,
436
+ systemPrompt: "system",
437
+ conversationId: "test-conversation",
377
438
  tools: dummyTools,
378
439
  toolExecutor: toolExecutor,
379
440
  });
380
441
 
381
442
  let reinjected = false;
382
443
  const events: AgentEvent[] = [];
383
- const compaction: MidLoopCompaction = {
384
- postCompactionHook: async () => {
385
- reinjected = true;
386
- return [userMessage];
387
- },
444
+ postCompactImpl = async () => {
445
+ reinjected = true;
446
+ return [userMessage];
388
447
  };
389
448
 
390
449
  // WHEN the in-loop budget gate trips at the checkpoint
391
- const result = await loop.run(
392
- [userMessage],
393
- (event) => {
450
+ const result = await loop.run({
451
+ messages: [userMessage],
452
+ onEvent: (event) => {
394
453
  events.push(event);
395
454
  },
396
- {
397
- resolveContextWindow: () => ({
398
- maxInputTokens: 10,
399
- overflowRecovery: { enabled: true, safetyMarginRatio: 0 },
400
- }),
401
- compaction,
402
- turnContext: fakeCompactionTurnContext({
403
- compacted: true,
404
- exhausted: false,
405
- }),
406
- },
407
- );
455
+ resolveContextWindow: () => ({
456
+ maxInputTokens: 10,
457
+ overflowRecovery: { enabled: true, safetyMarginRatio: 0 },
458
+ }),
459
+ compactInPlace: true,
460
+ ...fakeCompaction({
461
+ compacted: true,
462
+ exhausted: false,
463
+ }),
464
+ });
408
465
 
409
466
  // THEN the loop runs the compaction ceremony in place and continues to a
410
467
  // clean exit instead of yielding for budget. The durable commit is
@@ -416,67 +473,36 @@ describe("AgentLoop exit-reason instrumentation", () => {
416
473
  expect(result.exitReason).not.toBe("budget");
417
474
  });
418
475
 
419
- test("yields 'budget' when inline compaction times out", async () => {
420
- const { provider } = createMockProvider([
421
- toolUseResponse("t1", "read_file", { path: "/a.txt" }),
422
- textResponse("never reached"),
423
- ]);
424
- const toolExecutor = async () => ({ content: "ok", isError: false });
425
- const loop = new AgentLoop(provider, "system", {
426
- tools: dummyTools,
427
- toolExecutor: toolExecutor,
428
- });
429
-
430
- const compaction: MidLoopCompaction = {
431
- postCompactionHook: async () => {
432
- throw new Error("postCompactionHook must not run after a timeout");
433
- },
434
- };
435
- const recordOutcomeSpy = spyOn(loop.compactionCircuit, "recordOutcome");
436
-
437
- // WHEN the compaction pipeline throws a PluginTimeoutError
438
- const result = await loop.run([userMessage], () => {}, {
439
- resolveContextWindow: () => ({
440
- maxInputTokens: 10,
441
- overflowRecovery: { enabled: true, safetyMarginRatio: 0 },
442
- }),
443
- compaction,
444
- turnContext: timeoutCompactionTurnContext(),
445
- });
446
-
447
- // THEN the loop records the timeout as a compaction failure against its
448
- // own circuit breaker, and yields for budget so the orchestrator can
449
- // escalate.
450
- expect(recordOutcomeSpy).toHaveBeenCalledTimes(1);
451
- expect(recordOutcomeSpy.mock.calls[0]?.[1]).toBe(true);
452
- expect(result.exitReason).toBe("budget");
453
- });
454
-
455
476
  test("yields 'budget' when inline compaction reports exhausted", async () => {
456
477
  const { provider } = createMockProvider([
457
478
  toolUseResponse("t1", "read_file", { path: "/a.txt" }),
458
479
  textResponse("never reached"),
459
480
  ]);
460
481
  const toolExecutor = async () => ({ content: "ok", isError: false });
461
- const loop = new AgentLoop(provider, "system", {
482
+ const loop = new AgentLoop({
483
+ provider: provider,
484
+ systemPrompt: "system",
485
+ conversationId: "test-conversation",
462
486
  tools: dummyTools,
463
487
  toolExecutor: toolExecutor,
464
488
  });
465
489
 
466
- const compaction: MidLoopCompaction = {
467
- postCompactionHook: async () => {
468
- throw new Error("postCompactionHook must not run when exhausted");
469
- },
490
+ postCompactImpl = async () => {
491
+ throw new Error(
492
+ "post-compaction re-injection must not run when exhausted",
493
+ );
470
494
  };
471
495
 
472
496
  // WHEN compaction exhausts its retry budget
473
- const result = await loop.run([userMessage], () => {}, {
497
+ const result = await loop.run({
498
+ messages: [userMessage],
499
+ onEvent: () => {},
474
500
  resolveContextWindow: () => ({
475
501
  maxInputTokens: 10,
476
502
  overflowRecovery: { enabled: true, safetyMarginRatio: 0 },
477
503
  }),
478
- compaction,
479
- turnContext: fakeCompactionTurnContext({
504
+ compactInPlace: true,
505
+ ...fakeCompaction({
480
506
  compacted: false,
481
507
  exhausted: true,
482
508
  }),
@@ -493,11 +519,19 @@ describe("AgentLoop exit-reason instrumentation", () => {
493
519
  throw new Error("provider exploded");
494
520
  },
495
521
  };
496
- const loop = new AgentLoop(provider, "system prompt");
522
+ const loop = new AgentLoop({
523
+ provider: provider,
524
+ systemPrompt: "system prompt",
525
+ conversationId: "test-conversation",
526
+ });
497
527
 
498
528
  const events: AgentEvent[] = [];
499
- await loop.run([userMessage], (e) => {
500
- events.push(e);
529
+ await loop.run({
530
+ messages: [userMessage],
531
+ onEvent: (e) => {
532
+ events.push(e);
533
+ },
534
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
501
535
  });
502
536
 
503
537
  expect(countExitEvents(events)).toBe(1);
@@ -516,14 +550,21 @@ describe("AgentLoop exit-reason instrumentation", () => {
516
550
  isError: false,
517
551
  yieldToUser: true,
518
552
  });
519
- const loop = new AgentLoop(provider, "system", {
553
+ const loop = new AgentLoop({
554
+ provider: provider,
555
+ systemPrompt: "system",
556
+ conversationId: "test-conversation",
520
557
  tools: dummyTools,
521
558
  toolExecutor: toolExecutor,
522
559
  });
523
560
 
524
561
  const events: AgentEvent[] = [];
525
- await loop.run([userMessage], (e) => {
526
- events.push(e);
562
+ await loop.run({
563
+ messages: [userMessage],
564
+ onEvent: (e) => {
565
+ events.push(e);
566
+ },
567
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
527
568
  });
528
569
 
529
570
  expect(countExitEvents(events)).toBe(1);
@@ -540,19 +581,23 @@ describe("AgentLoop exit-reason instrumentation", () => {
540
581
  controller.abort();
541
582
  return { content: "ok", isError: false };
542
583
  };
543
- const loop = new AgentLoop(provider, "system", {
584
+ const loop = new AgentLoop({
585
+ provider: provider,
586
+ systemPrompt: "system",
587
+ conversationId: "test-conversation",
544
588
  tools: dummyTools,
545
589
  toolExecutor: toolExecutor,
546
590
  });
547
591
 
548
592
  const events: AgentEvent[] = [];
549
- await loop.run(
550
- [userMessage],
551
- (e) => {
593
+ await loop.run({
594
+ messages: [userMessage],
595
+ onEvent: (e) => {
552
596
  events.push(e);
553
597
  },
554
- { signal: controller.signal },
555
- );
598
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
599
+ signal: controller.signal,
600
+ });
556
601
 
557
602
  expect(countExitEvents(events)).toBe(1);
558
603
  expect(lastExitEvent(events)?.reason).toBe("aborted_during_tools");
@@ -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
  });