@vellumai/assistant 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (463) hide show
  1. package/bun.lock +40 -40
  2. package/bunfig.toml +3 -0
  3. package/docker-entrypoint.sh +12 -2
  4. package/docs/architecture/memory.md +1 -1
  5. package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
  6. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
  7. package/openapi.yaml +184 -69
  8. package/package.json +41 -41
  9. package/scripts/generate-openapi.ts +1 -2
  10. package/src/__tests__/acp-session.test.ts +43 -0
  11. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
  12. package/src/__tests__/app-executors.test.ts +1 -0
  13. package/src/__tests__/app-source-watcher.test.ts +37 -11
  14. package/src/__tests__/approval-routes-http.test.ts +178 -1
  15. package/src/__tests__/assistant-event-hub.test.ts +30 -0
  16. package/src/__tests__/browser-fill-credential.test.ts +229 -94
  17. package/src/__tests__/browser-manager.test.ts +40 -27
  18. package/src/__tests__/catalog-files.test.ts +862 -0
  19. package/src/__tests__/channel-approvals.test.ts +53 -0
  20. package/src/__tests__/checker.test.ts +104 -170
  21. package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
  22. package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
  23. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  24. package/src/__tests__/config-schema.test.ts +125 -48
  25. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
  26. package/src/__tests__/context-overflow-approval.test.ts +21 -6
  27. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  28. package/src/__tests__/conversation-agent-loop.test.ts +1 -1
  29. package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
  30. package/src/__tests__/conversation-attachments.test.ts +80 -4
  31. package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
  32. package/src/__tests__/conversation-directories-parse.test.ts +105 -0
  33. package/src/__tests__/conversation-fork-crud.test.ts +17 -0
  34. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  35. package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
  36. package/src/__tests__/conversation-inject-context.test.ts +103 -0
  37. package/src/__tests__/conversation-queue.test.ts +45 -2
  38. package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
  39. package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
  40. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  41. package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
  42. package/src/__tests__/conversation-starter-routes.test.ts +126 -0
  43. package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
  44. package/src/__tests__/conversation-store.test.ts +195 -0
  45. package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
  46. package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -3
  47. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  48. package/src/__tests__/credential-vault-unit.test.ts +4 -4
  49. package/src/__tests__/credential-vault.test.ts +152 -13
  50. package/src/__tests__/credentials-cli.test.ts +2 -2
  51. package/src/__tests__/date-context.test.ts +4 -4
  52. package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
  53. package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
  54. package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
  55. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  56. package/src/__tests__/gemini-provider.test.ts +2 -2
  57. package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
  58. package/src/__tests__/headless-browser-interactions.test.ts +707 -371
  59. package/src/__tests__/headless-browser-navigate.test.ts +389 -47
  60. package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
  61. package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
  62. package/src/__tests__/host-bash-proxy.test.ts +150 -1
  63. package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
  64. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
  65. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
  66. package/src/__tests__/host-browser-event-routes.test.ts +350 -0
  67. package/src/__tests__/host-browser-proxy.test.ts +444 -0
  68. package/src/__tests__/host-browser-routes.test.ts +198 -0
  69. package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
  70. package/src/__tests__/host-cu-proxy.test.ts +171 -1
  71. package/src/__tests__/host-file-proxy.test.ts +185 -1
  72. package/src/__tests__/host-file-read-tool.test.ts +52 -0
  73. package/src/__tests__/host-proxy-interface.test.ts +165 -0
  74. package/src/__tests__/host-shell-tool.test.ts +1 -11
  75. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  76. package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
  77. package/src/__tests__/inline-command-runner.test.ts +7 -5
  78. package/src/__tests__/integration-status.test.ts +6 -7
  79. package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
  80. package/src/__tests__/log-export-workspace.test.ts +190 -0
  81. package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
  82. package/src/__tests__/mcp-client-auth.test.ts +40 -4
  83. package/src/__tests__/mcp-health-check.test.ts +10 -3
  84. package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
  85. package/src/__tests__/migration-export-http.test.ts +61 -2
  86. package/src/__tests__/migration-export-streaming.test.ts +66 -0
  87. package/src/__tests__/migration-import-commit-http.test.ts +101 -1
  88. package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
  89. package/src/__tests__/navigate-settings-tab.test.ts +14 -1
  90. package/src/__tests__/notification-broadcaster.test.ts +65 -0
  91. package/src/__tests__/oauth-apps-routes.test.ts +17 -12
  92. package/src/__tests__/oauth-cli.test.ts +707 -60
  93. package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
  94. package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
  95. package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
  96. package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
  97. package/src/__tests__/oauth-providers-routes.test.ts +50 -14
  98. package/src/__tests__/oauth-store.test.ts +1386 -182
  99. package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
  100. package/src/__tests__/onboarding-template-contract.test.ts +74 -55
  101. package/src/__tests__/openai-provider.test.ts +2 -2
  102. package/src/__tests__/outlook-categories.test.ts +1 -1
  103. package/src/__tests__/outlook-client-automation.test.ts +1 -1
  104. package/src/__tests__/outlook-compose-tools.test.ts +1 -1
  105. package/src/__tests__/outlook-email-watcher.test.ts +1 -1
  106. package/src/__tests__/outlook-follow-up.test.ts +1 -1
  107. package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
  108. package/src/__tests__/outlook-trash.test.ts +1 -1
  109. package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
  110. package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
  111. package/src/__tests__/permission-mode.test.ts +28 -56
  112. package/src/__tests__/pkb-autoinject.test.ts +96 -0
  113. package/src/__tests__/platform-callback-registration.test.ts +19 -0
  114. package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
  115. package/src/__tests__/proxy-approval-callback.test.ts +18 -0
  116. package/src/__tests__/require-fresh-approval.test.ts +40 -3
  117. package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
  118. package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
  119. package/src/__tests__/schedule-routes.test.ts +162 -0
  120. package/src/__tests__/secret-detection-handler.test.ts +84 -0
  121. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  122. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  123. package/src/__tests__/set-permission-mode.test.ts +13 -250
  124. package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
  125. package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
  126. package/src/__tests__/slack-channel-config.test.ts +12 -15
  127. package/src/__tests__/subagent-detail.test.ts +44 -2
  128. package/src/__tests__/subagent-disposal.test.ts +1 -0
  129. package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
  130. package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
  131. package/src/__tests__/subagent-manager-notify.test.ts +1 -0
  132. package/src/__tests__/subagent-notify-parent.test.ts +1 -0
  133. package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
  134. package/src/__tests__/subagent-tools.test.ts +1 -0
  135. package/src/__tests__/subagent-types.test.ts +1 -0
  136. package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
  137. package/src/__tests__/system-prompt.test.ts +72 -1
  138. package/src/__tests__/task-scheduler.test.ts +32 -6
  139. package/src/__tests__/telegram-config.test.ts +10 -13
  140. package/src/__tests__/terminal-sandbox.test.ts +1 -1
  141. package/src/__tests__/terminal-tools.test.ts +11 -5
  142. package/src/__tests__/test-preload.ts +14 -0
  143. package/src/__tests__/tool-approval-handler.test.ts +73 -0
  144. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
  145. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
  146. package/src/__tests__/tool-executor.test.ts +0 -1
  147. package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
  148. package/src/__tests__/top-level-renderer.test.ts +73 -1
  149. package/src/__tests__/transport-hints-queue.test.ts +62 -0
  150. package/src/__tests__/trust-store.test.ts +4 -4
  151. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
  152. package/src/__tests__/v2-consent-policy.test.ts +103 -0
  153. package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
  154. package/src/__tests__/workspace-policy.test.ts +2 -7
  155. package/src/acp/client-handler.ts +30 -4
  156. package/src/agent/loop.ts +12 -35
  157. package/src/approvals/guardian-request-resolvers.ts +21 -15
  158. package/src/browser-session/__tests__/manager.test.ts +297 -0
  159. package/src/browser-session/backends/cdp-inspect.ts +30 -0
  160. package/src/browser-session/backends/extension.ts +26 -0
  161. package/src/browser-session/backends/local.ts +24 -0
  162. package/src/browser-session/events.ts +164 -0
  163. package/src/browser-session/index.ts +27 -0
  164. package/src/browser-session/manager.ts +159 -0
  165. package/src/browser-session/types.ts +28 -0
  166. package/src/channels/__tests__/types.test.ts +134 -0
  167. package/src/channels/types.ts +55 -0
  168. package/src/cli/__tests__/run-assistant-command.ts +34 -7
  169. package/src/cli/__tests__/unknown-command.test.ts +33 -0
  170. package/src/cli/commands/browser-relay.ts +339 -409
  171. package/src/cli/commands/credentials.ts +3 -3
  172. package/src/cli/commands/default-action.ts +68 -1
  173. package/src/cli/commands/email.ts +18 -13
  174. package/src/cli/commands/mcp.ts +16 -4
  175. package/src/cli/commands/oauth/__tests__/connect.test.ts +68 -41
  176. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
  177. package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
  178. package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
  179. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
  180. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
  181. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
  182. package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
  183. package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
  184. package/src/cli/commands/oauth/apps.ts +7 -4
  185. package/src/cli/commands/oauth/connect.ts +16 -2
  186. package/src/cli/commands/oauth/disconnect.ts +1 -1
  187. package/src/cli/commands/oauth/providers.ts +200 -36
  188. package/src/cli/commands/oauth/shared.ts +5 -5
  189. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
  190. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  191. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  192. package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
  193. package/src/cli/commands/platform/index.ts +107 -10
  194. package/src/cli/commands/usage.ts +10 -9
  195. package/src/cli/lib/daemon-credential-client.ts +4 -0
  196. package/src/cli/program.ts +10 -3
  197. package/src/config/assistant-feature-flags.ts +59 -55
  198. package/src/config/bundled-skills/app-builder/SKILL.md +33 -173
  199. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
  200. package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
  201. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
  202. package/src/config/bundled-skills/contacts/SKILL.md +3 -0
  203. package/src/config/bundled-skills/document/SKILL.md +4 -0
  204. package/src/config/bundled-skills/gmail/SKILL.md +12 -7
  205. package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
  206. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
  207. package/src/config/bundled-skills/outlook/SKILL.md +7 -0
  208. package/src/config/bundled-skills/settings/TOOLS.json +1 -1
  209. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
  210. package/src/config/bundled-skills/subagent/SKILL.md +21 -0
  211. package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
  212. package/src/config/bundled-skills/tasks/SKILL.md +5 -0
  213. package/src/config/env-registry.ts +14 -0
  214. package/src/config/env.ts +21 -0
  215. package/src/config/feature-flag-registry.json +46 -7
  216. package/src/config/loader.ts +56 -1
  217. package/src/config/sanitize-for-transfer.ts +47 -0
  218. package/src/config/schema.ts +46 -5
  219. package/src/config/schemas/host-browser.ts +66 -0
  220. package/src/config/schemas/memory-lifecycle.ts +1 -1
  221. package/src/config/schemas/memory-retrieval.ts +103 -0
  222. package/src/config/schemas/security.ts +0 -6
  223. package/src/config/schemas/services.ts +16 -0
  224. package/src/config/types.ts +0 -1
  225. package/src/context/post-turn-tool-result-truncation.ts +176 -0
  226. package/src/context/window-manager.ts +19 -1
  227. package/src/credential-execution/approval-bridge.ts +49 -16
  228. package/src/credential-execution/managed-catalog.ts +3 -7
  229. package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
  230. package/src/daemon/app-source-watcher.ts +35 -0
  231. package/src/daemon/config-watcher.ts +6 -2
  232. package/src/daemon/context-overflow-approval.ts +5 -1
  233. package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
  234. package/src/daemon/conversation-agent-loop.ts +74 -19
  235. package/src/daemon/conversation-attachments.ts +40 -1
  236. package/src/daemon/conversation-messaging.ts +3 -0
  237. package/src/daemon/conversation-process.ts +66 -3
  238. package/src/daemon/conversation-queue-manager.ts +8 -0
  239. package/src/daemon/conversation-runtime-assembly.ts +159 -20
  240. package/src/daemon/conversation-surfaces.ts +78 -12
  241. package/src/daemon/conversation-tool-setup.ts +74 -11
  242. package/src/daemon/conversation-workspace.ts +12 -0
  243. package/src/daemon/conversation.ts +227 -11
  244. package/src/daemon/date-context.ts +10 -10
  245. package/src/daemon/first-greeting.ts +3 -2
  246. package/src/daemon/handlers/conversations.ts +9 -139
  247. package/src/daemon/handlers/shared.ts +65 -0
  248. package/src/daemon/handlers/skills.ts +232 -37
  249. package/src/daemon/host-bash-proxy.ts +48 -13
  250. package/src/daemon/host-browser-proxy.ts +191 -0
  251. package/src/daemon/host-cu-proxy.ts +36 -11
  252. package/src/daemon/host-file-proxy.ts +57 -9
  253. package/src/daemon/lifecycle.ts +86 -12
  254. package/src/daemon/message-protocol.ts +7 -0
  255. package/src/daemon/message-types/conversations.ts +59 -13
  256. package/src/daemon/message-types/host-browser.ts +100 -0
  257. package/src/daemon/message-types/messages.ts +5 -6
  258. package/src/daemon/message-types/notifications.ts +12 -0
  259. package/src/daemon/message-types/settings.ts +12 -0
  260. package/src/daemon/message-types/skills.ts +10 -0
  261. package/src/daemon/message-types/subagents.ts +2 -0
  262. package/src/daemon/server.ts +112 -35
  263. package/src/daemon/tool-side-effects.ts +6 -0
  264. package/src/daemon/transport-hints.ts +14 -0
  265. package/src/inbound/platform-callback-registration.ts +18 -17
  266. package/src/index.ts +1 -1
  267. package/src/mcp/client.ts +59 -24
  268. package/src/memory/app-store.ts +31 -1
  269. package/src/memory/conversation-crud.ts +38 -10
  270. package/src/memory/conversation-directories.ts +39 -0
  271. package/src/memory/conversation-group-migration.ts +65 -5
  272. package/src/memory/conversation-starters-cadence.ts +76 -0
  273. package/src/memory/conversation-title-service.ts +5 -2
  274. package/src/memory/db-init.ts +12 -0
  275. package/src/memory/embedding-backend.test.ts +75 -0
  276. package/src/memory/embedding-backend.ts +131 -5
  277. package/src/memory/embedding-gemini.test.ts +54 -0
  278. package/src/memory/embedding-gemini.ts +20 -9
  279. package/src/memory/embedding-local.ts +177 -18
  280. package/src/memory/graph/capability-seed.ts +3 -5
  281. package/src/memory/graph/consolidation.ts +10 -23
  282. package/src/memory/graph/extraction-job.ts +15 -0
  283. package/src/memory/graph/retriever.ts +40 -22
  284. package/src/memory/graph/store.test.ts +7 -3
  285. package/src/memory/graph/store.ts +47 -12
  286. package/src/memory/group-crud.ts +25 -9
  287. package/src/memory/llm-usage-store.ts +45 -4
  288. package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
  289. package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
  290. package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
  291. package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
  292. package/src/memory/migrations/217-conversation-host-access.ts +40 -0
  293. package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
  294. package/src/memory/migrations/index.ts +6 -0
  295. package/src/memory/migrations/registry.ts +8 -0
  296. package/src/memory/schema/conversations.ts +1 -0
  297. package/src/memory/schema/oauth.ts +18 -13
  298. package/src/messaging/provider.ts +1 -1
  299. package/src/notifications/broadcaster.ts +6 -0
  300. package/src/notifications/conversation-pairing.ts +12 -4
  301. package/src/notifications/emit-signal.ts +14 -0
  302. package/src/notifications/signal.ts +11 -0
  303. package/src/oauth/AGENTS.md +76 -0
  304. package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
  305. package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
  306. package/src/oauth/byo-connection.test.ts +8 -8
  307. package/src/oauth/byo-connection.ts +7 -7
  308. package/src/oauth/connect-orchestrator.ts +23 -21
  309. package/src/oauth/connect-types.ts +3 -3
  310. package/src/oauth/connection-resolver.test.ts +17 -4
  311. package/src/oauth/connection-resolver.ts +16 -16
  312. package/src/oauth/connection.ts +1 -1
  313. package/src/oauth/manual-token-connection.ts +13 -13
  314. package/src/oauth/oauth-store.ts +214 -100
  315. package/src/oauth/platform-connection.test.ts +5 -5
  316. package/src/oauth/platform-connection.ts +4 -4
  317. package/src/oauth/provider-serializer.ts +31 -5
  318. package/src/oauth/revoke.ts +76 -0
  319. package/src/oauth/seed-providers.ts +127 -87
  320. package/src/oauth/token-persistence.ts +1 -1
  321. package/src/permissions/checker.ts +3 -3
  322. package/src/permissions/defaults.ts +7 -8
  323. package/src/permissions/permission-mode.ts +4 -11
  324. package/src/permissions/prompter.ts +13 -3
  325. package/src/permissions/v2-consent-policy.ts +87 -0
  326. package/src/platform/client.ts +1 -1
  327. package/src/prompts/system-prompt.ts +18 -21
  328. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
  329. package/src/prompts/templates/BOOTSTRAP.md +59 -96
  330. package/src/prompts/templates/SOUL.md +11 -11
  331. package/src/providers/anthropic/client.ts +1 -0
  332. package/src/providers/types.ts +1 -1
  333. package/src/runtime/AGENTS.md +23 -0
  334. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
  335. package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
  336. package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
  337. package/src/runtime/assistant-event-hub.ts +24 -2
  338. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
  339. package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
  340. package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
  341. package/src/runtime/auth/middleware.ts +98 -0
  342. package/src/runtime/auth/route-policy.ts +6 -7
  343. package/src/runtime/auth/token-service.ts +8 -0
  344. package/src/runtime/capability-tokens.ts +414 -0
  345. package/src/runtime/channel-approvals.ts +18 -5
  346. package/src/runtime/chrome-extension-registry.ts +332 -0
  347. package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
  348. package/src/runtime/guardian-decision-types.ts +7 -0
  349. package/src/runtime/http-server.ts +425 -70
  350. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
  351. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
  352. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
  353. package/src/runtime/migrations/migration-transport.ts +6 -0
  354. package/src/runtime/migrations/migration-wizard.ts +22 -2
  355. package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
  356. package/src/runtime/migrations/vbundle-builder.ts +145 -38
  357. package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
  358. package/src/runtime/migrations/vbundle-importer.ts +55 -5
  359. package/src/runtime/pending-interactions.ts +29 -13
  360. package/src/runtime/routes/approval-routes.ts +90 -16
  361. package/src/runtime/routes/browser-cdp-routes.ts +229 -0
  362. package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
  363. package/src/runtime/routes/conversation-analysis-routes.ts +18 -5
  364. package/src/runtime/routes/conversation-management-routes.ts +108 -0
  365. package/src/runtime/routes/conversation-routes.ts +308 -28
  366. package/src/runtime/routes/conversation-starter-routes.ts +78 -16
  367. package/src/runtime/routes/group-routes.ts +22 -8
  368. package/src/runtime/routes/guardian-action-routes.ts +24 -13
  369. package/src/runtime/routes/host-browser-routes.ts +279 -0
  370. package/src/runtime/routes/host-file-routes.ts +9 -1
  371. package/src/runtime/routes/identity-routes.ts +259 -16
  372. package/src/runtime/routes/log-export/AGENTS.md +104 -0
  373. package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
  374. package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
  375. package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
  376. package/src/runtime/routes/log-export-routes.ts +60 -25
  377. package/src/runtime/routes/memory-item-routes.ts +1 -7
  378. package/src/runtime/routes/migration-routes.ts +87 -2
  379. package/src/runtime/routes/oauth-apps.ts +15 -17
  380. package/src/runtime/routes/oauth-providers.ts +4 -0
  381. package/src/runtime/routes/schedule-routes.ts +24 -11
  382. package/src/runtime/routes/settings-routes.ts +9 -97
  383. package/src/runtime/routes/skills-routes.ts +52 -2
  384. package/src/runtime/routes/subagents-routes.ts +14 -10
  385. package/src/runtime/routes/usage-routes.ts +8 -7
  386. package/src/runtime/routes/workspace-routes.test.ts +22 -0
  387. package/src/runtime/routes/workspace-routes.ts +8 -1
  388. package/src/runtime/routes/workspace-utils.ts +2 -0
  389. package/src/schedule/scheduler.ts +7 -5
  390. package/src/security/ces-credential-client.ts +20 -0
  391. package/src/security/ces-rpc-credential-backend.ts +17 -0
  392. package/src/security/credential-backend.ts +5 -0
  393. package/src/security/oauth2.ts +42 -25
  394. package/src/security/secure-keys.ts +118 -25
  395. package/src/security/token-manager.ts +23 -10
  396. package/src/skills/catalog-files.ts +492 -0
  397. package/src/skills/inline-command-runner.ts +12 -14
  398. package/src/subagent/manager.ts +131 -26
  399. package/src/subagent/types.ts +19 -0
  400. package/src/tools/apps/executors.ts +11 -2
  401. package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
  402. package/src/tools/browser/auth-detector.ts +43 -12
  403. package/src/tools/browser/browser-execution.ts +645 -340
  404. package/src/tools/browser/browser-manager.ts +36 -12
  405. package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
  406. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
  407. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
  408. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
  409. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
  410. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
  411. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
  412. package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
  413. package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
  414. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
  415. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
  416. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
  417. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
  418. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
  419. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
  420. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
  421. package/src/tools/browser/cdp-client/errors.ts +34 -0
  422. package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
  423. package/src/tools/browser/cdp-client/factory.ts +204 -0
  424. package/src/tools/browser/cdp-client/index.ts +14 -0
  425. package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
  426. package/src/tools/browser/cdp-client/types.ts +52 -0
  427. package/src/tools/filesystem/edit.ts +1 -1
  428. package/src/tools/filesystem/list.ts +1 -1
  429. package/src/tools/filesystem/read.ts +1 -1
  430. package/src/tools/filesystem/write.ts +2 -1
  431. package/src/tools/host-filesystem/edit.ts +1 -1
  432. package/src/tools/host-filesystem/read.ts +12 -15
  433. package/src/tools/host-filesystem/write.ts +1 -1
  434. package/src/tools/host-terminal/host-shell.ts +21 -16
  435. package/src/tools/permission-checker.ts +77 -100
  436. package/src/tools/registry.ts +0 -2
  437. package/src/tools/secret-detection-handler.ts +34 -1
  438. package/src/tools/shared/filesystem/image-read.ts +61 -40
  439. package/src/tools/skills/sandbox-runner.ts +3 -6
  440. package/src/tools/subagent/spawn.ts +47 -3
  441. package/src/tools/subagent/status.ts +2 -0
  442. package/src/tools/system/register.ts +2 -16
  443. package/src/tools/terminal/safe-env.ts +7 -0
  444. package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
  445. package/src/tools/terminal/sandbox.ts +4 -1
  446. package/src/tools/terminal/shell.ts +24 -21
  447. package/src/tools/tool-approval-handler.ts +48 -2
  448. package/src/tools/types.ts +2 -3
  449. package/src/util/platform.ts +14 -19
  450. package/src/watcher/provider-types.ts +1 -1
  451. package/src/workspace/migrations/029-seed-pkb.ts +1 -0
  452. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
  453. package/src/workspace/migrations/registry.ts +2 -0
  454. package/src/workspace/top-level-renderer.ts +19 -1
  455. package/src/__tests__/chrome-cdp.test.ts +0 -419
  456. package/src/__tests__/permission-mode-sse.test.ts +0 -418
  457. package/src/__tests__/permission-mode-store.test.ts +0 -277
  458. package/src/browser-extension-relay/protocol.ts +0 -63
  459. package/src/browser-extension-relay/server.ts +0 -203
  460. package/src/config/schemas/sandbox.ts +0 -14
  461. package/src/permissions/permission-mode-store.ts +0 -180
  462. package/src/tools/browser/chrome-cdp.ts +0 -239
  463. package/src/tools/system/set-permission-mode.ts +0 -103
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Guard tests for Chrome extension allowlist configuration.
3
+ *
4
+ * Single source of truth:
5
+ * meta/browser-extension/chrome-extension-allowlist.json
6
+ *
7
+ * This guard ensures:
8
+ * 1) Canonical config has a valid shape and valid extension IDs.
9
+ * 2) Assistant runtime allowlist mirrors canonical config.
10
+ * 3) The concrete extension ID literal appears only in canonical config
11
+ * (not duplicated across runtime/tests/docs).
12
+ */
13
+
14
+ import { readdirSync, readFileSync, statSync } from "node:fs";
15
+ import { join, resolve } from "node:path";
16
+ import { describe, expect, test } from "bun:test";
17
+
18
+ import { ALLOWED_EXTENSION_ORIGINS } from "../runtime/routes/browser-extension-pair-routes.js";
19
+
20
+ const repoRoot = resolve(__dirname, "..", "..", "..");
21
+ const CANONICAL_CONFIG_REL_PATH =
22
+ "meta/browser-extension/chrome-extension-allowlist.json";
23
+ const CANONICAL_CONFIG_ABS_PATH = join(repoRoot, CANONICAL_CONFIG_REL_PATH);
24
+
25
+ const EXTENSION_ID_REGEX = /^[a-p]{32}$/;
26
+
27
+ type AllowlistConfig = {
28
+ version: number;
29
+ allowedExtensionIds: string[];
30
+ };
31
+
32
+ function parseCanonicalConfig(): AllowlistConfig {
33
+ const raw = readFileSync(CANONICAL_CONFIG_ABS_PATH, "utf8");
34
+ const parsed = JSON.parse(raw) as Partial<AllowlistConfig>;
35
+
36
+ if (!Number.isInteger(parsed.version) || (parsed.version ?? 0) <= 0) {
37
+ throw new Error("Invalid canonical config: version must be a positive integer");
38
+ }
39
+
40
+ if (!Array.isArray(parsed.allowedExtensionIds)) {
41
+ throw new Error("Invalid canonical config: allowedExtensionIds must be an array");
42
+ }
43
+
44
+ if (parsed.allowedExtensionIds.length === 0) {
45
+ throw new Error(
46
+ "Invalid canonical config: allowedExtensionIds must contain at least one id",
47
+ );
48
+ }
49
+
50
+ const seen = new Set<string>();
51
+ for (const id of parsed.allowedExtensionIds) {
52
+ if (typeof id !== "string" || !EXTENSION_ID_REGEX.test(id)) {
53
+ throw new Error(`Invalid canonical extension id: ${String(id)}`);
54
+ }
55
+ if (seen.has(id)) {
56
+ throw new Error(`Duplicate canonical extension id: ${id}`);
57
+ }
58
+ seen.add(id);
59
+ }
60
+
61
+ return {
62
+ version: parsed.version as number,
63
+ allowedExtensionIds: parsed.allowedExtensionIds as string[],
64
+ };
65
+ }
66
+
67
+ function listTextFilesRecursively(root: string): string[] {
68
+ const ignoredDirs = new Set([
69
+ ".git",
70
+ "node_modules",
71
+ "dist",
72
+ "build",
73
+ "coverage",
74
+ ".next",
75
+ ".turbo",
76
+ ".idea",
77
+ ".vscode",
78
+ ]);
79
+
80
+ const allowedExtensions = new Set([
81
+ ".ts",
82
+ ".tsx",
83
+ ".js",
84
+ ".jsx",
85
+ ".mjs",
86
+ ".cjs",
87
+ ".json",
88
+ ".md",
89
+ ".swift",
90
+ ".sh",
91
+ ".toml",
92
+ ".yml",
93
+ ".yaml",
94
+ ".txt",
95
+ ]);
96
+
97
+ const out: string[] = [];
98
+
99
+ function walk(dir: string): void {
100
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
101
+ if (entry.name.startsWith(".DS_Store")) continue;
102
+ const absPath = join(dir, entry.name);
103
+ if (entry.isDirectory()) {
104
+ if (ignoredDirs.has(entry.name)) continue;
105
+ walk(absPath);
106
+ continue;
107
+ }
108
+ if (!entry.isFile()) continue;
109
+
110
+ const ext = entry.name.slice(entry.name.lastIndexOf("."));
111
+ if (!allowedExtensions.has(ext)) continue;
112
+
113
+ // Skip large files to keep this guard lightweight.
114
+ const size = statSync(absPath).size;
115
+ if (size > 1_000_000) continue;
116
+ out.push(absPath);
117
+ }
118
+ }
119
+
120
+ walk(root);
121
+ return out;
122
+ }
123
+
124
+ describe("Chrome extension allowlist guard", () => {
125
+ test("canonical allowlist config is valid", () => {
126
+ const config = parseCanonicalConfig();
127
+ expect(config.version).toBeGreaterThan(0);
128
+ expect(config.allowedExtensionIds.length).toBeGreaterThan(0);
129
+ });
130
+
131
+ test("assistant runtime allowlist mirrors canonical config", () => {
132
+ const config = parseCanonicalConfig();
133
+ const expectedOrigins = new Set(
134
+ config.allowedExtensionIds.map((id) => `chrome-extension://${id}/`),
135
+ );
136
+ expect(ALLOWED_EXTENSION_ORIGINS).toEqual(expectedOrigins);
137
+ });
138
+
139
+ test("concrete extension IDs appear only in canonical config", () => {
140
+ const config = parseCanonicalConfig();
141
+ const allFiles = listTextFilesRecursively(repoRoot);
142
+
143
+ for (const extensionId of config.allowedExtensionIds) {
144
+ const matches: string[] = [];
145
+ for (const absPath of allFiles) {
146
+ const relPath = absPath.replace(`${repoRoot}/`, "");
147
+ const content = readFileSync(absPath, "utf8");
148
+ if (content.includes(extensionId)) {
149
+ matches.push(relPath);
150
+ }
151
+ }
152
+ expect(matches).toEqual([CANONICAL_CONFIG_REL_PATH]);
153
+ }
154
+ });
155
+ });
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Mock Chrome extension test fixture.
3
+ *
4
+ * Opens a WebSocket to the runtime's `/v1/browser-relay` endpoint using a
5
+ * caller-supplied JWT (so the upgrade handler registers the connection
6
+ * under the guardianId encoded in the token), handles incoming
7
+ * `host_browser_request` frames by calling a mock CDP proxy, and POSTs
8
+ * the result back to `/v1/host-browser-result`.
9
+ *
10
+ * Used by e2e tests (PR 15/16) to exercise the full round-trip without
11
+ * requiring a real Chrome browser or the real extension worker.
12
+ *
13
+ * The fixture is intentionally minimal — it does not implement heartbeats
14
+ * or reconnect logic. It only needs to carry host_browser_request frames
15
+ * end-to-end.
16
+ */
17
+
18
+ // ── Types ───────────────────────────────────────────────────────────
19
+
20
+ /** Incoming `host_browser_request` envelope (wire format). */
21
+ export interface HostBrowserRequestFrame {
22
+ type: "host_browser_request";
23
+ requestId: string;
24
+ conversationId: string;
25
+ cdpMethod: string;
26
+ cdpParams?: Record<string, unknown>;
27
+ cdpSessionId?: string;
28
+ timeout_seconds?: number;
29
+ }
30
+
31
+ /** Incoming `host_browser_cancel` envelope (wire format). */
32
+ export interface HostBrowserCancelFrame {
33
+ type: "host_browser_cancel";
34
+ requestId: string;
35
+ }
36
+
37
+ /** Result body POSTed back to `/v1/host-browser-result`. */
38
+ export interface HostBrowserResultBody {
39
+ requestId: string;
40
+ content: string;
41
+ isError: boolean;
42
+ }
43
+
44
+ /**
45
+ * Callback that handles a CDP request and returns a
46
+ * (content, isError) pair to be POSTed back to the runtime.
47
+ *
48
+ * Tests pass in a mock that simulates `chrome.debugger.sendCommand` for a
49
+ * handful of methods (e.g. `Browser.getVersion`).
50
+ */
51
+ export type MockCdpHandler = (
52
+ frame: HostBrowserRequestFrame,
53
+ ) => Promise<{ content: string; isError: boolean }>;
54
+
55
+ export interface MockChromeExtensionOptions {
56
+ /** Base URL of the runtime HTTP server, e.g. `http://127.0.0.1:19801`. */
57
+ runtimeBaseUrl: string;
58
+ /** JWT bearer token for both the WebSocket handshake and the POST callback. */
59
+ token: string;
60
+ /**
61
+ * CDP command handler. Defaults to a handler that recognises
62
+ * `Browser.getVersion` and returns a fake product string.
63
+ */
64
+ cdpHandler?: MockCdpHandler;
65
+ /**
66
+ * Optional extra headers forwarded on the WebSocket handshake (e.g.
67
+ * `x-guardian-id` when using a service token that doesn't carry an
68
+ * actor principal id).
69
+ */
70
+ extraHandshakeHeaders?: Record<string, string>;
71
+ /**
72
+ * Transport used to submit the result back to the runtime.
73
+ * - "http" (default): POST to `/v1/host-browser-result`.
74
+ * - "ws": send a `host_browser_result` frame back over the same
75
+ * `/v1/browser-relay` WebSocket that delivered the request.
76
+ *
77
+ * Both transports are expected to be fully functional in the runtime.
78
+ * The HTTP path is the legacy transport; the WS path was added so the
79
+ * extension can avoid an extra round-trip through the cloud ingress
80
+ * stack for each CDP command.
81
+ */
82
+ resultTransport?: "http" | "ws";
83
+ }
84
+
85
+ export interface MockChromeExtension {
86
+ /** Open the WebSocket and resolve once it's connected. */
87
+ start(): Promise<void>;
88
+ /** Close the WebSocket and drop any in-flight request tracking. */
89
+ stop(): Promise<void>;
90
+ /**
91
+ * Wait until the WebSocket has transitioned to OPEN. Useful to avoid
92
+ * races between `start()` and the runtime's `register()` bookkeeping.
93
+ */
94
+ waitForConnection(timeoutMs?: number): Promise<void>;
95
+ /** List of every `host_browser_request` frame received, in order. */
96
+ receivedRequests(): ReadonlyArray<HostBrowserRequestFrame>;
97
+ /** List of every `host_browser_cancel` frame received, in order. */
98
+ receivedCancels(): ReadonlyArray<HostBrowserCancelFrame>;
99
+ /** Swap the CDP handler at runtime (tests can inject failure modes). */
100
+ setCdpHandler(handler: MockCdpHandler): void;
101
+ /**
102
+ * Force-close the WebSocket without going through the teardown path.
103
+ * Simulates a flaky extension that drops the connection.
104
+ */
105
+ forceDisconnect(): void;
106
+ /**
107
+ * Send a `host_browser_event` frame over the active WebSocket,
108
+ * mirroring what the extension's host-browser-dispatcher does in
109
+ * response to `chrome.debugger.onEvent`. Used by PR10 acceptance
110
+ * tests to assert that the runtime's WS handler fans CDP events
111
+ * out through the browser-session event bus.
112
+ */
113
+ sendHostBrowserEvent(event: {
114
+ method: string;
115
+ params?: unknown;
116
+ cdpSessionId?: string;
117
+ }): void;
118
+ /**
119
+ * Send a `host_browser_session_invalidated` frame over the active
120
+ * WebSocket, mirroring what the extension's host-browser-dispatcher
121
+ * does in response to `chrome.debugger.onDetach`. Used by PR10
122
+ * acceptance tests to assert that the runtime-side session
123
+ * registry evicts stale sessions and forces reattach on the next
124
+ * command.
125
+ */
126
+ sendSessionInvalidated(event: { targetId?: string; reason?: string }): void;
127
+ }
128
+
129
+ // ── Defaults ────────────────────────────────────────────────────────
130
+
131
+ const DEFAULT_MOCK_BROWSER_VERSION = {
132
+ product: "Chrome/MockTest",
133
+ protocolVersion: "1.3",
134
+ revision: "@mock",
135
+ userAgent: "Mozilla/5.0 (mock chrome-extension e2e fixture)",
136
+ jsVersion: "0.0.0-mock",
137
+ };
138
+
139
+ /**
140
+ * Default CDP handler: answers `Browser.getVersion` with a fake product
141
+ * string. Unrecognised methods return an error envelope so tests can fail
142
+ * fast instead of hanging.
143
+ */
144
+ const defaultCdpHandler: MockCdpHandler = async (frame) => {
145
+ if (frame.cdpMethod === "Browser.getVersion") {
146
+ return {
147
+ content: JSON.stringify(DEFAULT_MOCK_BROWSER_VERSION),
148
+ isError: false,
149
+ };
150
+ }
151
+ return {
152
+ content: `mock-chrome-extension: unsupported cdpMethod "${frame.cdpMethod}"`,
153
+ isError: true,
154
+ };
155
+ };
156
+
157
+ // ── Implementation ──────────────────────────────────────────────────
158
+
159
+ /**
160
+ * Create a mock chrome-extension client bound to the given runtime base
161
+ * URL. The fixture does not start itself; callers must invoke `start()`.
162
+ */
163
+ export function createMockChromeExtension(
164
+ options: MockChromeExtensionOptions,
165
+ ): MockChromeExtension {
166
+ const baseHttp = options.runtimeBaseUrl.replace(/\/$/, "");
167
+ const wsBase = baseHttp.replace(/^http/i, "ws");
168
+ const wsUrl = `${wsBase}/v1/browser-relay?token=${encodeURIComponent(options.token)}`;
169
+
170
+ let ws: WebSocket | null = null;
171
+ let connected = false;
172
+ let handler = options.cdpHandler ?? defaultCdpHandler;
173
+ const receivedRequests: HostBrowserRequestFrame[] = [];
174
+ const receivedCancels: HostBrowserCancelFrame[] = [];
175
+ const inFlight = new Map<string, AbortController>();
176
+ const resultTransport = options.resultTransport ?? "http";
177
+
178
+ async function handleRequestFrame(
179
+ frame: HostBrowserRequestFrame,
180
+ ): Promise<void> {
181
+ const abortCtl = new AbortController();
182
+ inFlight.set(frame.requestId, abortCtl);
183
+ let result: { content: string; isError: boolean };
184
+ try {
185
+ result = await handler(frame);
186
+ } catch (err) {
187
+ result = {
188
+ content: err instanceof Error ? err.message : String(err),
189
+ isError: true,
190
+ };
191
+ } finally {
192
+ inFlight.delete(frame.requestId);
193
+ }
194
+ // If the request was aborted mid-flight, drop the result entirely
195
+ // (mirroring the production dispatcher, which doesn't POST a result
196
+ // for cancelled requests).
197
+ if (abortCtl.signal.aborted) return;
198
+
199
+ const body: HostBrowserResultBody = {
200
+ requestId: frame.requestId,
201
+ content: result.content,
202
+ isError: result.isError,
203
+ };
204
+ if (resultTransport === "ws") {
205
+ // Send the result back over the same `/v1/browser-relay` socket
206
+ // that delivered the request. The runtime WS message handler
207
+ // parses `host_browser_result` frames and resolves the pending
208
+ // interaction via the same core resolver the HTTP endpoint uses.
209
+ const sock = ws;
210
+ if (sock && sock.readyState === WebSocket.OPEN) {
211
+ try {
212
+ sock.send(
213
+ JSON.stringify({
214
+ type: "host_browser_result",
215
+ ...body,
216
+ }),
217
+ );
218
+ } catch {
219
+ // Best-effort — mirrors the HTTP POST failure mode.
220
+ }
221
+ }
222
+ return;
223
+ }
224
+ try {
225
+ const res = await fetch(`${baseHttp}/v1/host-browser-result`, {
226
+ method: "POST",
227
+ headers: {
228
+ "Content-Type": "application/json",
229
+ Authorization: `Bearer ${options.token}`,
230
+ },
231
+ body: JSON.stringify(body),
232
+ });
233
+ // Consume the body so Bun doesn't leak the response handle.
234
+ await res.body?.cancel();
235
+ } catch {
236
+ // Best-effort — if the runtime has torn down the server, the POST
237
+ // will throw. Tests assert on proxy behaviour, not POST success.
238
+ }
239
+ }
240
+
241
+ function handleMessage(raw: string): void {
242
+ let parsed: unknown;
243
+ try {
244
+ parsed = JSON.parse(raw);
245
+ } catch {
246
+ return;
247
+ }
248
+ if (!parsed || typeof parsed !== "object") return;
249
+ const frame = parsed as Record<string, unknown>;
250
+ if (frame.type === "host_browser_request") {
251
+ const typed = frame as unknown as HostBrowserRequestFrame;
252
+ receivedRequests.push(typed);
253
+ void handleRequestFrame(typed);
254
+ return;
255
+ }
256
+ if (frame.type === "host_browser_cancel") {
257
+ const typed = frame as unknown as HostBrowserCancelFrame;
258
+ receivedCancels.push(typed);
259
+ const abort = inFlight.get(typed.requestId);
260
+ if (abort) {
261
+ abort.abort();
262
+ inFlight.delete(typed.requestId);
263
+ }
264
+ return;
265
+ }
266
+ // Ignore any other frames.
267
+ }
268
+
269
+ return {
270
+ async start() {
271
+ if (ws) return;
272
+ // Bun's `WebSocket` constructor accepts a second-argument options
273
+ // object with a `headers` field (a Bun-specific extension of the
274
+ // standard WebSocket API). We forward `extraHandshakeHeaders`
275
+ // through it so tests using service tokens can supply the
276
+ // `x-guardian-id` fallback expected by `/v1/browser-relay`.
277
+ //
278
+ // We cast through `unknown` because the DOM `WebSocket` type only
279
+ // knows about `(url, protocols)`. If this fixture is ever run in
280
+ // an environment that isn't Bun, the options object would be
281
+ // silently ignored — acceptable for a test fixture.
282
+ const wsOptions: { headers?: Record<string, string> } = {};
283
+ if (options.extraHandshakeHeaders) {
284
+ wsOptions.headers = options.extraHandshakeHeaders;
285
+ }
286
+ ws = new WebSocket(wsUrl, wsOptions as unknown as string | string[]);
287
+ ws.addEventListener("open", () => {
288
+ connected = true;
289
+ });
290
+ ws.addEventListener("message", (ev: MessageEvent) => {
291
+ const data = ev.data;
292
+ if (typeof data === "string") {
293
+ handleMessage(data);
294
+ } else if (data instanceof ArrayBuffer) {
295
+ handleMessage(new TextDecoder().decode(data));
296
+ }
297
+ });
298
+ ws.addEventListener("close", () => {
299
+ connected = false;
300
+ });
301
+ },
302
+ async stop() {
303
+ const sock = ws;
304
+ ws = null;
305
+ if (sock) {
306
+ try {
307
+ sock.close(1000, "fixture shutdown");
308
+ } catch {
309
+ // best-effort
310
+ }
311
+ }
312
+ for (const abort of inFlight.values()) {
313
+ abort.abort();
314
+ }
315
+ inFlight.clear();
316
+ },
317
+ async waitForConnection(timeoutMs = 2000) {
318
+ const deadline = Date.now() + timeoutMs;
319
+ while (!connected) {
320
+ if (Date.now() > deadline) {
321
+ throw new Error(
322
+ `mock-chrome-extension: timed out waiting for WebSocket OPEN after ${timeoutMs}ms`,
323
+ );
324
+ }
325
+ await new Promise((r) => setTimeout(r, 10));
326
+ }
327
+ },
328
+ receivedRequests() {
329
+ return receivedRequests;
330
+ },
331
+ receivedCancels() {
332
+ return receivedCancels;
333
+ },
334
+ setCdpHandler(next) {
335
+ handler = next;
336
+ },
337
+ forceDisconnect() {
338
+ const sock = ws;
339
+ ws = null;
340
+ connected = false;
341
+ if (sock) {
342
+ try {
343
+ sock.close(4000, "forced disconnect");
344
+ } catch {
345
+ // best-effort
346
+ }
347
+ }
348
+ },
349
+ sendHostBrowserEvent(event) {
350
+ const sock = ws;
351
+ if (!sock || sock.readyState !== WebSocket.OPEN) return;
352
+ sock.send(
353
+ JSON.stringify({
354
+ type: "host_browser_event",
355
+ method: event.method,
356
+ ...(event.params !== undefined ? { params: event.params } : {}),
357
+ ...(event.cdpSessionId !== undefined
358
+ ? { cdpSessionId: event.cdpSessionId }
359
+ : {}),
360
+ }),
361
+ );
362
+ },
363
+ sendSessionInvalidated(event) {
364
+ const sock = ws;
365
+ if (!sock || sock.readyState !== WebSocket.OPEN) return;
366
+ sock.send(
367
+ JSON.stringify({
368
+ type: "host_browser_session_invalidated",
369
+ ...(event.targetId !== undefined ? { targetId: event.targetId } : {}),
370
+ ...(event.reason !== undefined ? { reason: event.reason } : {}),
371
+ }),
372
+ );
373
+ },
374
+ };
375
+ }
@@ -23,6 +23,7 @@ const ALLOWLIST = new Set([
23
23
 
24
24
  // --- Intentional local daemon-control paths ---
25
25
  "assistant/src/cli/commands/conversations.ts", // CLI wipe talks to runtime directly
26
+ "assistant/src/cli/commands/browser-relay.ts", // CLI shim talks to /v1/browser-cdp on the local daemon
26
27
  "clients/shared/Network/DaemonClient.swift",
27
28
  "clients/shared/App/Auth/PlatformOAuthService.swift", // comment explaining runtimeUrl vs platformUrl
28
29
  "clients/macos/vellum-assistant/App/AppDelegate.swift",
@@ -35,6 +36,8 @@ const ALLOWLIST = new Set([
35
36
  // --- Chrome extension (local relay communication, not gateway API consumption) ---
36
37
  "clients/chrome-extension/background/worker.ts",
37
38
  "clients/chrome-extension/popup/popup.ts",
39
+ // --- Chrome extension native messaging helper (local daemon pair endpoint, by design) ---
40
+ "clients/chrome-extension/native-host/src/index.ts",
38
41
 
39
42
  // --- Documentation and comments that mention the port for explanatory purposes ---
40
43
  "AGENTS.md", // documents the gateway-only rule itself
@@ -519,11 +519,11 @@ describe("GeminiProvider", () => {
519
519
  [{ role: "user", content: [{ type: "text", text: "Hi" }] }],
520
520
  undefined,
521
521
  undefined,
522
- { config: { max_tokens: 32000 } },
522
+ { config: { max_tokens: 64000 } },
523
523
  );
524
524
 
525
525
  const config = lastStreamParams!.config as Record<string, unknown>;
526
- expect(config.maxOutputTokens).toBe(32000);
526
+ expect(config.maxOutputTokens).toBe(64000);
527
527
  });
528
528
 
529
529
  // -----------------------------------------------------------------------
@@ -34,6 +34,7 @@ import {
34
34
  getRegisteredKinds,
35
35
  getResolver,
36
36
  } from "../approvals/guardian-request-resolvers.js";
37
+ import { _setOverridesForTesting } from "../config/assistant-feature-flags.js";
37
38
  import {
38
39
  createCanonicalGuardianRequest,
39
40
  getCanonicalGuardianRequest,
@@ -48,7 +49,10 @@ import {
48
49
  routeGuardianReply,
49
50
  } from "../runtime/guardian-reply-router.js";
50
51
  import * as pendingInteractions from "../runtime/pending-interactions.js";
51
- import { listGuardianDecisionPrompts } from "../runtime/routes/guardian-action-routes.js";
52
+ import {
53
+ handleGuardianActionDecision,
54
+ listGuardianDecisionPrompts,
55
+ } from "../runtime/routes/guardian-action-routes.js";
52
56
 
53
57
  initializeDb();
54
58
 
@@ -1712,7 +1716,10 @@ describe("routing invariant: expired requests are excluded from pending discover
1712
1716
  // ===========================================================================
1713
1717
 
1714
1718
  describe("routing invariant: kind-specific action sets in prompt mapping", () => {
1715
- beforeEach(() => resetTables());
1719
+ beforeEach(() => {
1720
+ resetTables();
1721
+ _setOverridesForTesting({});
1722
+ });
1716
1723
 
1717
1724
  test("buildDecisionActions({ forGuardianOnBehalf: true }) includes temporal actions", () => {
1718
1725
  const actions = buildDecisionActions({ forGuardianOnBehalf: true });
@@ -1768,6 +1775,67 @@ describe("routing invariant: kind-specific action sets in prompt mapping", () =>
1768
1775
  expect(actionIds).not.toContain("approve_always");
1769
1776
  });
1770
1777
 
1778
+ test("tool_approval prompt collapses to approve_once + reject under v2", () => {
1779
+ _setOverridesForTesting({ "permission-controls-v2": true });
1780
+ const convId = "conv-kind-tool-approval-v2";
1781
+ createCanonicalGuardianRequest({
1782
+ kind: "tool_approval",
1783
+ sourceType: "channel",
1784
+ conversationId: convId,
1785
+ guardianExternalUserId: "guardian-1",
1786
+ guardianPrincipalId: TEST_PRINCIPAL_ID,
1787
+ toolName: "shell",
1788
+ expiresAt: Date.now() + 60_000,
1789
+ });
1790
+
1791
+ const prompts = listGuardianDecisionPrompts({ conversationId: convId });
1792
+ expect(prompts).toHaveLength(1);
1793
+ expect(prompts[0].actions.map((a) => a.action)).toEqual([
1794
+ "approve_once",
1795
+ "reject",
1796
+ ]);
1797
+ });
1798
+
1799
+ test("guardian action POST rejects temporal actions under v2", async () => {
1800
+ _setOverridesForTesting({ "permission-controls-v2": true });
1801
+ const convId = "conv-kind-tool-approval-v2-submit";
1802
+ createCanonicalGuardianRequest({
1803
+ kind: "tool_approval",
1804
+ sourceType: "channel",
1805
+ conversationId: convId,
1806
+ guardianExternalUserId: "guardian-1",
1807
+ guardianPrincipalId: TEST_PRINCIPAL_ID,
1808
+ toolName: "shell",
1809
+ expiresAt: Date.now() + 60_000,
1810
+ });
1811
+
1812
+ const [prompt] = listGuardianDecisionPrompts({ conversationId: convId });
1813
+ const response = await handleGuardianActionDecision(
1814
+ new Request("http://localhost/v1/guardian-actions/decision", {
1815
+ method: "POST",
1816
+ headers: { "Content-Type": "application/json" },
1817
+ body: JSON.stringify({
1818
+ requestId: prompt.requestId,
1819
+ action: "approve_10m",
1820
+ conversationId: convId,
1821
+ }),
1822
+ }),
1823
+ {
1824
+ subject: "actor:self:test-principal",
1825
+ principalType: "actor",
1826
+ assistantId: "self",
1827
+ actorPrincipalId: TEST_PRINCIPAL_ID,
1828
+ scopeProfile: "actor_client_v1",
1829
+ scopes: new Set(["approval.write"]),
1830
+ policyEpoch: 1,
1831
+ },
1832
+ );
1833
+
1834
+ expect(response.status).toBe(403);
1835
+ const body = (await response.json()) as { error?: { message?: string } };
1836
+ expect(body.error?.message).toContain("approve_once or reject");
1837
+ });
1838
+
1771
1839
  test("pending_question prompt has approve_once + reject only (no temporal actions)", () => {
1772
1840
  const convId = "conv-kind-pending-question";
1773
1841
  createCanonicalGuardianRequest({