@vellumai/assistant 0.6.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (396) hide show
  1. package/bun.lock +40 -40
  2. package/bunfig.toml +3 -0
  3. package/docs/architecture/memory.md +1 -1
  4. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
  5. package/openapi.yaml +184 -69
  6. package/package.json +41 -41
  7. package/scripts/generate-openapi.ts +1 -2
  8. package/src/__tests__/acp-session.test.ts +43 -0
  9. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
  10. package/src/__tests__/app-executors.test.ts +1 -0
  11. package/src/__tests__/app-source-watcher.test.ts +37 -11
  12. package/src/__tests__/approval-routes-http.test.ts +178 -1
  13. package/src/__tests__/browser-fill-credential.test.ts +229 -94
  14. package/src/__tests__/browser-manager.test.ts +40 -27
  15. package/src/__tests__/catalog-files.test.ts +862 -0
  16. package/src/__tests__/channel-approvals.test.ts +53 -0
  17. package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
  18. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  19. package/src/__tests__/config-schema.test.ts +125 -48
  20. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
  21. package/src/__tests__/context-overflow-approval.test.ts +16 -1
  22. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  23. package/src/__tests__/conversation-agent-loop.test.ts +1 -1
  24. package/src/__tests__/conversation-analysis-routes.test.ts +2 -2
  25. package/src/__tests__/conversation-attachments.test.ts +80 -4
  26. package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
  27. package/src/__tests__/conversation-fork-crud.test.ts +17 -0
  28. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  29. package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
  30. package/src/__tests__/conversation-inject-context.test.ts +103 -0
  31. package/src/__tests__/conversation-queue.test.ts +45 -2
  32. package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
  33. package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
  34. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  35. package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
  36. package/src/__tests__/conversation-starter-routes.test.ts +126 -0
  37. package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
  38. package/src/__tests__/conversation-store.test.ts +195 -0
  39. package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
  40. package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
  41. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  42. package/src/__tests__/credential-vault-unit.test.ts +4 -4
  43. package/src/__tests__/credential-vault.test.ts +152 -13
  44. package/src/__tests__/credentials-cli.test.ts +2 -2
  45. package/src/__tests__/date-context.test.ts +4 -4
  46. package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
  47. package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
  48. package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
  49. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  50. package/src/__tests__/gemini-provider.test.ts +2 -2
  51. package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
  52. package/src/__tests__/headless-browser-interactions.test.ts +707 -371
  53. package/src/__tests__/headless-browser-navigate.test.ts +389 -47
  54. package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
  55. package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
  56. package/src/__tests__/host-bash-proxy.test.ts +150 -1
  57. package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
  58. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
  59. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
  60. package/src/__tests__/host-browser-event-routes.test.ts +350 -0
  61. package/src/__tests__/host-browser-proxy.test.ts +444 -0
  62. package/src/__tests__/host-browser-routes.test.ts +198 -0
  63. package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
  64. package/src/__tests__/host-cu-proxy.test.ts +171 -1
  65. package/src/__tests__/host-file-proxy.test.ts +185 -1
  66. package/src/__tests__/host-file-read-tool.test.ts +52 -0
  67. package/src/__tests__/host-proxy-interface.test.ts +165 -0
  68. package/src/__tests__/host-shell-tool.test.ts +1 -11
  69. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  70. package/src/__tests__/integration-status.test.ts +6 -7
  71. package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
  72. package/src/__tests__/mcp-client-auth.test.ts +40 -4
  73. package/src/__tests__/mcp-health-check.test.ts +10 -3
  74. package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
  75. package/src/__tests__/migration-export-http.test.ts +61 -2
  76. package/src/__tests__/migration-export-streaming.test.ts +66 -0
  77. package/src/__tests__/migration-import-commit-http.test.ts +101 -1
  78. package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
  79. package/src/__tests__/oauth-apps-routes.test.ts +17 -12
  80. package/src/__tests__/oauth-cli.test.ts +707 -60
  81. package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
  82. package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
  83. package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
  84. package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
  85. package/src/__tests__/oauth-providers-routes.test.ts +50 -14
  86. package/src/__tests__/oauth-store.test.ts +1386 -182
  87. package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
  88. package/src/__tests__/onboarding-template-contract.test.ts +75 -57
  89. package/src/__tests__/openai-provider.test.ts +2 -2
  90. package/src/__tests__/outlook-categories.test.ts +1 -1
  91. package/src/__tests__/outlook-client-automation.test.ts +1 -1
  92. package/src/__tests__/outlook-compose-tools.test.ts +1 -1
  93. package/src/__tests__/outlook-email-watcher.test.ts +1 -1
  94. package/src/__tests__/outlook-follow-up.test.ts +1 -1
  95. package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
  96. package/src/__tests__/outlook-trash.test.ts +1 -1
  97. package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
  98. package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
  99. package/src/__tests__/permission-mode.test.ts +28 -56
  100. package/src/__tests__/platform-callback-registration.test.ts +19 -0
  101. package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
  102. package/src/__tests__/proxy-approval-callback.test.ts +18 -0
  103. package/src/__tests__/require-fresh-approval.test.ts +40 -1
  104. package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
  105. package/src/__tests__/schedule-routes.test.ts +162 -0
  106. package/src/__tests__/secret-detection-handler.test.ts +84 -0
  107. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  108. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  109. package/src/__tests__/set-permission-mode.test.ts +13 -250
  110. package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
  111. package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
  112. package/src/__tests__/slack-channel-config.test.ts +12 -15
  113. package/src/__tests__/subagent-detail.test.ts +44 -2
  114. package/src/__tests__/subagent-disposal.test.ts +1 -0
  115. package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
  116. package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
  117. package/src/__tests__/subagent-manager-notify.test.ts +1 -0
  118. package/src/__tests__/subagent-notify-parent.test.ts +1 -0
  119. package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
  120. package/src/__tests__/subagent-tools.test.ts +1 -0
  121. package/src/__tests__/subagent-types.test.ts +1 -0
  122. package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
  123. package/src/__tests__/system-prompt.test.ts +72 -1
  124. package/src/__tests__/task-scheduler.test.ts +32 -6
  125. package/src/__tests__/telegram-config.test.ts +10 -13
  126. package/src/__tests__/terminal-tools.test.ts +9 -0
  127. package/src/__tests__/tool-approval-handler.test.ts +73 -0
  128. package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
  129. package/src/__tests__/top-level-renderer.test.ts +73 -1
  130. package/src/__tests__/transport-hints-queue.test.ts +14 -29
  131. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
  132. package/src/__tests__/v2-consent-policy.test.ts +103 -0
  133. package/src/acp/client-handler.ts +30 -4
  134. package/src/agent/loop.ts +12 -6
  135. package/src/approvals/guardian-request-resolvers.ts +21 -15
  136. package/src/browser-session/__tests__/manager.test.ts +297 -0
  137. package/src/browser-session/backends/cdp-inspect.ts +30 -0
  138. package/src/browser-session/backends/extension.ts +26 -0
  139. package/src/browser-session/backends/local.ts +24 -0
  140. package/src/browser-session/events.ts +164 -0
  141. package/src/browser-session/index.ts +27 -0
  142. package/src/browser-session/manager.ts +159 -0
  143. package/src/browser-session/types.ts +28 -0
  144. package/src/channels/__tests__/types.test.ts +134 -0
  145. package/src/channels/types.ts +53 -3
  146. package/src/cli/commands/browser-relay.ts +339 -409
  147. package/src/cli/commands/credentials.ts +3 -3
  148. package/src/cli/commands/email.ts +18 -13
  149. package/src/cli/commands/mcp.ts +16 -4
  150. package/src/cli/commands/oauth/__tests__/connect.test.ts +44 -44
  151. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
  152. package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
  153. package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
  154. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
  155. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
  156. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
  157. package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
  158. package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
  159. package/src/cli/commands/oauth/apps.ts +7 -4
  160. package/src/cli/commands/oauth/connect.ts +6 -3
  161. package/src/cli/commands/oauth/disconnect.ts +1 -1
  162. package/src/cli/commands/oauth/providers.ts +200 -36
  163. package/src/cli/commands/oauth/shared.ts +5 -5
  164. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
  165. package/src/cli/commands/platform/index.ts +107 -10
  166. package/src/cli/commands/usage.ts +10 -9
  167. package/src/cli/lib/daemon-credential-client.ts +4 -0
  168. package/src/cli/program.ts +1 -1
  169. package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
  170. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
  171. package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
  172. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
  173. package/src/config/bundled-skills/contacts/SKILL.md +3 -0
  174. package/src/config/bundled-skills/document/SKILL.md +4 -0
  175. package/src/config/bundled-skills/gmail/SKILL.md +1 -1
  176. package/src/config/bundled-skills/outlook/SKILL.md +7 -0
  177. package/src/config/bundled-skills/subagent/SKILL.md +21 -0
  178. package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
  179. package/src/config/bundled-skills/tasks/SKILL.md +5 -0
  180. package/src/config/env-registry.ts +14 -0
  181. package/src/config/env.ts +21 -0
  182. package/src/config/feature-flag-registry.json +44 -5
  183. package/src/config/loader.ts +56 -1
  184. package/src/config/sanitize-for-transfer.ts +47 -0
  185. package/src/config/schema.ts +46 -5
  186. package/src/config/schemas/host-browser.ts +66 -0
  187. package/src/config/schemas/memory-lifecycle.ts +1 -1
  188. package/src/config/schemas/memory-retrieval.ts +103 -0
  189. package/src/config/schemas/security.ts +0 -6
  190. package/src/config/schemas/services.ts +8 -0
  191. package/src/config/types.ts +0 -1
  192. package/src/context/post-turn-tool-result-truncation.ts +176 -0
  193. package/src/context/window-manager.ts +19 -1
  194. package/src/credential-execution/approval-bridge.ts +49 -15
  195. package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
  196. package/src/daemon/app-source-watcher.ts +35 -0
  197. package/src/daemon/context-overflow-approval.ts +5 -0
  198. package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
  199. package/src/daemon/conversation-agent-loop.ts +58 -24
  200. package/src/daemon/conversation-attachments.ts +40 -0
  201. package/src/daemon/conversation-process.ts +48 -1
  202. package/src/daemon/conversation-runtime-assembly.ts +118 -36
  203. package/src/daemon/conversation-surfaces.ts +37 -36
  204. package/src/daemon/conversation-tool-setup.ts +74 -8
  205. package/src/daemon/conversation-workspace.ts +12 -0
  206. package/src/daemon/conversation.ts +226 -8
  207. package/src/daemon/date-context.ts +10 -10
  208. package/src/daemon/first-greeting.ts +3 -2
  209. package/src/daemon/handlers/conversations.ts +9 -140
  210. package/src/daemon/handlers/shared.ts +58 -0
  211. package/src/daemon/handlers/skills.ts +232 -37
  212. package/src/daemon/host-bash-proxy.ts +48 -13
  213. package/src/daemon/host-browser-proxy.ts +191 -0
  214. package/src/daemon/host-cu-proxy.ts +36 -11
  215. package/src/daemon/host-file-proxy.ts +57 -9
  216. package/src/daemon/lifecycle.ts +65 -11
  217. package/src/daemon/message-protocol.ts +7 -0
  218. package/src/daemon/message-types/conversations.ts +55 -13
  219. package/src/daemon/message-types/host-browser.ts +100 -0
  220. package/src/daemon/message-types/messages.ts +5 -5
  221. package/src/daemon/message-types/skills.ts +10 -0
  222. package/src/daemon/message-types/subagents.ts +2 -0
  223. package/src/daemon/server.ts +92 -12
  224. package/src/daemon/tool-side-effects.ts +6 -0
  225. package/src/daemon/transport-hints.ts +5 -24
  226. package/src/inbound/platform-callback-registration.ts +18 -17
  227. package/src/mcp/client.ts +59 -24
  228. package/src/memory/app-store.ts +31 -1
  229. package/src/memory/conversation-crud.ts +23 -0
  230. package/src/memory/conversation-starters-cadence.ts +76 -0
  231. package/src/memory/conversation-title-service.ts +5 -2
  232. package/src/memory/db-init.ts +12 -0
  233. package/src/memory/embedding-backend.test.ts +75 -0
  234. package/src/memory/embedding-backend.ts +131 -5
  235. package/src/memory/embedding-gemini.test.ts +54 -0
  236. package/src/memory/embedding-gemini.ts +20 -9
  237. package/src/memory/embedding-local.ts +176 -17
  238. package/src/memory/graph/consolidation.ts +10 -23
  239. package/src/memory/graph/extraction-job.ts +15 -0
  240. package/src/memory/graph/retriever.ts +40 -22
  241. package/src/memory/graph/store.test.ts +7 -3
  242. package/src/memory/graph/store.ts +47 -12
  243. package/src/memory/llm-usage-store.ts +45 -4
  244. package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
  245. package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
  246. package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
  247. package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
  248. package/src/memory/migrations/217-conversation-host-access.ts +40 -0
  249. package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
  250. package/src/memory/migrations/index.ts +6 -0
  251. package/src/memory/migrations/registry.ts +8 -0
  252. package/src/memory/schema/conversations.ts +1 -0
  253. package/src/memory/schema/oauth.ts +18 -13
  254. package/src/oauth/AGENTS.md +76 -0
  255. package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
  256. package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
  257. package/src/oauth/byo-connection.test.ts +8 -8
  258. package/src/oauth/byo-connection.ts +7 -7
  259. package/src/oauth/connect-orchestrator.ts +23 -21
  260. package/src/oauth/connect-types.ts +3 -3
  261. package/src/oauth/connection-resolver.test.ts +17 -4
  262. package/src/oauth/connection-resolver.ts +16 -16
  263. package/src/oauth/connection.ts +1 -1
  264. package/src/oauth/manual-token-connection.ts +13 -13
  265. package/src/oauth/oauth-store.ts +214 -100
  266. package/src/oauth/platform-connection.test.ts +3 -3
  267. package/src/oauth/platform-connection.ts +4 -4
  268. package/src/oauth/provider-serializer.ts +31 -5
  269. package/src/oauth/revoke.ts +76 -0
  270. package/src/oauth/seed-providers.ts +126 -87
  271. package/src/oauth/token-persistence.ts +1 -1
  272. package/src/permissions/permission-mode.ts +4 -11
  273. package/src/permissions/prompter.ts +13 -1
  274. package/src/permissions/v2-consent-policy.ts +87 -0
  275. package/src/prompts/system-prompt.ts +18 -21
  276. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
  277. package/src/prompts/templates/BOOTSTRAP.md +59 -105
  278. package/src/providers/anthropic/client.ts +1 -0
  279. package/src/providers/types.ts +1 -1
  280. package/src/runtime/AGENTS.md +23 -0
  281. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
  282. package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
  283. package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
  284. package/src/runtime/assistant-event-hub.ts +2 -2
  285. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
  286. package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
  287. package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
  288. package/src/runtime/auth/middleware.ts +98 -0
  289. package/src/runtime/auth/route-policy.ts +6 -7
  290. package/src/runtime/capability-tokens.ts +414 -0
  291. package/src/runtime/channel-approvals.ts +18 -5
  292. package/src/runtime/chrome-extension-registry.ts +332 -0
  293. package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
  294. package/src/runtime/guardian-decision-types.ts +7 -0
  295. package/src/runtime/http-server.ts +425 -70
  296. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
  297. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
  298. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
  299. package/src/runtime/migrations/migration-transport.ts +6 -0
  300. package/src/runtime/migrations/migration-wizard.ts +22 -2
  301. package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
  302. package/src/runtime/migrations/vbundle-builder.ts +145 -38
  303. package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
  304. package/src/runtime/migrations/vbundle-importer.ts +55 -5
  305. package/src/runtime/pending-interactions.ts +29 -13
  306. package/src/runtime/routes/approval-routes.ts +90 -16
  307. package/src/runtime/routes/browser-cdp-routes.ts +229 -0
  308. package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
  309. package/src/runtime/routes/conversation-analysis-routes.ts +2 -1
  310. package/src/runtime/routes/conversation-management-routes.ts +108 -0
  311. package/src/runtime/routes/conversation-routes.ts +301 -27
  312. package/src/runtime/routes/conversation-starter-routes.ts +78 -16
  313. package/src/runtime/routes/guardian-action-routes.ts +24 -13
  314. package/src/runtime/routes/host-browser-routes.ts +279 -0
  315. package/src/runtime/routes/host-file-routes.ts +9 -1
  316. package/src/runtime/routes/identity-routes.ts +259 -16
  317. package/src/runtime/routes/log-export-routes.ts +42 -22
  318. package/src/runtime/routes/memory-item-routes.ts +1 -7
  319. package/src/runtime/routes/migration-routes.ts +87 -2
  320. package/src/runtime/routes/oauth-apps.ts +15 -17
  321. package/src/runtime/routes/oauth-providers.ts +4 -0
  322. package/src/runtime/routes/schedule-routes.ts +24 -11
  323. package/src/runtime/routes/settings-routes.ts +9 -97
  324. package/src/runtime/routes/skills-routes.ts +52 -2
  325. package/src/runtime/routes/subagents-routes.ts +14 -10
  326. package/src/runtime/routes/usage-routes.ts +8 -7
  327. package/src/runtime/routes/workspace-routes.test.ts +22 -0
  328. package/src/runtime/routes/workspace-routes.ts +8 -1
  329. package/src/runtime/routes/workspace-utils.ts +2 -0
  330. package/src/schedule/scheduler.ts +7 -5
  331. package/src/security/ces-credential-client.ts +20 -0
  332. package/src/security/ces-rpc-credential-backend.ts +17 -0
  333. package/src/security/credential-backend.ts +5 -0
  334. package/src/security/oauth2.ts +42 -25
  335. package/src/security/secure-keys.ts +118 -25
  336. package/src/security/token-manager.ts +23 -10
  337. package/src/skills/catalog-files.ts +492 -0
  338. package/src/subagent/manager.ts +131 -26
  339. package/src/subagent/types.ts +19 -0
  340. package/src/tools/apps/executors.ts +11 -2
  341. package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
  342. package/src/tools/browser/auth-detector.ts +43 -12
  343. package/src/tools/browser/browser-execution.ts +645 -340
  344. package/src/tools/browser/browser-manager.ts +36 -12
  345. package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
  346. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
  347. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
  348. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
  349. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
  350. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
  351. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
  352. package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
  353. package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
  354. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
  355. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
  356. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
  357. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
  358. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
  359. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
  360. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
  361. package/src/tools/browser/cdp-client/errors.ts +34 -0
  362. package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
  363. package/src/tools/browser/cdp-client/factory.ts +204 -0
  364. package/src/tools/browser/cdp-client/index.ts +14 -0
  365. package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
  366. package/src/tools/browser/cdp-client/types.ts +52 -0
  367. package/src/tools/filesystem/edit.ts +1 -1
  368. package/src/tools/filesystem/list.ts +1 -1
  369. package/src/tools/filesystem/read.ts +1 -1
  370. package/src/tools/filesystem/write.ts +2 -1
  371. package/src/tools/host-filesystem/edit.ts +1 -1
  372. package/src/tools/host-filesystem/read.ts +12 -15
  373. package/src/tools/host-filesystem/write.ts +1 -1
  374. package/src/tools/host-terminal/host-shell.ts +21 -16
  375. package/src/tools/permission-checker.ts +77 -82
  376. package/src/tools/registry.ts +0 -2
  377. package/src/tools/secret-detection-handler.ts +34 -0
  378. package/src/tools/shared/filesystem/image-read.ts +61 -40
  379. package/src/tools/subagent/spawn.ts +47 -3
  380. package/src/tools/subagent/status.ts +2 -0
  381. package/src/tools/system/register.ts +2 -16
  382. package/src/tools/terminal/safe-env.ts +7 -0
  383. package/src/tools/terminal/shell.ts +21 -16
  384. package/src/tools/tool-approval-handler.ts +48 -2
  385. package/src/tools/types.ts +2 -0
  386. package/src/util/platform.ts +14 -19
  387. package/src/workspace/top-level-renderer.ts +19 -1
  388. package/src/__tests__/chrome-cdp.test.ts +0 -419
  389. package/src/__tests__/permission-mode-sse.test.ts +0 -418
  390. package/src/__tests__/permission-mode-store.test.ts +0 -277
  391. package/src/browser-extension-relay/protocol.ts +0 -63
  392. package/src/browser-extension-relay/server.ts +0 -203
  393. package/src/config/schemas/sandbox.ts +0 -14
  394. package/src/permissions/permission-mode-store.ts +0 -180
  395. package/src/tools/browser/chrome-cdp.ts +0 -239
  396. package/src/tools/system/set-permission-mode.ts +0 -103
