@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
@@ -4,13 +4,17 @@
4
4
  * GET /v1/conversation-starters — list conversation starters (chips)
5
5
  */
6
6
 
7
- import { and, desc, eq, inArray, like } from "drizzle-orm";
7
+ import { and, desc, eq, inArray, sql } from "drizzle-orm";
8
8
  import { z } from "zod";
9
9
 
10
10
  import { getDb } from "../../memory/db.js";
11
11
  import { enqueueMemoryJob } from "../../memory/jobs-store.js";
12
12
  import { rawGet } from "../../memory/raw-query.js";
13
- import { conversationStarters, memoryJobs } from "../../memory/schema.js";
13
+ import {
14
+ conversationStarters,
15
+ memoryCheckpoints,
16
+ memoryJobs,
17
+ } from "../../memory/schema.js";
14
18
  import type { RouteDefinition } from "../http-router.js";
15
19
 
16
20
  // ---------------------------------------------------------------------------
@@ -26,6 +30,39 @@ interface StarterItem {
26
30
  batch: number;
27
31
  }
28
32
 
33
+ const CK_ITEM_COUNT = "conversation_starters:item_count_at_last_gen";
34
+ const CK_LAST_GEN_AT = "conversation_starters:last_gen_at";
35
+ export const CONVERSATION_STARTERS_STALE_TTL_MS = 24 * 60 * 60 * 1000;
36
+
37
+ function checkpointKey(base: string, scopeId: string): string {
38
+ return `${base}:${scopeId}`;
39
+ }
40
+
41
+ function parseCheckpointInt(value: string | undefined): number | null {
42
+ if (!value) return null;
43
+ const parsed = Number.parseInt(value, 10);
44
+ return Number.isFinite(parsed) ? parsed : null;
45
+ }
46
+
47
+ function hasActiveConversationStarterJob(
48
+ db: ReturnType<typeof getDb>,
49
+ scopeId: string,
50
+ ): boolean {
51
+ return (
52
+ db
53
+ .select({ id: memoryJobs.id })
54
+ .from(memoryJobs)
55
+ .where(
56
+ and(
57
+ eq(memoryJobs.type, "generate_conversation_starters"),
58
+ inArray(memoryJobs.status, ["pending", "running"]),
59
+ sql`json_extract(${memoryJobs.payload}, '$.scopeId') = ${scopeId}`,
60
+ ),
61
+ )
62
+ .get() != null
63
+ );
64
+ }
65
+
29
66
  /**
30
67
  * Re-order starters so adjacent items have distinct categories wherever
31
68
  * possible. Within each category, preserve the original (batch-desc) order.
@@ -150,14 +187,47 @@ function handleListConversationStarters(url: URL): Response {
150
187
 
151
188
  const total = allItems.length;
152
189
 
153
- // If starters exist, reorder for category diversity then paginate.
190
+ // If starters exist, return them immediately. If the batch is stale or
191
+ // the generation checkpoint is ahead of the current active memory count,
192
+ // kick off a background refresh but keep the existing chips visible.
154
193
  if (total > 0) {
194
+ const totalActive =
195
+ rawGet<{ c: number }>(
196
+ `SELECT COUNT(*) AS c FROM memory_graph_nodes WHERE fidelity != 'gone' AND scope_id = ?`,
197
+ scopeId,
198
+ )?.c ?? 0;
199
+ const lastCount = parseCheckpointInt(
200
+ db
201
+ .select({ value: memoryCheckpoints.value })
202
+ .from(memoryCheckpoints)
203
+ .where(eq(memoryCheckpoints.key, checkpointKey(CK_ITEM_COUNT, scopeId)))
204
+ .get()?.value,
205
+ );
206
+ const lastGenAt = parseCheckpointInt(
207
+ db
208
+ .select({ value: memoryCheckpoints.value })
209
+ .from(memoryCheckpoints)
210
+ .where(eq(memoryCheckpoints.key, checkpointKey(CK_LAST_GEN_AT, scopeId)))
211
+ .get()?.value,
212
+ );
213
+ const staleByAge =
214
+ lastGenAt == null ||
215
+ Date.now() - lastGenAt >= CONVERSATION_STARTERS_STALE_TTL_MS;
216
+ const checkpointAhead = lastCount != null && totalActive < lastCount;
217
+ let hasActiveJob = hasActiveConversationStarterJob(db, scopeId);
218
+ const shouldRefresh = staleByAge || checkpointAhead;
219
+
220
+ if (shouldRefresh && !hasActiveJob) {
221
+ enqueueMemoryJob("generate_conversation_starters", { scopeId });
222
+ hasActiveJob = true;
223
+ }
224
+
155
225
  const ordered = orderStrongestFirst(allItems);
156
226
  const page = ordered.slice(offsetParam, offsetParam + limitParam);
157
227
  return Response.json({
158
228
  starters: page,
159
229
  total,
160
- status: "ready",
230
+ status: hasActiveJob ? "refreshing" : "ready",
161
231
  });
162
232
  }
163
233
 
@@ -172,17 +242,7 @@ function handleListConversationStarters(url: URL): Response {
172
242
  }
173
243
 
174
244
  // Memory items exist but no starters yet — ensure a generation job is queued.
175
- const existing = db
176
- .select({ id: memoryJobs.id })
177
- .from(memoryJobs)
178
- .where(
179
- and(
180
- eq(memoryJobs.type, "generate_conversation_starters"),
181
- inArray(memoryJobs.status, ["pending", "running"]),
182
- like(memoryJobs.payload, `%"scopeId":"${scopeId}"%`),
183
- ),
184
- )
185
- .get();
245
+ const existing = hasActiveConversationStarterJob(db, scopeId);
186
246
 
187
247
  if (!existing) {
188
248
  enqueueMemoryJob("generate_conversation_starters", { scopeId });
@@ -227,7 +287,9 @@ export function conversationStarterRouteDefinitions(): RouteDefinition[] {
227
287
  .array(z.unknown())
228
288
  .describe("Ordered list of starter chips"),
229
289
  total: z.number().int().describe("Total number of available starters"),
230
- status: z.string().describe("One of: ready, empty, generating"),
290
+ status: z
291
+ .enum(["ready", "refreshing", "empty", "generating"])
292
+ .describe("One of: ready, refreshing, empty, generating"),
231
293
  }),
232
294
  },
233
295
  ];
@@ -63,8 +63,22 @@ export function groupRouteDefinitions(): RouteDefinition[] {
63
63
  if (!body.name || typeof body.name !== "string") {
64
64
  return httpError("BAD_REQUEST", "Missing or invalid name", 400);
65
65
  }
66
- const group = createGroup(body.name);
67
- return Response.json(serializeGroup(group), { status: 201 });
66
+ try {
67
+ const group = createGroup(body.name);
68
+ return Response.json(serializeGroup(group), { status: 201 });
69
+ } catch (err) {
70
+ if (
71
+ err instanceof Error &&
72
+ err.message.includes("sort_position must be >= 4")
73
+ ) {
74
+ return httpError(
75
+ "BAD_REQUEST",
76
+ "Too many custom groups — sort_position ceiling reached",
77
+ 400,
78
+ );
79
+ }
80
+ throw err;
81
+ }
68
82
  },
69
83
  },
70
84
  {
@@ -105,16 +119,16 @@ export function groupRouteDefinitions(): RouteDefinition[] {
105
119
  403,
106
120
  );
107
121
  }
108
- // Custom group sort_position must be >= 3
122
+ // Custom group sort_position must be >= 4 (0–3 reserved for system groups)
109
123
  if (
110
124
  body.sortPosition !== undefined &&
111
125
  (typeof body.sortPosition !== "number" ||
112
126
  !isFinite(body.sortPosition) ||
113
- body.sortPosition < 3)
127
+ body.sortPosition < 4)
114
128
  ) {
115
129
  return httpError(
116
130
  "BAD_REQUEST",
117
- "Custom group sort_position must be >= 3",
131
+ "Custom group sort_position must be >= 4",
118
132
  400,
119
133
  );
120
134
  }
@@ -176,7 +190,7 @@ export function groupRouteDefinitions(): RouteDefinition[] {
176
190
  if (!Array.isArray(body.updates)) {
177
191
  return httpError("BAD_REQUEST", "Missing updates array", 400);
178
192
  }
179
- // Validate: no system group reordering, no sort_position < 3 for custom groups
193
+ // Validate: no system group reordering, sort_position >= 4 for custom groups
180
194
  for (const update of body.updates) {
181
195
  const group = getGroup(update.groupId);
182
196
  if (!group) continue;
@@ -190,11 +204,11 @@ export function groupRouteDefinitions(): RouteDefinition[] {
190
204
  if (
191
205
  typeof update.sortPosition !== "number" ||
192
206
  !isFinite(update.sortPosition) ||
193
- update.sortPosition < 3
207
+ update.sortPosition < 4
194
208
  ) {
195
209
  return httpError(
196
210
  "BAD_REQUEST",
197
- `Custom group sort_position must be >= 3 (got ${update.sortPosition} for ${update.groupId})`,
211
+ `Custom group sort_position must be >= 4 (got ${update.sortPosition} for ${update.groupId})`,
198
212
  400,
199
213
  );
200
214
  }
@@ -19,13 +19,14 @@ import {
19
19
  type CanonicalGuardianRequest,
20
20
  listPendingRequestsByConversationScope,
21
21
  } from "../../memory/canonical-guardian-store.js";
22
+ import { isPermissionControlsV2Enabled } from "../../permissions/v2-consent-policy.js";
22
23
  import { requireBoundGuardian } from "../auth/require-bound-guardian.js";
23
24
  import type { AuthContext } from "../auth/types.js";
24
25
  import { processGuardianDecision } from "../guardian-action-service.js";
25
26
  import type { GuardianDecisionPrompt } from "../guardian-decision-types.js";
26
27
  import {
27
28
  buildDecisionActions,
28
- GUARDIAN_DECISION_ACTIONS,
29
+ buildOneTimeDecisionActions,
29
30
  } from "../guardian-decision-types.js";
30
31
  import { httpError } from "../http-errors.js";
31
32
  import type { RouteDefinition } from "../http-router.js";
@@ -98,6 +99,18 @@ export async function handleGuardianActionDecision(
98
99
  return httpError("BAD_REQUEST", "action is required", 400);
99
100
  }
100
101
 
102
+ if (
103
+ isPermissionControlsV2Enabled() &&
104
+ action !== "approve_once" &&
105
+ action !== "reject"
106
+ ) {
107
+ return httpError(
108
+ "FORBIDDEN",
109
+ "permission-controls-v2 only accepts approve_once or reject for guardian actions",
110
+ 403,
111
+ );
112
+ }
113
+
101
114
  // Resolve the actor's guardian principal ID. For JWT-verified actors this
102
115
  // comes from the token claims. For dev bypass (HTTP auth disabled) the
103
116
  // synthetic "dev-bypass" principal won't match the real guardian binding,
@@ -193,15 +206,17 @@ export function listGuardianDecisionPrompts(params: {
193
206
  * Map a canonical guardian request to the client-facing prompt format.
194
207
  *
195
208
  * Generates kind-specific questionText and action sets:
196
- * - `tool_approval`: temporal modes (approve_once, approve_10m, approve_conversation) + reject
209
+ * - `tool_approval`: temporal modes (approve_once, approve_10m, approve_conversation) + reject in legacy mode,
210
+ * approve_once + reject only under v2
197
211
  * - `pending_question`: approve_once + reject only
198
212
  * - `access_request`: approve_once + reject only, with text fallback instructions
199
213
  * (request code + "open invite flow")
200
214
  * - `tool_grant_request`: approve_once + reject only
201
215
  *
202
- * Only `tool_approval` receives temporal modes because time-scoped grants
203
- * are meaningful only for tool execution. All other kinds get a simple
204
- * approve_once/reject pair.
216
+ * Under permission-controls-v2 we collapse all deterministic guardian action
217
+ * prompts to approve_once + reject only. Outside v2, only `tool_approval`
218
+ * receives temporal modes because time-scoped grants are meaningful only for
219
+ * tool execution.
205
220
  */
