@vellumai/assistant 0.8.8 → 0.8.9-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (360) hide show
  1. package/ARCHITECTURE.md +6 -6
  2. package/bun.lock +2 -2
  3. package/examples/plugins/echo/README.md +61 -60
  4. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  5. package/examples/plugins/echo/hooks/stop.ts +16 -0
  6. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  7. package/examples/plugins/echo/package.json +1 -2
  8. package/examples/plugins/echo/src/emit.ts +19 -0
  9. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  10. package/openapi.yaml +235 -6
  11. package/package.json +2 -2
  12. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  13. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  14. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  15. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  16. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  17. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  18. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  19. package/src/__tests__/agent-loop.test.ts +441 -96
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  21. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  22. package/src/__tests__/anthropic-provider.test.ts +1 -1
  23. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  24. package/src/__tests__/app-control-flow.test.ts +1 -1
  25. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  26. package/src/__tests__/app-executors.test.ts +132 -0
  27. package/src/__tests__/approval-cascade.test.ts +5 -4
  28. package/src/__tests__/approval-routes-http.test.ts +4 -1
  29. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  30. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  31. package/src/__tests__/channel-approvals.test.ts +1 -1
  32. package/src/__tests__/compaction-circuit.test.ts +258 -0
  33. package/src/__tests__/compaction-direct.test.ts +132 -0
  34. package/src/__tests__/compaction-events.test.ts +5 -5
  35. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  36. package/src/__tests__/context-overflow-reducer.test.ts +1 -1
  37. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -4
  38. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  39. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  40. package/src/__tests__/conversation-agent-loop-overflow.test.ts +142 -218
  41. package/src/__tests__/conversation-agent-loop.test.ts +297 -586
  42. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  43. package/src/__tests__/conversation-confirmation-signals.test.ts +5 -4
  44. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  45. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  46. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -4
  47. package/src/__tests__/conversation-process-callsite.test.ts +14 -14
  48. package/src/__tests__/conversation-provider-retry-repair.test.ts +70 -65
  49. package/src/__tests__/conversation-queue.test.ts +9 -9
  50. package/src/__tests__/conversation-runtime-assembly.test.ts +923 -231
  51. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  52. package/src/__tests__/conversation-slash-queue.test.ts +6 -4
  53. package/src/__tests__/conversation-slash-unknown.test.ts +6 -4
  54. package/src/__tests__/conversation-speed-override.test.ts +10 -9
  55. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  56. package/src/__tests__/conversation-workspace-cache-state.test.ts +23 -20
  57. package/src/__tests__/conversation-workspace-injection.test.ts +68 -6
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +14 -11
  59. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  60. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  61. package/src/__tests__/db-acp-history.test.ts +101 -0
  62. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  63. package/src/__tests__/empty-response-hook.test.ts +1 -1
  64. package/src/__tests__/file-write-tool.test.ts +63 -0
  65. package/src/__tests__/gateway-only-guard.test.ts +12 -2
  66. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  67. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  68. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  69. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  70. package/src/__tests__/heartbeat-service.test.ts +1 -0
  71. package/src/__tests__/history-repair-hook.test.ts +1 -1
  72. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  73. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  74. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  75. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  76. package/src/__tests__/injector-background-turn.test.ts +13 -23
  77. package/src/__tests__/injector-chain.test.ts +268 -44
  78. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  79. package/src/__tests__/injector-document-comments.test.ts +97 -114
  80. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  81. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  82. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  83. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  84. package/src/__tests__/memory-retrieval-hook.test.ts +131 -26
  85. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  86. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  87. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  88. package/src/__tests__/plugin-bootstrap.test.ts +12 -23
  89. package/src/__tests__/plugin-registry.test.ts +3 -49
  90. package/src/__tests__/plugin-types.test.ts +0 -70
  91. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  92. package/src/__tests__/reaction-persistence.test.ts +1 -1
  93. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  94. package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
  95. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  96. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  97. package/src/__tests__/subagent-detail.test.ts +25 -7
  98. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  99. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  100. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  101. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  102. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  103. package/src/__tests__/title-generate-hook.test.ts +1 -1
  104. package/src/__tests__/tool-error-hook.test.ts +1 -1
  105. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  106. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  107. package/src/acp/__tests__/agent-process.test.ts +161 -0
  108. package/src/acp/__tests__/client-handler.test.ts +40 -0
  109. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  110. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  111. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  112. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  113. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  114. package/src/acp/agent-process.ts +61 -1
  115. package/src/acp/auto-install.test.ts +280 -0
  116. package/src/acp/auto-install.ts +232 -0
  117. package/src/acp/client-handler.ts +31 -0
  118. package/src/acp/feature-gate.test.ts +48 -0
  119. package/src/acp/feature-gate.ts +34 -0
  120. package/src/acp/prepare-agent-env.ts +80 -27
  121. package/src/acp/resolve-agent.test.ts +225 -9
  122. package/src/acp/resolve-agent.ts +122 -17
  123. package/src/acp/resume-hint.ts +23 -0
  124. package/src/acp/session-manager.ts +507 -73
  125. package/src/agent/compaction-circuit.ts +60 -102
  126. package/src/agent/loop.ts +414 -248
  127. package/src/api/responses/conversation-message.ts +14 -1
  128. package/src/approvals/guardian-request-resolvers.ts +1 -1
  129. package/src/background-wake/next-wake.ts +1 -0
  130. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  131. package/src/cli/commands/plugins.ts +43 -37
  132. package/src/cli/lib/__tests__/install-from-github.test.ts +429 -111
  133. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  134. package/src/cli/lib/__tests__/plugin-details.test.ts +372 -0
  135. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +220 -0
  136. package/src/cli/lib/__tests__/search-plugins.test.ts +226 -32
  137. package/src/cli/lib/install-from-github.ts +464 -55
  138. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  139. package/src/cli/lib/plugin-details.ts +409 -0
  140. package/src/cli/lib/plugin-marketplace.ts +197 -0
  141. package/src/cli/lib/search-plugins.ts +195 -29
  142. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  143. package/src/config/acp-defaults.test.ts +10 -0
  144. package/src/config/acp-defaults.ts +6 -0
  145. package/src/config/bundled-skills/acp/SKILL.md +85 -33
  146. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  147. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  148. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  149. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  150. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  151. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  152. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  153. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  154. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  155. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  156. package/src/config/bundled-tool-registry.ts +4 -0
  157. package/src/config/call-site-defaults.ts +0 -2
  158. package/src/config/feature-flag-registry.json +15 -6
  159. package/src/config/schemas/call-site-catalog.ts +0 -14
  160. package/src/config/schemas/heartbeat.ts +9 -0
  161. package/src/config/schemas/llm.ts +0 -2
  162. package/src/context/compactor.ts +22 -4
  163. package/src/context/strip-injections.ts +8 -2
  164. package/src/context/window-manager.ts +27 -13
  165. package/src/daemon/conversation-agent-loop-handlers.ts +10 -35
  166. package/src/daemon/conversation-agent-loop.ts +175 -1055
  167. package/src/daemon/conversation-lifecycle.ts +11 -255
  168. package/src/daemon/conversation-process.ts +8 -136
  169. package/src/daemon/conversation-registry.ts +159 -0
  170. package/src/daemon/conversation-runtime-assembly.ts +293 -392
  171. package/src/daemon/conversation-store.ts +9 -90
  172. package/src/daemon/conversation-surfaces.ts +24 -8
  173. package/src/daemon/conversation-workspace.ts +17 -0
  174. package/src/daemon/conversation.ts +404 -57
  175. package/src/daemon/disk-pressure-policy.ts +0 -1
  176. package/src/daemon/external-plugins-bootstrap.ts +14 -19
  177. package/src/daemon/handlers/conversations.ts +3 -1
  178. package/src/daemon/handlers/skills.ts +4 -1
  179. package/src/daemon/host-proxy-preactivation.ts +1 -3
  180. package/src/daemon/lifecycle.ts +21 -7
  181. package/src/daemon/server.ts +2 -0
  182. package/src/daemon/wake-conversation-ops.ts +269 -0
  183. package/src/embedded/plugin-api.ts +2 -2
  184. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  185. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  186. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  187. package/src/heartbeat/heartbeat-service.ts +26 -0
  188. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  189. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  190. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  191. package/src/ipc/skill-routes/memory.ts +4 -2
  192. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  193. package/src/memory/conversation-crud.ts +29 -19
  194. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  195. package/src/memory/db-init.ts +2 -0
  196. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  197. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  198. package/src/memory/jobs-worker.ts +25 -1
  199. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  200. package/src/memory/migrations/index.ts +1 -0
  201. package/src/memory/schema/acp.ts +4 -0
  202. package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
  203. package/src/memory/v2/consolidation-job.ts +13 -4
  204. package/src/plugin-api/constants.ts +4 -0
  205. package/src/plugin-api/index.ts +6 -5
  206. package/src/plugin-api/types.ts +75 -0
  207. package/src/plugins/defaults/compaction/compact.ts +59 -0
  208. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +7 -7
  209. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  210. package/src/plugins/defaults/compaction/package.json +1 -2
  211. package/src/plugins/defaults/empty-response/package.json +0 -1
  212. package/src/plugins/defaults/history-repair/package.json +0 -1
  213. package/src/plugins/defaults/index.ts +135 -26
  214. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  215. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +184 -74
  216. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  217. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  218. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  219. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
  220. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +9 -6
  221. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
  222. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +8 -5
  223. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
  224. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
  225. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +10 -5
  226. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
  227. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +5 -5
  228. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +16 -17
  229. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -2
  230. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +9 -5
  231. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +5 -2
  232. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  233. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  234. package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
  235. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +15 -8
  236. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +2 -2
  237. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  238. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
  239. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/provider-blocks.ts +1 -1
  240. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +7 -3
  241. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
  242. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +5 -5
  243. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
  244. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +7 -7
  245. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +32 -94
  246. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
  247. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +1 -1
  248. package/src/plugins/defaults/title-generate/package.json +0 -1
  249. package/src/plugins/defaults/tool-error/package.json +0 -1
  250. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  251. package/src/plugins/pipeline.ts +6 -293
  252. package/src/plugins/registry.ts +9 -37
  253. package/src/plugins/types.ts +76 -381
  254. package/src/plugins/user-loader.ts +30 -127
  255. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  256. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +35 -3
  257. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  258. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  259. package/src/runtime/agent-wake.ts +108 -209
  260. package/src/runtime/assistant-event-hub.ts +1 -1
  261. package/src/runtime/channel-approvals.ts +1 -1
  262. package/src/runtime/interactive-ui.ts +1 -1
  263. package/src/runtime/routes/__tests__/acp-routes.test.ts +315 -55
  264. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  265. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  266. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  267. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  268. package/src/runtime/routes/acp-routes.test.ts +89 -25
  269. package/src/runtime/routes/acp-routes.ts +81 -29
  270. package/src/runtime/routes/approval-routes.ts +1 -1
  271. package/src/runtime/routes/browser-routes.ts +1 -1
  272. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  273. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  274. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  275. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  276. package/src/runtime/routes/conversation-routes.ts +28 -2
  277. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  278. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  279. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  280. package/src/runtime/routes/host-cu-routes.ts +1 -1
  281. package/src/runtime/routes/identity-routes.ts +18 -3
  282. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  283. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  284. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  285. package/src/runtime/routes/memory-v3-routes.ts +16 -6
  286. package/src/runtime/routes/playground/helpers.ts +1 -1
  287. package/src/runtime/routes/plugins-routes.ts +337 -35
  288. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  289. package/src/runtime/routes/work-items-routes.ts +2 -4
  290. package/src/runtime/services/conversation-serializer.ts +1 -1
  291. package/src/signals/cancel.ts +2 -4
  292. package/src/subagent/manager.ts +21 -5
  293. package/src/tools/acp/context.ts +20 -0
  294. package/src/tools/acp/list-agents.test.ts +8 -2
  295. package/src/tools/acp/spawn.test.ts +176 -195
  296. package/src/tools/acp/spawn.ts +37 -172
  297. package/src/tools/acp/steer.test.ts +105 -8
  298. package/src/tools/acp/steer.ts +48 -17
  299. package/src/tools/apps/executors.ts +166 -50
  300. package/src/tools/filesystem/write.ts +34 -0
  301. package/src/tools/subagent/spawn.ts +2 -4
  302. package/src/tools/ui-surface/definitions.ts +25 -5
  303. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  304. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  305. package/docs/plugins.md +0 -832
  306. package/examples/plugins/echo/register.ts +0 -143
  307. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  308. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  309. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  310. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  311. package/src/__tests__/pipeline-runner.test.ts +0 -554
  312. package/src/__tests__/plugin-external-api.test.ts +0 -68
  313. package/src/daemon/wake-target-adapter.ts +0 -253
  314. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  315. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  316. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  317. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  318. package/src/plugins/defaults/compaction/register.ts +0 -35
  319. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  320. package/src/plugins/defaults/empty-response/register.ts +0 -23
  321. package/src/plugins/defaults/history-repair/register.ts +0 -24
  322. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  323. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  324. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  325. package/src/plugins/defaults/title-generate/register.ts +0 -35
  326. package/src/plugins/defaults/tool-error/register.ts +0 -23
  327. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  328. package/src/plugins/external-api.ts +0 -104
  329. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  330. package/src/proactive-artifact/decision.test.ts +0 -226
  331. package/src/proactive-artifact/decision.ts +0 -165
  332. package/src/proactive-artifact/index.ts +0 -7
  333. package/src/proactive-artifact/job.test.ts +0 -962
  334. package/src/proactive-artifact/job.ts +0 -372
  335. package/src/proactive-artifact/message-copy.ts +0 -58
  336. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  337. package/src/proactive-artifact/trigger-state.ts +0 -123
  338. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
  339. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
  340. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  341. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  342. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +0 -0
  343. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
  344. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/provider-blocks.test.ts +0 -0
  345. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
  346. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
  347. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  348. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  349. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
  350. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  351. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  352. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  353. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  354. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  355. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  356. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
  357. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
  358. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
  359. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
  360. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