@@ -0,0 +1,320 @@
1
+ /**
2
+ * End-to-end WebSocket dispatch test for the PR10 envelopes:
3
+ *
4
+ * - `host_browser_event` — the extension forwards every
5
+ * `chrome.debugger.onEvent` firing to the runtime over the
6
+ * browser-relay WebSocket. This test asserts that the runtime's
7
+ * inbound frame handler fans the event out to subscribers of the
8
+ * module-level browser-session event bus with the method + params
9
+ * + cdpSessionId preserved.
10
+ *
11
+ * - `host_browser_session_invalidated` — the extension forwards a
12
+ * detach notification over the same socket. This test asserts
13
+ * that the runtime-side `BrowserSessionManager` evicts any stale
14
+ * session whose `targetId` matches the invalidated envelope and
15
+ * that the next CDP command against that session throws, forcing
16
+ * the owning tool to create a fresh session (which in production
17
+ * triggers a reattach on the extension's dispatcher).
18
+ *
19
+ * Unlike the unit test in `host-browser-event-routes.test.ts`, this
20
+ * file stands up the full `RuntimeHttpServer` so the WS upgrade,
21
+ * frame parse, dispatch switch, and resolver helpers all run through
22
+ * their production code paths. The capability-token transport is
23
+ * used so the test does not depend on a valid guardian-bound JWT.
24
+ */
25
+
26
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
27
+
28
+ // ── Module mocks (must be declared before the real imports below) ───
29
+
30
+ mock.module("../util/logger.js", () => ({
31
+ getLogger: () =>
32
+ new Proxy({} as Record<string, unknown>, {
33
+ get: () => () => {},
34
+ }),
35
+ }));
36
+
37
+ mock.module("../config/loader.js", () => ({
38
+ getConfig: () => ({
39
+ ui: {},
40
+ model: "test",
41
+ provider: "test",
42
+ memory: { enabled: false },
43
+ rateLimit: { maxRequestsPerMinute: 0 },
44
+ secretDetection: { enabled: false },
45
+ contextWindow: { maxInputTokens: 200000 },
46
+ services: {
47
+ inference: {
48
+ mode: "your-own",
49
+ provider: "anthropic",
50
+ model: "claude-opus-4-6",
51
+ },
52
+ "image-generation": {
53
+ mode: "your-own",
54
+ provider: "gemini",
55
+ model: "gemini-3.1-flash-image-preview",
56
+ },
57
+ "web-search": { mode: "your-own", provider: "inference-provider-native" },
58
+ },
59
+ }),
60
+ }));
61
+
62
+ // ── Real imports (after mocks) ──────────────────────────────────────
63
+
64
+ import {
65
+ __resetBrowserSessionEventsForTests,
66
+ BrowserSessionManager,
67
+ createExtensionBackend,
68
+ type ForwardedCdpEvent,
69
+ onCdpEvent,
70
+ } from "../browser-session/index.js";
71
+ import { getDb, initializeDb } from "../memory/db.js";
72
+ import { mintHostBrowserCapability } from "../runtime/capability-tokens.js";
73
+ import {
74
+ __resetChromeExtensionRegistryForTests,
75
+ getChromeExtensionRegistry,
76
+ } from "../runtime/chrome-extension-registry.js";
77
+ import { RuntimeHttpServer } from "../runtime/http-server.js";
78
+ import * as pendingInteractions from "../runtime/pending-interactions.js";
79
+
80
+ initializeDb();
81
+
82
+ // ── Helpers ─────────────────────────────────────────────────────────
83
+
84
+ async function waitFor(
85
+ predicate: () => boolean,
86
+ timeoutMs = 2000,
87
+ ): Promise<void> {
88
+ const deadline = Date.now() + timeoutMs;
89
+ while (!predicate()) {
90
+ if (Date.now() > deadline) {
91
+ throw new Error(
92
+ `waitFor: predicate did not become true within ${timeoutMs}ms`,
93
+ );
94
+ }
95
+ await new Promise((r) => setTimeout(r, 10));
96
+ }
97
+ }
98
+
99
+ async function waitForRegistryEntry(
100
+ guardianId: string,
101
+ timeoutMs = 2000,
102
+ ): Promise<void> {
103
+ await waitFor(
104
+ () => getChromeExtensionRegistry().get(guardianId) !== undefined,
105
+ timeoutMs,
106
+ );
107
+ }
108
+
109
+ // ── Tests ───────────────────────────────────────────────────────────
110
+
111
+ describe("host_browser WS event + invalidation e2e", () => {
112
+ let server: RuntimeHttpServer;
113
+ let port: number;
114
+ let runtimeBaseUrl: string;
115
+
116
+ beforeEach(async () => {
117
+ const db = getDb();
118
+ db.run("DELETE FROM contact_channels");
119
+ db.run("DELETE FROM contacts");
120
+ pendingInteractions.clear();
121
+ __resetChromeExtensionRegistryForTests();
122
+ __resetBrowserSessionEventsForTests();
123
+
124
+ // Pick a non-colliding port in the same band as the other
125
+ // host-browser e2e tests but offset so parallel runs don't
126
+ // step on one another.
127
+ port = 19900 + Math.floor(Math.random() * 200);
128
+ runtimeBaseUrl = `http://127.0.0.1:${port}`;
129
+ server = new RuntimeHttpServer({ port });
130
+ await server.start();
131
+ });
132
+
133
+ afterEach(async () => {
134
+ await server?.stop();
135
+ pendingInteractions.clear();
136
+ __resetChromeExtensionRegistryForTests();
137
+ __resetBrowserSessionEventsForTests();
138
+ });
139
+
140
+ test("host_browser_event frame fans out to browser-session event bus subscribers", async () => {
141
+ const guardianId = `guardian-${crypto.randomUUID()}`;
142
+ const { token } = mintHostBrowserCapability(guardianId);
143
+
144
+ const { createMockChromeExtension } =
145
+ await import("./fixtures/mock-chrome-extension.js");
146
+ const mockExt = createMockChromeExtension({
147
+ runtimeBaseUrl,
148
+ token,
149
+ resultTransport: "ws",
150
+ });
151
+ await mockExt.start();
152
+ await mockExt.waitForConnection();
153
+ await waitForRegistryEntry(guardianId);
154
+
155
+ // Subscribe BEFORE sending the frame so we're guaranteed to see
156
+ // the fanout. The subscription is module-level so it survives
157
+ // across the WS round-trip naturally.
158
+ const observed: ForwardedCdpEvent[] = [];
159
+ const unsubscribe = onCdpEvent((event) => observed.push(event));
160
+
161
+ mockExt.sendHostBrowserEvent({
162
+ method: "Page.frameNavigated",
163
+ params: { frame: { id: "frame-1", url: "https://example.com" } },
164
+ cdpSessionId: "target-abc",
165
+ });
166
+
167
+ // The WS dispatch hop is asynchronous — poll until the event
168
+ // lands or the test times out.
169
+ await waitFor(() => observed.length === 1);
170
+
171
+ expect(observed[0].method).toBe("Page.frameNavigated");
172
+ expect(observed[0].params).toEqual({
173
+ frame: { id: "frame-1", url: "https://example.com" },
174
+ });
175
+ expect(observed[0].cdpSessionId).toBe("target-abc");
176
+
177
+ unsubscribe();
178
+ await mockExt.stop();
179
+ });
180
+
181
+ test("host_browser_event frames with no params are still routed", async () => {
182
+ const guardianId = `guardian-${crypto.randomUUID()}`;
183
+ const { token } = mintHostBrowserCapability(guardianId);
184
+
185
+ const { createMockChromeExtension } =
186
+ await import("./fixtures/mock-chrome-extension.js");
187
+ const mockExt = createMockChromeExtension({
188
+ runtimeBaseUrl,
189
+ token,
190
+ resultTransport: "ws",
191
+ });
192
+ await mockExt.start();
193
+ await mockExt.waitForConnection();
194
+ await waitForRegistryEntry(guardianId);
195
+
196
+ const observed: ForwardedCdpEvent[] = [];
197
+ const unsubscribe = onCdpEvent((event) => observed.push(event));
198
+
199
+ mockExt.sendHostBrowserEvent({ method: "Target.targetDestroyed" });
200
+
201
+ await waitFor(() => observed.length === 1);
202
+ expect(observed[0].method).toBe("Target.targetDestroyed");
203
+ expect(observed[0].params).toBeUndefined();
204
+ expect(observed[0].cdpSessionId).toBeUndefined();
205
+
206
+ unsubscribe();
207
+ await mockExt.stop();
208
+ });
209
+
210
+ test("host_browser_session_invalidated frame evicts stale sessions and the next command forces reattach", async () => {
211
+ const guardianId = `guardian-${crypto.randomUUID()}`;
212
+ const { token } = mintHostBrowserCapability(guardianId);
213
+
214
+ const { createMockChromeExtension } =
215
+ await import("./fixtures/mock-chrome-extension.js");
216
+ const mockExt = createMockChromeExtension({
217
+ runtimeBaseUrl,
218
+ token,
219
+ resultTransport: "ws",
220
+ });
221
+ await mockExt.start();
222
+ await mockExt.waitForConnection();
223
+ await waitForRegistryEntry(guardianId);
224
+
225
+ // Stand up a BrowserSessionManager that mirrors what a tool
226
+ // invocation would build. The backend counts dispatch attempts
227
+ // so we can assert the first post-invalidation send never
228
+ // reached the backend while the second (after reattach) did.
229
+ const sent: Array<{ method: string }> = [];
230
+ const backend = createExtensionBackend({
231
+ isAvailable: () => true,
232
+ sendCdp: async (command) => {
233
+ sent.push({ method: command.method });
234
+ return { result: { ok: true } };
235
+ },
236
+ dispose: () => {},
237
+ });
238
+ const manager = new BrowserSessionManager({ backends: [backend] });
239
+ const session = manager.createSession();
240
+ session.targetId = "tab-77";
241
+
242
+ // Fire the invalidation envelope from the extension side.
243
+ mockExt.sendSessionInvalidated({
244
+ targetId: "tab-77",
245
+ reason: "target_closed",
246
+ });
247
+
248
+ // Wait until the WS dispatch hop lands — `isTargetInvalidated`
249
+ // peeks at the registry without consuming the entry, so we can
250
+ // poll safely.
251
+ const { isTargetInvalidated } =
252
+ await import("../browser-session/events.js");
253
+ await waitFor(() => isTargetInvalidated("tab-77"));
254
+
255
+ // The next send against the invalidated session MUST throw —
256
+ // the manager consumes the invalidation flag, evicts the
257
+ // session, and rejects the command so the caller can create a
258
+ // fresh session (which triggers a reattach on the extension
259
+ // side).
260
+ await expect(
261
+ manager.send(session.id, { method: "Page.navigate" }),
262
+ ).rejects.toThrow(/invalidated/);
263
+
264
+ // Sanity: the backend never saw the doomed command.
265
+ expect(sent).toHaveLength(0);
266
+
267
+ // The evicted session is gone — sending again throws
268
+ // "Unknown browser session", which is the signal a tool uses
269
+ // to rebuild a fresh session.
270
+ await expect(
271
+ manager.send(session.id, { method: "Page.navigate" }),
272
+ ).rejects.toThrow(/Unknown browser session/);
273
+
274
+ // Creating a fresh session proves the reattach path works:
275
+ // the caller bounces through `createSession` and a subsequent
276
+ // send dispatches normally through the backend.
277
+ const fresh = manager.createSession();
278
+ const result = await manager.send(fresh.id, {
279
+ method: "Page.navigate",
280
+ });
281
+ expect(result.result).toEqual({ ok: true });
282
+ expect(sent).toEqual([{ method: "Page.navigate" }]);
283
+
284
+ await mockExt.stop();
285
+ });
286
+
287
+ test("malformed host_browser_event frames are dropped without tearing down the socket", async () => {
288
+ const guardianId = `guardian-${crypto.randomUUID()}`;
289
+ const { token } = mintHostBrowserCapability(guardianId);
290
+
291
+ const { createMockChromeExtension } =
292
+ await import("./fixtures/mock-chrome-extension.js");
293
+ const mockExt = createMockChromeExtension({
294
+ runtimeBaseUrl,
295
+ token,
296
+ resultTransport: "ws",
297
+ });
298
+ await mockExt.start();
299
+ await mockExt.waitForConnection();
300
+ await waitForRegistryEntry(guardianId);
301
+
302
+ const observed: ForwardedCdpEvent[] = [];
303
+ const unsubscribe = onCdpEvent((event) => observed.push(event));
304
+
305
+ // Send a frame with no method — the resolver must reject it
306
+ // and the WS dispatcher must swallow the rejection.
307
+ mockExt.sendHostBrowserEvent({ method: "" });
308
+
309
+ // Follow up with a valid frame and assert that ONLY the valid
310
+ // frame was published — proving the socket survived the bad
311
+ // frame and the dispatcher kept processing subsequent messages.
312
+ mockExt.sendHostBrowserEvent({ method: "Page.loadEventFired" });
313
+
314
+ await waitFor(() => observed.length === 1);
315
+ expect(observed[0].method).toBe("Page.loadEventFired");
316
+
317
+ unsubscribe();
318
+ await mockExt.stop();
319
+ });
320
+ });
@@ -1,4 +1,4 @@
1
- import { afterEach, describe, expect, test } from "bun:test";
1
+ import { afterEach, describe, expect, jest, test } from "bun:test";
2
2
 
