@vellumai/assistant 0.8.7 → 0.8.8-dev.202606052332.17fc8ea

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 (570) hide show
  1. package/Dockerfile +20 -4
  2. package/bun.lock +2 -2
  3. package/docker-entrypoint.sh +4 -2
  4. package/docker-init-apt-root.sh +3 -1
  5. package/docker-kata-apt-env.sh +3 -1
  6. package/docker-kata-runtime-family.sh +12 -0
  7. package/docs/architecture/memory.md +1 -1
  8. package/examples/plugins/echo/README.md +61 -66
  9. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  10. package/examples/plugins/echo/hooks/stop.ts +16 -0
  11. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  12. package/examples/plugins/echo/package.json +1 -2
  13. package/examples/plugins/echo/src/emit.ts +19 -0
  14. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
  15. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  16. package/openapi.yaml +3378 -335
  17. package/package.json +2 -2
  18. package/scripts/generate-openapi.ts +68 -41
  19. package/src/__tests__/agent-loop-exit-reason.test.ts +35 -93
  20. package/src/__tests__/agent-loop-provider-error-recording.test.ts +1 -1
  21. package/src/__tests__/agent-loop.test.ts +37 -87
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  23. package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
  24. package/src/__tests__/annotate-risk-options.test.ts +2 -3
  25. package/src/__tests__/anthropic-provider.test.ts +95 -2
  26. package/src/__tests__/app-control-flow.test.ts +1 -1
  27. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  28. package/src/__tests__/approval-routes-http.test.ts +4 -1
  29. package/src/__tests__/assistant-event-hub.test.ts +25 -0
  30. package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
  31. package/src/__tests__/{conversation-stream-state.test.ts → assistant-stream-state.test.ts} +252 -91
  32. package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
  33. package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
  34. package/src/__tests__/btw-routes.test.ts +62 -3
  35. package/src/__tests__/build-persisted-content.test.ts +184 -0
  36. package/src/__tests__/catalog-files.test.ts +1 -1
  37. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  38. package/src/__tests__/channel-approvals.test.ts +1 -1
  39. package/src/__tests__/clawhub-files.test.ts +1 -1
  40. package/src/__tests__/compaction-circuit.test.ts +258 -0
  41. package/src/__tests__/compaction-direct.test.ts +132 -0
  42. package/src/__tests__/compaction.benchmark.test.ts +0 -30
  43. package/src/__tests__/config-watcher.test.ts +1 -1
  44. package/src/__tests__/conversation-abort-tool-results.test.ts +57 -19
  45. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +6 -5
  46. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -7
  47. package/src/__tests__/conversation-agent-loop-overflow.test.ts +316 -1143
  48. package/src/__tests__/conversation-agent-loop.test.ts +638 -1655
  49. package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
  50. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  51. package/src/__tests__/conversation-history-web-search.test.ts +11 -1
  52. package/src/__tests__/conversation-pairing.test.ts +4 -31
  53. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +6 -0
  54. package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -10
  55. package/src/__tests__/conversation-queue.test.ts +2 -0
  56. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -0
  57. package/src/__tests__/conversation-routes-slash-commands.test.ts +6 -5
  58. package/src/__tests__/conversation-runtime-assembly.test.ts +310 -300
  59. package/src/__tests__/conversation-runtime-workspace.test.ts +105 -45
  60. package/src/__tests__/conversation-slash-commands.test.ts +8 -42
  61. package/src/__tests__/conversation-slash-queue.test.ts +6 -1
  62. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  63. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +84 -0
  64. package/src/__tests__/conversation-sync-tags.test.ts +27 -15
  65. package/src/__tests__/conversation-title-service.test.ts +135 -2
  66. package/src/__tests__/conversation-workspace-cache-state.test.ts +17 -16
  67. package/src/__tests__/conversation-workspace-injection.test.ts +67 -2
  68. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +7 -6
  69. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  70. package/src/__tests__/cross-provider-web-search.test.ts +214 -1
  71. package/src/__tests__/db-acp-history.test.ts +101 -0
  72. package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
  73. package/src/__tests__/dm-persistence.test.ts +5 -1
  74. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  75. package/src/__tests__/empty-response-hook.test.ts +304 -0
  76. package/src/__tests__/feature-flag-test-helpers.ts +2 -2
  77. package/src/__tests__/file-write-tool.test.ts +63 -0
  78. package/src/__tests__/gateway-only-guard.test.ts +12 -2
  79. package/src/__tests__/gemini-image-service.test.ts +13 -0
  80. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  81. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  82. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  83. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  84. package/src/__tests__/heartbeat-service.test.ts +1 -0
  85. package/src/__tests__/helpers/mock-provider.ts +110 -0
  86. package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
  87. package/src/__tests__/history-repair-hook.test.ts +1 -0
  88. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  89. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  90. package/src/__tests__/identity-intro-cache.test.ts +12 -100
  91. package/src/__tests__/identity-routes.test.ts +248 -7
  92. package/src/__tests__/inbound-slack-persistence.test.ts +5 -1
  93. package/src/__tests__/injector-background-turn.test.ts +3 -9
  94. package/src/__tests__/injector-chain.test.ts +139 -275
  95. package/src/__tests__/injector-disk-pressure.test.ts +75 -41
  96. package/src/__tests__/injector-document-comments.test.ts +3 -3
  97. package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
  98. package/src/__tests__/injector-v3-suppression.test.ts +31 -37
  99. package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
  100. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  101. package/src/__tests__/list-messages-page-latest.test.ts +60 -0
  102. package/src/__tests__/list-messages-tool-merge.test.ts +20 -0
  103. package/src/__tests__/llm-usage-store.test.ts +223 -1
  104. package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
  105. package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
  106. package/src/__tests__/native-web-search.test.ts +191 -0
  107. package/src/__tests__/onboarding-template-contract.test.ts +2 -0
  108. package/src/__tests__/openai-image-service.test.ts +17 -0
  109. package/src/__tests__/openai-provider.test.ts +31 -1
  110. package/src/__tests__/{overflow-reduce-pipeline.test.ts → overflow-reduction-loop.test.ts} +64 -284
  111. package/src/__tests__/persist-unsendable-image.test.ts +215 -0
  112. package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
  113. package/src/__tests__/pkb-autoinject.test.ts +2 -5
  114. package/src/__tests__/plugin-api-shim.test.ts +3 -6
  115. package/src/__tests__/plugin-bootstrap.test.ts +14 -40
  116. package/src/__tests__/plugin-registry.test.ts +3 -76
  117. package/src/__tests__/plugin-types.test.ts +0 -193
  118. package/src/__tests__/process-message-display-content.test.ts +6 -2
  119. package/src/__tests__/reaction-persistence.test.ts +1 -1
  120. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
  121. package/src/__tests__/resolve-trust-class.test.ts +4 -4
  122. package/src/__tests__/runtime-events-sse-reconnect.test.ts +60 -23
  123. package/src/__tests__/schedule-routes.test.ts +603 -2
  124. package/src/__tests__/schedule-store.test.ts +41 -0
  125. package/src/__tests__/schedule-tools.test.ts +35 -0
  126. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  127. package/src/__tests__/server-history-render.test.ts +314 -1
  128. package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
  129. package/src/__tests__/skillssh-files.test.ts +1 -1
  130. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  131. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  132. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  133. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  134. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  135. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  136. package/src/__tests__/system-prompt.test.ts +20 -0
  137. package/src/__tests__/task-scheduler.test.ts +162 -1
  138. package/src/__tests__/terminal-tools.test.ts +6 -1
  139. package/src/__tests__/title-generate-hook.test.ts +319 -0
  140. package/src/__tests__/tool-error-hook.test.ts +278 -0
  141. package/src/__tests__/tool-preview-lifecycle.test.ts +468 -5
  142. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  143. package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
  144. package/src/__tests__/tool-result-truncation.test.ts +0 -2
  145. package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
  146. package/src/__tests__/ui-work-result-surface.test.ts +159 -0
  147. package/src/__tests__/usage-routes.test.ts +285 -1
  148. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  149. package/src/__tests__/voice-session-bridge.test.ts +6 -3
  150. package/src/__tests__/web-search-backend-failure.test.ts +166 -0
  151. package/src/acp/__tests__/agent-process.test.ts +161 -0
  152. package/src/acp/__tests__/client-handler.test.ts +40 -0
  153. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  154. package/src/acp/__tests__/helpers/exec-file-stub.ts +101 -0
  155. package/src/acp/__tests__/prepare-agent-env.test.ts +137 -0
  156. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  157. package/src/acp/__tests__/session-manager-resume.test.ts +736 -0
  158. package/src/acp/agent-process.ts +61 -1
  159. package/src/acp/auto-install.test.ts +196 -0
  160. package/src/acp/auto-install.ts +177 -0
  161. package/src/acp/client-handler.ts +31 -0
  162. package/src/acp/feature-gate.test.ts +48 -0
  163. package/src/acp/feature-gate.ts +34 -0
  164. package/src/acp/prepare-agent-env.ts +83 -29
  165. package/src/acp/resolve-agent.test.ts +320 -7
  166. package/src/acp/resolve-agent.ts +182 -18
  167. package/src/acp/resume-hint.ts +25 -0
  168. package/src/acp/session-manager.ts +495 -73
  169. package/src/acp/types.ts +8 -0
  170. package/src/agent/compaction-circuit.ts +60 -102
  171. package/src/agent/loop.ts +362 -485
  172. package/src/api/events/assistant-thinking-delta.ts +33 -0
  173. package/src/api/events/tool-output-chunk.ts +45 -0
  174. package/src/api/events/tool-use-preview-start.ts +32 -0
  175. package/src/api/events/trace-event.ts +69 -0
  176. package/src/api/index.ts +48 -13
  177. package/src/api/responses/conversation-message.ts +374 -0
  178. package/src/approvals/guardian-request-resolvers.ts +1 -1
  179. package/src/avatar/__tests__/avatar-store.test.ts +34 -29
  180. package/src/background-wake/next-wake.ts +1 -0
  181. package/src/cli/commands/__tests__/notifications.test.ts +58 -14
  182. package/src/cli/commands/notifications.ts +112 -60
  183. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  184. package/src/config/acp-defaults.test.ts +10 -0
  185. package/src/config/acp-defaults.ts +6 -0
  186. package/src/config/assistant-feature-flags.ts +22 -11
  187. package/src/config/bundled-skills/acp/SKILL.md +83 -31
  188. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  189. package/src/config/bundled-skills/app-builder/SKILL.md +224 -398
  190. package/src/config/bundled-skills/app-builder/TOOLS.json +29 -0
  191. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  192. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  193. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  194. package/src/config/bundled-skills/app-builder/references/examples/README.md +17 -0
  195. package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
  196. package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
  197. package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
  198. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  199. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  200. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  201. package/src/config/bundled-skills/messaging/SKILL.md +0 -7
  202. package/src/config/bundled-tool-registry.ts +2 -0
  203. package/src/config/feature-flag-cache.ts +3 -3
  204. package/src/config/feature-flag-registry.json +48 -7
  205. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
  206. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
  207. package/src/config/schemas/heartbeat.ts +9 -0
  208. package/src/config/schemas/llm.ts +1 -0
  209. package/src/config/schemas/memory-v2.ts +8 -0
  210. package/src/config/schemas/memory-v3.ts +8 -0
  211. package/src/config/schemas/platform.ts +8 -0
  212. package/src/config/seed-inference-profiles.ts +2 -2
  213. package/src/config/skills.ts +13 -0
  214. package/src/context/compactor.ts +1 -1
  215. package/src/context/strip-injections.ts +128 -0
  216. package/src/context/token-estimator.ts +23 -0
  217. package/src/context/tool-result-truncation.ts +0 -23
  218. package/src/context/window-manager.ts +5 -7
  219. package/src/credential-execution/executable-discovery.ts +16 -0
  220. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +6 -0
  221. package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
  222. package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
  223. package/src/daemon/assistant-attachments.ts +1 -1
  224. package/src/daemon/config-watcher.ts +2 -2
  225. package/src/daemon/context-overflow-reducer.ts +0 -1
  226. package/src/daemon/conversation-agent-loop-handlers.ts +594 -153
  227. package/src/daemon/conversation-agent-loop.ts +301 -997
  228. package/src/daemon/conversation-history.ts +5 -4
  229. package/src/daemon/conversation-lifecycle.ts +3 -4
  230. package/src/daemon/conversation-messaging.ts +7 -6
  231. package/src/daemon/conversation-process.ts +11 -16
  232. package/src/daemon/conversation-registry.ts +159 -0
  233. package/src/daemon/conversation-runtime-assembly.ts +218 -398
  234. package/src/daemon/conversation-slash.ts +6 -25
  235. package/src/daemon/conversation-store.ts +9 -90
  236. package/src/daemon/conversation-surfaces.ts +222 -4
  237. package/src/daemon/conversation-tool-setup.ts +2 -29
  238. package/src/daemon/conversation-workspace.ts +17 -0
  239. package/src/daemon/conversation.ts +32 -20
  240. package/src/daemon/external-plugins-bootstrap.ts +17 -18
  241. package/src/daemon/handlers/config-a2a.ts +51 -36
  242. package/src/daemon/handlers/config-slack-channel.ts +20 -14
  243. package/src/daemon/handlers/config-telegram.ts +16 -2
  244. package/src/daemon/handlers/conversations.ts +3 -1
  245. package/src/daemon/handlers/shared.ts +156 -84
  246. package/src/daemon/handlers/skills.ts +42 -10
  247. package/src/daemon/lifecycle.ts +25 -0
  248. package/src/daemon/message-types/apps.ts +1 -29
  249. package/src/daemon/message-types/messages.ts +9 -57
  250. package/src/daemon/message-types/skills.ts +2 -0
  251. package/src/daemon/message-types/surfaces.ts +136 -3
  252. package/src/daemon/now-scratchpad.ts +21 -0
  253. package/src/daemon/orphan-reaper.test.ts +210 -0
  254. package/src/daemon/orphan-reaper.ts +240 -0
  255. package/src/daemon/overflow-reduction-loop.ts +230 -0
  256. package/src/daemon/persist-unsendable-image.ts +117 -0
  257. package/src/daemon/process-message.ts +1 -3
  258. package/src/daemon/server.ts +2 -0
  259. package/src/daemon/trace-emitter.ts +6 -4
  260. package/src/daemon/trust-context.ts +19 -0
  261. package/src/daemon/wake-target-adapter.ts +3 -1
  262. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  263. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  264. package/src/heartbeat/heartbeat-service.ts +26 -0
  265. package/src/home/home-greeting-cache.ts +24 -1
  266. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  267. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  268. package/src/ipc/gateway-client.test.ts +2 -2
  269. package/src/ipc/gateway-client.ts +3 -3
  270. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  271. package/src/ipc/skill-routes/memory.ts +4 -2
  272. package/src/media/gemini-image-service.ts +15 -0
  273. package/src/media/openai-image-service.ts +14 -0
  274. package/src/media/types.ts +34 -0
  275. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +56 -0
  276. package/src/memory/auth-fallback-events-store.ts +94 -0
  277. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  278. package/src/memory/conversation-title-service.ts +65 -41
  279. package/src/memory/db-init.ts +6 -0
  280. package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
  281. package/src/memory/graph/conversation-graph-memory.ts +65 -0
  282. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  283. package/src/memory/jobs-store.ts +33 -0
  284. package/src/memory/jobs-worker.ts +32 -5
  285. package/src/memory/llm-usage-store.ts +224 -50
  286. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
  287. package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
  288. package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
  289. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  290. package/src/memory/migrations/index.ts +3 -0
  291. package/src/memory/pkb/autoinject.ts +61 -0
  292. package/src/memory/pkb/context.ts +50 -0
  293. package/src/memory/pkb/types.ts +14 -0
  294. package/src/memory/schedule-attribution-sql.ts +104 -0
  295. package/src/memory/schema/acp.ts +4 -0
  296. package/src/memory/schema/infrastructure.ts +16 -0
  297. package/src/memory/usage-grouped-buckets.ts +6 -1
  298. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -4
  299. package/src/memory/v2/consolidation-job.ts +14 -5
  300. package/src/notifications/conversation-pairing.ts +8 -15
  301. package/src/notifications/decision-engine.ts +6 -3
  302. package/src/notifications/home-feed-side-effect.ts +12 -1
  303. package/src/permissions/prompter.ts +4 -0
  304. package/src/plugin-api/constants.ts +4 -0
  305. package/src/plugin-api/index.ts +7 -5
  306. package/src/plugin-api/types.ts +151 -1
  307. package/src/plugins/defaults/compaction/compact.ts +59 -0
  308. package/src/plugins/defaults/compaction/package.json +1 -1
  309. package/src/plugins/defaults/compaction/register.ts +8 -19
  310. package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
  311. package/src/plugins/defaults/empty-response/register.ts +8 -13
  312. package/src/plugins/defaults/index.ts +2 -18
  313. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +95 -0
  314. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
  315. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
  316. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +288 -81
  317. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
  318. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +16 -0
  319. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +4 -4
  320. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
  321. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +48 -12
  322. package/src/plugins/defaults/memory-v3-shadow/__tests__/provider-blocks.test.ts +13 -0
  323. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
  324. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
  325. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +104 -32
  326. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
  327. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +96 -30
  328. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +34 -16
  329. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +5 -5
  330. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +2 -2
  331. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
  332. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  333. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  334. package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
  335. package/src/plugins/defaults/memory-v3-shadow/llm-retry.ts +32 -0
  336. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +8 -8
  337. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +26 -14
  338. package/src/plugins/defaults/{llm-call → memory-v3-shadow}/package.json +2 -2
  339. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
  340. package/src/plugins/defaults/memory-v3-shadow/provider-blocks.ts +26 -0
  341. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +3 -3
  342. package/src/plugins/defaults/memory-v3-shadow/register.ts +26 -0
  343. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
  344. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +51 -45
  345. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
  346. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +61 -46
  347. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +69 -99
  348. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
  349. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +8 -0
  350. package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
  351. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
  352. package/src/plugins/defaults/title-generate/package.json +1 -1
  353. package/src/plugins/defaults/title-generate/register.ts +18 -18
  354. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
  355. package/src/plugins/defaults/tool-error/package.json +1 -1
  356. package/src/plugins/defaults/tool-error/register.ts +9 -21
  357. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
  358. package/src/plugins/defaults/tool-result-truncate/register.ts +10 -21
  359. package/src/plugins/defaults/tool-result-truncate/terminal.ts +37 -18
  360. package/src/plugins/external-api.ts +2 -2
  361. package/src/plugins/pipeline.ts +6 -305
  362. package/src/plugins/registry.ts +10 -55
  363. package/src/plugins/types.ts +62 -797
  364. package/src/plugins/user-loader.ts +30 -127
  365. package/src/proactive-artifact/aux-message-injector.ts +4 -4
  366. package/src/proactive-artifact/job.test.ts +8 -13
  367. package/src/prompts/__tests__/system-prompt.test.ts +42 -0
  368. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +64 -0
  369. package/src/prompts/templates/BOOTSTRAP.md +2 -2
  370. package/src/prompts/templates/system-sections.ts +15 -0
  371. package/src/providers/anthropic/client.ts +37 -29
  372. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -0
  373. package/src/providers/openai/chat-completions-provider.ts +44 -0
  374. package/src/providers/openrouter/client.ts +1 -0
  375. package/src/providers/placeholder-sentinels.ts +35 -0
  376. package/src/runtime/__tests__/agent-wake.test.ts +10 -6
  377. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  378. package/src/runtime/agent-wake.ts +2 -5
  379. package/src/runtime/assistant-event-hub.ts +37 -7
  380. package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
  381. package/src/runtime/channel-approvals.ts +1 -1
  382. package/src/runtime/http-router.ts +16 -21
  383. package/src/runtime/http-types.ts +16 -70
  384. package/src/runtime/interactive-ui.ts +1 -1
  385. package/src/runtime/pending-interactions.ts +1 -0
  386. package/src/runtime/routes/__tests__/acp-routes.test.ts +283 -55
  387. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
  388. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  389. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
  390. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
  391. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  392. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  393. package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
  394. package/src/runtime/routes/acp-routes.test.ts +89 -25
  395. package/src/runtime/routes/acp-routes.ts +81 -29
  396. package/src/runtime/routes/app-management-routes.ts +6 -117
  397. package/src/runtime/routes/app-routes.ts +13 -15
  398. package/src/runtime/routes/approval-routes.ts +1 -1
  399. package/src/runtime/routes/attachment-routes.ts +26 -15
  400. package/src/runtime/routes/avatar-routes.ts +26 -0
  401. package/src/runtime/routes/browser-routes.ts +1 -1
  402. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  403. package/src/runtime/routes/btw-routes.ts +29 -23
  404. package/src/runtime/routes/consolidation-routes.ts +120 -20
  405. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  406. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  407. package/src/runtime/routes/conversation-query-routes.ts +3 -1
  408. package/src/runtime/routes/conversation-routes.ts +372 -185
  409. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  410. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  411. package/src/runtime/routes/documents-routes.ts +4 -0
  412. package/src/runtime/routes/domain-routes.ts +51 -37
  413. package/src/runtime/routes/epoch-millis-range.ts +34 -0
  414. package/src/runtime/routes/events-routes.ts +28 -34
  415. package/src/runtime/routes/gateway-log-routes.ts +26 -4
  416. package/src/runtime/routes/heartbeat-routes.ts +32 -12
  417. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  418. package/src/runtime/routes/host-cu-routes.ts +1 -1
  419. package/src/runtime/routes/identity-intro-cache.ts +11 -34
  420. package/src/runtime/routes/identity-routes.ts +224 -18
  421. package/src/runtime/routes/image-generation-routes.ts +40 -2
  422. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  423. package/src/runtime/routes/index.ts +2 -0
  424. package/src/runtime/routes/integrations/a2a.ts +12 -10
  425. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
  426. package/src/runtime/routes/integrations/slack/channel.ts +4 -0
  427. package/src/runtime/routes/integrations/slack/share.ts +27 -6
  428. package/src/runtime/routes/integrations/telegram.ts +6 -0
  429. package/src/runtime/routes/integrations/twilio.ts +42 -0
  430. package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
  431. package/src/runtime/routes/log-export-routes.ts +8 -0
  432. package/src/runtime/routes/memory-v2-routes.ts +15 -8
  433. package/src/runtime/routes/memory-v3-routes.ts +66 -34
  434. package/src/runtime/routes/oauth-apps.ts +66 -12
  435. package/src/runtime/routes/oauth-providers.ts +44 -5
  436. package/src/runtime/routes/platform-routes.ts +81 -5
  437. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +6 -4
  438. package/src/runtime/routes/playground/force-compact.ts +1 -1
  439. package/src/runtime/routes/playground/helpers.ts +1 -1
  440. package/src/runtime/routes/rename-conversation-routes.ts +5 -0
  441. package/src/runtime/routes/schedule-routes.ts +152 -42
  442. package/src/runtime/routes/secret-routes.ts +14 -2
  443. package/src/runtime/routes/skills-routes.ts +43 -14
  444. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  445. package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
  446. package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
  447. package/src/runtime/routes/trust-rules-routes.ts +26 -2
  448. package/src/runtime/routes/tts-routes.ts +35 -0
  449. package/src/runtime/routes/types.ts +66 -8
  450. package/src/runtime/routes/usage-routes.ts +47 -39
  451. package/src/runtime/routes/webhook-routes.ts +41 -2
  452. package/src/runtime/routes/work-items-routes.ts +2 -4
  453. package/src/runtime/routes/workspace-routes.ts +4 -0
  454. package/src/runtime/services/__tests__/analyze-conversation.test.ts +6 -0
  455. package/src/runtime/services/analyze-conversation.ts +2 -2
  456. package/src/runtime/services/conversation-serializer.ts +1 -1
  457. package/src/schedule/schedule-store.ts +20 -1
  458. package/src/schedule/schedule-usage-store.ts +83 -0
  459. package/src/schedule/scheduler.ts +12 -5
  460. package/src/signals/cancel.ts +2 -4
  461. package/src/skills/catalog-files.ts +2 -2
  462. package/src/skills/catalog-install.ts +3 -0
  463. package/src/skills/categories-cache.ts +118 -0
  464. package/src/skills/clawhub-files.ts +1 -2
  465. package/src/skills/skillssh-files.ts +1 -2
  466. package/src/subagent/manager.ts +17 -5
  467. package/src/telemetry/types.ts +29 -1
  468. package/src/telemetry/usage-telemetry-reporter.test.ts +112 -3
  469. package/src/telemetry/usage-telemetry-reporter.ts +57 -2
  470. package/src/tools/acp/context.ts +20 -0
  471. package/src/tools/acp/list-agents.test.ts +7 -1
  472. package/src/tools/acp/spawn.test.ts +158 -55
  473. package/src/tools/acp/spawn.ts +47 -72
  474. package/src/tools/acp/steer.test.ts +105 -8
  475. package/src/tools/acp/steer.ts +48 -17
  476. package/src/tools/apps/executors.ts +13 -8
  477. package/src/tools/executor.ts +1 -53
  478. package/src/tools/filesystem/write.ts +34 -0
  479. package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
  480. package/src/tools/network/__tests__/web-search.test.ts +11 -3
  481. package/src/tools/network/web-search-error.test.ts +248 -0
  482. package/src/tools/network/web-search-error.ts +267 -0
  483. package/src/tools/network/web-search.ts +207 -48
  484. package/src/tools/schedule/create.ts +2 -0
  485. package/src/tools/subagent/spawn.ts +2 -4
  486. package/src/tools/terminal/safe-env.ts +10 -1
  487. package/src/tools/ui-surface/definitions.ts +34 -5
  488. package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
  489. package/src/tts/provider-catalog.ts +76 -1
  490. package/src/util/mutex.ts +47 -0
  491. package/src/workspace/git-service.ts +1 -42
  492. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  493. package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
  494. package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
  495. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +117 -0
  496. package/src/workspace/migrations/registry.ts +6 -0
  497. package/docs/plugins.md +0 -836
  498. package/examples/plugins/echo/register.ts +0 -184
  499. package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
  500. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  501. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  502. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  503. package/src/__tests__/empty-response-pipeline.test.ts +0 -423
  504. package/src/__tests__/llm-call-pipeline.test.ts +0 -287
  505. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
  506. package/src/__tests__/persistence-pipeline.test.ts +0 -503
  507. package/src/__tests__/pipeline-runner.test.ts +0 -564
  508. package/src/__tests__/title-generate-pipeline.test.ts +0 -211
  509. package/src/__tests__/token-estimate-pipeline.test.ts +0 -479
  510. package/src/__tests__/tool-error-pipeline.test.ts +0 -241
  511. package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
  512. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -341
  513. package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
  514. package/src/gallery/default-gallery.ts +0 -1359
  515. package/src/gallery/gallery-manifest.ts +0 -28
  516. package/src/home/feature-gate.ts +0 -22
  517. package/src/memory/v3/provider-blocks.ts +0 -16
  518. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  519. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  520. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  521. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  522. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  523. package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +0 -22
  524. package/src/plugins/defaults/empty-response/terminal.ts +0 -106
  525. package/src/plugins/defaults/injectors/package.json +0 -15
  526. package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +0 -17
  527. package/src/plugins/defaults/llm-call/register.ts +0 -45
  528. package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +0 -17
  529. package/src/plugins/defaults/memory-retrieval/package.json +0 -15
  530. package/src/plugins/defaults/memory-retrieval/register.ts +0 -181
  531. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  532. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  533. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  534. package/src/plugins/defaults/persistence/middlewares/persistence.ts +0 -19
  535. package/src/plugins/defaults/persistence/package.json +0 -15
  536. package/src/plugins/defaults/persistence/register.ts +0 -38
  537. package/src/plugins/defaults/persistence/terminal.ts +0 -83
  538. package/src/plugins/defaults/title-generate/terminal.ts +0 -31
  539. package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +0 -23
  540. package/src/plugins/defaults/token-estimate/package.json +0 -15
  541. package/src/plugins/defaults/token-estimate/register.ts +0 -34
  542. package/src/plugins/defaults/token-estimate/terminal.ts +0 -40
  543. package/src/plugins/defaults/tool-error/middlewares/toolError.ts +0 -21
  544. package/src/plugins/defaults/tool-error/terminal.ts +0 -47
  545. package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +0 -23
  546. package/src/plugins/defaults/tool-execute/package.json +0 -15
  547. package/src/plugins/defaults/tool-execute/register.ts +0 -49
  548. package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +0 -23
  549. package/src/plugins/defaults/tool-result-truncate/types.ts +0 -22
  550. package/src/skills/category-inference.ts +0 -111
  551. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
  552. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
  553. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  554. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  555. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
  556. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
  557. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
  558. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +0 -0
  559. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  560. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  561. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
  562. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  563. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  564. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  565. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  566. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  567. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  568. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
  569. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
  570. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
