@vellumai/assistant 0.6.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (396) hide show
  1. package/bun.lock +40 -40
  2. package/bunfig.toml +3 -0
  3. package/docs/architecture/memory.md +1 -1
  4. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
  5. package/openapi.yaml +184 -69
  6. package/package.json +41 -41
  7. package/scripts/generate-openapi.ts +1 -2
  8. package/src/__tests__/acp-session.test.ts +43 -0
  9. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
  10. package/src/__tests__/app-executors.test.ts +1 -0
  11. package/src/__tests__/app-source-watcher.test.ts +37 -11
  12. package/src/__tests__/approval-routes-http.test.ts +178 -1
  13. package/src/__tests__/browser-fill-credential.test.ts +229 -94
  14. package/src/__tests__/browser-manager.test.ts +40 -27
  15. package/src/__tests__/catalog-files.test.ts +862 -0
  16. package/src/__tests__/channel-approvals.test.ts +53 -0
  17. package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
  18. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  19. package/src/__tests__/config-schema.test.ts +125 -48
  20. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
  21. package/src/__tests__/context-overflow-approval.test.ts +16 -1
  22. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  23. package/src/__tests__/conversation-agent-loop.test.ts +1 -1
  24. package/src/__tests__/conversation-analysis-routes.test.ts +2 -2
  25. package/src/__tests__/conversation-attachments.test.ts +80 -4
  26. package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
  27. package/src/__tests__/conversation-fork-crud.test.ts +17 -0
  28. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  29. package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
  30. package/src/__tests__/conversation-inject-context.test.ts +103 -0
  31. package/src/__tests__/conversation-queue.test.ts +45 -2
  32. package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
  33. package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
  34. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  35. package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
  36. package/src/__tests__/conversation-starter-routes.test.ts +126 -0
  37. package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
  38. package/src/__tests__/conversation-store.test.ts +195 -0
  39. package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
  40. package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
  41. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  42. package/src/__tests__/credential-vault-unit.test.ts +4 -4
  43. package/src/__tests__/credential-vault.test.ts +152 -13
  44. package/src/__tests__/credentials-cli.test.ts +2 -2
  45. package/src/__tests__/date-context.test.ts +4 -4
  46. package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
  47. package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
  48. package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
  49. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  50. package/src/__tests__/gemini-provider.test.ts +2 -2
  51. package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
  52. package/src/__tests__/headless-browser-interactions.test.ts +707 -371
  53. package/src/__tests__/headless-browser-navigate.test.ts +389 -47
  54. package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
  55. package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
  56. package/src/__tests__/host-bash-proxy.test.ts +150 -1
  57. package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
  58. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
  59. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
  60. package/src/__tests__/host-browser-event-routes.test.ts +350 -0
  61. package/src/__tests__/host-browser-proxy.test.ts +444 -0
  62. package/src/__tests__/host-browser-routes.test.ts +198 -0
  63. package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
  64. package/src/__tests__/host-cu-proxy.test.ts +171 -1
  65. package/src/__tests__/host-file-proxy.test.ts +185 -1
  66. package/src/__tests__/host-file-read-tool.test.ts +52 -0
  67. package/src/__tests__/host-proxy-interface.test.ts +165 -0
  68. package/src/__tests__/host-shell-tool.test.ts +1 -11
  69. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  70. package/src/__tests__/integration-status.test.ts +6 -7
  71. package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
  72. package/src/__tests__/mcp-client-auth.test.ts +40 -4
  73. package/src/__tests__/mcp-health-check.test.ts +10 -3
  74. package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
  75. package/src/__tests__/migration-export-http.test.ts +61 -2
  76. package/src/__tests__/migration-export-streaming.test.ts +66 -0
  77. package/src/__tests__/migration-import-commit-http.test.ts +101 -1
  78. package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
  79. package/src/__tests__/oauth-apps-routes.test.ts +17 -12
  80. package/src/__tests__/oauth-cli.test.ts +707 -60
  81. package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
  82. package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
  83. package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
  84. package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
  85. package/src/__tests__/oauth-providers-routes.test.ts +50 -14
  86. package/src/__tests__/oauth-store.test.ts +1386 -182
  87. package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
  88. package/src/__tests__/onboarding-template-contract.test.ts +75 -57
  89. package/src/__tests__/openai-provider.test.ts +2 -2
  90. package/src/__tests__/outlook-categories.test.ts +1 -1
  91. package/src/__tests__/outlook-client-automation.test.ts +1 -1
  92. package/src/__tests__/outlook-compose-tools.test.ts +1 -1
  93. package/src/__tests__/outlook-email-watcher.test.ts +1 -1
  94. package/src/__tests__/outlook-follow-up.test.ts +1 -1
  95. package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
  96. package/src/__tests__/outlook-trash.test.ts +1 -1
  97. package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
  98. package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
  99. package/src/__tests__/permission-mode.test.ts +28 -56
  100. package/src/__tests__/platform-callback-registration.test.ts +19 -0
  101. package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
  102. package/src/__tests__/proxy-approval-callback.test.ts +18 -0
  103. package/src/__tests__/require-fresh-approval.test.ts +40 -1
  104. package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
  105. package/src/__tests__/schedule-routes.test.ts +162 -0
  106. package/src/__tests__/secret-detection-handler.test.ts +84 -0
  107. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  108. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  109. package/src/__tests__/set-permission-mode.test.ts +13 -250
  110. package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
  111. package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
  112. package/src/__tests__/slack-channel-config.test.ts +12 -15
  113. package/src/__tests__/subagent-detail.test.ts +44 -2
  114. package/src/__tests__/subagent-disposal.test.ts +1 -0
  115. package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
  116. package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
  117. package/src/__tests__/subagent-manager-notify.test.ts +1 -0
  118. package/src/__tests__/subagent-notify-parent.test.ts +1 -0
  119. package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
  120. package/src/__tests__/subagent-tools.test.ts +1 -0
  121. package/src/__tests__/subagent-types.test.ts +1 -0
  122. package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
  123. package/src/__tests__/system-prompt.test.ts +72 -1
  124. package/src/__tests__/task-scheduler.test.ts +32 -6
  125. package/src/__tests__/telegram-config.test.ts +10 -13
  126. package/src/__tests__/terminal-tools.test.ts +9 -0
  127. package/src/__tests__/tool-approval-handler.test.ts +73 -0
  128. package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
  129. package/src/__tests__/top-level-renderer.test.ts +73 -1
  130. package/src/__tests__/transport-hints-queue.test.ts +14 -29
  131. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
  132. package/src/__tests__/v2-consent-policy.test.ts +103 -0
  133. package/src/acp/client-handler.ts +30 -4
  134. package/src/agent/loop.ts +12 -6
  135. package/src/approvals/guardian-request-resolvers.ts +21 -15
  136. package/src/browser-session/__tests__/manager.test.ts +297 -0
  137. package/src/browser-session/backends/cdp-inspect.ts +30 -0
  138. package/src/browser-session/backends/extension.ts +26 -0
  139. package/src/browser-session/backends/local.ts +24 -0
  140. package/src/browser-session/events.ts +164 -0
  141. package/src/browser-session/index.ts +27 -0
  142. package/src/browser-session/manager.ts +159 -0
  143. package/src/browser-session/types.ts +28 -0
  144. package/src/channels/__tests__/types.test.ts +134 -0
  145. package/src/channels/types.ts +53 -3
  146. package/src/cli/commands/browser-relay.ts +339 -409
  147. package/src/cli/commands/credentials.ts +3 -3
  148. package/src/cli/commands/email.ts +18 -13
  149. package/src/cli/commands/mcp.ts +16 -4
  150. package/src/cli/commands/oauth/__tests__/connect.test.ts +44 -44
  151. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
  152. package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
  153. package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
  154. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
  155. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
  156. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
  157. package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
  158. package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
  159. package/src/cli/commands/oauth/apps.ts +7 -4
  160. package/src/cli/commands/oauth/connect.ts +6 -3
  161. package/src/cli/commands/oauth/disconnect.ts +1 -1
  162. package/src/cli/commands/oauth/providers.ts +200 -36
  163. package/src/cli/commands/oauth/shared.ts +5 -5
  164. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
  165. package/src/cli/commands/platform/index.ts +107 -10
  166. package/src/cli/commands/usage.ts +10 -9
  167. package/src/cli/lib/daemon-credential-client.ts +4 -0
  168. package/src/cli/program.ts +1 -1
  169. package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
  170. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
  171. package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
  172. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
  173. package/src/config/bundled-skills/contacts/SKILL.md +3 -0
  174. package/src/config/bundled-skills/document/SKILL.md +4 -0
  175. package/src/config/bundled-skills/gmail/SKILL.md +1 -1
  176. package/src/config/bundled-skills/outlook/SKILL.md +7 -0
  177. package/src/config/bundled-skills/subagent/SKILL.md +21 -0
  178. package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
  179. package/src/config/bundled-skills/tasks/SKILL.md +5 -0
  180. package/src/config/env-registry.ts +14 -0
  181. package/src/config/env.ts +21 -0
  182. package/src/config/feature-flag-registry.json +44 -5
  183. package/src/config/loader.ts +56 -1
  184. package/src/config/sanitize-for-transfer.ts +47 -0
  185. package/src/config/schema.ts +46 -5
  186. package/src/config/schemas/host-browser.ts +66 -0
  187. package/src/config/schemas/memory-lifecycle.ts +1 -1
  188. package/src/config/schemas/memory-retrieval.ts +103 -0
  189. package/src/config/schemas/security.ts +0 -6
  190. package/src/config/schemas/services.ts +8 -0
  191. package/src/config/types.ts +0 -1
  192. package/src/context/post-turn-tool-result-truncation.ts +176 -0
  193. package/src/context/window-manager.ts +19 -1
  194. package/src/credential-execution/approval-bridge.ts +49 -15
  195. package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
  196. package/src/daemon/app-source-watcher.ts +35 -0
  197. package/src/daemon/context-overflow-approval.ts +5 -0
  198. package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
  199. package/src/daemon/conversation-agent-loop.ts +58 -24
  200. package/src/daemon/conversation-attachments.ts +40 -0
  201. package/src/daemon/conversation-process.ts +48 -1
  202. package/src/daemon/conversation-runtime-assembly.ts +118 -36
  203. package/src/daemon/conversation-surfaces.ts +37 -36
  204. package/src/daemon/conversation-tool-setup.ts +74 -8
  205. package/src/daemon/conversation-workspace.ts +12 -0
  206. package/src/daemon/conversation.ts +226 -8
  207. package/src/daemon/date-context.ts +10 -10
  208. package/src/daemon/first-greeting.ts +3 -2
  209. package/src/daemon/handlers/conversations.ts +9 -140
  210. package/src/daemon/handlers/shared.ts +58 -0
  211. package/src/daemon/handlers/skills.ts +232 -37
  212. package/src/daemon/host-bash-proxy.ts +48 -13
  213. package/src/daemon/host-browser-proxy.ts +191 -0
  214. package/src/daemon/host-cu-proxy.ts +36 -11
  215. package/src/daemon/host-file-proxy.ts +57 -9
  216. package/src/daemon/lifecycle.ts +65 -11
  217. package/src/daemon/message-protocol.ts +7 -0
  218. package/src/daemon/message-types/conversations.ts +55 -13
  219. package/src/daemon/message-types/host-browser.ts +100 -0
  220. package/src/daemon/message-types/messages.ts +5 -5
  221. package/src/daemon/message-types/skills.ts +10 -0
  222. package/src/daemon/message-types/subagents.ts +2 -0
  223. package/src/daemon/server.ts +92 -12
  224. package/src/daemon/tool-side-effects.ts +6 -0
  225. package/src/daemon/transport-hints.ts +5 -24
  226. package/src/inbound/platform-callback-registration.ts +18 -17
  227. package/src/mcp/client.ts +59 -24
  228. package/src/memory/app-store.ts +31 -1
  229. package/src/memory/conversation-crud.ts +23 -0
  230. package/src/memory/conversation-starters-cadence.ts +76 -0
  231. package/src/memory/conversation-title-service.ts +5 -2
  232. package/src/memory/db-init.ts +12 -0
  233. package/src/memory/embedding-backend.test.ts +75 -0
  234. package/src/memory/embedding-backend.ts +131 -5
  235. package/src/memory/embedding-gemini.test.ts +54 -0
  236. package/src/memory/embedding-gemini.ts +20 -9
  237. package/src/memory/embedding-local.ts +176 -17
  238. package/src/memory/graph/consolidation.ts +10 -23
  239. package/src/memory/graph/extraction-job.ts +15 -0
  240. package/src/memory/graph/retriever.ts +40 -22
  241. package/src/memory/graph/store.test.ts +7 -3
  242. package/src/memory/graph/store.ts +47 -12
  243. package/src/memory/llm-usage-store.ts +45 -4
  244. package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
  245. package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
  246. package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
  247. package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
  248. package/src/memory/migrations/217-conversation-host-access.ts +40 -0
  249. package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
  250. package/src/memory/migrations/index.ts +6 -0
  251. package/src/memory/migrations/registry.ts +8 -0
  252. package/src/memory/schema/conversations.ts +1 -0
  253. package/src/memory/schema/oauth.ts +18 -13
  254. package/src/oauth/AGENTS.md +76 -0
  255. package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
  256. package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
  257. package/src/oauth/byo-connection.test.ts +8 -8
  258. package/src/oauth/byo-connection.ts +7 -7
  259. package/src/oauth/connect-orchestrator.ts +23 -21
  260. package/src/oauth/connect-types.ts +3 -3
  261. package/src/oauth/connection-resolver.test.ts +17 -4
  262. package/src/oauth/connection-resolver.ts +16 -16
  263. package/src/oauth/connection.ts +1 -1
  264. package/src/oauth/manual-token-connection.ts +13 -13
  265. package/src/oauth/oauth-store.ts +214 -100
  266. package/src/oauth/platform-connection.test.ts +3 -3
  267. package/src/oauth/platform-connection.ts +4 -4
  268. package/src/oauth/provider-serializer.ts +31 -5
  269. package/src/oauth/revoke.ts +76 -0
  270. package/src/oauth/seed-providers.ts +126 -87
  271. package/src/oauth/token-persistence.ts +1 -1
  272. package/src/permissions/permission-mode.ts +4 -11
  273. package/src/permissions/prompter.ts +13 -1
  274. package/src/permissions/v2-consent-policy.ts +87 -0
  275. package/src/prompts/system-prompt.ts +18 -21
  276. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
  277. package/src/prompts/templates/BOOTSTRAP.md +59 -105
  278. package/src/providers/anthropic/client.ts +1 -0
  279. package/src/providers/types.ts +1 -1
  280. package/src/runtime/AGENTS.md +23 -0
  281. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
  282. package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
  283. package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
  284. package/src/runtime/assistant-event-hub.ts +2 -2
  285. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
  286. package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
  287. package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
  288. package/src/runtime/auth/middleware.ts +98 -0
  289. package/src/runtime/auth/route-policy.ts +6 -7
  290. package/src/runtime/capability-tokens.ts +414 -0
  291. package/src/runtime/channel-approvals.ts +18 -5
  292. package/src/runtime/chrome-extension-registry.ts +332 -0
  293. package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
  294. package/src/runtime/guardian-decision-types.ts +7 -0
  295. package/src/runtime/http-server.ts +425 -70
  296. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
  297. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
  298. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
  299. package/src/runtime/migrations/migration-transport.ts +6 -0
  300. package/src/runtime/migrations/migration-wizard.ts +22 -2
  301. package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
  302. package/src/runtime/migrations/vbundle-builder.ts +145 -38
  303. package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
  304. package/src/runtime/migrations/vbundle-importer.ts +55 -5
  305. package/src/runtime/pending-interactions.ts +29 -13
  306. package/src/runtime/routes/approval-routes.ts +90 -16
  307. package/src/runtime/routes/browser-cdp-routes.ts +229 -0
  308. package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
  309. package/src/runtime/routes/conversation-analysis-routes.ts +2 -1
  310. package/src/runtime/routes/conversation-management-routes.ts +108 -0
  311. package/src/runtime/routes/conversation-routes.ts +301 -27
  312. package/src/runtime/routes/conversation-starter-routes.ts +78 -16
  313. package/src/runtime/routes/guardian-action-routes.ts +24 -13
  314. package/src/runtime/routes/host-browser-routes.ts +279 -0
  315. package/src/runtime/routes/host-file-routes.ts +9 -1
  316. package/src/runtime/routes/identity-routes.ts +259 -16
  317. package/src/runtime/routes/log-export-routes.ts +42 -22
  318. package/src/runtime/routes/memory-item-routes.ts +1 -7
  319. package/src/runtime/routes/migration-routes.ts +87 -2
  320. package/src/runtime/routes/oauth-apps.ts +15 -17
  321. package/src/runtime/routes/oauth-providers.ts +4 -0
  322. package/src/runtime/routes/schedule-routes.ts +24 -11
  323. package/src/runtime/routes/settings-routes.ts +9 -97
  324. package/src/runtime/routes/skills-routes.ts +52 -2
  325. package/src/runtime/routes/subagents-routes.ts +14 -10
  326. package/src/runtime/routes/usage-routes.ts +8 -7
  327. package/src/runtime/routes/workspace-routes.test.ts +22 -0
  328. package/src/runtime/routes/workspace-routes.ts +8 -1
  329. package/src/runtime/routes/workspace-utils.ts +2 -0
  330. package/src/schedule/scheduler.ts +7 -5
  331. package/src/security/ces-credential-client.ts +20 -0
  332. package/src/security/ces-rpc-credential-backend.ts +17 -0
  333. package/src/security/credential-backend.ts +5 -0
  334. package/src/security/oauth2.ts +42 -25
  335. package/src/security/secure-keys.ts +118 -25
  336. package/src/security/token-manager.ts +23 -10
  337. package/src/skills/catalog-files.ts +492 -0
  338. package/src/subagent/manager.ts +131 -26
  339. package/src/subagent/types.ts +19 -0
  340. package/src/tools/apps/executors.ts +11 -2
  341. package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
  342. package/src/tools/browser/auth-detector.ts +43 -12
  343. package/src/tools/browser/browser-execution.ts +645 -340
  344. package/src/tools/browser/browser-manager.ts +36 -12
  345. package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
  346. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
  347. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
  348. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
  349. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
  350. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
  351. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
  352. package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
  353. package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
  354. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
  355. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
  356. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
  357. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
  358. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
  359. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
  360. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
  361. package/src/tools/browser/cdp-client/errors.ts +34 -0
  362. package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
  363. package/src/tools/browser/cdp-client/factory.ts +204 -0
  364. package/src/tools/browser/cdp-client/index.ts +14 -0
  365. package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
  366. package/src/tools/browser/cdp-client/types.ts +52 -0
  367. package/src/tools/filesystem/edit.ts +1 -1
  368. package/src/tools/filesystem/list.ts +1 -1
  369. package/src/tools/filesystem/read.ts +1 -1
  370. package/src/tools/filesystem/write.ts +2 -1
  371. package/src/tools/host-filesystem/edit.ts +1 -1
  372. package/src/tools/host-filesystem/read.ts +12 -15
  373. package/src/tools/host-filesystem/write.ts +1 -1
  374. package/src/tools/host-terminal/host-shell.ts +21 -16
  375. package/src/tools/permission-checker.ts +77 -82
  376. package/src/tools/registry.ts +0 -2
  377. package/src/tools/secret-detection-handler.ts +34 -0
  378. package/src/tools/shared/filesystem/image-read.ts +61 -40
  379. package/src/tools/subagent/spawn.ts +47 -3
  380. package/src/tools/subagent/status.ts +2 -0
  381. package/src/tools/system/register.ts +2 -16
  382. package/src/tools/terminal/safe-env.ts +7 -0
  383. package/src/tools/terminal/shell.ts +21 -16
  384. package/src/tools/tool-approval-handler.ts +48 -2
  385. package/src/tools/types.ts +2 -0
  386. package/src/util/platform.ts +14 -19
  387. package/src/workspace/top-level-renderer.ts +19 -1
  388. package/src/__tests__/chrome-cdp.test.ts +0 -419
  389. package/src/__tests__/permission-mode-sse.test.ts +0 -418
  390. package/src/__tests__/permission-mode-store.test.ts +0 -277
  391. package/src/browser-extension-relay/protocol.ts +0 -63
  392. package/src/browser-extension-relay/server.ts +0 -203
  393. package/src/config/schemas/sandbox.ts +0 -14
  394. package/src/permissions/permission-mode-store.ts +0 -180
  395. package/src/tools/browser/chrome-cdp.ts +0 -239
  396. package/src/tools/system/set-permission-mode.ts +0 -103
