@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,579 @@
1
+ /**
2
+ * Raw CDP JSON-RPC WebSocket transport used by the `cdp-inspect`
3
+ * backend. This module is intentionally backend-agnostic: it has no
4
+ * dependency on the browser-session manager, the cdp-inspect client,
5
+ * or any feature-flag / config plumbing. It simply adapts a CDP
6
+ * WebSocket URL (as returned from DevTools `/json/version` or
7
+ * `/json/list`) into an asynchronous request/response + event
8
+ * interface.
9
+ *
10
+ * The transport is deliberately minimal:
11
+ * - `send(method, params, opts?)` writes a JSON-RPC 2.0 request
12
+ * frame with a monotonic id, registers a pending entry in the
13
+ * correlation map, and resolves/rejects when the matching
14
+ * response arrives (or the socket dies / the caller aborts).
15
+ * - `addEventListener(listener)` subscribes to every inbound frame
16
+ * that carries no `id` — these are CDP domain events fanned out
17
+ * verbatim to listeners. Listeners do not affect request/response
18
+ * correlation.
19
+ * - `dispose()` proactively closes the socket and rejects every
20
+ * still-pending request exactly once with `CdpWsTransportError(
21
+ * "closed")`. It is idempotent.
22
+ *
23
+ * Failure modes map 1:1 onto {@link CdpWsTransportError} codes:
24
+ * - `closed` — the socket was closed (remote close,
25
+ * `dispose()`, or a pending send racing an
26
+ * already-closed transport).
27
+ * - `aborted` — the per-request `AbortSignal` fired. Any
28
+ * subsequent CDP response for that id is
29
+ * silently dropped.
30
+ * - `timeout` — the connect timeout expired before the
31
+ * socket reached `OPEN`.
32
+ * - `transport_error`— a WebSocket `error` event fired, or a send
33
+ * failed (e.g. serialization failure).
34
+ * - `cdp_error` — the peer returned a JSON-RPC error envelope
35
+ * (`{id, error: {code, message}}`).
36
+ */
37
+
38
+ export type CdpWsTransportErrorCode =
39
+ | "closed"
40
+ | "aborted"
41
+ | "timeout"
42
+ | "transport_error"
43
+ | "cdp_error";
44
+
45
+ /**
46
+ * Error thrown (or used to reject) by {@link CdpWsTransport} and
47
+ * {@link connectCdpWsTransport}. The `code` discriminates the
48
+ * category of failure so callers can branch without string-sniffing
49
+ * the message. For `cdp_error`, the CDP JSON-RPC error envelope
50
+ * fields are copied through verbatim for logging and upstream
51
+ * error mapping.
52
+ */
53
+ export class CdpWsTransportError extends Error {
54
+ readonly code: CdpWsTransportErrorCode;
55
+ readonly cdpMethod?: string;
56
+ readonly cdpCode?: number;
57
+ readonly cdpMessage?: string;
58
+ readonly cdpData?: unknown;
59
+ readonly underlying?: unknown;
60
+
61
+ constructor(
62
+ code: CdpWsTransportErrorCode,
63
+ message?: string,
64
+ details?: {
65
+ cdpMethod?: string;
66
+ cdpCode?: number;
67
+ cdpMessage?: string;
68
+ cdpData?: unknown;
69
+ underlying?: unknown;
70
+ },
71
+ ) {
72
+ super(message ?? code);
73
+ this.name = "CdpWsTransportError";
74
+ this.code = code;
75
+ this.cdpMethod = details?.cdpMethod;
76
+ this.cdpCode = details?.cdpCode;
77
+ this.cdpMessage = details?.cdpMessage;
78
+ this.cdpData = details?.cdpData;
79
+ this.underlying = details?.underlying;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Payload handed to event listeners registered via
85
+ * {@link CdpWsTransport.addEventListener}. Mirrors the wire-level
86
+ * shape of a CDP JSON-RPC notification minus the `id` field
87
+ * (notifications, by definition, carry no id).
88
+ */
89
+ export interface CdpTransportEvent {
90
+ method: string;
91
+ params?: unknown;
92
+ sessionId?: string;
93
+ }
94
+
95
+ /**
96
+ * Public interface exposed by this transport. Deliberately narrower
97
+ * than the higher-level `CdpClient` type used by tool code — this
98
+ * layer does not know about conversations, backend selection, or
99
+ * CDP error mapping to the shared `CdpError` taxonomy.
100
+ */
101
+ export interface CdpWsTransport {
102
+ /**
103
+ * Send a CDP method call over the socket and await its response.
104
+ *
105
+ * - `method` / `params` are serialized verbatim into a JSON-RPC
106
+ * 2.0 request envelope.
107
+ * - `opts.sessionId`, if provided, is forwarded on the wire as a
108
+ * top-level `sessionId` field — required for CDP "flattened"
109
+ * session attach mode.
110
+ * - `opts.signal` cancels the pending request: the returned
111
+ * promise rejects with `CdpWsTransportError("aborted")` and any
112
+ * subsequent response carrying the matching id is dropped.
113
+ *
114
+ * Failure modes:
115
+ * - resolves with the `result` field on success.
116
+ * - rejects with `cdp_error` if the peer returns a
117
+ * `{id, error}` envelope.
118
+ * - rejects with `closed` if the socket closes (or is disposed)
119
+ * before a response arrives.
120
+ * - rejects with `aborted` if the caller cancels.
121
+ * - rejects with `transport_error` if the send itself fails
122
+ * (e.g. the socket is already in a non-OPEN state and we race
123
+ * a close, or JSON serialization fails).
124
+ */
125
+ send<T = unknown>(
126
+ method: string,
127
+ params?: Record<string, unknown>,
128
+ opts?: { sessionId?: string; signal?: AbortSignal },
129
+ ): Promise<T>;
130
+
131
+ /**
132
+ * Register a listener for every inbound JSON-RPC notification
133
+ * (i.e. any frame whose `id` is missing). Returns an unsubscribe
134
+ * function. Listener errors are swallowed so one bad consumer
135
+ * cannot tear down the transport.
136
+ */
137
+ addEventListener(listener: (event: CdpTransportEvent) => void): () => void;
138
+
139
+ /**
140
+ * Close the underlying socket and reject every still-pending
141
+ * request with `CdpWsTransportError("closed")`. Idempotent —
142
+ * calling `dispose()` twice does nothing on the second call. A
143
+ * `dispose()` after a remote close is still safe.
144
+ */
145
+ dispose(): void;
146
+ }
147
+
148
+ interface PendingRequest {
149
+ resolve: (value: unknown) => void;
150
+ reject: (err: CdpWsTransportError) => void;
151
+ method: string;
152
+ // Listener cleanup for the per-request abort signal. May be null if
153
+ // the caller did not provide a signal.
154
+ cleanupAbort: (() => void) | null;
155
+ }
156
+
157
+ /**
158
+ * Minimal structural shape of the WebSocket we depend on. Using a
159
+ * local interface (instead of the DOM / bun-types `WebSocket`
160
+ * global's static constants) lets us stay compatible with either
161
+ * runtime and keeps the tests free of DOM typing hassles.
162
+ */
163
+ interface WsLike {
164
+ readyState: number;
165
+ send(data: string): void;
166
+ close(): void;
167
+ addEventListener(type: "open", listener: () => void): void;
168
+ addEventListener(type: "close", listener: () => void): void;
169
+ addEventListener(type: "error", listener: (ev: unknown) => void): void;
170
+ addEventListener(
171
+ type: "message",
172
+ listener: (ev: { data: unknown }) => void,
173
+ ): void;
174
+ removeEventListener?: (type: string, listener: unknown) => void;
175
+ }
176
+
177
+ // WebSocket.readyState constants. We avoid depending on the global
178
+ // WebSocket static (e.g. `WebSocket.OPEN`) because test fakes may not
179
+ // expose the static properties.
180
+ const WS_OPEN = 1;
181
+
182
+ const DEFAULT_CONNECT_TIMEOUT_MS = 5_000;
183
+
184
+ /**
185
+ * Open a raw CDP WebSocket transport against `url`. Resolves only
186
+ * after the socket has reached `OPEN`; rejects with
187
+ * `CdpWsTransportError("timeout")` if the connect-timeout expires,
188
+ * `CdpWsTransportError("aborted")` if `opts.signal` fires, or
189
+ * `transport_error` if the socket errors or closes before opening.
190
+ */
191
+ export async function connectCdpWsTransport(
192
+ url: string,
193
+ opts?: { connectTimeoutMs?: number; signal?: AbortSignal },
194
+ ): Promise<CdpWsTransport> {
195
+ const connectTimeoutMs = opts?.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
196
+ const callerSignal = opts?.signal;
197
+
198
+ if (callerSignal?.aborted) {
199
+ throw new CdpWsTransportError("aborted", "aborted before connect");
200
+ }
201
+
202
+ // bun's global `WebSocket` is API-compatible with the browser one.
203
+ const WebSocketCtor: new (url: string) => WsLike = (
204
+ globalThis as unknown as {
205
+ WebSocket: new (url: string) => WsLike;
206
+ }
207
+ ).WebSocket;
208
+ if (typeof WebSocketCtor !== "function") {
209
+ throw new CdpWsTransportError(
210
+ "transport_error",
211
+ "global WebSocket is not available in this runtime",
212
+ );
213
+ }
214
+
215
+ let ws: WsLike;
216
+ try {
217
+ ws = new WebSocketCtor(url);
218
+ } catch (err) {
219
+ throw new CdpWsTransportError(
220
+ "transport_error",
221
+ err instanceof Error ? err.message : String(err),
222
+ { underlying: err },
223
+ );
224
+ }
225
+
226
+ await new Promise<void>((resolve, reject) => {
227
+ let settled = false;
228
+ const timer = setTimeout(() => {
229
+ if (settled) return;
230
+ settled = true;
231
+ cleanupAbort();
232
+ try {
233
+ ws.close();
234
+ } catch {
235
+ // best effort
236
+ }
237
+ reject(new CdpWsTransportError("timeout", "connect timeout"));
238
+ }, connectTimeoutMs);
239
+
240
+ const onOpen = () => {
241
+ if (settled) return;
242
+ settled = true;
243
+ clearTimeout(timer);
244
+ cleanupAbort();
245
+ resolve();
246
+ };
247
+ const onError = (ev: unknown) => {
248
+ if (settled) return;
249
+ settled = true;
250
+ clearTimeout(timer);
251
+ cleanupAbort();
252
+ try {
253
+ ws.close();
254
+ } catch {
255
+ // best effort
256
+ }
257
+ reject(
258
+ new CdpWsTransportError(
259
+ "transport_error",
260
+ "websocket error during connect",
261
+ { underlying: ev },
262
+ ),
263
+ );
264
+ };
265
+ const onCloseBeforeOpen = () => {
266
+ if (settled) return;
267
+ settled = true;
268
+ clearTimeout(timer);
269
+ cleanupAbort();
270
+ reject(
271
+ new CdpWsTransportError(
272
+ "transport_error",
273
+ "websocket closed before open",
274
+ ),
275
+ );
276
+ };
277
+ const onCallerAbort = () => {
278
+ if (settled) return;
279
+ settled = true;
280
+ clearTimeout(timer);
281
+ cleanupAbort();
282
+ try {
283
+ ws.close();
284
+ } catch {
285
+ // best effort
286
+ }
287
+ reject(new CdpWsTransportError("aborted", "aborted during connect"));
288
+ };
289
+ const cleanupAbort = () => {
290
+ if (callerSignal) {
291
+ callerSignal.removeEventListener("abort", onCallerAbort);
292
+ }
293
+ };
294
+
295
+ ws.addEventListener("open", onOpen);
296
+ ws.addEventListener("error", onError);
297
+ ws.addEventListener("close", onCloseBeforeOpen);
298
+ if (callerSignal) {
299
+ callerSignal.addEventListener("abort", onCallerAbort, { once: true });
300
+ }
301
+ });
302
+
303
+ return createTransport(ws);
304
+ }
305
+
306
+ function createTransport(ws: WsLike): CdpWsTransport {
307
+ const pending = new Map<number, PendingRequest>();
308
+ const listeners = new Set<(event: CdpTransportEvent) => void>();
309
+ let nextId = 1;
310
+ let disposed = false;
311
+ let closed = false;
312
+
313
+ const rejectAllPending = (code: CdpWsTransportErrorCode, message: string) => {
314
+ if (pending.size === 0) return;
315
+ // Snapshot entries so that caller `.catch()` handlers invoked
316
+ // synchronously via `reject` cannot mutate the map we are iterating.
317
+ const entries = Array.from(pending.entries());
318
+ pending.clear();
319
+ for (const [, entry] of entries) {
320
+ entry.cleanupAbort?.();
321
+ entry.reject(
322
+ new CdpWsTransportError(code, message, { cdpMethod: entry.method }),
323
+ );
324
+ }
325
+ };
326
+
327
+ const handleMessage = (ev: { data: unknown }) => {
328
+ if (disposed) return;
329
+ let raw: string;
330
+ if (typeof ev.data === "string") {
331
+ raw = ev.data;
332
+ } else if (ev.data instanceof ArrayBuffer) {
333
+ raw = new TextDecoder().decode(ev.data);
334
+ } else if (
335
+ typeof (ev.data as { toString?: () => string })?.toString === "function"
336
+ ) {
337
+ raw = String(ev.data);
338
+ } else {
339
+ // Unknown binary payload — CDP is always JSON text, so drop it.
340
+ return;
341
+ }
342
+ let frame: unknown;
343
+ try {
344
+ frame = JSON.parse(raw);
345
+ } catch {
346
+ return;
347
+ }
348
+ if (!frame || typeof frame !== "object") return;
349
+ const obj = frame as {
350
+ id?: unknown;
351
+ result?: unknown;
352
+ error?: { code?: unknown; message?: unknown; data?: unknown };
353
+ method?: unknown;
354
+ params?: unknown;
355
+ sessionId?: unknown;
356
+ };
357
+
358
+ if (typeof obj.id === "number") {
359
+ const entry = pending.get(obj.id);
360
+ if (!entry) {
361
+ // Either an unknown id (protocol violation) or an aborted
362
+ // request whose entry we already removed — drop silently.
363
+ return;
364
+ }
365
+ pending.delete(obj.id);
366
+ entry.cleanupAbort?.();
367
+ if (obj.error && typeof obj.error === "object") {
368
+ const cdpCode =
369
+ typeof obj.error.code === "number" ? obj.error.code : undefined;
370
+ const cdpMessage =
371
+ typeof obj.error.message === "string" ? obj.error.message : undefined;
372
+ entry.reject(
373
+ new CdpWsTransportError(
374
+ "cdp_error",
375
+ cdpMessage ?? `cdp error for ${entry.method}`,
376
+ {
377
+ cdpMethod: entry.method,
378
+ cdpCode,
379
+ cdpMessage,
380
+ cdpData: obj.error.data,
381
+ },
382
+ ),
383
+ );
384
+ } else {
385
+ entry.resolve(obj.result);
386
+ }
387
+ return;
388
+ }
389
+
390
+ // No id → CDP domain event. Fan out to listeners, swallowing
391
+ // any listener throws.
392
+ if (typeof obj.method === "string") {
393
+ const event: CdpTransportEvent = {
394
+ method: obj.method,
395
+ params: obj.params,
396
+ sessionId:
397
+ typeof obj.sessionId === "string" ? obj.sessionId : undefined,
398
+ };
399
+ for (const listener of listeners) {
400
+ try {
401
+ listener(event);
402
+ } catch {
403
+ // listener errors are swallowed to keep the transport alive
404
+ }
405
+ }
406
+ }
407
+ };
408
+
409
+ const handleClose = () => {
410
+ if (closed) return;
411
+ closed = true;
412
+ rejectAllPending("closed", "websocket closed");
413
+ };
414
+
415
+ const handleError = (ev: unknown) => {
416
+ if (closed) return;
417
+ closed = true;
418
+ // Best-effort close after an error so we don't leak a half-open
419
+ // socket. Do not throw on already-closed sockets.
420
+ try {
421
+ ws.close();
422
+ } catch {
423
+ // ignored
424
+ }
425
+ // Reject pending as transport_error so callers can distinguish
426
+ // a protocol-level peer close from an explicit socket error.
427
+ if (pending.size > 0) {
428
+ const entries = Array.from(pending.entries());
429
+ pending.clear();
430
+ for (const [, entry] of entries) {
431
+ entry.cleanupAbort?.();
432
+ entry.reject(
433
+ new CdpWsTransportError("transport_error", "websocket error", {
434
+ cdpMethod: entry.method,
435
+ underlying: ev,
436
+ }),
437
+ );
438
+ }
439
+ }
440
+ };
441
+
442
+ ws.addEventListener("message", handleMessage);
443
+ ws.addEventListener("close", handleClose);
444
+ ws.addEventListener("error", handleError);
445
+
446
+ const transport: CdpWsTransport = {
447
+ send<T = unknown>(
448
+ method: string,
449
+ params?: Record<string, unknown>,
450
+ opts?: { sessionId?: string; signal?: AbortSignal },
451
+ ): Promise<T> {
452
+ if (disposed || closed) {
453
+ return Promise.reject(
454
+ new CdpWsTransportError("closed", "transport already closed", {
455
+ cdpMethod: method,
456
+ }),
457
+ );
458
+ }
459
+ const signal = opts?.signal;
460
+ if (signal?.aborted) {
461
+ return Promise.reject(
462
+ new CdpWsTransportError("aborted", "aborted before send", {
463
+ cdpMethod: method,
464
+ }),
465
+ );
466
+ }
467
+
468
+ const id = nextId++;
469
+ const frame: Record<string, unknown> = { id, method };
470
+ if (params !== undefined) frame.params = params;
471
+ if (opts?.sessionId !== undefined) frame.sessionId = opts.sessionId;
472
+
473
+ let serialized: string;
474
+ try {
475
+ serialized = JSON.stringify(frame);
476
+ } catch (err) {
477
+ return Promise.reject(
478
+ new CdpWsTransportError(
479
+ "transport_error",
480
+ err instanceof Error ? err.message : String(err),
481
+ { cdpMethod: method, underlying: err },
482
+ ),
483
+ );
484
+ }
485
+
486
+ // Guard against sending on a non-OPEN socket. By construction
487
+ // the socket is OPEN at the time we hand the transport to
488
+ // callers (connectCdpWsTransport waits for the `open` event),
489
+ // so any other readyState means the socket has since moved
490
+ // past OPEN — treat it as closed so callers can't observe
491
+ // silently dropped frames.
492
+ if (ws.readyState !== WS_OPEN) {
493
+ return Promise.reject(
494
+ new CdpWsTransportError("closed", "socket not open", {
495
+ cdpMethod: method,
496
+ }),
497
+ );
498
+ }
499
+
500
+ return new Promise<T>((resolve, reject) => {
501
+ // Register the pending entry FIRST so that an abort or
502
+ // inbound response racing the rest of this function body
503
+ // always has a live entry to act on. Without this ordering
504
+ // a synchronous abort registered below could fire before
505
+ // the entry exists, silently dropping the cancellation.
506
+ pending.set(id, {
507
+ resolve: (value: unknown) => resolve(value as T),
508
+ reject,
509
+ method,
510
+ cleanupAbort: null,
511
+ });
512
+
513
+ if (signal) {
514
+ const onAbort = () => {
515
+ const entry = pending.get(id);
516
+ if (!entry) return;
517
+ pending.delete(id);
518
+ entry.cleanupAbort?.();
519
+ entry.reject(
520
+ new CdpWsTransportError("aborted", "aborted during send", {
521
+ cdpMethod: method,
522
+ }),
523
+ );
524
+ };
525
+ signal.addEventListener("abort", onAbort, { once: true });
526
+ const entry = pending.get(id);
527
+ if (entry) {
528
+ entry.cleanupAbort = () => {
529
+ signal.removeEventListener("abort", onAbort);
530
+ };
531
+ }
532
+ }
533
+
534
+ try {
535
+ ws.send(serialized);
536
+ } catch (err) {
537
+ const entry = pending.get(id);
538
+ if (entry) {
539
+ pending.delete(id);
540
+ entry.cleanupAbort?.();
541
+ }
542
+ reject(
543
+ new CdpWsTransportError(
544
+ "transport_error",
545
+ err instanceof Error ? err.message : String(err),
546
+ { cdpMethod: method, underlying: err },
547
+ ),
548
+ );
549
+ }
550
+ });
551
+ },
552
+
553
+ addEventListener(listener) {
554
+ listeners.add(listener);
555
+ return () => {
556
+ listeners.delete(listener);
557
+ };
558
+ },
559
+
560
+ dispose() {
561
+ if (disposed) return;
562
+ disposed = true;
563
+ // Reject pending requests BEFORE calling close() so that
564
+ // callers observe the explicit "disposed" signal even if the
565
+ // underlying `close()` fires a synchronous `close` event.
566
+ rejectAllPending("closed", "transport disposed");
567
+ if (!closed) {
568
+ closed = true;
569
+ try {
570
+ ws.close();
571
+ } catch {
572
+ // ignored — already-closed sockets may throw
573
+ }
574
+ }
575
+ },
576
+ };
577
+
578
+ return transport;
579
+ }