@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,103 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ import { _setOverridesForTesting } from "../config/assistant-feature-flags.js";
4
+ import type { ToolContext } from "../tools/types.js";
5
+
6
+ const hostAccessByConversation = new Map<string, boolean>();
7
+
8
+ mock.module("../memory/conversation-crud.js", () => ({
9
+ getConversationHostAccess: (conversationId: string) =>
10
+ hostAccessByConversation.get(conversationId) ?? false,
11
+ }));
12
+
13
+ function makeContext(overrides?: Partial<ToolContext>): ToolContext {
14
+ return {
15
+ workingDir: "/tmp/test",
16
+ conversationId: "test-conv",
17
+ trustClass: "guardian",
18
+ isInteractive: true,
19
+ ...overrides,
20
+ } as ToolContext;
21
+ }
22
+
23
+ beforeEach(() => {
24
+ _setOverridesForTesting({});
25
+ hostAccessByConversation.clear();
26
+ });
27
+
28
+ afterEach(() => {
29
+ _setOverridesForTesting({});
30
+ hostAccessByConversation.clear();
31
+ });
32
+
33
+ describe("v2-consent-policy", () => {
34
+ test("returns legacy when the flag is disabled", async () => {
35
+ const { evaluateV2ConsentDisposition } =
36
+ await import("../permissions/v2-consent-policy.js");
37
+
38
+ expect(evaluateV2ConsentDisposition("host_bash", {}, makeContext())).toBe(
39
+ "legacy",
40
+ );
41
+ });
42
+
43
+ test("auto-allows non-host tools when the flag is enabled", async () => {
44
+ _setOverridesForTesting({ "permission-controls-v2": true });
45
+ const { evaluateV2ConsentDisposition } =
46
+ await import("../permissions/v2-consent-policy.js");
47
+
48
+ expect(evaluateV2ConsentDisposition("bash", {}, makeContext())).toBe(
49
+ "auto_allow",
50
+ );
51
+ });
52
+
53
+ test("uses conversation-scoped host access when the flag is enabled", async () => {
54
+ _setOverridesForTesting({ "permission-controls-v2": true });
55
+ hostAccessByConversation.set("allowed-conv", true);
56
+ hostAccessByConversation.set("blocked-conv", false);
57
+ const { evaluateV2ConsentDisposition } =
58
+ await import("../permissions/v2-consent-policy.js");
59
+
60
+ expect(
61
+ evaluateV2ConsentDisposition(
62
+ "host_bash",
63
+ {},
64
+ makeContext({ conversationId: "allowed-conv" }),
65
+ ),
66
+ ).toBe("auto_allow");
67
+ expect(
68
+ evaluateV2ConsentDisposition(
69
+ "host_bash",
70
+ {},
71
+ makeContext({ conversationId: "blocked-conv" }),
72
+ ),
73
+ ).toBe("prompt_host_access");
74
+ });
75
+
76
+ test("host-access prompts are identified by the stripped-down prompt shape", async () => {
77
+ _setOverridesForTesting({ "permission-controls-v2": true });
78
+ const {
79
+ CONVERSATION_HOST_ACCESS_PROMPT,
80
+ isConversationHostAccessEnablePrompt,
81
+ } = await import("../permissions/v2-consent-policy.js");
82
+
83
+ expect(
84
+ isConversationHostAccessEnablePrompt({
85
+ toolName: "host_bash",
86
+ ...CONVERSATION_HOST_ACCESS_PROMPT,
87
+ }),
88
+ ).toBe(true);
89
+ expect(
90
+ isConversationHostAccessEnablePrompt({
91
+ toolName: "host_bash",
92
+ ...CONVERSATION_HOST_ACCESS_PROMPT,
93
+ allowlistOptions: [
94
+ {
95
+ label: "Specific command",
96
+ description: "legacy rule",
97
+ pattern: "bash:*",
98
+ },
99
+ ],
100
+ }),
101
+ ).toBe(false);
102
+ });
103
+ });
@@ -32,6 +32,7 @@ import type {
32
32
 
33
33
  import type { ServerMessage } from "../daemon/message-protocol.js";
34
34
  import type { UserDecision } from "../permissions/types.js";
35
+ import { isPermissionControlsV2Enabled } from "../permissions/v2-consent-policy.js";
35
36
  import * as pendingInteractions from "../runtime/pending-interactions.js";
36
37
  import { getLogger } from "../util/logger.js";
37
38
 
@@ -184,6 +185,15 @@ export class VellumAcpClientHandler implements Client {
184
185
  kind: opt.kind,
185
186
  }));
