@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
@@ -9,15 +9,18 @@ import { getAvatarImagePath } from "../../util/platform.js";
9
9
  import { broadcastMessage } from "../assistant-event-hub.js";
10
10
  import { publishSyncInvalidation } from "./sync-publisher.js";
11
11
 
12
- export function publishAvatarChanged(): void {
12
+ export function publishAvatarChanged(originClientId?: string): void {
13
13
  broadcastMessage({
14
14
  type: "avatar_updated",
15
15
  avatarPath: getAvatarImagePath(),
16
16
  });
17
- void publishSyncInvalidation([SYNC_TAGS.assistantAvatar]);
17
+ void publishSyncInvalidation([SYNC_TAGS.assistantAvatar], originClientId);
18
18
  }
19
19
 
20
- export function publishIdentityChanged(fields: IdentityFields): void {
20
+ export function publishIdentityChanged(
21
+ fields: IdentityFields,
22
+ originClientId?: string,
23
+ ): void {
21
24
  broadcastMessage({
22
25
  type: "identity_changed",
23
26
  name: fields.name,
@@ -26,59 +29,112 @@ export function publishIdentityChanged(fields: IdentityFields): void {
26
29
  emoji: fields.emoji,
27
30
  home: fields.home,
28
31
  });
29
- void publishSyncInvalidation([SYNC_TAGS.assistantIdentity]);
32
+ void publishSyncInvalidation([SYNC_TAGS.assistantIdentity], originClientId);
30
33
  }
31
34
 
32
- export function publishConfigChanged(): void {
35
+ export function publishConfigChanged(originClientId?: string): void {
33
36
  broadcastMessage({ type: "config_changed" });
34
- void publishSyncInvalidation([SYNC_TAGS.assistantConfig]);
37
+ void publishSyncInvalidation([SYNC_TAGS.assistantConfig], originClientId);
35
38
  }
36
39
 
37
- export function publishSoundsConfigUpdated(): void {
40
+ export function publishSoundsConfigUpdated(originClientId?: string): void {
38
41
  broadcastMessage({ type: "sounds_config_updated" });
39
- void publishSyncInvalidation([SYNC_TAGS.assistantSounds]);
42
+ void publishSyncInvalidation([SYNC_TAGS.assistantSounds], originClientId);
40
43
  }
41
44
 
42
- export function publishSchedulesChanged(): void {
43
- void publishSyncInvalidation([SYNC_TAGS.assistantSchedules]);
45
+ export function publishSchedulesChanged(originClientId?: string): void {
46
+ void publishSyncInvalidation([SYNC_TAGS.assistantSchedules], originClientId);
47
+ }
48
+
49
+ /**
50
+ * Reasons that change the *shape* of the conversation list — a row is
51
+ * added, removed, or its position changes. These require web clients to
52
+ * refetch the paginated list because the row patch path (`refreshConversationRow`)
53
+ * only handles a single known id. Reasons not in this set are *content-only*
54
+ * changes to an existing row (e.g. `seen_changed`, `renamed`) and are
55
+ * delivered exclusively via the per-conversation `sync_changed` tag, which
56
+ * web consumes by GET-and-patching the single row.
57
+ */
58
+ const SHAPE_CHANGING_REASONS: ReadonlySet<ConversationListInvalidatedReason> =
59
+ new Set(["created", "deleted", "reordered"]);
60
+
61
+ /**
62
+ * Publish the legacy `conversation_list_invalidated` broadcast to macOS
63
+ * subscribers only.
64
+ *
65
+ * Web consumes `sync_changed` (`conversationsList` for shape changes,
66
+ * `conversation:<id>:metadata` for content changes) directly and patches
67
+ * the cached list in place — see `useAssistantSyncStream` for the consumer
68
+ * side. macOS (`ConversationRestorer.swift`) still listens for the typed
69
+ * broadcast.
70
+ *
71
+ * TODO(electron-cutover): remove this helper and all callers once macOS
72
+ * migrates to the Electron client and consumes `sync_changed` directly.
73
+ * At that point the `conversation_list_invalidated` message type can be
74
+ * retired entirely.
75
+ */
76
+ function broadcastConversationListInvalidatedToMacos(
77
+ reason: ConversationListInvalidatedReason,
78
+ ): void {
79
+ broadcastMessage(
80
+ {
81
+ type: "conversation_list_invalidated",
82
+ reason,
83
+ },
84
+ undefined,
85
+ { targetInterfaceId: "macos" },
86
+ );
44
87
  }
45
88
 
46
89
  export function publishConversationListChanged(
47
90
  reason: ConversationListInvalidatedReason,
91
+ originClientId?: string,
48
92
  ): void {
49
- broadcastMessage({
50
- type: "conversation_list_invalidated",
51
- reason,
52
- });
53
- void publishSyncInvalidation([SYNC_TAGS.conversationsList]);
93
+ broadcastConversationListInvalidatedToMacos(reason);
94
+ void publishSyncInvalidation([SYNC_TAGS.conversationsList], originClientId);
54
95
  }
55
96
 
56
97
  export function publishConversationMessagesChanged(
57
98
  conversationId: string,
99
+ originClientId?: string,
58
100
  ): void {
59
- void publishSyncInvalidation([conversationMessagesSyncTag(conversationId)]);
101
+ void publishSyncInvalidation(
102
+ [conversationMessagesSyncTag(conversationId)],
103
+ originClientId,
104
+ );
60
105
  }
61
106
 
62
107
  export function publishConversationListAndMetadataChanged(
63
108
  reason: ConversationListInvalidatedReason,
64
109
  conversationIds: string | string[],
110
+ originClientId?: string,
65
111
  ): void {
66
112
  const ids = Array.isArray(conversationIds)
67
113
  ? conversationIds
68
114
  : [conversationIds];
69
- broadcastMessage({
70
- type: "conversation_list_invalidated",
71
- reason,
72
- });
73
- void publishSyncInvalidation([
74
- SYNC_TAGS.conversationsList,
75
- ...ids.map((conversationId) => conversationMetadataSyncTag(conversationId)),
76
- ]);
115
+ broadcastConversationListInvalidatedToMacos(reason);
116
+
117
+ // Shape-changing reasons (`created`, `deleted`, `reordered`) add or
118
+ // remove rows or change the order of the paginated window — web must
119
+ // refetch the list, so include the `conversationsList` umbrella tag.
120
+ // Content-only reasons (`renamed`, `seen_changed`) modify an existing
121
+ // row; web consumes the per-conversation metadata tag and GET-and-
122
+ // patches the single row, avoiding the full paginated list drain
123
+ // (`limit=50&offset=0..N` × foreground + background — ~14 requests
124
+ // per write at a few hundred conversations).
125
+ const tags: string[] = ids.map((conversationId) =>
126
+ conversationMetadataSyncTag(conversationId),
127
+ );
128
+ if (SHAPE_CHANGING_REASONS.has(reason)) {
129
+ tags.unshift(SYNC_TAGS.conversationsList);
130
+ }
131
+ void publishSyncInvalidation(tags, originClientId);
77
132
  }
78
133
 
79
134
  export function publishConversationTitleChanged(
80
135
  conversationId: string,
81
136
  title: string,
137
+ originClientId?: string,
82
138
  ): void {
83
139
  broadcastMessage(
84
140
  {
@@ -88,18 +144,27 @@ export function publishConversationTitleChanged(
88
144
  },
89
145
  conversationId,
90
146
  );
91
- void publishSyncInvalidation([
92
- SYNC_TAGS.conversationsList,
93
- conversationMetadataSyncTag(conversationId),
94
- ]);
147
+ // Renames are content-only — the paired typed `conversation_title_updated`
148
+ // event already carries the new title and patches the row in place on
149
+ // web; macOS receives the per-interface `conversation_list_invalidated`
150
+ // emitted from `broadcastMessage` (see `assistant-event-hub.ts`). The
151
+ // `sync_changed` metadata tag is included as a belt-and-suspenders signal
152
+ // for any sibling-tab consumer that missed the typed event.
153
+ void publishSyncInvalidation(
154
+ [conversationMetadataSyncTag(conversationId)],
155
+ originClientId,
156
+ );
95
157
  }
96
158
 
97
- export function publishConversationInferenceProfileChanged(params: {
98
- conversationId: string;
99
- profile: string | null;
100
- sessionId?: string | null;
101
- expiresAt?: number | null;
102
- }): void {
159
+ export function publishConversationInferenceProfileChanged(
160
+ params: {
161
+ conversationId: string;
162
+ profile: string | null;
163
+ sessionId?: string | null;
164
+ expiresAt?: number | null;
165
+ },
166
+ originClientId?: string,
167
+ ): void {
103
168
  broadcastMessage(
104
169
  {
105
170
  type: "conversation_inference_profile_updated",
@@ -110,8 +175,11 @@ export function publishConversationInferenceProfileChanged(params: {
110
175
  },
111
176
  params.conversationId,
112
177
  );
113
- void publishSyncInvalidation([
114
- SYNC_TAGS.conversationsList,
115
- conversationMetadataSyncTag(params.conversationId),
116
- ]);
178
+ void publishSyncInvalidation(
179
+ [
180
+ SYNC_TAGS.conversationsList,
181
+ conversationMetadataSyncTag(params.conversationId),
182
+ ],
183
+ originClientId,
184
+ );
117
185
  }
@@ -102,4 +102,53 @@ describe("sync publisher", () => {
102
102
  subscription.dispose();
103
103
  }
104
104
  });
105
+
106
+ test("forwards originClientId when provided", async () => {
107
+ const received: AssistantEvent[] = [];
108
+ const subscription = assistantEventHub.subscribe({
109
+ type: "process",
110
+ callback: (event) => {
111
+ received.push(event);
112
+ },
113
+ });
114
+
115
+ try {
116
+ const message = await publishSyncInvalidation(
117
+ [SYNC_TAGS.assistantAvatar],
118
+ "client-abc",
119
+ );
120
+
121
+ await waitFor(() => received.length === 1);
122
+
123
+ expect(message).toEqual({
124
+ type: "sync_changed",
125
+ tags: [SYNC_TAGS.assistantAvatar],
126
+ originClientId: "client-abc",
127
+ });
128
+ expect(received[0].message).toEqual(message);
129
+ } finally {
130
+ subscription.dispose();
131
+ }
132
+ });
133
+
134
+ test("omits originClientId when not provided", async () => {
135
+ const received: AssistantEvent[] = [];
136
+ const subscription = assistantEventHub.subscribe({
137
+ type: "process",
138
+ callback: (event) => {
139
+ received.push(event);
140
+ },
141
+ });
142
+
143
+ try {
144
+ const message = await publishSyncInvalidation([SYNC_TAGS.assistantAvatar]);
145
+
146
+ await waitFor(() => received.length === 1);
147
+
148
+ expect("originClientId" in message).toBe(false);
149
+ expect(received[0].message).toEqual(message);
150
+ } finally {
151
+ subscription.dispose();
152
+ }
153
+ });
105
154
  });
@@ -10,8 +10,9 @@ const log = getLogger("sync-publisher");
10
10
 
11
11
  export async function publishSyncInvalidation(
12
12
  tags: SyncInvalidationTag[],
13
+ originClientId?: string,
13
14
  ): Promise<SyncChangedMessage> {
14
- const message = buildSyncChangedMessage(tags);
15
+ const message = buildSyncChangedMessage(tags, originClientId);
15
16
  try {
16
17
  broadcastMessage(message);
17
18
  } catch (err) {
@@ -234,12 +234,20 @@ export async function startOutbound(
234
234
  params.rebind,
235
235
  originConversationId,
236
236
  );
237
+ } else if (channel === "email") {
238
+ return startOutboundEmail(
239
+ params.destination,
240
+ assistantId,
241
+ channel,
242
+ params.rebind,
243
+ originConversationId,
244
+ );
237
245
  }
238
246
 
239
247
  return {
240
248
  success: false,
241
249
  error: "unsupported_channel",
242
- message: `Outbound verification is only supported for Telegram, phone, and Slack. Got: ${channel}`,
250
+ message: `Outbound verification is not supported for ${channel}. Supported channels: Telegram, phone, Slack, email.`,
243
251
  channel,
244
252
  };
245
253
  }
@@ -582,6 +590,70 @@ function startOutboundSlack(
582
590
  };
583
591
  }
584
592
 
593
+ function startOutboundEmail(
594
+ destination: string | undefined,
595
+ assistantId: string,
596
+ channel: ChannelId,
597
+ rebind?: boolean,
598
+ originConversationId?: string,
599
+ ): OutboundActionResult {
600
+ if (!destination) {
601
+ return {
602
+ success: false,
603
+ error: "missing_destination",
604
+ message:
605
+ "An email address is required for outbound email verification.",
606
+ channel,
607
+ };
608
+ }
609
+
610
+ const normalizedEmail = destination.trim().toLowerCase();
611
+
612
+ const existingBinding = getGuardianBinding(assistantId, channel);
613
+ if (existingBinding && !rebind) {
614
+ return {
615
+ success: false,
616
+ error: "already_bound",
617
+ message:
618
+ "A guardian is already bound for this channel. Set rebind: true to replace.",
619
+ channel,
620
+ };
621
+ }
622
+
623
+ const recentSendCount = countRecentSendsToDestination(
624
+ channel,
625
+ normalizedEmail,
626
+ DESTINATION_RATE_WINDOW_MS,
627
+ );
628
+ if (recentSendCount >= MAX_SENDS_PER_DESTINATION_WINDOW) {
629
+ return {
630
+ success: false,
631
+ error: "rate_limited",
632
+ message:
633
+ "Too many verification attempts to this email address. Please try again later.",
634
+ channel,
635
+ };
636
+ }
637
+
638
+ const sessionResult = createOutboundSession({
639
+ channel,
640
+ expectedExternalUserId: normalizedEmail,
641
+ expectedChatId: normalizedEmail,
642
+ identityBindingStatus: "bound",
643
+ destinationAddress: normalizedEmail,
644
+ verificationPurpose: "guardian",
645
+ });
646
+
647
+ return {
648
+ success: true,
649
+ verificationSessionId: sessionResult.sessionId,
650
+ secret: sessionResult.secret,
651
+ expiresAt: sessionResult.expiresAt,
652
+ channel,
653
+ originConversationId,
654
+ };
655
+ }
656
+
585
657
  // ---------------------------------------------------------------------------
586
658
  // Resend outbound
587
659
  // ---------------------------------------------------------------------------
@@ -38,6 +38,18 @@ export interface LlmUsageTelemetryEvent extends TelemetryEventBase {
38
38
  output_tokens: number;
39
39
  cache_creation_input_tokens: number | null;
40
40
  cache_read_input_tokens: number | null;
41
+ /**
42
+ * The provider's untouched `usage` block. Anthropic surfaces a TTL
43
+ * breakdown under `cache_creation.ephemeral_{5m,1h}_input_tokens`;
44
+ * OpenAI surfaces cached-read counts under
45
+ * `prompt_tokens_details.cached_tokens` (and reasoning tokens under
46
+ * `completion_tokens_details`). Both shapes are forwarded verbatim so
47
+ * downstream consumers (admin charts, dbt models) can extract any
48
+ * provider-specific detail without requiring a schema change here.
49
+ * Null when the provider did not return a usage payload, and for
50
+ * daemons that predate `260-llm-usage-add-raw-usage`.
51
+ */
52
+ raw_usage: Record<string, unknown> | null;
41
53
  actor: string;
42
54
  llm_call_site: LLMCallSite | null;
43
55
  inference_profile: string | null;
@@ -159,6 +159,7 @@ function makeUsageEvent(
159
159
  outputTokens: 50,
160
160
  cacheCreationInputTokens: 10,
161
161
  cacheReadInputTokens: 5,
162
+ rawUsage: null,
162
163
  actor: "main_agent",
163
164
  callSite: null,
164
165
  inferenceProfile: null,
@@ -435,6 +436,53 @@ describe("UsageTelemetryReporter", () => {
435
436
  expect(e.inference_profile_source).toBe("conversation");
436
437
  expect(e.cost).toBe(0.001);
437
438
  expect(e.recorded_at).toBe(1700000099000);
439
+ // raw_usage defaults to null on this fixture (the makeUsageEvent default),
440
+ // confirming the wire shape carries the key as `null` rather than
441
+ // dropping it for legacy rows or providers that did not return a
442
+ // usage block.
443
+ expect(e.raw_usage).toBeNull();
444
+ });
445
+
446
+ test("payload forwards the provider's raw_usage block verbatim", async () => {
447
+ // The reporter must surface the literal usage object the provider
448
+ // returned (Anthropic nests TTL breakdown under `cache_creation`,
449
+ // OpenAI nests cached-read counts under `prompt_tokens_details`,
450
+ // etc.) so downstream consumers can extract any provider-specific
451
+ // detail without a wire-level schema change. Anything that
452
+ // transforms, summarises, or strips fields here destroys data the
453
+ // admin charts and dbt models depend on.
454
+ const rawUsage = {
455
+ input_tokens: 200,
456
+ output_tokens: 100,
457
+ cache_creation_input_tokens: 750,
458
+ cache_creation: {
459
+ ephemeral_5m_input_tokens: 250,
460
+ ephemeral_1h_input_tokens: 500,
461
+ },
462
+ cache_read_input_tokens: 15,
463
+ service_tier: "standard",
464
+ };
465
+ const event = makeUsageEvent({
466
+ id: "evt-raw-usage",
467
+ provider: "anthropic",
468
+ model: "claude-sonnet-4-20250514",
469
+ cacheCreationInputTokens: 750,
470
+ rawUsage,
471
+ });
472
+ mockQueryUnreportedUsageEvents.mockReturnValue([event]);
473
+ mockFetch.mockImplementation(() =>
474
+ Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
475
+ );
476
+
477
+ const reporter = new UsageTelemetryReporter();
478
+ await reporter.flush();
479
+
480
+ const body = JSON.parse(
481
+ (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
482
+ );
483
+ const e = body.events[0];
484
+ expect(e.cache_creation_input_tokens).toBe(750);
485
+ expect(e.raw_usage).toEqual(rawUsage);
438
486
  });
439
487
 
440
488
  test("payload preserves null attribution for historical usage rows", async () => {
@@ -234,6 +234,7 @@ export class UsageTelemetryReporter {
234
234
  output_tokens: e.outputTokens,
235
235
  cache_creation_input_tokens: e.cacheCreationInputTokens ?? null,
236
236
  cache_read_input_tokens: e.cacheReadInputTokens ?? null,
237
+ raw_usage: e.rawUsage,
237
238
  actor: e.actor,
238
239
  llm_call_site: e.callSite,
239
240
  inference_profile: e.inferenceProfile,
@@ -82,6 +82,25 @@ mock.module("../../util/logger.js", () => ({
82
82
  }),
83
83
  }));
84
84
 
85
+ // Stub secure-keys so the `prepareAgentEnv` preflight finds a token without
86
+ // the test having to populate the real OS keyring. Driven via `secureKeyStore`
87
+ // per test in beforeEach; the default seeds a vault token so existing tests
88
+ // (which assume claude spawns succeed) keep passing.
89
+ //
90
+ // The real module's other exports are spread in so transitive importers
91
+ // (e.g. session-manager → pending-interactions → credential-routes, which
92
+ // imports `getSecureKeyResultAsync`) still resolve at parse time. Bun's
93
+ // `mock.module` is process-global and returns *exactly* the keys the factory
94
+ // returns — without the spread, any consumer pulling a non-`getSecureKeyAsync`
95
+ // export errors with "Export named '<X>' not found".
96
+ const secureKeyStore = new Map<string, string>();
97
+ const realSecureKeys = await import("../../security/secure-keys.js");
98
+
99
+ mock.module("../../security/secure-keys.js", () => ({
100
+ ...realSecureKeys,
101
+ getSecureKeyAsync: async (key: string) => secureKeyStore.get(key),
102
+ }));
103
+
85
104
  // Stub session manager so we don't actually spawn child processes.
86
105
  const spawnMock = mock(
87
106
  async (
@@ -131,6 +150,14 @@ beforeEach(() => {
131
150
  _resetAdapterVersionCacheForTests();
132
151
  config.setConfig({ agents: DEFAULT_TEST_AGENTS });
133
152
  which.setWhich((cmd) => `/usr/local/bin/${cmd}`);
153
+ // Default: vault has a claude token so the preflight in `prepareAgentEnv`
154
+ // succeeds for tests that don't care about env injection. Env-injection
155
+ // tests below clear/override this explicitly.
156
+ secureKeyStore.clear();
157
+ secureKeyStore.set(
158
+ "credential/acp/claude_oauth_token",
159
+ "default-test-token",
160
+ );
134
161
  });
135
162
 
136
163
  // ---------------------------------------------------------------------------
@@ -376,3 +403,95 @@ describe("executeAcpSpawn — per-agent resume hint", () => {
376
403
  expect(payload.message).not.toContain("To resume this session later");
377
404
  });
378
405
  });
406
+
407
+ // ---------------------------------------------------------------------------
408
+ // executeAcpSpawn — CLAUDE_CODE_OAUTH_TOKEN env injection + preflight
409
+ //
410
+ // Mirrors the HTTP-route test block in
411
+ // `runtime/routes/acp-routes.test.ts` — the skill tool calls into the same
412
+ // `prepareAgentEnv` helper, and the contract must be identical so a
413
+ // missing token fails the spawn at the tool boundary (`isError: true`)
414
+ // instead of letting a token-less subprocess zombie out. PR-history
415
+ // context: the inline env-injection used to live in the route only; this
416
+ // tool path was bypassing it entirely, causing every skill-driven ACP
417
+ // spawn to die on first prompt with "Authentication required". Pin both
418
+ // the happy paths and the throw path here so future drift on either
419
+ // caller fails the suite loudly.
420
+ // ---------------------------------------------------------------------------
421
+
422
+ describe("executeAcpSpawn — CLAUDE_CODE_OAUTH_TOKEN injection", () => {
423
+ test("injects CLAUDE_CODE_OAUTH_TOKEN from the secure store for the claude agent", async () => {
424
+ secureKeyStore.clear();
425
+ secureKeyStore.set(
426
+ "credential/acp/claude_oauth_token",
427
+ "tool-vault-token-abc",
428
+ );
429
+ execScripts.set("npm ls", { error: new Error("npm not installed") });
430
+ execScripts.set("npm view", { error: new Error("npm not installed") });
431
+
432
+ const result = await executeAcpSpawn(
433
+ { agent: "claude", task: "do something" },
434
+ makeContext(),
435
+ );
436
+
437
+ expect(result.isError).toBe(false);
438
+ expect(spawnMock).toHaveBeenCalledTimes(1);
439
+ const agentConfigArg = spawnMock.mock.calls[0][1] as {
440
+ env?: Record<string, string>;
441
+ };
442
+ expect(agentConfigArg.env?.CLAUDE_CODE_OAUTH_TOKEN).toBe(
443
+ "tool-vault-token-abc",
444
+ );
445
+ });
446
+
447
+ test("accepts CLAUDE_CODE_OAUTH_TOKEN from config.json agent.env without a vault entry", async () => {
448
+ // Mirrors the route-level precedence test: a config.json env override
449
+ // is the first-priority provisioning route. The resolver surfaces it
450
+ // on `resolved.agent.env`, which the helper preserves.
451
+ secureKeyStore.clear();
452
+ config.setConfig({
453
+ agents: {
454
+ claude: {
455
+ command: "claude-agent-acp",
456
+ args: [],
457
+ env: { CLAUDE_CODE_OAUTH_TOKEN: "tool-config-token-xyz" },
458
+ },
459
+ codex: { command: "codex-acp", args: [] },
460
+ "unknown-agent": { command: "some-other-binary", args: [] },
461
+ },
462
+ });
463
+ execScripts.set("npm ls", { error: new Error("npm not installed") });
464
+ execScripts.set("npm view", { error: new Error("npm not installed") });
465
+
466
+ const result = await executeAcpSpawn(
467
+ { agent: "claude", task: "do something" },
468
+ makeContext(),
469
+ );
470
+
471
+ expect(result.isError).toBe(false);
472
+ const agentConfigArg = spawnMock.mock.calls[0][1] as {
473
+ env?: Record<string, string>;
474
+ };
475
+ expect(agentConfigArg.env?.CLAUDE_CODE_OAUTH_TOKEN).toBe(
476
+ "tool-config-token-xyz",
477
+ );
478
+ });
479
+
480
+ test("returns isError when no token is available from either route (preflight throw mapped to tool result)", async () => {
481
+ // Both routes empty. The helper throws `FailedDependencyError`; the
482
+ // tool catches it and returns `{ isError: true, content: <msg> }`
483
+ // rather than letting it propagate (the tool boundary is a sync
484
+ // ToolExecutionResult, not an HTTP response).
485
+ secureKeyStore.clear();
486
+
487
+ const result = await executeAcpSpawn(
488
+ { agent: "claude", task: "do something" },
489
+ makeContext(),
490
+ );
491
+
492
+ expect(result.isError).toBe(true);
493
+ expect(result.content).toContain("CLAUDE_CODE_OAUTH_TOKEN");
494
+ // Spawn was NEVER called — preflight fired before the subprocess started.
495
+ expect(spawnMock).not.toHaveBeenCalled();
496
+ });
497
+ });
@@ -1,6 +1,7 @@
1
1
  import { execFile } from "node:child_process";
2
2
 
3
3
  import { getAcpSessionManager } from "../../acp/index.js";
4
+ import { prepareAgentEnv } from "../../acp/prepare-agent-env.js";
4
5
  import { resolveAcpAgent } from "../../acp/resolve-agent.js";
5
6
  import { DEFAULT_AGENT_NPM_PACKAGES } from "../../config/acp-defaults.js";
6
7
  import { getLogger } from "../../util/logger.js";
@@ -159,8 +160,6 @@ export async function executeAcpSpawn(
159
160
  }
160
161
  }
161
162
  }
162
- const agentConfig = resolved.agent;
163
-
164
163
  const sendToClient = context.sendToClient as
165
164
  | ((msg: { type: string; [key: string]: unknown }) => void)
166
165
  | undefined;
@@ -171,6 +170,20 @@ export async function executeAcpSpawn(
171
170
  };
172
171
  }
173
172
 
173
+ // Inject required env vars and preflight via the shared helper. Mirrors
174
+ // the HTTP route at `runtime/routes/acp-routes.ts:spawnSession` — both
175
+ // call sites MUST go through `prepareAgentEnv` before `manager.spawn`,
176
+ // otherwise the spawned subprocess starts with no auth and dies as a
177
+ // zombie after the first prompt. See `acp/prepare-agent-env.ts` for
178
+ // the full rationale.
179
+ let agentConfig;
180
+ try {
181
+ agentConfig = await prepareAgentEnv(resolved.agent);
182
+ } catch (err) {
183
+ const msg = err instanceof Error ? err.message : String(err);
184
+ return { content: msg, isError: true };
185
+ }
186
+
174
187
  // Best-effort version check — never blocks the spawn. If outdated, we
175
188
  // append a non-blocking warning to the success payload.
176
189
  const versionInfo = await checkAdapterVersion(agentConfig.command);
@@ -9,7 +9,6 @@
9
9
  */
10
10
 
11
11
  import { RiskLevel } from "../../permissions/types.js";
12
- import type { ToolDefinition } from "../../providers/types.js";
13
12
  import type { Tool, ToolExecutionResult } from "../types.js";
14
13
 
15
14
  // ---------------------------------------------------------------------------
@@ -33,12 +32,9 @@ const appOpenTool: Tool = {
33
32
  category: "apps",
34
33
  defaultRiskLevel: RiskLevel.Low,
35
34
  executionMode: "proxy",
35
+ executionTarget: "host",
36
36
 
37
- getDefinition(): ToolDefinition {
38
- return {
39
- name: this.name,
40
- description: this.description,
41
- input_schema: {
37
+ input_schema: {
42
38
  type: "object",
43
39
  properties: {
44
40
  app_id: {
@@ -54,8 +50,6 @@ const appOpenTool: Tool = {
54
50
  },
55
51
  required: ["app_id"],
56
52
  },
57
- };
58
- },
59
53
 
60
54
  execute: proxyExecute,
61
55
  };
@@ -51,7 +51,7 @@ const singleQ = {
51
51
 
52
52
  describe("AskQuestionTool definition", () => {
53
53
  test("exposes the expected schema shape and description language", () => {
54
- const def = new AskQuestionTool().getDefinition();
54
+ const def = new AskQuestionTool();
55
55
  expect(def.name).toBe("ask_question");
56
56
  expect(def.description).toContain("free-text fallback is always added");
57
57
  expect(def.description).toContain("do not");
@@ -463,8 +463,8 @@ describe("AskQuestionTool batched input", () => {
463
463
 
464
464
  describe("AskQuestionTool definition (batched schema)", () => {
465
465
  test("exposes `questions[]` shape, keeps legacy fields, omits per-question id", () => {
466
- const def = new AskQuestionTool().getDefinition();
467
- const schema = def.input_schema as {
466
+ const def = new AskQuestionTool();
467
+ const schema = def.input_schema as unknown as {
468
468
  properties: Record<
469
469
  string,
470
470
  {