@@ -1,10 +1,17 @@
1
1
  /**
2
- * Search for plugin directories in the canonical GitHub source.
2
+ * Search the installable plugin catalog in the canonical GitHub source.
3
3
  *
4
- * Lists `vellum-ai/vellum-assistant/experimental/plugins/` at the configured
5
- * git ref and filters the directory entries by case-insensitive ECMAScript
6
- * regex. A plain query like `"memory"` matches anywhere in the name; anchors
7
- * like `"^simple"` work without escaping.
4
+ * The catalog is the union of two sources, both fetched from the repo at the
5
+ * configured git ref:
6
+ * 1. First-party plugins directories under
7
+ * `vellum-ai/vellum-assistant/experimental/plugins/`.
8
+ * 2. Whitelisted external ecosystem plugins — entries in the curated
9
+ * `experimental/plugins/marketplace.json` manifest (see
10
+ * {@link ./plugin-marketplace}).
11
+ *
12
+ * Entries are filtered by case-insensitive ECMAScript regex against the
13
+ * plugin name. A plain query like `"memory"` matches anywhere in the name;
14
+ * anchors like `"^simple"` work without escaping.
8
15
  *
9
16
  * Designed for direct programmatic use. The CLI command
10
17
  * `assistant plugins search <query>` is a thin wrapper that supplies
@@ -15,6 +22,10 @@
15
22
 
16
23
  import type { FetchLike } from "./install-from-github.js";
17
24
  import { DEFAULT_PLUGIN_REF } from "./install-from-github.js";
25
+ import {
26
+ fetchMarketplaceEntries,
27
+ type MarketplaceEntry,
28
+ } from "./plugin-marketplace.js";
18
29
 
19
30
  // Re-export the dep-injection type so callers can grab everything they need
20
31
  // from one module rather than reaching into `install-from-github.js`.
@@ -50,12 +61,33 @@ export interface SearchPluginsDeps {
50
61
  readonly fetch: FetchLike;
51
62
  }
52
63
 
53
- /** One matching plugin directory. */
64
+ /** Where a catalog match comes from. */
65
+ export type PluginMatchSource =
66
+ | { readonly kind: "first-party" }
67
+ | {
68
+ readonly kind: "github";
69
+ /** `owner/repo` of the external plugin repository. */
70
+ readonly repo: string;
71
+ /** Directory within the repo, when the plugin is not at the root. */
72
+ readonly path?: string;
73
+ /** Pinned git ref the plugin is fetched from. */
74
+ readonly ref: string;
75
+ };
76
+
77
+ /** One matching catalog entry. */
54
78
  export interface PluginSearchMatch {
55
- /** Directory name under `experimental/plugins/`. */
79
+ /** Install name `assistant plugins install <name>` resolves to it. */
56
80
  readonly name: string;
57
- /** Path within the repo (e.g. `experimental/plugins/<name>`). */
81
+ /**
82
+ * Human-readable origin of the entry: the repo-relative path for
83
+ * first-party plugins (e.g. `experimental/plugins/<name>`) or a
84
+ * `github:owner/repo@ref` locator for external ones.
85
+ */
58
86
  readonly path: string;
87
+ /** Short description, when known (external entries only today). */
88
+ readonly description?: string;
89
+ /** Discriminated origin, so callers can render/install accordingly. */
90
+ readonly source: PluginMatchSource;
59
91
  }