@@ -1,84 +1,160 @@
1
+ /**
2
+ * `assistant browser chrome relay <action>` CLI shim.
3
+ *
4
+ * Translates the legacy relay actions (find_tab, new_tab, navigate,
5
+ * evaluate, get_cookies, set_cookie, screenshot) into Chrome DevTools
6
+ * Protocol commands and forwards them to the daemon's
7
+ * `/v1/browser-cdp` HTTP endpoint, which routes the command through
8
+ * the connected chrome-extension WebSocket.
9
+ *
10
+ * Why this exists: PR #24329 deleted the in-process extension relay
11
+ * server and the original CLI surface. Two in-tree skills (amazon and
12
+ * influencer) still spawn `assistant browser chrome relay <action>` as
13
+ * a subprocess and parse the JSON output. Until those skills migrate
14
+ * onto the new CDP-based skill API, this shim keeps them working by
15
+ * preserving the legacy stdout contract:
16
+ *
17
+ * { "ok": true, "tabId"?: <id>, "result"?: <unknown> }
18
+ * { "ok": false, "error": <string> }
19
+ *
20
+ * The CLI mints a short-lived daemon delivery JWT (same audience and
21
+ * scope profile as the daemon's internal callbacks) and POSTs directly
22
+ * to the runtime's loopback HTTP port — no gateway involvement
23
+ * required.
24
+ */
25
+
26
+ import { existsSync, readFileSync } from "node:fs";
27
+
1
28
  import type { Command } from "commander";