186
187
 
188
+ if (isPermissionControlsV2Enabled()) {
189
+ const allowOptionId = findAllowOptionId(options);
190
+ return {
191
+ outcome: allowOptionId
192
+ ? { outcome: "selected", optionId: allowOptionId }
193
+ : { outcome: "cancelled" },
194
+ };
195
+ }
196
+
187
197
  // Send the confirmation_request first — this triggers makeEventSender
188
198
  // which registers a normal "confirmation" entry in pendingInteractions.
189
199
  this.sendToVellum({
@@ -422,15 +432,31 @@ function mapDecisionToOptionId(
422
432
  const alwaysDeny = options.find((o) => o.kind === "reject_always");
423
433
  if (alwaysDeny) return alwaysDeny.optionId;
424
434
  }
425
- const denyOpt =
426
- options.find((o) => o.kind === "reject_once") ??
427
- options.find((o) => o.kind === "reject_always");
428
- if (denyOpt) return denyOpt.optionId;
435
+ const denyOpt = findRejectOptionId(options);
436
+ if (denyOpt) return denyOpt;
429
437
 
430
438
  // Fallback: return first option
431
439
  return options[0]?.optionId ?? "deny";
432
440
  }
433
441
 
442
+ function findRejectOptionId(
443
+ options: Array<{ optionId: string; kind: string }>,
444
+ ): string | undefined {
445
+ return (
446
+ options.find((o) => o.kind === "reject_once")?.optionId ??
447
+ options.find((o) => o.kind === "reject_always")?.optionId
448
+ );
449
+ }
450
+
451
+ function findAllowOptionId(
452
+ options: Array<{ optionId: string; kind: string }>,
453
+ ): string | undefined {
454
+ return (
455
+ options.find((o) => o.kind === "allow_once")?.optionId ??
456
+ options.find((o) => o.kind === "allow_always")?.optionId
457
+ );
458
+ }
459
+
434
460
  /**
435
461
  * Extracts text from a ContentBlock.
436
462
  */
package/src/agent/loop.ts CHANGED
@@ -82,7 +82,12 @@ export type AgentEvent =
82
82
  toolUseId: string;
83
83
  input: Record<string, unknown>;
84
84
  }
85
- | { type: "server_tool_complete"; toolUseId: string; isError: boolean }
85
+ | {
86
+ type: "server_tool_complete";
87
+ toolUseId: string;
88
+ isError: boolean;
89
+ content?: unknown[];
90
+ }
86
91
  | { type: "error"; error: Error }
87
92
  | {
88
93
  type: "usage";
@@ -98,7 +103,7 @@ export type AgentEvent =
98
103
  };
99
104
 
100
105
  const DEFAULT_CONFIG: AgentLoopConfig = {
101
- maxTokens: 16000,
106
+ maxTokens: 64000,
102
107
  effort: "high",
103
108
  minTurnIntervalMs: 150,
104
109
  };
@@ -344,6 +349,7 @@ export class AgentLoop {
344
349
  type: "server_tool_complete",
345
350
  toolUseId: event.toolUseId,
346
351
  isError: event.isError,
352
+ ...(event.content ? { content: event.content } : {}),
347
353
  });
348
354
  }
349
355
  },
