@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
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Tests for credential-aware rebind-secrets screen behavior.
3
+ *
4
+ * Covers:
5
+ * - All credentials imported successfully -> re-enter-secrets auto-completed
6
+ * - Partial import failure -> targeted list of failed credentials
7
+ * - Legacy bundles without credentials -> original rebind-secrets behavior
8
+ */
9
+
10
+ import { describe, expect, test } from "bun:test";
11
+
12
+ import type { MigrationWizardState } from "../migration-wizard.js";
13
+ import { createWizardState } from "../migration-wizard.js";
14
+ import {
15
+ createTaskCompletionState,
16
+ deriveRebindSecretsScreenState,
17
+ } from "../rebind-secrets-screen.js";
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Helpers
21
+ // ---------------------------------------------------------------------------
22
+
23
+ /**
24
+ * Create a wizard state that has progressed to the rebind-secrets step.
25
+ */
26
+ function wizardAtRebindSecrets(
27
+ credentialsImported?: MigrationWizardState["credentialsImported"],
28
+ ): MigrationWizardState {
29
+ const base = createWizardState();
30
+ return {
31
+ ...base,
32
+ currentStep: "rebind-secrets",
33
+ direction: "managed-to-self-hosted",
34
+ steps: {
35
+ ...base.steps,
36
+ "select-direction": { status: "success" },
37
+ "upload-bundle": { status: "success" },
38
+ validate: { status: "success" },
39
+ "preflight-review": { status: "success" },
40
+ transfer: { status: "success" },
41
+ "rebind-secrets": { status: "idle" },
42
+ complete: { status: "idle" },
43
+ },
44
+ hasBundleData: true,
45
+ credentialsImported,
46
+ };
47
+ }
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // Tests
51
+ // ---------------------------------------------------------------------------
52
+
53
+ describe("rebind-secrets-screen credential awareness", () => {
54
+ test("all credentials imported successfully -> re-enter-secrets is auto-completed", () => {
55
+ const wizard = wizardAtRebindSecrets({
56
+ total: 3,
57
+ succeeded: 3,
58
+ failed: 0,
59
+ failedAccounts: [],
60
+ });
61
+ const completion = createTaskCompletionState();
62
+ const screen = deriveRebindSecretsScreenState(wizard, completion);
63
+
64
+ expect(screen.phase).toBe("active");
65
+ if (screen.phase !== "active") return;
66
+
67
+ const secretsTask = screen.tasks.find((t) => t.id === "re-enter-secrets");
68
+ expect(secretsTask).toBeDefined();
69
+ expect(secretsTask!.status).toBe("complete");
70
+ expect(secretsTask!.title).toBe("API keys and secrets transferred");
71
+ expect(secretsTask!.description).toContain("3 credential(s)");
72
+ expect(secretsTask!.description).toContain("automatically imported");
73
+ });
74
+
75
+ test("partial import failure -> shows only failed credentials", () => {
76
+ const wizard = wizardAtRebindSecrets({
77
+ total: 3,
78
+ succeeded: 2,
79
+ failed: 1,
80
+ failedAccounts: ["openai-key"],
81
+ });
82
+ const completion = createTaskCompletionState();
83
+ const screen = deriveRebindSecretsScreenState(wizard, completion);
84
+
85
+ expect(screen.phase).toBe("active");
86
+ if (screen.phase !== "active") return;
87
+
88
+ const secretsTask = screen.tasks.find((t) => t.id === "re-enter-secrets");
89
+ expect(secretsTask).toBeDefined();
90
+ expect(secretsTask!.status).toBe("pending");
91
+ expect(secretsTask!.title).toBe("Re-enter failed credentials");
92
+ expect(secretsTask!.description).toContain("2 of 3");
93
+ expect(secretsTask!.description).toContain('"openai-key"');
94
+ expect(secretsTask!.description).toContain("manual re-entry");
95
+ });
96
+
97
+ test("legacy bundle without credentials -> original rebind-secrets behavior", () => {
98
+ const wizard = wizardAtRebindSecrets(undefined);
99
+ const completion = createTaskCompletionState();
100
+ const screen = deriveRebindSecretsScreenState(wizard, completion);
101
+
102
+ expect(screen.phase).toBe("active");
103
+ if (screen.phase !== "active") return;
104
+
105
+ const secretsTask = screen.tasks.find((t) => t.id === "re-enter-secrets");
106
+ expect(secretsTask).toBeDefined();
107
+ expect(secretsTask!.status).toBe("pending");
108
+ expect(secretsTask!.title).toBe("Re-enter API keys and secrets");
109
+ expect(secretsTask!.description).toContain("redacted in export bundles");
110
+ });
111
+
112
+ test("all credentials imported -> allRequiredComplete reflects auto-completion", () => {
113
+ const wizard = wizardAtRebindSecrets({
114
+ total: 2,
115
+ succeeded: 2,
116
+ failed: 0,
117
+ failedAccounts: [],
118
+ });
119
+ // Mark all other required tasks as complete
120
+ let completion = createTaskCompletionState();
121
+ completion = {
122
+ ...completion,
123
+ "rebind-channels": true,
124
+ "reconfigure-auth": true,
125
+ };
126
+ const screen = deriveRebindSecretsScreenState(wizard, completion);
127
+
128
+ expect(screen.phase).toBe("active");
129
+ if (screen.phase !== "active") return;
130
+
131
+ // re-enter-secrets is auto-completed, rebind-channels and reconfigure-auth are manually done
132
+ expect(screen.allRequiredComplete).toBe(true);
133
+ });
134
+
135
+ test("partial failure -> allRequiredComplete is false when failed creds not manually acknowledged", () => {
136
+ const wizard = wizardAtRebindSecrets({
137
+ total: 3,
138
+ succeeded: 2,
139
+ failed: 1,
140
+ failedAccounts: ["anthropic-key"],
141
+ });
142
+ const completion = createTaskCompletionState();
143
+ const screen = deriveRebindSecretsScreenState(wizard, completion);
144
+
145
+ expect(screen.phase).toBe("active");
146
+ if (screen.phase !== "active") return;
147
+
148
+ // re-enter-secrets is still pending (partial failure), so not all required complete
149
+ expect(screen.allRequiredComplete).toBe(false);
150
+ });
151
+
152
+ test("multiple failed credentials are listed in description", () => {
153
+ const wizard = wizardAtRebindSecrets({
154
+ total: 4,
155
+ succeeded: 1,
156
+ failed: 3,
157
+ failedAccounts: ["openai-key", "anthropic-key", "github-token"],
158
+ });
159
+ const completion = createTaskCompletionState();
160
+ const screen = deriveRebindSecretsScreenState(wizard, completion);
161
+
162
+ expect(screen.phase).toBe("active");
163
+ if (screen.phase !== "active") return;
164
+
165
+ const secretsTask = screen.tasks.find((t) => t.id === "re-enter-secrets");
166
+ expect(secretsTask).toBeDefined();
167
+ expect(secretsTask!.description).toContain('"openai-key"');
168
+ expect(secretsTask!.description).toContain('"anthropic-key"');
169
+ expect(secretsTask!.description).toContain('"github-token"');
170
+ expect(secretsTask!.description).toContain("1 of 4");
171
+ });
172
+ });
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Tests for credential inclusion in vbundle export.
3
+ *
4
+ * Covers:
5
+ * - buildExportVBundle() with credentials includes credentials/* entries
6
+ * - Credentials appear in the manifest with correct SHA-256 checksums
7
+ * - Export with no credentials produces the same archive as before (backward compat)
8
+ * - Streaming path (streamExportVBundle) includes credential entries
9
+ */
10
+
11
+ import { createHash } from "node:crypto";
12
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
13
+ import { tmpdir } from "node:os";
14
+ import { join } from "node:path";
15
+ import { gunzipSync } from "node:zlib";
16
+ import { describe, expect, test } from "bun:test";
17
+
18
+ import { buildExportVBundle, streamExportVBundle } from "../vbundle-builder.js";
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Helpers
22
+ // ---------------------------------------------------------------------------
23
+
24
+ const BLOCK_SIZE = 512;
25
+
26
+ function sha256Hex(data: Uint8Array | string): string {
27
+ return createHash("sha256").update(data).digest("hex");
28
+ }
29
+
30
+ /** Parse a tar archive into its entries (name -> data). */
31
+ function parseTar(tar: Uint8Array): Map<string, Uint8Array> {
32
+ const entries = new Map<string, Uint8Array>();
33
+ let offset = 0;
34
+ let paxPath: string | undefined;
35
+
36
+ while (offset + BLOCK_SIZE <= tar.length) {
37
+ const header = tar.subarray(offset, offset + BLOCK_SIZE);
38
+
39
+ // Check for end-of-archive (all zeros)
40
+ if (header.every((b) => b === 0)) break;
41
+
42
+ // Read file name
43
+ let name = "";
44
+ for (let i = 0; i < 100 && header[i] !== 0; i++) {
45
+ name += String.fromCharCode(header[i]);
46
+ }
47
+
48
+ // Read size (octal at offset 124, 12 bytes)
49
+ let sizeStr = "";
50
+ for (let i = 124; i < 136 && header[i] !== 0; i++) {
51
+ sizeStr += String.fromCharCode(header[i]);
52
+ }
53
+ const size = parseInt(sizeStr.trim(), 8) || 0;
54
+
55
+ // Type flag
56
+ const typeFlag = String.fromCharCode(header[156]);
57
+
58
+ offset += BLOCK_SIZE;
59
+
60
+ const data = tar.subarray(offset, offset + size);
61
+ const paddedSize = Math.ceil(size / BLOCK_SIZE) * BLOCK_SIZE;
62
+ offset += paddedSize;
63
+
64
+ if (typeFlag === "x") {
65
+ // PAX extended header — extract path
66
+ const paxStr = new TextDecoder().decode(data);
67
+ const match = paxStr.match(/\d+ path=(.+)\n/);
68
+ if (match) {
69
+ paxPath = match[1];
70
+ }
71
+ continue;
72
+ }
73
+
74
+ const entryName = paxPath ?? name;
75
+ paxPath = undefined;
76
+ entries.set(entryName, new Uint8Array(data));
77
+ }
78
+
79
+ return entries;
80
+ }
81
+
82
+ /** Create a minimal workspace directory with a config file for testing. */
83
+ function createTestWorkspace(): { dir: string; cleanup: () => void } {
84
+ const dir = join(
85
+ tmpdir(),
86
+ `vbundle-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
87
+ );
88
+ mkdirSync(dir, { recursive: true });
89
+ writeFileSync(join(dir, "config.json"), JSON.stringify({ test: true }));
90
+ // Create a minimal data/db directory with a fake database file
91
+ const dbDir = join(dir, "data", "db");
92
+ mkdirSync(dbDir, { recursive: true });
93
+ writeFileSync(join(dbDir, "assistant.db"), "fake-db-content");
94
+ return {
95
+ dir,
96
+ cleanup: () => rmSync(dir, { recursive: true, force: true }),
97
+ };
98
+ }
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Tests
102
+ // ---------------------------------------------------------------------------
103
+
104
+ describe("buildExportVBundle with credentials", () => {
105
+ test("includes credential entries under credentials/ prefix", () => {
106
+ const workspace = createTestWorkspace();
107
+ try {
108
+ const credentials = [
109
+ { account: "openai-key", value: "sk-test-123" },
110
+ { account: "anthropic-key", value: "sk-ant-456" },
111
+ ];
112
+
113
+ const result = buildExportVBundle({
114
+ workspaceDir: workspace.dir,
115
+ credentials,
116
+ });
117
+
118
+ const tar = gunzipSync(result.archive);
119
+ const entries = parseTar(tar);
120
+
121
+ expect(entries.has("credentials/openai-key")).toBe(true);
122
+ expect(entries.has("credentials/anthropic-key")).toBe(true);
123
+
124
+ const decoder = new TextDecoder();
125
+ expect(decoder.decode(entries.get("credentials/openai-key")!)).toBe(
126
+ "sk-test-123",
127
+ );
128
+ expect(decoder.decode(entries.get("credentials/anthropic-key")!)).toBe(
129
+ "sk-ant-456",
130
+ );
131
+ } finally {
132
+ workspace.cleanup();
133
+ }
134
+ });
135
+
136
+ test("credentials appear in manifest with correct SHA-256 checksums", () => {
137
+ const workspace = createTestWorkspace();
138
+ try {
139
+ const credentials = [
140
+ { account: "my-api-key", value: "secret-value-abc" },
141
+ ];
142
+
143
+ const result = buildExportVBundle({
144
+ workspaceDir: workspace.dir,
145
+ credentials,
146
+ });
147
+
148
+ const expectedHash = sha256Hex(
149
+ new TextEncoder().encode("secret-value-abc"),
150
+ );
151
+
152
+ const credEntry = result.manifest.files.find(
153
+ (f) => f.path === "credentials/my-api-key",
154
+ );
155
+ expect(credEntry).toBeDefined();
156
+ expect(credEntry!.sha256).toBe(expectedHash);
157
+ expect(credEntry!.size).toBe(
158
+ new TextEncoder().encode("secret-value-abc").length,
159
+ );
160
+ } finally {
161
+ workspace.cleanup();
162
+ }
163
+ });
164
+
165
+ test("export with no credentials produces archive without credentials/ entries", () => {
166
+ const workspace = createTestWorkspace();
167
+ try {
168
+ const result = buildExportVBundle({
169
+ workspaceDir: workspace.dir,
170
+ });
171
+
172
+ const tar = gunzipSync(result.archive);
173
+ const entries = parseTar(tar);
174
+
175
+ const credentialEntries = [...entries.keys()].filter((k) =>
176
+ k.startsWith("credentials/"),
177
+ );
178
+ expect(credentialEntries).toHaveLength(0);
179
+
180
+ // Manifest should have no credential entries
181
+ const credManifestEntries = result.manifest.files.filter((f) =>
182
+ f.path.startsWith("credentials/"),
183
+ );
184
+ expect(credManifestEntries).toHaveLength(0);
185
+ } finally {
186
+ workspace.cleanup();
187
+ }
188
+ });
189
+
190
+ test("export with empty credentials array produces archive without credentials/ entries", () => {
191
+ const workspace = createTestWorkspace();
192
+ try {
193
+ const result = buildExportVBundle({
194
+ workspaceDir: workspace.dir,
195
+ credentials: [],
196
+ });
197
+
198
+ const tar = gunzipSync(result.archive);
199
+ const entries = parseTar(tar);
200
+
201
+ const credentialEntries = [...entries.keys()].filter((k) =>
202
+ k.startsWith("credentials/"),
203
+ );
204
+ expect(credentialEntries).toHaveLength(0);
205
+ } finally {
206
+ workspace.cleanup();
207
+ }
208
+ });
209
+ });
210
+
211
+ describe("streamExportVBundle with credentials", () => {
212
+ test("includes credential entries in the streaming archive", async () => {
213
+ const workspace = createTestWorkspace();
214
+ let result: Awaited<ReturnType<typeof streamExportVBundle>> | undefined;
215
+ try {
216
+ const credentials = [
217
+ { account: "stream-key", value: "stream-secret-789" },
218
+ ];
219
+
220
+ result = await streamExportVBundle({
221
+ workspaceDir: workspace.dir,
222
+ credentials,
223
+ });
224
+
225
+ // Read the temp file and decompress
226
+ const archiveData = new Uint8Array(
227
+ await Bun.file(result.tempPath).arrayBuffer(),
228
+ );
229
+ const tar = gunzipSync(archiveData);
230
+ const entries = parseTar(tar);
231
+
232
+ expect(entries.has("credentials/stream-key")).toBe(true);
233
+
234
+ const decoder = new TextDecoder();
235
+ expect(decoder.decode(entries.get("credentials/stream-key")!)).toBe(
236
+ "stream-secret-789",
237
+ );
238
+
239
+ // Verify manifest entry
240
+ const credEntry = result.manifest.files.find(
241
+ (f) => f.path === "credentials/stream-key",
242
+ );
243
+ expect(credEntry).toBeDefined();
244
+ expect(credEntry!.sha256).toBe(
245
+ sha256Hex(new TextEncoder().encode("stream-secret-789")),
246
+ );
247
+ } finally {
248
+ await result?.cleanup();
249
+ workspace.cleanup();
250
+ }
251
+ });
252
+
253
+ test("streaming export without credentials has no credentials/ entries", async () => {
254
+ const workspace = createTestWorkspace();
255
+ let result: Awaited<ReturnType<typeof streamExportVBundle>> | undefined;
256
+ try {
257
+ result = await streamExportVBundle({
258
+ workspaceDir: workspace.dir,
259
+ });
260
+
261
+ const archiveData = new Uint8Array(
262
+ await Bun.file(result.tempPath).arrayBuffer(),
263
+ );
264
+ const tar = gunzipSync(archiveData);
265
+ const entries = parseTar(tar);
266
+
267
+ const credentialEntries = [...entries.keys()].filter((k) =>
268
+ k.startsWith("credentials/"),
269
+ );
270
+ expect(credentialEntries).toHaveLength(0);
271
+ } finally {
272
+ await result?.cleanup();
273
+ workspace.cleanup();
274
+ }
275
+ });
276
+ });
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Tests for credential import from vbundle archives.
3
+ *
4
+ * Covers:
5
+ * - extractCredentialsFromBundle() correctly extracts credential entries
6
+ * - DefaultPathResolver skips credentials/ paths (not written to disk)
7
+ * - Empty and missing credential entries are handled gracefully
8
+ */
9
+
10
+ import { describe, expect, test } from "bun:test";
11
+
12
+ import { DefaultPathResolver } from "../vbundle-import-analyzer.js";
13
+ import { extractCredentialsFromBundle } from "../vbundle-importer.js";
14
+ import type { ManifestType, VBundleTarEntry } from "../vbundle-validator.js";
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Helpers
18
+ // ---------------------------------------------------------------------------
19
+
20
+ function makeTarEntry(data: string): VBundleTarEntry {
21
+ const encoded = new TextEncoder().encode(data);
22
+ return { name: "", data: encoded, size: encoded.length };
23
+ }
24
+
25
+ function makeManifest(paths: string[]): ManifestType {
26
+ return {
27
+ schema_version: "1.0.0",
28
+ created_at: new Date().toISOString(),
29
+ source: "test",
30
+ manifest_sha256: "test",
31
+ files: paths.map((path) => ({
32
+ path,
33
+ size: 0,
34
+ sha256: "test",
35
+ })),
36
+ } as ManifestType;
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // extractCredentialsFromBundle
41
+ // ---------------------------------------------------------------------------
42
+
43
+ describe("extractCredentialsFromBundle", () => {
44
+ test("extracts credential entries from tar entries map", () => {
45
+ const entries = new Map<string, VBundleTarEntry>();
46
+ entries.set("credentials/openai-key", makeTarEntry("sk-test-123"));
47
+ entries.set("credentials/anthropic-key", makeTarEntry("sk-ant-456"));
48
+ entries.set("workspace/config.json", makeTarEntry('{"test": true}'));
49
+ entries.set("manifest.json", makeTarEntry("{}"));
50
+
51
+ const manifest = makeManifest([
52
+ "credentials/openai-key",
53
+ "credentials/anthropic-key",
54
+ "workspace/config.json",
55
+ ]);
56
+ const credentials = extractCredentialsFromBundle(entries, manifest);
57
+
58
+ expect(credentials).toHaveLength(2);
59
+ expect(credentials).toContainEqual({
60
+ account: "openai-key",
61
+ value: "sk-test-123",
62
+ });
63
+ expect(credentials).toContainEqual({
64
+ account: "anthropic-key",
65
+ value: "sk-ant-456",
66
+ });
67
+ });
68
+
69
+ test("returns empty array when no credential entries exist", () => {
70
+ const entries = new Map<string, VBundleTarEntry>();
71
+ entries.set("workspace/config.json", makeTarEntry('{"test": true}'));
72
+ entries.set("manifest.json", makeTarEntry("{}"));
73
+
74
+ const manifest = makeManifest(["workspace/config.json"]);
75
+ const credentials = extractCredentialsFromBundle(entries, manifest);
76
+
77
+ expect(credentials).toHaveLength(0);
78
+ });
79
+
80
+ test("returns empty array for empty entries map", () => {
81
+ const entries = new Map<string, VBundleTarEntry>();
82
+
83
+ const manifest = makeManifest([]);
84
+ const credentials = extractCredentialsFromBundle(entries, manifest);
85
+
86
+ expect(credentials).toHaveLength(0);
87
+ });
88
+
89
+ test("skips bare credentials/ path with no account name", () => {
90
+ const entries = new Map<string, VBundleTarEntry>();
91
+ entries.set("credentials/", makeTarEntry("some-value"));
92
+ entries.set("credentials/valid-key", makeTarEntry("valid-secret"));
93
+
94
+ const manifest = makeManifest(["credentials/", "credentials/valid-key"]);
95
+ const credentials = extractCredentialsFromBundle(entries, manifest);
96
+
97
+ expect(credentials).toHaveLength(1);
98
+ expect(credentials[0]).toEqual({
99
+ account: "valid-key",
100
+ value: "valid-secret",
101
+ });
102
+ });
103
+
104
+ test("ignores credential entries not declared in manifest", () => {
105
+ const entries = new Map<string, VBundleTarEntry>();
106
+ entries.set("credentials/declared-key", makeTarEntry("declared-secret"));
107
+ entries.set(
108
+ "credentials/undeclared-key",
109
+ makeTarEntry("undeclared-secret"),
110
+ );
111
+
112
+ const manifest = makeManifest(["credentials/declared-key"]);
113
+ const credentials = extractCredentialsFromBundle(entries, manifest);
114
+
115
+ expect(credentials).toHaveLength(1);
116
+ expect(credentials[0]).toEqual({
117
+ account: "declared-key",
118
+ value: "declared-secret",
119
+ });
120
+ });
121
+
122
+ test("handles credential values with special characters", () => {
123
+ const entries = new Map<string, VBundleTarEntry>();
124
+ entries.set(
125
+ "credentials/complex-key",
126
+ makeTarEntry("value with spaces & special=chars!"),
127
+ );
128
+
129
+ const manifest = makeManifest(["credentials/complex-key"]);
130
+ const credentials = extractCredentialsFromBundle(entries, manifest);
131
+
132
+ expect(credentials).toHaveLength(1);
133
+ expect(credentials[0]).toEqual({
134
+ account: "complex-key",
135
+ value: "value with spaces & special=chars!",
136
+ });
137
+ });
138
+ });
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // DefaultPathResolver — credential path skipping
142
+ // ---------------------------------------------------------------------------
143
+
144
+ describe("DefaultPathResolver skips credential paths", () => {
145
+ test("resolve() returns null for credentials/ paths", () => {
146
+ const resolver = new DefaultPathResolver("/tmp/workspace", "/tmp/hooks");
147
+
148
+ expect(resolver.resolve("credentials/openai-key")).toBeNull();
149
+ expect(resolver.resolve("credentials/anthropic-key")).toBeNull();
150
+ expect(resolver.resolve("credentials/")).toBeNull();
151
+ });
152
+
153
+ test("resolve() still resolves non-credential paths normally", () => {
154
+ const resolver = new DefaultPathResolver("/tmp/workspace", "/tmp/hooks");
155
+
156
+ // workspace/ paths should resolve
157
+ expect(resolver.resolve("workspace/config.json")).not.toBeNull();
158
+
159
+ // data/db should resolve
160
+ expect(resolver.resolve("data/db/assistant.db")).not.toBeNull();
161
+ });
162
+ });
@@ -202,6 +202,12 @@ export interface ImportCommitSuccessResponse {
202
202
  files: ImportedFileReport[];
203
203
  manifest: Manifest;
204
204
  warnings: string[];
205
+ credentialsImported?: {
206
+ total: number;
207
+ succeeded: number;
208
+ failed: number;
209
+ failedAccounts: string[];
210
+ };
205
211
  }
206
212
 
207
213
  export interface ImportCommitFailureResponse {
@@ -92,6 +92,14 @@ export interface MigrationWizardState {
92
92
  /** Import commit result. */
93
93
  importResult?: ImportCommitResponse;
94
94
 
95
+ /** Credential import results from the transfer step (if credentials were in the bundle). */
96
+ credentialsImported?: {
97
+ total: number;
98
+ succeeded: number;
99
+ failed: number;
100
+ failedAccounts: string[];
101
+ };
102
+
95
103
  /** Timestamp of last state change (ISO 8601). */
96
104
  updatedAt: string;
97
105
 
@@ -317,7 +325,11 @@ export function goBackTo(
317
325
  ? { preflightResult: undefined }
318
326
  : {}),
319
327
  ...(targetIdx <= STEP_INDEX.get("transfer")!
320
- ? { exportResult: undefined, importResult: undefined }
328
+ ? {
329
+ exportResult: undefined,
330
+ importResult: undefined,
331
+ credentialsImported: undefined,
332
+ }
321
333
  : {}),
322
334
  });
323
335
  }
@@ -488,7 +500,14 @@ export async function executeTransferStep(
488
500
 
489
501
  if (importResult.success) {
490
502
  current = setStepStatus(current, "transfer", "success");
491
- current = { ...current, currentStep: "rebind-secrets" };
503
+ // Extract credential import results from the response (if present)
504
+ current = {
505
+ ...current,
506
+ currentStep: "rebind-secrets",
507
+ ...(importResult.credentialsImported
508
+ ? { credentialsImported: importResult.credentialsImported }
509
+ : {}),
510
+ };
492
511
  } else {
493
512
  current = setStepStatus(current, "transfer", "error", {
494
513
  message:
@@ -639,6 +658,7 @@ export function prepareForResume(
639
658
  preflightResult: undefined,
640
659
  exportResult: undefined,
641
660
  importResult: undefined,
661
+ credentialsImported: undefined,
642
662
  };
643
663
  }
644
664