@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
@@ -194,18 +194,63 @@ export function markTaskPending(
194
194
 
195
195
  /**
196
196
  * Build the task list from definitions and completion state.
197
+ *
198
+ * When credential import results are provided:
199
+ * - If all credentials were imported successfully, the re-enter-secrets task
200
+ * is marked as auto-completed with an updated description.
201
+ * - If some credentials failed, the description is updated to list only the
202
+ * failed credentials that need manual re-entry.
203
+ * - If no credentials were in the bundle (legacy), the original behavior is kept.
197
204
  */
198
- function buildTasks(completionState: RebindTaskCompletionState): RebindTask[] {
199
- return TASK_DEFINITIONS.map((def) => ({
200
- id: def.id,
201
- title: def.title,
202
- description: def.description,
203
- status: completionState[def.id]
204
- ? ("complete" as const)
205
- : ("pending" as const),
206
- required: def.required,
207
- ...(def.helpText !== undefined ? { helpText: def.helpText } : {}),
208
- }));
205
+ function buildTasks(
206
+ completionState: RebindTaskCompletionState,
207
+ credentialsImported?: MigrationWizardState["credentialsImported"],
208
+ ): RebindTask[] {
209
+ return TASK_DEFINITIONS.map((def) => {
210
+ // Apply credential import awareness to the re-enter-secrets task
211
+ if (def.id === "re-enter-secrets" && credentialsImported) {
212
+ if (credentialsImported.failed === 0 && credentialsImported.total > 0) {
213
+ // All credentials imported successfully — auto-complete this task
214
+ return {
215
+ id: def.id,
216
+ title: "API keys and secrets transferred",
217
+ description: `All ${credentialsImported.total} credential(s) were automatically imported from the bundle. No manual re-entry needed.`,
218
+ status: "complete" as const,
219
+ required: def.required,
220
+ helpText:
221
+ "Credentials were securely transferred as part of the migration bundle. You can verify them in Settings > Models & Services.",
222
+ };
223
+ }
224
+
225
+ if (credentialsImported.failed > 0) {
226
+ // Partial failure — show only the failed credentials
227
+ const failedList = credentialsImported.failedAccounts
228
+ .map((a) => `"${a}"`)
229
+ .join(", ");
230
+ return {
231
+ id: def.id,
232
+ title: "Re-enter failed credentials",
233
+ description: `${credentialsImported.succeeded} of ${credentialsImported.total} credential(s) were imported automatically. The following failed and need manual re-entry: ${failedList}.`,
234
+ status: completionState[def.id]
235
+ ? ("complete" as const)
236
+ : ("pending" as const),
237
+ required: def.required,
238
+ helpText: `Navigate to Settings > Models & Services to re-enter the failed credential(s): ${failedList}.`,
239
+ };
240
+ }
241
+ }
242
+
243
+ return {
244
+ id: def.id,
245
+ title: def.title,
246
+ description: def.description,
247
+ status: completionState[def.id]
248
+ ? ("complete" as const)
249
+ : ("pending" as const),
250
+ required: def.required,
251
+ ...(def.helpText !== undefined ? { helpText: def.helpText } : {}),
252
+ };
253
+ });
209
254
  }
210
255
 
211
256
  /**
@@ -235,6 +280,7 @@ export function deriveRebindSecretsScreenState(
235
280
  completionState: RebindTaskCompletionState,
236
281
  ): RebindSecretsScreenState {
237
282
  const rebindStep = wizardState.steps["rebind-secrets"];
283
+ const credInfo = wizardState.credentialsImported;
238
284
 
239
285
  // Not yet accessible -- earlier steps incomplete
240
286
  if (
@@ -246,15 +292,22 @@ export function deriveRebindSecretsScreenState(
246
292
  }
247
293
  }
248
294
 
295
+ // Apply credential-aware completion state: if all credentials imported
296
+ // successfully, treat re-enter-secrets as auto-completed.
297
+ let effectiveCompletion = completionState;
298
+ if (credInfo && credInfo.total > 0 && credInfo.failed === 0) {
299
+ effectiveCompletion = markTaskComplete(completionState, "re-enter-secrets");
300
+ }
301
+
249
302
  // Already completed (viewing from a later step or after completion)
250
303
  if (rebindStep.status === "success") {
251
- const tasks = buildTasks(completionState);
304
+ const tasks = buildTasks(effectiveCompletion, credInfo);
252
305
  return { phase: "complete", tasks };
253
306
  }
254
307
 
255
308
  // Active -- show the checklist
256
309
  if (wizardState.currentStep === "rebind-secrets") {
257
- const tasks = buildTasks(completionState);
310
+ const tasks = buildTasks(effectiveCompletion, credInfo);
258
311
  const requiredTasks = tasks.filter((t) => t.required);
259
312
  const completedTasks = tasks.filter((t) => t.status === "complete");
260
313
  const requiredCompletedTasks = requiredTasks.filter(
@@ -264,7 +317,7 @@ export function deriveRebindSecretsScreenState(
264
317
  return {
265
318
  phase: "active",
266
319
  tasks,
267
- allRequiredComplete: areAllRequiredTasksComplete(completionState),
320
+ allRequiredComplete: areAllRequiredTasksComplete(effectiveCompletion),
268
321
  completedCount: completedTasks.length,
269
322
  totalCount: tasks.length,
270
323
  requiredCount: requiredTasks.length,
@@ -289,7 +342,15 @@ export function completeMigration(
289
342
  wizardState: MigrationWizardState,
290
343
  completionState: RebindTaskCompletionState,
291
344
  ): MigrationWizardState {
292
- if (!areAllRequiredTasksComplete(completionState)) {
345
+ // Apply credential-aware effective completion: if all credentials were
346
+ // imported successfully, treat re-enter-secrets as auto-completed
347
+ // (mirrors the logic in deriveRebindSecretsScreenState).
348
+ let effectiveCompletion = completionState;
349
+ const credInfo = wizardState?.credentialsImported;
350
+ if (credInfo && credInfo.total > 0 && credInfo.failed === 0) {
351
+ effectiveCompletion = { ...completionState, "re-enter-secrets": true };
352
+ }
353
+ if (!areAllRequiredTasksComplete(effectiveCompletion)) {
293
354
  throw new Error(
294
355
  "Cannot complete migration: not all required tasks are done",
295
356
  );
@@ -28,6 +28,7 @@ import { Readable } from "node:stream";
28
28
  import { pipeline } from "node:stream/promises";
29
29
  import { createGzip, gzipSync } from "node:zlib";
30
30
 
31
+ import { sanitizeConfigForTransfer } from "../../config/sanitize-for-transfer.js";
31
32
  import type {
32
33
  ManifestFileEntryType,
33
34
  ManifestType,
@@ -66,6 +67,20 @@ interface FileMetadata {
66
67
  size: number;
67
68
  }
68
69
 
70
+ /** In-memory entry for data not backed by a file on disk (e.g. credentials). */
71
+ interface InMemoryEntry {
72
+ archivePath: string;
73
+ data: Uint8Array;
74
+ size: number;
75
+ }
76
+
77
+ /** Union of disk-backed and in-memory tar stream entries. */
78
+ type TarStreamEntry = FileMetadata | InMemoryEntry;
79
+
80
+ function isInMemoryEntry(entry: TarStreamEntry): entry is InMemoryEntry {
81
+ return "data" in entry;
82
+ }
83
+
69
84
  // ---------------------------------------------------------------------------
70
85
  // Hash helpers
71
86
  // ---------------------------------------------------------------------------
@@ -439,6 +454,8 @@ export interface BuildExportVBundleOptions {
439
454
  * Called before the workspace walk so the DB file is up to date.
440
455
  */
441
456
  checkpoint?: () => void;
457
+ /** Optional credential entries to include in the archive under credentials/ prefix. */
458
+ credentials?: Array<{ account: string; value: string }>;
442
459
  }
443
460
 
444
461
  /**
@@ -456,8 +473,15 @@ export interface BuildExportVBundleOptions {
456
473
  export function buildExportVBundle(
457
474
  options: BuildExportVBundleOptions,
458
475
  ): BuildVBundleResult {
459
- const { source, description, checkpoint, trustPath, workspaceDir, hooksDir } =
460
- options;
476
+ const {
477
+ source,
478
+ description,
479
+ checkpoint,
480
+ trustPath,
481
+ workspaceDir,
482
+ hooksDir,
483
+ credentials,
484
+ } = options;
461
485
 
462
486
  // Flush WAL to the main database file before reading so the export
463
487
  // captures all committed rows (SQLite WAL mode keeps recent writes
@@ -483,6 +507,14 @@ export function buildExportVBundle(
483
507
  );
484
508
  }
485
509
 
510
+ // Sanitize workspace/config.json to strip environment-specific fields
511
+ const configEntry = files.find((f) => f.path === "workspace/config.json");
512
+ if (configEntry) {
513
+ const configJson = new TextDecoder().decode(configEntry.data);
514
+ const sanitized = sanitizeConfigForTransfer(configJson);
515
+ configEntry.data = new TextEncoder().encode(sanitized);
516
+ }
517
+
486
518
  // Include hooks directory if it exists (lives at ~/.vellum/hooks/, outside workspace).
487
519
  if (hooksDir && existsSync(hooksDir) && lstatSync(hooksDir).isDirectory()) {
488
520
  files.push(...walkDirectory(hooksDir, "hooks"));
@@ -494,6 +526,14 @@ export function buildExportVBundle(
494
526
  files.push({ path: "trust/trust.json", data: trustData });
495
527
  }
496
528
 
529
+ // Include credential entries if provided
530
+ if (credentials?.length) {
531
+ for (const { account, value } of credentials) {
532
+ const data = new TextEncoder().encode(value);
533
+ files.push({ path: `credentials/${account}`, data });
534
+ }
535
+ }
536
+
497
537
  return buildVBundle({
498
538
  files,
499
539
  source: source ?? "runtime-export",
@@ -688,7 +728,7 @@ function tarPaddingBytes(dataSize: number): Uint8Array {
688
728
  */
689
729
  async function* generateTarStream(
690
730
  manifestJson: Uint8Array,
691
- files: FileMetadata[],
731
+ files: TarStreamEntry[],
692
732
  ): AsyncGenerator<Uint8Array> {
693
733
  // Manifest entry
694
734
  yield createPaxAndHeaderBlocks("manifest.json", manifestJson.length);
@@ -697,43 +737,52 @@ async function* generateTarStream(
697
737
 
698
738
  // File entries
699
739
  for (const file of files) {
700
- yield createPaxAndHeaderBlocks(file.archivePath, file.size);
701
-
702
- // Stream exactly file.size bytes from disk. Capping the read at the
703
- // declared size keeps the tar structure valid even if the file grows
704
- // between passes (common for log files on active assistants). If the
705
- // file shrinks below the declared size, zero-pad to maintain block
706
- // alignment. The WAL checkpoint before export is the primary
707
- // consistency mechanism for the database.
708
- let bytesWritten = 0;
709
- if (file.size > 0) {
710
- try {
711
- const stream = createReadStream(file.diskPath, {
712
- start: 0,
713
- end: file.size - 1,
714
- });
715
- for await (const chunk of stream) {
716
- const data =
717
- chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
718
- bytesWritten += data.length;
719
- yield data;
740
+ const entrySize = isInMemoryEntry(file) ? file.size : file.size;
741
+ yield createPaxAndHeaderBlocks(file.archivePath, entrySize);
742
+
743
+ if (isInMemoryEntry(file)) {
744
+ // In-memory entry yield data directly
745
+ if (file.size > 0) {
746
+ yield file.data;
747
+ }
748
+ } else {
749
+ // Disk-backed entry stream from disk
750
+ // Stream exactly file.size bytes from disk. Capping the read at the
751
+ // declared size keeps the tar structure valid even if the file grows
752
+ // between passes (common for log files on active assistants). If the
753
+ // file shrinks below the declared size, zero-pad to maintain block
754
+ // alignment. The WAL checkpoint before export is the primary
755
+ // consistency mechanism for the database.
756
+ let bytesWritten = 0;
757
+ if (file.size > 0) {
758
+ try {
759
+ const stream = createReadStream(file.diskPath, {
760
+ start: 0,
761
+ end: file.size - 1,
762
+ });
763
+ for await (const chunk of stream) {
764
+ const data =
765
+ chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
766
+ bytesWritten += data.length;
767
+ yield data;
768
+ }
769
+ } catch {
770
+ // File was deleted or rotated between passes — emit zeros for
771
+ // the full declared size so the tar structure stays valid
720
772
  }
721
- } catch {
722
- // File was deleted or rotated between passes — emit zeros for
723
- // the full declared size so the tar structure stays valid
724
773
  }
725
- }
726
774
 
727
- // If the file shrank, pad with zeros in bounded chunks to reach
728
- // the declared size without a large single allocation
729
- let remaining = file.size - bytesWritten;
730
- while (remaining > 0) {
731
- const chunkSize = Math.min(remaining, 65536);
732
- yield new Uint8Array(chunkSize);
733
- remaining -= chunkSize;
775
+ // If the file shrank, pad with zeros in bounded chunks to reach
776
+ // the declared size without a large single allocation
777
+ let remaining = file.size - bytesWritten;
778
+ while (remaining > 0) {
779
+ const chunkSize = Math.min(remaining, 65536);
780
+ yield new Uint8Array(chunkSize);
781
+ remaining -= chunkSize;
782
+ }
734
783
  }
735
784
 
736
- yield tarPaddingBytes(file.size);
785
+ yield tarPaddingBytes(entrySize);
737
786
  }
738
787
 
739
788
  // End-of-archive: two zero blocks
@@ -765,8 +814,15 @@ export interface StreamExportVBundleResult {
765
814
  export async function streamExportVBundle(
766
815
  options: BuildExportVBundleOptions,
767
816
  ): Promise<StreamExportVBundleResult> {
768
- const { source, description, checkpoint, trustPath, workspaceDir, hooksDir } =
769
- options;
817
+ const {
818
+ source,
819
+ description,
820
+ checkpoint,
821
+ trustPath,
822
+ workspaceDir,
823
+ hooksDir,
824
+ credentials,
825
+ } = options;
770
826
 
771
827
  // Flush WAL to the main database file before reading
772
828
  if (checkpoint) {
@@ -806,6 +862,42 @@ export async function streamExportVBundle(
806
862
  }
807
863
  }
808
864
 
865
+ // Sanitize workspace/config.json: read from disk, sanitize, and replace the
866
+ // disk-backed metadata entry with an in-memory entry so the streaming tar
867
+ // writes sanitized content instead of the raw file.
868
+ const configMetadataIdx = allFileMetadata.findIndex(
869
+ (f) => f.archivePath === "workspace/config.json",
870
+ );
871
+
872
+ const sanitizedConfigEntries: InMemoryEntry[] = [];
873
+ if (configMetadataIdx !== -1) {
874
+ const configMeta = allFileMetadata[configMetadataIdx];
875
+ const rawConfigData = readFileSync(configMeta.diskPath, "utf8");
876
+ const sanitized = sanitizeConfigForTransfer(rawConfigData);
877
+ const sanitizedData = new TextEncoder().encode(sanitized);
878
+
879
+ // Remove the disk-backed entry and replace with an in-memory entry
880
+ allFileMetadata.splice(configMetadataIdx, 1);
881
+ sanitizedConfigEntries.push({
882
+ archivePath: "workspace/config.json",
883
+ data: sanitizedData,
884
+ size: sanitizedData.length,
885
+ });
886
+ }
887
+
888
+ // Build in-memory entries for credentials (not disk-backed)
889
+ const inMemoryEntries: InMemoryEntry[] = [];
890
+ if (credentials?.length) {
891
+ for (const { account, value } of credentials) {
892
+ const data = new TextEncoder().encode(value);
893
+ inMemoryEntries.push({
894
+ archivePath: `credentials/${account}`,
895
+ data,
896
+ size: data.length,
897
+ });
898
+ }
899
+ }
900
+
809
901
  // ------------------------------------------------------------------
810
902
  // Pass 1: Compute SHA-256 checksums to build the manifest
811
903
  // ------------------------------------------------------------------
@@ -820,6 +912,16 @@ export async function streamExportVBundle(
820
912
  });
821
913
  }
822
914
 
915
+ // Add in-memory entries (sanitized config, credentials) to the manifest
916
+ for (const entry of [...sanitizedConfigEntries, ...inMemoryEntries]) {
917
+ const sha256 = sha256Hex(entry.data);
918
+ fileEntries.push({
919
+ path: entry.archivePath,
920
+ sha256,
921
+ size: entry.size,
922
+ });
923
+ }
924
+
823
925
  const manifestWithoutChecksum = {
824
926
  schema_version: "1.0",
825
927
  created_at: new Date().toISOString(),
@@ -842,7 +944,12 @@ export async function streamExportVBundle(
842
944
 
843
945
  const tempPath = join(tmpdir(), `vbundle-export-${randomUUID()}.tmp`);
844
946
 
845
- const tarGenerator = generateTarStream(manifestData, allFileMetadata);
947
+ const allEntries: TarStreamEntry[] = [
948
+ ...allFileMetadata,
949
+ ...sanitizedConfigEntries,
950
+ ...inMemoryEntries,
951
+ ];
952
+ const tarGenerator = generateTarStream(manifestData, allEntries);
846
953
  const tarReadable = Readable.from(tarGenerator);
847
954
  const gzipStream = createGzip();
848
955
  const writeStream = createWriteStream(tempPath, { mode: 0o600 });
@@ -93,6 +93,11 @@ export class DefaultPathResolver implements PathResolver {
93
93
  ) {}
94
94
 
95
95
  resolve(archivePath: string): string | null {
96
+ // Skip credential entries — handled separately by the credential import step
97
+ if (archivePath.startsWith("credentials/")) {
98
+ return null;
99
+ }
100
+
96
101
  // New format: workspace/ prefix — maps directly into the workspace dir
97
102
  if (archivePath.startsWith("workspace/") && this.workspaceDir) {
98
103
  const relPath = archivePath.slice("workspace/".length);
@@ -190,6 +195,20 @@ export function analyzeImport(
190
195
  for (const fileEntry of manifest.files) {
191
196
  const diskPath = pathResolver.resolve(fileEntry.path);
192
197
 
198
+ // Credential entries are handled separately by the credential import
199
+ // step — skip them without flagging as unknown/conflict.
200
+ if (fileEntry.path.startsWith("credentials/")) {
201
+ files.push({
202
+ path: fileEntry.path,
203
+ action: "skip",
204
+ bundle_size: fileEntry.size,
205
+ bundle_sha256: fileEntry.sha256,
206
+ current_size: null,
207
+ current_sha256: null,
208
+ });
209
+ continue;
210
+ }
211
+
193
212
  if (!diskPath) {
194
213
  // Unknown archive path — would have nowhere to write
195
214
  conflicts.push({
@@ -24,6 +24,7 @@ import {
24
24
  } from "node:fs";
25
25
  import { dirname, join } from "node:path";
26
26
 
27
+ import { sanitizeConfigForTransfer } from "../../config/sanitize-for-transfer.js";
27
28
  import type { PathResolver } from "./vbundle-import-analyzer.js";
28
29
  import type { ManifestType, VBundleTarEntry } from "./vbundle-validator.js";
29
30
  import { validateVBundle } from "./vbundle-validator.js";
@@ -227,6 +228,12 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
227
228
  let backupsCreated = 0;
228
229
 
229
230
  for (const fileEntry of manifest.files) {
231
+ // Credential entries are handled separately by extractCredentialsFromBundle()
232
+ // in migration-routes.ts — skip them silently without warnings or skip counts.
233
+ if (fileEntry.path.startsWith("credentials/")) {
234
+ continue;
235
+ }
236
+
230
237
  const diskPath = pathResolver.resolve(fileEntry.path);
231
238
 
232
239
  if (!diskPath) {
@@ -315,9 +322,20 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
315
322
  }
316
323
  }
317
324
 
325
+ // Sanitize config files to strip environment-specific fields (defense-in-depth)
326
+ let dataToWrite: Uint8Array = archiveEntry.data;
327
+ if (
328
+ fileEntry.path === "workspace/config.json" ||
329
+ fileEntry.path === "config/settings.json"
330
+ ) {
331
+ const configJson = new TextDecoder().decode(archiveEntry.data);
332
+ const sanitized = sanitizeConfigForTransfer(configJson);
333
+ dataToWrite = new TextEncoder().encode(sanitized);
334
+ }
335
+
318
336
  // Write the file
319
337
  try {
320
- writeFileSync(diskPath, archiveEntry.data);
338
+ writeFileSync(diskPath, dataToWrite);
321
339
  } catch (err) {
322
340
  return {
323
341
  ok: false,
@@ -335,14 +353,17 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
335
353
  }
336
354
 
337
355
  // Step 3: Post-write integrity check — verify the written file
356
+ // Use the SHA of the data we actually wrote (which may differ from the
357
+ // manifest SHA if the config was sanitized during import).
358
+ const expectedSha256 = sha256Hex(dataToWrite);
338
359
  try {
339
360
  const writtenData = new Uint8Array(readFileSync(diskPath));
340
361
  const writtenSha256 = sha256Hex(writtenData);
341
362
 
342
- if (writtenSha256 !== fileEntry.sha256) {
363
+ if (writtenSha256 !== expectedSha256) {
343
364
  warnings.push(
344
365
  `Post-write integrity warning for "${fileEntry.path}": ` +
345
- `expected SHA-256 ${fileEntry.sha256}, got ${writtenSha256}`,
366
+ `expected SHA-256 ${expectedSha256}, got ${writtenSha256}`,
346
367
  );
347
368
  }
348
369
  } catch {
@@ -355,8 +376,8 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
355
376
  path: fileEntry.path,
356
377
  disk_path: diskPath,
357
378
  action,
358
- size: archiveEntry.size,
359
- sha256: fileEntry.sha256,
379
+ size: dataToWrite.length,
380
+ sha256: expectedSha256,
360
381
  backup_path: backupPath,
361
382
  });
362
383
  }
@@ -380,6 +401,35 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
380
401
  return { ok: true, report };
381
402
  }
382
403
 
404
+ // ---------------------------------------------------------------------------
405
+ // Credential extraction
406
+ // ---------------------------------------------------------------------------
407
+
408
+ /**
409
+ * Extract credential entries from a validated vbundle tar entries map.
410
+ *
411
+ * Credentials are stored under the `credentials/` prefix in the archive,
412
+ * where the remainder of the path is the account name and the entry data
413
+ * is the credential value.
414
+ */
415
+ export function extractCredentialsFromBundle(
416
+ entries: Map<string, VBundleTarEntry>,
417
+ manifest: ManifestType,
418
+ ): Array<{ account: string; value: string }> {
419
+ const manifestPaths = new Set(manifest.files.map((f) => f.path));
420
+ const credentials: Array<{ account: string; value: string }> = [];
421
+ for (const [path, entry] of entries) {
422
+ if (path.startsWith("credentials/") && manifestPaths.has(path)) {
423
+ const account = path.slice("credentials/".length);
424
+ if (account) {
425
+ const value = new TextDecoder().decode(entry.data);
426
+ credentials.push({ account, value });
427
+ }
428
+ }
429
+ }
430
+ return credentials;
431
+ }
432
+
383
433
  // ---------------------------------------------------------------------------
384
434
  // Helpers
385
435
  // ---------------------------------------------------------------------------
@@ -1,13 +1,15 @@
1
1
  /**
2
2
  * In-memory tracker that maps requestId to conversation info for pending
3
- * confirmation, secret, host_bash, host_file, and host_cu interactions.
3
+ * confirmation, secret, host_bash, host_file, host_cu, and host_browser
4
+ * interactions.
4
5
  *
5
6
  * When the agent loop emits a confirmation_request, secret_request,
6
- * host_bash_request, host_file_request, or host_cu_request, the onEvent
7
- * callback registers the interaction here. Standalone HTTP endpoints
8
- * (/v1/confirm, /v1/secret, /v1/trust-rules, /v1/host-bash-result,
9
- * /v1/host-file-result, /v1/host-cu-result) look up the conversation from this
10
- * tracker to resolve the interaction.
7
+ * host_bash_request, host_file_request, host_cu_request, or
8
+ * host_browser_request, the onEvent callback registers the interaction here.
9
+ * Standalone HTTP endpoints (/v1/confirm, /v1/secret, /v1/trust-rules,
10
+ * /v1/host-bash-result, /v1/host-file-result, /v1/host-cu-result,
11
+ * /v1/host-browser-result) look up the conversation from this tracker to
12
+ * resolve the interaction.
11
13
  */
12
14
 
13
15
  import type { Conversation } from "../daemon/conversation.js";
@@ -45,10 +47,22 @@ export interface PendingInteraction {
45
47
  | "host_bash"
46
48
  | "host_file"
47
49
  | "host_cu"
50
+ | "host_browser"
48
51
  | "acp_confirmation";
49
52
  confirmationDetails?: ConfirmationDetails;
50
53
  /** For ACP permissions: resolves directly without a Conversation object. */
51
54
  directResolve?: (decision: UserDecision) => void;
55
+ /**
56
+ * For host_browser interactions originating outside an agent loop
57
+ * (e.g. the `assistant browser chrome relay` CLI shim that POSTs to
58
+ * /v1/browser-cdp). Resolves the CDP round-trip directly without
59
+ * touching a Conversation. When set, /v1/host-browser-result invokes
60
+ * this instead of `interaction.conversation.resolveHostBrowser`.
61
+ */
62
+ directBrowserResolve?: (response: {
63
+ content: string;
64
+ isError: boolean;
65
+ }) => void;
52
66
  }
53
67
 
54
68
  const pending = new Map<string, PendingInteraction>();
@@ -82,7 +96,7 @@ export function get(requestId: string): PendingInteraction | undefined {
82
96
 
83
97
  /**
84
98
  * Return all pending interactions for a given conversation.
85
- * Needed by channel approval migration (PR 3).
99
+ * Needed by channel approval migration.
86
100
  */
87
101
  export function getByConversation(
88
102
  conversationId: string,
@@ -100,12 +114,13 @@ export function getByConversation(
100
114
  * Remove pending confirmation and secret interactions for a given conversation.
101
115
  * Used when auto-denying all pending interactions (e.g. new user message).
102
116
  *
103
- * host_bash, host_file, and host_cu interactions are intentionally skipped
104
- * — they represent in-flight tool executions proxied to the client, not
105
- * confirmations to auto-deny. Removing them would orphan the request: the
106
- * client would POST to /v1/host-bash-result, /v1/host-file-result, or
107
- * /v1/host-cu-result after completing the operation, get a 404, and the
108
- * proxy timer would fire with a spurious timeout error.
117
+ * host_bash, host_file, host_cu, and host_browser interactions are
118
+ * intentionally skipped — they represent in-flight tool executions proxied to
119
+ * the client, not confirmations to auto-deny. Removing them would orphan the
120
+ * request: the client would POST to /v1/host-bash-result,
121
+ * /v1/host-file-result, /v1/host-cu-result, or /v1/host-browser-result after
122
+ * completing the operation, get a 404, and the proxy timer would fire with a
123
+ * spurious timeout error.
109
124
  */
110
125
  export function removeByConversation(conversation: Conversation): void {
111
126
  for (const [requestId, interaction] of pending) {
@@ -114,6 +129,7 @@ export function removeByConversation(conversation: Conversation): void {
114
129
  interaction.kind !== "host_bash" &&
115
130
  interaction.kind !== "host_file" &&
116
131
  interaction.kind !== "host_cu" &&
132
+ interaction.kind !== "host_browser" &&
117
133
  interaction.kind !== "acp_confirmation"
118
134
  ) {
119
135
  pending.delete(requestId);