@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091926.ebb2d62

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 (490) hide show
  1. package/ARCHITECTURE.md +6 -6
  2. package/bun.lock +2 -2
  3. package/docs/activation-funnel-telemetry.md +310 -0
  4. package/examples/plugins/echo/README.md +61 -60
  5. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  6. package/examples/plugins/echo/hooks/stop.ts +16 -0
  7. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  8. package/examples/plugins/echo/package.json +1 -2
  9. package/examples/plugins/echo/src/emit.ts +19 -0
  10. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  11. package/openapi.yaml +273 -121
  12. package/package.json +2 -2
  13. package/scripts/sync-llm-catalog.ts +2 -0
  14. package/src/__tests__/activation-early-marking.test.ts +120 -0
  15. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  16. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  17. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  18. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  19. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  20. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  21. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  22. package/src/__tests__/agent-loop.test.ts +441 -96
  23. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  24. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  25. package/src/__tests__/anthropic-provider.test.ts +23 -8
  26. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  27. package/src/__tests__/app-control-flow.test.ts +1 -1
  28. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  29. package/src/__tests__/app-executors.test.ts +132 -0
  30. package/src/__tests__/approval-cascade.test.ts +6 -5
  31. package/src/__tests__/approval-routes-http.test.ts +4 -1
  32. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  33. package/src/__tests__/build-persisted-content.test.ts +75 -1
  34. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  35. package/src/__tests__/channel-approvals.test.ts +1 -1
  36. package/src/__tests__/compaction-circuit.test.ts +258 -0
  37. package/src/__tests__/compaction-direct.test.ts +146 -0
  38. package/src/__tests__/compaction-events.test.ts +7 -7
  39. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  40. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  41. package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
  42. package/src/__tests__/context-overflow-reducer.test.ts +265 -125
  43. package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
  44. package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
  45. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  46. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  47. package/src/__tests__/conversation-agent-loop-overflow.test.ts +158 -234
  48. package/src/__tests__/conversation-agent-loop.test.ts +342 -623
  49. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  50. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
  51. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  52. package/src/__tests__/conversation-error.test.ts +15 -1
  53. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  54. package/src/__tests__/conversation-media-retry.test.ts +1 -1
  55. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
  56. package/src/__tests__/conversation-process-callsite.test.ts +15 -15
  57. package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
  58. package/src/__tests__/conversation-queue.test.ts +10 -10
  59. package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
  60. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  61. package/src/__tests__/conversation-slash-queue.test.ts +7 -5
  62. package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
  63. package/src/__tests__/conversation-speed-override.test.ts +11 -10
  64. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  65. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
  66. package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
  67. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
  68. package/src/__tests__/conversation-undo.test.ts +2 -2
  69. package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
  70. package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
  71. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
  72. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  73. package/src/__tests__/corrected-target.test.ts +93 -0
  74. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -4
  75. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  76. package/src/__tests__/cu-unified-flow.test.ts +36 -0
  77. package/src/__tests__/db-acp-history.test.ts +101 -0
  78. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  79. package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
  80. package/src/__tests__/empty-response-hook.test.ts +1 -1
  81. package/src/__tests__/file-write-tool.test.ts +63 -0
  82. package/src/__tests__/gateway-only-guard.test.ts +17 -2
  83. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  84. package/src/__tests__/guardian-routing-invariants.test.ts +93 -4
  85. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  86. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  87. package/src/__tests__/heartbeat-service.test.ts +1 -0
  88. package/src/__tests__/history-repair-hook.test.ts +5 -1
  89. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  90. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  91. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  92. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  93. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  94. package/src/__tests__/injector-background-turn.test.ts +13 -23
  95. package/src/__tests__/injector-chain.test.ts +268 -44
  96. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  97. package/src/__tests__/injector-document-comments.test.ts +97 -114
  98. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  99. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  100. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  101. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  102. package/src/__tests__/llm-catalog-parity.test.ts +4 -0
  103. package/src/__tests__/llm-resolver.test.ts +73 -0
  104. package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
  105. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  106. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  107. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  108. package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
  109. package/src/__tests__/persist-unsendable-image.test.ts +97 -1
  110. package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
  111. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  112. package/src/__tests__/plugin-bootstrap.test.ts +91 -81
  113. package/src/__tests__/plugin-registry.test.ts +3 -49
  114. package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
  115. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  116. package/src/__tests__/plugin-types.test.ts +0 -70
  117. package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
  118. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  119. package/src/__tests__/published-app-updater.test.ts +138 -0
  120. package/src/__tests__/reaction-persistence.test.ts +1 -1
  121. package/src/__tests__/retry-thinking-adaptive-only.test.ts +245 -0
  122. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  123. package/src/__tests__/server-history-render.test.ts +39 -0
  124. package/src/__tests__/skill-feature-flags-integration.test.ts +31 -0
  125. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  126. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  127. package/src/__tests__/subagent-detail.test.ts +25 -7
  128. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  129. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  130. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  131. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  132. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  133. package/src/__tests__/title-generate-hook.test.ts +5 -1
  134. package/src/__tests__/tool-error-hook.test.ts +1 -1
  135. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  136. package/src/__tests__/tool-start-timestamp.test.ts +218 -0
  137. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  138. package/src/__tests__/web-fetch.test.ts +45 -0
  139. package/src/acp/__tests__/agent-process.test.ts +161 -0
  140. package/src/acp/__tests__/client-handler.test.ts +40 -0
  141. package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
  142. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  143. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  144. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  145. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  146. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  147. package/src/acp/agent-process.ts +61 -1
  148. package/src/acp/auto-install.test.ts +280 -0
  149. package/src/acp/auto-install.ts +232 -0
  150. package/src/acp/client-handler.ts +31 -0
  151. package/src/acp/prepare-agent-env.ts +80 -27
  152. package/src/acp/resolve-agent.test.ts +188 -28
  153. package/src/acp/resolve-agent.ts +119 -42
  154. package/src/acp/resume-hint.ts +23 -0
  155. package/src/acp/session-manager.ts +507 -73
  156. package/src/agent/compaction-circuit.ts +60 -102
  157. package/src/agent/loop.ts +403 -251
  158. package/src/api/events/tool-result.ts +9 -0
  159. package/src/api/events/tool-use-start.ts +7 -0
  160. package/src/api/responses/conversation-message.ts +72 -13
  161. package/src/api/responses/memory-v3-selection-log.ts +19 -10
  162. package/src/approvals/guardian-request-resolvers.ts +27 -1
  163. package/src/background-wake/next-wake.ts +1 -0
  164. package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
  165. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  166. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  167. package/src/cli/commands/memory-v3.ts +57 -199
  168. package/src/cli/commands/plugins.ts +43 -37
  169. package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
  170. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  171. package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
  172. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
  173. package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
  174. package/src/cli/lib/install-from-github.ts +797 -60
  175. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  176. package/src/cli/lib/plugin-details.ts +416 -0
  177. package/src/cli/lib/plugin-marketplace.ts +215 -0
  178. package/src/cli/lib/search-plugins.ts +201 -29
  179. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  180. package/src/config/acp-defaults.test.ts +10 -0
  181. package/src/config/acp-defaults.ts +9 -3
  182. package/src/config/acp-schema.ts +1 -7
  183. package/src/config/bundled-skills/acp/SKILL.md +80 -41
  184. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  185. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  186. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  187. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  188. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  189. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  190. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  191. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  192. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  193. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  194. package/src/config/bundled-tool-registry.ts +4 -0
  195. package/src/config/call-site-defaults.ts +0 -3
  196. package/src/config/feature-flag-registry.json +10 -56
  197. package/src/config/llm-resolver.ts +39 -7
  198. package/src/config/loader.ts +5 -22
  199. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
  200. package/src/config/schemas/call-site-catalog.ts +0 -21
  201. package/src/config/schemas/heartbeat.ts +9 -0
  202. package/src/config/schemas/llm.ts +17 -3
  203. package/src/config/schemas/memory-v3.ts +58 -8
  204. package/src/config/seed-inference-profiles.ts +18 -0
  205. package/src/context/compactor.ts +22 -4
  206. package/src/context/post-turn-tool-result-truncation.ts +39 -1
  207. package/src/context/strip-injections.ts +8 -2
  208. package/src/daemon/conversation-agent-loop-handlers.ts +120 -42
  209. package/src/daemon/conversation-agent-loop.ts +308 -1297
  210. package/src/daemon/conversation-error.ts +31 -4
  211. package/src/daemon/conversation-history.ts +1 -1
  212. package/src/daemon/conversation-lifecycle.ts +11 -255
  213. package/src/daemon/conversation-media-retry.ts +19 -6
  214. package/src/daemon/conversation-messaging.ts +17 -0
  215. package/src/daemon/conversation-process.ts +22 -141
  216. package/src/daemon/conversation-queue-manager.ts +8 -0
  217. package/src/daemon/conversation-registry.ts +159 -0
  218. package/src/daemon/conversation-runtime-assembly.ts +361 -393
  219. package/src/daemon/conversation-store.ts +9 -90
  220. package/src/daemon/conversation-surfaces.ts +165 -11
  221. package/src/daemon/conversation-workspace.ts +17 -0
  222. package/src/daemon/conversation.ts +444 -62
  223. package/src/daemon/disk-pressure-policy.ts +0 -1
  224. package/src/daemon/external-plugins-bootstrap.ts +36 -46
  225. package/src/daemon/handlers/config-channels.ts +11 -2
  226. package/src/daemon/handlers/conversations.ts +3 -1
  227. package/src/daemon/handlers/shared.ts +9 -1
  228. package/src/daemon/handlers/skills.ts +4 -1
  229. package/src/daemon/host-app-control-proxy.ts +72 -57
  230. package/src/daemon/host-proxy-preactivation.ts +1 -3
  231. package/src/daemon/lifecycle.ts +21 -7
  232. package/src/daemon/persist-unsendable-image.ts +62 -25
  233. package/src/daemon/process-message.ts +1 -1
  234. package/src/daemon/server.ts +2 -0
  235. package/src/daemon/tool-side-effects.ts +15 -0
  236. package/src/daemon/wake-conversation-ops.ts +269 -0
  237. package/src/embedded/plugin-api.ts +2 -2
  238. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  239. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  240. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  241. package/src/heartbeat/heartbeat-service.ts +26 -0
  242. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  243. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  244. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  245. package/src/ipc/skill-routes/memory.ts +4 -2
  246. package/src/memory/__tests__/activation-session-store.test.ts +41 -0
  247. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  248. package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
  249. package/src/memory/activation-session-store.ts +43 -0
  250. package/src/memory/conversation-crud.ts +29 -19
  251. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  252. package/src/memory/db-init.ts +6 -0
  253. package/src/memory/embedding-backend.ts +5 -22
  254. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  255. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  256. package/src/memory/jobs-worker.ts +25 -1
  257. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  258. package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
  259. package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
  260. package/src/memory/migrations/index.ts +3 -0
  261. package/src/memory/onboarding-events-store.ts +66 -18
  262. package/src/memory/schema/acp.ts +4 -0
  263. package/src/memory/schema/infrastructure.ts +13 -0
  264. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
  265. package/src/memory/v2/__tests__/page-store.test.ts +22 -0
  266. package/src/memory/v2/consolidation-job.ts +2 -63
  267. package/src/memory/v2/types.ts +5 -0
  268. package/src/messaging/providers/telegram-bot/api.ts +14 -5
  269. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  270. package/src/notifications/access-request-copy.ts +298 -0
  271. package/src/notifications/adapters/slack.ts +3 -3
  272. package/src/notifications/adapters/telegram.ts +9 -2
  273. package/src/notifications/copy-composer.ts +49 -267
  274. package/src/notifications/decision-engine.ts +16 -35
  275. package/src/notifications/home-feed-side-effect.ts +1 -6
  276. package/src/plugin-api/constants.ts +4 -0
  277. package/src/plugin-api/index.ts +40 -11
  278. package/src/plugin-api/types.ts +108 -0
  279. package/src/plugins/defaults/compaction/compact.ts +106 -0
  280. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
  281. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  282. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  283. package/src/plugins/defaults/compaction/package.json +1 -2
  284. package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
  285. package/src/plugins/defaults/empty-response/package.json +0 -1
  286. package/src/plugins/defaults/history-repair/package.json +0 -1
  287. package/src/plugins/defaults/index.ts +156 -26
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  289. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
  290. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  291. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  292. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  293. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
  297. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
  298. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
  299. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
  300. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
  301. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
  302. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
  303. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
  304. package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
  305. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
  306. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
  307. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
  308. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
  309. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
  310. package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
  311. package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
  312. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  313. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  314. package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
  315. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
  316. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
  317. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  318. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
  319. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
  320. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
  321. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
  322. package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
  323. package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
  324. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
  325. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
  326. package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
  327. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -1
  328. package/src/plugins/defaults/title-generate/package.json +0 -1
  329. package/src/plugins/defaults/tool-error/package.json +0 -1
  330. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  331. package/src/plugins/external-api.ts +12 -2
  332. package/src/plugins/pipeline.ts +6 -293
  333. package/src/plugins/registry.ts +9 -37
  334. package/src/plugins/types.ts +76 -381
  335. package/src/plugins/user-loader.ts +30 -127
  336. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  337. package/src/prompts/system-prompt.ts +61 -10
  338. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
  339. package/src/providers/anthropic/client.ts +9 -10
  340. package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
  341. package/src/providers/inference/logit-bias.ts +55 -0
  342. package/src/providers/model-catalog.ts +57 -0
  343. package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
  344. package/src/providers/openai/chat-completions-provider.ts +44 -1
  345. package/src/providers/retry.ts +57 -7
  346. package/src/providers/types.ts +6 -0
  347. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  348. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  349. package/src/runtime/agent-wake.ts +108 -209
  350. package/src/runtime/assistant-event-hub.ts +1 -1
  351. package/src/runtime/channel-approvals.ts +1 -1
  352. package/src/runtime/interactive-ui.ts +1 -1
  353. package/src/runtime/routes/__tests__/acp-routes.test.ts +466 -55
  354. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  355. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  356. package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
  357. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  358. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  359. package/src/runtime/routes/acp-routes.test.ts +185 -32
  360. package/src/runtime/routes/acp-routes.ts +328 -30
  361. package/src/runtime/routes/app-management-routes.ts +3 -0
  362. package/src/runtime/routes/approval-routes.ts +1 -1
  363. package/src/runtime/routes/browser-routes.ts +1 -1
  364. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  365. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  366. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  367. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  368. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  369. package/src/runtime/routes/conversation-routes.ts +30 -2
  370. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  371. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  372. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  373. package/src/runtime/routes/host-cu-routes.ts +1 -1
  374. package/src/runtime/routes/identity-routes.ts +18 -3
  375. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  376. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  377. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  378. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  379. package/src/runtime/routes/memory-v3-routes.ts +65 -305
  380. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
  381. package/src/runtime/routes/playground/helpers.ts +1 -1
  382. package/src/runtime/routes/plugins-routes.ts +337 -35
  383. package/src/runtime/routes/stt-routes.ts +45 -12
  384. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  385. package/src/runtime/routes/work-items-routes.ts +2 -4
  386. package/src/runtime/routes/workspace-routes.ts +50 -15
  387. package/src/runtime/services/conversation-serializer.ts +1 -1
  388. package/src/runtime/verification-outbound-actions.ts +147 -2
  389. package/src/runtime/verification-templates.ts +29 -3
  390. package/src/services/published-app-updater.ts +30 -8
  391. package/src/signals/cancel.ts +2 -4
  392. package/src/subagent/manager.ts +21 -5
  393. package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
  394. package/src/telemetry/activation-funnel.ts +167 -0
  395. package/src/telemetry/types.ts +13 -0
  396. package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
  397. package/src/telemetry/usage-telemetry-reporter.ts +26 -1
  398. package/src/tools/acp/context.ts +20 -0
  399. package/src/tools/acp/list-agents.test.ts +9 -19
  400. package/src/tools/acp/list-agents.ts +3 -15
  401. package/src/tools/acp/spawn.test.ts +173 -202
  402. package/src/tools/acp/spawn.ts +37 -172
  403. package/src/tools/acp/steer.test.ts +105 -8
  404. package/src/tools/acp/steer.ts +48 -17
  405. package/src/tools/apps/executors.ts +166 -50
  406. package/src/tools/browser/browser-execution.ts +12 -2
  407. package/src/tools/filesystem/write.ts +34 -0
  408. package/src/tools/network/web-fetch.ts +65 -24
  409. package/src/tools/skills/load.ts +1 -1
  410. package/src/tools/subagent/spawn.ts +2 -4
  411. package/src/tools/ui-surface/definitions.ts +32 -5
  412. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  413. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  414. package/docs/plugins.md +0 -832
  415. package/examples/plugins/echo/register.ts +0 -143
  416. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  417. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  418. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  419. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  420. package/src/__tests__/pipeline-runner.test.ts +0 -554
  421. package/src/daemon/wake-target-adapter.ts +0 -253
  422. package/src/memory/v3/__tests__/assign.test.ts +0 -242
  423. package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
  424. package/src/memory/v3/__tests__/core.test.ts +0 -39
  425. package/src/memory/v3/__tests__/health.test.ts +0 -219
  426. package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
  427. package/src/memory/v3/__tests__/needle.test.ts +0 -107
  428. package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
  429. package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
  430. package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
  431. package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
  432. package/src/memory/v3/__tests__/router.test.ts +0 -332
  433. package/src/memory/v3/__tests__/selector.test.ts +0 -470
  434. package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
  435. package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
  436. package/src/memory/v3/__tests__/tree.test.ts +0 -192
  437. package/src/memory/v3/assign.ts +0 -268
  438. package/src/memory/v3/capabilities.ts +0 -124
  439. package/src/memory/v3/core.ts +0 -26
  440. package/src/memory/v3/health.ts +0 -0
  441. package/src/memory/v3/maintain-job.ts +0 -314
  442. package/src/memory/v3/needle.ts +0 -115
  443. package/src/memory/v3/orchestrate.ts +0 -126
  444. package/src/memory/v3/page-content.ts +0 -34
  445. package/src/memory/v3/provider-blocks.ts +0 -26
  446. package/src/memory/v3/reconcile.ts +0 -523
  447. package/src/memory/v3/router.ts +0 -190
  448. package/src/memory/v3/selection-log-store.ts +0 -84
  449. package/src/memory/v3/selector.ts +0 -226
  450. package/src/memory/v3/shadow-plugin.ts +0 -411
  451. package/src/memory/v3/snapshot.ts +0 -209
  452. package/src/memory/v3/tree.ts +0 -174
  453. package/src/memory/v3/types.ts +0 -59
  454. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  455. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  456. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  457. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  458. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  459. package/src/plugins/defaults/compaction/register.ts +0 -35
  460. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  461. package/src/plugins/defaults/empty-response/register.ts +0 -23
  462. package/src/plugins/defaults/history-repair/register.ts +0 -24
  463. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
  464. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  465. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  466. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  467. package/src/plugins/defaults/title-generate/register.ts +0 -35
  468. package/src/plugins/defaults/tool-error/register.ts +0 -23
  469. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  470. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  471. package/src/proactive-artifact/decision.test.ts +0 -226
  472. package/src/proactive-artifact/decision.ts +0 -165
  473. package/src/proactive-artifact/index.ts +0 -7
  474. package/src/proactive-artifact/job.test.ts +0 -962
  475. package/src/proactive-artifact/job.ts +0 -372
  476. package/src/proactive-artifact/message-copy.ts +0 -58
  477. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  478. package/src/proactive-artifact/trigger-state.ts +0 -123
  479. package/src/util/map-limit.ts +0 -27
  480. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  481. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  482. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  483. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  484. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  485. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  486. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  487. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  488. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  489. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  490. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.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,280 @@
