@vellumai/assistant 0.6.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (396) hide show
  1. package/bun.lock +40 -40
  2. package/bunfig.toml +3 -0
  3. package/docs/architecture/memory.md +1 -1
  4. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
  5. package/openapi.yaml +184 -69
  6. package/package.json +41 -41
  7. package/scripts/generate-openapi.ts +1 -2
  8. package/src/__tests__/acp-session.test.ts +43 -0
  9. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
  10. package/src/__tests__/app-executors.test.ts +1 -0
  11. package/src/__tests__/app-source-watcher.test.ts +37 -11
  12. package/src/__tests__/approval-routes-http.test.ts +178 -1
  13. package/src/__tests__/browser-fill-credential.test.ts +229 -94
  14. package/src/__tests__/browser-manager.test.ts +40 -27
  15. package/src/__tests__/catalog-files.test.ts +862 -0
  16. package/src/__tests__/channel-approvals.test.ts +53 -0
  17. package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
  18. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  19. package/src/__tests__/config-schema.test.ts +125 -48
  20. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
  21. package/src/__tests__/context-overflow-approval.test.ts +16 -1
  22. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  23. package/src/__tests__/conversation-agent-loop.test.ts +1 -1
  24. package/src/__tests__/conversation-analysis-routes.test.ts +2 -2
  25. package/src/__tests__/conversation-attachments.test.ts +80 -4
  26. package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
  27. package/src/__tests__/conversation-fork-crud.test.ts +17 -0
  28. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  29. package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
  30. package/src/__tests__/conversation-inject-context.test.ts +103 -0
  31. package/src/__tests__/conversation-queue.test.ts +45 -2
  32. package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
  33. package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
  34. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  35. package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
  36. package/src/__tests__/conversation-starter-routes.test.ts +126 -0
  37. package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
  38. package/src/__tests__/conversation-store.test.ts +195 -0
  39. package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
  40. package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
  41. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  42. package/src/__tests__/credential-vault-unit.test.ts +4 -4
  43. package/src/__tests__/credential-vault.test.ts +152 -13
  44. package/src/__tests__/credentials-cli.test.ts +2 -2
  45. package/src/__tests__/date-context.test.ts +4 -4
  46. package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
  47. package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
  48. package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
  49. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  50. package/src/__tests__/gemini-provider.test.ts +2 -2
  51. package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
  52. package/src/__tests__/headless-browser-interactions.test.ts +707 -371
  53. package/src/__tests__/headless-browser-navigate.test.ts +389 -47
  54. package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
  55. package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
  56. package/src/__tests__/host-bash-proxy.test.ts +150 -1
  57. package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
  58. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
  59. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
  60. package/src/__tests__/host-browser-event-routes.test.ts +350 -0
  61. package/src/__tests__/host-browser-proxy.test.ts +444 -0
  62. package/src/__tests__/host-browser-routes.test.ts +198 -0
  63. package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
  64. package/src/__tests__/host-cu-proxy.test.ts +171 -1
  65. package/src/__tests__/host-file-proxy.test.ts +185 -1
  66. package/src/__tests__/host-file-read-tool.test.ts +52 -0
  67. package/src/__tests__/host-proxy-interface.test.ts +165 -0
  68. package/src/__tests__/host-shell-tool.test.ts +1 -11
  69. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  70. package/src/__tests__/integration-status.test.ts +6 -7
  71. package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
  72. package/src/__tests__/mcp-client-auth.test.ts +40 -4
  73. package/src/__tests__/mcp-health-check.test.ts +10 -3
  74. package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
  75. package/src/__tests__/migration-export-http.test.ts +61 -2
  76. package/src/__tests__/migration-export-streaming.test.ts +66 -0
  77. package/src/__tests__/migration-import-commit-http.test.ts +101 -1
  78. package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
  79. package/src/__tests__/oauth-apps-routes.test.ts +17 -12
  80. package/src/__tests__/oauth-cli.test.ts +707 -60
  81. package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
  82. package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
  83. package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
  84. package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
  85. package/src/__tests__/oauth-providers-routes.test.ts +50 -14
  86. package/src/__tests__/oauth-store.test.ts +1386 -182
  87. package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
  88. package/src/__tests__/onboarding-template-contract.test.ts +75 -57
  89. package/src/__tests__/openai-provider.test.ts +2 -2
  90. package/src/__tests__/outlook-categories.test.ts +1 -1
  91. package/src/__tests__/outlook-client-automation.test.ts +1 -1
  92. package/src/__tests__/outlook-compose-tools.test.ts +1 -1
  93. package/src/__tests__/outlook-email-watcher.test.ts +1 -1
  94. package/src/__tests__/outlook-follow-up.test.ts +1 -1
  95. package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
  96. package/src/__tests__/outlook-trash.test.ts +1 -1
  97. package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
  98. package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
  99. package/src/__tests__/permission-mode.test.ts +28 -56
  100. package/src/__tests__/platform-callback-registration.test.ts +19 -0
  101. package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
  102. package/src/__tests__/proxy-approval-callback.test.ts +18 -0
  103. package/src/__tests__/require-fresh-approval.test.ts +40 -1
  104. package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
  105. package/src/__tests__/schedule-routes.test.ts +162 -0
  106. package/src/__tests__/secret-detection-handler.test.ts +84 -0
  107. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  108. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  109. package/src/__tests__/set-permission-mode.test.ts +13 -250
  110. package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
  111. package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
  112. package/src/__tests__/slack-channel-config.test.ts +12 -15
  113. package/src/__tests__/subagent-detail.test.ts +44 -2
  114. package/src/__tests__/subagent-disposal.test.ts +1 -0
  115. package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
  116. package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
  117. package/src/__tests__/subagent-manager-notify.test.ts +1 -0
  118. package/src/__tests__/subagent-notify-parent.test.ts +1 -0
  119. package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
  120. package/src/__tests__/subagent-tools.test.ts +1 -0
  121. package/src/__tests__/subagent-types.test.ts +1 -0
  122. package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
  123. package/src/__tests__/system-prompt.test.ts +72 -1
  124. package/src/__tests__/task-scheduler.test.ts +32 -6
  125. package/src/__tests__/telegram-config.test.ts +10 -13
  126. package/src/__tests__/terminal-tools.test.ts +9 -0
  127. package/src/__tests__/tool-approval-handler.test.ts +73 -0
  128. package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
  129. package/src/__tests__/top-level-renderer.test.ts +73 -1
  130. package/src/__tests__/transport-hints-queue.test.ts +14 -29
  131. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
  132. package/src/__tests__/v2-consent-policy.test.ts +103 -0
  133. package/src/acp/client-handler.ts +30 -4
  134. package/src/agent/loop.ts +12 -6
  135. package/src/approvals/guardian-request-resolvers.ts +21 -15
  136. package/src/browser-session/__tests__/manager.test.ts +297 -0
  137. package/src/browser-session/backends/cdp-inspect.ts +30 -0
  138. package/src/browser-session/backends/extension.ts +26 -0
  139. package/src/browser-session/backends/local.ts +24 -0
  140. package/src/browser-session/events.ts +164 -0
  141. package/src/browser-session/index.ts +27 -0
  142. package/src/browser-session/manager.ts +159 -0
  143. package/src/browser-session/types.ts +28 -0
  144. package/src/channels/__tests__/types.test.ts +134 -0
  145. package/src/channels/types.ts +53 -3
  146. package/src/cli/commands/browser-relay.ts +339 -409
  147. package/src/cli/commands/credentials.ts +3 -3
  148. package/src/cli/commands/email.ts +18 -13
  149. package/src/cli/commands/mcp.ts +16 -4
  150. package/src/cli/commands/oauth/__tests__/connect.test.ts +44 -44
  151. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
  152. package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
  153. package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
  154. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
  155. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
  156. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
  157. package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
  158. package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
  159. package/src/cli/commands/oauth/apps.ts +7 -4
  160. package/src/cli/commands/oauth/connect.ts +6 -3
  161. package/src/cli/commands/oauth/disconnect.ts +1 -1
  162. package/src/cli/commands/oauth/providers.ts +200 -36
  163. package/src/cli/commands/oauth/shared.ts +5 -5
  164. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
  165. package/src/cli/commands/platform/index.ts +107 -10
  166. package/src/cli/commands/usage.ts +10 -9
  167. package/src/cli/lib/daemon-credential-client.ts +4 -0
  168. package/src/cli/program.ts +1 -1
  169. package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
  170. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
  171. package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
  172. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
  173. package/src/config/bundled-skills/contacts/SKILL.md +3 -0
  174. package/src/config/bundled-skills/document/SKILL.md +4 -0
  175. package/src/config/bundled-skills/gmail/SKILL.md +1 -1
  176. package/src/config/bundled-skills/outlook/SKILL.md +7 -0
  177. package/src/config/bundled-skills/subagent/SKILL.md +21 -0
  178. package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
  179. package/src/config/bundled-skills/tasks/SKILL.md +5 -0
  180. package/src/config/env-registry.ts +14 -0
  181. package/src/config/env.ts +21 -0
  182. package/src/config/feature-flag-registry.json +44 -5
  183. package/src/config/loader.ts +56 -1
  184. package/src/config/sanitize-for-transfer.ts +47 -0
  185. package/src/config/schema.ts +46 -5
  186. package/src/config/schemas/host-browser.ts +66 -0
  187. package/src/config/schemas/memory-lifecycle.ts +1 -1
  188. package/src/config/schemas/memory-retrieval.ts +103 -0
  189. package/src/config/schemas/security.ts +0 -6
  190. package/src/config/schemas/services.ts +8 -0
  191. package/src/config/types.ts +0 -1
  192. package/src/context/post-turn-tool-result-truncation.ts +176 -0
  193. package/src/context/window-manager.ts +19 -1
  194. package/src/credential-execution/approval-bridge.ts +49 -15
  195. package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
  196. package/src/daemon/app-source-watcher.ts +35 -0
  197. package/src/daemon/context-overflow-approval.ts +5 -0
  198. package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
  199. package/src/daemon/conversation-agent-loop.ts +58 -24
  200. package/src/daemon/conversation-attachments.ts +40 -0
  201. package/src/daemon/conversation-process.ts +48 -1
  202. package/src/daemon/conversation-runtime-assembly.ts +118 -36
  203. package/src/daemon/conversation-surfaces.ts +37 -36
  204. package/src/daemon/conversation-tool-setup.ts +74 -8
  205. package/src/daemon/conversation-workspace.ts +12 -0
  206. package/src/daemon/conversation.ts +226 -8
  207. package/src/daemon/date-context.ts +10 -10
  208. package/src/daemon/first-greeting.ts +3 -2
  209. package/src/daemon/handlers/conversations.ts +9 -140
  210. package/src/daemon/handlers/shared.ts +58 -0
  211. package/src/daemon/handlers/skills.ts +232 -37
  212. package/src/daemon/host-bash-proxy.ts +48 -13
  213. package/src/daemon/host-browser-proxy.ts +191 -0
  214. package/src/daemon/host-cu-proxy.ts +36 -11
  215. package/src/daemon/host-file-proxy.ts +57 -9
  216. package/src/daemon/lifecycle.ts +65 -11
  217. package/src/daemon/message-protocol.ts +7 -0
  218. package/src/daemon/message-types/conversations.ts +55 -13
  219. package/src/daemon/message-types/host-browser.ts +100 -0
  220. package/src/daemon/message-types/messages.ts +5 -5
  221. package/src/daemon/message-types/skills.ts +10 -0
  222. package/src/daemon/message-types/subagents.ts +2 -0
  223. package/src/daemon/server.ts +92 -12
  224. package/src/daemon/tool-side-effects.ts +6 -0
  225. package/src/daemon/transport-hints.ts +5 -24
  226. package/src/inbound/platform-callback-registration.ts +18 -17
  227. package/src/mcp/client.ts +59 -24
  228. package/src/memory/app-store.ts +31 -1
  229. package/src/memory/conversation-crud.ts +23 -0
  230. package/src/memory/conversation-starters-cadence.ts +76 -0
  231. package/src/memory/conversation-title-service.ts +5 -2
  232. package/src/memory/db-init.ts +12 -0
  233. package/src/memory/embedding-backend.test.ts +75 -0
  234. package/src/memory/embedding-backend.ts +131 -5
  235. package/src/memory/embedding-gemini.test.ts +54 -0
  236. package/src/memory/embedding-gemini.ts +20 -9
  237. package/src/memory/embedding-local.ts +176 -17
  238. package/src/memory/graph/consolidation.ts +10 -23
  239. package/src/memory/graph/extraction-job.ts +15 -0
  240. package/src/memory/graph/retriever.ts +40 -22
  241. package/src/memory/graph/store.test.ts +7 -3
  242. package/src/memory/graph/store.ts +47 -12
  243. package/src/memory/llm-usage-store.ts +45 -4
  244. package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
  245. package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
  246. package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
  247. package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
  248. package/src/memory/migrations/217-conversation-host-access.ts +40 -0
  249. package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
  250. package/src/memory/migrations/index.ts +6 -0
  251. package/src/memory/migrations/registry.ts +8 -0
  252. package/src/memory/schema/conversations.ts +1 -0
  253. package/src/memory/schema/oauth.ts +18 -13
  254. package/src/oauth/AGENTS.md +76 -0
  255. package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
  256. package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
  257. package/src/oauth/byo-connection.test.ts +8 -8
  258. package/src/oauth/byo-connection.ts +7 -7
  259. package/src/oauth/connect-orchestrator.ts +23 -21
  260. package/src/oauth/connect-types.ts +3 -3
  261. package/src/oauth/connection-resolver.test.ts +17 -4
  262. package/src/oauth/connection-resolver.ts +16 -16
  263. package/src/oauth/connection.ts +1 -1
  264. package/src/oauth/manual-token-connection.ts +13 -13
  265. package/src/oauth/oauth-store.ts +214 -100
  266. package/src/oauth/platform-connection.test.ts +3 -3
  267. package/src/oauth/platform-connection.ts +4 -4
  268. package/src/oauth/provider-serializer.ts +31 -5
  269. package/src/oauth/revoke.ts +76 -0
  270. package/src/oauth/seed-providers.ts +126 -87
  271. package/src/oauth/token-persistence.ts +1 -1
  272. package/src/permissions/permission-mode.ts +4 -11
  273. package/src/permissions/prompter.ts +13 -1
  274. package/src/permissions/v2-consent-policy.ts +87 -0
  275. package/src/prompts/system-prompt.ts +18 -21
  276. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
  277. package/src/prompts/templates/BOOTSTRAP.md +59 -105
  278. package/src/providers/anthropic/client.ts +1 -0
  279. package/src/providers/types.ts +1 -1
  280. package/src/runtime/AGENTS.md +23 -0
  281. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
  282. package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
  283. package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
  284. package/src/runtime/assistant-event-hub.ts +2 -2
  285. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
  286. package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
  287. package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
  288. package/src/runtime/auth/middleware.ts +98 -0
  289. package/src/runtime/auth/route-policy.ts +6 -7
  290. package/src/runtime/capability-tokens.ts +414 -0
  291. package/src/runtime/channel-approvals.ts +18 -5
  292. package/src/runtime/chrome-extension-registry.ts +332 -0
  293. package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
  294. package/src/runtime/guardian-decision-types.ts +7 -0
  295. package/src/runtime/http-server.ts +425 -70
  296. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
  297. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
  298. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
  299. package/src/runtime/migrations/migration-transport.ts +6 -0
  300. package/src/runtime/migrations/migration-wizard.ts +22 -2
  301. package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
  302. package/src/runtime/migrations/vbundle-builder.ts +145 -38
  303. package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
  304. package/src/runtime/migrations/vbundle-importer.ts +55 -5
  305. package/src/runtime/pending-interactions.ts +29 -13
  306. package/src/runtime/routes/approval-routes.ts +90 -16
  307. package/src/runtime/routes/browser-cdp-routes.ts +229 -0
  308. package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
  309. package/src/runtime/routes/conversation-analysis-routes.ts +2 -1
  310. package/src/runtime/routes/conversation-management-routes.ts +108 -0
  311. package/src/runtime/routes/conversation-routes.ts +301 -27
  312. package/src/runtime/routes/conversation-starter-routes.ts +78 -16
  313. package/src/runtime/routes/guardian-action-routes.ts +24 -13
  314. package/src/runtime/routes/host-browser-routes.ts +279 -0
  315. package/src/runtime/routes/host-file-routes.ts +9 -1
  316. package/src/runtime/routes/identity-routes.ts +259 -16
  317. package/src/runtime/routes/log-export-routes.ts +42 -22
  318. package/src/runtime/routes/memory-item-routes.ts +1 -7
  319. package/src/runtime/routes/migration-routes.ts +87 -2
  320. package/src/runtime/routes/oauth-apps.ts +15 -17
  321. package/src/runtime/routes/oauth-providers.ts +4 -0
  322. package/src/runtime/routes/schedule-routes.ts +24 -11
  323. package/src/runtime/routes/settings-routes.ts +9 -97
  324. package/src/runtime/routes/skills-routes.ts +52 -2
  325. package/src/runtime/routes/subagents-routes.ts +14 -10
  326. package/src/runtime/routes/usage-routes.ts +8 -7
  327. package/src/runtime/routes/workspace-routes.test.ts +22 -0
  328. package/src/runtime/routes/workspace-routes.ts +8 -1
  329. package/src/runtime/routes/workspace-utils.ts +2 -0
  330. package/src/schedule/scheduler.ts +7 -5
  331. package/src/security/ces-credential-client.ts +20 -0
  332. package/src/security/ces-rpc-credential-backend.ts +17 -0
  333. package/src/security/credential-backend.ts +5 -0
  334. package/src/security/oauth2.ts +42 -25
  335. package/src/security/secure-keys.ts +118 -25
  336. package/src/security/token-manager.ts +23 -10
  337. package/src/skills/catalog-files.ts +492 -0
  338. package/src/subagent/manager.ts +131 -26
  339. package/src/subagent/types.ts +19 -0
  340. package/src/tools/apps/executors.ts +11 -2
  341. package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
  342. package/src/tools/browser/auth-detector.ts +43 -12
  343. package/src/tools/browser/browser-execution.ts +645 -340
  344. package/src/tools/browser/browser-manager.ts +36 -12
  345. package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
  346. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
  347. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
  348. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
  349. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
  350. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
  351. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
  352. package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
  353. package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
  354. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
  355. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
  356. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
  357. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
  358. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
  359. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
  360. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
  361. package/src/tools/browser/cdp-client/errors.ts +34 -0
  362. package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
  363. package/src/tools/browser/cdp-client/factory.ts +204 -0
  364. package/src/tools/browser/cdp-client/index.ts +14 -0
  365. package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
  366. package/src/tools/browser/cdp-client/types.ts +52 -0
  367. package/src/tools/filesystem/edit.ts +1 -1
  368. package/src/tools/filesystem/list.ts +1 -1
  369. package/src/tools/filesystem/read.ts +1 -1
  370. package/src/tools/filesystem/write.ts +2 -1
  371. package/src/tools/host-filesystem/edit.ts +1 -1
  372. package/src/tools/host-filesystem/read.ts +12 -15
  373. package/src/tools/host-filesystem/write.ts +1 -1
  374. package/src/tools/host-terminal/host-shell.ts +21 -16
  375. package/src/tools/permission-checker.ts +77 -82
  376. package/src/tools/registry.ts +0 -2
  377. package/src/tools/secret-detection-handler.ts +34 -0
  378. package/src/tools/shared/filesystem/image-read.ts +61 -40
  379. package/src/tools/subagent/spawn.ts +47 -3
  380. package/src/tools/subagent/status.ts +2 -0
  381. package/src/tools/system/register.ts +2 -16
  382. package/src/tools/terminal/safe-env.ts +7 -0
  383. package/src/tools/terminal/shell.ts +21 -16
  384. package/src/tools/tool-approval-handler.ts +48 -2
  385. package/src/tools/types.ts +2 -0
  386. package/src/util/platform.ts +14 -19
  387. package/src/workspace/top-level-renderer.ts +19 -1
  388. package/src/__tests__/chrome-cdp.test.ts +0 -419
  389. package/src/__tests__/permission-mode-sse.test.ts +0 -418
  390. package/src/__tests__/permission-mode-store.test.ts +0 -277
  391. package/src/browser-extension-relay/protocol.ts +0 -63
  392. package/src/browser-extension-relay/server.ts +0 -203
  393. package/src/config/schemas/sandbox.ts +0 -14
  394. package/src/permissions/permission-mode-store.ts +0 -180
  395. package/src/tools/browser/chrome-cdp.ts +0 -239
  396. package/src/tools/system/set-permission-mode.ts +0 -103
