@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
@@ -79,12 +79,6 @@ export const PermissionsConfigSchema = z
79
79
  .describe(
80
80
  "Permission mode — 'strict' requires explicit approval for all operations, 'workspace' allows operations within the workspace",
81
81
  ),
82
- askBeforeActing: z
83
- .boolean({
84
- error: "permissions.askBeforeActing must be a boolean",
85
- })
86
- .default(true)
87
- .describe("Whether the assistant should check in before taking actions"),
88
82
  hostAccess: z
89
83
  .boolean({
90
84
  error: "permissions.hostAccess must be a boolean",
@@ -61,6 +61,11 @@ export const LinearOAuthServiceSchema = BaseServiceSchema.extend({
61
61
  });
62
62
  export type LinearOAuthService = z.infer<typeof LinearOAuthServiceSchema>;
63
63
 
64
+ export const GitHubOAuthServiceSchema = BaseServiceSchema.extend({
65
+ mode: ServiceModeSchema.default("your-own"),
66
+ });
67
+ export type GitHubOAuthService = z.infer<typeof GitHubOAuthServiceSchema>;
68
+
64
69
  export const ServicesSchema = z.object({
65
70
  inference: InferenceServiceSchema.default(InferenceServiceSchema.parse({})),
66
71
  "image-generation": ImageGenerationServiceSchema.default(
@@ -78,5 +83,8 @@ export const ServicesSchema = z.object({
78
83
  "linear-oauth": LinearOAuthServiceSchema.default(
79
84
  LinearOAuthServiceSchema.parse({}),
80
85
  ),
86
+ "github-oauth": GitHubOAuthServiceSchema.default(
87
+ GitHubOAuthServiceSchema.parse({}),
88
+ ),
81
89
  });
82
90
  export type Services = z.infer<typeof ServicesSchema>;
@@ -27,7 +27,6 @@ export type {
27
27
  PermissionsConfig,
28
28
  QdrantConfig,
29
29
  RateLimitConfig,
30
- SandboxConfig,
31
30
  SecretDetectionConfig,
32
31
  ServiceMode,
33
32
  Services,
@@ -0,0 +1,176 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdirSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+
5
+ import type {
6
+ ContentBlock,
7
+ Message,
8
+ ToolResultContent,
9
+ ToolUseContent,
10
+ } from "../providers/types.js";
11
+
12
+ /** Minimum content length (chars) before a tool result is eligible for truncation. ~2000 tokens at 4 chars/token. */
13
+ export const THRESHOLD_CHARS = 8_000;
14
+
15
+ /** Target size (chars) for the truncated stub. ~300 tokens at 4 chars/token. */
16
+ export const TARGET_CHARS = 1_200;
17
+
18
+ /** Subdirectory name under the conversation directory for saved full results. */
19
+ export const TOOL_RESULT_DIR = ".tool-results";
20
+
21
+ /** Marker used to detect already-truncated results (idempotency guard). */
22
+ export const TRUNCATION_MARKER = "\u2014 full result:";
23
+
24
+ /**
25
+ * Deterministic file path for a tool result's full content on disk.
26
+ * Uses the first 12 hex chars of the SHA-256 of the tool_use_id.
27
+ */
28
+ export function getToolResultFilePath(
29
+ conversationDir: string,
30
+ toolUseId: string,
31
+ ): string {
32
+ const hash = createHash("sha256").update(toolUseId).digest("hex").slice(0, 12);
33
+ return join(conversationDir, TOOL_RESULT_DIR, `${hash}.txt`);
34
+ }
35
+
36
+ /**
37
+ * Build the truncated stub that replaces a large tool result in context.
38
+ * Preserves the first and last halves of TARGET_CHARS, with a middle marker
39
+ * indicating how many tokens were omitted and where to find the full result.
40
+ */
41
+ export function buildTruncatedContent(
42
+ original: string,
43
+ filePath: string,
44
+ ): string {
45
+ const half = Math.floor(TARGET_CHARS / 2);
46
+ const prefix = original.slice(0, half);
47
+ const suffix = original.slice(-half);
48
+ const omittedChars = original.length - TARGET_CHARS;
49
+ const estimatedTokens = Math.round(omittedChars / 4);
50
+ return `${prefix}\n\n...(${estimatedTokens} tokens omitted ${TRUNCATION_MARKER} ${filePath})\n\n${suffix}`;
51
+ }
52
+
53
+ /**
54
+ * Walk all messages and truncate tool results that exceed `THRESHOLD_CHARS`.
55
+ *
56
+ * For each eligible result:
57
+ * - The full content is persisted to a deterministic file path on disk.
58
+ * - The in-context content is replaced with a prefix/suffix stub.
59
+ *
60
+ * Results are skipped if they are below threshold, are error results,
61
+ * or have already been truncated (contain `TRUNCATION_MARKER`).
62
+ *
63
+ * Returns a shallow-copied messages array (only modified messages are cloned)
64
+ * and the count of results that were truncated.
65
+ */
66
+ export function postTurnTruncateToolResults(
67
+ messages: Message[],
68
+ options: { conversationDir: string },
69
+ ): { messages: Message[]; truncatedCount: number } {
70
+ let truncatedCount = 0;
71
+
72
+ const mapped = messages.map((msg) => {
73
+ let changed = false;
74
+ const nextContent: ContentBlock[] = msg.content.map((block) => {
75
+ if (block.type !== "tool_result") return block;
76
+ const tr = block as ToolResultContent;
77
+
78
+ // Skip short results.
79
+ if (tr.content.length <= THRESHOLD_CHARS) return block;
80
+
81
+ // Skip error results.
82
+ if (tr.is_error) return block;
83
+
84
+ // Skip already-truncated results (idempotency).
85
+ if (tr.content.includes(TRUNCATION_MARKER)) return block;
86
+
87
+ const filePath = getToolResultFilePath(
88
+ options.conversationDir,
89
+ tr.tool_use_id,
90
+ );
91
+
92
+ // Persist full content to disk.
93
+ mkdirSync(join(options.conversationDir, TOOL_RESULT_DIR), {
94
+ recursive: true,
95
+ });
96
+ writeFileSync(filePath, tr.content, "utf-8");
97
+
98
+ changed = true;
99
+ truncatedCount++;
100
+ return {
101
+ ...tr,
102
+ content: buildTruncatedContent(tr.content, filePath),
103
+ } as ContentBlock;
104
+ });
105
+
106
+ return changed ? { ...msg, content: nextContent } : msg;
107
+ });
108
+
109
+ return { messages: truncatedCount > 0 ? mapped : messages, truncatedCount };
110
+ }
111
+
112
+ /** Stub that replaces a re-read of a saved tool result to avoid context duplication. */
113
+ export const REREAD_STUB =
114
+ "(Re-read of saved tool result — original context is preserved above)";
115
+
116
+ /**
117
+ * Deduplicate re-reads of saved tool results.
118
+ *
119
+ * When `postTurnTruncateToolResults` truncates a large result, it saves the full
120
+ * content to a `.tool-results/` file. If the model later calls `file_read` on that
121
+ * saved file, the result is a second copy of content whose truncated prefix/suffix
122
+ * is already in context. This function detects those re-reads and replaces their
123
+ * tool_result content with a short stub to avoid duplication.
124
+ */
125
+ export function derefToolResultReReads(messages: Message[]): {
126
+ messages: Message[];
127
+ dereferencedCount: number;
128
+ } {
129
+ // Build a set of tool_use_ids that are file_read calls targeting .tool-results/ paths.
130
+ const reReadToolUseIds = new Set<string>();
131
+
132
+ for (const msg of messages) {
133
+ if (msg.role !== "assistant") continue;
134
+ for (const block of msg.content) {
135
+ if (block.type !== "tool_use") continue;
136
+ const tu = block as ToolUseContent;
137
+ if (tu.name !== "file_read") continue;
138
+ const filePath = tu.input.path ?? tu.input.file_path;
139
+ if (typeof filePath !== "string") continue;
140
+ if (filePath.includes(`/${TOOL_RESULT_DIR}/`)) {
141
+ reReadToolUseIds.add(tu.id);
142
+ }
143
+ }
144
+ }
145
+
146
+ if (reReadToolUseIds.size === 0) {
147
+ return { messages, dereferencedCount: 0 };
148
+ }
149
+
150
+ let dereferencedCount = 0;
151
+
152
+ const mapped = messages.map((msg) => {
153
+ if (msg.role !== "user") return msg;
154
+
155
+ let changed = false;
156
+ const nextContent: ContentBlock[] = msg.content.map((block) => {
157
+ if (block.type !== "tool_result") return block;
158
+ const tr = block as ToolResultContent;
159
+ if (!reReadToolUseIds.has(tr.tool_use_id)) return block;
160
+
161
+ // Skip error results — preserve diagnostics (e.g. file not found).
162
+ if (tr.is_error) return block;
163
+
164
+ changed = true;
165
+ dereferencedCount++;
166
+ return { ...tr, content: REREAD_STUB } as ContentBlock;
167
+ });
168
+
169
+ return changed ? { ...msg, content: nextContent } : msg;
170
+ });
171
+
172
+ return {
173
+ messages: dereferencedCount > 0 ? mapped : messages,
174
+ dereferencedCount,
175
+ };
176
+ }
@@ -102,6 +102,14 @@ export class ContextWindowManager {
102
102
  private readonly _systemPrompt: string | (() => string);
103
103
  private readonly config: ContextWindowConfig;
104
104
  private readonly toolTokenBudget: number;
105
+ /**
106
+ * Number of leading messages that are non-persisted (injected inherited
107
+ * context from a parent conversation). `countPersistedMessages` subtracts
108
+ * this so `compactedPersistedMessages` only reflects DB-backed messages.
109
+ * Set by `Conversation.injectInheritedContext` and consumed (decremented)
110
+ * after a successful compaction pass.
111
+ */
112
+ nonPersistedPrefixCount = 0;
105
113
  /**
106
114
  * Cached resolved system prompt. Lazily populated on first access via the
107
115
  * `systemPrompt` getter and cleared after each compaction pass so the next
@@ -305,8 +313,12 @@ export class ContextWindowManager {
305
313
  };
306
314
  }
307
315
 
316
+ const injectedInCompactable = Math.min(
317
+ Math.max(0, this.nonPersistedPrefixCount - summaryOffset),
318
+ compactableMessages.length,
319
+ );
308
320
  const compactedPersistedMessages =
309
- countPersistedMessages(compactableMessages);
321
+ countPersistedMessages(compactableMessages) - injectedInCompactable;
310
322
  const rawProjectedMessages = [
311
323
  createContextSummaryMessage(existingSummary ?? "Projected summary"),
312
324
  ...messages.slice(keepPlan.keepFromIndex),
@@ -452,6 +464,12 @@ export class ContextWindowManager {
452
464
  toolTokenBudget: this.toolTokenBudget,
453
465
  },
454
466
  );
467
+ // Consume the injected prefix messages that were compacted away.
468
+ this.nonPersistedPrefixCount = Math.max(
469
+ 0,
470
+ this.nonPersistedPrefixCount - injectedInCompactable,
471
+ );
472
+
455
473
  log.info(
456
474
  {
457
475
  previousEstimatedInputTokens,
@@ -21,6 +21,7 @@ import type {
21
21
 
22
22
  import type { PermissionPrompter } from "../permissions/prompter.js";
23
23
  import type { UserDecision } from "../permissions/types.js";
24
+ import { isPermissionControlsV2Enabled } from "../permissions/v2-consent-policy.js";
24
25
  import { getLogger } from "../util/logger.js";
25
26
  import type { CesClient } from "./client.js";
26
27
 
@@ -173,13 +174,7 @@ export async function bridgeCesApproval(
173
174
  signal?: AbortSignal;
174
175
  },
175
176
  ): Promise<CesApprovalBridgeResult> {
176
- const {
177
- proposal,
178
- renderedProposal,
179
- proposalHash,
180
- sessionId,
181
- conversationId,
182
- } = approval;
177
+ const { proposal, renderedProposal, proposalHash, sessionId } = approval;
183
178
 
184
179
  // Non-interactive sessions have no client to respond — fail closed.
185
180
  if (options?.isInteractive === false) {
@@ -194,6 +189,24 @@ export async function bridgeCesApproval(
194
189
  return { outcome: "denied", userDecision: "deny" };
195
190
  }
196
191
 
192
+ if (isPermissionControlsV2Enabled()) {
193
+ log.info(
194
+ {
195
+ event: "ces_approval_bridge_v2_suppressed",
196
+ proposalHash,
197
+ sessionId,
198
+ },
199
+ "CES approval request auto-approved without deterministic prompt under v2",
200
+ );
201
+ const v2Decision = mapUserDecisionToCesDecision("allow");
202
+ return recordCesGrant({
203
+ approval,
204
+ cesClient,
205
+ decision: v2Decision,
206
+ reason: "permission_controls_v2_auto_allow",
207
+ });
208
+ }
209
+
197
210
  // Build the tool name and input for the confirmation prompt. The tool
198
211
  // name uses a `ces:` prefix so the client can distinguish CES approval
199
212
  // requests from regular tool confirmation prompts.
@@ -262,8 +275,29 @@ export async function bridgeCesApproval(
262
275
  `CES approval bridge: guardian decision is "${cesDecision.grantDecision}"`,
263
276
  );
264
277
 
265
- if (cesDecision.grantDecision === "denied") {
266
- return { outcome: "denied", userDecision: response.decision };
278
+ return recordCesGrant({
279
+ approval,
280
+ cesClient,
281
+ decision: cesDecision,
282
+ reason: response.decisionContext,
283
+ });
284
+ }
285
+
286
+ async function recordCesGrant({
287
+ approval,
288
+ cesClient,
289
+ decision,
290
+ reason,
291
+ }: {
292
+ approval: ApprovalRequired;
293
+ cesClient: CesClient;
294
+ decision: CesApprovalDecision;
295
+ reason?: string;
296
+ }): Promise<CesApprovalBridgeResult> {
297
+ const { proposal, proposalHash, sessionId, conversationId } = approval;
298
+
299
+ if (decision.grantDecision === "denied") {
300
+ return { outcome: "denied", userDecision: decision.userDecision };
267
301
  }
268
302
 
269
303
  // Commit the approved grant to CES via record_grant RPC.
@@ -274,12 +308,12 @@ export async function bridgeCesApproval(
274
308
  decision: {
275
309
  proposal,
276
310
  proposalHash,
277
- decision: cesDecision.grantDecision,
311
+ decision: decision.grantDecision,
278
312
  decidedBy: "guardian",
279
313
  decidedAt: new Date().toISOString(),
280
- reason: response.decisionContext,
281
- ttl: cesDecision.ttl,
282
- grantType: cesDecision.grantType,
314
+ reason,
315
+ ttl: decision.ttl,
316
+ grantType: decision.grantType,
283
317
  },
284
318
  sessionId,
285
319
  conversationId,
@@ -323,7 +357,7 @@ export async function bridgeCesApproval(
323
357
  proposalHash,
324
358
  sessionId,
325
359
  grantId,
326
- userDecision: response.decision,
360
+ userDecision: decision.userDecision,
327
361
  },
328
362
  "CES approval bridge: grant recorded successfully",
329
363
  );
@@ -331,7 +365,7 @@ export async function bridgeCesApproval(
331
365
  return {
332
366
  outcome: "approved",
333
367
  grantId,
334
- userDecision: response.decision,
368
+ userDecision: decision.userDecision,
335
369
  };
336
370
  } catch (err) {
337
371
  const msg = err instanceof Error ? err.message : String(err);
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Tests for `isToolActiveForContext` host-tool capability gating.
3
+ *
4
+ * Two scenarios are verified:
5
+ * - chrome-extension is its own executor and is exempt from the hasNoClient
6
+ * gate (the extension's own popup UI gates commands; there is no SSE
7
+ * interactive approval channel, and chrome-extension turns intentionally
8
+ * run with `hasNoClient: true` because chrome-extension is not in
9
+ * `INTERACTIVE_INTERFACES`).
10
+ * - macos still requires a connected SSE client for interactive approval, so
11
+ * `hasNoClient: true` continues to deny all host tools on macos.
12
+ *
13
+ * The per-capability check (`supportsHostProxy(transport, capability)`) runs
14
+ * first and is authoritative for structural support, so host_bash and
15
+ * host_file_* are filtered out for chrome-extension regardless of the
16
+ * hasNoClient flag.
17
+ */
18
+
19
+ import { describe, expect, test } from "bun:test";
20
+
21
+ import type { SkillProjectionCache } from "../conversation-skill-tools.js";
22
+ import {
23
+ HOST_TOOL_NAMES,
24
+ HOST_TOOL_TO_CAPABILITY,
25
+ isToolActiveForContext,
26
+ type SkillProjectionContext,
27
+ } from "../conversation-tool-setup.js";
28
+
29
+ function makeCtx(
30
+ overrides: Partial<SkillProjectionContext> = {},
31
+ ): SkillProjectionContext {
32
+ return {
33
+ skillProjectionState: new Map(),
34
+ skillProjectionCache: {} as SkillProjectionCache,
35
+ coreToolNames: new Set<string>(),
36
+ toolsDisabledDepth: 0,
37
+ ...overrides,
38
+ };
39
+ }
40
+
41
+ describe("isToolActiveForContext — host tool capability gating", () => {
42
+ // macOS transport: SSE-based interactive approval required.
43
+ test("host_bash is active for macOS with a connected client", () => {
44
+ expect(
45
+ isToolActiveForContext(
46
+ "host_bash",
47
+ makeCtx({ hasNoClient: false, transportInterface: "macos" }),
48
+ ),
49
+ ).toBe(true);
50
+ });
51
+
52
+ test("host_bash is NOT active for macOS when hasNoClient is true (security invariant)", () => {
53
+ // macOS uses an SSE-based interactive approval channel. Without a
54
+ // connected client the guardian auto-approve path could execute host
55
+ // commands unattended, so host tools must be denied.
56
+ expect(
57
+ isToolActiveForContext(
58
+ "host_bash",
59
+ makeCtx({ hasNoClient: true, transportInterface: "macos" }),
60
+ ),
61
+ ).toBe(false);
62
+ });
63
+
64
+ test("host_file_read is NOT active for macOS when hasNoClient is true", () => {
65
+ expect(
66
+ isToolActiveForContext(
67
+ "host_file_read",
68
+ makeCtx({ hasNoClient: true, transportInterface: "macos" }),
69
+ ),
70
+ ).toBe(false);
71
+ });
72
+
73
+ test("host_browser is active for macOS with a connected client", () => {
74
+ expect(
75
+ isToolActiveForContext(
76
+ "host_browser",
77
+ makeCtx({ hasNoClient: false, transportInterface: "macos" }),
78
+ ),
79
+ ).toBe(true);
80
+ });
81
+
82
+ test("host_browser is NOT active for macOS when hasNoClient is true", () => {
83
+ // macOS requires a client for any host tool — the SSE interactive
84
+ // approval channel must be available regardless of capability.
85
+ expect(
86
+ isToolActiveForContext(
87
+ "host_browser",
88
+ makeCtx({ hasNoClient: true, transportInterface: "macos" }),
89
+ ),
90
+ ).toBe(false);
91
+ });
92
+
93
+ // chrome-extension transport: the extension is its own executor.
94
+ test("host_browser is active for chrome-extension even when hasNoClient is true", () => {
95
+ // chrome-extension turns run with `hasNoClient: true` by design because
96
+ // chrome-extension is not in `INTERACTIVE_INTERFACES` — it is not an
97
+ // SSE interactive channel. The extension gates host_browser commands
98
+ // via its own popup UI, so the hasNoClient gate must not filter
99
+ // host_browser out for chrome-extension transports.
100
+ expect(
101
+ isToolActiveForContext(
102
+ "host_browser",
103
+ makeCtx({
104
+ hasNoClient: true,
105
+ transportInterface: "chrome-extension",
106
+ }),
107
+ ),
108
+ ).toBe(true);
109
+ });
110
+
111
+ test("host_browser is active for chrome-extension when hasNoClient is false", () => {
112
+ expect(
113
+ isToolActiveForContext(
114
+ "host_browser",
115
+ makeCtx({
116
+ hasNoClient: false,
117
+ transportInterface: "chrome-extension",
118
+ }),
119
+ ),
120
+ ).toBe(true);
121
+ });
122
+
123
+ test("host_bash is NOT active for chrome-extension even when hasNoClient is true", () => {
124
+ // The per-capability check runs first and is authoritative: chrome-extension
125
+ // only supports `host_browser`, so `host_bash` must be filtered out.
126
+ expect(
127
+ isToolActiveForContext(
128
+ "host_bash",
129
+ makeCtx({
130
+ hasNoClient: true,
131
+ transportInterface: "chrome-extension",
132
+ }),
133
+ ),
134
+ ).toBe(false);
135
+ });
136
+
137
+ test("host_file_read is NOT active for chrome-extension when hasNoClient is true", () => {
138
+ expect(
139
+ isToolActiveForContext(
140
+ "host_file_read",
141
+ makeCtx({
142
+ hasNoClient: true,
143
+ transportInterface: "chrome-extension",
144
+ }),
145
+ ),
146
+ ).toBe(false);
147
+ });
148
+
149
+ // Backwards-compat fallback: no transport plumbed through.
150
+ test("host_bash falls back to hasNoClient gate when transport is undefined (client connected)", () => {
151
+ // Without a transport interface we cannot run the per-capability check,
152
+ // so we fall back to the coarse-grained `hasNoClient` behavior.
153
+ expect(
154
+ isToolActiveForContext(
155
+ "host_bash",
156
+ makeCtx({ hasNoClient: false, transportInterface: undefined }),
157
+ ),
158
+ ).toBe(true);
159
+ });
160
+
161
+ test("host_bash falls back to hasNoClient gate when transport is undefined (no client)", () => {
162
+ expect(
163
+ isToolActiveForContext(
164
+ "host_bash",
165
+ makeCtx({ hasNoClient: true, transportInterface: undefined }),
166
+ ),
167
+ ).toBe(false);
168
+ });
169
+ });
170
+
171
+ describe("HOST_TOOL_NAMES derivation", () => {
172
+ test("HOST_TOOL_NAMES is derived from HOST_TOOL_TO_CAPABILITY", () => {
173
+ // Sanity check: every tool in the names set has a capability mapping.
174
+ // This is structurally enforced by the code (HOST_TOOL_NAMES is built
175
+ // from HOST_TOOL_TO_CAPABILITY.keys()), but we test it to make the
176
+ // invariant visible to readers and to catch any regression that
177
+ // splits the two collections back apart.
178
+ for (const name of HOST_TOOL_NAMES) {
179
+ expect(HOST_TOOL_TO_CAPABILITY.has(name)).toBe(true);
180
+ }
181
+ // Cardinality check: the two collections must have the same size so a
182
+ // future addition to HOST_TOOL_NAMES without a matching capability entry
183
+ // (or vice versa) would fail.
184
+ expect(HOST_TOOL_NAMES.size).toBe(HOST_TOOL_TO_CAPABILITY.size);
185
+ });
186
+ });
@@ -24,6 +24,20 @@ const APP_REFRESH_DEBOUNCE_MS = 500;
24
24
 
25
25
  export type AppSourceChangeCallback = (appId: string) => void;
26
26
 
27
+ /**
28
+ * Module-level callback so tool-side-effects can ensure the watcher starts
29
+ * after the apps directory is created (e.g. on first app_create).
30
+ */
31
+ let ensureWatcherStarted: (() => void) | null = null;
32
+
33
+ export function setEnsureAppSourceWatcher(fn: () => void): void {
34
+ ensureWatcherStarted = fn;
35
+ }
36
+
37
+ export function ensureAppSourceWatcher(): void {
38
+ ensureWatcherStarted?.();
39
+ }
40
+
27
41
  /**
28
42
  * Resolve app ID from a relative path within the apps directory.
29
43
  * Returns null if the path is not an app source file (e.g. dist/, records/).
@@ -48,12 +62,29 @@ function resolveAppIdFromRelPath(relPath: string): string | null {
48
62
 
49
63
  export class AppSourceWatcher {
50
64
  private watcher: FSWatcher | null = null;
65
+ private onChange: AppSourceChangeCallback | null = null;
51
66
  private debouncer = new DebouncerMap({
52
67
  defaultDelayMs: APP_REFRESH_DEBOUNCE_MS,
53
68
  maxEntries: 50,
54
69
  });
55
70
 
56
71
  start(onChange: AppSourceChangeCallback): void {
72
+ this.onChange = onChange;
73
+ this.tryWatch();
74
+ }
75
+
76
+ /**
77
+ * Ensure the watcher is running. Call after app creation so the watcher
78
+ * starts if the apps directory was created after daemon startup.
79
+ */
80
+ ensureStarted(): void {
81
+ if (this.watcher || !this.onChange) return;
82
+ this.tryWatch();
83
+ }
84
+
85
+ private tryWatch(): void {
86
+ if (this.watcher) return;
87
+
57
88
  let appsDir: string;
58
89
  try {
59
90
  appsDir = getAppsDir();
@@ -67,6 +98,9 @@ export class AppSourceWatcher {
67
98
  return;
68
99
  }
69
100
 
101
+ const onChange = this.onChange;
102
+ if (!onChange) return;
103
+
70
104
  try {
71
105
  this.watcher = watch(appsDir, { recursive: true }, (_eventType, filename) => {
72
106
  if (!filename) return;
@@ -78,6 +112,7 @@ export class AppSourceWatcher {
78
112
  onChange(appId);
79
113
  });
80
114
  });
115
+ log.info("App source watcher started");
81
116
  } catch (err) {
82
117
  log.warn({ err }, "Failed to watch apps directory; source watching disabled");
83
118
  }
@@ -1,5 +1,6 @@
1
1
  import type { PermissionPrompter } from "../permissions/prompter.js";
2
2
  import { isAllowDecision } from "../permissions/types.js";
3
+ import { isPermissionControlsV2Enabled } from "../permissions/v2-consent-policy.js";
3
4
 
4
5
  /**
5
6
  * Reserved pseudo tool name used for context overflow compression approval
@@ -26,6 +27,10 @@ export async function requestCompressionApproval(
26
27
  prompter: PermissionPrompter,
27
28
  opts?: { signal?: AbortSignal },
28
29
  ): Promise<CompressionApprovalResult> {
30
+ if (isPermissionControlsV2Enabled()) {
31
+ return { approved: true };
32
+ }
33
+
29
34
  const result = await prompter.prompt(
30
35
  CONTEXT_OVERFLOW_TOOL_NAME,
31
36
  {
@@ -932,10 +932,25 @@ export async function dispatchAgentEvent(
932
932
  "assistant_turn",
933
933
  deps.reqId,
934
934
  );
935
+
936
+ // Format web search results into a human-readable string for the client.
937
+ let resultText = "";
938
+ if (Array.isArray(event.content) && event.content.length > 0) {
939
+ resultText = (event.content as unknown[])
940
+ .filter(
941
+ (r): r is { type: string; title: string; url: string } =>
942
+ typeof r === "object" &&
943
+ r != null &&
944
+ (r as { type?: string }).type === "web_search_result",
945
+ )
946
+ .map((r) => `${r.title}\n${r.url}`)
947
+ .join("\n\n");
948
+ }
949
+
935
950
  deps.onEvent({
936
951
  type: "tool_result",
937
- toolName: "",
938
- result: "",
952
+ toolName: "web_search",
953
+ result: resultText,
939
954
  isError: event.isError,
940
955
  conversationId: deps.ctx.conversationId,
941
956
  toolUseId: event.toolUseId,