@vellumai/assistant 0.6.1 → 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 (463) hide show
  1. package/bun.lock +40 -40
  2. package/bunfig.toml +3 -0
  3. package/docker-entrypoint.sh +12 -2
  4. package/docs/architecture/memory.md +1 -1
  5. package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
  6. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
  7. package/openapi.yaml +184 -69
  8. package/package.json +41 -41
  9. package/scripts/generate-openapi.ts +1 -2
  10. package/src/__tests__/acp-session.test.ts +43 -0
  11. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
  12. package/src/__tests__/app-executors.test.ts +1 -0
  13. package/src/__tests__/app-source-watcher.test.ts +37 -11
  14. package/src/__tests__/approval-routes-http.test.ts +178 -1
  15. package/src/__tests__/assistant-event-hub.test.ts +30 -0
  16. package/src/__tests__/browser-fill-credential.test.ts +229 -94
  17. package/src/__tests__/browser-manager.test.ts +40 -27
  18. package/src/__tests__/catalog-files.test.ts +862 -0
  19. package/src/__tests__/channel-approvals.test.ts +53 -0
  20. package/src/__tests__/checker.test.ts +104 -170
  21. package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
  22. package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
  23. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  24. package/src/__tests__/config-schema.test.ts +125 -48
  25. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
  26. package/src/__tests__/context-overflow-approval.test.ts +21 -6
  27. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  28. package/src/__tests__/conversation-agent-loop.test.ts +1 -1
  29. package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
  30. package/src/__tests__/conversation-attachments.test.ts +80 -4
  31. package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
  32. package/src/__tests__/conversation-directories-parse.test.ts +105 -0
  33. package/src/__tests__/conversation-fork-crud.test.ts +17 -0
  34. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  35. package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
  36. package/src/__tests__/conversation-inject-context.test.ts +103 -0
  37. package/src/__tests__/conversation-queue.test.ts +45 -2
  38. package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
  39. package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
  40. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  41. package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
  42. package/src/__tests__/conversation-starter-routes.test.ts +126 -0
  43. package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
  44. package/src/__tests__/conversation-store.test.ts +195 -0
  45. package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
  46. package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -3
  47. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  48. package/src/__tests__/credential-vault-unit.test.ts +4 -4
  49. package/src/__tests__/credential-vault.test.ts +152 -13
  50. package/src/__tests__/credentials-cli.test.ts +2 -2
  51. package/src/__tests__/date-context.test.ts +4 -4
  52. package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
  53. package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
  54. package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
  55. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  56. package/src/__tests__/gemini-provider.test.ts +2 -2
  57. package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
  58. package/src/__tests__/headless-browser-interactions.test.ts +707 -371
  59. package/src/__tests__/headless-browser-navigate.test.ts +389 -47
  60. package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
  61. package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
  62. package/src/__tests__/host-bash-proxy.test.ts +150 -1
  63. package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
  64. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
  65. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
  66. package/src/__tests__/host-browser-event-routes.test.ts +350 -0
  67. package/src/__tests__/host-browser-proxy.test.ts +444 -0
  68. package/src/__tests__/host-browser-routes.test.ts +198 -0
  69. package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
  70. package/src/__tests__/host-cu-proxy.test.ts +171 -1
  71. package/src/__tests__/host-file-proxy.test.ts +185 -1
  72. package/src/__tests__/host-file-read-tool.test.ts +52 -0
  73. package/src/__tests__/host-proxy-interface.test.ts +165 -0
  74. package/src/__tests__/host-shell-tool.test.ts +1 -11
  75. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  76. package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
  77. package/src/__tests__/inline-command-runner.test.ts +7 -5
  78. package/src/__tests__/integration-status.test.ts +6 -7
  79. package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
  80. package/src/__tests__/log-export-workspace.test.ts +190 -0
  81. package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
  82. package/src/__tests__/mcp-client-auth.test.ts +40 -4
  83. package/src/__tests__/mcp-health-check.test.ts +10 -3
  84. package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
  85. package/src/__tests__/migration-export-http.test.ts +61 -2
  86. package/src/__tests__/migration-export-streaming.test.ts +66 -0
  87. package/src/__tests__/migration-import-commit-http.test.ts +101 -1
  88. package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
  89. package/src/__tests__/navigate-settings-tab.test.ts +14 -1
  90. package/src/__tests__/notification-broadcaster.test.ts +65 -0
  91. package/src/__tests__/oauth-apps-routes.test.ts +17 -12
  92. package/src/__tests__/oauth-cli.test.ts +707 -60
  93. package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
  94. package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
  95. package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
  96. package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
  97. package/src/__tests__/oauth-providers-routes.test.ts +50 -14
  98. package/src/__tests__/oauth-store.test.ts +1386 -182
  99. package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
  100. package/src/__tests__/onboarding-template-contract.test.ts +74 -55
  101. package/src/__tests__/openai-provider.test.ts +2 -2
  102. package/src/__tests__/outlook-categories.test.ts +1 -1
  103. package/src/__tests__/outlook-client-automation.test.ts +1 -1
  104. package/src/__tests__/outlook-compose-tools.test.ts +1 -1
  105. package/src/__tests__/outlook-email-watcher.test.ts +1 -1
  106. package/src/__tests__/outlook-follow-up.test.ts +1 -1
  107. package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
  108. package/src/__tests__/outlook-trash.test.ts +1 -1
  109. package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
  110. package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
  111. package/src/__tests__/permission-mode.test.ts +28 -56
  112. package/src/__tests__/pkb-autoinject.test.ts +96 -0
  113. package/src/__tests__/platform-callback-registration.test.ts +19 -0
  114. package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
  115. package/src/__tests__/proxy-approval-callback.test.ts +18 -0
  116. package/src/__tests__/require-fresh-approval.test.ts +40 -3
  117. package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
  118. package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
  119. package/src/__tests__/schedule-routes.test.ts +162 -0
  120. package/src/__tests__/secret-detection-handler.test.ts +84 -0
  121. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  122. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  123. package/src/__tests__/set-permission-mode.test.ts +13 -250
  124. package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
  125. package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
  126. package/src/__tests__/slack-channel-config.test.ts +12 -15
  127. package/src/__tests__/subagent-detail.test.ts +44 -2
  128. package/src/__tests__/subagent-disposal.test.ts +1 -0
  129. package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
  130. package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
  131. package/src/__tests__/subagent-manager-notify.test.ts +1 -0
  132. package/src/__tests__/subagent-notify-parent.test.ts +1 -0
  133. package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
  134. package/src/__tests__/subagent-tools.test.ts +1 -0
  135. package/src/__tests__/subagent-types.test.ts +1 -0
  136. package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
  137. package/src/__tests__/system-prompt.test.ts +72 -1
  138. package/src/__tests__/task-scheduler.test.ts +32 -6
  139. package/src/__tests__/telegram-config.test.ts +10 -13
  140. package/src/__tests__/terminal-sandbox.test.ts +1 -1
  141. package/src/__tests__/terminal-tools.test.ts +11 -5
  142. package/src/__tests__/test-preload.ts +14 -0
  143. package/src/__tests__/tool-approval-handler.test.ts +73 -0
  144. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
  145. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
  146. package/src/__tests__/tool-executor.test.ts +0 -1
  147. package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
  148. package/src/__tests__/top-level-renderer.test.ts +73 -1
  149. package/src/__tests__/transport-hints-queue.test.ts +62 -0
  150. package/src/__tests__/trust-store.test.ts +4 -4
  151. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
  152. package/src/__tests__/v2-consent-policy.test.ts +103 -0
  153. package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
  154. package/src/__tests__/workspace-policy.test.ts +2 -7
  155. package/src/acp/client-handler.ts +30 -4
  156. package/src/agent/loop.ts +12 -35
  157. package/src/approvals/guardian-request-resolvers.ts +21 -15
  158. package/src/browser-session/__tests__/manager.test.ts +297 -0
  159. package/src/browser-session/backends/cdp-inspect.ts +30 -0
  160. package/src/browser-session/backends/extension.ts +26 -0
  161. package/src/browser-session/backends/local.ts +24 -0
  162. package/src/browser-session/events.ts +164 -0
  163. package/src/browser-session/index.ts +27 -0
  164. package/src/browser-session/manager.ts +159 -0
  165. package/src/browser-session/types.ts +28 -0
  166. package/src/channels/__tests__/types.test.ts +134 -0
  167. package/src/channels/types.ts +55 -0
  168. package/src/cli/__tests__/run-assistant-command.ts +34 -7
  169. package/src/cli/__tests__/unknown-command.test.ts +33 -0
  170. package/src/cli/commands/browser-relay.ts +339 -409
  171. package/src/cli/commands/credentials.ts +3 -3
  172. package/src/cli/commands/default-action.ts +68 -1
  173. package/src/cli/commands/email.ts +18 -13
  174. package/src/cli/commands/mcp.ts +16 -4
  175. package/src/cli/commands/oauth/__tests__/connect.test.ts +68 -41
  176. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
  177. package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
  178. package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
  179. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
  180. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
  181. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
  182. package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
  183. package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
  184. package/src/cli/commands/oauth/apps.ts +7 -4
  185. package/src/cli/commands/oauth/connect.ts +16 -2
  186. package/src/cli/commands/oauth/disconnect.ts +1 -1
  187. package/src/cli/commands/oauth/providers.ts +200 -36
  188. package/src/cli/commands/oauth/shared.ts +5 -5
  189. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
  190. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  191. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  192. package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
  193. package/src/cli/commands/platform/index.ts +107 -10
  194. package/src/cli/commands/usage.ts +10 -9
  195. package/src/cli/lib/daemon-credential-client.ts +4 -0
  196. package/src/cli/program.ts +10 -3
  197. package/src/config/assistant-feature-flags.ts +59 -55
  198. package/src/config/bundled-skills/app-builder/SKILL.md +33 -173
  199. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
  200. package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
  201. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
  202. package/src/config/bundled-skills/contacts/SKILL.md +3 -0
  203. package/src/config/bundled-skills/document/SKILL.md +4 -0
  204. package/src/config/bundled-skills/gmail/SKILL.md +12 -7
  205. package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
  206. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
  207. package/src/config/bundled-skills/outlook/SKILL.md +7 -0
  208. package/src/config/bundled-skills/settings/TOOLS.json +1 -1
  209. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
  210. package/src/config/bundled-skills/subagent/SKILL.md +21 -0
  211. package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
  212. package/src/config/bundled-skills/tasks/SKILL.md +5 -0
  213. package/src/config/env-registry.ts +14 -0
  214. package/src/config/env.ts +21 -0
  215. package/src/config/feature-flag-registry.json +46 -7
  216. package/src/config/loader.ts +56 -1
  217. package/src/config/sanitize-for-transfer.ts +47 -0
  218. package/src/config/schema.ts +46 -5
  219. package/src/config/schemas/host-browser.ts +66 -0
  220. package/src/config/schemas/memory-lifecycle.ts +1 -1
  221. package/src/config/schemas/memory-retrieval.ts +103 -0
  222. package/src/config/schemas/security.ts +0 -6
  223. package/src/config/schemas/services.ts +16 -0
  224. package/src/config/types.ts +0 -1
  225. package/src/context/post-turn-tool-result-truncation.ts +176 -0
  226. package/src/context/window-manager.ts +19 -1
  227. package/src/credential-execution/approval-bridge.ts +49 -16
  228. package/src/credential-execution/managed-catalog.ts +3 -7
  229. package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
  230. package/src/daemon/app-source-watcher.ts +35 -0
  231. package/src/daemon/config-watcher.ts +6 -2
  232. package/src/daemon/context-overflow-approval.ts +5 -1
  233. package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
  234. package/src/daemon/conversation-agent-loop.ts +74 -19
  235. package/src/daemon/conversation-attachments.ts +40 -1
  236. package/src/daemon/conversation-messaging.ts +3 -0
  237. package/src/daemon/conversation-process.ts +66 -3
  238. package/src/daemon/conversation-queue-manager.ts +8 -0
  239. package/src/daemon/conversation-runtime-assembly.ts +159 -20
  240. package/src/daemon/conversation-surfaces.ts +78 -12
  241. package/src/daemon/conversation-tool-setup.ts +74 -11
  242. package/src/daemon/conversation-workspace.ts +12 -0
  243. package/src/daemon/conversation.ts +227 -11
  244. package/src/daemon/date-context.ts +10 -10
  245. package/src/daemon/first-greeting.ts +3 -2
  246. package/src/daemon/handlers/conversations.ts +9 -139
  247. package/src/daemon/handlers/shared.ts +65 -0
  248. package/src/daemon/handlers/skills.ts +232 -37
  249. package/src/daemon/host-bash-proxy.ts +48 -13
  250. package/src/daemon/host-browser-proxy.ts +191 -0
  251. package/src/daemon/host-cu-proxy.ts +36 -11
  252. package/src/daemon/host-file-proxy.ts +57 -9
  253. package/src/daemon/lifecycle.ts +86 -12
  254. package/src/daemon/message-protocol.ts +7 -0
  255. package/src/daemon/message-types/conversations.ts +59 -13
  256. package/src/daemon/message-types/host-browser.ts +100 -0
  257. package/src/daemon/message-types/messages.ts +5 -6
  258. package/src/daemon/message-types/notifications.ts +12 -0
  259. package/src/daemon/message-types/settings.ts +12 -0
  260. package/src/daemon/message-types/skills.ts +10 -0
  261. package/src/daemon/message-types/subagents.ts +2 -0
  262. package/src/daemon/server.ts +112 -35
  263. package/src/daemon/tool-side-effects.ts +6 -0
  264. package/src/daemon/transport-hints.ts +14 -0
  265. package/src/inbound/platform-callback-registration.ts +18 -17
  266. package/src/index.ts +1 -1
  267. package/src/mcp/client.ts +59 -24
  268. package/src/memory/app-store.ts +31 -1
  269. package/src/memory/conversation-crud.ts +38 -10
  270. package/src/memory/conversation-directories.ts +39 -0
  271. package/src/memory/conversation-group-migration.ts +65 -5
  272. package/src/memory/conversation-starters-cadence.ts +76 -0
  273. package/src/memory/conversation-title-service.ts +5 -2
  274. package/src/memory/db-init.ts +12 -0
  275. package/src/memory/embedding-backend.test.ts +75 -0
  276. package/src/memory/embedding-backend.ts +131 -5
  277. package/src/memory/embedding-gemini.test.ts +54 -0
  278. package/src/memory/embedding-gemini.ts +20 -9
  279. package/src/memory/embedding-local.ts +177 -18
  280. package/src/memory/graph/capability-seed.ts +3 -5
  281. package/src/memory/graph/consolidation.ts +10 -23
  282. package/src/memory/graph/extraction-job.ts +15 -0
  283. package/src/memory/graph/retriever.ts +40 -22
  284. package/src/memory/graph/store.test.ts +7 -3
  285. package/src/memory/graph/store.ts +47 -12
  286. package/src/memory/group-crud.ts +25 -9
  287. package/src/memory/llm-usage-store.ts +45 -4
  288. package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
  289. package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
  290. package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
  291. package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
  292. package/src/memory/migrations/217-conversation-host-access.ts +40 -0
  293. package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
  294. package/src/memory/migrations/index.ts +6 -0
  295. package/src/memory/migrations/registry.ts +8 -0
  296. package/src/memory/schema/conversations.ts +1 -0
  297. package/src/memory/schema/oauth.ts +18 -13
  298. package/src/messaging/provider.ts +1 -1
  299. package/src/notifications/broadcaster.ts +6 -0
  300. package/src/notifications/conversation-pairing.ts +12 -4
  301. package/src/notifications/emit-signal.ts +14 -0
  302. package/src/notifications/signal.ts +11 -0
  303. package/src/oauth/AGENTS.md +76 -0
  304. package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
  305. package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
  306. package/src/oauth/byo-connection.test.ts +8 -8
  307. package/src/oauth/byo-connection.ts +7 -7
  308. package/src/oauth/connect-orchestrator.ts +23 -21
  309. package/src/oauth/connect-types.ts +3 -3
  310. package/src/oauth/connection-resolver.test.ts +17 -4
  311. package/src/oauth/connection-resolver.ts +16 -16
  312. package/src/oauth/connection.ts +1 -1
  313. package/src/oauth/manual-token-connection.ts +13 -13
  314. package/src/oauth/oauth-store.ts +214 -100
  315. package/src/oauth/platform-connection.test.ts +5 -5
  316. package/src/oauth/platform-connection.ts +4 -4
  317. package/src/oauth/provider-serializer.ts +31 -5
  318. package/src/oauth/revoke.ts +76 -0
  319. package/src/oauth/seed-providers.ts +127 -87
  320. package/src/oauth/token-persistence.ts +1 -1
  321. package/src/permissions/checker.ts +3 -3
  322. package/src/permissions/defaults.ts +7 -8
  323. package/src/permissions/permission-mode.ts +4 -11
  324. package/src/permissions/prompter.ts +13 -3
  325. package/src/permissions/v2-consent-policy.ts +87 -0
  326. package/src/platform/client.ts +1 -1
  327. package/src/prompts/system-prompt.ts +18 -21
  328. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
  329. package/src/prompts/templates/BOOTSTRAP.md +59 -96
  330. package/src/prompts/templates/SOUL.md +11 -11
  331. package/src/providers/anthropic/client.ts +1 -0
  332. package/src/providers/types.ts +1 -1
  333. package/src/runtime/AGENTS.md +23 -0
  334. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
  335. package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
  336. package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
  337. package/src/runtime/assistant-event-hub.ts +24 -2
  338. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
  339. package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
  340. package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
  341. package/src/runtime/auth/middleware.ts +98 -0
  342. package/src/runtime/auth/route-policy.ts +6 -7
  343. package/src/runtime/auth/token-service.ts +8 -0
  344. package/src/runtime/capability-tokens.ts +414 -0
  345. package/src/runtime/channel-approvals.ts +18 -5
  346. package/src/runtime/chrome-extension-registry.ts +332 -0
  347. package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
  348. package/src/runtime/guardian-decision-types.ts +7 -0
  349. package/src/runtime/http-server.ts +425 -70
  350. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
  351. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
  352. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
  353. package/src/runtime/migrations/migration-transport.ts +6 -0
  354. package/src/runtime/migrations/migration-wizard.ts +22 -2
  355. package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
  356. package/src/runtime/migrations/vbundle-builder.ts +145 -38
  357. package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
  358. package/src/runtime/migrations/vbundle-importer.ts +55 -5
  359. package/src/runtime/pending-interactions.ts +29 -13
  360. package/src/runtime/routes/approval-routes.ts +90 -16
  361. package/src/runtime/routes/browser-cdp-routes.ts +229 -0
  362. package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
  363. package/src/runtime/routes/conversation-analysis-routes.ts +18 -5
  364. package/src/runtime/routes/conversation-management-routes.ts +108 -0
  365. package/src/runtime/routes/conversation-routes.ts +308 -28
  366. package/src/runtime/routes/conversation-starter-routes.ts +78 -16
  367. package/src/runtime/routes/group-routes.ts +22 -8
  368. package/src/runtime/routes/guardian-action-routes.ts +24 -13
  369. package/src/runtime/routes/host-browser-routes.ts +279 -0
  370. package/src/runtime/routes/host-file-routes.ts +9 -1
  371. package/src/runtime/routes/identity-routes.ts +259 -16
  372. package/src/runtime/routes/log-export/AGENTS.md +104 -0
  373. package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
  374. package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
  375. package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
  376. package/src/runtime/routes/log-export-routes.ts +60 -25
  377. package/src/runtime/routes/memory-item-routes.ts +1 -7
  378. package/src/runtime/routes/migration-routes.ts +87 -2
  379. package/src/runtime/routes/oauth-apps.ts +15 -17
  380. package/src/runtime/routes/oauth-providers.ts +4 -0
  381. package/src/runtime/routes/schedule-routes.ts +24 -11
  382. package/src/runtime/routes/settings-routes.ts +9 -97
  383. package/src/runtime/routes/skills-routes.ts +52 -2
  384. package/src/runtime/routes/subagents-routes.ts +14 -10
  385. package/src/runtime/routes/usage-routes.ts +8 -7
  386. package/src/runtime/routes/workspace-routes.test.ts +22 -0
  387. package/src/runtime/routes/workspace-routes.ts +8 -1
  388. package/src/runtime/routes/workspace-utils.ts +2 -0
  389. package/src/schedule/scheduler.ts +7 -5
  390. package/src/security/ces-credential-client.ts +20 -0
  391. package/src/security/ces-rpc-credential-backend.ts +17 -0
  392. package/src/security/credential-backend.ts +5 -0
  393. package/src/security/oauth2.ts +42 -25
  394. package/src/security/secure-keys.ts +118 -25
  395. package/src/security/token-manager.ts +23 -10
  396. package/src/skills/catalog-files.ts +492 -0
  397. package/src/skills/inline-command-runner.ts +12 -14
  398. package/src/subagent/manager.ts +131 -26
  399. package/src/subagent/types.ts +19 -0
  400. package/src/tools/apps/executors.ts +11 -2
  401. package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
  402. package/src/tools/browser/auth-detector.ts +43 -12
  403. package/src/tools/browser/browser-execution.ts +645 -340
  404. package/src/tools/browser/browser-manager.ts +36 -12
  405. package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
  406. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
  407. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
  408. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
  409. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
  410. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
  411. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
  412. package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
  413. package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
  414. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
  415. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
  416. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
  417. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
  418. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
  419. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
  420. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
  421. package/src/tools/browser/cdp-client/errors.ts +34 -0
  422. package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
  423. package/src/tools/browser/cdp-client/factory.ts +204 -0
  424. package/src/tools/browser/cdp-client/index.ts +14 -0
  425. package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
  426. package/src/tools/browser/cdp-client/types.ts +52 -0
  427. package/src/tools/filesystem/edit.ts +1 -1
  428. package/src/tools/filesystem/list.ts +1 -1
  429. package/src/tools/filesystem/read.ts +1 -1
  430. package/src/tools/filesystem/write.ts +2 -1
  431. package/src/tools/host-filesystem/edit.ts +1 -1
  432. package/src/tools/host-filesystem/read.ts +12 -15
  433. package/src/tools/host-filesystem/write.ts +1 -1
  434. package/src/tools/host-terminal/host-shell.ts +21 -16
  435. package/src/tools/permission-checker.ts +77 -100
  436. package/src/tools/registry.ts +0 -2
  437. package/src/tools/secret-detection-handler.ts +34 -1
  438. package/src/tools/shared/filesystem/image-read.ts +61 -40
  439. package/src/tools/skills/sandbox-runner.ts +3 -6
  440. package/src/tools/subagent/spawn.ts +47 -3
  441. package/src/tools/subagent/status.ts +2 -0
  442. package/src/tools/system/register.ts +2 -16
  443. package/src/tools/terminal/safe-env.ts +7 -0
  444. package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
  445. package/src/tools/terminal/sandbox.ts +4 -1
  446. package/src/tools/terminal/shell.ts +24 -21
  447. package/src/tools/tool-approval-handler.ts +48 -2
  448. package/src/tools/types.ts +2 -3
  449. package/src/util/platform.ts +14 -19
  450. package/src/watcher/provider-types.ts +1 -1
  451. package/src/workspace/migrations/029-seed-pkb.ts +1 -0
  452. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
  453. package/src/workspace/migrations/registry.ts +2 -0
  454. package/src/workspace/top-level-renderer.ts +19 -1
  455. package/src/__tests__/chrome-cdp.test.ts +0 -419
  456. package/src/__tests__/permission-mode-sse.test.ts +0 -418
  457. package/src/__tests__/permission-mode-store.test.ts +0 -277
  458. package/src/browser-extension-relay/protocol.ts +0 -63
  459. package/src/browser-extension-relay/server.ts +0 -203
  460. package/src/config/schemas/sandbox.ts +0 -14
  461. package/src/permissions/permission-mode-store.ts +0 -180
  462. package/src/tools/browser/chrome-cdp.ts +0 -239
  463. package/src/tools/system/set-permission-mode.ts +0 -103