@@ -34,6 +34,7 @@ export type AcpClientFactory = (agent: Agent) => Client;
34
34
  export class AcpAgentProcess {
35
35
  private proc: ChildProcess | null = null;
36
36
  private connection: acp.ClientSideConnection | null = null;
37
+ private initializeResponse: InitializeResponse | null = null;
37
38
 
38
39
  constructor(
39
40
  public readonly agentId: string,
@@ -99,7 +100,7 @@ export class AcpAgentProcess {
99
100
 
100
101
  log.info({ agentId: this.agentId }, "Initializing ACP connection");
101
102
 
102
- return this.connection.initialize({
103
+ const response = await this.connection.initialize({
103
104
  protocolVersion: acp.PROTOCOL_VERSION,
104
105
  clientInfo: { name: "vellum", version: "1.0.0" },
105
106
  clientCapabilities: {
@@ -107,6 +108,28 @@ export class AcpAgentProcess {
107
108
  terminal: true,
108
109
  },
109
110
  });
111
+
112
+ this.initializeResponse = response;
113
+ return response;
114
+ }
115
+
116
+ /**
117
+ * Whether the agent advertised support for `session/load` at initialize.
118
+ * Returns false before initialize() resolves.
119
+ */
120
+ get supportsLoadSession(): boolean {
121
+ return this.initializeResponse?.agentCapabilities?.loadSession === true;
122
+ }
123
+
124
+ /**
125
+ * Whether the agent advertised support for `session/resume` at initialize.
126
+ * Returns false before initialize() resolves.
127
+ */
128
+ get supportsSessionResume(): boolean {
129
+ return (
130
+ this.initializeResponse?.agentCapabilities?.sessionCapabilities?.resume !=
131
+ null
132
+ );
110
133
  }
111
134
 
112
135
  /**
@@ -128,6 +151,41 @@ export class AcpAgentProcess {
128
151
  return result.sessionId;
129
152
  }
130
153
 
154
+ /**
155
+ * Loads a previously persisted ACP session via `session/load`.
156
+ *
157
+ * Per the ACP spec, the agent replays the session's conversation history
158
+ * as `session/update` notifications before the load response resolves;
159
+ * callers should suppress forwarding of those replayed updates (see
160
+ * VellumAcpClientHandler.beginReplaySuppression).
161
+ */
162
+ async loadSession(sessionId: string, cwd: string): Promise<void> {
163
+ if (!this.connection) {
164
+ throw new Error(`ACP agent "${this.agentId}" is not spawned`);
165
+ }
166
+
167
+ log.info({ agentId: this.agentId, sessionId, cwd }, "Loading ACP session");
168
+
169
+ await this.connection.loadSession({ sessionId, cwd, mcpServers: [] });
170
+ }
171
+
172
+ /**
173
+ * Resumes a previously persisted ACP session via `session/resume`.
174
+ *
175
+ * Unlike `session/load`, resume performs no history replay, so it is
176
+ * preferred when the agent advertises the capability
177
+ * (see supportsSessionResume).
178
+ */
179
+ async resumeSession(sessionId: string, cwd: string): Promise<void> {
180
+ if (!this.connection) {
181
+ throw new Error(`ACP agent "${this.agentId}" is not spawned`);
182
+ }
183
+
184
+ log.info({ agentId: this.agentId, sessionId, cwd }, "Resuming ACP session");
185
+
186
+ await this.connection.resumeSession({ sessionId, cwd, mcpServers: [] });
187
+ }
188
+
131
189
  /**
132
190
  * Sends a prompt to an existing ACP session.
133
191
  * Returns the prompt response (includes stopReason).
@@ -175,6 +233,7 @@ export class AcpAgentProcess {
175
233
  this.proc = null;
176
234
  }
177
235
  this.connection = null;
236
+ this.initializeResponse = null;
178
237
  }
179
238
 
180
239
  /**
@@ -205,5 +264,6 @@ export class AcpAgentProcess {
205
264
 
206
265
  this.proc = null;
207
266
  this.connection = null;
267
+ this.initializeResponse = null;
208
268
  }
209
269
  }
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Tests for the silent ACP adapter auto-installer.
3
+ *
4
+ * `execFile` is stubbed via the shared `installExecFileStub` helper: a
5
+ * process-global `mock.module("node:child_process", ...)` driven by per-call
6
+ * scripted responses keyed on `${command} ${args[0]}`. The
7
+ * `resolveAgentWithAutoInstall` ordering suite additionally stubs the ACP
8
+ * config and `Bun.which` so it can flip bun / adapter-binary presence.
9
+ */
10
+
11
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
12
+
13
+ import { installAcpConfigStub } from "./__tests__/helpers/acp-config-stub.js";
14
+ import { installExecFileStub } from "./__tests__/helpers/exec-file-stub.js";
15
+ import { installWhichStub } from "./__tests__/helpers/which-stub.js";
16
+
17
+ const { execScripts, execFileMock, reset } = installExecFileStub();
18
+ const config = await installAcpConfigStub();
19
+ const which = installWhichStub();
20
+
21
+ afterAll(() => {
22
+ which.restore();
23
+ });
24
+
25
+ // Spread the real module so other test files that load logger consumers
26
+ // (e.g. `truncateForLog` importers) after this process-global mock still
27
+ // resolve every named export.
28
+ const realLogger = await import("../util/logger.js");
29
+ mock.module("../util/logger.js", () => ({
30
+ ...realLogger,
31
+ getLogger: () =>
32
+ new Proxy({} as Record<string, unknown>, {
33
+ get: () => () => {},
34
+ }),
35
+ }));
36
+
37
+ const {
38
+ ensureAdapterInstalled,
39
+ resolveAgentWithAutoInstall,
40
+ _resetAdapterInstallCacheForTests,
41
+ } = await import("./auto-install.js");
42
+
43
+ beforeEach(() => {
44
+ reset();
45
+ _resetAdapterInstallCacheForTests();
46
+ config.setConfig({ agents: {} });
47
+ which.setWhich({});
48
+ });
49
+
50
+ describe("ensureAdapterInstalled", () => {
51
+ test("known command: runs `npm i -g <pkg>` and reports installed", async () => {
52
+ execScripts.set("npm i", { stdout: "" });
53
+
54
+ const result = await ensureAdapterInstalled("claude-agent-acp");
55
+
56
+ expect(result).toEqual({ installed: true });
57
+ expect(execFileMock).toHaveBeenCalledTimes(1);
58
+ const [command, args] = execFileMock.mock.calls[0];
59
+ expect(command).toBe("npm");
60
+ expect(args).toEqual([
61
+ "i",
62
+ "-g",
63
+ "@agentclientprotocol/claude-agent-acp",
64
+ ]);
65
+ });
66
+
67
+ test("unknown command: never invokes npm (security allowlist)", async () => {
68
+ const result = await ensureAdapterInstalled("some-arbitrary-binary");
69
+
70
+ expect(result.installed).toBe(false);
71
+ expect(result.error).toBeUndefined();
72
+ expect(execFileMock).not.toHaveBeenCalled();
73
+ });
74
+
75
+ test("npm failure: reports the error and does not install", async () => {
76
+ execScripts.set("npm i", {
77
+ error: new Error("EACCES: permission denied"),
78
+ });
79
+
80
+ const result = await ensureAdapterInstalled("codex-acp");
81
+
82
+ expect(result.installed).toBe(false);
83
+ expect(result.error).toContain("EACCES");
84
+ });
85
+
86
+ test("failed install is retried on the next call", async () => {
87
+ execScripts.set("npm i", { error: new Error("network down") });
88
+ const first = await ensureAdapterInstalled("claude-agent-acp");
89
+ expect(first.installed).toBe(false);
90
+
91
+ execScripts.set("npm i", { stdout: "" });
92
+ const second = await ensureAdapterInstalled("claude-agent-acp");
93
+ expect(second.installed).toBe(true);
94
+ expect(execFileMock).toHaveBeenCalledTimes(2);
95
+ });
96
+
97
+ test("successful install is cached for the process lifetime", async () => {
98
+ execScripts.set("npm i", { stdout: "" });
99
+
100
+ await ensureAdapterInstalled("claude-agent-acp");
101
+ await ensureAdapterInstalled("claude-agent-acp");
102
+
103
+ expect(execFileMock).toHaveBeenCalledTimes(1);
104
+ });
105
+
106
+ test("concurrent calls dedupe to exactly one `npm i -g`", async () => {
107
+ execScripts.set("npm i", { stdout: "" });
108
+
109
+ const [a, b, c] = await Promise.all([
110
+ ensureAdapterInstalled("claude-agent-acp"),
111
+ ensureAdapterInstalled("claude-agent-acp"),
112
+ ensureAdapterInstalled("claude-agent-acp"),
113
+ ]);
114
+
115
+ expect(a.installed).toBe(true);
116
+ expect(b.installed).toBe(true);
117
+ expect(c.installed).toBe(true);
118
+ expect(execFileMock).toHaveBeenCalledTimes(1);
119
+ });
120
+
121
+ test("different commands install independently", async () => {
122
+ execScripts.set("npm i", { stdout: "" });
123
+
124
+ const [claude, codex] = await Promise.all([
125
+ ensureAdapterInstalled("claude-agent-acp"),
126
+ ensureAdapterInstalled("codex-acp"),
127
+ ]);
128
+
129
+ expect(claude.installed).toBe(true);
130
+ expect(codex.installed).toBe(true);
131
+ expect(execFileMock).toHaveBeenCalledTimes(2);
132
+ const installedPackages = execFileMock.mock.calls.map(
133
+ (call) => (call[1] as string[])[2],
134
+ );
135
+ expect(installedPackages.sort()).toEqual([
136
+ "@agentclientprotocol/claude-agent-acp",
137
+ "@zed-industries/codex-acp",
138
+ ]);
139
+ });
140
+ });
141
+
142
+ describe("resolveAgentWithAutoInstall - resolution order", () => {
143
+ test("binary missing + bun present: resolves via bunx without invoking npm", async () => {
144
+ which.setWhich({ bun: "/usr/local/bin/bun" });
145
+
146
+ const result = await resolveAgentWithAutoInstall("claude");
147
+
148
+ expect(result.resolved.ok).toBe(true);
149
+ if (!result.resolved.ok) return;
150
+ expect(result.resolved.agent.command).toBe("bun");
151
+ expect(result.resolved.agent.adapterCommand).toBe("claude-agent-acp");
152
+ expect(result.autoInstalledPackage).toBeUndefined();
153
+ expect(result.failureMessage).toBeUndefined();
154
+ expect(execFileMock).not.toHaveBeenCalled();
155
+ });
156
+
157
+ test("binary + bun missing, npm install succeeds: falls back to the npm flow", async () => {
158
+ let installed = false;
159
+ which.setWhich((cmd) =>
160
+ installed && cmd === "claude-agent-acp"
161
+ ? "/usr/local/bin/claude-agent-acp"
162
+ : null,
163
+ );
164
+ execScripts.set("npm i", {
165
+ stdout: "",
166
+ onCall: () => {
167
+ installed = true;
168
+ },
169
+ });
170
+
171
+ const result = await resolveAgentWithAutoInstall("claude");
172
+
173
+ expect(result.resolved.ok).toBe(true);
174
+ if (!result.resolved.ok) return;
175
+ expect(result.resolved.agent.command).toBe("claude-agent-acp");
176
+ expect(result.autoInstalledPackage).toBe(
177
+ "@agentclientprotocol/claude-agent-acp",
178
+ );
179
+ expect(execFileMock).toHaveBeenCalledTimes(1);
180
+ });
181
+
182
+ test("binary, bun, and npm all missing: failure message carries the hint and install error", async () => {
183
+ execScripts.set("npm i", { error: new Error("spawn npm ENOENT") });
184
+
185
+ const result = await resolveAgentWithAutoInstall("claude");
186
+
187
+ expect(result.resolved.ok).toBe(false);
188
+ expect(result.failureMessage).toContain(
189
+ "claude-agent-acp is not on PATH",
190
+ );
191
+ expect(result.failureMessage).toContain(
192
+ "npm i -g @agentclientprotocol/claude-agent-acp",
193
+ );
194
+ expect(result.failureMessage).toContain("ENOENT");
195
+ });
196
+ });
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Silent auto-install for known ACP adapter binaries.
3
+ *
4
+ * Fallback path: the resolver (`resolve-agent.ts`) already rewrites missing
5
+ * allowlisted adapters to run via `bun x` when `bun` is on PATH, so
6
+ * `binary_not_found` only reaches this module when bun is absent (or the
7
+ * command has no package mapping). When a spawn does fail preflight with
8
+ * `binary_not_found`, callers (the `acp_spawn` tool and the `/v1/acp/spawn`
9
+ * route) call `resolveAgentWithAutoInstall(agentId)`, which tries a global
10
+ * npm install of the mapped adapter package, then re-resolves and continues.
11
+ * On failure they fall back to the existing actionable install hint.
12
+ *
13
+ * Security boundary: only commands present in `DEFAULT_AGENT_NPM_PACKAGES`
14
+ * are ever installed. The package names are vendored constants, NOT user
15
+ * input: an arbitrary command from user config must never be turned into
16
+ * an `npm i -g <attacker-controlled-name>` execution.
17
+ */
18
+
19
+ import { execFile } from "node:child_process";
20
+
21
+ import { DEFAULT_AGENT_NPM_PACKAGES } from "../config/acp-defaults.js";
22
+ import { getLogger } from "../util/logger.js";
23
+ import {
24
+ resolveAcpAgent,
25
+ type ResolveAcpAgentResult,
26
+ } from "./resolve-agent.js";
27
+
28
+ const log = getLogger("acp:auto-install");
29
+
30
+ /** Per-install timeout for `npm i -g`. Generous: cold npm caches are slow. */
31
+ const NPM_INSTALL_TIMEOUT_MS = 120_000;
32
+
33
+ export interface AdapterInstallResult {
34
+ installed: boolean;
35
+ error?: string;
36
+ }
37
+
38
+ /**
39
+ * Per-command install promises. Concurrent spawns for the same missing
40
+ * binary dedupe to a single `npm i -g`; successful results are cached for
41
+ * the process lifetime. Failed installs are evicted so a later spawn can
42
+ * retry (e.g. after the user fixes network or npm permissions).
43
+ */
44
+ const installPromises = new Map<string, Promise<AdapterInstallResult>>();
45
+
46
+ /**
47
+ * Run `execFile` with an AbortController-driven timeout. Returns the stdout
48
+ * on success; throws on error or timeout. Shared by the adapter installer
49
+ * here and the version probes in `tools/acp/spawn.ts`.
50
+ */
51
+ export function execFileWithTimeout(
52
+ command: string,
53
+ args: string[],
54
+ timeoutMs: number,
55
+ ): Promise<string> {
56
+ return new Promise((resolve, reject) => {
57
+ const controller = new AbortController();
58
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
59
+ execFile(
60
+ command,
61
+ args,
62
+ { signal: controller.signal, encoding: "utf8" },
63
+ (err, stdout) => {
64
+ clearTimeout(timer);
65
+ if (err) {
66
+ reject(err);
67
+ return;
68
+ }
69
+ resolve(stdout);
70
+ },
71
+ );
72
+ });
73
+ }
74
+
75
+ /**
76
+ * Install the npm package mapped to `command` if (and only if) the command
77
+ * is a known adapter binary. Unknown commands resolve to
78
+ * `{ installed: false }` without ever invoking npm (see the security
79
+ * boundary note in the module doc).
80
+ */
81
+ export function ensureAdapterInstalled(
82
+ command: string,
83
+ ): Promise<AdapterInstallResult> {
84
+ const packageName = DEFAULT_AGENT_NPM_PACKAGES[command];
85
+ if (!packageName) {
86
+ return Promise.resolve({ installed: false });
87
+ }
88
+
89
+ const inFlight = installPromises.get(command);
90
+ if (inFlight) return inFlight;
91
+
92
+ const promise = runInstall(command, packageName).then((result) => {
93
+ if (!result.installed) installPromises.delete(command);
94
+ return result;
95
+ });
96
+ installPromises.set(command, promise);
97
+ return promise;
98
+ }
99
+
100
+ async function runInstall(
101
+ command: string,
102
+ packageName: string,
103
+ ): Promise<AdapterInstallResult> {
104
+ log.info({ command, packageName }, "Auto-installing missing ACP adapter");
105
+ try {
106
+ await execFileWithTimeout(
107
+ "npm",
108
+ ["i", "-g", packageName],
109
+ NPM_INSTALL_TIMEOUT_MS,
110
+ );
111
+ log.info({ command, packageName }, "ACP adapter auto-install succeeded");
112
+ return { installed: true };
113
+ } catch (err) {
114
+ const error = err instanceof Error ? err.message : String(err);
115
+ log.warn(
116
+ { err, command, packageName },
117
+ "ACP adapter auto-install failed (falling back to install hint)",
118
+ );
119
+ return { installed: false, error };
120
+ }
121
+ }
122
+
123
+ export interface ResolveWithAutoInstallResult {
124
+ /** The final resolver outcome (post-install re-resolve when applicable). */
125
+ resolved: ResolveAcpAgentResult;
126
+ /** Set when a missing adapter binary was silently installed via npm. */
127
+ autoInstalledPackage?: string;
128
+ /**
129
+ * Set when the auto-install itself failed: the original install hint
130
+ * augmented with the npm failure reason. Callers should surface this
131
+ * instead of re-deriving a message from `resolved`.
132
+ */
133
+ failureMessage?: string;
134
+ }
135
+
136
+ /**
137
+ * Resolve an ACP agent id, silently auto-installing the mapped adapter
138
+ * package when (and only when) the failure is a missing allowlisted binary.
139
+ * Shared by the `acp_spawn` tool and the `/v1/acp/spawn` route so the
140
+ * resolve-install-re-resolve flow has a single implementation; callers map
141
+ * the result to their transport (tool error result vs. HTTP error class).
142
+ */
143
+ export async function resolveAgentWithAutoInstall(
144
+ agentId: string,
145
+ ): Promise<ResolveWithAutoInstallResult> {
146
+ const resolved = resolveAcpAgent(agentId);
147
+ if (resolved.ok || resolved.reason !== "binary_not_found") {
148
+ return { resolved };
149
+ }
150
+
151
+ const { command, hint } = resolved;
152
+ const install = await ensureAdapterInstalled(command);
153
+ if (install.installed) {
154
+ const retried = resolveAcpAgent(agentId);
155
+ if (retried.ok) {
156
+ log.info(
157
+ { agentId, command },
158
+ "Auto-installed missing ACP adapter binary",
159
+ );
160
+ return {
161
+ resolved: retried,
162
+ autoInstalledPackage: DEFAULT_AGENT_NPM_PACKAGES[command],
163
+ };
164
+ }
165
+ } else if (install.error) {
166
+ return {
167
+ resolved,
168
+ failureMessage: `${command} is not on PATH. ${hint} (auto-install failed: ${install.error})`,
169
+ };
170
+ }
171
+ return { resolved };
172
+ }
173
+
174
+ /** @internal: exposed for tests only. */
175
+ export function _resetAdapterInstallCacheForTests(): void {
176
+ installPromises.clear();
177
+ }
@@ -51,6 +51,7 @@ interface TerminalState {
51
51
  export class VellumAcpClientHandler implements Client {
52
52
  private terminals = new Map<string, TerminalState>();
53
53
  private accumulatedText = "";
54
+ private suppressForwarding = false;
54
55
  /** Tracks pending ACP permission requestIds for cleanup on session close. */
55
56
  readonly pendingRequestIds = new Set<string>();
56
57
 
@@ -65,8 +66,38 @@ export class VellumAcpClientHandler implements Client {
65
66
  private readonly parentConversationId: string,
66
67
  ) {}
67
68
 
69
+ /**
70
+ * Begins suppressing session updates from being forwarded to Vellum.
71
+ *
72
+ * Per the ACP spec, `session/load` replays the entire conversation history
73
+ * as `session/update` notifications before the load response resolves. The
74
+ * parent conversation already received those events during the original
75
+ * run, so re-forwarding them would duplicate them into the conversation and
76
+ * the ring buffer. Callers wrap `loadSession` in
77
+ * beginReplaySuppression()/endReplaySuppression() to drop the replay.
78
+ * `session/resume` performs no replay, which is why it is preferred when
79
+ * the agent supports it.
80
+ */
81
+ beginReplaySuppression(): void {
82
+ this.suppressForwarding = true;
83
+ }
84
+
85
+ /** Ends replay suppression; subsequent updates flow normally. */
86
+ endReplaySuppression(): void {
87
+ this.suppressForwarding = false;
88
+ }
89
+
68
90
  async sessionUpdate(params: SessionNotification): Promise<void> {
69
91
  const update = params.update;
92
+
93
+ if (this.suppressForwarding) {
94
+ log.debug(
95
+ { acpSessionId: this.acpSessionId, updateType: update.sessionUpdate },
96
+ "Dropping replayed session update during suppression",
97
+ );
98
+ return;
99
+ }
100
+
70
101
  log.debug(
71
102
  { acpSessionId: this.acpSessionId, updateType: update.sessionUpdate },
72
103
  "ACP session update received",
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Tests for the ACP feature gate.
3
+ *
4
+ * `isAcpEnabled` is an OR of the `acp` feature flag and the legacy
5
+ * `config.acp.enabled` field: either switch enables the subsystem, and
6
+ * neither implicitly flips the other.
7
+ */
8
+
9
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
10
+
11
+ import { setOverridesForTesting } from "../__tests__/feature-flag-test-helpers.js";
12
+ import type { AssistantConfig } from "../config/schema.js";
13
+ import { ACP_FLAG_KEY, isAcpEnabled } from "./feature-gate.js";
14
+
15
+ beforeEach(() => {
16
+ setOverridesForTesting({});
17
+ });
18
+
19
+ afterEach(() => {
20
+ setOverridesForTesting({});
21
+ });
22
+
23
+ /** Minimal AssistantConfig carrying only the `acp` section the gate reads. */
24
+ function makeConfig(acpEnabled: boolean): AssistantConfig {
25
+ return {
26
+ acp: { enabled: acpEnabled, maxConcurrentSessions: 4, agents: {} },
27
+ } as AssistantConfig;
28
+ }
29
+
30
+ describe("isAcpEnabled", () => {
31
+ test("returns false when both the flag and config.acp.enabled are off", () => {
32
+ expect(isAcpEnabled(makeConfig(false))).toBe(false);
33
+ });
34
+
35
+ test("returns true when config.acp.enabled is true and the flag is off", () => {
36
+ expect(isAcpEnabled(makeConfig(true))).toBe(true);
37
+ });
38
+
39
+ test("returns true when the flag is on and config.acp.enabled is false", () => {
40
+ setOverridesForTesting({ [ACP_FLAG_KEY]: true });
41
+ expect(isAcpEnabled(makeConfig(false))).toBe(true);
42
+ });
43
+
44
+ test("explicit flag-off override does not defeat config.acp.enabled", () => {
45
+ setOverridesForTesting({ [ACP_FLAG_KEY]: false });
46
+ expect(isAcpEnabled(makeConfig(true))).toBe(true);
47
+ });
48
+ });
@@ -0,0 +1,34 @@
1
+ /**
2
+ * ACP (Agent Client Protocol) feature gate.
3
+ *
4
+ * Single source of truth for whether the ACP subsystem is enabled. Modeled
5
+ * on `credential-execution/feature-gates.ts`: the flag key is declared in
6
+ * `meta/feature-flags/feature-flag-registry.json` and resolved through the
7
+ * unified feature-flag resolver.
8
+ *
9
+ * The gate is an OR of two independent switches:
10
+ * 1. `config.acp.enabled` — the original workspace config field. Preserved
11
+ * so existing workspaces with `acp.enabled: true` keep working without
12
+ * any migration.
13
+ * 2. The `acp` feature flag — adds a UI toggle. Flag state is persisted by
14
+ * the gateway and hot-refreshed in the daemon via the
15
+ * `feature_flags_changed` SSE event, so toggling it takes effect
16
+ * without a restart or config-file edit.
17
+ */
18
+
19
+ import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
20
+ import type { AssistantConfig } from "../config/schema.js";
21
+
22
+ /** Gate for the ACP coding-agent subsystem (must match the registry). */
23
+ export const ACP_FLAG_KEY = "acp" as const;
24
+
25
+ /**
26
+ * Whether ACP agent spawning/steering is enabled, via either the legacy
27
+ * `acp.enabled` config field or the `acp` feature flag (see module doc).
28
+ */
29
+ export function isAcpEnabled(config: AssistantConfig): boolean {
30
+ return (
31
+ config.acp?.enabled === true ||
32
+ isAssistantFeatureFlagEnabled(ACP_FLAG_KEY, config)
33
+ );
34
+ }