2
29
 
3
- import type { ExtensionCommand } from "../../browser-extension-relay/protocol.js";
4
- import { extensionRelayServer } from "../../browser-extension-relay/server.js";
30
+ import { getRuntimeHttpPort } from "../../config/env.js";
31
+ import { CURRENT_POLICY_EPOCH } from "../../runtime/auth/policy.js";
32
+ import { mintToken } from "../../runtime/auth/token-service.js";
5
33
  import {
6
34
  initAuthSigningKey,
7
35
  isSigningKeyInitialized,
8
36
  loadOrCreateSigningKey,
9
37
  } from "../../runtime/auth/token-service.js";
10
- import {
11
- gatewayGet,
12
- gatewayPost,
13
- } from "../../runtime/gateway-internal-client.js";
14
- import {
15
- ensureChromeWithCdp,
16
- minimizeChromeWindow,
17
- restoreChromeWindow,
18
- } from "../../tools/browser/chrome-cdp.js";
38
+ import { getRuntimePortFilePath } from "../../util/platform.js";
19
39
 
20
40
  // ---------------------------------------------------------------------------
21
- // Shared relay helper — in-process when connected, gateway HTTP otherwise
41
+ // Daemon HTTP client
22
42
  // ---------------------------------------------------------------------------
23
43
 
