@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
@@ -4,9 +4,9 @@ import {
4
4
  getApp,
5
5
  getAppDirPath,
6
6
  getAppPreview,
7
- inlineDistAssets,
8
7
  isMultifileApp,
9
8
  resolveAppDir,
9
+ resolveEffectiveAppHtml,
10
10
  updateApp,
11
11
  } from "../memory/app-store.js";
12
12
  import {
@@ -65,32 +65,36 @@ export function markSurfaceCompleted(
65
65
  }
66
66
 
67
67
  // Persist to DB.
68
- const rows = getMessages(ctx.conversationId);
69
- for (let r = rows.length - 1; r >= 0; r--) {
70
- let parsed: unknown[];
71
- try {
72
- const result = JSON.parse(rows[r].content);
73
- if (!Array.isArray(result)) continue;
74
- parsed = result;
75
- } catch {
76
- // Some rows store plain text content (e.g. notification seeding) —
77
- // skip them and keep scanning.
78
- continue;
79
- }
80
- let found = false;
81
- for (const pb of parsed) {
82
- const rb = pb as Record<string, unknown>;
83
- if (rb.type === "ui_surface" && rb.surfaceId === surfaceId) {
84
- rb.completed = true;
85
- rb.completionSummary = summary;
86
- found = true;
87
- break;
68
+ try {
69
+ const rows = getMessages(ctx.conversationId);
70
+ for (let r = rows.length - 1; r >= 0; r--) {
71
+ let parsed: unknown[];
72
+ try {
73
+ const result = JSON.parse(rows[r].content);
74
+ if (!Array.isArray(result)) continue;
75
+ parsed = result;
76
+ } catch {
77
+ // Some rows store plain text content (e.g. notification seeding) —
78
+ // skip them and keep scanning.
79
+ continue;
80
+ }
81
+ let found = false;
82
+ for (const pb of parsed) {
83
+ const rb = pb as Record<string, unknown>;
84
+ if (rb.type === "ui_surface" && rb.surfaceId === surfaceId) {
85
+ rb.completed = true;
86
+ rb.completionSummary = summary;
87
+ found = true;
88
+ break;
89
+ }
90
+ }
91
+ if (found) {
92
+ updateMessageContent(rows[r].id, JSON.stringify(parsed));
93
+ return;
88
94
  }
89
95
  }
90
- if (found) {
91
- updateMessageContent(rows[r].id, JSON.stringify(parsed));
92
- return;
93
- }
96
+ } catch (err) {
97
+ log.warn({ err, surfaceId }, "Failed to persist surface completion to DB");
94
98
  }
95
99
  }
96
100
  const TASK_PROGRESS_TEMPLATE_FIELDS = ["title", "status", "steps"] as const;
@@ -1146,10 +1150,12 @@ export function refreshSurfacesForApp(
1146
1150
  // Push current HTML onto the undo stack before overwriting
1147
1151
  pushUndoState(ctx.surfaceUndoStacks, surfaceId, data.html);
1148
1152
 
1149
- // Update in-memory surface state so the next refinement gets fresh HTML
1153
+ // Update in-memory surface state so the next refinement gets fresh HTML.
1154
+ // For multifile apps, resolve the compiled dist/index.html with inlined
1155
+ // assets rather than the empty root index.html (app.htmlDefinition).
1150
1156
  const updatedData: DynamicPageSurfaceData = {
1151
1157
  ...data,
1152
- html: app.htmlDefinition,
1158
+ html: resolveEffectiveAppHtml(app),
1153
1159
  ...(opts?.fileChange
1154
1160
  ? { reloadGeneration: (data.reloadGeneration ?? 0) + 1 }
1155
1161
  : {}),
@@ -1539,11 +1545,10 @@ export async function surfaceProxyResolver(
1539
1545
  const storedPreview = getAppPreview(app.id);
1540
1546
  const { dirName } = resolveAppDir(app.id);
1541
1547
 
1542
- // For multifile TSX apps, resolve HTML from compiled dist/index.html
1543
- // rather than the root index.html (which is empty for formatVersion 2).
1544
- let html = app.htmlDefinition;
1548
+ // For multifile TSX apps, auto-compile if dist is missing, then
1549
+ // resolve HTML from compiled dist/index.html with inlined assets.
1545
1550
  if (isMultifileApp(app)) {
1546
- const { existsSync, readFileSync } = await import("node:fs");
1551
+ const { existsSync } = await import("node:fs");
1547
1552
  const { join } = await import("node:path");
1548
1553
  const appDir = getAppDirPath(app.id);
1549
1554
  const distIndex = join(appDir, "dist", "index.html");
@@ -1557,12 +1562,8 @@ export async function surfaceProxyResolver(
1557
1562
  );
1558
1563
  }
1559
1564
  }
1560
- if (existsSync(distIndex)) {
1561
- html = inlineDistAssets(appDir, readFileSync(distIndex, "utf-8"));
1562
- } else {
1563
- html = `<p>App compilation failed. Edit a source file to trigger a rebuild.</p>`;
1564
- }
1565
1565
  }
1566
+ const html = resolveEffectiveAppHtml(app);
1566
1567
 
1567
1568
  const surfaceData: DynamicPageSurfaceData = {
1568
1569
  html,
@@ -6,6 +6,11 @@
6
6
  * keeping the constructor body focused on wiring.
7
7
  */
8
8
 
9
+ import {
10
+ type HostProxyCapability,
11
+ type InterfaceId,
12
+ supportsHostProxy,
13
+ } from "../channels/types.js";
9
14
  import { isHttpAuthDisabled } from "../config/env.js";
10
15
  import { getIsPlatform } from "../config/env-registry.js";
11
16
  import type { CesClient } from "../credential-execution/client.js";
@@ -22,6 +27,7 @@ import {
22
27
  findHighestPriorityRule,
23
28
  } from "../permissions/trust-store.js";
24
29
  import { isAllowDecision } from "../permissions/types.js";
30
+ import { isPermissionControlsV2Enabled } from "../permissions/v2-consent-policy.js";
25
31
  import type { Message, ToolDefinition } from "../providers/types.js";
26
32
  import type { TrustClass } from "../runtime/actor-trust-resolver.js";
27
33
  import { coreAppProxyTools } from "../tools/apps/definitions.js";
@@ -106,6 +112,8 @@ export interface ToolSetupContext extends SurfaceConversationContext {
106
112
  callSessionId?: string;
107
113
  /** Optional proxy for delegating host_bash execution to a connected client. */
108
114
  hostBashProxy?: import("./host-bash-proxy.js").HostBashProxy;
115
+ /** Optional proxy for delegating CDP commands to a connected client (managed/cloud-hosted mode). */
116
+ hostBrowserProxy?: import("./host-browser-proxy.js").HostBrowserProxy;
109
117
  /** Optional proxy for delegating host_file_read/write/edit execution to a connected client. */
110
118
  hostFileProxy?: import("./host-file-proxy.js").HostFileProxy;
111
119
  /** CES RPC client for credential execution operations. Injected when CES tools are enabled and the CES process is available. */
@@ -190,6 +198,7 @@ export function createToolExecutor(
190
198
  forcePromptSideEffects: ctx.memoryPolicy.strictSideEffects,
191
199
  toolUseId,
192
200
  hostBashProxy: ctx.hostBashProxy,
201
+ hostBrowserProxy: ctx.hostBrowserProxy,
193
202
  hostFileProxy: ctx.hostFileProxy,
194
203
  isPlatformHosted: getIsPlatform(),
195
204
  cesClient: ctx.cesClient,
@@ -312,6 +321,13 @@ export function createProxyApprovalCallback(
312
321
  const { scheme } = decision.target;
313
322
  const url = `${scheme}://${hostname}${port ? ":" + port : ""}${path}`;
314
323
 
324
+ if (isPermissionControlsV2Enabled()) {
325
+ // Under v2 we suppress deterministic network approval cards entirely.
326
+ // Proxied asks should follow the same non-host auto-allow contract as
327
+ // regular network_request invocations instead of turning into hard blocks.
328
+ return true;
329
+ }
330
+
315
331
  const input: Record<string, unknown> = {
316
332
  url,
317
333
  proxy_session_id: request.sessionId,
@@ -456,17 +472,49 @@ export interface SkillProjectionContext {
456
472
  subagentAllowedTools?: Set<string>;
457
473
  /** True when this conversation belongs to a subagent spawned by SubagentManager. */
458
474
  readonly isSubagent?: boolean;
475
+ /**
476
+ * The interface id of the connected client driving the current turn (e.g.
477
+ * "macos", "chrome-extension"). Used to gate host tools by per-capability
478
+ * `supportsHostProxy(transport, capability)` so that interfaces which only
479
+ * support a subset of the host proxy set (e.g. chrome-extension supports
480
+ * `host_browser` but not `host_bash`/`host_file`) do not leak unsupported
481
+ * host tools into the LLM tool definitions.
482
+ */
483
+ readonly transportInterface?: InterfaceId;
459
484
  }
460
485
 
461
486
  // ── Conditional tool sets ────────────────────────────────────────────
462
487
 
463
488
  const UI_SURFACE_TOOL_NAMES = new Set(["ui_show", "ui_update", "ui_dismiss"]);
464
- const HOST_TOOL_NAMES = new Set([
465
- "host_file_read",
466
- "host_file_write",
467
- "host_file_edit",
468
- "host_bash",
489
+ /**
490
+ * Single source of truth for which tools are host tools and the capability
491
+ * each one requires from the connected client interface. Adding a tool here
492
+ * automatically adds it to `HOST_TOOL_NAMES` below, so the two collections
493
+ * cannot drift apart: if a new host tool is added without a capability
494
+ * mapping, `isToolActiveForContext` cannot accidentally return `true` for
495
+ * chrome-extension (or any other partial-capability transport) because
496
+ * `HOST_TOOL_NAMES` wouldn't contain it either.
497
+ *
498
+ * `isToolActiveForContext` uses this map to gate each host tool individually
499
+ * so that partial-capability transports (e.g. chrome-extension only supports
500
+ * `host_browser`) only see the host tools their interface can actually
501
+ * service.
502
+ *
503
+ * Note: there is no `host_cu` tool exposed via the tool gating layer today;
504
+ * computer-use is preactivated as a skill and projected through the skill
505
+ * tools path. Only host tools that flow through the per-capability gate
506
+ * need entries here.
507
+ */
508
+ export const HOST_TOOL_TO_CAPABILITY = new Map<string, HostProxyCapability>([
509
+ ["host_bash", "host_bash"],
510
+ ["host_file_read", "host_file"],
511
+ ["host_file_write", "host_file"],
512
+ ["host_file_edit", "host_file"],
513
+ ["host_browser", "host_browser"],
469
514
  ]);
515
+ // Derived from HOST_TOOL_TO_CAPABILITY so the invariant "every host tool has
516
+ // a capability mapping" is a structural fact — no runtime assertion needed.
517
+ export const HOST_TOOL_NAMES = new Set(HOST_TOOL_TO_CAPABILITY.keys());
470
518
  const CLIENT_CAPABILITY_TOOL_NAMES = new Set(["app_open"]);
471
519
  const PLATFORM_TOOL_NAMES = new Set(["request_system_permission"]);
472
520
 
@@ -495,9 +543,27 @@ export function isToolActiveForContext(
495
543
  return ctx.channelCapabilities?.supportsDynamicUi ?? !ctx.hasNoClient;
496
544
  }
497
545
  if (HOST_TOOL_NAMES.has(name)) {
498
- // Host tools require a connected client — without one, there is no human
499
- // to approve execution and the guardian auto-approve path would allow
500
- // unchecked host command execution on the daemon host.
546
+ const capability = HOST_TOOL_TO_CAPABILITY.get(name);
547
+ const transport = ctx.transportInterface;
548
+
549
+ // Per-capability check is authoritative for structural support: if the
550
+ // transport cannot service this capability, the tool is filtered out.
551
+ if (transport && capability && !supportsHostProxy(transport, capability)) {
552
+ return false;
553
+ }
554
+
555
+ // chrome-extension is its own executor — the extension's popup gates
556
+ // commands via its own UI, and the transport does not use an SSE-level
557
+ // interactive approval channel. hasNoClient is intentionally `true` for
558
+ // chrome-extension turns (chrome-extension is not in INTERACTIVE_INTERFACES)
559
+ // and must not gate host_browser. Trust the per-capability check.
560
+ if (transport === "chrome-extension") {
561
+ return true;
562
+ }
563
+
564
+ // For transports that surface approvals over SSE (macos, backwards-compat
565
+ // fallback), deny when no client is present so the guardian auto-approve
566
+ // path cannot execute host commands unattended.
501
567
  return !ctx.hasNoClient;
502
568
  }
503
569
  if (CLIENT_CAPABILITY_TOOL_NAMES.has(name)) {
@@ -13,6 +13,16 @@ export interface WorkspaceConversationContext {
13
13
  workingDir: string;
14
14
  workspaceTopLevelContext: string | null;
15
15
  workspaceTopLevelDirty: boolean;
16
+ /**
17
+ * Client-reported host home directory, populated from host-proxy
18
+ * transport metadata (see `supportsHostProxy` / `HostProxyInterfaceId`).
19
+ * Used to render the `<workspace>` block correctly for platform-managed
20
+ * daemons where `os.homedir()` would return the container's home instead
21
+ * of the user's actual client-side home.
22
+ */
23
+ hostHomeDir?: string;
24
+ /** Client-reported host username. See `hostHomeDir`. */
25
+ hostUsername?: string;
16
26
  }
17
27
 
18
28
  /** Refresh workspace top-level directory context if needed. */
@@ -36,6 +46,8 @@ export function refreshWorkspaceTopLevelContextIfNeeded(
36
46
  conversationAttachmentsPath: currentConversationPath
37
47
  ? `${currentConversationPath}attachments/`
38
48
  : null,
49
+ hostHomeDir: ctx.hostHomeDir,
50
+ hostUsername: ctx.hostUsername,
39
51
  });
40
52
  ctx.workspaceTopLevelDirty = false;
41
53
  }
@@ -18,6 +18,7 @@
18
18
  import type { ResolvedSystemPrompt } from "../agent/loop.js";
19
19
  import { AgentLoop } from "../agent/loop.js";
20
20
  import type {
21
+ InterfaceId,
21
22
  TurnChannelContext,
22
23
  TurnInterfaceContext,
23
24
  } from "../channels/types.js";
@@ -43,12 +44,20 @@ import {
43
44
  import { registerToolTraceListener } from "../events/tool-trace-listener.js";
44
45
  import { getHookManager } from "../hooks/manager.js";
45
46
  import { resolveCanonicalGuardianRequest } from "../memory/canonical-guardian-store.js";
46
- import { updateConversationContextWindow } from "../memory/conversation-crud.js";
47
+ import {
48
+ updateConversationContextWindow,
49
+ updateConversationHostAccess,
50
+ } from "../memory/conversation-crud.js";
47
51
  import { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
48
52
  import { PermissionPrompter } from "../permissions/prompter.js";
49
53
  import { SecretPrompter } from "../permissions/secret-prompter.js";
50
54
  import { patternMatchesCandidate } from "../permissions/trust-store.js";
51
55
  import type { UserDecision } from "../permissions/types.js";
56
+ import {
57
+ isConversationHostAccessDecision,
58
+ isConversationHostAccessEnabled,
59
+ isConversationHostAccessEnablePrompt,
60
+ } from "../permissions/v2-consent-policy.js";
52
61
  import { resolvePersonaContext } from "../prompts/persona-resolver.js";
53
62
  import { buildSystemPrompt } from "../prompts/system-prompt.js";
54
63
  import type { Message } from "../providers/types.js";
@@ -104,6 +113,7 @@ import {
104
113
  } from "./conversation-tool-setup.js";
105
114
  import { refreshWorkspaceTopLevelContextIfNeeded as refreshWorkspaceImpl } from "./conversation-workspace.js";
106
115
  import { HostBashProxy } from "./host-bash-proxy.js";
116
+ import { HostBrowserProxy } from "./host-browser-proxy.js";
107
117
  import type { CuObservationResult } from "./host-cu-proxy.js";
108
118
  import { HostCuProxy } from "./host-cu-proxy.js";
109
119
  import { HostFileProxy } from "./host-file-proxy.js";
@@ -115,6 +125,7 @@ import type {
115
125
  UserMessageAttachment,
116
126
  } from "./message-protocol.js";
117
127
  import type { ConversationTransportMetadata } from "./message-types/conversations.js";
128
+ import { isHostProxyTransport } from "./message-types/conversations.js";
118
129
  import type {
119
130
  AssistantActivityState,
120
131
  ConfirmationStateChanged,
@@ -180,8 +191,22 @@ export class Conversation {
180
191
  /** @internal */ taskRunId?: string;
181
192
  /** @internal */ callSessionId?: string;
182
193
  /** @internal */ hostBashProxy?: HostBashProxy;
194
+ /** @internal */ hostBrowserProxy?: HostBrowserProxy;
183
195
  /** @internal */ hostCuProxy?: HostCuProxy;
184
196
  /** @internal */ hostFileProxy?: HostFileProxy;
197
+ /**
198
+ * Optional override sender used by `restoreBrowserProxyAvailability` so
199
+ * non-SSE transports (e.g. chrome-extension, whose host_browser_request
200
+ * frames flow through the ChromeExtensionRegistry WebSocket rather than
201
+ * the SSE hub) can preserve their registry-routed sender across drain
202
+ * queue restores. When set, `restoreBrowserProxyAvailability()` uses this
203
+ * function instead of `sendToClient` so the drain-queue path doesn't
204
+ * clobber the chrome-extension sender with the SSE hub emitter.
205
+ *
206
+ * Populated by the POST /messages handler for chrome-extension turns and
207
+ * cleared when an unrelated interface takes over (see `updateClient`).
208
+ */
209
+ /** @internal */ hostBrowserSenderOverride?: (msg: ServerMessage) => void;
185
210
  /** @internal */ cesClient?: CesClient;
186
211
  /** @internal */ readonly queue = new MessageQueue();
187
212
  /** @internal */ currentActiveSurfaceId?: string;
@@ -246,8 +271,24 @@ export class Conversation {
246
271
  }> = [];
247
272
  /** @internal */ workspaceTopLevelContext: string | null = null;
248
273
  /** @internal */ workspaceTopLevelDirty = true;
274
+ /**
275
+ * Host home directory reported by the client (e.g. macOS
276
+ * `NSHomeDirectory()`). Populated from `HostProxyTransportMetadata` when
277
+ * a message arrives from an interface that supports host-proxy tools
278
+ * (see `supportsHostProxy`). Consumed by the `<workspace>` block renderer
279
+ * so platform-managed (containerized) daemons show the user's actual
280
+ * client-side home dir instead of the container's `os.homedir()`.
281
+ * @internal
282
+ */
283
+ hostHomeDir?: string;
284
+ /**
285
+ * Host username reported by the client (e.g. macOS `NSUserName()`).
286
+ * See `hostHomeDir`.
287
+ * @internal
288
+ */
289
+ hostUsername?: string;
249
290
  public readonly traceEmitter: TraceEmitter;
250
- public readonly hasSystemPromptOverride: boolean;
291
+ /** @internal */ hasSystemPromptOverride: boolean;
251
292
  public memoryPolicy: ConversationMemoryPolicy;
252
293
  /** @internal */ readonly graphMemory: ConversationGraphMemory;
253
294
  /** @internal */ activeContextNodeIds?: string[];
@@ -387,7 +428,7 @@ export class Conversation {
387
428
  _history: import("../providers/types.js").Message[],
388
429
  ): ResolvedSystemPrompt => {
389
430
  const resolved = {
390
- systemPrompt: hasSystemPromptOverride
431
+ systemPrompt: this.hasSystemPromptOverride
391
432
  ? systemPrompt
392
433
  : (() => {
393
434
  const persona = resolvePersonaContext(
@@ -501,6 +542,7 @@ export class Conversation {
501
542
  this.traceEmitter.updateSender(sendToClient);
502
543
  if (!opts?.skipProxySenderUpdate) {
503
544
  this.hostBashProxy?.updateSender(sendToClient, !hasNoClient);
545
+ this.hostBrowserProxy?.updateSender(sendToClient, !hasNoClient);
504
546
  this.hostCuProxy?.updateSender(sendToClient, !hasNoClient);
505
547
  this.hostFileProxy?.updateSender(sendToClient, !hasNoClient);
506
548
  }
@@ -527,6 +569,7 @@ export class Conversation {
527
569
  /** Mark host proxies as unavailable so tool execution uses local fallback. */
528
570
  clearProxyAvailability(): void {
529
571
  this.hostBashProxy?.updateSender(this.sendToClient, false);
572
+ this.hostBrowserProxy?.updateSender(this.sendToClient, false);
530
573
  this.hostCuProxy?.updateSender(this.sendToClient, false);
531
574
  this.hostFileProxy?.updateSender(this.sendToClient, false);
532
575
  }
@@ -535,11 +578,43 @@ export class Conversation {
535
578
  restoreProxyAvailability(): void {
536
579
  if (!this.hasNoClient) {
537
580
  this.hostBashProxy?.updateSender(this.sendToClient, true);
581
+ this.hostBrowserProxy?.updateSender(this.sendToClient, true);
538
582
  this.hostCuProxy?.updateSender(this.sendToClient, true);
539
583
  this.hostFileProxy?.updateSender(this.sendToClient, true);
540
584
  }
541
585
  }
542
586
 
587
+ /**
588
+ * Restore host browser proxy availability only. Used for non-desktop
589
+ * interfaces (e.g. chrome-extension) that support host_browser but not
590
+ * the full desktop proxy set, so calling restoreProxyAvailability() would
591
+ * incorrectly re-enable bash/file/CU proxies that should stay disabled.
592
+ *
593
+ * Unlike `restoreProxyAvailability()`, this helper does NOT gate on
594
+ * `hasNoClient`. The chrome-extension interface is non-interactive (so
595
+ * `hasNoClient === true`), but it DOES have a connected client that can
596
+ * service `host_browser_request` events. Gating on `hasNoClient` would
597
+ * leave the just-constructed proxy unavailable and the only way to make
598
+ * it available would be to flip `hasNoClient` false, which would
599
+ * incorrectly enable host_bash/host_file/host_cu tool gating downstream.
600
+ *
601
+ * When `hostBrowserSenderOverride` is set, that function is used as the
602
+ * sender instead of `sendToClient`. This is required for the
603
+ * chrome-extension interface whose host_browser frames route through the
604
+ * ChromeExtensionRegistry WebSocket rather than the SSE hub: if the
605
+ * queue-drain path called this helper with `sendToClient`, the
606
+ * registry-routed sender established at turn-start would be clobbered by
607
+ * the SSE hub emitter and host_browser_request frames would stop
608
+ * reaching the extension.
609
+ *
610
+ * Callers must only invoke this when they know the current interface
611
+ * supports host_browser (see `supportsHostProxy(id, "host_browser")`).
612
+ */
613
+ restoreBrowserProxyAvailability(): void {
614
+ const sender = this.hostBrowserSenderOverride ?? this.sendToClient;
615
+ this.hostBrowserProxy?.updateSender(sender, true);
616
+ }
617
+
543
618
  setSubagentAllowedTools(tools: Set<string> | undefined): void {
544
619
  this.subagentAllowedTools = tools;
545
620
  }
@@ -548,6 +623,35 @@ export class Conversation {
548
623
  this.isSubagent = value;
549
624
  }
550
625
 
626
+ /**
627
+ * Prepend inherited parent messages into the in-memory message array so that
628
+ * the AgentLoop includes them in provider calls (enabling KV cache sharing).
629
+ *
630
+ * These messages are NOT persisted to the database — they exist only in
631
+ * memory. When the conversation is later read from DB via getMessages(),
632
+ * only the conversation's own persisted messages appear.
633
+ *
634
+ * Must be called before the first persistUserMessage() call — i.e. while
635
+ * `this.messages` is still empty.
636
+ */
637
+ injectInheritedContext(messages: Message[]): void {
638
+ if (this.messages.length !== 0) {
639
+ throw new Error(
640
+ "injectInheritedContext must be called before any messages have been added",
641
+ );
642
+ }
643
+ this.messages = [...messages];
644
+ this.contextWindowManager.nonPersistedPrefixCount = messages.length;
645
+ }
646
+
647
+ /**
648
+ * Return the system prompt string set at construction time (or its override).
649
+ * Fork consumers use this to pass the parent's system prompt to the fork.
650
+ */
651
+ getCurrentSystemPrompt(): string {
652
+ return this.systemPrompt;
653
+ }
654
+
551
655
  isProcessing(): boolean {
552
656
  return this.processing;
553
657
  }
@@ -570,6 +674,7 @@ export class Conversation {
570
674
  dispose(): void {
571
675
  approvalOverrides.clearMode(this.conversationId);
572
676
  this.hostBashProxy?.dispose();
677
+ this.hostBrowserProxy?.dispose();
573
678
  this.hostCuProxy?.dispose();
574
679
  this.hostFileProxy?.dispose();
575
680
  // CES client is owned by DaemonServer — just drop the reference.
@@ -672,12 +777,35 @@ export class Conversation {
672
777
  return;
673
778
  }
674
779
 
780
+ const hostAccessEnablePrompt =
781
+ this.prompter.isHostAccessEnablePrompt(requestId) ||
782
+ isConversationHostAccessEnablePrompt(
783
+ pendingInteractions.get(requestId)?.confirmationDetails,
784
+ );
785
+ const effectiveDecision =
786
+ hostAccessEnablePrompt && !isConversationHostAccessDecision(decision)
787
+ ? "deny"
788
+ : decision;
789
+
790
+ if (
791
+ hostAccessEnablePrompt &&
792
+ effectiveDecision === "allow" &&
793
+ !isConversationHostAccessEnabled(this.conversationId)
794
+ ) {
795
+ updateConversationHostAccess(this.conversationId, true);
796
+ this.sendToClient({
797
+ type: "conversation_host_access_updated",
798
+ conversationId: this.conversationId,
799
+ hostAccess: true,
800
+ });
801
+ }
802
+
675
803
  // Capture toolUseId before resolving (resolution deletes the pending entry)
676
804
  const toolUseId = this.prompter.getToolUseId(requestId);
677
805
 
678
806
  this.prompter.resolveConfirmation(
679
807
  requestId,
680
- decision,
808
+ effectiveDecision,
681
809
  selectedPattern,
682
810
  selectedScope,
683
811
  decisionContext,
@@ -691,7 +819,7 @@ export class Conversation {
691
819
  // so ALL callers (HTTP handlers, /v1/confirm, channel bridges) get
692
820
  // consistent events without duplicating emission logic.
693
821
  const resolvedState =
694
- decision === "deny" || decision === "always_deny"
822
+ effectiveDecision === "deny" || effectiveDecision === "always_deny"
695
823
  ? ("denied" as const)
696
824
  : ("approved" as const);
697
825
  this.emitConfirmationStateChanged({
@@ -735,7 +863,12 @@ export class Conversation {
735
863
  }
736
864
 
737
865
  // Cascade to other pending confirmations that match this decision
738
- this.cascadePendingApprovals(requestId, decision, selectedPattern);
866
+ this.cascadePendingApprovals(
867
+ requestId,
868
+ effectiveDecision,
869
+ selectedPattern,
870
+ hostAccessEnablePrompt,
871
+ );
739
872
  }
740
873
 
741
874
  /**
@@ -751,9 +884,14 @@ export class Conversation {
751
884
  primaryRequestId: string,
752
885
  decision: UserDecision,
753
886
  selectedPattern?: string,
887
+ hostAccessEnablePrompt = false,
754
888
  ): void {
755
889
  // Single-action decisions don't cascade
756
- if (decision === "allow" || decision === "deny") return;
890
+ if (decision === "allow" || decision === "deny") {
891
+ if (!hostAccessEnablePrompt || decision !== "allow") {
892
+ return;
893
+ }
894
+ }
757
895
 
758
896
  const pendingRequestIds = this.prompter.getPendingRequestIds();
759
897
  if (pendingRequestIds.length === 0) return;
@@ -766,6 +904,28 @@ export class Conversation {
766
904
  if (interaction.conversationId !== this.conversationId) continue;
767
905
  if (interaction.kind !== "confirmation") continue;
768
906
 
907
+ if (
908
+ hostAccessEnablePrompt &&
909
+ (this.prompter.isHostAccessEnablePrompt(candidateId) ||
910
+ isConversationHostAccessEnablePrompt(interaction.confirmationDetails))
911
+ ) {
912
+ // Enabling computer access is conversation-scoped, so sibling host
913
+ // prompts should resolve immediately once the first approval lands.
914
+ pendingInteractions.resolve(candidateId);
915
+ this.handleConfirmationResponse(
916
+ candidateId,
917
+ "allow",
918
+ undefined,
919
+ undefined,
920
+ undefined,
921
+ {
922
+ source: "system",
923
+ causedByRequestId: primaryRequestId,
924
+ },
925
+ );
926
+ continue;
927
+ }
928
+
769
929
  const cascadeResult = this.shouldCascade(
770
930
  decision,
771
931
  selectedPattern,
@@ -868,9 +1028,23 @@ export class Conversation {
868
1028
  this.hostBashProxy = proxy;
869
1029
  }
870
1030
 
871
- resolveHostFile(
1031
+ resolveHostBrowser(
872
1032
  requestId: string,
873
1033
  response: { content: string; isError: boolean },
1034
+ ): void {
1035
+ this.hostBrowserProxy?.resolve(requestId, response);
1036
+ }
1037
+
1038
+ setHostBrowserProxy(proxy: HostBrowserProxy | undefined): void {
1039
+ if (this.hostBrowserProxy && this.hostBrowserProxy !== proxy) {
1040
+ this.hostBrowserProxy.dispose();
1041
+ }
1042
+ this.hostBrowserProxy = proxy;
1043
+ }
1044
+
1045
+ resolveHostFile(
1046
+ requestId: string,
1047
+ response: { content: string; isError: boolean; imageData?: string },
874
1048
  ): void {
875
1049
  this.hostFileProxy?.resolve(requestId, response);
876
1050
  }
@@ -984,6 +1158,40 @@ export class Conversation {
984
1158
  this.transportHints = hints;
985
1159
  }
986
1160
 
1161
+ /**
1162
+ * Apply client-reported host environment (home dir, username) from
1163
+ * transport metadata onto the conversation. Only interfaces whose
1164
+ * interfaceId passes `supportsHostProxy()` contribute values — all other
1165
+ * interfaces (CLI, channels, iOS, chrome-extension) clear any previously
1166
+ * stored values so a conversation reused across interfaces doesn't leak
1167
+ * stale paths into later `<workspace>` blocks.
1168
+ *
1169
+ * Gating on `supportsHostProxy` (rather than a specific interface name)
1170
+ * keeps this in lock-step with the capability set defined in
1171
+ * `HostProxyInterfaceId` — adding a new host-capable client only requires
1172
+ * extending those two, not touching this method.
1173
+ *
1174
+ * Invalidates the cached workspace top-level block when values change so
1175
+ * the next render picks up the new host env.
1176
+ */
1177
+ applyHostEnvFromTransport(transport: ConversationTransportMetadata): void {
1178
+ const prevHomeDir = this.hostHomeDir;
1179
+ const prevUsername = this.hostUsername;
1180
+ if (isHostProxyTransport(transport)) {
1181
+ this.hostHomeDir = transport.hostHomeDir;
1182
+ this.hostUsername = transport.hostUsername;
1183
+ } else {
1184
+ this.hostHomeDir = undefined;
1185
+ this.hostUsername = undefined;
1186
+ }
1187
+ if (
1188
+ prevHomeDir !== this.hostHomeDir ||
1189
+ prevUsername !== this.hostUsername
1190
+ ) {
1191
+ this.workspaceTopLevelDirty = true;
1192
+ }
1193
+ }
1194
+
987
1195
  setAssistantId(assistantId: string | null): void {
988
1196
  this.assistantId = assistantId ?? undefined;
989
1197
  }
@@ -1026,6 +1234,16 @@ export class Conversation {
1026
1234
  return this.currentTurnInterfaceContext;
1027
1235
  }
1028
1236
 
1237
+ /**
1238
+ * Implements the `transportInterface` field of `SkillProjectionContext` so
1239
+ * that `isToolActiveForContext` can gate host tools by per-capability
1240
+ * `supportsHostProxy(transport, capability)`. Derived from the live turn
1241
+ * interface context so it tracks the connected client across turns.
1242
+ */
1243
+ get transportInterface(): InterfaceId | undefined {
1244
+ return this.currentTurnInterfaceContext?.userMessageInterface;
1245
+ }
1246
+
1029
1247
  async persistUserMessage(
1030
1248
  content: string,
1031
1249
  attachments: UserMessageAttachment[],