@@ -709,10 +715,10 @@ export function compactAxTreeHistory(messages: Message[]): Message[] {
709
715
  * once by the LLM on the turn it was captured, then replaced with a text
710
716
  * placeholder on subsequent turns.
711
717
  *
712
- * We look for the last user message with tool_results (not just the last user
713
- * message) because the empty-response nudge path appends a text-only user
714
- * message after the tool results. Preserving images in that case ensures
715
- * the model still sees the screenshot on the retry.
718
+ * We target the last user message with tool_results (not just the last user
719
+ * message) because a plain-text user message may follow the tool-result
720
+ * turn. Using the last user message unconditionally would leave the most
721
+ * recent tool screenshots unprotected from stripping.
716
722
  */
717
723
  function stripOldImageBlocks(history: Message[]): Message[] {
718
724
  // Find the last user message that contains tool_result blocks.
@@ -26,6 +26,7 @@ import {
26
26
  } from "../notifications/signal.js";
27
27
  import { addRule } from "../permissions/trust-store.js";
28
28
  import type { UserDecision } from "../permissions/types.js";
29
+ import { isPermissionControlsV2Enabled } from "../permissions/v2-consent-policy.js";
29
30
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
30
31
  import { mintDaemonDeliveryToken } from "../runtime/auth/token-service.js";
31
32
  import type { ApprovalAction } from "../runtime/channel-approval-types.js";
@@ -192,8 +193,9 @@ const pendingInteractionResolver: GuardianRequestResolver = {
192
193
  // Handle approve_always: persist a trust rule when the confirmation
193
194
  // explicitly allows persistence and provides explicit options.
194
195
  if (
195
- decision.action === "approve_always" ||
196
- decision.action === "approve_once"
196
+ !isPermissionControlsV2Enabled() &&
197
+ (decision.action === "approve_always" ||
198
+ decision.action === "approve_once")
197
199
  ) {
198
200
  const details = interaction.confirmationDetails;
199
201
  if (
@@ -237,19 +239,23 @@ const pendingInteractionResolver: GuardianRequestResolver = {
237
239
  // temporal modes (approve_10m, approve_conversation) reach the session with
238
240
  // the correct UserDecision instead of collapsing to plain "allow".
239
241
  let userDecision: UserDecision;
240
- switch (decision.action) {
241
- case "reject":
242
- userDecision = "deny";
243
- break;
244
- case "approve_10m":
245
- userDecision = "allow_10m";
246
- break;
247
- case "approve_conversation":
248
- userDecision = "allow_conversation";
249
- break;
250
- default:
251
- userDecision = "allow";
252
- break;
242
+ if (isPermissionControlsV2Enabled()) {
243
+ userDecision = decision.action === "reject" ? "deny" : "allow";
244
+ } else {
245
+ switch (decision.action) {
246
+ case "reject":
247
+ userDecision = "deny";
248
+ break;
249
+ case "approve_10m":
250
+ userDecision = "allow_10m";
251
+ break;
252
+ case "approve_conversation":
253
+ userDecision = "allow_conversation";
254
+ break;
255
+ default:
256
+ userDecision = "allow";
257
+ break;
258
+ }
253
259
  }
254
260
  resolved.conversation!.handleConfirmationResponse(
255
261
  request.id,
@@ -0,0 +1,297 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ type BrowserBackend,
5
+ BrowserSessionManager,
6
+ type CdpCommand,
7
+ type CdpResult,
8
+ createExtensionBackend,
9
+ createLocalBackend,
10
+ } from "../index.js";
11
+
12
+ interface MockBackendState {
13
+ available: boolean;
14
+ disposed: boolean;
15
+ lastCommand?: CdpCommand;
16
+ lastSignal?: AbortSignal;
17
+ sendImpl?: (command: CdpCommand, signal?: AbortSignal) => Promise<CdpResult>;
18
+ }
19
+
20
+ function createMockExtensionBackend(state: MockBackendState): BrowserBackend {
21
+ return createExtensionBackend({
22
+ isAvailable: () => state.available,
23
+ sendCdp: async (command, signal) => {
24
+ state.lastCommand = command;
25
+ state.lastSignal = signal;
26
+ if (state.sendImpl) return state.sendImpl(command, signal);
27
+ return { result: { ok: true } };
28
+ },
29
+ dispose: () => {
30
+ state.disposed = true;
31
+ },
32
+ });
33
+ }
34
+
35
+ function createMockLocalBackend(state: MockBackendState): BrowserBackend {
36
+ return createLocalBackend({
37
+ isAvailable: () => state.available,
38
+ sendCdp: async (command, signal) => {
39
+ state.lastCommand = command;
40
+ state.lastSignal = signal;
41
+ if (state.sendImpl) return state.sendImpl(command, signal);
42
+ return { result: { ok: true, kind: "local" } };
43
+ },
44
+ dispose: () => {
45
+ state.disposed = true;
46
+ },
47
+ });
48
+ }
49
+
50
+ describe("BrowserSessionManager", () => {
51
+ test("selectBackend throws when no backend is available", () => {
52
+ const state: MockBackendState = { available: false, disposed: false };
53
+ const manager = new BrowserSessionManager({
54
+ backends: [createMockExtensionBackend(state)],
55
+ });
56
+ expect(() => manager.selectBackend()).toThrow(
57
+ "No available browser backend",
58
+ );
59
+ });
60
+
61
+ test("selectBackend returns the extension backend when available", () => {
62
+ const state: MockBackendState = { available: true, disposed: false };
63
+ const backend = createMockExtensionBackend(state);
64
+ const manager = new BrowserSessionManager({ backends: [backend] });
65
+ const selected = manager.selectBackend();
66
+ expect(selected.kind).toBe("extension");
67
+ expect(selected).toBe(backend);
68
+ });
69
+
70
+ test("createSession returns a session with a new uuid stored in the map", () => {
71
+ const state: MockBackendState = { available: true, disposed: false };
72
+ const manager = new BrowserSessionManager({
73
+ backends: [createMockExtensionBackend(state)],
74
+ });
75
+ const session = manager.createSession();
76
+ expect(session.id).toBeTruthy();
77
+ expect(session.backendKind).toBe("extension");
78
+ // Lookup round-trips.
79
+ expect(manager.getSession(session.id)).toEqual(session);
80
+ // Two sessions get unique ids.
81
+ const another = manager.createSession();
82
+ expect(another.id).not.toBe(session.id);
83
+ });
84
+
85
+ test("send delegates to backend.send and returns the CDP result", async () => {
86
+ const expectedResult: CdpResult = { result: { value: 42 } };
87
+ const state: MockBackendState = {
88
+ available: true,
89
+ disposed: false,
90
+ sendImpl: async () => expectedResult,
91
+ };
92
+ const manager = new BrowserSessionManager({
93
+ backends: [createMockExtensionBackend(state)],
94
+ });
95
+ const result = await manager.send(undefined, {
96
+ method: "Browser.getVersion",
97
+ params: { foo: "bar" },
98
+ });
99
+ expect(result).toEqual(expectedResult);
100
+ expect(state.lastCommand).toEqual({
101
+ method: "Browser.getVersion",
102
+ params: { foo: "bar" },
103
+ });
104
+ });
105
+
106
+ test("send with an aborted signal propagates the abort", async () => {
107
+ const state: MockBackendState = {
108
+ available: true,
109
+ disposed: false,
110
+ sendImpl: async (_command, signal) => {
111
+ if (signal?.aborted) {
112
+ throw new Error("aborted");
113
+ }
114
+ return { result: { ok: true } };
115
+ },
116
+ };
117
+ const manager = new BrowserSessionManager({
118
+ backends: [createMockExtensionBackend(state)],
119
+ });
120
+ const controller = new AbortController();
121
+ controller.abort();
122
+ await expect(
123
+ manager.send(
124
+ undefined,
125
+ { method: "Browser.getVersion" },
126
+ controller.signal,
127
+ ),
128
+ ).rejects.toThrow("aborted");
129
+ expect(state.lastSignal).toBe(controller.signal);
130
+ });
131
+
132
+ test("disposeAll calls backend.dispose and clears the session map", () => {
133
+ const state: MockBackendState = { available: true, disposed: false };
134
+ const manager = new BrowserSessionManager({
135
+ backends: [createMockExtensionBackend(state)],
136
+ });
137
+ const session = manager.createSession();
138
+ expect(manager.getSession(session.id)).toBeDefined();
139
+ manager.disposeAll();
140
+ expect(state.disposed).toBe(true);
141
+ expect(manager.getSession(session.id)).toBeUndefined();
142
+ });
143
+
144
+ test("send with a known sessionId routes through the matching backend", async () => {
145
+ const expectedResult: CdpResult = { result: { routed: true } };
146
+ const state: MockBackendState = {
147
+ available: true,
148
+ disposed: false,
149
+ sendImpl: async () => expectedResult,
150
+ };
151
+ const manager = new BrowserSessionManager({
152
+ backends: [createMockExtensionBackend(state)],
153
+ });
154
+ const session = manager.createSession();
155
+ const result = await manager.send(session.id, {
156
+ method: "Browser.getVersion",
157
+ });
158
+ expect(result).toEqual(expectedResult);
159
+ expect(state.lastCommand).toEqual({ method: "Browser.getVersion" });
160
+ });
161
+
162
+ test("send with an unknown sessionId throws", async () => {
163
+ const state: MockBackendState = { available: true, disposed: false };
164
+ const manager = new BrowserSessionManager({
165
+ backends: [createMockExtensionBackend(state)],
166
+ });
167
+ await expect(
168
+ manager.send("does-not-exist", { method: "Browser.getVersion" }),
169
+ ).rejects.toThrow("Unknown browser session: does-not-exist");
170
+ // The mock backend should not have received the command.
171
+ expect(state.lastCommand).toBeUndefined();
172
+ });
173
+
174
+ test("send with a sessionId of a disposed session throws", async () => {
175
+ const state: MockBackendState = { available: true, disposed: false };
176
+ const manager = new BrowserSessionManager({
177
+ backends: [createMockExtensionBackend(state)],
178
+ });
179
+ const session = manager.createSession();
180
+ manager.disposeSession(session.id);
181
+ await expect(
182
+ manager.send(session.id, { method: "Browser.getVersion" }),
183
+ ).rejects.toThrow(`Unknown browser session: ${session.id}`);
184
+ expect(state.lastCommand).toBeUndefined();
185
+ });
186
+
187
+ test("selectBackend returns a local backend when it is the only registration", () => {
188
+ const state: MockBackendState = { available: true, disposed: false };
189
+ const backend = createMockLocalBackend(state);
190
+ const manager = new BrowserSessionManager({ backends: [backend] });
191
+ const selected = manager.selectBackend();
192
+ expect(selected.kind).toBe("local");
193
+ expect(selected).toBe(backend);
194
+ });
195
+
196
+ test("createSession tags sessions with the local backend kind", () => {
197
+ const state: MockBackendState = { available: true, disposed: false };
198
+ const manager = new BrowserSessionManager({
199
+ backends: [createMockLocalBackend(state)],
200
+ });
201
+ const session = manager.createSession();
202
+ expect(session.backendKind).toBe("local");
203
+ });
204
+
205
+ test("send routes through local backend when its session is used", async () => {
206
+ const state: MockBackendState = { available: true, disposed: false };
207
+ const manager = new BrowserSessionManager({
208
+ backends: [createMockLocalBackend(state)],
209
+ });
210
+ const session = manager.createSession();
211
+ const result = await manager.send(session.id, {
212
+ method: "Runtime.evaluate",
213
+ params: { expression: "1+1" },
214
+ });
215
+ expect(result).toEqual({ result: { ok: true, kind: "local" } });
216
+ expect(state.lastCommand).toEqual({
217
+ method: "Runtime.evaluate",
218
+ params: { expression: "1+1" },
219
+ });
220
+ });
221
+
222
+ test("selectBackend falls back to the first available backend when earlier ones are unavailable", () => {
223
+ const extState: MockBackendState = { available: false, disposed: false };
224
+ const localState: MockBackendState = { available: true, disposed: false };
225
+ const ext = createMockExtensionBackend(extState);
226
+ const local = createMockLocalBackend(localState);
227
+ const manager = new BrowserSessionManager({ backends: [ext, local] });
228
+ const selected = manager.selectBackend();
229
+ expect(selected.kind).toBe("local");
230
+ expect(selected).toBe(local);
231
+ });
232
+
233
+ test("selectBackend prefers the first available backend", () => {
234
+ const extState: MockBackendState = { available: true, disposed: false };
235
+ const localState: MockBackendState = { available: true, disposed: false };
236
+ const ext = createMockExtensionBackend(extState);
237
+ const local = createMockLocalBackend(localState);
238
+ const manager = new BrowserSessionManager({ backends: [ext, local] });
239
+ const selected = manager.selectBackend();
240
+ expect(selected.kind).toBe("extension");
241
+ expect(selected).toBe(ext);
242
+ });
243
+
244
+ test("send routes to the backend matching the session's backendKind when multiple backends are registered", async () => {
245
+ const extState: MockBackendState = {
246
+ available: true,
247
+ disposed: false,
248
+ sendImpl: async () => ({ result: { from: "extension" } }),
249
+ };
250
+ const localState: MockBackendState = {
251
+ available: true,
252
+ disposed: false,
253
+ sendImpl: async () => ({ result: { from: "local" } }),
254
+ };
255
+ const ext = createMockExtensionBackend(extState);
256
+ const local = createMockLocalBackend(localState);
257
+ const manager = new BrowserSessionManager({ backends: [ext, local] });
258
+
259
+ // createSession picks the first available backend (extension), but we
260
+ // can also force the local one by passing a backendKind via direct
261
+ // session construction. To keep this test self-contained we verify the
262
+ // default path and then mark the extension unavailable to force the
263
+ // local backend for a new session.
264
+ const extSession = manager.createSession();
265
+ expect(extSession.backendKind).toBe("extension");
266
+ const extResult = await manager.send(extSession.id, {
267
+ method: "Browser.getVersion",
268
+ });
269
+ expect(extResult).toEqual({ result: { from: "extension" } });
270
+ expect(extState.lastCommand).toEqual({ method: "Browser.getVersion" });
271
+ expect(localState.lastCommand).toBeUndefined();
272
+
273
+ extState.available = false;
274
+ const localSession = manager.createSession();
275
+ expect(localSession.backendKind).toBe("local");
276
+ const localResult = await manager.send(localSession.id, {
277
+ method: "Runtime.evaluate",
278
+ });
279
+ expect(localResult).toEqual({ result: { from: "local" } });
280
+ expect(localState.lastCommand).toEqual({ method: "Runtime.evaluate" });
281
+ });
282
+
283
+ test("disposeAll disposes every registered backend", () => {
284
+ const extState: MockBackendState = { available: true, disposed: false };
285
+ const localState: MockBackendState = { available: true, disposed: false };
286
+ const manager = new BrowserSessionManager({
287
+ backends: [
288
+ createMockExtensionBackend(extState),
289
+ createMockLocalBackend(localState),
290
+ ],
291
+ });
292
+ manager.createSession();
293
+ manager.disposeAll();
294
+ expect(extState.disposed).toBe(true);
295
+ expect(localState.disposed).toBe(true);
296
+ });
297
+ });
@@ -0,0 +1,30 @@
1
+ import type { BrowserBackend, CdpCommand, CdpResult } from "../types.js";
2
+
3
+ /**
4
+ * cdp-inspect backend for BrowserSessionManager. Wraps a
5
+ * caller-provided `sendCdp` transport that talks to an already-running
6
+ * Chrome via DevTools JSON discovery + a raw WebSocket transport
7
+ * (see `assistant/src/tools/browser/cdp-client/cdp-inspect-client.ts`).
8
+ *
9
+ * The factory in
10
+ * `assistant/src/tools/browser/cdp-client/factory.ts` constructs
11
+ * one per tool invocation, paralleling the existing extension
12
+ * and local backend wiring.
13
+ */
14
+ export interface CdpInspectBackendDeps {
15
+ /** Sends a CDP command to the user's Chrome via cdp-inspect and returns the CDP result. */
16
+ sendCdp(command: CdpCommand, signal?: AbortSignal): Promise<CdpResult>;
17
+ isAvailable(): boolean;
18
+ dispose(): void;
19
+ }
20
+
21
+ export function createCdpInspectBackend(
22
+ deps: CdpInspectBackendDeps,
23
+ ): BrowserBackend {
24
+ return {
25
+ kind: "cdp-inspect",
26
+ isAvailable: deps.isAvailable,
27
+ send: deps.sendCdp,
28
+ dispose: deps.dispose,
29
+ };
30
+ }
@@ -0,0 +1,26 @@
1
+ import type { BrowserBackend, CdpCommand, CdpResult } from "../types.js";
2
+
3
+ /**
4
+ * Extension backend for BrowserSessionManager. Wraps a caller-provided
5
+ * `sendCdp` transport that routes CDP commands through the daemon's
6
+ * HostBrowserProxy to an attached chrome extension. The factory in
7
+ * `assistant/src/tools/browser/cdp-client/factory.ts` constructs one
8
+ * per tool invocation using the conversation's `hostBrowserProxy`.
9
+ */
10
+ export interface ExtensionBackendDeps {
11
+ /** Sends a CDP command to an attached chrome extension and returns the CDP result. */
12
+ sendCdp(command: CdpCommand, signal?: AbortSignal): Promise<CdpResult>;
13
+ isAvailable(): boolean;
14
+ dispose(): void;
15
+ }
16
+
17
+ export function createExtensionBackend(
18
+ deps: ExtensionBackendDeps,
19
+ ): BrowserBackend {
20
+ return {
21
+ kind: "extension",
22
+ isAvailable: deps.isAvailable,
23
+ send: deps.sendCdp,
24
+ dispose: deps.dispose,
25
+ };
26
+ }
@@ -0,0 +1,24 @@
1
+ import type { BrowserBackend, CdpCommand, CdpResult } from "../types.js";
2
+
3
+ /**
4
+ * Local backend for BrowserSessionManager. Wraps a caller-provided
5
+ * `sendCdp` transport that drives a Playwright CDPSession against the
6
+ * sacrificial-profile Chromium managed by `browserManager`. The factory
7
+ * in `assistant/src/tools/browser/cdp-client/factory.ts` constructs one
8
+ * per tool invocation using the per-conversation LocalCdpClient.
9
+ */
10
+ export interface LocalBackendDeps {
11
+ /** Sends a CDP command to a Playwright CDPSession and returns the CDP result. */
12
+ sendCdp(command: CdpCommand, signal?: AbortSignal): Promise<CdpResult>;
13
+ isAvailable(): boolean;
14
+ dispose(): void;
15
+ }
16
+
17
+ export function createLocalBackend(deps: LocalBackendDeps): BrowserBackend {
18
+ return {
19
+ kind: "local",
20
+ isAvailable: deps.isAvailable,
21
+ send: deps.sendCdp,
22
+ dispose: deps.dispose,
23
+ };
24
+ }