206
221
  function mapCanonicalRequestToPrompt(
207
222
  req: CanonicalGuardianRequest,
@@ -209,15 +224,11 @@ function mapCanonicalRequestToPrompt(
209
224
  ): GuardianDecisionPrompt {
210
225
  const questionText = buildKindAwareQuestionText(req);
211
226
 
212
- // Only tool_approval gets temporal modes (approve_10m, approve_conversation);
213
- // all other kinds get a simple approve_once + reject pair.
214
- const actions =
215
- req.kind === "tool_approval"
227
+ const actions = isPermissionControlsV2Enabled()
228
+ ? buildOneTimeDecisionActions()
229
+ : req.kind === "tool_approval"
216
230
  ? buildDecisionActions({ forGuardianOnBehalf: true })
217
- : [
218
- GUARDIAN_DECISION_ACTIONS.approve_once,
219
- GUARDIAN_DECISION_ACTIONS.reject,
220
- ];
231
+ : buildOneTimeDecisionActions();
221
232
 
222
233
  const expiresAt = req.expiresAt
223
234
  ? new Date(req.expiresAt).getTime()
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Route handler for host browser result submissions.
3
+ *
4
+ * Resolves pending host browser proxy requests by requestId when the desktop
5
+ * client returns CDP results via HTTP.
6
+ */
7
+ import { z } from "zod";
8
+
9
+ import {
10
+ markTargetInvalidated,
11
+ publishCdpEvent,
12
+ } from "../../browser-session/events.js";
13
+ import { requireBoundGuardian } from "../auth/require-bound-guardian.js";
14
+ import type { AuthContext } from "../auth/types.js";
15
+ import { httpError } from "../http-errors.js";
16
+ import type { RouteDefinition } from "../http-router.js";
17
+ import * as pendingInteractions from "../pending-interactions.js";
18
+
19
+ /**
20
+ * Result of attempting to resolve a host browser result frame. Used by both
21
+ * the HTTP endpoint and the WS relay path so they share the same validation
22
+ * and resolution semantics.
23
+ *
24
+ * Success → the pending interaction was consumed and the conversation (or
25
+ * CLI shim callback) was notified.
26
+ *
27
+ * Error variants mirror the HTTP status codes the `/v1/host-browser-result`
28
+ * endpoint returns, so the caller can log/translate them consistently.
29
+ */
30
+ export type HostBrowserResultResolution =
31
+ | { ok: true }
32
+ | {
33
+ ok: false;
34
+ code: "BAD_REQUEST" | "NOT_FOUND" | "CONFLICT";
35
+ status: 400 | 404 | 409;
36
+ message: string;
37
+ };
38
+
39
+ /**
40
+ * Shared resolver used by both the HTTP route handler and the WS
41
+ * `host_browser_result` frame handler. Looks up the pending interaction
42
+ * by requestId, validates its kind, and forwards the response to the
43
+ * owning conversation (or CLI shim callback).
44
+ *
45
+ * This function does NOT perform auth — callers are expected to have
46
+ * already authenticated the caller (the HTTP route uses
47
+ * `requireBoundGuardian`, the WS path relies on the JWT check performed
48
+ * at WebSocket upgrade time).
49
+ */
50
+ export function resolveHostBrowserResultByRequestId(frame: {
51
+ requestId?: unknown;
52
+ content?: unknown;
53
+ isError?: unknown;
54
+ }): HostBrowserResultResolution {
55
+ const { requestId, content, isError } = frame;
56
+
57
+ if (!requestId || typeof requestId !== "string") {
58
+ return {
59
+ ok: false,
60
+ code: "BAD_REQUEST",
61
+ status: 400,
62
+ message: "requestId is required",
63
+ };
64
+ }
65
+
66
+ // Peek first (non-destructive) so we can validate the interaction kind
67
+ // without accidentally consuming a confirmation or secret interaction.
68
+ const peeked = pendingInteractions.get(requestId);
69
+ if (!peeked) {
70
+ return {
71
+ ok: false,
72
+ code: "NOT_FOUND",
73
+ status: 404,
74
+ message: "No pending interaction found for this requestId",
75
+ };
76
+ }
77
+
78
+ if (peeked.kind !== "host_browser") {
79
+ return {
80
+ ok: false,
81
+ code: "CONFLICT",
82
+ status: 409,
83
+ message: `Pending interaction is of kind "${peeked.kind}", expected "host_browser"`,
84
+ };
85
+ }
86
+
87
+ // Validation passed — consume the pending interaction.
88
+ const interaction = pendingInteractions.resolve(requestId)!;
89
+
90
+ const normalizedContent = typeof content === "string" ? content : "";
91
+ const normalizedIsError = typeof isError === "boolean" ? isError : false;
92
+
93
+ // CLI shim path: pending interactions registered by /v1/browser-cdp carry
94
+ // a directBrowserResolve callback and are not bound to a Conversation.
95
+ // Resolve them in place without touching the conversation object.
96
+ if (interaction.directBrowserResolve) {
97
+ interaction.directBrowserResolve({
98
+ content: normalizedContent,
99
+ isError: normalizedIsError,
100
+ });
101
+ return { ok: true };
102
+ }
103
+
104
+ // The host_browser kind always has a conversation attached at register time
105
+ // (HostBrowserProxy.request wires it through), so this guard exists so a
106
+ // future refactor of pending-interactions can change the type without
107
+ // silently breaking the host_browser path. Prefer an explicit error over an
108
+ // optional-chain no-op that would leave the proxy request unresolved.
109
+ if (!interaction.conversation) {
110
+ return {
111
+ ok: false,
112
+ code: "BAD_REQUEST",
113
+ status: 400,
114
+ message:
115
+ "host_browser pending interaction has no associated conversation",
116
+ };
117
+ }
118
+
119
+ interaction.conversation.resolveHostBrowser(requestId, {
120
+ content: normalizedContent,
121
+ isError: normalizedIsError,
122
+ });
123
+
124
+ return { ok: true };
125
+ }
126
+
127
+ /**
128
+ * Result of attempting to resolve a `host_browser_event` frame. The
129
+ * event surface never fails — every well-formed frame is published
130
+ * to the runtime-side CDP event bus — so the resolution is either
131
+ * `ok: true` or a `BAD_REQUEST` when the frame is malformed. Kept as
132
+ * a discriminated union (rather than `void`) so the WS dispatcher
133
+ * can log rejected frames with a consistent shape.
134
+ */
135
+ export type HostBrowserEventResolution =
136
+ | { ok: true }
137
+ | {
138
+ ok: false;
139
+ code: "BAD_REQUEST";
140
+ status: 400;
141
+ message: string;
142
+ };
143
+
144
+ /**
145
+ * Shared resolver for `host_browser_event` envelopes. Publishes the
146
+ * event into the module-level browser-session event bus where
147
+ * runtime-side consumers can subscribe. Validation is intentionally
148
+ * minimal — the frame is opaque to the bus and the bus's listeners
149
+ * are the ones that care about params shape.
150
+ */
151
+ export function resolveHostBrowserEvent(frame: {
152
+ method?: unknown;
153
+ params?: unknown;
154
+ cdpSessionId?: unknown;
155
+ }): HostBrowserEventResolution {
156
+ const { method, params, cdpSessionId } = frame;
157
+
158
+ if (!method || typeof method !== "string") {
159
+ return {
160
+ ok: false,
161
+ code: "BAD_REQUEST",
162
+ status: 400,
163
+ message: "method is required",
164
+ };
165
+ }
166
+
167
+ publishCdpEvent({
168
+ method,
169
+ params,
170
+ cdpSessionId:
171
+ typeof cdpSessionId === "string" && cdpSessionId.length > 0
172
+ ? cdpSessionId
173
+ : undefined,
174
+ });
175
+
176
+ return { ok: true };
177
+ }
178
+
179
+ /**
180
+ * Result of attempting to resolve a `host_browser_session_invalidated`
181
+ * frame. Mirrors {@link HostBrowserEventResolution} — publishing into
182
+ * the invalidated-target registry never fails, so the resolution is
183
+ * either `ok: true` or a `BAD_REQUEST` on a malformed frame.
184
+ */
185
+ export type HostBrowserSessionInvalidatedResolution =
186
+ | { ok: true }
187
+ | {
188
+ ok: false;
189
+ code: "BAD_REQUEST";
190
+ status: 400;
191
+ message: string;
192
+ };
193
+
194
+ /**
195
+ * Shared resolver for `host_browser_session_invalidated` envelopes.
196
+ * Marks the target as invalidated in the runtime-side registry; the
197
+ * next `BrowserSessionManager.send()` against that target will
198
+ * evict the stale session and force the owning tool to reattach.
199
+ *
200
+ * A frame without a `targetId` is tolerated (some detach notifications
201
+ * carry only a `reason`) but logged at info because it is less
202
+ * actionable than a targeted invalidation — the caller can still
203
+ * subscribe to the event bus to observe the signal.
204
+ */
205
+ export function resolveHostBrowserSessionInvalidated(frame: {
206
+ targetId?: unknown;
207
+ reason?: unknown;
208
+ }): HostBrowserSessionInvalidatedResolution {
209
+ const { targetId, reason } = frame;
210
+
211
+ if (targetId !== undefined && typeof targetId !== "string") {
212
+ return {
213
+ ok: false,
214
+ code: "BAD_REQUEST",
215
+ status: 400,
216
+ message: "targetId must be a string when present",
217
+ };
218
+ }
219
+
220
+ if (typeof targetId === "string" && targetId.length > 0) {
221
+ markTargetInvalidated(
222
+ targetId,
223
+ typeof reason === "string" ? reason : undefined,
224
+ );
225
+ }
226
+
227
+ return { ok: true };
228
+ }
229
+
230
+ /**
231
+ * POST /v1/host-browser-result — resolve a pending host browser request by requestId.
232
+ * Requires AuthContext with guardian-bound actor.
233
+ */
234
+ export async function handleHostBrowserResult(
235
+ req: Request,
236
+ authContext: AuthContext,
237
+ ): Promise<Response> {
238
+ const authError = requireBoundGuardian(authContext);
239
+ if (authError) return authError;
240
+
241
+ const body = (await req.json()) as {
242
+ requestId?: string;
243
+ content?: string;
244
+ isError?: boolean;
245
+ };
246
+
247
+ const resolution = resolveHostBrowserResultByRequestId(body);
248
+ if (!resolution.ok) {
249
+ return httpError(resolution.code, resolution.message, resolution.status);
250
+ }
251
+
252
+ return Response.json({ accepted: true });
253
+ }
254
+
255
+ // ---------------------------------------------------------------------------
256
+ // Route definitions
257
+ // ---------------------------------------------------------------------------
258
+
259
+ export function hostBrowserRouteDefinitions(): RouteDefinition[] {
260
+ return [
261
+ {
262
+ endpoint: "host-browser-result",
263
+ method: "POST",
264
+ summary: "Submit host browser result",
265
+ description: "Resolve a pending host browser request by requestId.",
266
+ tags: ["host"],
267
+ requestBody: z.object({
268
+ requestId: z.string().describe("Pending browser request ID"),
269
+ content: z.string().optional(),
270
+ isError: z.boolean().optional(),
271
+ }),
272
+ responseBody: z.object({
273
+ accepted: z.boolean(),
274
+ }),
275
+ handler: async ({ req, authContext }) =>
276
+ handleHostBrowserResult(req, authContext),
277
+ },
278
+ ];
279
+ }
@@ -27,9 +27,10 @@ export async function handleHostFileResult(
27
27
  requestId?: string;
28
28
  content?: string;
29
29
  isError?: boolean;
30
+ imageData?: string;
30
31
  };
31
32
 
32
- const { requestId, content, isError } = body;
33
+ const { requestId, content, isError, imageData } = body;
33
34
 
34
35
  if (!requestId || typeof requestId !== "string") {
35
36
  return httpError("BAD_REQUEST", "requestId is required", 400);
@@ -60,6 +61,7 @@ export async function handleHostFileResult(
60
61
  interaction.conversation!.resolveHostFile(requestId, {
61
62
  content: content ?? "",
62
63
  isError: isError ?? false,
64
+ imageData,
63
65
  });
64
66
 
65
67
  return Response.json({ accepted: true });
@@ -87,6 +89,12 @@ export function hostFileRouteDefinitions(): RouteDefinition[] {
87
89
  .boolean()
88
90
  .describe("Whether the result is an error")
89
91
  .optional(),
92
+ imageData: z
93
+ .string()
94
+ .describe(
95
+ "Optional base64-encoded image bytes for successful image reads",
96
+ )
97
+ .optional(),
90
98
  }),
91
99
  responseBody: z.object({
92
100
  accepted: z.boolean(),