@vellumai/assistant 0.8.4 → 0.8.5

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 (438) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/docs/browser-use-architecture-phase2.md +1 -1
  3. package/knip.json +2 -1
  4. package/openapi.yaml +809 -11
  5. package/package.json +1 -1
  6. package/src/__tests__/anthropic-provider.test.ts +34 -37
  7. package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +293 -0
  8. package/src/__tests__/assistant-feature-flags-integration.test.ts +3 -3
  9. package/src/__tests__/audit-log-rotation.test.ts +70 -16
  10. package/src/__tests__/background-workers-disk-pressure.test.ts +3 -3
  11. package/src/__tests__/btw-routes.test.ts +2 -3
  12. package/src/__tests__/call-controller.test.ts +0 -1
  13. package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
  14. package/src/__tests__/channel-guardian.test.ts +3 -3
  15. package/src/__tests__/checker.test.ts +6 -15
  16. package/src/__tests__/compaction-events.test.ts +1 -0
  17. package/src/__tests__/compactor-call-site-logging.test.ts +214 -0
  18. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +5 -11
  19. package/src/__tests__/computer-use-tools.test.ts +2 -4
  20. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  21. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -1
  22. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
  23. package/src/__tests__/conversation-agent-loop-overflow.test.ts +197 -2
  24. package/src/__tests__/conversation-agent-loop.test.ts +163 -122
  25. package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -5
  26. package/src/__tests__/conversation-clear-safety.test.ts +25 -25
  27. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +1 -1
  28. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
  29. package/src/__tests__/conversation-error.test.ts +31 -0
  30. package/src/__tests__/conversation-fork-crud.test.ts +178 -15
  31. package/src/__tests__/conversation-lifecycle.test.ts +52 -11
  32. package/src/__tests__/{conversation-load-cleaned-at.test.ts → conversation-load-history-stripped.test.ts} +13 -13
  33. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
  34. package/src/__tests__/conversation-routes-disk-view.test.ts +109 -0
  35. package/src/__tests__/conversation-routes-slash-commands.test.ts +35 -0
  36. package/src/__tests__/conversation-skill-tools.test.ts +2 -5
  37. package/src/__tests__/conversation-store.test.ts +1 -1
  38. package/src/__tests__/conversation-sync-tags.test.ts +99 -32
  39. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  40. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  41. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  42. package/src/__tests__/credential-execution-feature-gates.test.ts +9 -7
  43. package/src/__tests__/credential-execution-tools.test.ts +6 -6
  44. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  45. package/src/__tests__/credential-vault-unit.test.ts +2 -2
  46. package/src/__tests__/dynamic-page-surface.test.ts +2 -2
  47. package/src/__tests__/email-html-renderer.test.ts +12 -0
  48. package/src/__tests__/gateway-flag-listener.test.ts +237 -0
  49. package/src/__tests__/gemini-provider.test.ts +78 -0
  50. package/src/__tests__/guardian-dispatch.test.ts +0 -1
  51. package/src/__tests__/guardian-outbound-http.test.ts +7 -5
  52. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  53. package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
  54. package/src/__tests__/heartbeat-service.test.ts +4 -0
  55. package/src/__tests__/host-shell-tool.test.ts +1 -1
  56. package/src/__tests__/init-feature-flag-overrides.test.ts +5 -6
  57. package/src/__tests__/list-messages-tool-merge.test.ts +70 -11
  58. package/src/__tests__/llm-request-log-call-site.test.ts +136 -0
  59. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +26 -0
  60. package/src/__tests__/llm-resolver.test.ts +77 -9
  61. package/src/__tests__/llm-usage-store.test.ts +66 -0
  62. package/src/__tests__/logger.test.ts +89 -0
  63. package/src/__tests__/mcp-abort-signal.test.ts +2 -2
  64. package/src/__tests__/media-generate-image.test.ts +31 -0
  65. package/src/__tests__/memory-v2-static-injector.test.ts +7 -7
  66. package/src/__tests__/model-intents.test.ts +2 -4
  67. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  68. package/src/__tests__/onboarding-template-contract.test.ts +1 -1
  69. package/src/__tests__/openai-provider.test.ts +46 -0
  70. package/src/__tests__/openai-responses-provider.test.ts +114 -12
  71. package/src/__tests__/pending-interactions-resolved-event.test.ts +0 -1
  72. package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
  73. package/src/__tests__/platform.test.ts +2 -2
  74. package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
  75. package/src/__tests__/plugin-bootstrap.test.ts +2 -2
  76. package/src/__tests__/plugin-tool-contribution.test.ts +13 -6
  77. package/src/__tests__/plugin-types.test.ts +3 -2
  78. package/src/__tests__/prechat-onboarding-contract.test.ts +131 -98
  79. package/src/__tests__/pricing.test.ts +12 -0
  80. package/src/__tests__/prune-jobs-changes-parser.test.ts +61 -0
  81. package/src/__tests__/registry.test.ts +2 -8
  82. package/src/__tests__/require-fresh-approval.test.ts +2 -2
  83. package/src/__tests__/runtime-events-sse-bilingual.test.ts +154 -0
  84. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
  85. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  86. package/src/__tests__/skill-projection-feature-flag.test.ts +4 -7
  87. package/src/__tests__/skill-projection.benchmark.test.ts +2 -6
  88. package/src/__tests__/skill-tool-factory.test.ts +1 -1
  89. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  90. package/src/__tests__/suggestion-routes.test.ts +1 -0
  91. package/src/__tests__/sync-message-contract.test.ts +59 -0
  92. package/src/__tests__/system-prompt.test.ts +145 -131
  93. package/src/__tests__/terminal-tools.test.ts +1 -1
  94. package/src/__tests__/tool-approval-handler.test.ts +1 -5
  95. package/src/__tests__/tool-execute-pipeline.test.ts +2 -2
  96. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -5
  97. package/src/__tests__/tool-executor-lifecycle-events.test.ts +15 -5
  98. package/src/__tests__/tool-executor.test.ts +9 -62
  99. package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
  100. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  101. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -6
  102. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
  103. package/src/__tests__/ui-file-upload-surface.test.ts +2 -2
  104. package/src/__tests__/usage-routes.test.ts +3 -0
  105. package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
  106. package/src/__tests__/workspace-git-service.test.ts +6 -5
  107. package/src/__tests__/workspace-migration-089-move-memory-tree-out-of-v3.test.ts +86 -0
  108. package/src/acp/__tests__/prepare-agent-env.test.ts +146 -0
  109. package/src/acp/prepare-agent-env.ts +78 -0
  110. package/src/acp/session-manager.ts +1 -1
  111. package/src/agent/loop.ts +8 -0
  112. package/src/api/README.md +5 -0
  113. package/src/api/index.ts +4 -0
  114. package/src/api/package.json +10 -0
  115. package/src/background-wake/background-wake-routes.test.ts +233 -0
  116. package/src/background-wake/runtime-registry.ts +24 -0
  117. package/src/cli/commands/__tests__/browser.test.ts +23 -5
  118. package/src/cli/commands/__tests__/domain-register.test.ts +110 -0
  119. package/src/cli/commands/__tests__/domain-status.test.ts +33 -33
  120. package/src/cli/commands/__tests__/inference-send.test.ts +108 -5
  121. package/src/cli/commands/__tests__/memory-v2-compare-render.test.ts +98 -0
  122. package/src/cli/commands/__tests__/memory-v2.test.ts +1 -0
  123. package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
  124. package/src/cli/commands/browser.ts +247 -0
  125. package/src/cli/commands/domain.ts +91 -41
  126. package/src/cli/commands/inference.ts +93 -40
  127. package/src/cli/commands/memory-v2-compare-render.ts +115 -0
  128. package/src/cli/commands/memory-v2.ts +176 -1
  129. package/src/cli/commands/memory-v3-render.ts +344 -0
  130. package/src/cli/commands/memory-v3.ts +316 -0
  131. package/src/cli/program.ts +2 -0
  132. package/src/config/assistant-feature-flags.ts +21 -9
  133. package/src/config/bundled-skills/document-editor/SKILL.md +11 -2
  134. package/src/config/bundled-skills/document-editor/TOOLS.json +18 -0
  135. package/src/config/bundled-skills/document-editor/tools/document-open.ts +12 -0
  136. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  137. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
  138. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +13 -8
  139. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +10 -3
  140. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +16 -14
  141. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +7 -2
  142. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -2
  143. package/src/config/bundled-tool-registry.ts +2 -0
  144. package/src/config/call-site-defaults.ts +7 -6
  145. package/src/config/feature-flag-registry.json +16 -0
  146. package/src/config/schemas/__tests__/memory-v2.test.ts +213 -1
  147. package/src/config/schemas/call-site-catalog.ts +21 -7
  148. package/src/config/schemas/llm.ts +12 -1
  149. package/src/config/schemas/memory-v2.ts +246 -0
  150. package/src/config/schemas/memory.ts +2 -1
  151. package/src/context/compactor.ts +52 -0
  152. package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
  153. package/src/conversations/message-consolidation.ts +404 -0
  154. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -1
  155. package/src/daemon/__tests__/meet-manifest-loader.test.ts +1 -1
  156. package/src/daemon/conversation-agent-loop-handlers.ts +2 -13
  157. package/src/daemon/conversation-agent-loop.ts +126 -76
  158. package/src/daemon/conversation-error.ts +31 -1
  159. package/src/daemon/conversation-lifecycle.ts +27 -22
  160. package/src/daemon/conversation-runtime-assembly.ts +10 -9
  161. package/src/daemon/conversation-tool-setup.ts +63 -3
  162. package/src/daemon/conversation-usage.ts +2 -0
  163. package/src/daemon/conversation.ts +14 -29
  164. package/src/daemon/disk-pressure-guard.ts +14 -2
  165. package/src/daemon/handlers/config-model.test.ts +1 -0
  166. package/src/daemon/handlers/conversations.ts +11 -3
  167. package/src/daemon/host-browser-proxy.ts +5 -5
  168. package/src/daemon/host-cu-proxy.ts +4 -4
  169. package/src/daemon/host-file-proxy.ts +4 -4
  170. package/src/daemon/host-proxy-base.ts +4 -4
  171. package/src/daemon/host-transfer-proxy.ts +10 -10
  172. package/src/daemon/lifecycle.ts +23 -20
  173. package/src/daemon/meet-manifest-loader.ts +1 -7
  174. package/src/daemon/message-types/conversations.ts +6 -9
  175. package/src/daemon/message-types/home.ts +1 -13
  176. package/src/daemon/message-types/messages.ts +6 -14
  177. package/src/daemon/message-types/sync.ts +14 -0
  178. package/src/daemon/shutdown-handlers.ts +24 -5
  179. package/src/daemon/switch-inference-profile-tool.ts +52 -0
  180. package/src/daemon/tool-setup-types.ts +13 -0
  181. package/src/events/relationship-state-updated.ts +25 -0
  182. package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
  183. package/src/home/home-greeting.ts +0 -9
  184. package/src/home/suggested-prompts.ts +0 -9
  185. package/src/ipc/gateway-flag-listener.ts +123 -0
  186. package/src/ipc/skill-routes/registries.ts +8 -12
  187. package/src/memory/__tests__/db-async-query.test.ts +165 -0
  188. package/src/memory/__tests__/db-maintenance.test.ts +115 -0
  189. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +241 -0
  190. package/src/memory/__tests__/jobs-store-job-classes.test.ts +28 -1
  191. package/src/memory/__tests__/memory-retrospective-job.test.ts +7 -0
  192. package/src/memory/auto-analysis-enqueue.ts +5 -1
  193. package/src/memory/conversation-crud.ts +71 -70
  194. package/src/memory/conversation-starters-cadence.ts +3 -1
  195. package/src/memory/conversation-title-service.ts +19 -3
  196. package/src/memory/db-async-query.ts +214 -0
  197. package/src/memory/db-init.ts +10 -0
  198. package/src/memory/db-maintenance.ts +30 -21
  199. package/src/memory/graph/bootstrap.ts +8 -1
  200. package/src/memory/graph/capability-seed.ts +7 -3
  201. package/src/memory/graph/conversation-graph-memory.ts +100 -17
  202. package/src/memory/graph/extraction.ts +1 -5
  203. package/src/memory/graph/graph-search.ts +7 -1
  204. package/src/memory/indexer.ts +28 -18
  205. package/src/memory/job-handlers/cleanup.ts +76 -18
  206. package/src/memory/job-handlers/conversation-starters.ts +1 -4
  207. package/src/memory/jobs/embed-pkb-file.ts +6 -1
  208. package/src/memory/jobs-store.ts +14 -0
  209. package/src/memory/jobs-worker.ts +55 -22
  210. package/src/memory/llm-request-log-source-clickhouse.ts +42 -2
  211. package/src/memory/llm-request-log-source-local.ts +7 -0
  212. package/src/memory/llm-request-log-source.ts +9 -2
  213. package/src/memory/llm-request-log-store.ts +43 -1
  214. package/src/memory/llm-usage-store.ts +24 -0
  215. package/src/memory/memory-retrospective-enqueue.ts +8 -1
  216. package/src/memory/memory-retrospective-job.ts +5 -0
  217. package/src/memory/memory-v2-activation-log-store.ts +15 -6
  218. package/src/memory/migrations/260-rename-cleaned-at.ts +44 -0
  219. package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +36 -0
  220. package/src/memory/migrations/262-memory-v3-coactivation.ts +57 -0
  221. package/src/memory/migrations/263-memory-v3-auto-edges.ts +50 -0
  222. package/src/memory/migrations/264-llm-request-log-call-site.ts +29 -0
  223. package/src/memory/migrations/index.ts +17 -0
  224. package/src/memory/migrations/registry.ts +33 -0
  225. package/src/memory/schema/conversations.ts +1 -1
  226. package/src/memory/schema/infrastructure.ts +21 -0
  227. package/src/memory/tool-usage-store.ts +36 -8
  228. package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -0
  229. package/src/memory/v2/__tests__/harness-compare.test.ts +186 -0
  230. package/src/memory/v2/__tests__/harness-metrics.test.ts +74 -0
  231. package/src/memory/v2/__tests__/harness-oracle.test.ts +257 -0
  232. package/src/memory/v2/__tests__/harness-replay-input.test.ts +225 -0
  233. package/src/memory/v2/__tests__/harness-runner.test.ts +109 -0
  234. package/src/memory/v2/__tests__/injection.test.ts +127 -98
  235. package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
  236. package/src/memory/v2/__tests__/router.test.ts +171 -3
  237. package/src/memory/v2/harness/compare.ts +57 -0
  238. package/src/memory/v2/harness/metrics.ts +124 -0
  239. package/src/memory/v2/harness/oracle.ts +145 -0
  240. package/src/memory/v2/harness/replay-input.ts +224 -0
  241. package/src/memory/v2/harness/retriever.ts +74 -0
  242. package/src/memory/v2/harness/router-retriever.ts +43 -0
  243. package/src/memory/v2/harness/runner.ts +106 -0
  244. package/src/memory/v2/harness/trace.ts +58 -0
  245. package/src/memory/v2/injection.ts +21 -15
  246. package/src/memory/v2/prompts/router.ts +26 -1
  247. package/src/memory/v2/qdrant.ts +14 -2
  248. package/src/memory/v2/router.ts +171 -18
  249. package/src/memory/v3/__tests__/coactivation-store.test.ts +422 -0
  250. package/src/memory/v3/__tests__/consolidation-job.test.ts +468 -0
  251. package/src/memory/v3/__tests__/edge-learning-job.test.ts +324 -0
  252. package/src/memory/v3/__tests__/edges.test.ts +563 -0
  253. package/src/memory/v3/__tests__/filter.test.ts +512 -0
  254. package/src/memory/v3/__tests__/gate.test.ts +574 -0
  255. package/src/memory/v3/__tests__/index-composition.test.ts +233 -0
  256. package/src/memory/v3/__tests__/loop.test.ts +530 -0
  257. package/src/memory/v3/__tests__/retriever.test.ts +226 -0
  258. package/src/memory/v3/__tests__/scouts.test.ts +440 -0
  259. package/src/memory/v3/__tests__/shadow-middleware.test.ts +312 -0
  260. package/src/memory/v3/__tests__/system-prompts.test.ts +154 -0
  261. package/src/memory/v3/__tests__/traversal.test.ts +469 -0
  262. package/src/memory/v3/__tests__/tree-index.test.ts +280 -0
  263. package/src/memory/v3/__tests__/tree-store.test.ts +529 -0
  264. package/src/memory/v3/__tests__/tree-walk.test.ts +707 -0
  265. package/src/memory/v3/__tests__/validate.test.ts +245 -0
  266. package/src/memory/v3/auto-edges.ts +223 -0
  267. package/src/memory/v3/coactivation-store.ts +124 -0
  268. package/src/memory/v3/consolidation-job.ts +323 -0
  269. package/src/memory/v3/edge-learning-job.ts +160 -0
  270. package/src/memory/v3/edges.ts +249 -0
  271. package/src/memory/v3/filter.ts +281 -0
  272. package/src/memory/v3/gate.ts +334 -0
  273. package/src/memory/v3/index-composition.ts +113 -0
  274. package/src/memory/v3/llm-capture.ts +46 -0
  275. package/src/memory/v3/loop.ts +382 -0
  276. package/src/memory/v3/maintenance.ts +144 -0
  277. package/src/memory/v3/prompt-context.ts +33 -0
  278. package/src/memory/v3/prompts/consolidation.ts +458 -0
  279. package/src/memory/v3/prompts/system-prompts.ts +196 -0
  280. package/src/memory/v3/retriever.ts +33 -0
  281. package/src/memory/v3/scouts.ts +420 -0
  282. package/src/memory/v3/shadow-middleware.ts +305 -0
  283. package/src/memory/v3/traversal.ts +206 -0
  284. package/src/memory/v3/tree-index.ts +237 -0
  285. package/src/memory/v3/tree-store.ts +394 -0
  286. package/src/memory/v3/tree-walk.ts +351 -0
  287. package/src/memory/v3/types.ts +65 -0
  288. package/src/memory/v3/validate.ts +300 -0
  289. package/src/notifications/adapters/macos.ts +18 -1
  290. package/src/notifications/adapters/platform.ts +1 -1
  291. package/src/notifications/decision-engine.ts +1 -4
  292. package/src/notifications/emit-signal.ts +29 -49
  293. package/src/permissions/prompter.ts +3 -3
  294. package/src/permissions/question-prompter.ts +5 -2
  295. package/src/permissions/secret-prompter.ts +2 -2
  296. package/src/plugin-api/index.ts +4 -0
  297. package/src/plugin-api/types.ts +7 -33
  298. package/src/plugins/defaults/index.ts +6 -0
  299. package/src/plugins/defaults/injectors.ts +18 -11
  300. package/src/plugins/external-plugin-loader.ts +5 -68
  301. package/src/plugins/types.ts +11 -16
  302. package/src/proactive-artifact/aux-message-injector.ts +17 -4
  303. package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
  304. package/src/prompts/persona-resolver.ts +36 -21
  305. package/src/prompts/sections.ts +39 -7
  306. package/src/prompts/system-prompt.ts +50 -185
  307. package/src/prompts/templates/BOOTSTRAP.md +2 -2
  308. package/src/prompts/templates/system-sections.ts +230 -8
  309. package/src/providers/__tests__/connection-model-compat.test.ts +234 -0
  310. package/src/providers/__tests__/retry-callsite.test.ts +85 -5
  311. package/src/providers/anthropic/client.ts +32 -66
  312. package/src/providers/call-site-routing.ts +14 -2
  313. package/src/providers/connection-model-compat.ts +38 -0
  314. package/src/providers/connection-resolution.ts +16 -2
  315. package/src/providers/gemini/client.ts +49 -6
  316. package/src/providers/inference/adapter-factory.ts +3 -0
  317. package/src/providers/minimax/client.ts +106 -0
  318. package/src/providers/model-catalog.ts +43 -0
  319. package/src/providers/model-intents.ts +1 -1
  320. package/src/providers/openai/chat-completions-provider.ts +6 -3
  321. package/src/providers/openai/codex-models.ts +18 -0
  322. package/src/providers/openai/responses-provider.ts +78 -21
  323. package/src/providers/provider-send-message.ts +7 -1
  324. package/src/providers/retry.ts +34 -3
  325. package/src/providers/thinking-config.ts +26 -1
  326. package/src/providers/usage-tracking.ts +2 -0
  327. package/src/runtime/AGENTS.md +2 -2
  328. package/src/runtime/agent-wake.ts +1 -0
  329. package/src/runtime/assistant-event-hub.ts +76 -6
  330. package/src/runtime/auth/route-policy.ts +36 -0
  331. package/src/runtime/btw-sidechain.ts +0 -6
  332. package/src/runtime/http-types.ts +0 -2
  333. package/src/runtime/migrations/vbundle-builder.ts +10 -3
  334. package/src/runtime/pending-interactions.ts +0 -1
  335. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +106 -0
  336. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +25 -6
  337. package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -0
  338. package/src/runtime/routes/acp-routes.test.ts +255 -6
  339. package/src/runtime/routes/acp-routes.ts +8 -1
  340. package/src/runtime/routes/avatar-routes.ts +10 -10
  341. package/src/runtime/routes/background-wake-routes.ts +188 -0
  342. package/src/runtime/routes/browser-tabs-routes.ts +200 -0
  343. package/src/runtime/routes/btw-routes.ts +0 -6
  344. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  345. package/src/runtime/routes/conversation-list-routes.ts +12 -4
  346. package/src/runtime/routes/conversation-management-routes.ts +77 -20
  347. package/src/runtime/routes/conversation-query-routes.ts +142 -36
  348. package/src/runtime/routes/conversation-routes.ts +252 -410
  349. package/src/runtime/routes/conversation-starter-routes.ts +6 -3
  350. package/src/runtime/routes/disk-pressure-routes.ts +1 -1
  351. package/src/runtime/routes/domain-routes.ts +60 -10
  352. package/src/runtime/routes/email-routes.ts +5 -2
  353. package/src/runtime/routes/events-routes.ts +54 -10
  354. package/src/runtime/routes/group-routes.ts +24 -8
  355. package/src/runtime/routes/host-browser-routes.ts +10 -2
  356. package/src/runtime/routes/host-cu-routes.ts +2 -2
  357. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +96 -3
  358. package/src/runtime/routes/index.ts +8 -0
  359. package/src/runtime/routes/inference-profile-session-handler.ts +22 -12
  360. package/src/runtime/routes/inference-profile-session-routes.ts +7 -1
  361. package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
  362. package/src/runtime/routes/memory-item-routes.ts +8 -3
  363. package/src/runtime/routes/memory-v2-routes.ts +215 -5
  364. package/src/runtime/routes/memory-v3-routes.ts +316 -0
  365. package/src/runtime/routes/migration-routes.ts +21 -24
  366. package/src/runtime/routes/plugins-routes.ts +337 -0
  367. package/src/runtime/routes/rename-conversation-routes.ts +6 -2
  368. package/src/runtime/routes/secret-routes.ts +25 -5
  369. package/src/runtime/routes/settings-routes.ts +12 -11
  370. package/src/runtime/routes/slack-channel-routes.ts +5 -4
  371. package/src/runtime/routes/workspace-routes.ts +25 -10
  372. package/src/runtime/sync/resource-sync-events.ts +106 -38
  373. package/src/runtime/sync/sync-publisher.test.ts +49 -0
  374. package/src/runtime/sync/sync-publisher.ts +2 -1
  375. package/src/runtime/verification-outbound-actions.ts +73 -1
  376. package/src/telemetry/types.ts +12 -0
  377. package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
  378. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  379. package/src/tools/acp/spawn.test.ts +119 -0
  380. package/src/tools/acp/spawn.ts +15 -2
  381. package/src/tools/apps/definitions.ts +2 -8
  382. package/src/tools/ask-question/ask-question-tool.test.ts +3 -3
  383. package/src/tools/ask-question/ask-question-tool.ts +38 -45
  384. package/src/tools/browser/__tests__/pinned-tabs.test.ts +70 -0
  385. package/src/tools/browser/browser-execution.ts +16 -3
  386. package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
  387. package/src/tools/browser/cdp-client/__tests__/types.test.ts +3 -0
  388. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +12 -0
  389. package/src/tools/browser/cdp-client/extension-cdp-client.ts +27 -1
  390. package/src/tools/browser/cdp-client/factory.ts +100 -17
  391. package/src/tools/browser/cdp-client/local-cdp-client.ts +12 -0
  392. package/src/tools/browser/cdp-client/types.ts +65 -0
  393. package/src/tools/browser/pinned-tabs.ts +96 -40
  394. package/src/tools/computer-use/definitions.ts +22 -78
  395. package/src/tools/credential-execution/make-authenticated-request.ts +3 -9
  396. package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -9
  397. package/src/tools/credential-execution/run-authenticated-command.ts +3 -9
  398. package/src/tools/credentials/vault.ts +3 -9
  399. package/src/tools/document/document-tool.ts +59 -0
  400. package/src/tools/execution-target.ts +21 -23
  401. package/src/tools/executor.ts +6 -1
  402. package/src/tools/filesystem/edit.ts +3 -9
  403. package/src/tools/filesystem/list.ts +3 -9
  404. package/src/tools/filesystem/read.ts +3 -9
  405. package/src/tools/filesystem/write.ts +3 -9
  406. package/src/tools/host-filesystem/edit.ts +3 -9
  407. package/src/tools/host-filesystem/read.ts +3 -9
  408. package/src/tools/host-filesystem/transfer.ts +3 -9
  409. package/src/tools/host-filesystem/write.ts +3 -9
  410. package/src/tools/host-terminal/host-shell.ts +3 -9
  411. package/src/tools/mcp/mcp-tool-factory.ts +1 -8
  412. package/src/tools/memory/register.test.ts +1 -1
  413. package/src/tools/memory/register.ts +4 -9
  414. package/src/tools/network/web-fetch.ts +3 -9
  415. package/src/tools/network/web-search.ts +25 -32
  416. package/src/tools/registry.ts +7 -23
  417. package/src/tools/schema-transforms.ts +1 -1
  418. package/src/tools/skills/execute.ts +3 -9
  419. package/src/tools/skills/load.ts +3 -9
  420. package/src/tools/skills/skill-tool-factory.ts +1 -8
  421. package/src/tools/subagent/notify-parent.ts +3 -9
  422. package/src/tools/system/request-permission.ts +3 -9
  423. package/src/tools/terminal/shell.ts +3 -9
  424. package/src/tools/tool-defaults.ts +94 -0
  425. package/src/tools/types.ts +27 -98
  426. package/src/tools/ui-surface/definitions.ts +6 -22
  427. package/src/usage/pricing.ts +23 -0
  428. package/src/usage/types.ts +12 -0
  429. package/src/util/logger.ts +16 -7
  430. package/src/util/platform.ts +7 -2
  431. package/src/util/sqlite3-runtime.ts +65 -0
  432. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
  433. package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
  434. package/src/workspace/migrations/registry.ts +2 -0
  435. package/src/__tests__/compaction-strip-metadata-clear.test.ts +0 -206
  436. package/src/__tests__/message-complete-display-id.test.ts +0 -175
  437. package/src/daemon/query-complexity-router.ts +0 -75
  438. package/src/prompts/cache-boundary.ts +0 -8
