@vellumai/assistant 0.6.2 → 0.6.3

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 (396) hide show
  1. package/bun.lock +40 -40
  2. package/bunfig.toml +3 -0
  3. package/docs/architecture/memory.md +1 -1
  4. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
  5. package/openapi.yaml +184 -69
  6. package/package.json +41 -41
  7. package/scripts/generate-openapi.ts +1 -2
  8. package/src/__tests__/acp-session.test.ts +43 -0
  9. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
  10. package/src/__tests__/app-executors.test.ts +1 -0
  11. package/src/__tests__/app-source-watcher.test.ts +37 -11
  12. package/src/__tests__/approval-routes-http.test.ts +178 -1
  13. package/src/__tests__/browser-fill-credential.test.ts +229 -94
  14. package/src/__tests__/browser-manager.test.ts +40 -27
  15. package/src/__tests__/catalog-files.test.ts +862 -0
  16. package/src/__tests__/channel-approvals.test.ts +53 -0
  17. package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
  18. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  19. package/src/__tests__/config-schema.test.ts +125 -48
  20. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
  21. package/src/__tests__/context-overflow-approval.test.ts +16 -1
  22. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  23. package/src/__tests__/conversation-agent-loop.test.ts +1 -1
  24. package/src/__tests__/conversation-analysis-routes.test.ts +2 -2
  25. package/src/__tests__/conversation-attachments.test.ts +80 -4
  26. package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
  27. package/src/__tests__/conversation-fork-crud.test.ts +17 -0
  28. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  29. package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
  30. package/src/__tests__/conversation-inject-context.test.ts +103 -0
  31. package/src/__tests__/conversation-queue.test.ts +45 -2
  32. package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
  33. package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
  34. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  35. package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
  36. package/src/__tests__/conversation-starter-routes.test.ts +126 -0
  37. package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
  38. package/src/__tests__/conversation-store.test.ts +195 -0
  39. package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
  40. package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
  41. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  42. package/src/__tests__/credential-vault-unit.test.ts +4 -4
  43. package/src/__tests__/credential-vault.test.ts +152 -13
  44. package/src/__tests__/credentials-cli.test.ts +2 -2
  45. package/src/__tests__/date-context.test.ts +4 -4
  46. package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
  47. package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
  48. package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
  49. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  50. package/src/__tests__/gemini-provider.test.ts +2 -2
  51. package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
  52. package/src/__tests__/headless-browser-interactions.test.ts +707 -371
  53. package/src/__tests__/headless-browser-navigate.test.ts +389 -47
  54. package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
  55. package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
  56. package/src/__tests__/host-bash-proxy.test.ts +150 -1
  57. package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
  58. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
  59. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
  60. package/src/__tests__/host-browser-event-routes.test.ts +350 -0
  61. package/src/__tests__/host-browser-proxy.test.ts +444 -0
  62. package/src/__tests__/host-browser-routes.test.ts +198 -0
  63. package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
  64. package/src/__tests__/host-cu-proxy.test.ts +171 -1
  65. package/src/__tests__/host-file-proxy.test.ts +185 -1
  66. package/src/__tests__/host-file-read-tool.test.ts +52 -0
  67. package/src/__tests__/host-proxy-interface.test.ts +165 -0
  68. package/src/__tests__/host-shell-tool.test.ts +1 -11
  69. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  70. package/src/__tests__/integration-status.test.ts +6 -7
  71. package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
  72. package/src/__tests__/mcp-client-auth.test.ts +40 -4
  73. package/src/__tests__/mcp-health-check.test.ts +10 -3
  74. package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
  75. package/src/__tests__/migration-export-http.test.ts +61 -2
  76. package/src/__tests__/migration-export-streaming.test.ts +66 -0
  77. package/src/__tests__/migration-import-commit-http.test.ts +101 -1
  78. package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
  79. package/src/__tests__/oauth-apps-routes.test.ts +17 -12
  80. package/src/__tests__/oauth-cli.test.ts +707 -60
  81. package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
  82. package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
  83. package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
  84. package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
  85. package/src/__tests__/oauth-providers-routes.test.ts +50 -14
  86. package/src/__tests__/oauth-store.test.ts +1386 -182
  87. package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
  88. package/src/__tests__/onboarding-template-contract.test.ts +75 -57
  89. package/src/__tests__/openai-provider.test.ts +2 -2
  90. package/src/__tests__/outlook-categories.test.ts +1 -1
  91. package/src/__tests__/outlook-client-automation.test.ts +1 -1
  92. package/src/__tests__/outlook-compose-tools.test.ts +1 -1
  93. package/src/__tests__/outlook-email-watcher.test.ts +1 -1
  94. package/src/__tests__/outlook-follow-up.test.ts +1 -1
  95. package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
  96. package/src/__tests__/outlook-trash.test.ts +1 -1
  97. package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
  98. package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
  99. package/src/__tests__/permission-mode.test.ts +28 -56
  100. package/src/__tests__/platform-callback-registration.test.ts +19 -0
  101. package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
  102. package/src/__tests__/proxy-approval-callback.test.ts +18 -0
  103. package/src/__tests__/require-fresh-approval.test.ts +40 -1
  104. package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
  105. package/src/__tests__/schedule-routes.test.ts +162 -0
  106. package/src/__tests__/secret-detection-handler.test.ts +84 -0
  107. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  108. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  109. package/src/__tests__/set-permission-mode.test.ts +13 -250
  110. package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
  111. package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
  112. package/src/__tests__/slack-channel-config.test.ts +12 -15
  113. package/src/__tests__/subagent-detail.test.ts +44 -2
  114. package/src/__tests__/subagent-disposal.test.ts +1 -0
  115. package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
  116. package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
  117. package/src/__tests__/subagent-manager-notify.test.ts +1 -0
  118. package/src/__tests__/subagent-notify-parent.test.ts +1 -0
  119. package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
  120. package/src/__tests__/subagent-tools.test.ts +1 -0
  121. package/src/__tests__/subagent-types.test.ts +1 -0
  122. package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
  123. package/src/__tests__/system-prompt.test.ts +72 -1
  124. package/src/__tests__/task-scheduler.test.ts +32 -6
  125. package/src/__tests__/telegram-config.test.ts +10 -13
  126. package/src/__tests__/terminal-tools.test.ts +9 -0
  127. package/src/__tests__/tool-approval-handler.test.ts +73 -0
  128. package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
  129. package/src/__tests__/top-level-renderer.test.ts +73 -1
  130. package/src/__tests__/transport-hints-queue.test.ts +14 -29
  131. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
  132. package/src/__tests__/v2-consent-policy.test.ts +103 -0
  133. package/src/acp/client-handler.ts +30 -4
  134. package/src/agent/loop.ts +12 -6
  135. package/src/approvals/guardian-request-resolvers.ts +21 -15
  136. package/src/browser-session/__tests__/manager.test.ts +297 -0
  137. package/src/browser-session/backends/cdp-inspect.ts +30 -0
  138. package/src/browser-session/backends/extension.ts +26 -0
  139. package/src/browser-session/backends/local.ts +24 -0
  140. package/src/browser-session/events.ts +164 -0
  141. package/src/browser-session/index.ts +27 -0
  142. package/src/browser-session/manager.ts +159 -0
  143. package/src/browser-session/types.ts +28 -0
  144. package/src/channels/__tests__/types.test.ts +134 -0
  145. package/src/channels/types.ts +53 -3
  146. package/src/cli/commands/browser-relay.ts +339 -409
  147. package/src/cli/commands/credentials.ts +3 -3
  148. package/src/cli/commands/email.ts +18 -13
  149. package/src/cli/commands/mcp.ts +16 -4
  150. package/src/cli/commands/oauth/__tests__/connect.test.ts +44 -44
  151. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
  152. package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
  153. package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
  154. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
  155. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
  156. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
  157. package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
  158. package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
  159. package/src/cli/commands/oauth/apps.ts +7 -4
  160. package/src/cli/commands/oauth/connect.ts +6 -3
  161. package/src/cli/commands/oauth/disconnect.ts +1 -1
  162. package/src/cli/commands/oauth/providers.ts +200 -36
  163. package/src/cli/commands/oauth/shared.ts +5 -5
  164. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
  165. package/src/cli/commands/platform/index.ts +107 -10
  166. package/src/cli/commands/usage.ts +10 -9
  167. package/src/cli/lib/daemon-credential-client.ts +4 -0
  168. package/src/cli/program.ts +1 -1
  169. package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
  170. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
  171. package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
  172. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
  173. package/src/config/bundled-skills/contacts/SKILL.md +3 -0
  174. package/src/config/bundled-skills/document/SKILL.md +4 -0
  175. package/src/config/bundled-skills/gmail/SKILL.md +1 -1
  176. package/src/config/bundled-skills/outlook/SKILL.md +7 -0
  177. package/src/config/bundled-skills/subagent/SKILL.md +21 -0
  178. package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
  179. package/src/config/bundled-skills/tasks/SKILL.md +5 -0
  180. package/src/config/env-registry.ts +14 -0
  181. package/src/config/env.ts +21 -0
  182. package/src/config/feature-flag-registry.json +44 -5
  183. package/src/config/loader.ts +56 -1
  184. package/src/config/sanitize-for-transfer.ts +47 -0
  185. package/src/config/schema.ts +46 -5
  186. package/src/config/schemas/host-browser.ts +66 -0
  187. package/src/config/schemas/memory-lifecycle.ts +1 -1
  188. package/src/config/schemas/memory-retrieval.ts +103 -0
  189. package/src/config/schemas/security.ts +0 -6
  190. package/src/config/schemas/services.ts +8 -0
  191. package/src/config/types.ts +0 -1
  192. package/src/context/post-turn-tool-result-truncation.ts +176 -0
  193. package/src/context/window-manager.ts +19 -1
  194. package/src/credential-execution/approval-bridge.ts +49 -15
  195. package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
  196. package/src/daemon/app-source-watcher.ts +35 -0
  197. package/src/daemon/context-overflow-approval.ts +5 -0
  198. package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
  199. package/src/daemon/conversation-agent-loop.ts +58 -24
  200. package/src/daemon/conversation-attachments.ts +40 -0
  201. package/src/daemon/conversation-process.ts +48 -1
  202. package/src/daemon/conversation-runtime-assembly.ts +118 -36
  203. package/src/daemon/conversation-surfaces.ts +37 -36
  204. package/src/daemon/conversation-tool-setup.ts +74 -8
  205. package/src/daemon/conversation-workspace.ts +12 -0
  206. package/src/daemon/conversation.ts +226 -8
  207. package/src/daemon/date-context.ts +10 -10
  208. package/src/daemon/first-greeting.ts +3 -2
  209. package/src/daemon/handlers/conversations.ts +9 -140
  210. package/src/daemon/handlers/shared.ts +58 -0
  211. package/src/daemon/handlers/skills.ts +232 -37
  212. package/src/daemon/host-bash-proxy.ts +48 -13
  213. package/src/daemon/host-browser-proxy.ts +191 -0
  214. package/src/daemon/host-cu-proxy.ts +36 -11
  215. package/src/daemon/host-file-proxy.ts +57 -9
  216. package/src/daemon/lifecycle.ts +65 -11
  217. package/src/daemon/message-protocol.ts +7 -0
  218. package/src/daemon/message-types/conversations.ts +55 -13
  219. package/src/daemon/message-types/host-browser.ts +100 -0
  220. package/src/daemon/message-types/messages.ts +5 -5
  221. package/src/daemon/message-types/skills.ts +10 -0
  222. package/src/daemon/message-types/subagents.ts +2 -0
  223. package/src/daemon/server.ts +92 -12
  224. package/src/daemon/tool-side-effects.ts +6 -0
  225. package/src/daemon/transport-hints.ts +5 -24
  226. package/src/inbound/platform-callback-registration.ts +18 -17
  227. package/src/mcp/client.ts +59 -24
  228. package/src/memory/app-store.ts +31 -1
  229. package/src/memory/conversation-crud.ts +23 -0
  230. package/src/memory/conversation-starters-cadence.ts +76 -0
  231. package/src/memory/conversation-title-service.ts +5 -2
  232. package/src/memory/db-init.ts +12 -0
  233. package/src/memory/embedding-backend.test.ts +75 -0
  234. package/src/memory/embedding-backend.ts +131 -5
  235. package/src/memory/embedding-gemini.test.ts +54 -0
  236. package/src/memory/embedding-gemini.ts +20 -9
  237. package/src/memory/embedding-local.ts +176 -17
  238. package/src/memory/graph/consolidation.ts +10 -23
  239. package/src/memory/graph/extraction-job.ts +15 -0
  240. package/src/memory/graph/retriever.ts +40 -22
  241. package/src/memory/graph/store.test.ts +7 -3
  242. package/src/memory/graph/store.ts +47 -12
  243. package/src/memory/llm-usage-store.ts +45 -4
  244. package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
  245. package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
  246. package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
  247. package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
  248. package/src/memory/migrations/217-conversation-host-access.ts +40 -0
  249. package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
  250. package/src/memory/migrations/index.ts +6 -0
  251. package/src/memory/migrations/registry.ts +8 -0
  252. package/src/memory/schema/conversations.ts +1 -0
  253. package/src/memory/schema/oauth.ts +18 -13
  254. package/src/oauth/AGENTS.md +76 -0
  255. package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
  256. package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
  257. package/src/oauth/byo-connection.test.ts +8 -8
  258. package/src/oauth/byo-connection.ts +7 -7
  259. package/src/oauth/connect-orchestrator.ts +23 -21
  260. package/src/oauth/connect-types.ts +3 -3
  261. package/src/oauth/connection-resolver.test.ts +17 -4
  262. package/src/oauth/connection-resolver.ts +16 -16
  263. package/src/oauth/connection.ts +1 -1
  264. package/src/oauth/manual-token-connection.ts +13 -13
  265. package/src/oauth/oauth-store.ts +214 -100
  266. package/src/oauth/platform-connection.test.ts +3 -3
  267. package/src/oauth/platform-connection.ts +4 -4
  268. package/src/oauth/provider-serializer.ts +31 -5
  269. package/src/oauth/revoke.ts +76 -0
  270. package/src/oauth/seed-providers.ts +126 -87
  271. package/src/oauth/token-persistence.ts +1 -1
  272. package/src/permissions/permission-mode.ts +4 -11
  273. package/src/permissions/prompter.ts +13 -1
  274. package/src/permissions/v2-consent-policy.ts +87 -0
  275. package/src/prompts/system-prompt.ts +18 -21
  276. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
  277. package/src/prompts/templates/BOOTSTRAP.md +59 -105
  278. package/src/providers/anthropic/client.ts +1 -0
  279. package/src/providers/types.ts +1 -1
  280. package/src/runtime/AGENTS.md +23 -0
  281. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
  282. package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
  283. package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
  284. package/src/runtime/assistant-event-hub.ts +2 -2
  285. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
  286. package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
  287. package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
  288. package/src/runtime/auth/middleware.ts +98 -0
  289. package/src/runtime/auth/route-policy.ts +6 -7
  290. package/src/runtime/capability-tokens.ts +414 -0
  291. package/src/runtime/channel-approvals.ts +18 -5
  292. package/src/runtime/chrome-extension-registry.ts +332 -0
  293. package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
  294. package/src/runtime/guardian-decision-types.ts +7 -0
  295. package/src/runtime/http-server.ts +425 -70
  296. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
  297. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
  298. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
  299. package/src/runtime/migrations/migration-transport.ts +6 -0
  300. package/src/runtime/migrations/migration-wizard.ts +22 -2
  301. package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
  302. package/src/runtime/migrations/vbundle-builder.ts +145 -38
  303. package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
  304. package/src/runtime/migrations/vbundle-importer.ts +55 -5
  305. package/src/runtime/pending-interactions.ts +29 -13
  306. package/src/runtime/routes/approval-routes.ts +90 -16
  307. package/src/runtime/routes/browser-cdp-routes.ts +229 -0
  308. package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
  309. package/src/runtime/routes/conversation-analysis-routes.ts +2 -1
  310. package/src/runtime/routes/conversation-management-routes.ts +108 -0
  311. package/src/runtime/routes/conversation-routes.ts +301 -27
  312. package/src/runtime/routes/conversation-starter-routes.ts +78 -16
  313. package/src/runtime/routes/guardian-action-routes.ts +24 -13
  314. package/src/runtime/routes/host-browser-routes.ts +279 -0
  315. package/src/runtime/routes/host-file-routes.ts +9 -1
  316. package/src/runtime/routes/identity-routes.ts +259 -16
  317. package/src/runtime/routes/log-export-routes.ts +42 -22
  318. package/src/runtime/routes/memory-item-routes.ts +1 -7
  319. package/src/runtime/routes/migration-routes.ts +87 -2
  320. package/src/runtime/routes/oauth-apps.ts +15 -17
  321. package/src/runtime/routes/oauth-providers.ts +4 -0
  322. package/src/runtime/routes/schedule-routes.ts +24 -11
  323. package/src/runtime/routes/settings-routes.ts +9 -97
  324. package/src/runtime/routes/skills-routes.ts +52 -2
  325. package/src/runtime/routes/subagents-routes.ts +14 -10
  326. package/src/runtime/routes/usage-routes.ts +8 -7
  327. package/src/runtime/routes/workspace-routes.test.ts +22 -0
  328. package/src/runtime/routes/workspace-routes.ts +8 -1
  329. package/src/runtime/routes/workspace-utils.ts +2 -0
  330. package/src/schedule/scheduler.ts +7 -5
  331. package/src/security/ces-credential-client.ts +20 -0
  332. package/src/security/ces-rpc-credential-backend.ts +17 -0
  333. package/src/security/credential-backend.ts +5 -0
  334. package/src/security/oauth2.ts +42 -25
  335. package/src/security/secure-keys.ts +118 -25
  336. package/src/security/token-manager.ts +23 -10
  337. package/src/skills/catalog-files.ts +492 -0
  338. package/src/subagent/manager.ts +131 -26
  339. package/src/subagent/types.ts +19 -0
  340. package/src/tools/apps/executors.ts +11 -2
  341. package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
  342. package/src/tools/browser/auth-detector.ts +43 -12
  343. package/src/tools/browser/browser-execution.ts +645 -340
  344. package/src/tools/browser/browser-manager.ts +36 -12
  345. package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
  346. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
  347. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
  348. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
  349. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
  350. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
  351. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
  352. package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
  353. package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
  354. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
  355. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
  356. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
  357. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
  358. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
  359. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
  360. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
  361. package/src/tools/browser/cdp-client/errors.ts +34 -0
  362. package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
  363. package/src/tools/browser/cdp-client/factory.ts +204 -0
  364. package/src/tools/browser/cdp-client/index.ts +14 -0
  365. package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
  366. package/src/tools/browser/cdp-client/types.ts +52 -0
  367. package/src/tools/filesystem/edit.ts +1 -1
  368. package/src/tools/filesystem/list.ts +1 -1
  369. package/src/tools/filesystem/read.ts +1 -1
  370. package/src/tools/filesystem/write.ts +2 -1
  371. package/src/tools/host-filesystem/edit.ts +1 -1
  372. package/src/tools/host-filesystem/read.ts +12 -15
  373. package/src/tools/host-filesystem/write.ts +1 -1
  374. package/src/tools/host-terminal/host-shell.ts +21 -16
  375. package/src/tools/permission-checker.ts +77 -82
  376. package/src/tools/registry.ts +0 -2
  377. package/src/tools/secret-detection-handler.ts +34 -0
  378. package/src/tools/shared/filesystem/image-read.ts +61 -40
  379. package/src/tools/subagent/spawn.ts +47 -3
  380. package/src/tools/subagent/status.ts +2 -0
  381. package/src/tools/system/register.ts +2 -16
  382. package/src/tools/terminal/safe-env.ts +7 -0
  383. package/src/tools/terminal/shell.ts +21 -16
  384. package/src/tools/tool-approval-handler.ts +48 -2
  385. package/src/tools/types.ts +2 -0
  386. package/src/util/platform.ts +14 -19
  387. package/src/workspace/top-level-renderer.ts +19 -1
  388. package/src/__tests__/chrome-cdp.test.ts +0 -419
  389. package/src/__tests__/permission-mode-sse.test.ts +0 -418
  390. package/src/__tests__/permission-mode-store.test.ts +0 -277
  391. package/src/browser-extension-relay/protocol.ts +0 -63
  392. package/src/browser-extension-relay/server.ts +0 -203
  393. package/src/config/schemas/sandbox.ts +0 -14
  394. package/src/permissions/permission-mode-store.ts +0 -180
  395. package/src/tools/browser/chrome-cdp.ts +0 -239
  396. package/src/tools/system/set-permission-mode.ts +0 -103
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Module-level event bus for out-of-band browser-session signals.
3
+ *
4
+ * The `host_browser_event` and `host_browser_session_invalidated`
5
+ * envelopes (see `assistant/src/daemon/message-types/host-browser.ts`)
6
+ * carry unsolicited CDP events and detach notifications from the
7
+ * chrome extension to the daemon. Unlike `host_browser_result`, these
8
+ * frames are not tied to a specific in-flight request and cannot be
9
+ * routed through `pending-interactions`. Instead they publish into
10
+ * this bus, where tool-side consumers subscribe to react to the signal.
11
+ *
12
+ * Two distinct surfaces:
13
+ *
14
+ * 1. **CDP event listeners** — free-form subscribers that observe
15
+ * every incoming `host_browser_event`. Primarily a seam for
16
+ * future work (event-driven session tracking, lifecycle
17
+ * instrumentation, tool-side reactive hooks) and for tests that
18
+ * need to assert that events were routed.
19
+ *
20
+ * 2. **Invalidated target registry** — a short-lived set of target
21
+ * ids that the extension has reported as detached. The
22
+ * `BrowserSessionManager` checks this set on its next `send()`
23
+ * and evicts any matching session before dispatch so the
24
+ * extension dispatcher can re-attach fresh. Entries are
25
+ * consumed on first lookup to keep the set from growing
26
+ * unbounded across long-running processes.
27
+ *
28
+ * Both surfaces are intentionally transport-agnostic — the WS and
29
+ * HTTP paths both publish through the same module so the routing
30
+ * semantics stay in lockstep.
31
+ */
32
+
33
+ import { getLogger } from "../util/logger.js";
34
+
35
+ const log = getLogger("browser-session-events");
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // CDP event listener surface
39
+ // ---------------------------------------------------------------------------
40
+
41
+ /**
42
+ * A forwarded CDP event as consumed by runtime-side subscribers. The
43
+ * wire shape comes from `HostBrowserEvent` in
44
+ * `assistant/src/daemon/message-types/host-browser.ts` and is stripped
45
+ * of its `type` discriminator here for ergonomic subscriber code.
46
+ */
47
+ export interface ForwardedCdpEvent {
48
+ /** CDP event method name, e.g. "Page.frameNavigated". */
49
+ method: string;
50
+ /** CDP event params forwarded verbatim. Opaque to the bus. */
51
+ params?: unknown;
52
+ /**
53
+ * Optional CDP session id — present for flat child sessions routed
54
+ * through `Target.attachToTarget` with `flatten: true`.
55
+ */
56
+ cdpSessionId?: string;
57
+ }
58
+
59
+ export type CdpEventListener = (event: ForwardedCdpEvent) => void;
60
+
61
+ const cdpEventListeners = new Set<CdpEventListener>();
62
+
63
+ /**
64
+ * Subscribe to forwarded CDP events. Returns an unsubscribe function;
65
+ * callers MUST invoke it at end-of-lifecycle to avoid leaking closures
66
+ * into the module-level set. Listener errors are caught and logged so
67
+ * a broken subscriber cannot take down the WS dispatch path.
68
+ */
69
+ export function onCdpEvent(listener: CdpEventListener): () => void {
70
+ cdpEventListeners.add(listener);
71
+ return () => {
72
+ cdpEventListeners.delete(listener);
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Fan an incoming CDP event out to all registered listeners. Called
78
+ * by the WS frame dispatcher in
79
+ * `assistant/src/runtime/routes/host-browser-routes.ts` after a
80
+ * `host_browser_event` envelope has been validated.
81
+ */
82
+ export function publishCdpEvent(event: ForwardedCdpEvent): void {
83
+ for (const listener of cdpEventListeners) {
84
+ try {
85
+ listener(event);
86
+ } catch (err) {
87
+ log.warn(
88
+ { err, method: event.method },
89
+ "CDP event listener threw — suppressing to protect dispatcher",
90
+ );
91
+ }
92
+ }
93
+ }
94
+
95
+ // ---------------------------------------------------------------------------
96
+ // Invalidated target registry
97
+ // ---------------------------------------------------------------------------
98
+
99
+ /**
100
+ * Short-lived set of target ids the chrome extension has reported as
101
+ * detached since the last `consumeInvalidatedTargetId` lookup. The
102
+ * `BrowserSessionManager.invalidateByTargetId` path reads entries
103
+ * out of this set on each `send()` and evicts any matching session
104
+ * before dispatch.
105
+ *
106
+ * Entries are consumed on first read so the set never grows
107
+ * unbounded. If a future consumer needs multiple reads of the same
108
+ * invalidation, a separate long-lived registry should be introduced
109
+ * — this set is scoped to the minimum viable behaviour for
110
+ * "next command forces reattach".
111
+ */
112
+ const invalidatedTargetIds = new Set<string>();
113
+
114
+ /**
115
+ * Record that the chrome extension has reported a target as
116
+ * detached. Idempotent — re-marking a target is a no-op. Logs at
117
+ * debug because the signal is benign (just a lifecycle notification)
118
+ * and noisy in high-churn workloads.
119
+ */
120
+ export function markTargetInvalidated(targetId: string, reason?: string): void {
121
+ invalidatedTargetIds.add(targetId);
122
+ log.debug({ targetId, reason }, "browser-session target invalidated");
123
+ }
124
+
125
+ /**
126
+ * Peek at whether a given target id is currently marked invalidated
127
+ * without consuming the entry. Primarily used by tests — production
128
+ * consumers should call {@link consumeInvalidatedTargetId} so the
129
+ * set stays bounded.
130
+ */
131
+ export function isTargetInvalidated(targetId: string): boolean {
132
+ return invalidatedTargetIds.has(targetId);
133
+ }
134
+
135
+ /**
136
+ * Test-only helper: snapshot the current invalidated set without
137
+ * draining it. Exported so unit tests can assert exact set contents
138
+ * across multiple frames; production code must not rely on this.
139
+ */
140
+ export function __peekInvalidatedTargetIdsForTests(): string[] {
141
+ return Array.from(invalidatedTargetIds);
142
+ }
143
+
144
+ /**
145
+ * Atomically remove and return a target id from the invalidated set.
146
+ * Returns `true` when the id was present (and has now been removed),
147
+ * `false` otherwise. Designed to be called by
148
+ * `BrowserSessionManager.send()` so the first dispatch after a
149
+ * detach forces a reattach and subsequent dispatches proceed normally.
150
+ */
151
+ export function consumeInvalidatedTargetId(targetId: string): boolean {
152
+ return invalidatedTargetIds.delete(targetId);
153
+ }
154
+
155
+ /**
156
+ * Test-only helper: clear the entire invalidated set. Not exported
157
+ * from any public index — used by unit tests that need a clean slate
158
+ * between cases. Not safe to call from production code because it
159
+ * would race against concurrent invalidations.
160
+ */
161
+ export function __resetBrowserSessionEventsForTests(): void {
162
+ cdpEventListeners.clear();
163
+ invalidatedTargetIds.clear();
164
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * BrowserSessionManager — multi-backend session router for host_browser.
3
+ *
4
+ * This module is the single CDP backend selector for browser tools. The
5
+ * `cdp-client` factory (`assistant/src/tools/browser/cdp-client/factory.ts`)
6
+ * constructs a BrowserSessionManager per tool invocation, registers the
7
+ * appropriate backend from a three-way selection:
8
+ *
9
+ * 1. **Extension** — selected when `hostBrowserProxy` is present (macOS
10
+ * desktop / cloud-hosted with a chrome-extension bound to the
11
+ * conversation).
12
+ * 2. **cdp-inspect** — selected when the extension is absent and
13
+ * `hostBrowser.cdpInspect.enabled` is `true` in config. Attaches to
14
+ * an already-running Chrome via `--remote-debugging-port`.
15
+ * 3. **Local** — default when neither of the above applies.
16
+ * Drives a Playwright-backed sacrificial-profile Chromium.
17
+ *
18
+ * The factory exposes a `ScopedCdpClient` that routes `send()` through
19
+ * the manager. This gives every call site a single choke point for
20
+ * session invalidation and future multi-tab routing.
21
+ */
22
+ export * from "./backends/cdp-inspect.js";
23
+ export * from "./backends/extension.js";
24
+ export * from "./backends/local.js";
25
+ export * from "./events.js";
26
+ export * from "./manager.js";
27
+ export * from "./types.js";
@@ -0,0 +1,159 @@
1
+ import { v4 as uuid } from "uuid";
2
+
3
+ import { consumeInvalidatedTargetId } from "./events.js";
4
+ import type {
5
+ BrowserBackend,
6
+ BrowserSession,
7
+ CdpCommand,
8
+ CdpResult,
9
+ } from "./types.js";
10
+
11
+ export interface BrowserSessionManagerOptions {
12
+ /** Ordered list of backends to try; first available wins. */
13
+ backends: BrowserBackend[];
14
+ }
15
+
16
+ export class BrowserSessionManager {
17
+ private backends: BrowserBackend[];
18
+ private sessions = new Map<string, BrowserSession>();
19
+
20
+ constructor(opts: BrowserSessionManagerOptions) {
21
+ this.backends = opts.backends;
22
+ }
23
+
24
+ /** Pick an available backend or throw. */
25
+ selectBackend(): BrowserBackend {
26
+ const b = this.backends.find((x) => x.isAvailable());
27
+ if (!b) throw new Error("No available browser backend");
28
+ return b;
29
+ }
30
+
31
+ createSession(): BrowserSession {
32
+ const backend = this.selectBackend();
33
+ const session: BrowserSession = { id: uuid(), backendKind: backend.kind };
34
+ this.sessions.set(session.id, session);
35
+ return session;
36
+ }
37
+
38
+ getSession(id: string): BrowserSession | undefined {
39
+ return this.sessions.get(id);
40
+ }
41
+
42
+ /**
43
+ * Dispatch a CDP command.
44
+ *
45
+ * - If `sessionId` is provided, the session must exist in the manager; otherwise this throws.
46
+ * The command is routed through the backend whose `kind` matches the session's `backendKind`,
47
+ * ensuring per-session backend isolation and making `disposeSession()` an actual enforcement
48
+ * boundary against stale ids. If the session has an opaque `targetId` and the command does
49
+ * not already carry its own CDP `sessionId`, the manager injects the session's `targetId`
50
+ * as the CDP `sessionId` so backends can multiplex commands across multiple tabs/targets.
51
+ * - If `sessionId` is `undefined`, the first available backend is selected for one-off
52
+ * commands without a session handle (e.g. transport health probes).
53
+ */
54
+ async send(
55
+ sessionId: string | undefined,
56
+ command: CdpCommand,
57
+ signal?: AbortSignal,
58
+ ): Promise<CdpResult> {
59
+ let backend: BrowserBackend;
60
+ let outgoing = command;
61
+ if (sessionId !== undefined) {
62
+ const session = this.sessions.get(sessionId);
63
+ if (!session) {
64
+ throw new Error(`Unknown browser session: ${sessionId}`);
65
+ }
66
+ // If the chrome extension has reported this session's target
67
+ // as detached since the last dispatch, evict the session and
68
+ // throw so the caller can create a fresh one. Reading (and
69
+ // consuming) the invalidation flag here keeps the "next
70
+ // command forces reattach" semantics in lockstep with the
71
+ // host_browser_session_invalidated envelope handler — without
72
+ // this check the manager would happily forward a CDP command
73
+ // against a torn-down target and hit a permanent failure.
74
+ if (
75
+ session.targetId !== undefined &&
76
+ consumeInvalidatedTargetId(session.targetId)
77
+ ) {
78
+ this.sessions.delete(sessionId);
79
+ throw new Error(
80
+ `Browser session ${sessionId} was invalidated (target ${session.targetId} detached)`,
81
+ );
82
+ }
83
+ const matched = this.backends.find((b) => b.kind === session.backendKind);
84
+ if (!matched) {
85
+ throw new Error(
86
+ `No backend available for session kind: ${session.backendKind}`,
87
+ );
88
+ }
89
+ backend = matched;
90
+ // If the session has an opaque targetId and the command does not
91
+ // carry its own CDP sessionId, inject the session's targetId as
92
+ // the CDP sessionId. Backends that support multi-target routing
93
+ // will forward it; backends that ignore it will treat the call
94
+ // as "most-recent-tab" as before.
95
+ if (session.targetId !== undefined && command.sessionId === undefined) {
96
+ outgoing = { ...command, sessionId: session.targetId };
97
+ }
98
+ } else {
99
+ backend = this.selectBackend();
100
+ }
101
+ return backend.send(outgoing, signal);
102
+ }
103
+
104
+ disposeSession(id: string): void {
105
+ this.sessions.delete(id);
106
+ }
107
+
108
+ /**
109
+ * Evict a session that the backend has informed us is no longer
110
+ * valid — e.g. the chrome extension dispatched a
111
+ * `host_browser_session_invalidated` envelope after Chrome detached
112
+ * the debugger from the underlying tab/target.
113
+ *
114
+ * Functionally equivalent to {@link disposeSession} today (both
115
+ * remove the session from the manager's map so a subsequent
116
+ * `send()` throws "Unknown browser session") but preserved as a
117
+ * distinct method so call sites can stay explicit about intent.
118
+ * Callers that receive a detach/invalidated signal should use this
119
+ * method; callers that are cleaning up at end-of-lifecycle should
120
+ * use {@link disposeSession}.
121
+ *
122
+ * Returns `true` when a session was actually removed, `false` when
123
+ * no session with that id was tracked. Returning a boolean lets
124
+ * transport-level dispatchers (see
125
+ * `resolveHostBrowserSessionInvalidated`) log at the right level
126
+ * based on whether the invalidation had any effect.
127
+ */
128
+ invalidateSession(id: string): boolean {
129
+ return this.sessions.delete(id);
130
+ }
131
+
132
+ /**
133
+ * Evict every session whose opaque `targetId` matches the supplied
134
+ * id. Used by the WS dispatcher when a `host_browser_session_invalidated`
135
+ * envelope arrives without a manager-level session id: the
136
+ * extension-side dispatcher only knows its own `tabId` / `targetId`
137
+ * and does not carry our uuid session handle.
138
+ *
139
+ * Returns the number of sessions removed. A zero return does not
140
+ * necessarily indicate an error — the target may not have a
141
+ * runtime-side session attached to it yet, or the session may
142
+ * already have been disposed by its owning tool.
143
+ */
144
+ invalidateByTargetId(targetId: string): number {
145
+ let removed = 0;
146
+ for (const [id, session] of this.sessions) {
147
+ if (session.targetId === targetId) {
148
+ this.sessions.delete(id);
149
+ removed += 1;
150
+ }
151
+ }
152
+ return removed;
153
+ }
154
+
155
+ disposeAll(): void {
156
+ for (const b of this.backends) b.dispose();
157
+ this.sessions.clear();
158
+ }
159
+ }
@@ -0,0 +1,28 @@
1
+ export type BrowserBackendKind = "extension" | "local" | "cdp-inspect";
2
+
3
+ export interface CdpCommand {
4
+ method: string;
5
+ params?: Record<string, unknown>;
6
+ sessionId?: string;
7
+ }
8
+
9
+ export interface CdpResult {
10
+ /** Raw CDP result object; opaque to the manager. */
11
+ result?: unknown;
12
+ /** CDP error envelope if the command failed. */
13
+ error?: { code: number; message: string; data?: unknown };
14
+ }
15
+
16
+ export interface BrowserSession {
17
+ id: string;
18
+ backendKind: BrowserBackendKind;
19
+ /** Opaque target/sessionId from the backend. Omitted for "most-recent-tab" commands. */
20
+ targetId?: string;
21
+ }
22
+
23
+ export interface BrowserBackend {
24
+ kind: BrowserBackendKind;
25
+ isAvailable(): boolean;
26
+ send(command: CdpCommand, signal?: AbortSignal): Promise<CdpResult>;
27
+ dispose(): void;
28
+ }
@@ -0,0 +1,134 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ INTERACTIVE_INTERFACES,
5
+ INTERFACE_IDS,
6
+ isInterfaceId,
7
+ supportsHostProxy,
8
+ } from "../types.js";
9
+
10
+ describe("INTERFACE_IDS", () => {
11
+ test("includes chrome-extension", () => {
12
+ expect(
13
+ (INTERFACE_IDS as readonly string[]).includes("chrome-extension"),
14
+ ).toBe(true);
15
+ });
16
+
17
+ test("still includes macos and other existing interfaces", () => {
18
+ for (const id of [
19
+ "macos",
20
+ "ios",
21
+ "cli",
22
+ "telegram",
23
+ "phone",
24
+ "vellum",
25
+ "whatsapp",
26
+ "slack",
27
+ "email",
28
+ ]) {
29
+ expect((INTERFACE_IDS as readonly string[]).includes(id)).toBe(true);
30
+ }
31
+ });
32
+ });
33
+
34
+ describe("INTERACTIVE_INTERFACES", () => {
35
+ test("does NOT include chrome-extension", () => {
36
+ // Chrome extensions don't render SSE-backed prompter UI, so they must
37
+ // stay out of the interactive set even though they have an InterfaceId.
38
+ expect(INTERACTIVE_INTERFACES.has("chrome-extension" as never)).toBe(false);
39
+ });
40
+
41
+ test("still includes macos", () => {
42
+ expect(INTERACTIVE_INTERFACES.has("macos")).toBe(true);
43
+ });
44
+ });
45
+
46
+ describe("isInterfaceId", () => {
47
+ test("returns true for chrome-extension", () => {
48
+ expect(isInterfaceId("chrome-extension")).toBe(true);
49
+ });
50
+
51
+ test("returns true for macos", () => {
52
+ expect(isInterfaceId("macos")).toBe(true);
53
+ });
54
+
55
+ test("returns false for unknown interface", () => {
56
+ expect(isInterfaceId("safari-extension")).toBe(false);
57
+ });
58
+ });
59
+
60
+ describe("supportsHostProxy", () => {
61
+ // ── macOS: supports host_bash / host_file / host_cu, but NOT host_browser. ──
62
+ test("macos returns true (no capability)", () => {
63
+ expect(supportsHostProxy("macos")).toBe(true);
64
+ });
65
+
66
+ test("macos returns true for host_bash", () => {
67
+ expect(supportsHostProxy("macos", "host_bash")).toBe(true);
68
+ });
69
+
70
+ test("macos returns true for host_file", () => {
71
+ expect(supportsHostProxy("macos", "host_file")).toBe(true);
72
+ });
73
+
74
+ test("macos returns true for host_cu", () => {
75
+ expect(supportsHostProxy("macos", "host_cu")).toBe(true);
76
+ });
77
+
78
+ test("macos returns false for host_browser", () => {
79
+ expect(supportsHostProxy("macos", "host_browser")).toBe(false);
80
+ });
81
+
82
+ // ── chrome-extension: only host_browser. ──
83
+ test("chrome-extension returns false (no capability)", () => {
84
+ // Chrome extension does not support "any host proxy at all" — it only
85
+ // supports host_browser, so the no-arg form must return false to keep
86
+ // existing call sites that guard desktop-only behavior unchanged.
87
+ expect(supportsHostProxy("chrome-extension")).toBe(false);
88
+ });
89
+
90
+ test("chrome-extension returns true for host_browser", () => {
91
+ expect(supportsHostProxy("chrome-extension", "host_browser")).toBe(true);
92
+ });
93
+
94
+ test("chrome-extension returns false for host_bash", () => {
95
+ expect(supportsHostProxy("chrome-extension", "host_bash")).toBe(false);
96
+ });
97
+
98
+ test("chrome-extension returns false for host_file", () => {
99
+ expect(supportsHostProxy("chrome-extension", "host_file")).toBe(false);
100
+ });
101
+
102
+ test("chrome-extension returns false for host_cu", () => {
103
+ expect(supportsHostProxy("chrome-extension", "host_cu")).toBe(false);
104
+ });
105
+
106
+ // ── Non-supporting interfaces: false in all forms. ──
107
+ test("cli returns false (no capability)", () => {
108
+ expect(supportsHostProxy("cli")).toBe(false);
109
+ });
110
+
111
+ test("cli returns false for host_bash", () => {
112
+ expect(supportsHostProxy("cli", "host_bash")).toBe(false);
113
+ });
114
+
115
+ test("cli returns false for host_browser", () => {
116
+ expect(supportsHostProxy("cli", "host_browser")).toBe(false);
117
+ });
118
+
119
+ test("telegram returns false (no capability)", () => {
120
+ expect(supportsHostProxy("telegram")).toBe(false);
121
+ });
122
+
123
+ test("telegram returns false for host_browser", () => {
124
+ expect(supportsHostProxy("telegram", "host_browser")).toBe(false);
125
+ });
126
+
127
+ test("vellum returns false (no capability)", () => {
128
+ expect(supportsHostProxy("vellum")).toBe(false);
129
+ });
130
+
131
+ test("email returns false for host_browser", () => {
132
+ expect(supportsHostProxy("email", "host_browser")).toBe(false);
133
+ });
134
+ });
@@ -48,6 +48,7 @@ export const INTERFACE_IDS = [
48
48
  "whatsapp",
49
49
  "slack",
50
50
  "email",
51
+ "chrome-extension",
51
52
  ] as const;
52
53
 
53
54
  export type InterfaceId = (typeof INTERFACE_IDS)[number];
@@ -90,9 +91,58 @@ export function isInteractiveInterface(id: InterfaceId): boolean {
90
91
  return INTERACTIVE_INTERFACES.has(id);
91
92
  }
92
93
 
93
- /** Whether the interface supports host proxies (bash, file, computer-use). */
94
- export function supportsHostProxy(id: InterfaceId): boolean {
95
- return id === "macos";
94
+ /**
95
+ * Host proxy capabilities that an interface can support. The macOS client
96
+ * supports all four; the chrome-extension interface only supports
97
+ * host_browser (via the Chrome DevTools Protocol proxy).
98
+ */
99
+ export type HostProxyCapability =
100
+ | "host_bash"
101
+ | "host_file"
102
+ | "host_cu"
103
+ | "host_browser";
104
+
105
+ /**
106
+ * Interfaces that support the full desktop host-proxy set (all four
107
+ * `HostProxyCapability` values). This is the capability-level identity used
108
+ * by the discriminated transport metadata union and by the
109
+ * `supportsHostProxy(id)` type predicate.
110
+ *
111
+ * Extend this literal type AND the `supportsHostProxy` implementation
112
+ * below in lock-step when adding a new host-capable client (e.g. a native
113
+ * Linux or Windows desktop).
114
+ */
115
+ export type HostProxyInterfaceId = "macos";
116
+
117
+ /**
118
+ * Whether the interface supports a host proxy capability.
119
+ *
120
+ * The no-arg form `supportsHostProxy(id)` asks "is this interface a desktop
121
+ * host-proxy client?" — it returns `true` only for macOS and is the type
122
+ * predicate that narrows `InterfaceId` to `HostProxyInterfaceId`. It returns
123
+ * `false` for chrome-extension because chrome-extension only supports
124
+ * `host_browser`, and the no-arg form is the gate that legacy desktop-only
125
+ * call sites use (e.g. preactivating computer-use, restoring host proxies
126
+ * in the drain queue). Callers that want to check a single capability —
127
+ * for example, to decide whether to keep `hostBrowserProxy` available for
128
+ * chrome-extension — should pass the capability explicitly:
129
+ * `supportsHostProxy(id, "host_browser")`.
130
+ */
131
+ export function supportsHostProxy(id: InterfaceId): id is HostProxyInterfaceId;
132
+ export function supportsHostProxy(
133
+ id: InterfaceId,
134
+ capability: HostProxyCapability,
135
+ ): boolean;
136
+ export function supportsHostProxy(
137
+ id: InterfaceId,
138
+ capability?: HostProxyCapability,
139
+ ): boolean {
140
+ // host_browser is excluded for macos because the proxy path requires a
141
+ // Chrome extension that isn't guaranteed to be attached; browser tools
142
+ // fall back to the local Playwright Chromium instead.
143
+ if (id === "macos") return capability !== "host_browser";
144
+ if (id === "chrome-extension" && capability === "host_browser") return true;
145
+ return false;
96
146
  }
97
147
 
98
148
  export interface TurnInterfaceContext {