60
92
 
61
93
  /** Search result envelope. */
@@ -75,12 +107,28 @@ export class InvalidSearchPatternError extends Error {
75
107
  }
76
108
 
77
109
  /**
78
- * List directories under `experimental/plugins/` at {@link opts.ref} and
79
- * filter by {@link opts.query}.
80
- *
81
- * Only `type === "dir"` entries are returned — `experimental/plugins/`
82
- * follows a convention where each plugin lives in its own directory, so
83
- * loose files at the prefix are not plugins.
110
+ * The catalog source (GitHub) was reachable but refused or could not serve
111
+ * the request right now — rate limiting (HTTP 403 with the rate-limit budget
112
+ * exhausted, or 429) or an upstream 5xx. Distinct from a hard 404 on the
113
+ * plugins prefix (a real "source gone" misconfiguration): a transient
114
+ * upstream failure should surface as a retryable "temporarily unavailable"
115
+ * rather than a generic internal error, and is a candidate for serving a
116
+ * stale cached catalog.
117
+ */
118
+ export class PluginCatalogUnavailableError extends Error {
119
+ /** Upstream HTTP status that triggered the failure. */
120
+ readonly status: number;
121
+ constructor(message: string, status: number) {
122
+ super(message);
123
+ this.name = "PluginCatalogUnavailableError";
124
+ this.status = status;
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Build the catalog at {@link opts.ref} and return the entries whose name
130
+ * matches {@link opts.query} (case-insensitive ECMAScript regex; an empty
131
+ * query matches everything).
84
132
  */
85
133
  export async function searchPlugins(
86
134
  opts: SearchPluginsOptions,
@@ -92,17 +140,119 @@ export async function searchPlugins(
92
140
  // the network — keeps "user typo" cheap to recover from.
93
141
  const matcher = buildMatcher(opts.query);
94
142
 
95
- const entries = await listDir(PLUGIN_SOURCE_PATH_PREFIX, ref, deps.fetch);
143
+ const { matches: catalog } = await loadPluginCatalog({ ref }, deps);
144
+ const matches = catalog.filter((m) => matcher(m.name));
145
+
146
+ return { query: opts.query, ref, matches };
147
+ }
148
+
149
+ /**
150
+ * Validate that {@link query} compiles as a case-insensitive ECMAScript regex,
151
+ * throwing {@link InvalidSearchPatternError} if not. Lets a caching caller
152
+ * (the daemon) reject a malformed query before loading the catalog, so a typo
153
+ * is a cheap deterministic 400 rather than a wasted GitHub request.
154
+ */
155
+ export function assertValidSearchPattern(query: string): void {
156
+ buildMatcher(query);
157
+ }
158
+
159
+ /**
160
+ * Filter a pre-loaded {@link PluginCatalog} by {@link query}, compiling it as
161
+ * a case-insensitive ECMAScript regex (an empty query matches everything).
162
+ * Lets a caching caller (the daemon) reuse one catalog load across many
163
+ * searches. Throws {@link InvalidSearchPatternError} on a malformed pattern.
164
+ */
165
+ export function filterPluginCatalog(
166
+ catalog: PluginCatalog,
167
+ query: string,
168
+ ): PluginSearchMatch[] {
169
+ const matcher = buildMatcher(query);
170
+ return catalog.matches.filter((m) => matcher(m.name));
171
+ }
172
+
173
+ /** The full, unfiltered catalog at a given ref. */
174
+ export interface PluginCatalog {
175
+ readonly ref: string;
176
+ /** Every catalog entry, deduped and sorted alphabetically by name. */
177
+ readonly matches: readonly PluginSearchMatch[];
178
+ }
179
+
180
+ /**
181
+ * Build the full catalog at {@link opts.ref}: every first-party plugin
182
+ * directory under `experimental/plugins/` merged with every whitelisted
183
+ * external entry in the marketplace manifest.
184
+ *
185
+ * The result is **query-independent** — `searchPlugins` applies the regex
186
+ * filter in memory afterwards. That separation is what lets a long-lived
187
+ * caller (the daemon) cache one catalog load and serve any number of
188
+ * searches from it without re-hitting GitHub (see {@link ./plugin-catalog-cache}).
189
+ */
190
+ export async function loadPluginCatalog(
191
+ opts: { readonly ref?: string },
192
+ deps: SearchPluginsDeps,
193
+ ): Promise<PluginCatalog> {
194
+ const ref = opts.ref ?? DEFAULT_PLUGIN_REF;
195
+
196
+ const [entries, marketplace] = await Promise.all([
197
+ listDir(PLUGIN_SOURCE_PATH_PREFIX, ref, deps.fetch),
198
+ fetchMarketplaceSafe(deps.fetch, ref),
199
+ ]);
96
200
 
97
201
  const matches: PluginSearchMatch[] = [];
202
+ const seen = new Set<string>();
98
203
  for (const entry of entries) {
99
204
  if (entry.type !== "dir") continue;
100
- if (!matcher(entry.name)) continue;
101
- matches.push({ name: entry.name, path: entry.path });
205
+ matches.push({
206
+ name: entry.name,
207
+ path: entry.path,
208
+ source: { kind: "first-party" },
209
+ });
210
+ seen.add(entry.name);
102
211
  }
212
+
213
+ for (const entry of marketplace) {
214
+ // First-party plugins win a name collision — the curated manifest is
215
+ // additive, never an override of what ships in-repo.
216
+ if (seen.has(entry.name)) continue;
217
+ matches.push(marketplaceMatch(entry));
218
+ seen.add(entry.name);
219
+ }
220
+
103
221
  matches.sort((a, b) => a.name.localeCompare(b.name));
104
222
 
105
- return { query: opts.query, ref, matches };
223
+ return { ref, matches };
224
+ }
225
+
226
+ /**
227
+ * Project a marketplace entry onto the catalog match shape, building a
228
+ * `github:owner/repo[/path]@ref` locator for display.
229
+ */
230
+ function marketplaceMatch(entry: MarketplaceEntry): PluginSearchMatch {
231
+ const { repo, path, ref } = entry.source;
232
+ const locator = `github:${repo}${path ? `/${path}` : ""}@${ref}`;
233
+ return {
234
+ name: entry.name,
235
+ path: locator,
236
+ description: entry.description,
237
+ source: { kind: "github", repo, path, ref },
238
+ };
239
+ }
240
+
241
+ /**
242
+ * Fetch the marketplace manifest, degrading to an empty whitelist on any
243
+ * failure. The manifest is supplementary to the first-party listing, so a
244
+ * missing or malformed manifest must never break the core catalog — mirroring
245
+ * the daemon's "never block over a subsystem failure" philosophy.
246
+ */
247
+ async function fetchMarketplaceSafe(
248
+ fetchFn: FetchLike,
249
+ ref: string,
250
+ ): Promise<readonly MarketplaceEntry[]> {
251
+ try {
252
+ return await fetchMarketplaceEntries({ fetch: fetchFn }, { ref });
253
+ } catch {
254
+ return [];
255
+ }
106
256
  }
107
257
 
108
258
  function buildMatcher(query: string): (name: string) => boolean {
@@ -127,13 +277,17 @@ async function listDir(
127
277
 
128
278
  const res = await githubFetch(url, fetchFn);
129
279
  if (!res.ok) {
130
- // Unlike `installPlugin`, where 404 on a specific plugin name is a
131
- // legitimate "not found" outcome, 404 on the plugins prefix itself
132
- // means the canonical source path is gone — surface it as an error
133
- // rather than silently returning empty results.
134
- throw new Error(
135
- `GitHub contents listing failed for ${apiPath} @ ${ref}: HTTP ${res.status}`,
136
- );
280
+ const detail = `GitHub contents listing failed for ${apiPath} @ ${ref}: HTTP ${res.status}`;
281
+ // Rate limiting (403 with the budget exhausted, or 429) and upstream
282
+ // 5xx are transient — surface them as a retryable "temporarily
283
+ // unavailable" so the caller can serve a stale cache and the route can
284
+ // map to 503 instead of a misleading 500. A 404 on the plugins prefix
285
+ // itself means the canonical source path is gone (a real
286
+ // misconfiguration), so it stays a hard error.
287
+ if (isTransientUpstreamStatus(res)) {
288
+ throw new PluginCatalogUnavailableError(detail, res.status);
289
+ }
290
+ throw new Error(detail);
137
291
  }
138
292
 
139
293
  const body = (await res.json()) as unknown;
@@ -150,10 +304,22 @@ async function listDir(
150
304
  * request. Unauthenticated — the canonical source is a public repo, mirroring
151
305
  * `installPlugin` which uses the same envelope.
152
306
  */
153
- async function githubFetch(
154
- url: string,
155
- fetchFn: FetchLike,
156
- ): Promise<Response> {
307
+ /**
308
+ * Whether a non-OK GitHub response should be treated as a transient
309
+ * "temporarily unavailable" failure rather than a hard error. Covers
310
+ * rate limiting (429, or 403 once the rate-limit budget is exhausted) and
311
+ * upstream server errors (5xx). A bare 403 without the rate-limit signal
312
+ * (e.g. a genuine permissions problem) is not treated as transient.
313
+ */
314
+ function isTransientUpstreamStatus(res: Response): boolean {
315
+ if (res.status === 429 || res.status >= 500) return true;
316
+ if (res.status === 403) {
317
+ return res.headers.get("x-ratelimit-remaining") === "0";
318
+ }
319
+ return false;
320
+ }
321
+
322
+ async function githubFetch(url: string, fetchFn: FetchLike): Promise<Response> {
157
323
  return fetchFn(url, {
158
324
  headers: {
159
325
  Accept: "application/vnd.github+json",
@@ -85,8 +85,8 @@ describe("unified feature flag registry guard", () => {
85
85
  ) {
86
86
  violations.push(`${prefix}: missing or non-string 'description'`);
87
87
  }
88
- if (typeof flag.defaultEnabled !== "boolean") {
89
- violations.push(`${prefix}: missing or non-boolean 'defaultEnabled'`);
88
+ if (typeof flag.defaultEnabled !== "boolean" && typeof flag.defaultEnabled !== "string") {
89
+ violations.push(`${prefix}: missing or invalid 'defaultEnabled' (expected boolean or string)`);
90
90
  }
91
91
  }
92
92
 
@@ -10,6 +10,7 @@ describe("DEFAULT_ACP_AGENT_PROFILES", () => {
10
10
  expect(Object.keys(DEFAULT_ACP_AGENT_PROFILES).sort()).toEqual([
11
11
  "claude",
12
12
  "codex",
13
+ "gemini",
13
14
  ]);
14
15
  });
15
16
 
@@ -29,6 +30,14 @@ describe("DEFAULT_ACP_AGENT_PROFILES", () => {
29
30
  });
30
31
  });
31
32
 
33
+ test("gemini profile speaks native ACP via gemini --acp (no adapter binary)", () => {
34
+ expect(DEFAULT_ACP_AGENT_PROFILES.gemini).toEqual({
35
+ command: "gemini",
36
+ args: ["--acp"],
37
+ description: "Google Gemini CLI (native ACP via gemini --acp)",
38
+ });
39
+ });
40
+
32
41
  test("is deeply frozen so mutation throws in strict mode", () => {
33
42
  expect(Object.isFrozen(DEFAULT_ACP_AGENT_PROFILES)).toBe(true);
34
43
  for (const profile of Object.values(DEFAULT_ACP_AGENT_PROFILES)) {
@@ -46,6 +55,7 @@ describe("DEFAULT_AGENT_NPM_PACKAGES", () => {
46
55
  expect(DEFAULT_AGENT_NPM_PACKAGES).toEqual({
47
56
  "claude-agent-acp": "@agentclientprotocol/claude-agent-acp",
48
57
  "codex-acp": "@zed-industries/codex-acp",
58
+ gemini: "@google/gemini-cli",
49
59
  });
50
60
  });
51
61
 
@@ -30,6 +30,11 @@ export const DEFAULT_ACP_AGENT_PROFILES: Readonly<
30
30
  args: FROZEN_EMPTY_ARGS,
31
31
  description: "OpenAI Codex CLI (via @zed-industries/codex-acp)",
32
32
  }),
33
+ gemini: Object.freeze({
34
+ command: "gemini",
35
+ args: Object.freeze(["--acp"]) as unknown as string[],
36
+ description: "Google Gemini CLI (native ACP via gemini --acp)",
37
+ }),
33
38
  });
34
39
 
35
40
  /**
@@ -44,4 +49,5 @@ export const DEFAULT_AGENT_NPM_PACKAGES: Readonly<Record<string, string>> =
44
49
  Object.freeze({
45
50
  "claude-agent-acp": "@agentclientprotocol/claude-agent-acp",
46
51
  "codex-acp": "@zed-industries/codex-acp",
52
+ gemini: "@google/gemini-cli",
47
53
  });
@@ -7,29 +7,30 @@ metadata:
7
7
  vellum:
8
8
  display-name: "ACP"
9
9
  activation-hints:
10
- - "User wants to delegate a coding task to Claude Code, Codex, or another ACP agent"
10
+ - "User asks to use Claude Code, Codex, or Gemini to do something"
11
+ - "User wants to delegate a coding task to Claude Code, Codex, Gemini, or another ACP agent"
12
+ - "User wants to hand a coding task to another agent and check on it later"
11
13
  - "User wants to spawn an external coding agent that runs autonomously and streams results back"
12
- - "User mentions ACP, claude-agent-acp, codex-acp, or running multiple coding agents in parallel"
14
+ - "User mentions ACP, claude-agent-acp, codex-acp, gemini --acp, or running multiple coding agents in parallel"
13
15
  avoid-when:
14
- - "Task is small enough to do inline with the assistant's own tools no need for an external agent"
16
+ - "Task is small enough to do inline with the assistant's own tools - no need for an external agent"
15
17
  ---
16
18
 
17
- ACP agent orchestration - spawn external coding agents (Claude Code, Codex, etc.) to work on tasks via the Agent Client Protocol. Each agent runs as its own subprocess speaking ACP over stdio and streams results back into the conversation.
19
+ ACP agent orchestration - spawn external coding agents (Claude Code, Codex, Gemini) to work on tasks via the Agent Client Protocol. Each agent runs as its own subprocess speaking ACP over stdio and streams results back into the conversation.
18
20
 
19
21
  ## Usage
20
22
 
21
23
  Use `acp_spawn` to delegate a coding task to an external agent. The agent runs as a subprocess speaking the ACP protocol over stdio and streams results back.
22
24
 
25
+ Users can refer to agents by natural names: "claude code", "codex cli", "openai codex", "gemini cli", and "google gemini" all resolve to the canonical `claude`, `codex`, and `gemini` ids (unless the user's config defines an agent literally keyed by that name, which always wins).
26
+
23
27
  ## First-time setup
24
28
 
25
- When the user first tries to use ACP and it's not configured, set it up automatically:
29
+ When the user first tries to use ACP and it's not enabled, set it up automatically:
26
30
 
27
- 1. **Check if `claude-agent-acp` is installed** by running `which claude-agent-acp`. If not found, install it:
28
- ```bash
29
- npm i -g @agentclientprotocol/claude-agent-acp
30
- ```
31
+ 1. **Enable the `acp` feature flag** (the primary enablement path). Either PATCH it via the gateway feature-flags endpoint or direct the user to toggle "ACP Coding Agents" in the client's feature flags UI. Flag changes are hot-refreshed in the assistant - no restart needed.
31
32
 
32
- 2. **Enable ACP in the workspace config** by editing the config file to add the `acp` section. Default profiles for `claude` and `codex` ship out-of-box, so the minimal config is just:
33
+ As a supported alternative, edit the workspace config file to add the `acp` section. Default profiles for `claude`, `codex`, and `gemini` ship out-of-box, so the minimal config is just:
33
34
  ```json
34
35
  {
35
36
  "acp": {
@@ -38,57 +39,108 @@ When the user first tries to use ACP and it's not configured, set it up automati
38
39
  }
39
40
  }
40
41
  ```
42
+ If you go the config route, **wait a few seconds** for the config watcher to pick up the change (it hot-reloads automatically - no restart needed).
41
43
 
42
- 3. **Wait a few seconds** for the config watcher to pick up the change (it hot-reloads automatically - no restart needed).
44
+ 2. Then retry the `acp_spawn` call. Do NOT run `vellum sleep && vellum wake` - that kills the conversation.
43
45
 
44
- 4. Then retry the `acp_spawn` call. Do NOT run `vellum sleep && vellum wake` - that kills the conversation.
46
+ No manual binary installation is needed first: missing adapter binaries are installed automatically (see below).
45
47
 
46
- ## Codex setup
48
+ ## Automatic adapter availability
47
49
 
48
- To use Codex via ACP, both the `codex-acp` adapter and the underlying `codex` CLI must be on PATH:
50
+ When `acp_spawn` finds the agent's binary missing from PATH, the assistant installs it once via a sandboxed bun global install and then runs the real installed binary. The install runs in a fresh empty temporary directory (never the task's project directory), with the Claude/Gemini secrets stripped from the installer environment and the registry pinned to the public npm registry, so a malicious project directory cannot hijack package resolution or capture a token. After this one-time install, the adapter is a normal trusted binary on PATH and every later spawn (and resume) uses it directly.
49
51
 
50
- 1. **Install the ACP adapter:**
51
- ```bash
52
- npm i -g @zed-industries/codex-acp
53
- ```
54
- This provides the `codex-acp` binary that the assistant spawns.
52
+ Only the allowlisted out-of-box packages are ever installed this way (`@agentclientprotocol/claude-agent-acp`, `@zed-industries/codex-acp`, `@google/gemini-cli`); user-configured agents with custom commands are never installed automatically.
53
+
54
+ Manual installation is fallback guidance for unusual setups: bun unavailable, restricted global installs, or an auto-install failure (the failure reason is surfaced in the tool result).
55
+
56
+ ```bash
57
+ bun add -g @agentclientprotocol/claude-agent-acp # claude
58
+ bun add -g @zed-industries/codex-acp # codex
59
+ bun add -g @google/gemini-cli # gemini
60
+ ```
61
+
62
+ ## Claude setup
55
63
 
56
- 2. **Install the Codex CLI** (version 0.111 or higher) via OpenAI's distribution channel of choice. The `codex-acp` adapter shells out to `codex` under the hood and will fail if it isn't on PATH.
64
+ The `claude-agent-acp` adapter requires a Claude OAuth token. Store it once in the credential store and every spawn injects it as `CLAUDE_CODE_OAUTH_TOKEN` automatically:
57
65
 
58
- 3. **Authenticate.** The `codex-acp` adapter inherits whatever auth the underlying `codex` CLI uses. Typical flows:
66
+ ```bash
67
+ assistant credentials set --service acp --field claude_oauth_token <token>
68
+ ```
69
+
70
+ When the token is missing, do NOT ask the user to paste it into chat. Collect it via the secret-request flow instead: `credential_store` with action `prompt`, service `acp`, field `claude_oauth_token`. That prompts the user through a secure UI so the token never enters the conversation or the workspace config. Users generate the token by running `claude setup-token` on a machine where they are logged in to Claude.
71
+
72
+ ## Codex setup
73
+
74
+ The `codex-acp` adapter is installed automatically when missing, but it shells out to the underlying `codex` CLI, which must also be on PATH:
75
+
76
+ 1. **Install the Codex CLI** (version 0.111 or higher) via OpenAI's distribution channel of choice. The adapter will fail if `codex` isn't on PATH.
77
+
78
+ 2. **Authenticate.** The `codex-acp` adapter inherits whatever auth the underlying `codex` CLI uses. Typical flows:
59
79
  - `codex login` (OAuth)
60
80
  - `CODEX_API_KEY` environment variable
61
81
  - `OPENAI_API_KEY` environment variable
62
82
 
63
- If `codex-acp` isn't on PATH when the user asks for it, the assistant will surface the install hint via `acp_list_agents`.
83
+ ## Gemini setup
84
+
85
+ Gemini CLI speaks ACP natively (`gemini --acp`) - there is no separate adapter binary. The CLI itself is installed from `@google/gemini-cli` when missing.
86
+
87
+ **Authenticate** with an API key through the credential store (the primary path):
88
+
89
+ ```bash
90
+ assistant credentials set --service acp --field gemini_api_key <key>
91
+ ```
92
+
93
+ Or collect the key via the secret-request flow: `credential_store` with action `prompt`, service `acp`, field `gemini_api_key`. Either way the key never appears in chat or workspace config, and every spawn injects it as `GEMINI_API_KEY` automatically. The key is optional - a spawn proceeds without it when the vault has no entry.
94
+
95
+ The alternative is browser OAuth: run `gemini` once interactively and complete the sign-in flow. This is impractical on hosted assistants (no browser), so prefer the credential store there.
96
+
97
+ Do NOT put API keys (or any secret) in the workspace config file - secrets never belong in the workspace directory. Use the credential store instead.
98
+
99
+ A workspace `acp.agents.gemini` override is only for non-secret customization (custom binary path, extra args, non-secret env vars). It must spell out the full `command` and `args` - see "Critical: correct agent command" below for the replace-not-merge rule:
100
+ ```json
101
+ {
102
+ "acp": {
103
+ "agents": {
104
+ "gemini": {
105
+ "command": "gemini",
106
+ "args": ["--acp"],
107
+ "env": { "NO_COLOR": "1" }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ ```
64
113
 
65
114
  ## Critical: correct agent command
66
115
 
67
- - `claude-agent-acp` and `codex-acp` are the two supported adapter binaries today. They are what speak the ACP JSON-RPC protocol.
68
- - NEVER use `claude`, `claude -p`, `claude --acp`, the bare `codex` CLI, or any other command as the ACP `command`. Only the dedicated `*-acp` adapters speak the protocol.
69
- - Default profiles for `claude` and `codex` ship out-of-box. Users only need an `agents.<id>` entry in config if they want to override the defaults (e.g. point to a custom binary path or pass extra args).
70
- - NEVER change an existing ACP config to use a different command. If the config already has `claude-agent-acp` or `codex-acp`, leave it alone.
116
+ - Three agents are supported out-of-box: `claude` (via the `claude-agent-acp` adapter), `codex` (via the `codex-acp` adapter), and `gemini` (via `gemini --acp` - Gemini speaks ACP natively, no adapter binary).
117
+ - NEVER use `claude`, `claude -p`, `claude --acp`, or the bare `codex` CLI as the ACP `command`. Claude and Codex only speak the protocol through their dedicated `*-acp` adapters. Gemini is the exception: the `gemini` CLI itself speaks ACP when launched with `--acp`.
118
+ - Default profiles for all three ship out-of-box. Users only need an `agents.<id>` entry in config if they want to override the defaults (e.g. point to a custom binary path or pass extra args/env). An `acp.agents.<id>` entry replaces the bundled default entirely (no field merge), so any override must spell out the full `command` and `args`, not just the field being changed.
119
+ - NEVER change an existing ACP config to use a different command. If the config already has `claude-agent-acp`, `codex-acp`, or `gemini`, leave it alone.
71
120
 
72
- ## Updating the adapter
121
+ ## Updating an adapter
73
122
 
74
- If `acp_spawn` reports that an adapter is outdated, ask the user before updating. To update:
123
+ Adapters are installed once via a bun global install. To update one to the latest version, ask the user first, then re-install it globally:
75
124
 
76
125
  ```bash
77
- npm i -g @agentclientprotocol/claude-agent-acp@latest
126
+ bun add -g @agentclientprotocol/claude-agent-acp@latest
127
+ # or
128
+ bun add -g @zed-industries/codex-acp@latest
78
129
  # or
79
- npm i -g @zed-industries/codex-acp@latest
130
+ bun add -g @google/gemini-cli@latest
80
131
  ```
81
132
 
82
133
  Then retry the `acp_spawn` call.
83
134
 
84
135
  ## When to use acp_steer vs acp_spawn
85
136
 
86
- - **`acp_steer` interrupts the in-flight prompt.** Use it to course-correct a running agent ("stop, do X instead"). It cancels whatever the agent is currently working on and replaces it with the new instruction.
87
- - **For follow-ups after the current task** ("also do Y when you're done"), do NOT use `acp_steer`. Wait for the `acp_session_completed` notification and call `acp_spawn` again with the new task. Queued follow-ups in the same session are not yet supported.
137
+ - **On a running session, `acp_steer` interrupts the in-flight prompt.** Use it to course-correct ("stop, do X instead"). It cancels whatever the agent is currently working on and replaces it with the new instruction. Queued follow-ups behind a running prompt are not supported - wait for the `acp_session_completed` notification instead.
138
+ - **On a completed (or assistant-restarted) session, `acp_steer` transparently resumes it.** The session is restored from persisted history via ACP session loading when the agent supports it, and the new instruction runs with the agent's full prior context. This is the primary way to do follow-up work on an existing session id - prefer it over spawning a fresh session that would lose context.
139
+ - If resume isn't possible (the session was recorded before resume support and has no working directory, or the agent lacks the capability), the error explains why; fall back to `acp_spawn`. For claude sessions, the completion message also includes a `claude --resume <id>` CLI hint for resuming outside the assistant.
88
140
 
89
141
  ## Discoverability
90
142
 
91
- Use `acp_list_agents` to see what's set up and what's missing. It returns each available agent profile, whether ACP is enabled, whether the agent's binary is on PATH, and an install hint if not. This is the right tool to call when deciding between `claude` and `codex`, or when the user asks "what coding agents do I have?"
143
+ Use `acp_list_agents` to see what's set up and what's missing. It returns each available agent profile, whether ACP is enabled, whether the agent's binary is on PATH (missing binaries are installed automatically on first spawn), and an install hint if not. This is the right tool to call when deciding between `claude`, `codex`, and `gemini`, or when the user asks "what coding agents do I have?"
92
144
 
93
145
  ## Working directory
94
146
 
@@ -3,7 +3,7 @@
3
3
  "tools": [
4
4
  {
5
5
  "name": "acp_spawn",
6
- "description": "Spawn an external coding agent (e.g. Claude Code, Codex) via ACP to work on a task. Default profiles ship for `claude` (`claude-agent-acp`) and `codex` (`codex-acp`); the assistant resolves the agent id to the right adapter binary. The agent runs as a subprocess and streams results back. Use this when you want to delegate a coding task to an external agent that has its own tools, file editing, and terminal access. If a binary is missing, the call returns an actionable install hint do NOT alter `agents.<id>.command` to swap binaries. If ACP is disabled, follow the setup instructions in SKILL.md.",
6
+ "description": "Spawn an external coding agent (e.g. Claude Code, Codex, Gemini) via ACP to work on a task. Default profiles ship for `claude` (`claude-agent-acp`), `codex` (`codex-acp`), and `gemini` (`gemini --acp`); the assistant resolves the agent id to the right binary. The agent runs as a subprocess and streams results back. Use this when you want to delegate a coding task to an external agent that has its own tools, file editing, and terminal access. If a default agent's binary is missing, the assistant installs it once via a sandboxed bun global install and proceeds in the same call; if that fails (e.g. bun unavailable), an actionable install hint is returned - do NOT alter `agents.<id>.command` to swap binaries. If ACP is disabled, follow the setup instructions in SKILL.md.",
7
7
  "category": "orchestration",
8
8
  "risk": "high",
9
9
  "input_schema": {
@@ -11,7 +11,7 @@
11
11
  "properties": {
12
12
  "agent": {
13
13
  "type": "string",
14
- "description": "Which agent to spawn (e.g. 'claude', 'codex'). Defaults to 'claude'."
14
+ "description": "Which agent to spawn (e.g. 'claude', 'codex', 'gemini'). Natural names like 'claude code' or 'gemini cli' also resolve. Defaults to 'claude'."
15
15
  },
16
16
  "task": {
17
17
  "type": "string",
@@ -65,7 +65,7 @@
65
65
  },
66
66
  {
67
67
  "name": "acp_steer",
68
- "description": "**Interrupts** the in-flight prompt of a running ACP session and replaces it with `instruction`. Use to redirect the agent (e.g., 'stop, do X instead'). For follow-up work that should happen *after* the current task, do NOT use this wait for `acp_session_completed` and `acp_spawn` again.",
68
+ "description": "Sends `instruction` to an ACP session. On a running session this **interrupts** the in-flight prompt and replaces it - use it to redirect the agent (e.g., 'stop, do X instead'). A completed or assistant-restarted session is transparently resumed from persisted history via ACP session loading when the agent supports it, so follow-up work on an existing session id CAN go through this tool instead of spawning a new session. If resume is not possible (legacy session without a recorded working directory, or the agent lacks the capability), the error explains why; fall back to `acp_spawn`.",
69
69
  "category": "orchestration",
70
70
  "risk": "high",
71
71
  "input_schema": {
@@ -87,7 +87,7 @@
87
87
  },
88
88
  {
89
89
  "name": "acp_list_agents",
90
- "description": "Lists ACP coding agents available to spawn. Each entry includes whether ACP is enabled, whether the agent's binary is on PATH, and an install command if not. Use this to decide between 'claude' and 'codex' or to surface setup steps to the user.",
90
+ "description": "Lists ACP coding agents available to spawn. Each entry includes whether ACP is enabled, whether the agent's binary is on PATH (missing binaries are installed automatically on first spawn), and an install command if not. Use this to decide between 'claude', 'codex', and 'gemini' or to surface setup steps to the user.",
91
91
  "category": "orchestration",
92
92
  "risk": "low",
93
93
  "input_schema": {