@@ -421,11 +421,8 @@ describe("Tool execution pipeline benchmark", () => {
421
421
  description: `Benchmark tool (${sleepMs}ms)`,
422
422
  category: "benchmark",
423
423
  defaultRiskLevel: RiskLevel.Low,
424
- getDefinition: () => ({
425
- name,
426
- description: `Benchmark tool (${sleepMs}ms)`,
427
- input_schema: { type: "object" as const, properties: {} },
428
- }),
424
+ executionTarget: "sandbox",
425
+ input_schema: { type: "object" as const, properties: {} },
429
426
  execute: async (): Promise<ToolExecutionResult> => {
430
427
  if (sleepMs > 0) {
431
428
  await new Promise((r) => setTimeout(r, sleepMs));
@@ -79,7 +79,7 @@ mock.module("../memory/conversation-crud.js", () => ({
79
79
  mock.module("../memory/tool-usage-store.js", () => ({
80
80
  recordToolInvocation: () => {},
81
81
  getRecentInvocations: () => [],
82
- rotateToolInvocations: () => 0,
82
+ rotateToolInvocations: async () => 0,
83
83
  }));
84
84
 
85
85
  mock.module("../tools/registry.js", () => ({
@@ -95,7 +95,7 @@ mock.module("../tools/registry.js", () => ({
95
95
  origin: "skill" as const,
96
96
  ownerSkillId: "test-skill",
97
97
  executionTarget: "host" as const,
98
- getDefinition: () => ({}),
98
+ input_schema: {},
99
99
  execute: async () => {
100
100
  if (toolThrow) throw toolThrow;
101
101
  return fakeToolResult;
@@ -111,7 +111,7 @@ mock.module("../tools/registry.js", () => ({
111
111
  origin: "skill" as const,
112
112
  ownerSkillId: "test-skill",
113
113
  executionTarget: "sandbox" as const,
114
- getDefinition: () => ({}),
114
+ input_schema: {},
115
115
  execute: async () => {
116
116
  if (toolThrow) throw toolThrow;
117
117
  return fakeToolResult;
@@ -129,19 +129,29 @@ mock.module("../tools/registry.js", () => ({
129
129
  origin: "skill" as const,
130
130
  ownerSkillId: "test-skill",
131
131
  executionTarget: "sandbox" as const,
132
- getDefinition: () => ({}),
132
+ input_schema: {},
133
133
  execute: async () => {
134
134
  if (toolThrow) throw toolThrow;
135
135
  return fakeToolResult;
136
136
  },
137
137
  };
138
138
  }
139
+ // Mirror what the real loader stamps onto a tool at registration time
140
+ // (every registered Tool has `executionTarget` set). Mirror the
141
+ // prefix heuristic here so the tests that exercise built-in tools
142
+ // (`bash`, `host_bash`, `file_read`) still observe production-shaped
143
+ // executionTarget values.
144
+ const executionTarget =
145
+ name.startsWith("host_") || name.startsWith("computer_use_")
146
+ ? ("host" as const)
147
+ : ("sandbox" as const);
139
148
  return {
140
149
  name,
141
150
  description: "test tool",
142
151
  category: "test",
143
152
  defaultRiskLevel: "low",
144
- getDefinition: () => ({}),
153
+ executionTarget,
154
+ input_schema: {},
145
155
  execute: async () => {
146
156
  if (toolThrow) throw toolThrow;
147
157
  return fakeToolResult;
@@ -131,7 +131,7 @@ mock.module("../permissions/checker.js", () => ({
131
131
  mock.module("../memory/tool-usage-store.js", () => ({
132
132
  recordToolInvocation: () => {},
133
133
  getRecentInvocations: () => [],
134
- rotateToolInvocations: () => 0,
134
+ rotateToolInvocations: async () => 0,
135
135
  }));
136
136
 
137
137
  mock.module("../tools/registry.js", () => ({
@@ -143,7 +143,7 @@ mock.module("../tools/registry.js", () => ({
143
143
  description: "test tool",
144
144
  category: "test",
145
145
  defaultRiskLevel: "low",
146
- getDefinition: () => ({}),
146
+ input_schema: {},
147
147
  execute: async () => fakeToolResult,
148
148
  };
149
149
  },
@@ -285,11 +285,7 @@ describe("ToolExecutor allowedToolNames gating", () => {
285
285
  description: "test tool",
286
286
  category: "test",
287
287
  defaultRiskLevel: RiskLevel.Low,
288
- getDefinition: () => ({
289
- name,
290
- description: "test tool",
291
- input_schema: { type: "object" as const, properties: {} },
292
- }),
288
+ input_schema: { type: "object" as const, properties: {} },
293
289
  execute: async () => fakeToolResult,
294
290
  }) as unknown as Tool;
295
291
  getAllToolsOverride = () => [
@@ -337,13 +333,10 @@ describe("ToolExecutor allowedToolNames gating", () => {
337
333
  description: "tool from a skill",
338
334
  category: "skill",
339
335
  defaultRiskLevel: RiskLevel.Low,
336
+ executionTarget: "sandbox" as const,
340
337
  origin: "skill" as const,
341
338
  ownerSkillId: "my-skill",
342
- getDefinition: () => ({
343
- name,
344
- description: "tool from a skill",
345
- input_schema: { type: "object" as const, properties: {} },
346
- }),
339
+ input_schema: { type: "object" as const, properties: {} },
347
340
  execute: async () => fakeToolResult,
348
341
  };
349
342
  };
@@ -382,11 +375,7 @@ describe("ToolExecutor policy context plumbing", () => {
382
375
  ownerSkillId: "my-skill-123",
383
376
  ownerSkillVersionHash: "abc123hash",
384
377
  executionTarget: "sandbox" as const,
385
- getDefinition: () => ({
386
- name,
387
- description: "skill tool",
388
- input_schema: { type: "object" as const, properties: {} },
389
- }),
378
+ input_schema: { type: "object" as const, properties: {} },
390
379
  execute: async () => fakeToolResult,
391
380
  };
392
381
  };
@@ -434,12 +423,9 @@ describe("ToolExecutor policy context plumbing", () => {
434
423
  description: "core tool",
435
424
  category: "core",
436
425
  defaultRiskLevel: RiskLevel.Low,
426
+ executionTarget: "sandbox" as const,
437
427
  origin: "core" as const,
438
- getDefinition: () => ({
439
- name,
440
- description: "core tool",
441
- input_schema: { type: "object" as const, properties: {} },
442
- }),
428
+ input_schema: { type: "object" as const, properties: {} },
443
429
  execute: async () => fakeToolResult,
444
430
  };
445
431
  };
@@ -471,11 +457,7 @@ describe("ToolExecutor policy context plumbing", () => {
471
457
  ownerSkillId: "host-skill",
472
458
  ownerSkillVersionHash: "host-hash",
473
459
  executionTarget: "host" as const,
474
- getDefinition: () => ({
475
- name,
476
- description: "host skill tool",
477
- input_schema: { type: "object" as const, properties: {} },
478
- }),
460
+ input_schema: { type: "object" as const, properties: {} },
479
461
  execute: async () => fakeToolResult,
480
462
  };
481
463
  };
@@ -496,41 +478,6 @@ describe("ToolExecutor policy context plumbing", () => {
496
478
  });
497
479
  });
498
480
 
499
- test("skill tool without executionTarget passes undefined executionTarget", async () => {
500
- getToolOverride = (name: string) => {
501
- if (name === "unknown_tool") return undefined;
502
- return {
503
- name,
504
- description: "skill without target",
505
- category: "skill",
506
- defaultRiskLevel: RiskLevel.Low,
507
- origin: "skill" as const,
508
- ownerSkillId: "no-target-skill",
509
- // executionTarget intentionally omitted
510
- getDefinition: () => ({
511
- name,
512
- description: "skill tool",
513
- input_schema: { type: "object" as const, properties: {} },
514
- }),
515
- execute: async () => fakeToolResult,
516
- };
517
- };
518
-
519
- const executor = new ToolExecutor(makePrompter());
520
- const result = await executor.execute(
521
- "no_target_tool",
522
- {},
523
- makeContext({ requireFreshApproval: true }),
524
- );
525
-
526
- expect(result.isError).toBe(false);
527
- expect(lastCheckArgs).toBeDefined();
528
- expect(lastCheckArgs!.policyContext).toEqual({
529
- conversationId: "conversation-1",
530
- executionContext: "conversation",
531
- executionTarget: undefined,
532
- });
533
- });
534
481
  });
535
482
 
536
483
  // ---------------------------------------------------------------------------
@@ -31,11 +31,7 @@ const fakeTool = {
31
31
  description: "Run a shell command",
32
32
  category: "shell",
33
33
  defaultRiskLevel: "high",
34
- getDefinition: () => ({
35
- name: "bash",
36
- description: "Run a shell command",
37
- input_schema: {},
38
- }),
34
+ input_schema: {},
39
35
  execute: async () => ({ content: "ok", isError: false }),
40
36
  };
41
37
 
@@ -57,7 +53,6 @@ mock.module("../notifications/emit-signal.js", () => ({
57
53
  deliveryResults: [],
58
54
  };
59
55
  },
60
- registerBroadcastFn: () => {},
61
56
  }));
62
57
 
63
58
  // Mock channel guardian service — provide a guardian binding for 'self' + 'telegram'
@@ -33,7 +33,6 @@ mock.module("../notifications/emit-signal.js", () => ({
33
33
  reason: "ok",
34
34
  deliveryResults: [],
35
35
  }),
36
- registerBroadcastFn: () => {},
37
36
  }));
38
37
 
39
38
  // ── Gateway client mock ──
@@ -48,7 +48,6 @@ mock.module("../notifications/emit-signal.js", () => ({
48
48
  ],
49
49
  };
50
50
  },
51
- registerBroadcastFn: () => {},
52
51
  }));
53
52
 
54
53
  // Mock task run rules
@@ -62,11 +61,7 @@ const fakeTool = {
62
61
  description: "Run a shell command",
63
62
  category: "shell",
64
63
  defaultRiskLevel: "high",
65
- getDefinition: () => ({
66
- name: "bash",
67
- description: "Run a shell command",
68
- input_schema: {},
69
- }),
64
+ input_schema: {},
70
65
  execute: async () => ({ content: "ok", isError: false }),
71
66
  };
72
67
  mock.module("../tools/registry.js", () => ({
@@ -36,7 +36,6 @@ mock.module("../notifications/emit-signal.js", () => ({
36
36
  deliveryResults: [],
37
37
  };
38
38
  },
39
- registerBroadcastFn: () => {},
40
39
  }));
41
40
 
42
41
  // Mock access-request-helper directly to capture notification calls.
@@ -78,7 +78,7 @@ describe("file_upload interactivity", () => {
78
78
 
79
79
  describe("ui_show tool includes file_upload", () => {
80
80
  test("input_schema surface_type enum includes file_upload", () => {
81
- const definition = uiShowTool.getDefinition();
81
+ const definition = uiShowTool;
82
82
  const surfaceTypeEnum = (
83
83
  definition.input_schema as {
84
84
  properties: { surface_type: { enum: string[] } };
@@ -89,7 +89,7 @@ describe("ui_show tool includes file_upload", () => {
89
89
  });
90
90
 
91
91
  test("description mentions file_upload", () => {
92
- const definition = uiShowTool.getDefinition();
92
+ const definition = uiShowTool;
93
93
  expect(definition.description).toContain("file_upload");
94
94
  });
95
95
  });
@@ -59,6 +59,7 @@ function seedEvents() {
59
59
  outputTokens: 200,
60
60
  cacheCreationInputTokens: 50,
61
61
  cacheReadInputTokens: 100,
62
+ rawUsage: null,
62
63
  },
63
64
  { estimatedCostUsd: 0.005, pricingStatus: "priced" },
64
65
  );
@@ -82,6 +83,7 @@ function seedEvents() {
82
83
  outputTokens: 100,
83
84
  cacheCreationInputTokens: 0,
84
85
  cacheReadInputTokens: 0,
86
+ rawUsage: null,
85
87
  },
86
88
  { estimatedCostUsd: 0.001, pricingStatus: "priced" },
87
89
  );
@@ -104,6 +106,7 @@ function seedEvents() {
104
106
  outputTokens: 400,
105
107
  cacheCreationInputTokens: 0,
106
108
  cacheReadInputTokens: 0,
109
+ rawUsage: null,
107
110
  },
108
111
  { estimatedCostUsd: 0, pricingStatus: "unpriced" },
109
112
  );
@@ -80,7 +80,7 @@ mock.module("../memory/conversation-crud.js", () => ({
80
80
  mock.module("../memory/tool-usage-store.js", () => ({
81
81
  recordToolInvocation: () => {},
82
82
  getRecentInvocations: () => [],
83
- rotateToolInvocations: () => 0,
83
+ rotateToolInvocations: async () => 0,
84
84
  }));
85
85
 
86
86
  mock.module("../tools/registry.js", () => ({
@@ -91,7 +91,7 @@ mock.module("../tools/registry.js", () => ({
91
91
  description: "test tool",
92
92
  category: "test",
93
93
  defaultRiskLevel: "low",
94
- getDefinition: () => ({}),
94
+ input_schema: {},
95
95
  execute: async () => fakeToolResult,
96
96
  };
97
97
  },
@@ -421,11 +421,12 @@ describe("WorkspaceGitService", () => {
421
421
 
422
422
  await Promise.all(commits);
423
423
 
424
- // All commits should have succeeded
425
- const log = execFileSync("git", ["log", "--oneline"], {
426
- cwd: testDir,
427
- encoding: "utf-8",
428
- });
424
+ // Read through the service so GIT_* vars set by CI runners are stripped
425
+ // (matches the env used for the commits themselves).
426
+ const { stdout: log } = await service.runReadOnlyGit([
427
+ "log",
428
+ "--oneline",
429
+ ]);
429
430
 
430
431
  for (let i = 0; i < 10; i++) {
431
432
  expect(log).toContain(`Add file ${i}`);
@@ -0,0 +1,86 @@
1
+ import * as fs from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
5
+
6
+ import { moveMemoryTreeOutOfV3Migration } from "../workspace/migrations/089-move-memory-tree-out-of-v3.js";
7
+
8
+ describe("workspace migration 089 — move memory tree out of v3", () => {
9
+ let ws: string;
10
+
11
+ beforeEach(() => {
12
+ ws = fs.mkdtempSync(join(tmpdir(), "ws-mig-089-"));
13
+ });
14
+
15
+ afterEach(() => {
16
+ fs.rmSync(ws, { recursive: true, force: true });
17
+ });
18
+
19
+ function writeOldTree(): void {
20
+ const oldTree = join(ws, "memory", "v3", "tree");
21
+ fs.mkdirSync(join(oldTree, "people"), { recursive: true });
22
+ fs.writeFileSync(join(oldTree, "_root.md"), "root");
23
+ fs.writeFileSync(join(oldTree, "people", "alice.md"), "alice");
24
+ }
25
+
26
+ test("moves memory/v3/tree -> memory/tree (incl. nested) and drops the empty v3 wrapper", () => {
27
+ writeOldTree();
28
+ moveMemoryTreeOutOfV3Migration.run(ws);
29
+
30
+ expect(fs.existsSync(join(ws, "memory", "v3"))).toBe(false);
31
+ expect(
32
+ fs.readFileSync(join(ws, "memory", "tree", "_root.md"), "utf-8"),
33
+ ).toBe("root");
34
+ expect(
35
+ fs.readFileSync(
36
+ join(ws, "memory", "tree", "people", "alice.md"),
37
+ "utf-8",
38
+ ),
39
+ ).toBe("alice");
40
+ });
41
+
42
+ test("is idempotent — re-running after the move changes nothing", () => {
43
+ writeOldTree();
44
+ moveMemoryTreeOutOfV3Migration.run(ws);
45
+ moveMemoryTreeOutOfV3Migration.run(ws);
46
+
47
+ expect(
48
+ fs.readFileSync(join(ws, "memory", "tree", "_root.md"), "utf-8"),
49
+ ).toBe("root");
50
+ });
51
+
52
+ test("no-op on a fresh workspace with no old tree", () => {
53
+ moveMemoryTreeOutOfV3Migration.run(ws);
54
+ expect(fs.existsSync(join(ws, "memory", "tree"))).toBe(false);
55
+ });
56
+
57
+ test("never clobbers an existing memory/tree", () => {
58
+ writeOldTree();
59
+ const newTree = join(ws, "memory", "tree");
60
+ fs.mkdirSync(newTree, { recursive: true });
61
+ fs.writeFileSync(join(newTree, "_root.md"), "existing");
62
+
63
+ moveMemoryTreeOutOfV3Migration.run(ws);
64
+
65
+ // Destination preserved; source left in place for manual resolution.
66
+ expect(fs.readFileSync(join(newTree, "_root.md"), "utf-8")).toBe(
67
+ "existing",
68
+ );
69
+ expect(fs.existsSync(join(ws, "memory", "v3", "tree", "_root.md"))).toBe(
70
+ true,
71
+ );
72
+ });
73
+
74
+ test("down() restores memory/tree back to memory/v3/tree", () => {
75
+ const newTree = join(ws, "memory", "tree");
76
+ fs.mkdirSync(newTree, { recursive: true });
77
+ fs.writeFileSync(join(newTree, "_root.md"), "root");
78
+
79
+ moveMemoryTreeOutOfV3Migration.down(ws);
80
+
81
+ expect(fs.existsSync(join(ws, "memory", "tree"))).toBe(false);
82
+ expect(
83
+ fs.readFileSync(join(ws, "memory", "v3", "tree", "_root.md"), "utf-8"),
84
+ ).toBe("root");
85
+ });
86
+ });
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Tests for `prepareAgentEnv` — the shared helper that injects required env
3
+ * vars onto an `AcpAgentConfig` and preflights that they're set.
4
+ *
5
+ * The route-level test in `runtime/routes/acp-routes.test.ts` covers the same
6
+ * behavior through the HTTP handler; these tests pin the helper in isolation
7
+ * so the contract is clear and a future refactor can't silently break it.
8
+ */
9
+
10
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
11
+
12
+ // Stub the secure-keys backend BEFORE importing the helper. Bun's
13
+ // `mock.module` is process-global and only takes effect for imports that
14
+ // follow it — the dynamic import below ensures correctness.
15
+ const secureKeyStore = new Map<string, string>();
16
+
17
+ mock.module("../../security/secure-keys.js", () => ({
18
+ getSecureKeyAsync: async (key: string) => secureKeyStore.get(key),
19
+ }));
20
+
21
+ const { prepareAgentEnv } = await import("../prepare-agent-env.js");
22
+
23
+ beforeEach(() => {
24
+ secureKeyStore.clear();
25
+ });
26
+
27
+ describe("prepareAgentEnv — claude-agent-acp gating", () => {
28
+ test("injects CLAUDE_CODE_OAUTH_TOKEN from the secure store when agent.env has no override", async () => {
29
+ secureKeyStore.set("credential/acp/claude_oauth_token", "vault-AAA");
30
+
31
+ const prepared = await prepareAgentEnv({
32
+ command: "claude-agent-acp",
33
+ args: [],
34
+ });
35
+
36
+ expect(prepared.env?.CLAUDE_CODE_OAUTH_TOKEN).toBe("vault-AAA");
37
+ });
38
+
39
+ test("accepts CLAUDE_CODE_OAUTH_TOKEN from agent.env (config.json override) with no vault entry", async () => {
40
+ const prepared = await prepareAgentEnv({
41
+ command: "claude-agent-acp",
42
+ args: [],
43
+ env: { CLAUDE_CODE_OAUTH_TOKEN: "config-BBB" },
44
+ });
45
+
46
+ expect(prepared.env?.CLAUDE_CODE_OAUTH_TOKEN).toBe("config-BBB");
47
+ });
48
+
49
+ test("agent.env override wins over the secure-store entry (precedence pin)", async () => {
50
+ // The config-supplied env wins so users can rotate per-workspace without
51
+ // racing the vault. Mirrors the route-level precedence test.
52
+ secureKeyStore.set("credential/acp/claude_oauth_token", "vault-CCC");
53
+
54
+ const prepared = await prepareAgentEnv({
55
+ command: "claude-agent-acp",
56
+ args: [],
57
+ env: { CLAUDE_CODE_OAUTH_TOKEN: "config-DDD" },
58
+ });
59
+
60
+ expect(prepared.env?.CLAUDE_CODE_OAUTH_TOKEN).toBe("config-DDD");
61
+ });
62
+
63
+ test("preserves unrelated env vars on agent.env when injecting from the vault", async () => {
64
+ secureKeyStore.set("credential/acp/claude_oauth_token", "vault-EEE");
65
+
66
+ const prepared = await prepareAgentEnv({
67
+ command: "claude-agent-acp",
68
+ args: [],
69
+ env: { OTHER_VAR: "keep-me" },
70
+ });
71
+
72
+ expect(prepared.env?.CLAUDE_CODE_OAUTH_TOKEN).toBe("vault-EEE");
73
+ expect(prepared.env?.OTHER_VAR).toBe("keep-me");
74
+ });
75
+
76
+ test("throws FailedDependencyError when no token is provided from either route", async () => {
77
+ // secureKeyStore empty, no agent.env override — the preflight must throw
78
+ // so callers fail fast instead of spawning a zombie subprocess that the
79
+ // SDK rejects with 'Authentication required' after the first prompt.
80
+ await expect(
81
+ prepareAgentEnv({ command: "claude-agent-acp", args: [] }),
82
+ ).rejects.toThrow("CLAUDE_CODE_OAUTH_TOKEN");
83
+ });
84
+
85
+ test("gates on the resolved command BASENAME (alias to /custom/path/claude-agent-acp still gets the token)", async () => {
86
+ // A user-supplied `acp.agents.my-claude = { command: "/opt/.../claude-agent-acp" }`
87
+ // is the only realistic path that lands a non-bare basename here. The
88
+ // helper must still recognize it.
89
+ secureKeyStore.set("credential/acp/claude_oauth_token", "vault-FFF");
90
+
91
+ const prepared = await prepareAgentEnv({
92
+ command: "/opt/bin/claude-agent-acp",
93
+ args: [],
94
+ });
95
+
96
+ expect(prepared.env?.CLAUDE_CODE_OAUTH_TOKEN).toBe("vault-FFF");
97
+ });
98
+
99
+ test("does NOT mutate the caller's agentConfig", async () => {
100
+ // Callers pass `resolved.agent` which is shared state from the resolver's
101
+ // config cache. The helper must clone before mutating, otherwise repeated
102
+ // spawns would race on the same object.
103
+ secureKeyStore.set("credential/acp/claude_oauth_token", "vault-GGG");
104
+ const original = {
105
+ command: "claude-agent-acp",
106
+ args: [],
107
+ env: { OTHER: "keep" },
108
+ };
109
+ const beforeEnv = { ...original.env };
110
+
111
+ const prepared = await prepareAgentEnv(original);
112
+
113
+ expect(prepared).not.toBe(original);
114
+ expect(prepared.env).not.toBe(original.env);
115
+ expect(original.env).toEqual(beforeEnv);
116
+ expect(original.env).not.toHaveProperty("CLAUDE_CODE_OAUTH_TOKEN");
117
+ });
118
+ });
119
+
120
+ describe("prepareAgentEnv — non-claude commands", () => {
121
+ test("returns the config unchanged for codex-acp (no required env vars today)", async () => {
122
+ // codex-acp inherits auth from the underlying `codex` CLI binary
123
+ // (codex login / CODEX_API_KEY / OPENAI_API_KEY in process.env) — no
124
+ // ACP-level env injection. Pin that contract so a future change has
125
+ // to update this test explicitly.
126
+ const prepared = await prepareAgentEnv({
127
+ command: "codex-acp",
128
+ args: [],
129
+ });
130
+
131
+ expect(prepared.env).toEqual({});
132
+ });
133
+
134
+ test("returns the config unchanged for an unrecognized command basename", async () => {
135
+ secureKeyStore.set("credential/acp/claude_oauth_token", "vault-HHH");
136
+
137
+ const prepared = await prepareAgentEnv({
138
+ command: "some-future-adapter",
139
+ args: [],
140
+ env: { FOO: "bar" },
141
+ });
142
+
143
+ // No injection — basename gate skipped.
144
+ expect(prepared.env).toEqual({ FOO: "bar" });
145
+ });
146
+ });
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Inject required env vars for an ACP agent and preflight that they're set.
3
+ *
4
+ * Called by every code path that hands an `AcpAgentConfig` to
5
+ * `AcpSessionManager.spawn`. There are TWO such paths today — the HTTP
6
+ * route `/v1/acp/spawn` (`runtime/routes/acp-routes.ts:spawnSession`) and
7
+ * the skill tool `acp_spawn` (`tools/acp/spawn.ts:executeAcpSpawn`) — and
8
+ * before this helper existed the env-injection logic lived inline in the
9
+ * route only. The skill-tool path bypassed it entirely, so spawns landed
10
+ * with no `CLAUDE_CODE_OAUTH_TOKEN`, the SDK rejected the first prompt
11
+ * with "Authentication required", and the subprocess died as a zombie
12
+ * with no completion notification.
13
+ *
14
+ * The fix: have this single helper own injection + preflight, and have
15
+ * every caller route through it before calling `manager.spawn`.
16
+ */
17
+
18
+ import { basename } from "node:path";
19
+
20
+ import { FailedDependencyError } from "../runtime/routes/errors.js";
21
+ import { credentialKey } from "../security/credential-key.js";
22
+ import { getSecureKeyAsync } from "../security/secure-keys.js";
23
+ import type { AcpAgentConfig } from "./types.js";
24
+
25
+ /**
26
+ * Returns a NEW config with any required credentials merged into `env`.
27
+ * Does NOT mutate the input. Throws `FailedDependencyError` if a required
28
+ * credential is missing from both the user-supplied env override and the
29
+ * secure store.
30
+ *
31
+ * Gating is keyed off the resolved agent COMMAND (basename), not the
32
+ * user-facing agent id, so a custom `acp.agents.my-claude = { command:
33
+ * "claude-agent-acp", ... }` alias still gets the env it needs.
34
+ *
35
+ * For `claude-agent-acp` the only required env var is
36
+ * `CLAUDE_CODE_OAUTH_TOKEN`. Two provisioning routes converge on it, with
37
+ * config.json winning over the vault so explicit user overrides
38
+ * (per-workspace, rotated, etc.) are never silently clobbered:
39
+ * 1. `acp.agents.<id>.env.CLAUDE_CODE_OAUTH_TOKEN` in `config.json` —
40
+ * the user-supplied env override on the resolved agent config.
41
+ * 2. Secure store via CLI: `assistant credentials set --service acp \
42
+ * --field claude_oauth_token <token>` — written to the canonical
43
+ * `credential/{service}/{field}` key built by `credentialKey()`,
44
+ * used as fallback when (1) is unset.
45
+ * After resolution, this asserts the token is present (from either route)
46
+ * before spawning. The "fail-fast" throw is symmetric with the existing
47
+ * `binary_not_found` preflight in `resolveAcpAgent` and strictly better
48
+ * than a `warn` + zombie subprocess 10 seconds later.
49
+ */
50
+ export async function prepareAgentEnv(
51
+ agentConfig: AcpAgentConfig,
52
+ ): Promise<AcpAgentConfig> {
53
+ // Clone caller's config + env so we never mutate the resolver's cached
54
+ // agent reference. The local `env` binding sidesteps TS narrowing
55
+ // limitations on the optional `AcpAgentConfig.env` field.
56
+ const env: Record<string, string> = { ...(agentConfig.env ?? {}) };
57
+ const commandBasename = basename(agentConfig.command);
58
+
59
+ if (commandBasename === "claude-agent-acp") {
60
+ if (!env.CLAUDE_CODE_OAUTH_TOKEN) {
61
+ const claudeToken = await getSecureKeyAsync(
62
+ credentialKey("acp", "claude_oauth_token"),
63
+ );
64
+ if (claudeToken) {
65
+ env.CLAUDE_CODE_OAUTH_TOKEN = claudeToken;
66
+ }
67
+ }
68
+ if (!env.CLAUDE_CODE_OAUTH_TOKEN) {
69
+ throw new FailedDependencyError(
70
+ "claude-agent-acp requires CLAUDE_CODE_OAUTH_TOKEN. " +
71
+ "Run: assistant credentials set --service acp --field claude_oauth_token <token> " +
72
+ "(or set it under acp.agents.<id>.env in config.json).",
73
+ );
74
+ }
75
+ }
76
+
77
+ return { ...agentConfig, env };
78
+ }
@@ -306,7 +306,7 @@ export class AcpSessionManager {
306
306
  */
307
307
  private teardownSession(acpSessionId: string, entry: SessionEntry): void {
308
308
  for (const requestId of entry.clientHandler.pendingRequestIds) {
309
- const interaction = pendingInteractions.resolve(requestId);
309
+ const interaction = pendingInteractions.resolve(requestId, "cancelled");
310
310
  if (interaction?.directResolve) {
311
311
  interaction.directResolve("deny");
312
312
  }