@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.
- package/bun.lock +40 -40
- package/bunfig.toml +3 -0
- package/docs/architecture/memory.md +1 -1
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
- package/openapi.yaml +184 -69
- package/package.json +41 -41
- package/scripts/generate-openapi.ts +1 -2
- package/src/__tests__/acp-session.test.ts +43 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +1 -0
- package/src/__tests__/app-source-watcher.test.ts +37 -11
- package/src/__tests__/approval-routes-http.test.ts +178 -1
- package/src/__tests__/browser-fill-credential.test.ts +229 -94
- package/src/__tests__/browser-manager.test.ts +40 -27
- package/src/__tests__/catalog-files.test.ts +862 -0
- package/src/__tests__/channel-approvals.test.ts +53 -0
- package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +125 -48
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
- package/src/__tests__/context-overflow-approval.test.ts +16 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop.test.ts +1 -1
- package/src/__tests__/conversation-analysis-routes.test.ts +2 -2
- package/src/__tests__/conversation-attachments.test.ts +80 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
- package/src/__tests__/conversation-fork-crud.test.ts +17 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
- package/src/__tests__/conversation-inject-context.test.ts +103 -0
- package/src/__tests__/conversation-queue.test.ts +45 -2
- package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
- package/src/__tests__/conversation-starter-routes.test.ts +126 -0
- package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
- package/src/__tests__/conversation-store.test.ts +195 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
- package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +4 -4
- package/src/__tests__/credential-vault.test.ts +152 -13
- package/src/__tests__/credentials-cli.test.ts +2 -2
- package/src/__tests__/date-context.test.ts +4 -4
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
- package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
- package/src/__tests__/gateway-only-guard.test.ts +3 -0
- package/src/__tests__/gemini-provider.test.ts +2 -2
- package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
- package/src/__tests__/headless-browser-interactions.test.ts +707 -371
- package/src/__tests__/headless-browser-navigate.test.ts +389 -47
- package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
- package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
- package/src/__tests__/host-bash-proxy.test.ts +150 -1
- package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
- package/src/__tests__/host-browser-event-routes.test.ts +350 -0
- package/src/__tests__/host-browser-proxy.test.ts +444 -0
- package/src/__tests__/host-browser-routes.test.ts +198 -0
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
- package/src/__tests__/host-cu-proxy.test.ts +171 -1
- package/src/__tests__/host-file-proxy.test.ts +185 -1
- package/src/__tests__/host-file-read-tool.test.ts +52 -0
- package/src/__tests__/host-proxy-interface.test.ts +165 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -11
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/integration-status.test.ts +6 -7
- package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
- package/src/__tests__/mcp-client-auth.test.ts +40 -4
- package/src/__tests__/mcp-health-check.test.ts +10 -3
- package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
- package/src/__tests__/migration-export-http.test.ts +61 -2
- package/src/__tests__/migration-export-streaming.test.ts +66 -0
- package/src/__tests__/migration-import-commit-http.test.ts +101 -1
- package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
- package/src/__tests__/oauth-apps-routes.test.ts +17 -12
- package/src/__tests__/oauth-cli.test.ts +707 -60
- package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
- package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
- package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
- package/src/__tests__/oauth-providers-routes.test.ts +50 -14
- package/src/__tests__/oauth-store.test.ts +1386 -182
- package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
- package/src/__tests__/onboarding-template-contract.test.ts +75 -57
- package/src/__tests__/openai-provider.test.ts +2 -2
- package/src/__tests__/outlook-categories.test.ts +1 -1
- package/src/__tests__/outlook-client-automation.test.ts +1 -1
- package/src/__tests__/outlook-compose-tools.test.ts +1 -1
- package/src/__tests__/outlook-email-watcher.test.ts +1 -1
- package/src/__tests__/outlook-follow-up.test.ts +1 -1
- package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
- package/src/__tests__/outlook-trash.test.ts +1 -1
- package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
- package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
- package/src/__tests__/permission-mode.test.ts +28 -56
- package/src/__tests__/platform-callback-registration.test.ts +19 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
- package/src/__tests__/proxy-approval-callback.test.ts +18 -0
- package/src/__tests__/require-fresh-approval.test.ts +40 -1
- package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
- package/src/__tests__/schedule-routes.test.ts +162 -0
- package/src/__tests__/secret-detection-handler.test.ts +84 -0
- package/src/__tests__/secret-ingress-http.test.ts +1 -0
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/set-permission-mode.test.ts +13 -250
- package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
- package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
- package/src/__tests__/slack-channel-config.test.ts +12 -15
- package/src/__tests__/subagent-detail.test.ts +44 -2
- package/src/__tests__/subagent-disposal.test.ts +1 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
- package/src/__tests__/subagent-manager-notify.test.ts +1 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/subagent-types.test.ts +1 -0
- package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
- package/src/__tests__/system-prompt.test.ts +72 -1
- package/src/__tests__/task-scheduler.test.ts +32 -6
- package/src/__tests__/telegram-config.test.ts +10 -13
- package/src/__tests__/terminal-tools.test.ts +9 -0
- package/src/__tests__/tool-approval-handler.test.ts +73 -0
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
- package/src/__tests__/top-level-renderer.test.ts +73 -1
- package/src/__tests__/transport-hints-queue.test.ts +14 -29
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
- package/src/__tests__/v2-consent-policy.test.ts +103 -0
- package/src/acp/client-handler.ts +30 -4
- package/src/agent/loop.ts +12 -6
- package/src/approvals/guardian-request-resolvers.ts +21 -15
- package/src/browser-session/__tests__/manager.test.ts +297 -0
- package/src/browser-session/backends/cdp-inspect.ts +30 -0
- package/src/browser-session/backends/extension.ts +26 -0
- package/src/browser-session/backends/local.ts +24 -0
- package/src/browser-session/events.ts +164 -0
- package/src/browser-session/index.ts +27 -0
- package/src/browser-session/manager.ts +159 -0
- package/src/browser-session/types.ts +28 -0
- package/src/channels/__tests__/types.test.ts +134 -0
- package/src/channels/types.ts +53 -3
- package/src/cli/commands/browser-relay.ts +339 -409
- package/src/cli/commands/credentials.ts +3 -3
- package/src/cli/commands/email.ts +18 -13
- package/src/cli/commands/mcp.ts +16 -4
- package/src/cli/commands/oauth/__tests__/connect.test.ts +44 -44
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
- package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
- package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
- package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
- package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
- package/src/cli/commands/oauth/apps.ts +7 -4
- package/src/cli/commands/oauth/connect.ts +6 -3
- package/src/cli/commands/oauth/disconnect.ts +1 -1
- package/src/cli/commands/oauth/providers.ts +200 -36
- package/src/cli/commands/oauth/shared.ts +5 -5
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
- package/src/cli/commands/platform/index.ts +107 -10
- package/src/cli/commands/usage.ts +10 -9
- package/src/cli/lib/daemon-credential-client.ts +4 -0
- package/src/cli/program.ts +1 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
- package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
- package/src/config/bundled-skills/contacts/SKILL.md +3 -0
- package/src/config/bundled-skills/document/SKILL.md +4 -0
- package/src/config/bundled-skills/gmail/SKILL.md +1 -1
- package/src/config/bundled-skills/outlook/SKILL.md +7 -0
- package/src/config/bundled-skills/subagent/SKILL.md +21 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
- package/src/config/bundled-skills/tasks/SKILL.md +5 -0
- package/src/config/env-registry.ts +14 -0
- package/src/config/env.ts +21 -0
- package/src/config/feature-flag-registry.json +44 -5
- package/src/config/loader.ts +56 -1
- package/src/config/sanitize-for-transfer.ts +47 -0
- package/src/config/schema.ts +46 -5
- package/src/config/schemas/host-browser.ts +66 -0
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/schemas/memory-retrieval.ts +103 -0
- package/src/config/schemas/security.ts +0 -6
- package/src/config/schemas/services.ts +8 -0
- package/src/config/types.ts +0 -1
- package/src/context/post-turn-tool-result-truncation.ts +176 -0
- package/src/context/window-manager.ts +19 -1
- package/src/credential-execution/approval-bridge.ts +49 -15
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
- package/src/daemon/app-source-watcher.ts +35 -0
- package/src/daemon/context-overflow-approval.ts +5 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
- package/src/daemon/conversation-agent-loop.ts +58 -24
- package/src/daemon/conversation-attachments.ts +40 -0
- package/src/daemon/conversation-process.ts +48 -1
- package/src/daemon/conversation-runtime-assembly.ts +118 -36
- package/src/daemon/conversation-surfaces.ts +37 -36
- package/src/daemon/conversation-tool-setup.ts +74 -8
- package/src/daemon/conversation-workspace.ts +12 -0
- package/src/daemon/conversation.ts +226 -8
- package/src/daemon/date-context.ts +10 -10
- package/src/daemon/first-greeting.ts +3 -2
- package/src/daemon/handlers/conversations.ts +9 -140
- package/src/daemon/handlers/shared.ts +58 -0
- package/src/daemon/handlers/skills.ts +232 -37
- package/src/daemon/host-bash-proxy.ts +48 -13
- package/src/daemon/host-browser-proxy.ts +191 -0
- package/src/daemon/host-cu-proxy.ts +36 -11
- package/src/daemon/host-file-proxy.ts +57 -9
- package/src/daemon/lifecycle.ts +65 -11
- package/src/daemon/message-protocol.ts +7 -0
- package/src/daemon/message-types/conversations.ts +55 -13
- package/src/daemon/message-types/host-browser.ts +100 -0
- package/src/daemon/message-types/messages.ts +5 -5
- package/src/daemon/message-types/skills.ts +10 -0
- package/src/daemon/message-types/subagents.ts +2 -0
- package/src/daemon/server.ts +92 -12
- package/src/daemon/tool-side-effects.ts +6 -0
- package/src/daemon/transport-hints.ts +5 -24
- package/src/inbound/platform-callback-registration.ts +18 -17
- package/src/mcp/client.ts +59 -24
- package/src/memory/app-store.ts +31 -1
- package/src/memory/conversation-crud.ts +23 -0
- package/src/memory/conversation-starters-cadence.ts +76 -0
- package/src/memory/conversation-title-service.ts +5 -2
- package/src/memory/db-init.ts +12 -0
- package/src/memory/embedding-backend.test.ts +75 -0
- package/src/memory/embedding-backend.ts +131 -5
- package/src/memory/embedding-gemini.test.ts +54 -0
- package/src/memory/embedding-gemini.ts +20 -9
- package/src/memory/embedding-local.ts +176 -17
- package/src/memory/graph/consolidation.ts +10 -23
- package/src/memory/graph/extraction-job.ts +15 -0
- package/src/memory/graph/retriever.ts +40 -22
- package/src/memory/graph/store.test.ts +7 -3
- package/src/memory/graph/store.ts +47 -12
- package/src/memory/llm-usage-store.ts +45 -4
- package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
- package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
- package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
- package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
- package/src/memory/migrations/217-conversation-host-access.ts +40 -0
- package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +1 -0
- package/src/memory/schema/oauth.ts +18 -13
- package/src/oauth/AGENTS.md +76 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
- package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
- package/src/oauth/byo-connection.test.ts +8 -8
- package/src/oauth/byo-connection.ts +7 -7
- package/src/oauth/connect-orchestrator.ts +23 -21
- package/src/oauth/connect-types.ts +3 -3
- package/src/oauth/connection-resolver.test.ts +17 -4
- package/src/oauth/connection-resolver.ts +16 -16
- package/src/oauth/connection.ts +1 -1
- package/src/oauth/manual-token-connection.ts +13 -13
- package/src/oauth/oauth-store.ts +214 -100
- package/src/oauth/platform-connection.test.ts +3 -3
- package/src/oauth/platform-connection.ts +4 -4
- package/src/oauth/provider-serializer.ts +31 -5
- package/src/oauth/revoke.ts +76 -0
- package/src/oauth/seed-providers.ts +126 -87
- package/src/oauth/token-persistence.ts +1 -1
- package/src/permissions/permission-mode.ts +4 -11
- package/src/permissions/prompter.ts +13 -1
- package/src/permissions/v2-consent-policy.ts +87 -0
- package/src/prompts/system-prompt.ts +18 -21
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
- package/src/prompts/templates/BOOTSTRAP.md +59 -105
- package/src/providers/anthropic/client.ts +1 -0
- package/src/providers/types.ts +1 -1
- package/src/runtime/AGENTS.md +23 -0
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
- package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
- package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
- package/src/runtime/assistant-event-hub.ts +2 -2
- package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
- package/src/runtime/auth/middleware.ts +98 -0
- package/src/runtime/auth/route-policy.ts +6 -7
- package/src/runtime/capability-tokens.ts +414 -0
- package/src/runtime/channel-approvals.ts +18 -5
- package/src/runtime/chrome-extension-registry.ts +332 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
- package/src/runtime/guardian-decision-types.ts +7 -0
- package/src/runtime/http-server.ts +425 -70
- package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
- package/src/runtime/migrations/migration-transport.ts +6 -0
- package/src/runtime/migrations/migration-wizard.ts +22 -2
- package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
- package/src/runtime/migrations/vbundle-builder.ts +145 -38
- package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
- package/src/runtime/migrations/vbundle-importer.ts +55 -5
- package/src/runtime/pending-interactions.ts +29 -13
- package/src/runtime/routes/approval-routes.ts +90 -16
- package/src/runtime/routes/browser-cdp-routes.ts +229 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
- package/src/runtime/routes/conversation-analysis-routes.ts +2 -1
- package/src/runtime/routes/conversation-management-routes.ts +108 -0
- package/src/runtime/routes/conversation-routes.ts +301 -27
- package/src/runtime/routes/conversation-starter-routes.ts +78 -16
- package/src/runtime/routes/guardian-action-routes.ts +24 -13
- package/src/runtime/routes/host-browser-routes.ts +279 -0
- package/src/runtime/routes/host-file-routes.ts +9 -1
- package/src/runtime/routes/identity-routes.ts +259 -16
- package/src/runtime/routes/log-export-routes.ts +42 -22
- package/src/runtime/routes/memory-item-routes.ts +1 -7
- package/src/runtime/routes/migration-routes.ts +87 -2
- package/src/runtime/routes/oauth-apps.ts +15 -17
- package/src/runtime/routes/oauth-providers.ts +4 -0
- package/src/runtime/routes/schedule-routes.ts +24 -11
- package/src/runtime/routes/settings-routes.ts +9 -97
- package/src/runtime/routes/skills-routes.ts +52 -2
- package/src/runtime/routes/subagents-routes.ts +14 -10
- package/src/runtime/routes/usage-routes.ts +8 -7
- package/src/runtime/routes/workspace-routes.test.ts +22 -0
- package/src/runtime/routes/workspace-routes.ts +8 -1
- package/src/runtime/routes/workspace-utils.ts +2 -0
- package/src/schedule/scheduler.ts +7 -5
- package/src/security/ces-credential-client.ts +20 -0
- package/src/security/ces-rpc-credential-backend.ts +17 -0
- package/src/security/credential-backend.ts +5 -0
- package/src/security/oauth2.ts +42 -25
- package/src/security/secure-keys.ts +118 -25
- package/src/security/token-manager.ts +23 -10
- package/src/skills/catalog-files.ts +492 -0
- package/src/subagent/manager.ts +131 -26
- package/src/subagent/types.ts +19 -0
- package/src/tools/apps/executors.ts +11 -2
- package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
- package/src/tools/browser/auth-detector.ts +43 -12
- package/src/tools/browser/browser-execution.ts +645 -340
- package/src/tools/browser/browser-manager.ts +36 -12
- package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
- package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
- package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
- package/src/tools/browser/cdp-client/errors.ts +34 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
- package/src/tools/browser/cdp-client/factory.ts +204 -0
- package/src/tools/browser/cdp-client/index.ts +14 -0
- package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
- package/src/tools/browser/cdp-client/types.ts +52 -0
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +1 -1
- package/src/tools/host-filesystem/read.ts +12 -15
- package/src/tools/host-filesystem/write.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +21 -16
- package/src/tools/permission-checker.ts +77 -82
- package/src/tools/registry.ts +0 -2
- package/src/tools/secret-detection-handler.ts +34 -0
- package/src/tools/shared/filesystem/image-read.ts +61 -40
- package/src/tools/subagent/spawn.ts +47 -3
- package/src/tools/subagent/status.ts +2 -0
- package/src/tools/system/register.ts +2 -16
- package/src/tools/terminal/safe-env.ts +7 -0
- package/src/tools/terminal/shell.ts +21 -16
- package/src/tools/tool-approval-handler.ts +48 -2
- package/src/tools/types.ts +2 -0
- package/src/util/platform.ts +14 -19
- package/src/workspace/top-level-renderer.ts +19 -1
- package/src/__tests__/chrome-cdp.test.ts +0 -419
- package/src/__tests__/permission-mode-sse.test.ts +0 -418
- package/src/__tests__/permission-mode-store.test.ts +0 -277
- package/src/browser-extension-relay/protocol.ts +0 -63
- package/src/browser-extension-relay/server.ts +0 -203
- package/src/config/schemas/sandbox.ts +0 -14
- package/src/permissions/permission-mode-store.ts +0 -180
- package/src/tools/browser/chrome-cdp.ts +0 -239
- 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
|
-
? {
|
|
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
|
-
|
|
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
|
|