@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
@@ -13,7 +13,11 @@ import { v4 as uuid } from "uuid";
13
13
  import { resolveCallSiteConfig } from "../config/llm-resolver.js";
14
14
  import { getConfig } from "../config/loader.js";
15
15
  import { Conversation } from "../daemon/conversation.js";
16
- import { findConversation } from "../daemon/conversation-store.js";
16
+ import {
17
+ findConversation,
18
+ removeSubagentConversation,
19
+ setSubagentConversation,
20
+ } from "../daemon/conversation-registry.js";
17
21
  import type { ServerMessage } from "../daemon/message-protocol.js";
18
22
  import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
19
23
  import { wrapWithCallSiteRouting } from "../providers/call-site-routing.js";
@@ -294,6 +298,10 @@ export class SubagentManager {
294
298
  // This ensures interactive prompts (host attachment reads) fail fast.
295
299
  conversation.updateClient(wrappedSendToClient, true);
296
300
  conversation.setIsSubagent(true);
301
+ // Subagents are created as background conversations (see the
302
+ // `bootstrapConversation` call above) and never call `loadFromDb`, so cache
303
+ // the type on the live conversation directly for the runtime-assembly path.
304
+ conversation.conversationType = "background";
297
305
 
298
306
  // Subagents execute as background child conversations, but their tool
299
307
  // permissions must still be scoped to the actor that spawned them. Without
@@ -334,6 +342,10 @@ export class SubagentManager {
334
342
 
335
343
  managed.conversation = conversation;
336
344
  this.subagents.set(subagentId, managed);
345
+ // Index the live conversation so the per-conversation injectors (workspace
346
+ // context, disk-pressure warning) can resolve it by id; subagents are not
347
+ // in the eviction-managed conversation store.
348
+ setSubagentConversation(conversationRecord.id, conversation);
337
349
  const labelKey = `${config.parentConversationId}:${config.label.toLowerCase().trim()}`;
338
350
  if (this.labelIndex.has(labelKey)) {
339
351
  log.warn(
@@ -695,7 +707,9 @@ export class SubagentManager {
695
707
  */
696
708
  private releaseConversation(managed: ManagedSubagent): void {
697
709
  if (!managed.conversation) return;
698
- managed.conversation.dispose();
710
+ const conversation = managed.conversation;
711
+ removeSubagentConversation(conversation.conversationId, conversation);
712
+ conversation.dispose();
699
713
  managed.conversation = null;
700
714
  managed.retainedUntil = Date.now() + TERMINAL_RETENTION_MS;
701
715
  this.ensureSweepRunning();
@@ -716,16 +730,18 @@ export class SubagentManager {
716
730
  if (!managed) return;
717
731
 
718
732
  if (managed.conversation) {
733
+ const conversation = managed.conversation;
719
734
  if (!TERMINAL_STATUSES.has(managed.state.status)) {
720
- managed.conversation.abort(
735
+ conversation.abort(
721
736
  createAbortReason(
722
737
  "subagent_aborted",
723
738
  "SubagentManager.dispose",
724
- managed.conversation.conversationId,
739
+ conversation.conversationId,
725
740
  ),
726
741
  );
727
742
  }
728
- managed.conversation.dispose();
743
+ removeSubagentConversation(conversation.conversationId, conversation);
744
+ conversation.dispose();
729
745
  managed.conversation = null;
730
746
  }
731
747
  this.subagents.delete(subagentId);
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Shared ToolContext accessors for the ACP tools.
3
+ */
4
+
5
+ import type { ServerMessage } from "../../daemon/message-protocol.js";
6
+ import type { ToolContext } from "../types.js";
7
+
8
+ /**
9
+ * Narrows `ToolContext.sendToClient` to the `ServerMessage` sender the ACP
10
+ * session manager expects. ToolContext types the callback against an
11
+ * index-signature message shape (`{ type: string; [key: string]: unknown }`)
12
+ * that ServerMessage's union members don't structurally satisfy, so the
13
+ * cast lives here once instead of being duplicated at every ACP tool call
14
+ * site.
15
+ */
16
+ export function getSendToClient(
17
+ context: ToolContext,
18
+ ): ((msg: ServerMessage) => void) | undefined {
19
+ return context.sendToClient as ((msg: ServerMessage) => void) | undefined;
20
+ }
@@ -47,7 +47,7 @@ describe("executeAcpListAgents", () => {
47
47
  expect(parsed.hint).toContain("config.json");
48
48
  });
49
49
 
50
- test("enabled, no user config: both defaults present with source 'default' and available based on Bun.which", async () => {
50
+ test("enabled, no user config: all defaults present with source 'default' and available based on Bun.which", async () => {
51
51
  config.setConfig({ agents: {} });
52
52
 
53
53
  const result = await executeAcpListAgents({}, makeContext());
@@ -58,6 +58,7 @@ describe("executeAcpListAgents", () => {
58
58
  expect(parsed.agents.map((a: { id: string }) => a.id)).toEqual([
59
59
  "claude",
60
60
  "codex",
61
+ "gemini",
61
62
  ]);
62
63
  for (const entry of parsed.agents) {
63
64
  expect(entry.source).toBe("default");
@@ -106,7 +107,12 @@ describe("executeAcpListAgents", () => {
106
107
  const codex = parsed.agents.find((a: { id: string }) => a.id === "codex");
107
108
  expect(codex.available).toBe(false);
108
109
  expect(codex.unavailableReason).toBe("'codex-acp' is not on PATH");
109
- expect(codex.setupHint).toBe("npm i -g @zed-industries/codex-acp");
110
+ expect(codex.setupHint).toBe("bun add -g @zed-industries/codex-acp");
111
+
112
+ const gemini = parsed.agents.find((a: { id: string }) => a.id === "gemini");
113
+ expect(gemini.available).toBe(false);
114
+ expect(gemini.unavailableReason).toBe("'gemini' is not on PATH");
115
+ expect(gemini.setupHint).toBe("bun add -g @google/gemini-cli");
110
116
 
111
117
  const claude = parsed.agents.find((a: { id: string }) => a.id === "claude");
112
118
  expect(claude.available).toBe(true);
@@ -1,7 +1,7 @@
1
- import * as realChildProcess from "node:child_process";
2
1
  import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
3
2
 
4
3
  import { installAcpConfigStub } from "../../acp/__tests__/helpers/acp-config-stub.js";
4
+ import { installExecFileStub } from "../../acp/__tests__/helpers/exec-file-stub.js";
5
5
  import { installWhichStub } from "../../acp/__tests__/helpers/which-stub.js";
6
6
  import type { ToolContext } from "../types.js";
7
7
 
@@ -9,58 +9,18 @@ import type { ToolContext } from "../types.js";
9
9
  // Mock infrastructure
10
10
  // ---------------------------------------------------------------------------
11
11
 
12
- type ExecCallback = (
13
- err: Error | null,
14
- stdout: string | Buffer,
15
- stderr: string | Buffer,
16
- ) => void;
17
-
18
- interface ExecScript {
19
- /** When set, the call rejects with this error. */
20
- error?: Error;
21
- /** When set, the call resolves with this stdout. */
22
- stdout?: string;
23
- }
12
+ const {
13
+ execScripts,
14
+ execFileMock,
15
+ reset: resetExecFileStub,
16
+ } = installExecFileStub();
24
17
 
25
- /**
26
- * Per-call scripted responses for `execFile`. Keyed by `${command} ${args[0]}`
27
- * so tests can target `npm ls` and `npm view` independently.
28
- */
29
- const execScripts: Map<string, ExecScript> = new Map();
30
-
31
- const execFileMock = mock(
32
- (
33
- command: string,
34
- args: string[],
35
- _options: unknown,
36
- callback?: ExecCallback,
37
- ) => {
38
- const key = `${command} ${args[0]}`;
39
- const script = execScripts.get(key);
40
- queueMicrotask(() => {
41
- if (!callback) return;
42
- if (!script) {
43
- callback(new Error(`No script for ${key}`), "", "");
44
- return;
45
- }
46
- if (script.error) {
47
- callback(script.error, "", "");
48
- return;
49
- }
50
- callback(null, script.stdout ?? "", "");
51
- });
52
- // Return value is not used by execFileWithTimeout.
53
- return {} as ReturnType<typeof realChildProcess.execFile>;
54
- },
55
- );
56
-
57
- mock.module("node:child_process", () => ({
58
- ...realChildProcess,
59
- execFile: execFileMock,
60
- }));
18
+ /** Fixed resolved `bun` path so install script keys are predictable. */
19
+ const BUN_BIN = "/usr/local/bin/bun";
20
+ const BUN_ADD_KEY = `${BUN_BIN} add`;
61
21
 
62
22
  // Default ACP config used by these tests: the `unknown-agent` entry is here
63
- // to give the "no version check" test a configured agent whose binary isn't
23
+ // to give the "unmapped binary" tests a configured agent whose command isn't
64
24
  // in DEFAULT_AGENT_NPM_PACKAGES.
65
25
  const DEFAULT_TEST_AGENTS = {
66
26
  claude: { command: "claude-agent-acp", args: [] },
@@ -187,8 +147,10 @@ mock.module("../../acp/index.js", () => ({
187
147
  getAcpSessionManager: () => ({ spawn: spawnMock }),
188
148
  }));
189
149
 
190
- const { executeAcpSpawn, _resetAdapterVersionCacheForTests } =
191
- await import("./spawn.js");
150
+ const { executeAcpSpawn } = await import("./spawn.js");
151
+ const { _resetAdapterInstallCacheForTests } = await import(
152
+ "../../acp/auto-install.js"
153
+ );
192
154
 
193
155
  // ---------------------------------------------------------------------------
194
156
  // Helpers
@@ -204,11 +166,12 @@ function makeContext(): ToolContext {
204
166
  }
205
167
 
206
168
  beforeEach(() => {
207
- execScripts.clear();
208
- execFileMock.mockClear();
169
+ resetExecFileStub();
209
170
  spawnMock.mockClear();
210
- _resetAdapterVersionCacheForTests();
171
+ _resetAdapterInstallCacheForTests();
211
172
  config.setConfig({ agents: DEFAULT_TEST_AGENTS });
173
+ // Default: every command (including bun and the adapters) on PATH, so
174
+ // spawns resolve directly with no install.
212
175
  which.setWhich((cmd) => `/usr/local/bin/${cmd}`);
213
176
  // Default: vault has a claude token so the preflight in `prepareAgentEnv`
214
177
  // succeeds for tests that don't care about env injection. Env-injection
@@ -222,135 +185,41 @@ beforeEach(() => {
222
185
  // Tests
223
186
  // ---------------------------------------------------------------------------
224
187
 
225
- describe("executeAcpSpawn version check", () => {
226
- test("execFile failure: spawn proceeds without warning", async () => {
227
- execScripts.set("npm ls", { error: new Error("npm not installed") });
228
- execScripts.set("npm view", { error: new Error("npm not installed") });
229
-
188
+ describe("executeAcpSpawn - happy path", () => {
189
+ test("binary on PATH: spawns directly with no install and no npm probe", async () => {
230
190
  const result = await executeAcpSpawn(
231
191
  { agent: "claude", task: "do something" },
232
192
  makeContext(),
233
193
  );
234
194
 
235
195
  expect(result.isError).toBe(false);
196
+ // No package manager is ever invoked when the binary is already present.
197
+ expect(execFileMock).not.toHaveBeenCalled();
236
198
  expect(result.content).not.toContain("outdated");
237
- expect(result.content).not.toContain("Note:");
238
- // Spawn was actually invoked.
239
199
  expect(spawnMock).toHaveBeenCalledTimes(1);
240
- // Sanity: payload shape preserved.
241
- const lines = result.content.split("\n\n");
242
- const payload = JSON.parse(lines[0]);
200
+ const payload = JSON.parse(result.content);
243
201
  expect(payload.acpSessionId).toBe("acp-session-test");
244
202
  expect(payload.status).toBe("running");
203
+ // The real adapter binary is spawned (no `bun x` wrapper).
204
+ const agentConfigArg = spawnMock.mock.calls[0][1] as { command: string };
205
+ expect(agentConfigArg.command).toBe("claude-agent-acp");
245
206
  });
246
207
 
247
- test("outdated version: spawn proceeds AND warning appears in content", async () => {
248
- execScripts.set("npm ls", {
249
- stdout: JSON.stringify({
250
- dependencies: {
251
- "@agentclientprotocol/claude-agent-acp": { version: "0.1.0" },
252
- },
253
- }),
254
- });
255
- execScripts.set("npm view", { stdout: "0.2.0\n" });
256
-
257
- const result = await executeAcpSpawn(
258
- { agent: "claude", task: "do something" },
259
- makeContext(),
260
- );
261
-
262
- expect(result.isError).toBe(false);
263
- expect(result.content).toContain("outdated");
264
- expect(result.content).toContain("@agentclientprotocol/claude-agent-acp");
265
- expect(result.content).toContain("0.1.0");
266
- expect(result.content).toContain("0.2.0");
267
- expect(result.content).toContain(
268
- "npm install -g @agentclientprotocol/claude-agent-acp@0.2.0",
269
- );
270
- expect(spawnMock).toHaveBeenCalledTimes(1);
271
- // Payload still parses as JSON before the warning suffix.
272
- const [payloadJson] = result.content.split("\n\n");
273
- const payload = JSON.parse(payloadJson);
274
- expect(payload.acpSessionId).toBe("acp-session-test");
275
- });
276
-
277
- test("up-to-date version: spawn proceeds, no warning", async () => {
278
- execScripts.set("npm ls", {
279
- stdout: JSON.stringify({
280
- dependencies: {
281
- "@agentclientprotocol/claude-agent-acp": { version: "0.2.0" },
282
- },
283
- }),
284
- });
285
- execScripts.set("npm view", { stdout: "0.2.0\n" });
286
-
287
- const result = await executeAcpSpawn(
288
- { agent: "claude", task: "do something" },
289
- makeContext(),
290
- );
291
-
292
- expect(result.isError).toBe(false);
293
- expect(result.content).not.toContain("outdated");
294
- expect(spawnMock).toHaveBeenCalledTimes(1);
295
- });
296
-
297
- test("unknown command: no version check is performed", async () => {
298
- // No execScripts set — if the implementation tried to run npm, the
299
- // mock would callback with "No script for ..." and we could detect
300
- // the failure. But since the registry doesn't include this command,
301
- // the implementation should skip the check entirely without calling
302
- // execFile.
303
- execFileMock.mockClear();
208
+ test("default-profile fallback when user config is empty", async () => {
209
+ // No user `agents.codex` entry, but `agent: "codex"` works via the bundled
210
+ // default profile (command: "codex-acp"). The resolver merges defaults
211
+ // automatically.
212
+ config.setConfig({ agents: {} });
304
213
 
305
214
  const result = await executeAcpSpawn(
306
- { agent: "unknown-agent", task: "do something" },
215
+ { agent: "codex", task: "do something" },
307
216
  makeContext(),
308
217
  );
309
218
 
310
219
  expect(result.isError).toBe(false);
311
- expect(execFileMock).not.toHaveBeenCalled();
312
220
  expect(spawnMock).toHaveBeenCalledTimes(1);
313
- // No outdated note suffix.
314
- expect(result.content).not.toContain("outdated");
315
- });
316
-
317
- test("cached null result: second call does not reprobe", async () => {
318
- execScripts.set("npm ls", { error: new Error("npm not installed") });
319
- execScripts.set("npm view", { error: new Error("npm not installed") });
320
-
321
- await executeAcpSpawn({ agent: "claude", task: "task 1" }, makeContext());
322
- const firstCallCount = execFileMock.mock.calls.length;
323
- expect(firstCallCount).toBeGreaterThan(0);
324
-
325
- await executeAcpSpawn({ agent: "claude", task: "task 2" }, makeContext());
326
- // No additional execFile calls — result was cached.
327
- expect(execFileMock.mock.calls.length).toBe(firstCallCount);
328
- });
329
-
330
- test("cached outdated result: second call does not reprobe but still warns", async () => {
331
- execScripts.set("npm ls", {
332
- stdout: JSON.stringify({
333
- dependencies: {
334
- "@agentclientprotocol/claude-agent-acp": { version: "0.1.0" },
335
- },
336
- }),
337
- });
338
- execScripts.set("npm view", { stdout: "0.2.0\n" });
339
-
340
- const first = await executeAcpSpawn(
341
- { agent: "claude", task: "task 1" },
342
- makeContext(),
343
- );
344
- expect(first.content).toContain("outdated");
345
- const firstCallCount = execFileMock.mock.calls.length;
346
-
347
- const second = await executeAcpSpawn(
348
- { agent: "claude", task: "task 2" },
349
- makeContext(),
350
- );
351
- expect(second.content).toContain("outdated");
352
- // No additional execFile calls — outdated info was cached.
353
- expect(execFileMock.mock.calls.length).toBe(firstCallCount);
221
+ const agentConfigArg = spawnMock.mock.calls[0][1] as { command: string };
222
+ expect(agentConfigArg.command).toBe("codex-acp");
354
223
  });
355
224
  });
356
225
 
@@ -391,8 +260,8 @@ describe("executeAcpSpawn — input validation", () => {
391
260
  expect(result.content).toContain("acp.enabled");
392
261
  });
393
262
 
394
- test("missing binary returns install hint", async () => {
395
- which.setWhich({});
263
+ test("missing binary + bun absent returns install hint, no install attempted", async () => {
264
+ which.setWhich({}); // neither bun nor the adapter on PATH
396
265
  const result = await executeAcpSpawn(
397
266
  { agent: "claude", task: "do something" },
398
267
  makeContext(),
@@ -400,65 +269,181 @@ describe("executeAcpSpawn — input validation", () => {
400
269
  expect(result.isError).toBe(true);
401
270
  expect(result.content).toContain("claude-agent-acp is not on PATH");
402
271
  expect(result.content).toContain(
403
- "npm i -g @agentclientprotocol/claude-agent-acp",
272
+ "bun add -g @agentclientprotocol/claude-agent-acp",
404
273
  );
274
+ expect(execFileMock).not.toHaveBeenCalled();
405
275
  expect(spawnMock).not.toHaveBeenCalled();
406
276
  });
277
+ });
407
278
 
408
- test("default-profile fallback when user config is empty", async () => {
409
- // No user `agents.codex` entry, but `agent: "codex"` works via the bundled
410
- // default profile (command: "codex-acp"). The resolver merges defaults
411
- // automatically.
412
- config.setConfig({ agents: {} });
413
- execScripts.set("npm ls", { error: new Error("npm not installed") });
414
- execScripts.set("npm view", { error: new Error("npm not installed") });
279
+ describe("executeAcpSpawn: sandboxed bun auto-install on missing binary", () => {
280
+ test("known command + bun present: installs via bun and spawn proceeds with a note", async () => {
281
+ // Only bun is on PATH until `bun add --global` runs, simulating a
282
+ // successful global install that links the adapter bin onto PATH.
283
+ let binaryOnPath = false;
284
+ which.setWhich((cmd) => {
285
+ if (cmd === "bun") return BUN_BIN;
286
+ if (binaryOnPath) return `/usr/local/bin/${cmd}`;
287
+ return null;
288
+ });
289
+ execScripts.set(BUN_ADD_KEY, {
290
+ stdout: "",
291
+ onCall: () => {
292
+ binaryOnPath = true;
293
+ },
294
+ });
415
295
 
416
296
  const result = await executeAcpSpawn(
417
- { agent: "codex", task: "do something" },
297
+ { agent: "claude", task: "do something" },
418
298
  makeContext(),
419
299
  );
420
300
 
421
301
  expect(result.isError).toBe(false);
422
302
  expect(spawnMock).toHaveBeenCalledTimes(1);
423
- // The agentConfig handed to spawn() should be the bundled default.
424
- const agentConfigArg = spawnMock.mock.calls[0][1] as { command: string };
425
- expect(agentConfigArg.command).toBe("codex-acp");
303
+ const payload = JSON.parse(result.content);
304
+ expect(payload.message).toContain(
305
+ "Installed @agentclientprotocol/claude-agent-acp automatically.",
306
+ );
307
+ // The real binary was spawned with cwd = the project dir and token
308
+ // injected (trusted-binary config, no resolution at spawn).
309
+ const agentConfigArg = spawnMock.mock.calls[0][1] as {
310
+ command: string;
311
+ env?: Record<string, string>;
312
+ };
313
+ expect(agentConfigArg.command).toBe("claude-agent-acp");
314
+ expect(agentConfigArg.env?.CLAUDE_CODE_OAUTH_TOKEN).toBe(
315
+ "default-test-token",
316
+ );
317
+ expect(spawnMock.mock.calls[0][3]).toBe("/tmp");
318
+
319
+ // Exactly one install, and it was `bun add --global` (never npm).
320
+ expect(execFileMock).toHaveBeenCalledTimes(1);
321
+ const [command, args] = execFileMock.mock.calls[0];
322
+ expect(command).toBe(BUN_BIN);
323
+ expect(args).toEqual([
324
+ "add",
325
+ "--global",
326
+ "@agentclientprotocol/claude-agent-acp",
327
+ ]);
328
+ });
329
+
330
+ test("the installer cwd is a temp dir (not the project cwd) with secrets stripped", async () => {
331
+ let binaryOnPath = false;
332
+ which.setWhich((cmd) => {
333
+ if (cmd === "bun") return BUN_BIN;
334
+ if (binaryOnPath) return `/usr/local/bin/${cmd}`;
335
+ return null;
336
+ });
337
+ execScripts.set(BUN_ADD_KEY, {
338
+ stdout: "",
339
+ onCall: () => {
340
+ binaryOnPath = true;
341
+ },
342
+ });
343
+
344
+ await executeAcpSpawn(
345
+ { agent: "claude", task: "do something", cwd: "/untrusted/project" },
346
+ makeContext(),
347
+ );
348
+
349
+ const options = execFileMock.mock.calls[0][2] as {
350
+ cwd?: string;
351
+ env?: NodeJS.ProcessEnv;
352
+ };
353
+ expect(options.cwd).toBeDefined();
354
+ expect(options.cwd).not.toBe("/untrusted/project");
355
+ expect(options.cwd).toContain("vellum-acp-install-");
356
+ expect(options.env?.CLAUDE_CODE_OAUTH_TOKEN).toBeUndefined();
357
+ expect(options.env?.GEMINI_API_KEY).toBeUndefined();
358
+ expect(options.env?.BUN_CONFIG_REGISTRY).toBe(
359
+ "https://registry.npmjs.org/",
360
+ );
361
+ });
362
+
363
+ test("unmapped command: no install attempted, plain hint returned", async () => {
364
+ which.setWhich({});
365
+
366
+ const result = await executeAcpSpawn(
367
+ { agent: "unknown-agent", task: "do something" },
368
+ makeContext(),
369
+ );
370
+
371
+ expect(result.isError).toBe(true);
372
+ expect(result.content).toContain("some-other-binary is not on PATH");
373
+ expect(result.content).toContain("Install 'some-other-binary'");
374
+ expect(result.content).not.toContain("auto-install failed");
375
+ expect(execFileMock).not.toHaveBeenCalled();
376
+ expect(spawnMock).not.toHaveBeenCalled();
377
+ });
378
+
379
+ test("no client connected: no install attempted even when the binary is missing", async () => {
380
+ // The no-client guard is a pure precondition and must run BEFORE the
381
+ // auto-install side effect: without a client the spawn fails anyway, so
382
+ // the host must not be mutated by a global install (which can also block
383
+ // for up to the install timeout).
384
+ which.setWhich({ bun: BUN_BIN });
385
+
386
+ const result = await executeAcpSpawn(
387
+ { agent: "claude", task: "do something" },
388
+ { ...makeContext(), sendToClient: undefined },
389
+ );
390
+
391
+ expect(result.isError).toBe(true);
392
+ expect(result.content).toContain("No client connected");
393
+ expect(execFileMock).not.toHaveBeenCalled();
394
+ expect(spawnMock).not.toHaveBeenCalled();
395
+ });
396
+
397
+ test("install failure: hint and install failure both surface, never npm", async () => {
398
+ which.setWhich({ bun: BUN_BIN }); // bun present, adapter never appears
399
+ execScripts.set(BUN_ADD_KEY, {
400
+ error: new Error("EACCES: permission denied"),
401
+ });
402
+
403
+ const result = await executeAcpSpawn(
404
+ { agent: "claude", task: "do something" },
405
+ makeContext(),
406
+ );
407
+
408
+ expect(result.isError).toBe(true);
409
+ expect(result.content).toContain("claude-agent-acp is not on PATH");
410
+ expect(result.content).toContain(
411
+ "bun add -g @agentclientprotocol/claude-agent-acp",
412
+ );
413
+ expect(result.content).toContain("auto-install failed");
414
+ expect(result.content).toContain("EACCES");
415
+ for (const call of execFileMock.mock.calls) {
416
+ expect(call[0]).not.toBe("npm");
417
+ }
418
+ expect(spawnMock).not.toHaveBeenCalled();
426
419
  });
427
420
  });
428
421
 
429
422
  describe("executeAcpSpawn — per-agent resume hint", () => {
430
423
  test("claude payload includes the `claude --resume` hint", async () => {
431
- execScripts.set("npm ls", { error: new Error("npm not installed") });
432
- execScripts.set("npm view", { error: new Error("npm not installed") });
433
-
434
424
  const result = await executeAcpSpawn(
435
425
  { agent: "claude", task: "do something" },
436
426
  makeContext(),
437
427
  );
438
428
 
439
429
  expect(result.isError).toBe(false);
440
- const [payloadJson] = result.content.split("\n\n");
441
- const payload = JSON.parse(payloadJson);
430
+ const payload = JSON.parse(result.content);
442
431
  expect(payload.message).toContain("claude --resume");
443
- expect(payload.message).toContain("To resume this session later");
432
+ expect(payload.message).toContain("To resume:");
444
433
  });
445
434
 
446
435
  test("non-claude payload omits the `claude --resume` hint", async () => {
447
436
  // `claude --resume <id>` is Claude Code-specific. Codex (and any other
448
437
  // adapter) should not have that command suggested back to the user.
449
- execScripts.set("npm ls", { error: new Error("npm not installed") });
450
- execScripts.set("npm view", { error: new Error("npm not installed") });
451
-
452
438
  const result = await executeAcpSpawn(
453
439
  { agent: "codex", task: "do something" },
454
440
  makeContext(),
455
441
  );
456
442
 
457
443
  expect(result.isError).toBe(false);
458
- const [payloadJson] = result.content.split("\n\n");
459
- const payload = JSON.parse(payloadJson);
444
+ const payload = JSON.parse(result.content);
460
445
  expect(payload.message).not.toContain("claude --resume");
461
- expect(payload.message).not.toContain("To resume this session later");
446
+ expect(payload.message).not.toContain("To resume:");
462
447
  });
463
448
  });
464
449
 
@@ -482,8 +467,6 @@ describe("executeAcpSpawn — CLAUDE_CODE_OAUTH_TOKEN injection", () => {
482
467
  vaultStore.clear();
483
468
  metadataStore.clear();
484
469
  vaultStore.set("acp/claude_oauth_token", "tool-vault-token-abc");
485
- execScripts.set("npm ls", { error: new Error("npm not installed") });
486
- execScripts.set("npm view", { error: new Error("npm not installed") });
487
470
 
488
471
  const result = await executeAcpSpawn(
489
472
  { agent: "claude", task: "do something" },
@@ -514,8 +497,6 @@ describe("executeAcpSpawn — CLAUDE_CODE_OAUTH_TOKEN injection", () => {
514
497
  "unknown-agent": { command: "some-other-binary", args: [] },
515
498
  },
516
499
  });
517
- execScripts.set("npm ls", { error: new Error("npm not installed") });
518
- execScripts.set("npm view", { error: new Error("npm not installed") });
519
500
 
520
501
  const result = await executeAcpSpawn(
521
502
  { agent: "claude", task: "do something" },