@@ -0,0 +1,125 @@
1
+ import type { HostBrowserProxy } from "../../../daemon/host-browser-proxy.js";
2
+ import { getLogger } from "../../../util/logger.js";
3
+ import { CdpError } from "./errors.js";
4
+ import type { CdpClientKind, ScopedCdpClient } from "./types.js";
5
+
6
+ const log = getLogger("extension-cdp-client");
7
+
8
+ /**
9
+ * CdpClient backed by HostBrowserProxy. Each `send` becomes a
10
+ * host_browser_request / host_browser_result round-trip over the
11
+ * chrome-extension WebSocket.
12
+ *
13
+ * Unlike LocalCdpClient, this implementation cannot deliver
14
+ * CDP events (subscribing to "Page.lifecycleEvent" etc.) because
15
+ * HostBrowserProxy is request/reply only. Helpers that need
16
+ * event subscription (waitForLifecycleEvent) must fall back to
17
+ * polling via Runtime.evaluate — see cdp-dom-helpers.ts#navigateAndWait.
18
+ */
19
+ export class ExtensionCdpClient implements ScopedCdpClient {
20
+ readonly kind: CdpClientKind = "extension";
21
+ private disposed = false;
22
+
23
+ constructor(
24
+ private readonly proxy: HostBrowserProxy,
25
+ public readonly conversationId: string,
26
+ private readonly cdpSessionId?: string,
27
+ ) {}
28
+
29
+ async send<T = unknown>(
30
+ method: string,
31
+ params?: Record<string, unknown>,
32
+ signal?: AbortSignal,
33
+ ): Promise<T> {
34
+ if (this.disposed) {
35
+ throw new CdpError("disposed", "ExtensionCdpClient already disposed", {
36
+ cdpMethod: method,
37
+ cdpParams: params,
38
+ });
39
+ }
40
+ if (signal?.aborted) {
41
+ throw new CdpError("aborted", "Aborted before send", {
42
+ cdpMethod: method,
43
+ cdpParams: params,
44
+ });
45
+ }
46
+
47
+ let result;
48
+ try {
49
+ result = await this.proxy.request(
50
+ {
51
+ cdpMethod: method,
52
+ cdpParams: params,
53
+ cdpSessionId: this.cdpSessionId,
54
+ },
55
+ this.conversationId,
56
+ signal,
57
+ );
58
+ } catch (err) {
59
+ throw new CdpError(
60
+ "transport_error",
61
+ err instanceof Error ? err.message : String(err),
62
+ {
63
+ cdpMethod: method,
64
+ cdpParams: params,
65
+ underlying: err,
66
+ },
67
+ );
68
+ }
69
+
70
+ if (signal?.aborted || result.content === "Aborted") {
71
+ throw new CdpError("aborted", "CDP call aborted", {
72
+ cdpMethod: method,
73
+ cdpParams: params,
74
+ });
75
+ }
76
+
77
+ let parsed: unknown;
78
+ try {
79
+ parsed = JSON.parse(result.content);
80
+ } catch (err) {
81
+ throw new CdpError(
82
+ "transport_error",
83
+ `Non-JSON content from host_browser_result: ${result.content.slice(0, 200)}`,
84
+ {
85
+ cdpMethod: method,
86
+ cdpParams: params,
87
+ underlying: err,
88
+ },
89
+ );
90
+ }
91
+
92
+ if (result.isError) {
93
+ const msg =
94
+ (typeof parsed === "object" &&
95
+ parsed !== null &&
96
+ "message" in parsed &&
97
+ typeof (parsed as { message: unknown }).message === "string" &&
98
+ (parsed as { message: string }).message) ||
99
+ `CDP error for ${method}`;
100
+ log.debug({ method, params, parsed }, "ExtensionCdpClient: CDP error");
101
+ throw new CdpError("cdp_error", msg, {
102
+ cdpMethod: method,
103
+ cdpParams: params,
104
+ underlying: parsed,
105
+ });
106
+ }
107
+
108
+ return parsed as T;
109
+ }
110
+
111
+ dispose(): void {
112
+ this.disposed = true;
113
+ // HostBrowserProxy is owned by the conversation — do NOT dispose
114
+ // it here. In-flight requests will be cancelled by the AbortSignal
115
+ // the tool passes in, or by conversation teardown.
116
+ }
117
+ }
118
+
119
+ export function createExtensionCdpClient(
120
+ proxy: HostBrowserProxy,
121
+ conversationId: string,
122
+ cdpSessionId?: string,
123
+ ): ExtensionCdpClient {
124
+ return new ExtensionCdpClient(proxy, conversationId, cdpSessionId);
125
+ }
@@ -0,0 +1,204 @@
1
+ import {
2
+ type BrowserBackend,
3
+ BrowserSessionManager,
4
+ type CdpCommand,
5
+ type CdpResult,
6
+ createCdpInspectBackend,
7
+ createExtensionBackend,
8
+ createLocalBackend,
9
+ } from "../../../browser-session/index.js";
10
+ import { getConfig } from "../../../config/loader.js";
11
+ import type { ToolContext } from "../../types.js";
12
+ import { createCdpInspectClient } from "./cdp-inspect-client.js";
13
+ import { CdpError } from "./errors.js";
14
+ import { createExtensionCdpClient } from "./extension-cdp-client.js";
15
+ import { createLocalCdpClient } from "./local-cdp-client.js";
16
+ import type { CdpClient, CdpClientKind, ScopedCdpClient } from "./types.js";
17
+
18
+ /**
19
+ * Select the appropriate CdpClient implementation for a tool
20
+ * invocation based on the ToolContext and config. Three backends are
21
+ * considered in priority order:
22
+ *
23
+ * 1. **Extension** — When `context.hostBrowserProxy` is set (macOS
24
+ * desktop / cloud-hosted with a chrome-extension bound to the
25
+ * conversation), register an extension backend so CDP commands
26
+ * ride the host_browser_request / host_browser_result round-trip.
27
+ * 2. **cdp-inspect** — When the extension is absent and
28
+ * `hostBrowser.cdpInspect.enabled` is `true` in config, construct
29
+ * a `CdpInspectClient` that attaches to an already-running Chrome
30
+ * via the DevTools JSON protocol (`--remote-debugging-port`).
31
+ * 3. **Local** — Default. Drives Playwright's CDPSession
32
+ * against the sacrificial-profile browser managed by
33
+ * browserManager.
34
+ *
35
+ * All three paths go through a per-invocation `BrowserSessionManager`
36
+ * so the manager is the single choke point for CDP routing, session
37
+ * lifetime, and (future) session invalidation handling. The returned
38
+ * client is `kind`-tagged so tools can branch on transport — e.g.
39
+ * browser_navigate skips Playwright-specific screencast and handoff
40
+ * hooks when `kind === "extension"`.
41
+ *
42
+ * IMPORTANT: the returned client is per-invocation. Tools MUST call
43
+ * `dispose()` in a finally block. Dispose tears down the manager's
44
+ * session and the underlying CDP client. Disposing an extension-backed
45
+ * client does NOT dispose the underlying HostBrowserProxy — that is
46
+ * owned by the conversation.
47
+ */
48
+ export function getCdpClient(context: ToolContext): ScopedCdpClient {
49
+ const { conversationId, hostBrowserProxy } = context;
50
+
51
+ // 1. Extension backend — preferred when a chrome-extension is bound.
52
+ if (hostBrowserProxy) {
53
+ const client = createExtensionCdpClient(hostBrowserProxy, conversationId);
54
+ const backend = createExtensionBackend({
55
+ isAvailable: () => true,
56
+ sendCdp: (command, signal) =>
57
+ dispatchThroughClient(client, command, signal),
58
+ dispose: () => client.dispose(),
59
+ });
60
+ return buildManagedClient("extension", conversationId, backend);
61
+ }
62
+
63
+ // 2. cdp-inspect backend — opt-in via config when the extension is absent.
64
+ const cdpInspectConfig = getConfig().hostBrowser.cdpInspect;
65
+ if (cdpInspectConfig.enabled) {
66
+ const client = createCdpInspectClient(conversationId, {
67
+ host: cdpInspectConfig.host,
68
+ port: cdpInspectConfig.port,
69
+ discoveryTimeoutMs: cdpInspectConfig.probeTimeoutMs,
70
+ });
71
+ const backend = createCdpInspectBackend({
72
+ isAvailable: () => true,
73
+ sendCdp: (command, signal) =>
74
+ dispatchThroughClient(client, command, signal),
75
+ dispose: () => client.dispose(),
76
+ });
77
+ return buildManagedClient("cdp-inspect", conversationId, backend);
78
+ }
79
+
80
+ // 3. Local backend — default (Playwright-backed Chromium).
81
+ const client = createLocalCdpClient(conversationId);
82
+ const backend = createLocalBackend({
83
+ isAvailable: () => true,
84
+ sendCdp: (command, signal) =>
85
+ dispatchThroughClient(client, command, signal),
86
+ dispose: () => client.dispose(),
87
+ });
88
+ return buildManagedClient("local", conversationId, backend);
89
+ }
90
+
91
+ /**
92
+ * Build a ScopedCdpClient whose `send()` routes through a
93
+ * BrowserSessionManager seeded with a single backend + session. This
94
+ * lets tool call sites remain backend-agnostic while giving the
95
+ * manager a seam for future session-invalidation and multi-target
96
+ * routing work.
97
+ */
98
+ function buildManagedClient(
99
+ kind: CdpClientKind,
100
+ conversationId: string,
101
+ backend: BrowserBackend,
102
+ ): ScopedCdpClient {
103
+ const manager = new BrowserSessionManager({ backends: [backend] });
104
+ const session = manager.createSession();
105
+ let disposed = false;
106
+
107
+ return {
108
+ kind,
109
+ conversationId,
110
+ async send<T = unknown>(
111
+ method: string,
112
+ params?: Record<string, unknown>,
113
+ signal?: AbortSignal,
114
+ ): Promise<T> {
115
+ if (disposed) {
116
+ const clientName =
117
+ kind === "extension"
118
+ ? "ExtensionCdpClient"
119
+ : kind === "cdp-inspect"
120
+ ? "CdpInspectClient"
121
+ : "LocalCdpClient";
122
+ throw new CdpError("disposed", `${clientName} already disposed`, {
123
+ cdpMethod: method,
124
+ cdpParams: params,
125
+ });
126
+ }
127
+ const command: CdpCommand = { method, params };
128
+ const envelope = await manager.send(session.id, command, signal);
129
+ return unwrapResult<T>(envelope, method, params);
130
+ },
131
+ dispose(): void {
132
+ if (disposed) return;
133
+ disposed = true;
134
+ // disposeAll() tears down the per-invocation backend (which in
135
+ // turn disposes the underlying CdpClient) and clears the single
136
+ // session we created in buildManagedClient.
137
+ manager.disposeAll();
138
+ },
139
+ };
140
+ }
141
+
142
+ /**
143
+ * Adapter that makes an existing `CdpClient` look like a
144
+ * `BrowserBackend.send`. Converts thrown CdpErrors back into a
145
+ * `CdpResult` envelope with an `error` payload so the manager does
146
+ * not need to know about our thrown-error convention, then the
147
+ * envelope is unwrapped again on the way out of the managed client.
148
+ *
149
+ * The per-command `command.sessionId` (populated by the manager from
150
+ * a session's opaque `targetId`) is intentionally not forwarded to
151
+ * the underlying CdpClient today — both LocalCdpClient and
152
+ * ExtensionCdpClient take their CDP sessionId at construction time
153
+ * and tools run one client per invocation. The seam is preserved so
154
+ * a future multi-target backend can read it off the CdpCommand.
155
+ */
156
+ async function dispatchThroughClient(
157
+ client: CdpClient,
158
+ command: CdpCommand,
159
+ signal: AbortSignal | undefined,
160
+ ): Promise<CdpResult> {
161
+ try {
162
+ const result = await client.send(command.method, command.params, signal);
163
+ return { result };
164
+ } catch (err) {
165
+ if (err instanceof CdpError) {
166
+ // Preserve the original CdpError so unwrapResult can re-throw it
167
+ // verbatim. CdpResult's error channel is opaque to the manager,
168
+ // so stashing the instance under `data` is safe.
169
+ return {
170
+ error: {
171
+ code: -1,
172
+ message: err.message,
173
+ data: err,
174
+ },
175
+ };
176
+ }
177
+ throw err;
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Unwrap a CdpResult envelope into the raw CDP result `T` or throw
183
+ * the underlying CdpError. If the envelope carries an error but the
184
+ * `data` is not a CdpError (e.g. a future backend surfaces a JSON-RPC
185
+ * error envelope directly), synthesize a transport_error CdpError so
186
+ * call sites keep their uniform error handling.
187
+ */
188
+ function unwrapResult<T>(
189
+ envelope: CdpResult,
190
+ method: string,
191
+ params?: Record<string, unknown>,
192
+ ): T {
193
+ if (envelope.error) {
194
+ if (envelope.error.data instanceof CdpError) {
195
+ throw envelope.error.data;
196
+ }
197
+ throw new CdpError("cdp_error", envelope.error.message, {
198
+ cdpMethod: method,
199
+ cdpParams: params,
200
+ underlying: envelope.error,
201
+ });
202
+ }
203
+ return envelope.result as T;
204
+ }
@@ -0,0 +1,14 @@
1
+ export {
2
+ CdpInspectClient,
3
+ type CdpInspectClientOptions,
4
+ type CdpInspectHelpers,
5
+ createCdpInspectClient,
6
+ } from "./cdp-inspect-client.js";
7
+ export { CdpError, type CdpErrorCode } from "./errors.js";
8
+ export {
9
+ createExtensionCdpClient,
10
+ ExtensionCdpClient,
11
+ } from "./extension-cdp-client.js";
12
+ export { getCdpClient } from "./factory.js";
13
+ export { createLocalCdpClient, LocalCdpClient } from "./local-cdp-client.js";
14
+ export type { CdpClient, CdpClientKind, ScopedCdpClient } from "./types.js";
@@ -0,0 +1,187 @@
1
+ import { getLogger } from "../../../util/logger.js";
2
+ import { browserManager } from "../browser-manager.js";
3
+ import { CdpError } from "./errors.js";
4
+ import type { CdpClientKind, ScopedCdpClient } from "./types.js";
5
+
6
+ const log = getLogger("local-cdp-client");
7
+
8
+ /**
9
+ * Minimal shape of the Playwright CDPSession we depend on. Avoids a
10
+ * direct Playwright type import so the CDP client stays buildable
11
+ * even when Playwright types are not present (dev builds, CI jobs
12
+ * that skip the playwright install).
13
+ */
14
+ interface PlaywrightCdpSession {
15
+ send(method: string, params?: Record<string, unknown>): Promise<unknown>;
16
+ detach(): Promise<void>;
17
+ }
18
+
19
+ /**
20
+ * Minimal shape of the Playwright Page we use for CDP session
21
+ * creation. Intentionally narrow so we never have to import the
22
+ * Playwright types directly from this module.
23
+ */
24
+ interface RawPlaywrightPage {
25
+ context(): {
26
+ newCDPSession(page: unknown): Promise<PlaywrightCdpSession>;
27
+ };
28
+ }
29
+
30
+ /**
31
+ * Playwright-backed implementation of {@link ScopedCdpClient}. Used
32
+ * for CLI conversations, headless cloud conversations, unit tests,
33
+ * and any desktop conversation that does not have a `hostBrowserProxy`
34
+ * configured.
35
+ *
36
+ * LocalCdpClient owns only the per-conversation CDP session; the
37
+ * underlying Chromium is still launched and torn down by
38
+ * `browserManager.ensureContext()` / `browserManager.shutdown()`.
39
+ */
40
+ export class LocalCdpClient implements ScopedCdpClient {
41
+ readonly kind: CdpClientKind = "local";
42
+
43
+ private sessionPromise: Promise<PlaywrightCdpSession> | null = null;
44
+ private disposed = false;
45
+
46
+ constructor(public readonly conversationId: string) {}
47
+
48
+ /**
49
+ * Lazily create (and cache) a Playwright CDP session for this
50
+ * conversation. Concurrent callers share the same in-flight promise
51
+ * so `newCDPSession` is only called once per LocalCdpClient
52
+ * instance.
53
+ *
54
+ * If the underlying browser launch / `newCDPSession` rejects (e.g.
55
+ * a transient Chromium spawn failure), the cached promise is
56
+ * cleared so the next `ensureSession()` call retries from scratch
57
+ * instead of replaying the same rejection forever.
58
+ */
59
+ private async ensureSession(): Promise<PlaywrightCdpSession> {
60
+ if (this.disposed) {
61
+ throw new CdpError("disposed", "LocalCdpClient already disposed");
62
+ }
63
+ if (this.sessionPromise) return this.sessionPromise;
64
+ const created = this.createSession();
65
+ this.sessionPromise = created;
66
+ // Clear the cached promise on rejection so the next call retries
67
+ // from scratch instead of replaying the same failure forever.
68
+ // Only clear if `created` is still the cached promise — a
69
+ // concurrent dispose may have already nulled it.
70
+ created.catch(() => {
71
+ if (this.sessionPromise === created) {
72
+ this.sessionPromise = null;
73
+ }
74
+ });
75
+ return created;
76
+ }
77
+
78
+ private async createSession(): Promise<PlaywrightCdpSession> {
79
+ const page = await browserManager.getOrCreateSessionPage(
80
+ this.conversationId,
81
+ );
82
+ const rawPage = page as unknown as RawPlaywrightPage;
83
+ const session = await rawPage.context().newCDPSession(rawPage);
84
+ log.debug(
85
+ { conversationId: this.conversationId },
86
+ "Created Playwright CDP session for LocalCdpClient",
87
+ );
88
+ return session;
89
+ }
90
+
91
+ async send<T = unknown>(
92
+ method: string,
93
+ params?: Record<string, unknown>,
94
+ signal?: AbortSignal,
95
+ ): Promise<T> {
96
+ if (this.disposed) {
97
+ throw new CdpError("disposed", "LocalCdpClient already disposed", {
98
+ cdpMethod: method,
99
+ cdpParams: params,
100
+ });
101
+ }
102
+ if (signal?.aborted) {
103
+ throw new CdpError("aborted", "Aborted before send", {
104
+ cdpMethod: method,
105
+ cdpParams: params,
106
+ });
107
+ }
108
+ let session: PlaywrightCdpSession;
109
+ try {
110
+ session = await this.ensureSession();
111
+ } catch (err) {
112
+ if (signal?.aborted) {
113
+ throw new CdpError("aborted", "Aborted during send", {
114
+ cdpMethod: method,
115
+ cdpParams: params,
116
+ underlying: err,
117
+ });
118
+ }
119
+ // Re-throw existing CdpError instances unchanged so we don't
120
+ // double-wrap (e.g. a "disposed" error raised by a concurrent
121
+ // dispose landing during ensureSession()).
122
+ if (err instanceof CdpError) {
123
+ throw err;
124
+ }
125
+ // ensureSession failures (browser launch errors, Chromium
126
+ // spawn failures, etc) are surfaced as transport_error so
127
+ // callers can distinguish them from CDP protocol errors raised
128
+ // by session.send below.
129
+ const msg = err instanceof Error ? err.message : String(err);
130
+ throw new CdpError("transport_error", msg, {
131
+ cdpMethod: method,
132
+ cdpParams: params,
133
+ underlying: err,
134
+ });
135
+ }
136
+ try {
137
+ const result = (await session.send(method, params)) as T;
138
+ return result;
139
+ } catch (err) {
140
+ if (signal?.aborted) {
141
+ throw new CdpError("aborted", "Aborted during send", {
142
+ cdpMethod: method,
143
+ cdpParams: params,
144
+ underlying: err,
145
+ });
146
+ }
147
+ if (err instanceof CdpError) {
148
+ throw err;
149
+ }
150
+ const msg = err instanceof Error ? err.message : String(err);
151
+ throw new CdpError("cdp_error", msg, {
152
+ cdpMethod: method,
153
+ cdpParams: params,
154
+ underlying: err,
155
+ });
156
+ }
157
+ }
158
+
159
+ dispose(): void {
160
+ if (this.disposed) return;
161
+ this.disposed = true;
162
+ const pending = this.sessionPromise;
163
+ this.sessionPromise = null;
164
+ if (!pending) return;
165
+ pending
166
+ .then(async (session) => {
167
+ try {
168
+ await session.detach();
169
+ } catch (err) {
170
+ log.debug({ err }, "LocalCdpClient: session.detach threw (ignored)");
171
+ }
172
+ })
173
+ .catch(() => {
174
+ // Session never resolved — nothing to detach.
175
+ });
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Factory for a fresh {@link LocalCdpClient} bound to a conversation.
181
+ * Keeping the constructor + factory split lets the cdp-client factory
182
+ * branch between local and extension transports without
183
+ * exposing the class directly to callers.
184
+ */
185
+ export function createLocalCdpClient(conversationId: string): LocalCdpClient {
186
+ return new LocalCdpClient(conversationId);
187
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Minimal typed surface over Chrome DevTools Protocol. Implemented by
3
+ * LocalCdpClient (Playwright-backed, same-process Chromium),
4
+ * ExtensionCdpClient (routes through HostBrowserProxy to the user's
5
+ * Chrome via chrome.debugger), and CdpInspectClient (connects to a
6
+ * remote browser over a raw CDP WebSocket URL). Tools call
7
+ * `send(method, params)` with a CDP method name and return the raw
8
+ * CDP result object; errors are thrown as {@link CdpError}.
9
+ */
10
+ export interface CdpClient {
11
+ /**
12
+ * Send a CDP command and await the result. `method` must be a
13
+ * well-known CDP method name (e.g. "Page.navigate",
14
+ * "Runtime.evaluate", "Accessibility.getFullAXTree"). `params` is
15
+ * forwarded verbatim.
16
+ *
17
+ * On success, returns the raw `result` object from the CDP response
18
+ * as `T`. On JSON-RPC error or transport failure, throws a
19
+ * {@link CdpError}. Abort propagates via `signal`; aborted calls
20
+ * throw an {@link CdpError} with `code === "aborted"`.
21
+ */
22
+ send<T = unknown>(
23
+ method: string,
24
+ params?: Record<string, unknown>,
25
+ signal?: AbortSignal,
26
+ ): Promise<T>;
27
+
28
+ /**
29
+ * Release any backend-side resources (CDP sessions, in-flight
30
+ * requests, listeners). Idempotent. Calling `send` after `dispose`
31
+ * is allowed but should surface as an error.
32
+ */
33
+ dispose(): void;
34
+ }
35
+
36
+ /**
37
+ * Backend kind exposed by a concrete CdpClient. Used by tools that
38
+ * want to branch on the transport (e.g. browser_navigate should skip
39
+ * the sacrificial-profile screencast setup when running against the
40
+ * user's own Chrome via the extension).
41
+ */
42
+ export type CdpClientKind = "local" | "extension" | "cdp-inspect";
43
+
44
+ /**
45
+ * Concrete CdpClient instance returned by the factory. Carries the
46
+ * backend `kind` for transport-aware branches in tool code.
47
+ */
48
+ export interface ScopedCdpClient extends CdpClient {
49
+ readonly kind: CdpClientKind;
50
+ /** Stable conversation id this client is bound to. */
51
+ readonly conversationId: string;
52
+ }
@@ -9,7 +9,7 @@ import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
9
9
  class FileEditTool implements Tool {
10
10
  name = "file_edit";
11
11
  description =
12
- "Replace an exact string in a file with a new string. Use this for surgical edits instead of rewriting entire files. To delete a file, use bash with rm instead.";
12
+ "Replace an exact string in a file on your own machine with a new string. Use this for surgical edits instead of rewriting entire files. Use host_file_edit for files on your guardian's device instead.";
13
13
  category = "filesystem";
14
14
  defaultRiskLevel = RiskLevel.Low;
15
15
 
@@ -8,7 +8,7 @@ import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
8
8
  class FileListTool implements Tool {
9
9
  name = "file_list";
10
10
  description =
11
- "List the contents of a directory. Returns file and subdirectory names with type indicators and sizes.";
11
+ "List the contents of a directory on your own machine. Returns file and subdirectory names with type indicators and sizes.";
12
12
  category = "filesystem";
13
13
  defaultRiskLevel = RiskLevel.Low;
14
14
 
@@ -14,7 +14,7 @@ import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
14
14
  class FileReadTool implements Tool {
15
15
  name = "file_read";
16
16
  description =
17
- "Read the contents of a file. For image files (JPEG, PNG, GIF, WebP), returns the image for visual analysis. Always use this tool (not host_file_read) for workspace files under .vellum.";
17
+ "Read the contents of a file on your own machine. For image files (JPEG, PNG, GIF, WebP), returns the image for visual analysis. Use host_file_read for files on your guardian's device instead.";
18
18
  category = "filesystem";
19
19
  defaultRiskLevel = RiskLevel.Low;
20
20
 
@@ -8,7 +8,8 @@ import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
8
8
 
9
9
  class FileWriteTool implements Tool {
10
10
  name = "file_write";
11
- description = "Write content to a file, creating it if it does not exist";
11
+ description =
12
+ "Write content to a file on your own machine, creating it if it does not exist. Use host_file_write for files on your guardian's device instead.";
12
13
  category = "filesystem";
13
14
  defaultRiskLevel = RiskLevel.Low;
14
15
 
@@ -8,7 +8,7 @@ import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
8
8
  class HostFileEditTool implements Tool {
9
9
  name = "host_file_edit";
10
10
  description =
11
- "Replace exact text in a host filesystem file with new text. Not for workspace files under .vellum (use file_edit instead).";
11
+ "Replace exact text in a file on your guardian's device with new text. For files on your own machine, use file_edit instead.";
12
12
  category = "host-filesystem";
13
13
  defaultRiskLevel = RiskLevel.Medium;
14
14
 
@@ -13,7 +13,7 @@ import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
13
13
  class HostFileReadTool implements Tool {
14
14
  name = "host_file_read";
15
15
  description =
16
- "Read the contents of a file on the host filesystem, including images (JPEG, PNG, GIF, WebP). Not for workspace files under .vellum (use file_read instead).";
16
+ "Read the contents of a file on your guardian's device, including images (JPEG, PNG, GIF, WebP). For files on your own machine, use file_read instead.";
17
17
  category = "host-filesystem";
18
18
  defaultRiskLevel = RiskLevel.Medium;
19
19
 
@@ -54,21 +54,9 @@ class HostFileReadTool implements Tool {
54
54
  };
55
55
  }
56
56
 
57
- // Image files must be handled locally — the host-file proxy protocol
58
- // only carries {content, isError} and cannot transport contentBlocks
59
- // (base64 image data). Check for image extensions before the proxy
60
- // short-circuit so image reads work in managed/macOS+iOS sessions.
61
- const ext = extname(rawPath).toLowerCase();
62
- if (IMAGE_EXTENSIONS.has(ext)) {
63
- const pathCheck = hostPolicy(rawPath);
64
- if (!pathCheck.ok) {
65
- return { content: `Error: ${pathCheck.error}`, isError: true };
66
- }
67
- return readImageFile(pathCheck.resolved);
68
- }
69
-
70
57
  // Proxy to connected client for execution on the user's machine
71
- // when a capable client is available (managed/cloud-hosted mode).
58
+ // when a capable client is available (managed/cloud-hosted mode),
59
+ // including image reads that need the host filesystem view.
72
60
  if (context.hostFileProxy?.isAvailable()) {
73
61
  return context.hostFileProxy.request(
74
62
  {
@@ -82,6 +70,15 @@ class HostFileReadTool implements Tool {
82
70
  );
83
71
  }
84
72
 
73
+ const ext = extname(rawPath).toLowerCase();
74
+ if (IMAGE_EXTENSIONS.has(ext)) {
75
+ const pathCheck = hostPolicy(rawPath);
76
+ if (!pathCheck.ok) {
77
+ return { content: `Error: ${pathCheck.error}`, isError: true };
78
+ }
79
+ return readImageFile(pathCheck.resolved);
80
+ }
81
+
85
82
  const ops = new FileSystemOps(hostPolicy);
86
83
 
87
84
  const result = ops.readFileSafe({