3
3
  import { HostCuProxy } from "../daemon/host-cu-proxy.js";
4
4
 
@@ -776,6 +776,176 @@ describe("HostCuProxy", () => {
776
776
  });
777
777
  });
778
778
 
779
+ // -------------------------------------------------------------------------
780
+ // abort listener lifecycle
781
+ // -------------------------------------------------------------------------
782
+
783
+ describe("abort listener lifecycle", () => {
784
+ // Helper that wraps an AbortSignal to observe add/removeEventListener
785
+ // invocations without tripping over tsc's strict overload matching on
786
+ // AbortSignal itself.
787
+ type Spied = {
788
+ signal: AbortSignal;
789
+ addCalls: string[];
790
+ removeCalls: string[];
791
+ };
792
+ function spySignal(source: AbortSignal): Spied {
793
+ const addCalls: string[] = [];
794
+ const removeCalls: string[] = [];
795
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
796
+ const s = source as any;
797
+ const origAdd = source.addEventListener.bind(source);
798
+ const origRemove = source.removeEventListener.bind(source);
799
+ s.addEventListener = (
800
+ type: string,
801
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
802
+ ...rest: any[]
803
+ ) => {
804
+ addCalls.push(type);
805
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
806
+ return (origAdd as any)(type, ...rest);
807
+ };
808
+ s.removeEventListener = (
809
+ type: string,
810
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
811
+ ...rest: any[]
812
+ ) => {
813
+ removeCalls.push(type);
814
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
815
+ return (origRemove as any)(type, ...rest);
816
+ };
817
+ return { signal: source, addCalls, removeCalls };
818
+ }
819
+
820
+ test("removes abort listener from signal after resolve completes", async () => {
821
+ setup();
822
+ const controller = new AbortController();
823
+ const spy = spySignal(controller.signal);
824
+
825
+ const resultPromise = proxy.request(
826
+ "computer_use_click",
827
+ { element_id: 1 },
828
+ "session-1",
829
+ 1,
830
+ undefined,
831
+ spy.signal,
832
+ );
833
+
834
+ expect(spy.addCalls).toEqual(["abort"]);
835
+ expect(spy.removeCalls).toEqual([]);
836
+
837
+ const requestId = (sentMessages[0] as Record<string, unknown>)
838
+ .requestId as string;
839
+ proxy.resolve(requestId, { axTree: "Button [1]" });
840
+ await resultPromise;
841
+
842
+ // Listener is detached after normal completion.
843
+ expect(spy.removeCalls).toEqual(["abort"]);
844
+
845
+ // Subsequent aborts are harmless no-ops (no side effects on the proxy).
846
+ controller.abort();
847
+ // No additional emitted envelopes from the late abort.
848
+ expect(sentMessages).toHaveLength(1);
849
+ });
850
+
851
+ test("removes abort listener from signal on timer timeout", async () => {
852
+ setup();
853
+
854
+ jest.useFakeTimers();
855
+ try {
856
+ const controller = new AbortController();
857
+ const spy = spySignal(controller.signal);
858
+
859
+ const resultPromise = proxy.request(
860
+ "computer_use_click",
861
+ { element_id: 1 },
862
+ "session-1",
863
+ 1,
864
+ undefined,
865
+ spy.signal,
866
+ );
867
+
868
+ expect(spy.addCalls).toEqual(["abort"]);
869
+ expect(spy.removeCalls).toEqual([]);
870
+
871
+ const requestId = (sentMessages[0] as Record<string, unknown>)
872
+ .requestId as string;
873
+ expect(proxy.hasPendingRequest(requestId)).toBe(true);
874
+
875
+ // Advance past the 60s internal timeout.
876
+ jest.advanceTimersByTime(61 * 1000);
877
+
878
+ const result = await resultPromise;
879
+ expect(result.isError).toBe(true);
880
+ expect(result.content).toContain("Host CU proxy timed out");
881
+ expect(proxy.hasPendingRequest(requestId)).toBe(false);
882
+
883
+ // Listener is detached after the timer fires.
884
+ expect(spy.removeCalls).toEqual(["abort"]);
885
+
886
+ // Subsequent aborts should be harmless — no cancel emitted.
887
+ controller.abort();
888
+ expect(sentMessages).toHaveLength(1);
889
+ } finally {
890
+ jest.useRealTimers();
891
+ }
892
+ });
893
+ });
894
+
895
+ // -------------------------------------------------------------------------
896
+ // sender throws synchronously
897
+ // -------------------------------------------------------------------------
898
+
899
+ describe("sender throws synchronously", () => {
900
+ test("rejects the promise, clears pending state and timer, invokes onInternalResolve", async () => {
901
+ sentMessages = [];
902
+ resolvedRequestIds = [];
903
+ const throwingSend = () => {
904
+ throw new Error("transport down");
905
+ };
906
+ proxy = new HostCuProxy(throwingSend as never, (requestId: string) =>
907
+ resolvedRequestIds.push(requestId),
908
+ );
909
+
910
+ // request() synchronously calls sendToClient inside the Promise
911
+ // executor. A throw there surfaces as a rejected promise.
912
+ const resultPromise = proxy.request(
913
+ "computer_use_click",
914
+ { element_id: 1 },
915
+ "session-1",
916
+ 1,
917
+ );
918
+
919
+ await expect(resultPromise).rejects.toThrow("transport down");
920
+
921
+ // The internal resolve should fire exactly once as part of cleanup.
922
+ expect(resolvedRequestIds).toHaveLength(1);
923
+
924
+ // Issue a new request on a fresh (non-throwing) sender and verify
925
+ // the proxy is still functional — no stale timers or bookkeeping
926
+ // from the failed request.
927
+ sentMessages = [];
928
+ proxy.updateSender(
929
+ ((msg: unknown) => sentMessages.push(msg)) as never,
930
+ true,
931
+ );
932
+ const okPromise = proxy.request(
933
+ "computer_use_click",
934
+ { element_id: 2 },
935
+ "session-1",
936
+ 2,
937
+ );
938
+ expect(sentMessages).toHaveLength(1);
939
+ const okRequestId = (sentMessages[0] as Record<string, unknown>)
940
+ .requestId as string;
941
+ expect(proxy.hasPendingRequest(okRequestId)).toBe(true);
942
+ proxy.resolve(okRequestId, { axTree: "Button [2]" });
943
+ const okResult = await okPromise;
944
+ expect(okResult.isError).toBe(false);
945
+ expect(okResult.content).toContain("Button [2]");
946
+ });
947
+ });
948
+
779
949
  // -------------------------------------------------------------------------
780
950
  // onInternalResolve callback
781
951
  // -------------------------------------------------------------------------