24
- async function relayCommand(command: Record<string, unknown>): Promise<void> {
44
+ interface BrowserCdpResponse {
45
+ result?: unknown;
46
+ error?: { code: string; message: string };
47
+ }
48
+
49
+ /**
50
+ * Resolve the daemon's runtime HTTP port. Prefers the runtime-port
51
+ * file written by the daemon at startup so non-default ports
52
+ * (RUNTIME_HTTP_PORT) are picked up automatically without an env var
53
+ * roundtrip. Falls back to the env-var-derived default.
54
+ */
55
+ function resolveRuntimePort(): number {
25
56
  try {
26
- // Dual-path: use in-process relay when connected (daemon context),
27
- // otherwise fall back to gateway HTTP (out-of-process CLI context).
28
- // We check connection status upfront rather than try/catch to avoid
29
- // double-execution of side-effectful commands (navigate, new_tab, etc.)
30
- // when sendCommand fails after the command was already dispatched.
31
- let data: {
32
- id?: string;
33
- success: boolean;
34
- result?: unknown;
35
- error?: string;
36
- tabId?: number;
37
- };
38
-
39
- if (extensionRelayServer.getStatus().connected) {
40
- data = await extensionRelayServer.sendCommand(
41
- command as Omit<ExtensionCommand, "id">,
42
- );
43
- } else {
44
- // In-process relay not connected — fall back to gateway HTTP
45
- if (!isSigningKeyInitialized()) {
46
- initAuthSigningKey(loadOrCreateSigningKey());
57
+ const portFile = getRuntimePortFilePath();
58
+ if (existsSync(portFile)) {
59
+ const raw = readFileSync(portFile, "utf-8").trim();
60
+ const parsed = Number(raw);
61
+ if (Number.isFinite(parsed) && parsed > 0 && parsed < 65536) {
62
+ return parsed;
47
63
  }
48
- ({ data } = await gatewayPost<typeof data>(
49
- "/v1/browser-relay/command",
50
- command,
51
- ));
52
64
  }
65
+ } catch {
66
+ // Fall through to env-var default
67
+ }
68
+ return getRuntimeHttpPort();
69
+ }
53
70
 
54
- if (data.success) {
55
- process.stdout.write(
56
- JSON.stringify({
57
- ok: true,
58
- ...(data.tabId !== undefined ? { tabId: data.tabId } : {}),
59
- ...(data.result !== undefined ? { result: data.result } : {}),
60
- }) + "\n",
61
- );
62
- } else {
63
- process.stdout.write(
64
- JSON.stringify({
65
- ok: false,
66
- error: data.error ?? "Unknown relay error",
67
- }) + "\n",
68
- );
69
- process.exitCode = 1;
70
- }
71
- } catch (err) {
72
- const message = err instanceof Error ? err.message : String(err);
73
- process.stdout.write(JSON.stringify({ ok: false, error: message }) + "\n");
74
- process.exitCode = 1;
71
+ /**
72
+ * Mint a short-lived JWT acceptable to the runtime auth middleware.
73
+ * Mirrors `mintDaemonDeliveryToken` (sub=svc:daemon:self,
74
+ * scope_profile=gateway_service_v1, aud=vellum-daemon) but is minted
75
+ * out-of-process by the CLI using the on-disk signing key.
76
+ */
77
+ function mintCliToken(): string {
78
+ if (!isSigningKeyInitialized()) {
79
+ initAuthSigningKey(loadOrCreateSigningKey());
75
80
  }
81
+ return mintToken({
82
+ aud: "vellum-daemon",
83
+ sub: "svc:daemon:self",
84
+ scope_profile: "gateway_service_v1",
85
+ policy_epoch: CURRENT_POLICY_EPOCH,
86
+ ttlSeconds: 60,
87
+ });
88
+ }
89
+
90
+ /**
91
+ * Send a single CDP command to the daemon's /v1/browser-cdp route and
92
+ * return the parsed response. Throws on transport-level errors; the
93
+ * caller wraps the throw into a `{ ok: false, error }` envelope.
94
+ */
95
+ async function postBrowserCdp(payload: {
96
+ cdpMethod: string;
97
+ cdpParams?: Record<string, unknown>;
98
+ cdpSessionId?: string;
99
+ timeoutMs?: number;
100
+ }): Promise<BrowserCdpResponse> {
101
+ const port = resolveRuntimePort();
102
+ const token = mintCliToken();
103
+ const url = `http://127.0.0.1:${port}/v1/browser-cdp`;
104
+
105
+ const resp = await fetch(url, {
106
+ method: "POST",
107
+ headers: {
108
+ "Content-Type": "application/json",
109
+ Authorization: `Bearer ${token}`,
110
+ },
111
+ body: JSON.stringify(payload),
112
+ });
113
+
114
+ const bodyText = await resp.text();
115
+ let parsed: BrowserCdpResponse;
116
+ try {
117
+ parsed = JSON.parse(bodyText) as BrowserCdpResponse;
118
+ } catch {
119
+ throw new Error(
120
+ `Daemon returned non-JSON response (HTTP ${resp.status}): ${bodyText.slice(0, 200)}`,
121
+ );
122
+ }
123
+
124
+ if (!resp.ok) {
125
+ const message = parsed.error?.message ?? `HTTP ${resp.status}`;
126
+ throw new Error(message);
127
+ }
128
+
129
+ return parsed;
76
130
  }
77
131
 
78
132
  // ---------------------------------------------------------------------------
79
- // Stdin reader helper
133
+ // Stdout helpers
80
134
  // ---------------------------------------------------------------------------
81
135
 
136
+ interface RelayResultOk {
137
+ ok: true;
138
+ tabId?: number | string;
139
+ result?: unknown;
140
+ }
141
+
142
+ interface RelayResultErr {
143
+ ok: false;
144
+ error: string;
145
+ }
146
+
147
+ function emitOk(payload: Omit<RelayResultOk, "ok">): void {
148
+ const out: RelayResultOk = { ok: true, ...payload };
149
+ process.stdout.write(JSON.stringify(out) + "\n");
150
+ }
151
+
152
+ function emitError(message: string): void {
153
+ const out: RelayResultErr = { ok: false, error: message };
154
+ process.stdout.write(JSON.stringify(out) + "\n");
155
+ process.exitCode = 1;
156
+ }
157
+
82
158
  async function readStdin(): Promise<string> {
83
159
  const chunks: Buffer[] = [];
84
160
  for await (const chunk of process.stdin) {
@@ -87,6 +163,176 @@ async function readStdin(): Promise<string> {
87
163
  return Buffer.concat(chunks).toString("utf-8");
88
164
  }
89
165
 
166
+ // ---------------------------------------------------------------------------
167
+ // URL glob matching for find-tab
168
+ // ---------------------------------------------------------------------------
169
+
170
+ /**
171
+ * Convert a Chrome match-pattern style glob (e.g. `*://*.amazon.com/*`)
172
+ * into a regular expression. Matches the chrome.tabs.query semantics
173
+ * the legacy relay CLI exposed:
174
+ *
175
+ * - `*` is a wildcard that matches any sequence (including `/` in
176
+ * the path component, mirroring the legacy minimatch behaviour).
177
+ * - All other regex metacharacters are escaped.
178
+ */
179
+ function globToRegex(glob: string): RegExp {
180
+ const escaped = glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
181
+ const pattern = "^" + escaped.replace(/\*/g, ".*") + "$";
182
+ return new RegExp(pattern);
183
+ }
184
+
185
+ // ---------------------------------------------------------------------------
186
+ // Action handlers — translate legacy actions into CDP commands
187
+ // ---------------------------------------------------------------------------
188
+
189
+ interface CdpTarget {
190
+ targetId: string;
191
+ type: string;
192
+ url: string;
193
+ title?: string;
194
+ attached?: boolean;
195
+ }
196
+
197
+ interface CdpTargetsResult {
198
+ targetInfos: CdpTarget[];
199
+ }
200
+
201
+ async function actionFindTab(urlPattern: string): Promise<void> {
202
+ try {
203
+ const resp = await postBrowserCdp({ cdpMethod: "Target.getTargets" });
204
+ const targets =
205
+ (resp.result as CdpTargetsResult | undefined)?.targetInfos ?? [];
206
+ const re = globToRegex(urlPattern);
207
+ const match = targets.find((t) => t.type === "page" && re.test(t.url));
208
+ if (!match) {
209
+ emitError(`No tab matched URL pattern: ${urlPattern}`);
210
+ return;
211
+ }
212
+ emitOk({ tabId: match.targetId });
213
+ } catch (err) {
214
+ emitError(err instanceof Error ? err.message : String(err));
215
+ }
216
+ }
217
+
218
+ async function actionNewTab(url: string): Promise<void> {
219
+ try {
220
+ const resp = await postBrowserCdp({
221
+ cdpMethod: "Target.createTarget",
222
+ cdpParams: { url },
223
+ });
224
+ const targetId = (resp.result as { targetId?: string } | undefined)
225
+ ?.targetId;
226
+ if (!targetId) {
227
+ emitError("Target.createTarget did not return a targetId");
228
+ return;
229
+ }
230
+ emitOk({ tabId: targetId });
231
+ } catch (err) {
232
+ emitError(err instanceof Error ? err.message : String(err));
233
+ }
234
+ }
235
+
236
+ async function actionNavigate(tabId: string, url: string): Promise<void> {
237
+ try {
238
+ await postBrowserCdp({
239
+ cdpMethod: "Page.navigate",
240
+ cdpParams: { url },
241
+ cdpSessionId: tabId,
242
+ });
243
+ emitOk({});
244
+ } catch (err) {
245
+ emitError(err instanceof Error ? err.message : String(err));
246
+ }
247
+ }
248
+
249
+ async function actionEvaluate(tabId: string, code: string): Promise<void> {
250
+ try {
251
+ const resp = await postBrowserCdp({
252
+ cdpMethod: "Runtime.evaluate",
253
+ cdpParams: {
254
+ expression: code,
255
+ returnByValue: true,
256
+ awaitPromise: true,
257
+ },
258
+ cdpSessionId: tabId,
259
+ });
260
+ // CDP Runtime.evaluate returns { result: { type, value }, exceptionDetails? }.
261
+ // Surface exceptions as relay errors so callers don't silently get undefined.
262
+ const result = resp.result as
263
+ | {
264
+ result?: { value?: unknown };
265
+ exceptionDetails?: {
266
+ text?: string;
267
+ exception?: { description?: string };
268
+ };
269
+ }
270
+ | undefined;
271
+ if (result?.exceptionDetails) {
272
+ const desc =
273
+ result.exceptionDetails.exception?.description ??
274
+ result.exceptionDetails.text ??
275
+ "Runtime exception during evaluate";
276
+ emitError(desc);
277
+ return;
278
+ }
279
+ emitOk({ result: result?.result?.value });
280
+ } catch (err) {
281
+ emitError(err instanceof Error ? err.message : String(err));
282
+ }
283
+ }
284
+
285
+ async function actionGetCookies(domain: string): Promise<void> {
286
+ try {
287
+ const resp = await postBrowserCdp({ cdpMethod: "Network.getCookies" });
288
+ const cookies =
289
+ (resp.result as { cookies?: Array<Record<string, unknown>> } | undefined)
290
+ ?.cookies ?? [];
291
+ // Filter by domain (Chrome stores cookies with leading-dot or bare-host
292
+ // domains depending on the Set-Cookie source). Match either form so the
293
+ // legacy "amazon.com" / ".amazon.com" callers both succeed.
294
+ const trimmed = domain.startsWith(".") ? domain.slice(1) : domain;
295
+ const filtered = cookies.filter((c) => {
296
+ const d = String(c.domain ?? "");
297
+ const dTrim = d.startsWith(".") ? d.slice(1) : d;
298
+ return dTrim === trimmed || dTrim.endsWith("." + trimmed);
299
+ });
300
+ emitOk({ result: filtered });
301
+ } catch (err) {
302
+ emitError(err instanceof Error ? err.message : String(err));
303
+ }
304
+ }
305
+
306
+ async function actionSetCookie(cookie: Record<string, unknown>): Promise<void> {
307
+ try {
308
+ await postBrowserCdp({
309
+ cdpMethod: "Network.setCookie",
310
+ cdpParams: cookie,
311
+ });
312
+ emitOk({});
313
+ } catch (err) {
314
+ emitError(err instanceof Error ? err.message : String(err));
315
+ }
316
+ }
317
+
318
+ async function actionScreenshot(tabId?: string): Promise<void> {
319
+ try {
320
+ const resp = await postBrowserCdp({
321
+ cdpMethod: "Page.captureScreenshot",
322
+ cdpParams: { format: "png" },
323
+ ...(tabId !== undefined ? { cdpSessionId: tabId } : {}),
324
+ });
325
+ const data = (resp.result as { data?: string } | undefined)?.data;
326
+ if (data === undefined) {
327
+ emitError("Page.captureScreenshot returned no data");
328
+ return;
329
+ }
330
+ emitOk({ result: data });
331
+ } catch (err) {
332
+ emitError(err instanceof Error ? err.message : String(err));
333
+ }
334
+ }
335
+
90
336
  // ---------------------------------------------------------------------------
91
337
  // Command registration
92
338
  // ---------------------------------------------------------------------------
@@ -95,65 +341,35 @@ export function registerBrowserRelayCommand(program: Command): void {
95
341
  const browser = program
96
342
  .command("browser")
97
343
  .description(
98
- "Browser automation, extension relay, and Chrome CDP management",
344
+ "Browser automation surface (`chrome relay <action>` CDP shim)",
99
345
  );
100
346
 
101
347
  browser.addHelpText(
102
348
  "after",
103
349
  `
104
- Browser automation commands. Use a browser-specific subcommand to interact
105
- with browser tabs and extensions.
350
+ Provides a thin CDP-over-HTTP shim used by in-tree skills that have not
351
+ yet migrated onto the new CDP-based skill API. Each command translates
352
+ the legacy action into a Chrome DevTools Protocol call and forwards it
353
+ to the daemon's /v1/browser-cdp route, which routes through the
354
+ connected chrome-extension WebSocket.
106
355
 
107
356
  Examples:
108
- $ assistant browser chrome relay find-tab --url "*://*.instagram.com/*"
109
- $ assistant browser chrome relay evaluate --tab-id 123 --code "document.title"
110
- $ assistant browser chrome relay screenshot --tab-id 123
111
- $ assistant browser chrome relay status`,
357
+ $ assistant browser chrome relay find-tab --url "*://*.amazon.com/*"
358
+ $ assistant browser chrome relay new-tab --url "https://example.com"
359
+ $ assistant browser chrome relay evaluate --tab-id <id> --code "document.title"
360
+ $ assistant browser chrome relay screenshot --tab-id <id>`,
112
361
  );
113
362
 
114
363
  const chrome = browser
115
364
  .command("chrome")
116
- .description("Chrome browser automation via the extension relay");
117
-
118
- chrome.addHelpText(
119
- "after",
120
- `
121
- Manages a dedicated Chrome instance with Chrome DevTools Protocol (CDP)
122
- enabled, separate from the user's regular Chrome profile. The CDP instance
123
- uses a dedicated user data directory at ~/Library/Application Support/Google/Chrome-CDP
124
- and defaults to port 9222. Commands are routed through a Chrome extension
125
- relay that bridges the assistant to open Chrome tabs.
126
-
127
- Examples:
128
- $ assistant browser chrome launch
129
- $ assistant browser chrome launch --start-url "https://example.com" --port 9333
130
- $ assistant browser chrome minimize
131
- $ assistant browser chrome restore
132
- $ assistant browser chrome relay status
133
- $ assistant browser chrome relay find-tab --url "*://*.github.com/*"`,
134
- );
365
+ .description("Chrome browser automation via the chrome-extension proxy");
135
366
 
136
367
  const relay = chrome
137
368
  .command("relay")
138
369
  .description(
139
- "Send commands to Chrome tabs via the browser extension relay",
370
+ "Send a single CDP command to a Chrome tab via the chrome extension",
140
371
  );
141
372
 
142
- relay.addHelpText(
143
- "after",
144
- `
145
- Routes commands to Chrome tabs through the browser extension relay. The relay
146
- connects the assistant to a Chrome extension that can inspect and control
147
- browser tabs. Commands support URL glob patterns for tab discovery and
148
- JavaScript evaluation with stdin piping for long scripts.
149
-
150
- Examples:
151
- $ assistant browser chrome relay find-tab --url "*://*.amazon.com/*"
152
- $ assistant browser chrome relay new-tab --url "https://example.com"
153
- $ assistant browser chrome relay evaluate --tab-id 42 --code "document.title"
154
- $ echo "document.querySelectorAll('a').length" | assistant browser chrome relay evaluate --tab-id 42`,
155
- );
156
-
157
373
  // -- find-tab --
158
374
 
159
375
  relay
@@ -163,21 +379,8 @@ Examples:
163
379
  "--url <pattern>",
164
380
  "URL glob pattern to match (e.g. *://*.instagram.com/*)",
165
381
  )
166
- .addHelpText(
167
- "after",
168
- `
169
- Arguments:
170
- --url <pattern> Glob pattern matched against open tab URLs. Supports
171
- wildcards: *://*.instagram.com/* matches any Instagram page.
172
-
173
- Returns the tab ID of the first matching tab, or an error if no match is found.
174
-
175
- Examples:
176
- $ assistant browser chrome relay find-tab --url "*://*.amazon.com/*"
177
- $ assistant browser chrome relay find-tab --url "*://mail.google.com/*"`,
178
- )
179
382
  .action(async (opts: { url: string }) => {
180
- await relayCommand({ action: "find_tab", url: opts.url });
383
+ await actionFindTab(opts.url);
181
384
  });
182
385
 
183
386
  // -- new-tab --
@@ -186,20 +389,8 @@ Examples:
186
389
  .command("new-tab")
187
390
  .description("Open a new tab with the given URL")
188
391
  .requiredOption("--url <url>", "URL to open in a new tab")
189
- .addHelpText(
190
- "after",
191
- `
192
- Arguments:
193
- --url <url> The full URL to open in a new Chrome tab.
194
-
195
- Returns the tab ID of the newly created tab.
196
-
197
- Examples:
198
- $ assistant browser chrome relay new-tab --url "https://example.com"
199
- $ assistant browser chrome relay new-tab --url "https://www.instagram.com/explore/"`,
200
- )
201
392
  .action(async (opts: { url: string }) => {
202
- await relayCommand({ action: "new_tab", url: opts.url });
393
+ await actionNewTab(opts.url);
203
394
  });
204
395
 
205
396
  // -- navigate --
@@ -207,24 +398,10 @@ Examples:
207
398
  relay
208
399
  .command("navigate")
209
400
  .description("Navigate an existing tab to a new URL")
210
- .requiredOption("--tab-id <id>", "Target tab ID", parseInt)
401
+ .requiredOption("--tab-id <id>", "Target tab ID")
211
402
  .requiredOption("--url <url>", "URL to navigate to")
212
- .addHelpText(
213
- "after",
214
- `
215
- Arguments:
216
- --tab-id <id> Numeric Chrome tab ID (from find-tab or new-tab output).
217
- --url <url> The URL to navigate the tab to.
218
-
219
- Examples:
220
- $ assistant browser chrome relay navigate --tab-id 123 --url "https://example.com/page2"`,
221
- )
222
- .action(async (opts: { tabId: number; url: string }) => {
223
- await relayCommand({
224
- action: "navigate",
225
- tabId: opts.tabId,
226
- url: opts.url,
227
- });
403
+ .action(async (opts: { tabId: string; url: string }) => {
404
+ await actionNavigate(opts.tabId, opts.url);
228
405
  });
229
406
 
230
407
  // -- evaluate --
@@ -232,45 +409,22 @@ Examples:
232
409
  relay
233
410
  .command("evaluate")
234
411
  .description("Execute JavaScript in a Chrome tab")
235
- .requiredOption("--tab-id <id>", "Target tab ID", parseInt)
236
- .option("--code <script>", "JavaScript code to evaluate in the tab")
237
- .addHelpText(
238
- "after",
239
- `
240
- Arguments:
241
- --tab-id <id> Numeric Chrome tab ID (from find-tab or new-tab output).
242
- --code <script> JavaScript code to evaluate. If omitted, reads from stdin.
243
-
244
- If --code is omitted, reads JavaScript from stdin. This is useful for long
245
- scripts that would be unwieldy as a single CLI argument.
246
-
247
- Examples:
248
- $ assistant browser chrome relay evaluate --tab-id 123 --code "document.title"
249
- $ echo "document.querySelectorAll('a').length" | assistant browser chrome relay evaluate --tab-id 123
250
- $ cat scrape.js | assistant browser chrome relay evaluate --tab-id 123`,
412
+ .requiredOption("--tab-id <id>", "Target tab ID")
413
+ .option(
414
+ "--code <script>",
415
+ "JavaScript code to evaluate (or read from stdin)",
251
416
  )
252
- .action(async (opts: { tabId: number; code?: string }) => {
417
+ .action(async (opts: { tabId: string; code?: string }) => {
253
418
  let code: string;
254
419
  if (opts.code) {
255
420
  code = opts.code;
256
421
  } else if (process.stdin.isTTY) {
257
- process.stdout.write(
258
- JSON.stringify({
259
- ok: false,
260
- error: "No code provided. Use --code or pipe JavaScript via stdin.",
261
- }) + "\n",
262
- );
263
- process.exitCode = 1;
422
+ emitError("No code provided. Use --code or pipe JavaScript via stdin.");
264
423
  return;
265
424
  } else {
266
425
  code = await readStdin();
267
426
  }
268
-
269
- await relayCommand({
270
- action: "evaluate",
271
- tabId: opts.tabId,
272
- code,
273
- });
427
+ await actionEvaluate(opts.tabId, code);
274
428
  });
275
429
 
276
430
  // -- get-cookies --
@@ -279,20 +433,8 @@ Examples:
279
433
  .command("get-cookies")
280
434
  .description("Fetch cookies for a domain")
281
435
  .requiredOption("--domain <domain>", "Cookie domain to fetch")
282
- .addHelpText(
283
- "after",
284
- `
285
- Arguments:
286
- --domain <domain> The cookie domain to query (e.g. ".instagram.com").
287
-
288
- Returns all cookies matching the specified domain.
289
-
290
- Examples:
291
- $ assistant browser chrome relay get-cookies --domain ".instagram.com"
292
- $ assistant browser chrome relay get-cookies --domain ".amazon.com"`,
293
- )
294
436
  .action(async (opts: { domain: string }) => {
295
- await relayCommand({ action: "get_cookies", domain: opts.domain });
437
+ await actionGetCookies(opts.domain);
296
438
  });
297
439
 
298
440
  // -- set-cookie --
@@ -301,236 +443,24 @@ Examples:
301
443
  .command("set-cookie")
302
444
  .description("Set a cookie in the browser")
303
445
  .requiredOption("--cookie <json>", "Cookie specification as JSON")
304
- .addHelpText(
305
- "after",
306
- `
307
- Arguments:
308
- --cookie <json> JSON object specifying the cookie to set. Must include
309
- at minimum "name", "value", and "domain" fields.
310
-
311
- Examples:
312
- $ assistant browser chrome relay set-cookie --cookie '{"name":"session","value":"abc123","domain":".example.com"}'`,
313
- )
314
446
  .action(async (opts: { cookie: string }) => {
315
- let parsed: unknown;
447
+ let parsed: Record<string, unknown>;
316
448
  try {
317
- parsed = JSON.parse(opts.cookie);
449
+ parsed = JSON.parse(opts.cookie) as Record<string, unknown>;
318
450
  } catch {
319
- process.stdout.write(
320
- JSON.stringify({
321
- ok: false,
322
- error: "Invalid JSON in --cookie argument",
323
- }) + "\n",
324
- );
325
- process.exitCode = 1;
451
+ emitError("Invalid JSON in --cookie argument");
326
452
  return;
327
453
  }
328
- await relayCommand({ action: "set_cookie", cookie: parsed });
454
+ await actionSetCookie(parsed);
329
455
  });
330
456
 
331
457
  // -- screenshot --
332
458
 
333
459
  relay
334
460
  .command("screenshot")
335
- .description("Capture a screenshot of a Chrome tab")
336
- .option("--tab-id <id>", "Target tab ID", parseInt)
337
- .addHelpText(
338
- "after",
339
- `
340
- Arguments:
341
- --tab-id <id> Optional numeric Chrome tab ID. If omitted, captures
342
- the currently active tab.
343
-
344
- Returns a base64-encoded screenshot image.
345
-
346
- Examples:
347
- $ assistant browser chrome relay screenshot --tab-id 123
348
- $ assistant browser chrome relay screenshot`,
349
- )
350
- .action(async (opts: { tabId?: number }) => {
351
- await relayCommand({
352
- action: "screenshot",
353
- ...(opts.tabId !== undefined ? { tabId: opts.tabId } : {}),
354
- });
355
- });
356
-
357
- // -- status --
358
-
359
- relay
360
- .command("status")
361
- .description("Check browser extension relay connection status")
362
- .addHelpText(
363
- "after",
364
- `
365
- Reports whether the browser extension relay is connected, including the
366
- connection ID, last heartbeat time, and number of pending commands.
367
-
368
- Examples:
369
- $ assistant browser chrome relay status`,
370
- )
371
- .action(async () => {
372
- try {
373
- // Dual-path: use in-process status when connected (daemon context),
374
- // otherwise query gateway HTTP (out-of-process CLI context).
375
- // getStatus() is a synchronous getter that never throws — we check
376
- // .connected to decide whether the local status is meaningful.
377
- let data: {
378
- connected: boolean;
379
- connectionId?: string | null;
380
- lastHeartbeatAt?: number | null;
381
- pendingCommandCount: number;
382
- };
383
-
384
- const localStatus = extensionRelayServer.getStatus();
385
- if (localStatus.connected) {
386
- data = localStatus;
387
- } else {
388
- // In-process relay not connected — fall back to gateway HTTP
389
- if (!isSigningKeyInitialized()) {
390
- initAuthSigningKey(loadOrCreateSigningKey());
391
- }
392
- data = await gatewayGet<typeof data>("/v1/browser-relay/status");
393
- }
394
-
395
- process.stdout.write(
396
- JSON.stringify({
397
- ok: true,
398
- connected: data.connected,
399
- connectionId: data.connectionId ?? null,
400
- lastHeartbeatAt: data.lastHeartbeatAt
401
- ? new Date(data.lastHeartbeatAt).toISOString()
402
- : null,
403
- pendingCommandCount: data.pendingCommandCount,
404
- }) + "\n",
405
- );
406
- } catch (err) {
407
- const message = err instanceof Error ? err.message : String(err);
408
- process.stdout.write(
409
- JSON.stringify({ ok: false, error: message }) + "\n",
410
- );
411
- process.exitCode = 1;
412
- }
413
- });
414
-
415
- // ---------------------------------------------------------------------------
416
- // chrome launch
417
- // ---------------------------------------------------------------------------
418
-
419
- chrome
420
- .command("launch")
421
- .description(
422
- "Launch or connect to a Chrome instance with CDP (Chrome DevTools Protocol)",
423
- )
424
- .option("--start-url <url>", "Initial URL to open when launching Chrome")
425
- .option("--port <port>", "CDP port (default: 9222)", parseInt)
426
- .addHelpText(
427
- "after",
428
- `
429
- Launches a Chrome instance with Chrome DevTools Protocol (CDP) enabled, or
430
- returns the existing session if Chrome is already running with open tabs.
431
- Idempotent — returns immediately if Chrome is already running with tabs.
432
- Kills stale CDP instances (CDP endpoint up but no tabs) and relaunches.
433
- Polls up to 15 seconds for the CDP endpoint to become ready.
434
-
435
- Arguments:
436
- --start-url <url> Initial URL to open in the new Chrome window. If
437
- omitted, Chrome opens to its default start page.
438
- --port <port> CDP port to use. Defaults to 9222.
439
-
440
- Examples:
441
- $ assistant browser chrome launch
442
- $ assistant browser chrome launch --start-url "https://x.com/login" --port 9333`,
443
- )
444
- .action(async (opts: { startUrl?: string; port?: number }) => {
445
- try {
446
- const session = await ensureChromeWithCdp({
447
- startUrl: opts.startUrl,
448
- port: opts.port,
449
- });
450
- process.stdout.write(
451
- JSON.stringify({
452
- ok: true,
453
- baseUrl: session.baseUrl,
454
- launchedByUs: session.launchedByUs,
455
- userDataDir: session.userDataDir,
456
- }) + "\n",
457
- );
458
- } catch (err) {
459
- const message = err instanceof Error ? err.message : String(err);
460
- process.stdout.write(
461
- JSON.stringify({ ok: false, error: message }) + "\n",
462
- );
463
- process.exitCode = 1;
464
- }
465
- });
466
-
467
- // ---------------------------------------------------------------------------
468
- // chrome minimize
469
- // ---------------------------------------------------------------------------
470
-
471
- chrome
472
- .command("minimize")
473
- .description("Minimize the Chrome CDP window")
474
- .option("--port <port>", "CDP port (default: 9222)", parseInt)
475
- .addHelpText(
476
- "after",
477
- `
478
- Minimizes the Chrome window associated with the CDP session. Uses the
479
- Browser.setWindowBounds CDP method to set the window state to minimized.
480
-
481
- Arguments:
482
- --port <port> CDP port to connect to. Defaults to 9222.
483
-
484
- Examples:
485
- $ assistant browser chrome minimize
486
- $ assistant browser chrome minimize --port 9333`,
487
- )
488
- .action(async (opts: { port?: number }) => {
489
- try {
490
- const cdpBase = opts.port ? `http://localhost:${opts.port}` : undefined;
491
- await minimizeChromeWindow(cdpBase);
492
- process.stdout.write(JSON.stringify({ ok: true }) + "\n");
493
- } catch (err) {
494
- const message = err instanceof Error ? err.message : String(err);
495
- process.stdout.write(
496
- JSON.stringify({ ok: false, error: message }) + "\n",
497
- );
498
- process.exitCode = 1;
499
- }
500
- });
501
-
502
- // ---------------------------------------------------------------------------
503
- // chrome restore
504
- // ---------------------------------------------------------------------------
505
-
506
- chrome
507
- .command("restore")
508
- .description("Restore the Chrome CDP window from minimized state")
509
- .option("--port <port>", "CDP port (default: 9222)", parseInt)
510
- .addHelpText(
511
- "after",
512
- `
513
- Restores (un-minimizes) the Chrome window associated with the CDP session.
514
- Uses the Browser.setWindowBounds CDP method to set the window state to normal.
515
-
516
- Arguments:
517
- --port <port> CDP port to connect to. Defaults to 9222.
518
-
519
- Examples:
520
- $ assistant browser chrome restore
521
- $ assistant browser chrome restore --port 9333`,
522
- )
523
- .action(async (opts: { port?: number }) => {
524
- try {
525
- const cdpBase = opts.port ? `http://localhost:${opts.port}` : undefined;
526
- await restoreChromeWindow(cdpBase);
527
- process.stdout.write(JSON.stringify({ ok: true }) + "\n");
528
- } catch (err) {
529
- const message = err instanceof Error ? err.message : String(err);
530
- process.stdout.write(
531
- JSON.stringify({ ok: false, error: message }) + "\n",
532
- );
533
- process.exitCode = 1;
534
- }
461
+ .description("Capture a base64-encoded PNG screenshot of a Chrome tab")
462
+ .option("--tab-id <id>", "Target tab ID (defaults to active tab)")
463
+ .action(async (opts: { tabId?: string }) => {
464
+ await actionScreenshot(opts.tabId);
535
465
  });
536
466
  }