1
+ /**
2
+ * Tests for the sandboxed 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]}`. `Bun.which` is stubbed
7
+ * via `installWhichStub` so each test controls whether `bun` is on PATH and
8
+ * whether the adapter binary resolves after the install.
9
+ *
10
+ * The security-critical assertions live here: the installer must be `bun`
11
+ * (never `npm`), run in a fresh temp dir (NOT the task cwd), with the ACP
12
+ * secrets stripped from its env and the registry pinned to the public npm
13
+ * registry.
14
+ */
15
+
16
+ import { tmpdir } from "node:os";
17
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
18
+
19
+ import { installAcpConfigStub } from "./__tests__/helpers/acp-config-stub.js";
20
+ import { installExecFileStub } from "./__tests__/helpers/exec-file-stub.js";
21
+ import { installWhichStub } from "./__tests__/helpers/which-stub.js";
22
+
23
+ const { execScripts, execFileMock, reset } = installExecFileStub();
24
+ const config = await installAcpConfigStub();
25
+ const which = installWhichStub();
26
+
27
+ /** Fixed resolved `bun` path so script keys are predictable. */
28
+ const BUN_BIN = "/usr/local/bin/bun";
29
+ /** Key the exec stub uses for the global install. */
30
+ const BUN_ADD_KEY = `${BUN_BIN} add`;
31
+
32
+ afterAll(() => {
33
+ which.restore();
34
+ });
35
+
36
+ // Spread the real module so other test files that load logger consumers
37
+ // (e.g. `truncateForLog` importers) after this process-global mock still
38
+ // resolve every named export.
39
+ const realLogger = await import("../util/logger.js");
40
+ mock.module("../util/logger.js", () => ({
41
+ ...realLogger,
42
+ getLogger: () =>
43
+ new Proxy({} as Record<string, unknown>, {
44
+ get: () => () => {},
45
+ }),
46
+ }));
47
+
48
+ const {
49
+ ensureAdapterInstalled,
50
+ resolveAgentWithAutoInstall,
51
+ _resetAdapterInstallCacheForTests,
52
+ } = await import("./auto-install.js");
53
+
54
+ /** Latest call's execFile options ({ cwd, env, ... }). */
55
+ function lastInstallOptions(): { cwd?: string; env?: NodeJS.ProcessEnv } {
56
+ const call = execFileMock.mock.calls.at(-1)!;
57
+ return call[2] as { cwd?: string; env?: NodeJS.ProcessEnv };
58
+ }
59
+
60
+ beforeEach(() => {
61
+ reset();
62
+ _resetAdapterInstallCacheForTests();
63
+ config.setConfig({ agents: {} });
64
+ // Default: bun on PATH, nothing else.
65
+ which.setWhich({ bun: BUN_BIN });
66
+ });
67
+
68
+ describe("ensureAdapterInstalled", () => {
69
+ test("known command: runs `bun add --global <pkg>` and reports installed", async () => {
70
+ execScripts.set(BUN_ADD_KEY, { stdout: "" });
71
+
72
+ const result = await ensureAdapterInstalled("claude-agent-acp");
73
+
74
+ expect(result).toEqual({ installed: true });
75
+ expect(execFileMock).toHaveBeenCalledTimes(1);
76
+ const [command, args] = execFileMock.mock.calls[0];
77
+ expect(command).toBe(BUN_BIN);
78
+ expect(args).toEqual([
79
+ "add",
80
+ "--global",
81
+ "@agentclientprotocol/claude-agent-acp",
82
+ ]);
83
+ });
84
+
85
+ test("installer never invokes npm", async () => {
86
+ execScripts.set(BUN_ADD_KEY, { stdout: "" });
87
+
88
+ await ensureAdapterInstalled("claude-agent-acp");
89
+
90
+ for (const call of execFileMock.mock.calls) {
91
+ expect(call[0]).not.toBe("npm");
92
+ }
93
+ });
94
+
95
+ test("runs in a fresh temp dir (NOT the task cwd), token-free env, public registry", async () => {
96
+ // Seed the secrets on the ambient env so we can assert they are stripped.
97
+ process.env.CLAUDE_CODE_OAUTH_TOKEN = "should-not-leak";
98
+ process.env.GEMINI_API_KEY = "should-not-leak-either";
99
+ execScripts.set(BUN_ADD_KEY, { stdout: "" });
100
+
101
+ try {
102
+ await ensureAdapterInstalled("claude-agent-acp");
103
+ } finally {
104
+ delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
105
+ delete process.env.GEMINI_API_KEY;
106
+ }
107
+
108
+ const { cwd, env } = lastInstallOptions();
109
+ // Sandboxed cwd: a temp dir under the OS temp root, never the process cwd.
110
+ expect(cwd).toBeDefined();
111
+ expect(cwd!.startsWith(tmpdir())).toBe(true);
112
+ expect(cwd).not.toBe(process.cwd());
113
+ expect(cwd).toContain("vellum-acp-install-");
114
+
115
+ // Secrets stripped, registry pinned.
116
+ expect(env).toBeDefined();
117
+ expect(env!.CLAUDE_CODE_OAUTH_TOKEN).toBeUndefined();
118
+ expect(env!.GEMINI_API_KEY).toBeUndefined();
119
+ expect(env!.BUN_CONFIG_REGISTRY).toBe("https://registry.npmjs.org/");
120
+ });
121
+
122
+ test("unknown command: never invokes a package manager (security allowlist)", async () => {
123
+ const result = await ensureAdapterInstalled("some-arbitrary-binary");
124
+
125
+ expect(result.installed).toBe(false);
126
+ expect(result.error).toBeUndefined();
127
+ expect(execFileMock).not.toHaveBeenCalled();
128
+ });
129
+
130
+ test("bun absent: no install attempted, reports not installed", async () => {
131
+ which.setWhich({}); // bun not on PATH
132
+
133
+ const result = await ensureAdapterInstalled("claude-agent-acp");
134
+
135
+ expect(result.installed).toBe(false);
136
+ expect(result.error).toBeUndefined();
137
+ expect(execFileMock).not.toHaveBeenCalled();
138
+ });
139
+
140
+ test("install failure: reports the error and does not install", async () => {
141
+ execScripts.set(BUN_ADD_KEY, {
142
+ error: new Error("EACCES: permission denied"),
143
+ });
144
+
145
+ const result = await ensureAdapterInstalled("codex-acp");
146
+
147
+ expect(result.installed).toBe(false);
148
+ expect(result.error).toContain("EACCES");
149
+ });
150
+
151
+ test("failed install is retried on the next call", async () => {
152
+ execScripts.set(BUN_ADD_KEY, { error: new Error("network down") });
153
+ const first = await ensureAdapterInstalled("claude-agent-acp");
154
+ expect(first.installed).toBe(false);
155
+
156
+ execScripts.set(BUN_ADD_KEY, { stdout: "" });
157
+ const second = await ensureAdapterInstalled("claude-agent-acp");
158
+ expect(second.installed).toBe(true);
159
+ expect(execFileMock).toHaveBeenCalledTimes(2);
160
+ });
161
+
162
+ test("successful install is cached for the process lifetime", async () => {
163
+ execScripts.set(BUN_ADD_KEY, { stdout: "" });
164
+
165
+ await ensureAdapterInstalled("claude-agent-acp");
166
+ await ensureAdapterInstalled("claude-agent-acp");
167
+
168
+ expect(execFileMock).toHaveBeenCalledTimes(1);
169
+ });
170
+
171
+ test("concurrent calls dedupe to exactly one install", async () => {
172
+ execScripts.set(BUN_ADD_KEY, { stdout: "" });
173
+
174
+ const [a, b, c] = await Promise.all([
175
+ ensureAdapterInstalled("claude-agent-acp"),
176
+ ensureAdapterInstalled("claude-agent-acp"),
177
+ ensureAdapterInstalled("claude-agent-acp"),
178
+ ]);
179
+
180
+ expect(a.installed).toBe(true);
181
+ expect(b.installed).toBe(true);
182
+ expect(c.installed).toBe(true);
183
+ expect(execFileMock).toHaveBeenCalledTimes(1);
184
+ });
185
+
186
+ test("different commands install independently", async () => {
187
+ execScripts.set(BUN_ADD_KEY, { stdout: "" });
188
+
189
+ const [claude, codex] = await Promise.all([
190
+ ensureAdapterInstalled("claude-agent-acp"),
191
+ ensureAdapterInstalled("codex-acp"),
192
+ ]);
193
+
194
+ expect(claude.installed).toBe(true);
195
+ expect(codex.installed).toBe(true);
196
+ expect(execFileMock).toHaveBeenCalledTimes(2);
197
+ const installedPackages = execFileMock.mock.calls.map(
198
+ (call) => (call[1] as string[])[2],
199
+ );
200
+ expect(installedPackages.sort()).toEqual([
201
+ "@agentclientprotocol/claude-agent-acp",
202
+ "@zed-industries/codex-acp",
203
+ ]);
204
+ });
205
+ });
206
+
207
+ describe("resolveAgentWithAutoInstall - resolution order", () => {
208
+ test("binary missing + bun present: installs then resolves to the real binary", async () => {
209
+ let installed = false;
210
+ which.setWhich((cmd) => {
211
+ if (cmd === "bun") return BUN_BIN;
212
+ if (cmd === "claude-agent-acp" && installed) {
213
+ return "/usr/local/bin/claude-agent-acp";
214
+ }
215
+ return null;
216
+ });
217
+ execScripts.set(BUN_ADD_KEY, {
218
+ stdout: "",
219
+ onCall: () => {
220
+ installed = true;
221
+ },
222
+ });
223
+
224
+ const result = await resolveAgentWithAutoInstall("claude");
225
+
226
+ expect(result.resolved.ok).toBe(true);
227
+ if (!result.resolved.ok) return;
228
+ // The resolved command is the REAL binary, not a `bun x` wrapper.
229
+ expect(result.resolved.agent.command).toBe("claude-agent-acp");
230
+ expect(result.autoInstalledPackage).toBe(
231
+ "@agentclientprotocol/claude-agent-acp",
232
+ );
233
+ expect(result.failureMessage).toBeUndefined();
234
+ expect(execFileMock).toHaveBeenCalledTimes(1);
235
+ expect(execFileMock.mock.calls[0][0]).toBe(BUN_BIN);
236
+ });
237
+
238
+ test("binary missing + bun absent: no install, plain failure with the hint", async () => {
239
+ which.setWhich({}); // neither bun nor the adapter on PATH
240
+
241
+ const result = await resolveAgentWithAutoInstall("claude");
242
+
243
+ expect(result.resolved.ok).toBe(false);
244
+ expect(result.autoInstalledPackage).toBeUndefined();
245
+ // No augmented failure message (the plain binary_not_found hint is
246
+ // surfaced by the caller via formatResolveFailure).
247
+ expect(result.failureMessage).toBeUndefined();
248
+ expect(execFileMock).not.toHaveBeenCalled();
249
+ });
250
+
251
+ test("install fails: failure message carries the hint and install error, never npm", async () => {
252
+ which.setWhich({ bun: BUN_BIN });
253
+ execScripts.set(BUN_ADD_KEY, { error: new Error("network is down") });
254
+
255
+ const result = await resolveAgentWithAutoInstall("claude");
256
+
257
+ expect(result.resolved.ok).toBe(false);
258
+ expect(result.failureMessage).toContain("claude-agent-acp is not on PATH");
259
+ expect(result.failureMessage).toContain(
260
+ "bun add -g @agentclientprotocol/claude-agent-acp",
261
+ );
262
+ expect(result.failureMessage).toContain("network is down");
263
+ for (const call of execFileMock.mock.calls) {
264
+ expect(call[0]).not.toBe("npm");
265
+ }
266
+ });
267
+
268
+ test("codex unaffected: already on PATH resolves with no install", async () => {
269
+ which.setWhich((cmd) =>
270
+ cmd === "codex-acp" ? "/usr/local/bin/codex-acp" : null,
271
+ );
272
+
273
+ const result = await resolveAgentWithAutoInstall("codex");
274
+
275
+ expect(result.resolved.ok).toBe(true);
276
+ if (!result.resolved.ok) return;
277
+ expect(result.resolved.agent.command).toBe("codex-acp");
278
+ expect(execFileMock).not.toHaveBeenCalled();
279
+ });
280
+ });
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Sandboxed auto-install for known ACP adapter binaries.
3
+ *
4
+ * When a spawn fails preflight with `binary_not_found`, callers (the
5
+ * `acp_spawn` tool and the `/v1/acp/spawn` route) call
6
+ * `resolveAgentWithAutoInstall(agentId)`, which performs a ONE-TIME global
7
+ * install of the mapped adapter package via `bun`, then re-resolves and
8
+ * continues. After the install the adapter is a normal trusted binary on
9
+ * PATH, and the session manager spawns it the usual way (project cwd, token
10
+ * injected only at spawn).
11
+ *
12
+ * Security boundaries (this is the ATL-808 fix):
13
+ * - Only commands present in `DEFAULT_AGENT_NPM_PACKAGES` are ever
14
+ * installed. The package names are vendored constants, NOT user input.
15
+ * - The install runs `bun` with cwd = a FRESH empty daemon-owned temp dir,
16
+ * never the untrusted task project dir. A clean cwd has no project-local
17
+ * `node_modules/.bin`, `bunfig.toml`, or `.npmrc`, so none of the
18
+ * cwd-based package-resolution hijacks apply.
19
+ * - The installer env is a SANITIZED copy of `process.env` with
20
+ * `CLAUDE_CODE_OAUTH_TOKEN` and `GEMINI_API_KEY` stripped, so the secret
21
+ * is never in scope during package resolution, and `BUN_CONFIG_REGISTRY`
22
+ * forced to the public npm registry so a redirected registry cannot serve
23
+ * a malicious package.
24
+ * - The token is injected ONLY later, at spawn time, on the real installed
25
+ * binary (see `prepare-agent-env.ts`). `prepareAgentEnv` is never called
26
+ * here.
27
+ */
28
+
29
+ import { execFile } from "node:child_process";
30
+ import { mkdtemp, rm } from "node:fs/promises";
31
+ import { tmpdir } from "node:os";
32
+ import { join } from "node:path";
33
+
34
+ import { DEFAULT_AGENT_NPM_PACKAGES } from "../config/acp-defaults.js";
35
+ import { getLogger } from "../util/logger.js";
36
+ import {
37
+ resolveAcpAgent,
38
+ type ResolveAcpAgentResult,
39
+ } from "./resolve-agent.js";
40
+
41
+ const log = getLogger("acp:auto-install");
42
+
43
+ /** Per-install timeout for the global install. Generous: cold caches are slow. */
44
+ const BUN_INSTALL_TIMEOUT_MS = 120_000;
45
+
46
+ /**
47
+ * The trusted public npm registry. Forced via `BUN_CONFIG_REGISTRY` on the
48
+ * installer env so a stray `.npmrc`/`bunfig.toml` in the ambient environment
49
+ * cannot redirect package downloads to an attacker-controlled registry.
50
+ */
51
+ const PUBLIC_NPM_REGISTRY = "https://registry.npmjs.org/";
52
+
53
+ export interface AdapterInstallResult {
54
+ installed: boolean;
55
+ error?: string;
56
+ }
57
+
58
+ /**
59
+ * Per-command install promises. Concurrent spawns for the same missing
60
+ * binary dedupe to a single global install; successful results are cached for
61
+ * the process lifetime. Failed installs are evicted so a later spawn can
62
+ * retry (e.g. after the user fixes network or install permissions).
63
+ */
64
+ const installPromises = new Map<string, Promise<AdapterInstallResult>>();
65
+
66
+ /**
67
+ * Run `execFile` with an AbortController-driven timeout. Returns the stdout
68
+ * on success; throws on error or timeout. The optional `cwd`/`env` let the
69
+ * installer run in a sandboxed working directory with a sanitized env.
70
+ */
71
+ function execFileWithTimeout(
72
+ command: string,
73
+ args: string[],
74
+ timeoutMs: number,
75
+ options?: { cwd?: string; env?: NodeJS.ProcessEnv },
76
+ ): Promise<string> {
77
+ return new Promise((resolve, reject) => {
78
+ const controller = new AbortController();
79
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
80
+ execFile(
81
+ command,
82
+ args,
83
+ {
84
+ signal: controller.signal,
85
+ encoding: "utf8",
86
+ cwd: options?.cwd,
87
+ env: options?.env,
88
+ },
89
+ (err, stdout) => {
90
+ clearTimeout(timer);
91
+ if (err) {
92
+ reject(err);
93
+ return;
94
+ }
95
+ resolve(stdout);
96
+ },
97
+ );
98
+ });
99
+ }
100
+
101
+ /**
102
+ * A copy of `process.env` safe to hand to the package installer: the ACP
103
+ * secrets are stripped so they can never leak into a resolved package's
104
+ * lifecycle/runtime, and the registry is pinned to the trusted public one.
105
+ */
106
+ function sanitizedInstallEnv(): NodeJS.ProcessEnv {
107
+ const env: NodeJS.ProcessEnv = { ...process.env };
108
+ delete env.CLAUDE_CODE_OAUTH_TOKEN;
109
+ delete env.GEMINI_API_KEY;
110
+ env.BUN_CONFIG_REGISTRY = PUBLIC_NPM_REGISTRY;
111
+ return env;
112
+ }
113
+
114
+ /**
115
+ * Install the npm-registry package mapped to `command` via a sandboxed `bun`
116
+ * global install, if (and only if) the command is a known adapter binary and
117
+ * `bun` is on PATH. Unknown commands, or hosts without `bun`, resolve to
118
+ * `{ installed: false }` without ever invoking a package manager (see the
119
+ * security boundary note in the module doc).
120
+ */
121
+ export function ensureAdapterInstalled(
122
+ command: string,
123
+ ): Promise<AdapterInstallResult> {
124
+ const packageName = DEFAULT_AGENT_NPM_PACKAGES[command];
125
+ if (!packageName) {
126
+ return Promise.resolve({ installed: false });
127
+ }
128
+
129
+ const bunPath = Bun.which("bun");
130
+ if (!bunPath) {
131
+ return Promise.resolve({ installed: false });
132
+ }
133
+
134
+ const inFlight = installPromises.get(command);
135
+ if (inFlight) return inFlight;
136
+
137
+ const promise = runInstall(bunPath, command, packageName).then((result) => {
138
+ if (!result.installed) installPromises.delete(command);
139
+ return result;
140
+ });
141
+ installPromises.set(command, promise);
142
+ return promise;
143
+ }
144
+
145
+ async function runInstall(
146
+ bunPath: string,
147
+ command: string,
148
+ packageName: string,
149
+ ): Promise<AdapterInstallResult> {
150
+ log.info({ command, packageName }, "Auto-installing missing ACP adapter");
151
+ // Fresh empty dir guaranteed to have no project-local node_modules,
152
+ // bunfig.toml, or .npmrc - this neutralizes the cwd-based resolution
153
+ // hijacks the untrusted task dir would otherwise enable.
154
+ const installDir = await mkdtemp(join(tmpdir(), "vellum-acp-install-"));
155
+ try {
156
+ await execFileWithTimeout(
157
+ bunPath,
158
+ // `bun add --global` installs AND links the package bin into bun's
159
+ // global bin dir (on PATH in every image).
160
+ ["add", "--global", packageName],
161
+ BUN_INSTALL_TIMEOUT_MS,
162
+ { cwd: installDir, env: sanitizedInstallEnv() },
163
+ );
164
+ log.info({ command, packageName }, "ACP adapter auto-install succeeded");
165
+ return { installed: true };
166
+ } catch (err) {
167
+ const error = err instanceof Error ? err.message : String(err);
168
+ log.warn(
169
+ { err, command, packageName },
170
+ "ACP adapter auto-install failed (falling back to install hint)",
171
+ );
172
+ return { installed: false, error };
173
+ } finally {
174
+ await rm(installDir, { recursive: true, force: true }).catch(() => {});
175
+ }
176
+ }
177
+
178
+ export interface ResolveWithAutoInstallResult {
179
+ /** The final resolver outcome (post-install re-resolve when applicable). */
180
+ resolved: ResolveAcpAgentResult;
181
+ /** Set when a missing adapter binary was silently installed via bun. */
182
+ autoInstalledPackage?: string;
183
+ /**
184
+ * Set when the auto-install itself failed: the original install hint
185
+ * augmented with the install failure reason. Callers should surface this
186
+ * instead of re-deriving a message from `resolved`.
187
+ */
188
+ failureMessage?: string;
189
+ }
190
+
191
+ /**
192
+ * Resolve an ACP agent id, silently auto-installing the mapped adapter
193
+ * package when (and only when) the failure is a missing allowlisted binary.
194
+ * Shared by the `acp_spawn` tool and the `/v1/acp/spawn` route so the
195
+ * resolve-install-re-resolve flow has a single implementation; callers map
196
+ * the result to their transport (tool error result vs. HTTP error class).
197
+ */
198
+ export async function resolveAgentWithAutoInstall(
199
+ agentId: string,
200
+ ): Promise<ResolveWithAutoInstallResult> {
201
+ const resolved = resolveAcpAgent(agentId);
202
+ if (resolved.ok || resolved.reason !== "binary_not_found") {
203
+ return { resolved };
204
+ }
205
+
206
+ const { command, hint } = resolved;
207
+ const install = await ensureAdapterInstalled(command);
208
+ if (install.installed) {
209
+ const retried = resolveAcpAgent(agentId);
210
+ if (retried.ok) {
211
+ log.info(
212
+ { agentId, command },
213
+ "Auto-installed missing ACP adapter binary",
214
+ );
215
+ return {
216
+ resolved: retried,
217
+ autoInstalledPackage: DEFAULT_AGENT_NPM_PACKAGES[command],
218
+ };
219
+ }
220
+ } else if (install.error) {
221
+ return {
222
+ resolved,
223
+ failureMessage: `${command} is not on PATH. ${hint} (auto-install failed: ${install.error})`,
224
+ };
225
+ }
226
+ return { resolved };
227
+ }
228
+
229
+ /** @internal: exposed for tests only. */
230
+ export function _resetAdapterInstallCacheForTests(): void {
231
+ installPromises.clear();
232
+ }
@@ -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",