@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,695 @@
1
+ /**
2
+ * Common CDP idioms that each browser tool would otherwise reimplement:
3
+ * selector resolution, mouse/keyboard dispatch, screenshot capture,
4
+ * navigation, polling waits, and small Runtime.evaluate wrappers.
5
+ *
6
+ * Every helper takes a {@link CdpClient} as its first argument, forwards
7
+ * an optional {@link AbortSignal} verbatim to `CdpClient.send`, and
8
+ * throws a {@link CdpError} on failure. The module is pure plumbing —
9
+ * no I/O beyond the injected CdpClient — which keeps it trivial to
10
+ * unit-test against a fake in-memory client.
11
+ */
12
+
13
+ import { CdpError } from "./errors.js";
14
+ import type { CdpClient } from "./types.js";
15
+
16
+ // ── Selector / node resolution ────────────────────────────────────────
17
+
18
+ /**
19
+ * Resolve a CSS selector to a CDP `backendNodeId`. Runs
20
+ * `DOM.getDocument` → `DOM.querySelector` → `DOM.describeNode` and
21
+ * throws {@link CdpError} with `code: "cdp_error"` if no element
22
+ * matches (CDP signals this by returning `nodeId: 0`).
23
+ */
24
+ export async function querySelectorBackendNodeId(
25
+ cdp: CdpClient,
26
+ selector: string,
27
+ signal?: AbortSignal,
28
+ ): Promise<number> {
29
+ const { root } = await cdp.send<{ root: { nodeId: number } }>(
30
+ "DOM.getDocument",
31
+ {},
32
+ signal,
33
+ );
34
+ const { nodeId } = await cdp.send<{ nodeId: number }>(
35
+ "DOM.querySelector",
36
+ { nodeId: root.nodeId, selector },
37
+ signal,
38
+ );
39
+ if (!nodeId) {
40
+ throw new CdpError("cdp_error", `Element not found: ${selector}`, {
41
+ cdpMethod: "DOM.querySelector",
42
+ cdpParams: { selector },
43
+ });
44
+ }
45
+ const { node } = await cdp.send<{ node: { backendNodeId: number } }>(
46
+ "DOM.describeNode",
47
+ { nodeId, depth: 0 },
48
+ signal,
49
+ );
50
+ return node.backendNodeId;
51
+ }
52
+
53
+ /** Scroll the element identified by `backendNodeId` into view if needed. */
54
+ export async function scrollIntoViewIfNeeded(
55
+ cdp: CdpClient,
56
+ backendNodeId: number,
57
+ signal?: AbortSignal,
58
+ ): Promise<void> {
59
+ await cdp.send("DOM.scrollIntoViewIfNeeded", { backendNodeId }, signal);
60
+ }
61
+
62
+ /**
63
+ * Read the element's content-quad via `DOM.getBoxModel` and return the
64
+ * midpoint in viewport coordinates. CDP returns `content` as a flat
65
+ * 8-number array `[x1,y1, x2,y2, x3,y3, x4,y4]`.
66
+ */
67
+ export async function getCenterPoint(
68
+ cdp: CdpClient,
69
+ backendNodeId: number,
70
+ signal?: AbortSignal,
71
+ ): Promise<{ x: number; y: number }> {
72
+ const { model } = await cdp.send<{
73
+ model: { content: number[] };
74
+ }>("DOM.getBoxModel", { backendNodeId }, signal);
75
+ const xs = [
76
+ model.content[0]!,
77
+ model.content[2]!,
78
+ model.content[4]!,
79
+ model.content[6]!,
80
+ ];
81
+ const ys = [
82
+ model.content[1]!,
83
+ model.content[3]!,
84
+ model.content[5]!,
85
+ model.content[7]!,
86
+ ];
87
+ return {
88
+ x: (Math.min(...xs) + Math.max(...xs)) / 2,
89
+ y: (Math.min(...ys) + Math.max(...ys)) / 2,
90
+ };
91
+ }
92
+
93
+ /** Focus an element by `backendNodeId` via `DOM.focus`. */
94
+ export async function focusElement(
95
+ cdp: CdpClient,
96
+ backendNodeId: number,
97
+ signal?: AbortSignal,
98
+ ): Promise<void> {
99
+ await cdp.send("DOM.focus", { backendNodeId }, signal);
100
+ }
101
+
102
+ // ── Mouse / keyboard / wheel dispatch ─────────────────────────────────
103
+
104
+ /**
105
+ * Dispatch a full left-click (mouseMoved + mousePressed + mouseReleased)
106
+ * at the given viewport point.
107
+ */
108
+ export async function dispatchClickAt(
109
+ cdp: CdpClient,
110
+ point: { x: number; y: number },
111
+ signal?: AbortSignal,
112
+ ): Promise<void> {
113
+ const base = { x: point.x, y: point.y, button: "left", clickCount: 1 };
114
+ await cdp.send(
115
+ "Input.dispatchMouseEvent",
116
+ { ...base, type: "mouseMoved" },
117
+ signal,
118
+ );
119
+ await cdp.send(
120
+ "Input.dispatchMouseEvent",
121
+ { ...base, type: "mousePressed" },
122
+ signal,
123
+ );
124
+ await cdp.send(
125
+ "Input.dispatchMouseEvent",
126
+ { ...base, type: "mouseReleased" },
127
+ signal,
128
+ );
129
+ }
130
+
131
+ /** Dispatch a single mouseMoved (hover) at the given viewport point. */
132
+ export async function dispatchHoverAt(
133
+ cdp: CdpClient,
134
+ point: { x: number; y: number },
135
+ signal?: AbortSignal,
136
+ ): Promise<void> {
137
+ await cdp.send(
138
+ "Input.dispatchMouseEvent",
139
+ { type: "mouseMoved", x: point.x, y: point.y, button: "none" },
140
+ signal,
141
+ );
142
+ }
143
+
144
+ /**
145
+ * Insert text at the currently focused element via `Input.insertText`.
146
+ * Unlike synthesizing individual key events, this dispatches the right
147
+ * `input`/`change` events that form controls expect.
148
+ */
149
+ export async function dispatchInsertText(
150
+ cdp: CdpClient,
151
+ text: string,
152
+ signal?: AbortSignal,
153
+ ): Promise<void> {
154
+ await cdp.send("Input.insertText", { text }, signal);
155
+ }
156
+
157
+ /**
158
+ * Per-key descriptor used by {@link dispatchKeyPress}. Mirrors the
159
+ * fields CDP's `Input.dispatchKeyEvent` accepts. `text` is set only
160
+ * for printable keys (so we know to also dispatch a `char` event).
161
+ */
162
+ interface KeyDescriptor {
163
+ key: string;
164
+ code: string;
165
+ windowsVirtualKeyCode: number;
166
+ text?: string;
167
+ }
168
+
169
+ /**
170
+ * Subset of the US keyboard layout used to populate
171
+ * `Input.dispatchKeyEvent` params. Without these fields, sites that
172
+ * read `event.keyCode` (e.g. `event.keyCode === 13` for Enter) or
173
+ * `event.code` see zeros and the press is silently ignored.
174
+ *
175
+ * Single-character keys (a-z, A-Z, 0-9) are resolved dynamically by
176
+ * {@link resolveKeyDescriptor} to keep the static map small.
177
+ */
178
+ const KEY_DESCRIPTORS: Record<string, KeyDescriptor> = {
179
+ Enter: {
180
+ key: "Enter",
181
+ code: "Enter",
182
+ windowsVirtualKeyCode: 13,
183
+ text: "\r",
184
+ },
185
+ Tab: { key: "Tab", code: "Tab", windowsVirtualKeyCode: 9, text: "\t" },
186
+ Escape: { key: "Escape", code: "Escape", windowsVirtualKeyCode: 27 },
187
+ Backspace: {
188
+ key: "Backspace",
189
+ code: "Backspace",
190
+ windowsVirtualKeyCode: 8,
191
+ },
192
+ Delete: { key: "Delete", code: "Delete", windowsVirtualKeyCode: 46 },
193
+ Insert: { key: "Insert", code: "Insert", windowsVirtualKeyCode: 45 },
194
+ ArrowUp: { key: "ArrowUp", code: "ArrowUp", windowsVirtualKeyCode: 38 },
195
+ ArrowDown: {
196
+ key: "ArrowDown",
197
+ code: "ArrowDown",
198
+ windowsVirtualKeyCode: 40,
199
+ },
200
+ ArrowLeft: {
201
+ key: "ArrowLeft",
202
+ code: "ArrowLeft",
203
+ windowsVirtualKeyCode: 37,
204
+ },
205
+ ArrowRight: {
206
+ key: "ArrowRight",
207
+ code: "ArrowRight",
208
+ windowsVirtualKeyCode: 39,
209
+ },
210
+ // Navigation keys. Sites commonly check `event.keyCode` for these
211
+ // (PageDown = 34 to scroll a page, Home = 36 to jump to top, etc.)
212
+ // so omitting `code`/`windowsVirtualKeyCode` makes the press a
213
+ // silent no-op on those handlers.
214
+ Home: { key: "Home", code: "Home", windowsVirtualKeyCode: 36 },
215
+ End: { key: "End", code: "End", windowsVirtualKeyCode: 35 },
216
+ PageUp: { key: "PageUp", code: "PageUp", windowsVirtualKeyCode: 33 },
217
+ PageDown: { key: "PageDown", code: "PageDown", windowsVirtualKeyCode: 34 },
218
+ // Space is special: Playwright callers use "Space" as the key name
219
+ // but `event.key` is actually " ". Accept both spellings so either
220
+ // calling convention works, and always emit `code: "Space"` +
221
+ // `windowsVirtualKeyCode: 32` so Space-to-activate / Space-to-scroll
222
+ // handlers fire correctly.
223
+ Space: { key: " ", code: "Space", windowsVirtualKeyCode: 32, text: " " },
224
+ " ": { key: " ", code: "Space", windowsVirtualKeyCode: 32, text: " " },
225
+ // Function keys (F1-F12). Virtual key codes 112-123 per the Windows
226
+ // input API. `resolveKeyDescriptor` cannot derive these dynamically
227
+ // because they are multi-character names with no 1:1 char mapping.
228
+ F1: { key: "F1", code: "F1", windowsVirtualKeyCode: 112 },
229
+ F2: { key: "F2", code: "F2", windowsVirtualKeyCode: 113 },
230
+ F3: { key: "F3", code: "F3", windowsVirtualKeyCode: 114 },
231
+ F4: { key: "F4", code: "F4", windowsVirtualKeyCode: 115 },
232
+ F5: { key: "F5", code: "F5", windowsVirtualKeyCode: 116 },
233
+ F6: { key: "F6", code: "F6", windowsVirtualKeyCode: 117 },
234
+ F7: { key: "F7", code: "F7", windowsVirtualKeyCode: 118 },
235
+ F8: { key: "F8", code: "F8", windowsVirtualKeyCode: 119 },
236
+ F9: { key: "F9", code: "F9", windowsVirtualKeyCode: 120 },
237
+ F10: { key: "F10", code: "F10", windowsVirtualKeyCode: 121 },
238
+ F11: { key: "F11", code: "F11", windowsVirtualKeyCode: 122 },
239
+ F12: { key: "F12", code: "F12", windowsVirtualKeyCode: 123 },
240
+ };
241
+
242
+ /**
243
+ * Resolve a key name into a {@link KeyDescriptor}. Single-character
244
+ * keys (a-z, A-Z, 0-9) are computed on demand: `code` is `KeyA`/
245
+ * `Digit0`/etc., `windowsVirtualKeyCode` is the uppercase ASCII code,
246
+ * and `text` is the literal character. Returns `null` for unknown
247
+ * multi-character keys so callers can fall back to a minimal event.
248
+ */
249
+ function resolveKeyDescriptor(key: string): KeyDescriptor | null {
250
+ const fromMap = KEY_DESCRIPTORS[key];
251
+ if (fromMap) return fromMap;
252
+ if (key.length !== 1) return null;
253
+ const charCode = key.charCodeAt(0);
254
+ // a-z / A-Z
255
+ if (
256
+ (charCode >= 65 && charCode <= 90) ||
257
+ (charCode >= 97 && charCode <= 122)
258
+ ) {
259
+ const upper = key.toUpperCase();
260
+ return {
261
+ key,
262
+ code: `Key${upper}`,
263
+ windowsVirtualKeyCode: upper.charCodeAt(0),
264
+ text: key,
265
+ };
266
+ }
267
+ // 0-9
268
+ if (charCode >= 48 && charCode <= 57) {
269
+ return {
270
+ key,
271
+ code: `Digit${key}`,
272
+ windowsVirtualKeyCode: charCode,
273
+ text: key,
274
+ };
275
+ }
276
+ // Other printable ASCII (space, punctuation): still emit text + the
277
+ // raw char code so sites that check `event.key` and `event.charCode`
278
+ // see something sensible.
279
+ if (charCode >= 32 && charCode <= 126) {
280
+ return {
281
+ key,
282
+ code: "",
283
+ windowsVirtualKeyCode: charCode,
284
+ text: key,
285
+ };
286
+ }
287
+ return null;
288
+ }
289
+
290
+ /**
291
+ * Press a single key (keyDown + optional `char` + keyUp). Resolves
292
+ * the key name to a {@link KeyDescriptor} so CDP receives the right
293
+ * `code` / `windowsVirtualKeyCode` / `text` fields — required by
294
+ * sites that check `event.keyCode` (e.g. Enter-to-submit) or
295
+ * `event.code`. For printable keys we also dispatch a `char` event
296
+ * between keyDown and keyUp so the character is actually inserted
297
+ * into focused inputs.
298
+ */
299
+ export async function dispatchKeyPress(
300
+ cdp: CdpClient,
301
+ key: string,
302
+ signal?: AbortSignal,
303
+ ): Promise<void> {
304
+ const desc = resolveKeyDescriptor(key);
305
+ if (!desc) {
306
+ // Unknown multi-character key (e.g. F-keys we have not mapped).
307
+ // Fall back to the minimal payload so callers still see a
308
+ // keyDown/keyUp pair, and warn so we can extend the map.
309
+
310
+ console.warn(
311
+ `dispatchKeyPress: no descriptor for key "${key}", sending minimal event`,
312
+ );
313
+ await cdp.send("Input.dispatchKeyEvent", { type: "keyDown", key }, signal);
314
+ await cdp.send("Input.dispatchKeyEvent", { type: "keyUp", key }, signal);
315
+ return;
316
+ }
317
+
318
+ const baseParams: Record<string, unknown> = {
319
+ key: desc.key,
320
+ code: desc.code,
321
+ windowsVirtualKeyCode: desc.windowsVirtualKeyCode,
322
+ };
323
+ if (desc.text !== undefined) {
324
+ baseParams.text = desc.text;
325
+ }
326
+
327
+ await cdp.send(
328
+ "Input.dispatchKeyEvent",
329
+ { ...baseParams, type: "keyDown" },
330
+ signal,
331
+ );
332
+ if (desc.text !== undefined) {
333
+ await cdp.send(
334
+ "Input.dispatchKeyEvent",
335
+ { ...baseParams, type: "char" },
336
+ signal,
337
+ );
338
+ }
339
+ await cdp.send(
340
+ "Input.dispatchKeyEvent",
341
+ { ...baseParams, type: "keyUp" },
342
+ signal,
343
+ );
344
+ }
345
+
346
+ /** Dispatch a wheel scroll delta at the given viewport point. */
347
+ export async function dispatchWheelScroll(
348
+ cdp: CdpClient,
349
+ point: { x: number; y: number },
350
+ delta: { deltaX: number; deltaY: number },
351
+ signal?: AbortSignal,
352
+ ): Promise<void> {
353
+ await cdp.send(
354
+ "Input.dispatchMouseEvent",
355
+ {
356
+ type: "mouseWheel",
357
+ x: point.x,
358
+ y: point.y,
359
+ deltaX: delta.deltaX,
360
+ deltaY: delta.deltaY,
361
+ },
362
+ signal,
363
+ );
364
+ }
365
+
366
+ // ── Runtime.evaluate wrappers ─────────────────────────────────────────
367
+
368
+ /** Get the current page URL via `Runtime.evaluate("document.location.href")`. */
369
+ export async function getCurrentUrl(
370
+ cdp: CdpClient,
371
+ signal?: AbortSignal,
372
+ ): Promise<string> {
373
+ const { result } = await cdp.send<{ result: { value: string } }>(
374
+ "Runtime.evaluate",
375
+ { expression: "document.location.href", returnByValue: true },
376
+ signal,
377
+ );
378
+ return result.value;
379
+ }
380
+
381
+ /** Get the current page title via `Runtime.evaluate("document.title")`. */
382
+ export async function getPageTitle(
383
+ cdp: CdpClient,
384
+ signal?: AbortSignal,
385
+ ): Promise<string> {
386
+ const { result } = await cdp.send<{ result: { value: string } }>(
387
+ "Runtime.evaluate",
388
+ { expression: "document.title", returnByValue: true },
389
+ signal,
390
+ );
391
+ return result.value ?? "";
392
+ }
393
+
394
+ /**
395
+ * Evaluate a JS expression via `Runtime.evaluate` and return the
396
+ * deserialized value. Throws {@link CdpError} with `code: "cdp_error"`
397
+ * if the expression threw (surfaced via CDP's `exceptionDetails`).
398
+ *
399
+ * Defaults: `returnByValue: true`, `awaitPromise: true`, `userGesture: true`.
400
+ */
401
+ export async function evaluateExpression<T = unknown>(
402
+ cdp: CdpClient,
403
+ expression: string,
404
+ opts?: { awaitPromise?: boolean },
405
+ signal?: AbortSignal,
406
+ ): Promise<T> {
407
+ const res = await cdp.send<{
408
+ result: { value: T };
409
+ exceptionDetails?: {
410
+ text?: string;
411
+ exception?: { description?: string };
412
+ };
413
+ }>(
414
+ "Runtime.evaluate",
415
+ {
416
+ expression,
417
+ returnByValue: true,
418
+ awaitPromise: opts?.awaitPromise ?? true,
419
+ userGesture: true,
420
+ },
421
+ signal,
422
+ );
423
+ if (res.exceptionDetails) {
424
+ const msg =
425
+ res.exceptionDetails.exception?.description ??
426
+ res.exceptionDetails.text ??
427
+ "Runtime.evaluate exception";
428
+ throw new CdpError("cdp_error", msg, {
429
+ cdpMethod: "Runtime.evaluate",
430
+ cdpParams: { expression },
431
+ });
432
+ }
433
+ return res.result.value;
434
+ }
435
+
436
+ // ── Screenshot ────────────────────────────────────────────────────────
437
+
438
+ /**
439
+ * Capture a JPEG screenshot via `Page.captureScreenshot` and return the
440
+ * decoded bytes as a Node `Buffer`. Defaults to quality 80. Pass
441
+ * `fullPage: true` to capture beyond the viewport.
442
+ */
443
+ export async function captureScreenshotJpeg(
444
+ cdp: CdpClient,
445
+ opts: { quality?: number; fullPage?: boolean } = {},
446
+ signal?: AbortSignal,
447
+ ): Promise<Buffer> {
448
+ const { data } = await cdp.send<{ data: string }>(
449
+ "Page.captureScreenshot",
450
+ {
451
+ format: "jpeg",
452
+ quality: opts.quality ?? 80,
453
+ captureBeyondViewport: opts.fullPage === true,
454
+ },
455
+ signal,
456
+ );
457
+ return Buffer.from(data, "base64");
458
+ }
459
+
460
+ // ── Navigation / waiting ──────────────────────────────────────────────
461
+
462
+ /**
463
+ * Navigate to `url` and wait until the new document has committed
464
+ * (the URL has changed from the pre-navigation URL, or it's a
465
+ * same-URL reload) AND `document.readyState` has reached
466
+ * `interactive` or `complete`, or the timeout elapses.
467
+ *
468
+ * CDP's `Page.navigate` resolves as soon as the request is sent, not
469
+ * when the page has loaded. Subscribing to lifecycle events would
470
+ * require a long-lived event channel that the extension-backed
471
+ * CdpClient cannot currently provide, so this helper polls both
472
+ * `document.readyState` and `document.location.href` via
473
+ * {@link evaluateExpression} — which works uniformly across both
474
+ * Playwright-backed and extension-backed clients.
475
+ *
476
+ * The commit-detection step is the interesting part: on same-origin
477
+ * navigations or cached responses, the browser can return
478
+ * `readyState === "complete"` from the OLD execution context for a
479
+ * brief window after `Page.navigate` resolves but before the new
480
+ * document has been installed. Reading only `readyState` would
481
+ * accept that stale state and report success against the old URL.
482
+ * Combining the two observations in a single evaluate and requiring
483
+ * an observed URL change closes that race.
484
+ *
485
+ * Returns `{ finalUrl, timedOut }`. `finalUrl` is the last `href`
486
+ * observed inside the polling loop (so it reflects the new document
487
+ * even on commit races) and may differ from `url` if the page
488
+ * redirected.
489
+ */
490
+ export async function navigateAndWait(
491
+ cdp: CdpClient,
492
+ url: string,
493
+ opts: { timeoutMs?: number } = {},
494
+ signal?: AbortSignal,
495
+ ): Promise<{ finalUrl: string; timedOut: boolean }> {
496
+ const timeoutMs = opts.timeoutMs ?? 15_000;
497
+
498
+ // Capture the pre-navigation URL so the polling loop can detect
499
+ // when the new document has committed. If the pre-read fails (rare
500
+ // — e.g. a fresh about:blank that hasn't initialized a Runtime
501
+ // context yet), we fall back to readyState-only polling because we
502
+ // have no baseline to compare against.
503
+ let urlBeforeNav = "";
504
+ try {
505
+ urlBeforeNav = await getCurrentUrl(cdp, signal);
506
+ } catch {
507
+ // Non-fatal: urlBeforeNav stays empty and commit detection becomes
508
+ // a no-op (see `committed` below).
509
+ }
510
+
511
+ // CDP's `Page.navigate` does NOT throw on transport-layer errors
512
+ // (DNS failure, connection refused, etc.). Instead it resolves with
513
+ // `{ frameId, errorText? }` and we have to surface the failure
514
+ // ourselves. Otherwise we silently start polling readyState on the
515
+ // OLD page (which is "complete") and report success with the stale
516
+ // URL.
517
+ const navResp = await cdp.send<{ frameId?: string; errorText?: string }>(
518
+ "Page.navigate",
519
+ { url },
520
+ signal,
521
+ );
522
+ if (navResp?.errorText) {
523
+ throw new CdpError("cdp_error", navResp.errorText, {
524
+ cdpMethod: "Page.navigate",
525
+ cdpParams: { url },
526
+ });
527
+ }
528
+
529
+ // Same-URL reloads (including `about:blank` → `about:blank`) can't
530
+ // be detected via URL change. Fall back to readyState-only polling
531
+ // in that case, matching the pre-commit-detection behavior.
532
+ const sameUrlReload = urlBeforeNav !== "" && url === urlBeforeNav;
533
+
534
+ const startedAt = Date.now();
535
+ // Track exit reason explicitly so the post-loop classification does
536
+ // not race against `Date.now()` (the final read could otherwise
537
+ // push us across the timeout boundary and falsely flip `timedOut`
538
+ // back to true).
539
+ let completed = false;
540
+ // Track the last href we successfully observed from inside the
541
+ // loop. We prefer this to a post-loop `getCurrentUrl` call because
542
+ // the latter races the very same commit window that motivates the
543
+ // in-loop commit check.
544
+ let lastKnownHref = urlBeforeNav;
545
+
546
+ while (Date.now() - startedAt < timeoutMs) {
547
+ if (signal?.aborted) {
548
+ throw new CdpError("aborted", "Navigation aborted");
549
+ }
550
+
551
+ // Query `readyState` and `location.href` in a single evaluate so
552
+ // the two observations come from the same execution context and
553
+ // cannot straddle a commit boundary.
554
+ try {
555
+ const snapshot = await evaluateExpression<{
556
+ readyState: string;
557
+ href: string;
558
+ }>(
559
+ cdp,
560
+ "({ readyState: document.readyState, href: document.location.href })",
561
+ {},
562
+ signal,
563
+ );
564
+ if (snapshot && typeof snapshot.href === "string") {
565
+ lastKnownHref = snapshot.href;
566
+ }
567
+ const readyStateOk =
568
+ snapshot?.readyState === "interactive" ||
569
+ snapshot?.readyState === "complete";
570
+ // On cross-URL navigations, require BOTH a ready readyState
571
+ // AND an observed URL change so we don't accept the OLD
572
+ // page's "complete" state before the new document has
573
+ // committed. Same-URL reloads and missing pre-nav URLs fall
574
+ // back to readyState-only because there's nothing to compare.
575
+ const committed =
576
+ sameUrlReload ||
577
+ urlBeforeNav === "" ||
578
+ (typeof snapshot?.href === "string" && snapshot.href !== urlBeforeNav);
579
+ if (readyStateOk && committed) {
580
+ completed = true;
581
+ break;
582
+ }
583
+ } catch (err) {
584
+ // `Runtime.evaluate` can fail transiently while the old
585
+ // execution context is being torn down and the new one has
586
+ // not yet been created ("Execution context was destroyed" /
587
+ // "Cannot find context with specified id"). Treat CDP errors
588
+ // as retry-worthy; the timeout bound below guarantees we
589
+ // don't loop forever. Abort errors are re-thrown so the
590
+ // caller's AbortSignal is still honoured promptly.
591
+ if (err instanceof CdpError && err.code === "aborted") throw err;
592
+ if (!(err instanceof CdpError)) throw err;
593
+ }
594
+
595
+ await new Promise((r) => setTimeout(r, 100));
596
+ }
597
+
598
+ const timedOut = !completed;
599
+
600
+ // Prefer the last href observed inside the loop. If the loop never
601
+ // produced a successful observation (e.g. all evaluates failed to
602
+ // transient context errors), fall back to `getCurrentUrl` as a
603
+ // best-effort read.
604
+ let finalUrl = lastKnownHref;
605
+ if (finalUrl === "") {
606
+ try {
607
+ finalUrl = await getCurrentUrl(cdp, signal);
608
+ } catch {
609
+ // Nothing more to do — surface empty string and let the caller
610
+ // decide how to render it.
611
+ }
612
+ }
613
+ return { finalUrl, timedOut };
614
+ }
615
+
616
+ /**
617
+ * Poll until a selector matches an element in the requested state,
618
+ * then return its `backendNodeId`. Throws {@link CdpError} on timeout
619
+ * or abort.
620
+ *
621
+ * `state` controls the readiness check:
622
+ * - `"visible"` (default): the element must be in the DOM AND have a
623
+ * non-zero bounding box AND not be `display:none` /
624
+ * `visibility:hidden`. This matches Playwright's
625
+ * `page.waitForSelector` default and is the right semantics for
626
+ * click/hover targets that may be hydrated asynchronously.
627
+ * - `"attached"`: the element only needs to exist in the DOM. Useful
628
+ * for `browser_wait_for` selector mode where the caller just wants
629
+ * to know "did this node appear at all" regardless of layout.
630
+ */
631
+ export async function waitForSelector(
632
+ cdp: CdpClient,
633
+ selector: string,
634
+ timeoutMs: number,
635
+ signal?: AbortSignal,
636
+ opts: { state?: "attached" | "visible" } = {},
637
+ ): Promise<number> {
638
+ const state = opts.state ?? "visible";
639
+ const startedAt = Date.now();
640
+ const escapedSel = JSON.stringify(selector);
641
+ const expression =
642
+ state === "visible"
643
+ ? `(() => {
644
+ const el = document.querySelector(${escapedSel});
645
+ if (!el) return false;
646
+ const r = el.getBoundingClientRect();
647
+ const cs = getComputedStyle(el);
648
+ return r.width > 0 && r.height > 0 && cs.display !== "none" && cs.visibility !== "hidden";
649
+ })()`
650
+ : `document.querySelector(${escapedSel}) !== null`;
651
+ while (Date.now() - startedAt < timeoutMs) {
652
+ if (signal?.aborted) {
653
+ throw new CdpError("aborted", "waitForSelector aborted");
654
+ }
655
+ const ready = await evaluateExpression<boolean>(
656
+ cdp,
657
+ expression,
658
+ {},
659
+ signal,
660
+ );
661
+ if (ready) {
662
+ return await querySelectorBackendNodeId(cdp, selector, signal);
663
+ }
664
+ await new Promise((r) => setTimeout(r, 100));
665
+ }
666
+ throw new CdpError("cdp_error", `Timed out waiting for ${selector}`);
667
+ }
668
+
669
+ /**
670
+ * Poll `document.body.innerText` for a substring. Throws
671
+ * {@link CdpError} on timeout or abort.
672
+ */
673
+ export async function waitForText(
674
+ cdp: CdpClient,
675
+ text: string,
676
+ timeoutMs: number,
677
+ signal?: AbortSignal,
678
+ ): Promise<void> {
679
+ const escaped = JSON.stringify(text);
680
+ const startedAt = Date.now();
681
+ while (Date.now() - startedAt < timeoutMs) {
682
+ if (signal?.aborted) {
683
+ throw new CdpError("aborted", "waitForText aborted");
684
+ }
685
+ const found = await evaluateExpression<boolean>(
686
+ cdp,
687
+ `(document.body?.innerText ?? "").includes(${escaped})`,
688
+ {},
689
+ signal,
690
+ );
691
+ if (found) return;
692
+ await new Promise((r) => setTimeout(r, 100));
693
+ }
694
+ throw new CdpError("cdp_error", `Timed out waiting for text: ${text}`);
695
+ }