@@ -14,6 +14,8 @@ interface PendingRequest {
14
14
  reject: (err: Error) => void;
15
15
  timer: ReturnType<typeof setTimeout>;
16
16
  timeoutSec: number;
17
+ /** Detach the abort listener from the caller's signal. No-op when no signal was passed. */
18
+ detachAbort: () => void;
17
19
  }
18
20
 
19
21
  export class HostBashProxy {
@@ -60,8 +62,14 @@ export class HostBashProxy {
60
62
  const timeoutSec = input.timeout_seconds ?? shellMaxTimeoutSec;
61
63
  // Proxy timeout: slightly after client-side timeout, but before executor's outer timeout
62
64
  const proxyTimeoutSec = timeoutSec + 3;
65
+
66
+ // Declared up-front so onAbort (defined before detachAbort is assigned)
67
+ // can close over a stable reference once it's wired below.
68
+ let detachAbort: () => void = () => {};
69
+
63
70
  const timer = setTimeout(() => {
64
71
  this.pending.delete(requestId);
72
+ detachAbort();
65
73
  this.onInternalResolve?.(requestId);
66
74
  log.warn(
67
75
  { requestId, command: input.command },
@@ -78,13 +86,14 @@ export class HostBashProxy {
78
86
  );
79
87
  }, proxyTimeoutSec * 1000);
80
88
 
81
- this.pending.set(requestId, { resolve, reject, timer, timeoutSec });
82
-
83
89
  if (signal) {
84
90
  const onAbort = () => {
85
91
  if (this.pending.has(requestId)) {
86
92
  clearTimeout(timer);
87
93
  this.pending.delete(requestId);
94
+ // Abort fired — nothing to detach, but call the no-op for symmetry
95
+ // so callers can rely on detachAbort being idempotent.
96
+ detachAbort();
88
97
  this.onInternalResolve?.(requestId);
89
98
  try {
90
99
  this.sendToClient({
@@ -98,19 +107,43 @@ export class HostBashProxy {
98
107
  }
99
108
  };
100
109
  signal.addEventListener("abort", onAbort, { once: true });
110
+ detachAbort = () => signal.removeEventListener("abort", onAbort);
101
111
  }
102
112
 
103
- this.sendToClient({
104
- type: "host_bash_request",
105
- requestId,
106
- conversationId,
107
- command: input.command,
108
- working_dir: input.working_dir,
109
- timeout_seconds: input.timeout_seconds,
110
- ...(input.env && Object.keys(input.env).length > 0
111
- ? { env: input.env }
112
- : {}),
113
- } as ServerMessage);
113
+ this.pending.set(requestId, {
114
+ resolve,
115
+ reject,
116
+ timer,
117
+ timeoutSec,
118
+ detachAbort,
119
+ });
120
+
121
+ try {
122
+ this.sendToClient({
123
+ type: "host_bash_request",
124
+ requestId,
125
+ conversationId,
126
+ command: input.command,
127
+ working_dir: input.working_dir,
128
+ timeout_seconds: input.timeout_seconds,
129
+ ...(input.env && Object.keys(input.env).length > 0
130
+ ? { env: input.env }
131
+ : {}),
132
+ } as ServerMessage);
133
+ } catch (err) {
134
+ // Sender threw synchronously (e.g. client transport error during
135
+ // event emission). Clean up pending state and timer so we don't
136
+ // leak an in-flight entry that nothing will ever resolve.
137
+ clearTimeout(timer);
138
+ this.pending.delete(requestId);
139
+ detachAbort();
140
+ this.onInternalResolve?.(requestId);
141
+ log.warn(
142
+ { requestId, command: input.command, err },
143
+ "Host bash proxy send failed",
144
+ );
145
+ reject(err instanceof Error ? err : new Error(String(err)));
146
+ }
114
147
  });
115
148
  }
116
149
 
@@ -129,6 +162,7 @@ export class HostBashProxy {
129
162
  return;
130
163
  }
131
164
  clearTimeout(entry.timer);
165
+ entry.detachAbort();
132
166
  this.pending.delete(requestId);
133
167
  const result = formatShellOutput(
134
168
  response.stdout,
@@ -151,6 +185,7 @@ export class HostBashProxy {
151
185
  dispose(): void {
152
186
  for (const [requestId, entry] of this.pending) {
153
187
  clearTimeout(entry.timer);
188
+ entry.detachAbort();
154
189
  this.onInternalResolve?.(requestId);
155
190
  try {
156
191
  this.sendToClient({
@@ -0,0 +1,191 @@
1
+ import { v4 as uuid } from "uuid";
2
+
3
+ import type { ToolExecutionResult } from "../tools/types.js";
4
+ import { AssistantError, ErrorCode } from "../util/errors.js";
5
+ import { getLogger } from "../util/logger.js";
6
+ import type { ServerMessage } from "./message-protocol.js";
7
+ import type { HostBrowserRequest } from "./message-types/host-browser.js";
8
+
9
+ /** Distributive omit that preserves union variant fields. */
10
+ type DistributiveOmit<T, K extends PropertyKey> = T extends unknown
11
+ ? Omit<T, K>
12
+ : never;
13
+
14
+ /** Clean input type for callers — transport envelope fields are added by the proxy. */
15
+ export type HostBrowserInput = DistributiveOmit<
16
+ HostBrowserRequest,
17
+ "type" | "requestId" | "conversationId"
18
+ >;
19
+
20
+ const log = getLogger("host-browser-proxy");
21
+
22
+ interface PendingRequest {
23
+ resolve: (result: ToolExecutionResult) => void;
24
+ reject: (err: Error) => void;
25
+ timer: ReturnType<typeof setTimeout>;
26
+ /** Detach the abort listener from the caller's signal. No-op when no signal was passed. */
27
+ detachAbort: () => void;
28
+ }
29
+
30
+ export class HostBrowserProxy {
31
+ private pending = new Map<string, PendingRequest>();
32
+ private sendToClient: (msg: ServerMessage) => void;
33
+ private onInternalResolve?: (requestId: string) => void;
34
+ private clientConnected = false;
35
+
36
+ constructor(
37
+ sendToClient: (msg: ServerMessage) => void,
38
+ onInternalResolve?: (requestId: string) => void,
39
+ ) {
40
+ this.sendToClient = sendToClient;
41
+ this.onInternalResolve = onInternalResolve;
42
+ }
43
+
44
+ updateSender(
45
+ sendToClient: (msg: ServerMessage) => void,
46
+ clientConnected: boolean,
47
+ ): void {
48
+ this.sendToClient = sendToClient;
49
+ this.clientConnected = clientConnected;
50
+ }
51
+
52
+ request(
53
+ input: HostBrowserInput,
54
+ conversationId: string,
55
+ signal?: AbortSignal,
56
+ ): Promise<ToolExecutionResult> {
57
+ if (signal?.aborted) {
58
+ return Promise.resolve({ content: "Aborted", isError: true });
59
+ }
60
+
61
+ const requestId = uuid();
62
+
63
+ return new Promise<ToolExecutionResult>((resolve, reject) => {
64
+ // CDP operations should be fast — 30 second default timeout matches host_file.
65
+ const timeoutSec = input.timeout_seconds ?? 30;
66
+
67
+ // Declared up-front so onAbort (defined before detachAbort is assigned)
68
+ // can close over a stable reference once it's wired below.
69
+ let detachAbort: () => void = () => {};
70
+
71
+ const timer = setTimeout(() => {
72
+ this.pending.delete(requestId);
73
+ detachAbort();
74
+ this.onInternalResolve?.(requestId);
75
+ log.warn(
76
+ { requestId, cdpMethod: input.cdpMethod },
77
+ "Host browser proxy request timed out",
78
+ );
79
+ resolve({
80
+ content: "Host browser proxy timed out waiting for client response",
81
+ isError: true,
82
+ });
83
+ }, timeoutSec * 1000);
84
+
85
+ if (signal) {
86
+ const onAbort = () => {
87
+ if (this.pending.has(requestId)) {
88
+ clearTimeout(timer);
89
+ this.pending.delete(requestId);
90
+ // Abort fired — nothing to detach, but call the no-op for symmetry
91
+ // so callers can rely on detachAbort being idempotent.
92
+ detachAbort();
93
+ this.onInternalResolve?.(requestId);
94
+ try {
95
+ this.sendToClient({
96
+ type: "host_browser_cancel",
97
+ requestId,
98
+ } as ServerMessage);
99
+ } catch {
100
+ // Best-effort cancel notification — connection may already be closed.
101
+ }
102
+ resolve({ content: "Aborted", isError: true });
103
+ }
104
+ };
105
+ signal.addEventListener("abort", onAbort, { once: true });
106
+ detachAbort = () => signal.removeEventListener("abort", onAbort);
107
+ }
108
+
109
+ this.pending.set(requestId, { resolve, reject, timer, detachAbort });
110
+
111
+ try {
112
+ this.sendToClient({
113
+ ...input,
114
+ type: "host_browser_request",
115
+ requestId,
116
+ conversationId,
117
+ } as ServerMessage);
118
+ } catch (err) {
119
+ // Sender threw synchronously (e.g. client transport error during
120
+ // event emission). Clean up pending state and timer so we don't
121
+ // leak an in-flight entry that nothing will ever resolve.
122
+ clearTimeout(timer);
123
+ this.pending.delete(requestId);
124
+ detachAbort();
125
+ this.onInternalResolve?.(requestId);
126
+ log.warn(
127
+ { requestId, cdpMethod: input.cdpMethod, err },
128
+ "Host browser proxy send failed",
129
+ );
130
+ reject(err instanceof Error ? err : new Error(String(err)));
131
+ }
132
+ });
133
+ }
134
+
135
+ resolve(
136
+ requestId: string,
137
+ response: { content: string; isError: boolean },
138
+ ): void {
139
+ const entry = this.pending.get(requestId);
140
+ if (!entry) {
141
+ // Benign race, not an error. A late result frame with no matching
142
+ // pending entry means one of:
143
+ // - the proxy-side setTimeout has already resolved the caller;
144
+ // - the caller's AbortSignal fired and the entry was torn down;
145
+ // - a duplicate result frame was delivered (e.g. retry after a
146
+ // transient WS drop).
147
+ // Log at debug so operators don't chase false-positive "timeout"
148
+ // alerts on what is actually a cleanly-handled race.
149
+ log.debug(
150
+ { requestId },
151
+ "Ignoring host_browser_result for unknown or already-resolved request",
152
+ );
153
+ return;
154
+ }
155
+ clearTimeout(entry.timer);
156
+ entry.detachAbort();
157
+ this.pending.delete(requestId);
158
+ entry.resolve({ content: response.content, isError: response.isError });
159
+ }
160
+
161
+ hasPendingRequest(requestId: string): boolean {
162
+ return this.pending.has(requestId);
163
+ }
164
+
165
+ isAvailable(): boolean {
166
+ return this.clientConnected;
167
+ }
168
+
169
+ dispose(): void {
170
+ for (const [requestId, entry] of this.pending) {
171
+ clearTimeout(entry.timer);
172
+ entry.detachAbort();
173
+ this.onInternalResolve?.(requestId);
174
+ try {
175
+ this.sendToClient({
176
+ type: "host_browser_cancel",
177
+ requestId,
178
+ } as ServerMessage);
179
+ } catch {
180
+ // Best-effort cancel notification — connection may already be closed.
181
+ }
182
+ entry.reject(
183
+ new AssistantError(
184
+ "Host browser proxy disposed",
185
+ ErrorCode.INTERNAL_ERROR,
186
+ ),
187
+ );
188
+ }
189
+ this.pending.clear();
190
+ }
191
+ }
@@ -57,6 +57,8 @@ interface PendingRequest {
57
57
  resolve: (result: ToolExecutionResult) => void;
58
58
  reject: (err: Error) => void;
59
59
  timer: ReturnType<typeof setTimeout>;
60
+ /** Detach the abort listener from the caller's signal. No-op when no signal was passed. */
61
+ detachAbort: () => void;
60
62
  }
61
63
 
62
64
  // ---------------------------------------------------------------------------
@@ -152,8 +154,13 @@ export class HostCuProxy {
152
154
  const requestId = uuid();
153
155
 
154
156
  return new Promise<ToolExecutionResult>((resolve, reject) => {
157
+ // Declared up-front so onAbort (defined before detachAbort is assigned)
158
+ // can close over a stable reference once it's wired below.
159
+ let detachAbort: () => void = () => {};
160
+
155
161
  const timer = setTimeout(() => {
156
162
  this.pending.delete(requestId);
163
+ detachAbort();
157
164
  this.onInternalResolve?.(requestId);
158
165
  log.warn({ requestId, toolName }, "Host CU proxy request timed out");
159
166
  resolve({
@@ -162,13 +169,14 @@ export class HostCuProxy {
162
169
  });
163
170
  }, REQUEST_TIMEOUT_SEC * 1000);
164
171
 
165
- this.pending.set(requestId, { resolve, reject, timer });
166
-
167
172
  if (signal) {
168
173
  const onAbort = () => {
169
174
  if (this.pending.has(requestId)) {
170
175
  clearTimeout(timer);
171
176
  this.pending.delete(requestId);
177
+ // Abort fired — nothing to detach, but call the no-op for symmetry
178
+ // so callers can rely on detachAbort being idempotent.
179
+ detachAbort();
172
180
  this.onInternalResolve?.(requestId);
173
181
  try {
174
182
  this.sendToClient({
@@ -182,17 +190,32 @@ export class HostCuProxy {
182
190
  }
183
191
  };
184
192
  signal.addEventListener("abort", onAbort, { once: true });
193
+ detachAbort = () => signal.removeEventListener("abort", onAbort);
185
194
  }
186
195
 
187
- this.sendToClient({
188
- type: "host_cu_request",
189
- requestId,
190
- conversationId,
191
- toolName,
192
- input,
193
- stepNumber,
194
- reasoning,
195
- } as ServerMessage);
196
+ this.pending.set(requestId, { resolve, reject, timer, detachAbort });
197
+
198
+ try {
199
+ this.sendToClient({
200
+ type: "host_cu_request",
201
+ requestId,
202
+ conversationId,
203
+ toolName,
204
+ input,
205
+ stepNumber,
206
+ reasoning,
207
+ } as ServerMessage);
208
+ } catch (err) {
209
+ // Sender threw synchronously (e.g. client transport error during
210
+ // event emission). Clean up pending state and timer so we don't
211
+ // leak an in-flight entry that nothing will ever resolve.
212
+ clearTimeout(timer);
213
+ this.pending.delete(requestId);
214
+ detachAbort();
215
+ this.onInternalResolve?.(requestId);
216
+ log.warn({ requestId, toolName, err }, "Host CU proxy send failed");
217
+ reject(err instanceof Error ? err : new Error(String(err)));
218
+ }
196
219
  });
197
220
  }
198
221
 
@@ -203,6 +226,7 @@ export class HostCuProxy {
203
226
  return;
204
227
  }
205
228
  clearTimeout(entry.timer);
229
+ entry.detachAbort();
206
230
  this.pending.delete(requestId);
207
231
 
208
232
  // Capture pre-update state so formatObservation sees the correct previous AX tree
@@ -388,6 +412,7 @@ export class HostCuProxy {
388
412
  dispose(): void {
389
413
  for (const [requestId, entry] of this.pending) {
390
414
  clearTimeout(entry.timer);
415
+ entry.detachAbort();
391
416
  this.onInternalResolve?.(requestId);
392
417
  try {
393
418
  this.sendToClient({
@@ -1,5 +1,6 @@
1
1
  import { v4 as uuid } from "uuid";
2
2
 
3
+ import { readImageBase64 } from "../tools/shared/filesystem/image-read.js";
3
4
  import type { ToolExecutionResult } from "../tools/types.js";
4
5
  import { AssistantError, ErrorCode } from "../util/errors.js";
5
6
  import { getLogger } from "../util/logger.js";
@@ -23,6 +24,10 @@ interface PendingRequest {
23
24
  resolve: (result: ToolExecutionResult) => void;
24
25
  reject: (err: Error) => void;
25
26
  timer: ReturnType<typeof setTimeout>;
27
+ operation: HostFileInput["operation"];
28
+ path: string;
29
+ /** Detach the abort listener from the caller's signal. No-op when no signal was passed. */
30
+ detachAbort: () => void;
26
31
  }
27
32
 
28
33
  export class HostFileProxy {
@@ -61,8 +66,14 @@ export class HostFileProxy {
61
66
  return new Promise<ToolExecutionResult>((resolve, reject) => {
62
67
  // File operations should be fast — 30 second timeout.
63
68
  const timeoutSec = 30;
69
+
70
+ // Declared up-front so onAbort (defined before detachAbort is assigned)
71
+ // can close over a stable reference once it's wired below.
72
+ let detachAbort: () => void = () => {};
73
+
64
74
  const timer = setTimeout(() => {
65
75
  this.pending.delete(requestId);
76
+ detachAbort();
66
77
  this.onInternalResolve?.(requestId);
67
78
  log.warn(
68
79
  { requestId, operation: input.operation },
@@ -74,13 +85,14 @@ export class HostFileProxy {
74
85
  });
75
86
  }, timeoutSec * 1000);
76
87
 
77
- this.pending.set(requestId, { resolve, reject, timer });
78
-
79
88
  if (signal) {
80
89
  const onAbort = () => {
81
90
  if (this.pending.has(requestId)) {
82
91
  clearTimeout(timer);
83
92
  this.pending.delete(requestId);
93
+ // Abort fired — nothing to detach, but call the no-op for symmetry
94
+ // so callers can rely on detachAbort being idempotent.
95
+ detachAbort();
84
96
  this.onInternalResolve?.(requestId);
85
97
  try {
86
98
  this.sendToClient({
@@ -94,20 +106,45 @@ export class HostFileProxy {
94
106
  }
95
107
  };
96
108
  signal.addEventListener("abort", onAbort, { once: true });
109
+ detachAbort = () => signal.removeEventListener("abort", onAbort);
97
110
  }
98
111
 
99
- this.sendToClient({
100
- ...input,
101
- type: "host_file_request",
102
- requestId,
103
- conversationId,
104
- } as ServerMessage);
112
+ this.pending.set(requestId, {
113
+ resolve,
114
+ reject,
115
+ timer,
116
+ operation: input.operation,
117
+ path: input.path,
118
+ detachAbort,
119
+ });
120
+
121
+ try {
122
+ this.sendToClient({
123
+ ...input,
124
+ type: "host_file_request",
125
+ requestId,
126
+ conversationId,
127
+ } as ServerMessage);
128
+ } catch (err) {
129
+ // Sender threw synchronously (e.g. client transport error during
130
+ // event emission). Clean up pending state and timer so we don't
131
+ // leak an in-flight entry that nothing will ever resolve.
132
+ clearTimeout(timer);
133
+ this.pending.delete(requestId);
134
+ detachAbort();
135
+ this.onInternalResolve?.(requestId);
136
+ log.warn(
137
+ { requestId, operation: input.operation, err },
138
+ "Host file proxy send failed",
139
+ );
140
+ reject(err instanceof Error ? err : new Error(String(err)));
141
+ }
105
142
  });
106
143
  }
107
144
 
108
145
  resolve(
109
146
  requestId: string,
110
- response: { content: string; isError: boolean },
147
+ response: { content: string; isError: boolean; imageData?: string },
111
148
  ): void {
112
149
  const entry = this.pending.get(requestId);
113
150
  if (!entry) {
@@ -115,7 +152,17 @@ export class HostFileProxy {
115
152
  return;
116
153
  }
117
154
  clearTimeout(entry.timer);
155
+ entry.detachAbort();
118
156
  this.pending.delete(requestId);
157
+ if (
158
+ entry.operation === "read" &&
159
+ !response.isError &&
160
+ typeof response.imageData === "string" &&
161
+ response.imageData.length > 0
162
+ ) {
163
+ entry.resolve(readImageBase64(response.imageData, entry.path));
164
+ return;
165
+ }
119
166
  entry.resolve({ content: response.content, isError: response.isError });
120
167
  }
121
168
 
@@ -130,6 +177,7 @@ export class HostFileProxy {
130
177
  dispose(): void {
131
178
  for (const [requestId, entry] of this.pending) {
132
179
  clearTimeout(entry.timer);
180
+ entry.detachAbort();
133
181
  this.onInternalResolve?.(requestId);
134
182
  try {
135
183
  this.sendToClient({
@@ -75,6 +75,11 @@ import {
75
75
  mintPairingBearerToken,
76
76
  resolveSigningKey,
77
77
  } from "../runtime/auth/token-service.js";
78
+ import {
79
+ initCapabilityTokenSecret,
80
+ loadOrCreateCapabilityTokenSecret,
81
+ writeDaemonTokenFallback,
82
+ } from "../runtime/capability-tokens.js";
78
83
  import { ensureVellumGuardianBinding } from "../runtime/guardian-vellum-migration.js";
79
84
  import { RuntimeHttpServer } from "../runtime/http-server.js";
80
85
  import { startScheduler } from "../schedule/scheduler.js";
@@ -270,6 +275,20 @@ export async function runDaemon(): Promise<void> {
270
275
  const signingKey = resolveSigningKey();
271
276
  initAuthSigningKey(signingKey);
272
277
 
278
+ // Load (or generate + persist) the capability-token HMAC secret used
279
+ // to mint scoped tokens for the chrome extension pair endpoint.
280
+ // Wrapped in try/catch so a disk failure here never blocks startup —
281
+ // tokens can still be minted using a lazy on-demand load inside the
282
+ // capability-tokens module.
283
+ try {
284
+ initCapabilityTokenSecret(loadOrCreateCapabilityTokenSecret());
285
+ } catch (err) {
286
+ log.warn(
287
+ { err },
288
+ "Failed to pre-load capability token secret — continuing startup (lazy load will handle subsequent calls)",
289
+ );
290
+ }
291
+
273
292
  // Pre-populate the feature flag cache from the gateway so all
274
293
  // subsequent sync isAssistantFeatureFlagEnabled() calls have data.
275
294
  // Fired non-blocking so a slow or unreachable gateway doesn't delay
@@ -432,8 +451,11 @@ export async function runDaemon(): Promise<void> {
432
451
 
433
452
  // Ensure a vellum guardian binding exists so the identity system works
434
453
  // without requiring a manual bootstrap step.
454
+ let localGuardianPrincipalId = "local";
435
455
  try {
436
- ensureVellumGuardianBinding(DAEMON_INTERNAL_ASSISTANT_ID);
456
+ localGuardianPrincipalId = ensureVellumGuardianBinding(
457
+ DAEMON_INTERNAL_ASSISTANT_ID,
458
+ );
437
459
  } catch (err) {
438
460
  log.warn(
439
461
  { err },
@@ -441,6 +463,19 @@ export async function runDaemon(): Promise<void> {
441
463
  );
442
464
  }
443
465
 
466
+ // Write a dev-only fallback capability token to `~/.vellum/daemon-token`
467
+ // so developers can manually pair the chrome extension without the
468
+ // native messaging helper. Production pairing goes through
469
+ // `POST /v1/browser-extension-pair` via the native helper.
470
+ try {
471
+ writeDaemonTokenFallback(localGuardianPrincipalId);
472
+ } catch (err) {
473
+ log.warn(
474
+ { err },
475
+ "Failed to write dev daemon-token fallback — continuing startup",
476
+ );
477
+ }
478
+
444
479
  try {
445
480
  syncUpdateBulletinOnStartup();
446
481
  } catch (err) {
@@ -589,8 +624,6 @@ export async function runDaemon(): Promise<void> {
589
624
  }
590
625
  }
591
626
 
592
- await initializeProvidersAndTools(config);
593
-
594
627
  // Start the DaemonServer (conversation manager) before Qdrant so HTTP
595
628
  // routes can begin accepting requests while Qdrant initializes.
596
629
  log.info("Daemon startup: starting DaemonServer");
@@ -745,12 +778,17 @@ export async function runDaemon(): Promise<void> {
745
778
  conversationId,
746
779
  message,
747
780
  undefined,
748
- options?.trustClass
781
+ options
749
782
  ? {
750
- trustContext: {
751
- sourceChannel: "vellum",
752
- trustClass: options.trustClass,
753
- },
783
+ ...(options.trustClass
784
+ ? {
785
+ trustContext: {
786
+ sourceChannel: "vellum",
787
+ trustClass: options.trustClass,
788
+ },
789
+ }
790
+ : {}),
791
+ ...(options.taskRunId ? { taskRunId: options.taskRunId } : {}),
754
792
  }
755
793
  : undefined,
756
794
  );
@@ -1170,6 +1208,21 @@ export async function runDaemon(): Promise<void> {
1170
1208
  runtimeHttp = null;
1171
1209
  }
1172
1210
 
1211
+ // Initialize providers and tools after the HTTP server is listening so
1212
+ // health-check and pairing requests can be served immediately. Wrapped in
1213
+ // its own try/catch so a failure here doesn't tear down the running HTTP
1214
+ // server (DaemonServer.start() already calls initializeProviders internally
1215
+ // and tools are resolved lazily at conversation creation time).
1216
+ try {
1217
+ log.info("Daemon startup: initializing providers and tools");
1218
+ await initializeProvidersAndTools(config);
1219
+ } catch (err) {
1220
+ log.warn(
1221
+ { err },
1222
+ "Provider/tool initialization failed — continuing with degraded functionality",
1223
+ );
1224
+ }
1225
+
1173
1226
  writePid(process.pid);
1174
1227
  log.info({ pid: process.pid }, "Daemon started");
1175
1228
 
@@ -1198,10 +1251,11 @@ export async function runDaemon(): Promise<void> {
1198
1251
  if (!runtimeManager.isReady()) {
1199
1252
  log.info("Downloading embedding runtime in background...");
1200
1253
  await runtimeManager.ensureInstalled();
1201
- // Reset the localBackendBroken flag so auto mode retries local embeddings
1202
- const { clearEmbeddingBackendCache } =
1254
+ // Reset the sticky local-backend failure flag so auto mode retries
1255
+ // local embeddings without evicting a worker that may already be live.
1256
+ const { resetLocalEmbeddingFailureState } =
1203
1257
  await import("../memory/embedding-backend.js");
1204
- clearEmbeddingBackendCache();
1258
+ resetLocalEmbeddingFailureState();
1205
1259
  log.info("Embedding runtime download complete");
1206
1260
  }
1207
1261
  } catch (err) {
@@ -23,6 +23,7 @@ export * from "./message-types/diagnostics.js";
23
23
  export * from "./message-types/documents.js";
24
24
  export * from "./message-types/guardian-actions.js";
25
25
  export * from "./message-types/host-bash.js";
26
+ export * from "./message-types/host-browser.js";
26
27
  export * from "./message-types/host-cu.js";
27
28
  export * from "./message-types/host-file.js";
28
29
  export * from "./message-types/inbox.js";
@@ -77,6 +78,10 @@ import type {
77
78
  _GuardianActionsServerMessages,
78
79
  } from "./message-types/guardian-actions.js";
79
80
  import type { _HostBashServerMessages } from "./message-types/host-bash.js";
81
+ import type {
82
+ _HostBrowserClientMessages,
83
+ _HostBrowserServerMessages,
84
+ } from "./message-types/host-browser.js";
80
85
  import type { _HostCuServerMessages } from "./message-types/host-cu.js";
81
86
  import type { _HostFileServerMessages } from "./message-types/host-file.js";
82
87
  import type {
@@ -157,6 +162,7 @@ export type ClientMessage =
157
162
  | _ContactsClientMessages
158
163
  | _WorkItemsClientMessages
159
164
  | _BrowserClientMessages
165
+ | _HostBrowserClientMessages
160
166
  | _SubagentsClientMessages
161
167
  | _DocumentsClientMessages
162
168
  | _GuardianActionsClientMessages
@@ -186,6 +192,7 @@ export type ServerMessage =
186
192
  | _DocumentsServerMessages
187
193
  | _GuardianActionsServerMessages
188
194
  | _HostBashServerMessages
195
+ | _HostBrowserServerMessages
189
196
  | _HostCuServerMessages
190
197
  | _HostFileServerMessages
191
198
  | _MemoryServerMessages