@vellumai/assistant 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +40 -40
- package/bunfig.toml +3 -0
- package/docker-entrypoint.sh +12 -2
- package/docs/architecture/memory.md +1 -1
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
- 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__/assistant-event-hub.test.ts +30 -0
- 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__/checker.test.ts +104 -170
- package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
- 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 +21 -6
- 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 +169 -0
- package/src/__tests__/conversation-attachments.test.ts +80 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
- package/src/__tests__/conversation-directories-parse.test.ts +105 -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 -3
- 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__/init-feature-flag-overrides.test.ts +167 -0
- package/src/__tests__/inline-command-runner.test.ts +7 -5
- package/src/__tests__/integration-status.test.ts +6 -7
- package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
- package/src/__tests__/log-export-workspace.test.ts +190 -0
- package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
- 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__/navigate-settings-tab.test.ts +14 -1
- package/src/__tests__/notification-broadcaster.test.ts +65 -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 +74 -55
- 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__/pkb-autoinject.test.ts +96 -0
- 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 -3
- package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
- 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-sandbox.test.ts +1 -1
- package/src/__tests__/terminal-tools.test.ts +11 -5
- package/src/__tests__/test-preload.ts +14 -0
- package/src/__tests__/tool-approval-handler.test.ts +73 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
- package/src/__tests__/tool-executor.test.ts +0 -1
- 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 +62 -0
- package/src/__tests__/trust-store.test.ts +4 -4
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
- package/src/__tests__/v2-consent-policy.test.ts +103 -0
- package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
- package/src/__tests__/workspace-policy.test.ts +2 -7
- package/src/acp/client-handler.ts +30 -4
- package/src/agent/loop.ts +12 -35
- 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 +55 -0
- package/src/cli/__tests__/run-assistant-command.ts +34 -7
- package/src/cli/__tests__/unknown-command.test.ts +33 -0
- package/src/cli/commands/browser-relay.ts +339 -409
- package/src/cli/commands/credentials.ts +3 -3
- package/src/cli/commands/default-action.ts +68 -1
- 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 +68 -41
- 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 +16 -2
- 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/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
- 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 +10 -3
- package/src/config/assistant-feature-flags.ts +59 -55
- package/src/config/bundled-skills/app-builder/SKILL.md +33 -173
- 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 +12 -7
- package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
- package/src/config/bundled-skills/outlook/SKILL.md +7 -0
- package/src/config/bundled-skills/settings/TOOLS.json +1 -1
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
- 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 +46 -7
- 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 +16 -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 -16
- package/src/credential-execution/managed-catalog.ts +3 -7
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
- package/src/daemon/app-source-watcher.ts +35 -0
- package/src/daemon/config-watcher.ts +6 -2
- package/src/daemon/context-overflow-approval.ts +5 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
- package/src/daemon/conversation-agent-loop.ts +74 -19
- package/src/daemon/conversation-attachments.ts +40 -1
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +66 -3
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +159 -20
- package/src/daemon/conversation-surfaces.ts +78 -12
- package/src/daemon/conversation-tool-setup.ts +74 -11
- package/src/daemon/conversation-workspace.ts +12 -0
- package/src/daemon/conversation.ts +227 -11
- package/src/daemon/date-context.ts +10 -10
- package/src/daemon/first-greeting.ts +3 -2
- package/src/daemon/handlers/conversations.ts +9 -139
- package/src/daemon/handlers/shared.ts +65 -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 +86 -12
- package/src/daemon/message-protocol.ts +7 -0
- package/src/daemon/message-types/conversations.ts +59 -13
- package/src/daemon/message-types/host-browser.ts +100 -0
- package/src/daemon/message-types/messages.ts +5 -6
- package/src/daemon/message-types/notifications.ts +12 -0
- package/src/daemon/message-types/settings.ts +12 -0
- package/src/daemon/message-types/skills.ts +10 -0
- package/src/daemon/message-types/subagents.ts +2 -0
- package/src/daemon/server.ts +112 -35
- package/src/daemon/tool-side-effects.ts +6 -0
- package/src/daemon/transport-hints.ts +14 -0
- package/src/inbound/platform-callback-registration.ts +18 -17
- package/src/index.ts +1 -1
- package/src/mcp/client.ts +59 -24
- package/src/memory/app-store.ts +31 -1
- package/src/memory/conversation-crud.ts +38 -10
- package/src/memory/conversation-directories.ts +39 -0
- package/src/memory/conversation-group-migration.ts +65 -5
- 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 +177 -18
- package/src/memory/graph/capability-seed.ts +3 -5
- 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/group-crud.ts +25 -9
- 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/messaging/provider.ts +1 -1
- package/src/notifications/broadcaster.ts +6 -0
- package/src/notifications/conversation-pairing.ts +12 -4
- package/src/notifications/emit-signal.ts +14 -0
- package/src/notifications/signal.ts +11 -0
- 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 +5 -5
- 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 +127 -87
- package/src/oauth/token-persistence.ts +1 -1
- package/src/permissions/checker.ts +3 -3
- package/src/permissions/defaults.ts +7 -8
- package/src/permissions/permission-mode.ts +4 -11
- package/src/permissions/prompter.ts +13 -3
- package/src/permissions/v2-consent-policy.ts +87 -0
- package/src/platform/client.ts +1 -1
- package/src/prompts/system-prompt.ts +18 -21
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
- package/src/prompts/templates/BOOTSTRAP.md +59 -96
- package/src/prompts/templates/SOUL.md +11 -11
- 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 +24 -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/auth/token-service.ts +8 -0
- 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 +18 -5
- package/src/runtime/routes/conversation-management-routes.ts +108 -0
- package/src/runtime/routes/conversation-routes.ts +308 -28
- package/src/runtime/routes/conversation-starter-routes.ts +78 -16
- package/src/runtime/routes/group-routes.ts +22 -8
- 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/AGENTS.md +104 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
- package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
- package/src/runtime/routes/log-export-routes.ts +60 -25
- 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/skills/inline-command-runner.ts +12 -14
- 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 -100
- package/src/tools/registry.ts +0 -2
- package/src/tools/secret-detection-handler.ts +34 -1
- package/src/tools/shared/filesystem/image-read.ts +61 -40
- package/src/tools/skills/sandbox-runner.ts +3 -6
- 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/sandbox-diagnostics.ts +4 -4
- package/src/tools/terminal/sandbox.ts +4 -1
- package/src/tools/terminal/shell.ts +24 -21
- package/src/tools/tool-approval-handler.ts +48 -2
- package/src/tools/types.ts +2 -3
- package/src/util/platform.ts +14 -19
- package/src/watcher/provider-types.ts +1 -1
- package/src/workspace/migrations/029-seed-pkb.ts +1 -0
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
- package/src/workspace/migrations/registry.ts +2 -0
- 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
|
@@ -21,17 +21,16 @@ mock.module("../config/loader.js", () => ({
|
|
|
21
21
|
}));
|
|
22
22
|
|
|
23
23
|
mock.module("../oauth/oauth-store.js", () => ({
|
|
24
|
-
isProviderConnected: (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
? { id: `conn-${providerKey}`, status: "active" }
|
|
24
|
+
isProviderConnected: (provider: string) => connectedProviders.has(provider),
|
|
25
|
+
getConnectionByProvider: (provider: string) =>
|
|
26
|
+
connectedProviders.has(provider)
|
|
27
|
+
? { id: `conn-${provider}`, status: "active" }
|
|
29
28
|
: undefined,
|
|
30
29
|
}));
|
|
31
30
|
|
|
32
31
|
/** Mark a provider as fully connected (active row + access token). */
|
|
33
|
-
function setOAuthConnected(
|
|
34
|
-
connectedProviders.add(
|
|
32
|
+
function setOAuthConnected(provider: string): void {
|
|
33
|
+
connectedProviders.add(provider);
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
const { getIntegrationSummary, formatIntegrationSummary, hasCapability } =
|
|
@@ -84,7 +84,11 @@ describe("handleListMessages tool_result merging", () => {
|
|
|
84
84
|
conv.id,
|
|
85
85
|
"user",
|
|
86
86
|
JSON.stringify([
|
|
87
|
-
{
|
|
87
|
+
{
|
|
88
|
+
type: "tool_result",
|
|
89
|
+
tool_use_id: "tu1",
|
|
90
|
+
content: "file1.txt\nfile2.txt",
|
|
91
|
+
},
|
|
88
92
|
]),
|
|
89
93
|
);
|
|
90
94
|
|
|
@@ -117,7 +121,12 @@ describe("handleListMessages tool_result merging", () => {
|
|
|
117
121
|
JSON.stringify([
|
|
118
122
|
{ type: "tool_use", id: "tu1", name: "bash", input: { command: "ls" } },
|
|
119
123
|
{ type: "text", text: "and also" },
|
|
120
|
-
{
|
|
124
|
+
{
|
|
125
|
+
type: "tool_use",
|
|
126
|
+
id: "tu2",
|
|
127
|
+
name: "file_read",
|
|
128
|
+
input: { path: "/tmp/a" },
|
|
129
|
+
},
|
|
121
130
|
]),
|
|
122
131
|
);
|
|
123
132
|
await addMessage(
|
|
@@ -176,7 +185,11 @@ describe("handleListMessages tool_result merging", () => {
|
|
|
176
185
|
conv.id,
|
|
177
186
|
"user",
|
|
178
187
|
JSON.stringify([
|
|
179
|
-
{
|
|
188
|
+
{
|
|
189
|
+
type: "tool_result",
|
|
190
|
+
tool_use_id: "tu_orphan",
|
|
191
|
+
content: "stale result",
|
|
192
|
+
},
|
|
180
193
|
]),
|
|
181
194
|
);
|
|
182
195
|
await addMessage(
|
|
@@ -228,7 +241,12 @@ describe("handleListMessages tool_result merging", () => {
|
|
|
228
241
|
"assistant",
|
|
229
242
|
JSON.stringify([
|
|
230
243
|
{ type: "text", text: "Now reading:" },
|
|
231
|
-
{
|
|
244
|
+
{
|
|
245
|
+
type: "tool_use",
|
|
246
|
+
id: "tu2",
|
|
247
|
+
name: "file_read",
|
|
248
|
+
input: { path: "/x" },
|
|
249
|
+
},
|
|
232
250
|
]),
|
|
233
251
|
);
|
|
234
252
|
await addMessage(
|
|
@@ -248,17 +266,19 @@ describe("handleListMessages tool_result merging", () => {
|
|
|
248
266
|
const response = handleListMessages(createTestUrl(conv.id), null);
|
|
249
267
|
const body = (await response.json()) as { messages: MessagePayload[] };
|
|
250
268
|
|
|
251
|
-
//
|
|
252
|
-
|
|
269
|
+
// Consecutive assistant messages are merged at query time so the client
|
|
270
|
+
// sees one grouped message (matching the streaming path behavior).
|
|
271
|
+
// user("list files"), merged-assistant(bash + file_read), user("thanks")
|
|
272
|
+
expect(body.messages).toHaveLength(3);
|
|
253
273
|
expect(body.messages[0].role).toBe("user");
|
|
254
274
|
expect(body.messages[1].role).toBe("assistant");
|
|
275
|
+
expect(body.messages[1].toolCalls).toHaveLength(2);
|
|
255
276
|
expect(body.messages[1].toolCalls![0].name).toBe("bash");
|
|
256
277
|
expect(body.messages[1].toolCalls![0].result).toBe("files");
|
|
257
|
-
expect(body.messages[
|
|
258
|
-
expect(body.messages[
|
|
259
|
-
expect(body.messages[2].
|
|
260
|
-
expect(body.messages[
|
|
261
|
-
expect(body.messages[3].content).toBe("thanks");
|
|
278
|
+
expect(body.messages[1].toolCalls![1].name).toBe("file_read");
|
|
279
|
+
expect(body.messages[1].toolCalls![1].result).toBe("file data");
|
|
280
|
+
expect(body.messages[2].role).toBe("user");
|
|
281
|
+
expect(body.messages[2].content).toBe("thanks");
|
|
262
282
|
});
|
|
263
283
|
|
|
264
284
|
test("tool_result with is_error propagates error status", async () => {
|
|
@@ -272,7 +292,12 @@ describe("handleListMessages tool_result merging", () => {
|
|
|
272
292
|
conv.id,
|
|
273
293
|
"assistant",
|
|
274
294
|
JSON.stringify([
|
|
275
|
-
{
|
|
295
|
+
{
|
|
296
|
+
type: "tool_use",
|
|
297
|
+
id: "tu1",
|
|
298
|
+
name: "bash",
|
|
299
|
+
input: { command: "fail" },
|
|
300
|
+
},
|
|
276
301
|
]),
|
|
277
302
|
);
|
|
278
303
|
await addMessage(
|
|
@@ -93,6 +93,35 @@ writeFileSync(
|
|
|
93
93
|
JSON.stringify({ provider: "anthropic" }),
|
|
94
94
|
);
|
|
95
95
|
|
|
96
|
+
// Conversation directories — used for workspace allowlist tests
|
|
97
|
+
const conversationsDir = join(testWorkspaceDir, "conversations");
|
|
98
|
+
mkdirSync(conversationsDir, { recursive: true });
|
|
99
|
+
|
|
100
|
+
function seedConversation(name: string, body: string) {
|
|
101
|
+
const dir = join(conversationsDir, name);
|
|
102
|
+
mkdirSync(dir, { recursive: true });
|
|
103
|
+
writeFileSync(join(dir, "meta.json"), "{}\n");
|
|
104
|
+
writeFileSync(join(dir, "messages.jsonl"), body);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
seedConversation(
|
|
108
|
+
"2025-01-10T00-00-00.000Z_conv-jan10",
|
|
109
|
+
'{"role":"user","content":"jan 10"}\n',
|
|
110
|
+
);
|
|
111
|
+
seedConversation(
|
|
112
|
+
"2025-01-15T00-00-00.000Z_conv-jan15",
|
|
113
|
+
'{"role":"user","content":"jan 15"}\n',
|
|
114
|
+
);
|
|
115
|
+
seedConversation(
|
|
116
|
+
"2025-01-20T00-00-00.000Z_conv-jan20",
|
|
117
|
+
'{"role":"user","content":"jan 20"}\n',
|
|
118
|
+
);
|
|
119
|
+
seedConversation(
|
|
120
|
+
"2025-01-25T00-00-00.000Z_conv-jan25",
|
|
121
|
+
'{"role":"user","content":"jan 25"}\n',
|
|
122
|
+
);
|
|
123
|
+
seedConversation("malformed-name", '{"role":"user","content":"x"}\n');
|
|
124
|
+
|
|
96
125
|
// Daemon log files — used for date filtering tests
|
|
97
126
|
const logsDir = join(testWorkspaceDir, "data", "logs");
|
|
98
127
|
mkdirSync(logsDir, { recursive: true });
|
|
@@ -241,3 +270,164 @@ describe("POST /v1/export — daemon log date filtering", () => {
|
|
|
241
270
|
}
|
|
242
271
|
});
|
|
243
272
|
});
|
|
273
|
+
|
|
274
|
+
describe("POST /v1/export — workspace allowlist", () => {
|
|
275
|
+
test("includes all valid conversation dirs by default", async () => {
|
|
276
|
+
const res = await callExport();
|
|
277
|
+
const dir = await extractArchive(res);
|
|
278
|
+
try {
|
|
279
|
+
const convs = readdirSync(join(dir, "workspace", "conversations"));
|
|
280
|
+
expect(convs).toContain("2025-01-10T00-00-00.000Z_conv-jan10");
|
|
281
|
+
expect(convs).toContain("2025-01-15T00-00-00.000Z_conv-jan15");
|
|
282
|
+
expect(convs).toContain("2025-01-20T00-00-00.000Z_conv-jan20");
|
|
283
|
+
expect(convs).toContain("2025-01-25T00-00-00.000Z_conv-jan25");
|
|
284
|
+
expect(convs).not.toContain("malformed-name");
|
|
285
|
+
} finally {
|
|
286
|
+
rmSync(dir, { recursive: true, force: true });
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("skips malformed conversation dir names", async () => {
|
|
291
|
+
const res = await callExport();
|
|
292
|
+
const dir = await extractArchive(res);
|
|
293
|
+
try {
|
|
294
|
+
const convs = readdirSync(join(dir, "workspace", "conversations"));
|
|
295
|
+
expect(convs).not.toContain("malformed-name");
|
|
296
|
+
} finally {
|
|
297
|
+
rmSync(dir, { recursive: true, force: true });
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test("filters conversation dirs by startTime", async () => {
|
|
302
|
+
const startTime = Date.parse("2025-01-14T00:00:00Z");
|
|
303
|
+
const res = await callExport({ startTime });
|
|
304
|
+
const dir = await extractArchive(res);
|
|
305
|
+
try {
|
|
306
|
+
const convs = readdirSync(join(dir, "workspace", "conversations"));
|
|
307
|
+
expect(convs).not.toContain("2025-01-10T00-00-00.000Z_conv-jan10");
|
|
308
|
+
expect(convs).toContain("2025-01-15T00-00-00.000Z_conv-jan15");
|
|
309
|
+
expect(convs).toContain("2025-01-20T00-00-00.000Z_conv-jan20");
|
|
310
|
+
expect(convs).toContain("2025-01-25T00-00-00.000Z_conv-jan25");
|
|
311
|
+
} finally {
|
|
312
|
+
rmSync(dir, { recursive: true, force: true });
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("filters conversation dirs by endTime", async () => {
|
|
317
|
+
const endTime = Date.parse("2025-01-22T00:00:00Z");
|
|
318
|
+
const res = await callExport({ endTime });
|
|
319
|
+
const dir = await extractArchive(res);
|
|
320
|
+
try {
|
|
321
|
+
const convs = readdirSync(join(dir, "workspace", "conversations"));
|
|
322
|
+
expect(convs).toContain("2025-01-10T00-00-00.000Z_conv-jan10");
|
|
323
|
+
expect(convs).toContain("2025-01-15T00-00-00.000Z_conv-jan15");
|
|
324
|
+
expect(convs).toContain("2025-01-20T00-00-00.000Z_conv-jan20");
|
|
325
|
+
expect(convs).not.toContain("2025-01-25T00-00-00.000Z_conv-jan25");
|
|
326
|
+
} finally {
|
|
327
|
+
rmSync(dir, { recursive: true, force: true });
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("filters conversation dirs by both startTime and endTime", async () => {
|
|
332
|
+
const startTime = Date.parse("2025-01-14T00:00:00Z");
|
|
333
|
+
const endTime = Date.parse("2025-01-22T00:00:00Z");
|
|
334
|
+
const res = await callExport({ startTime, endTime });
|
|
335
|
+
const dir = await extractArchive(res);
|
|
336
|
+
try {
|
|
337
|
+
const convs = readdirSync(join(dir, "workspace", "conversations"));
|
|
338
|
+
expect(convs).not.toContain("2025-01-10T00-00-00.000Z_conv-jan10");
|
|
339
|
+
expect(convs).toContain("2025-01-15T00-00-00.000Z_conv-jan15");
|
|
340
|
+
expect(convs).toContain("2025-01-20T00-00-00.000Z_conv-jan20");
|
|
341
|
+
expect(convs).not.toContain("2025-01-25T00-00-00.000Z_conv-jan25");
|
|
342
|
+
} finally {
|
|
343
|
+
rmSync(dir, { recursive: true, force: true });
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test("filters conversation dirs by conversationId", async () => {
|
|
348
|
+
const res = await callExport({ conversationId: "conv-jan15" });
|
|
349
|
+
const dir = await extractArchive(res);
|
|
350
|
+
try {
|
|
351
|
+
const convs = readdirSync(join(dir, "workspace", "conversations"));
|
|
352
|
+
expect(convs).toContain("2025-01-15T00-00-00.000Z_conv-jan15");
|
|
353
|
+
expect(convs).not.toContain("2025-01-10T00-00-00.000Z_conv-jan10");
|
|
354
|
+
expect(convs).not.toContain("2025-01-20T00-00-00.000Z_conv-jan20");
|
|
355
|
+
expect(convs).not.toContain("2025-01-25T00-00-00.000Z_conv-jan25");
|
|
356
|
+
expect(convs).not.toContain("malformed-name");
|
|
357
|
+
} finally {
|
|
358
|
+
rmSync(dir, { recursive: true, force: true });
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
test("conversationId + time filter intersect", async () => {
|
|
363
|
+
const res = await callExport({
|
|
364
|
+
conversationId: "conv-jan15",
|
|
365
|
+
startTime: Date.parse("2025-02-01T00:00:00Z"),
|
|
366
|
+
});
|
|
367
|
+
const dir = await extractArchive(res);
|
|
368
|
+
try {
|
|
369
|
+
const conversationsPath = join(dir, "workspace", "conversations");
|
|
370
|
+
let convs: string[] = [];
|
|
371
|
+
try {
|
|
372
|
+
convs = readdirSync(conversationsPath);
|
|
373
|
+
} catch {
|
|
374
|
+
// Directory does not exist — acceptable per the test contract.
|
|
375
|
+
}
|
|
376
|
+
expect(convs).toEqual([]);
|
|
377
|
+
} finally {
|
|
378
|
+
rmSync(dir, { recursive: true, force: true });
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("conversation dir contents survive the round trip", async () => {
|
|
383
|
+
const res = await callExport();
|
|
384
|
+
const dir = await extractArchive(res);
|
|
385
|
+
try {
|
|
386
|
+
const messagesPath = join(
|
|
387
|
+
dir,
|
|
388
|
+
"workspace",
|
|
389
|
+
"conversations",
|
|
390
|
+
"2025-01-15T00-00-00.000Z_conv-jan15",
|
|
391
|
+
"messages.jsonl",
|
|
392
|
+
);
|
|
393
|
+
const content = readFileSync(messagesPath, "utf-8");
|
|
394
|
+
expect(content).toBe('{"role":"user","content":"jan 15"}\n');
|
|
395
|
+
} finally {
|
|
396
|
+
rmSync(dir, { recursive: true, force: true });
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test("treats empty-string conversationId as no filter", async () => {
|
|
401
|
+
const res = await callExport({ conversationId: "" });
|
|
402
|
+
const dir = await extractArchive(res);
|
|
403
|
+
try {
|
|
404
|
+
// With conversationId === "" (which the rest of handleExport treats as
|
|
405
|
+
// unfiltered), workspace conversations should also be unfiltered. All
|
|
406
|
+
// four canonical conversation dirs should be present.
|
|
407
|
+
const conversationsDir = join(dir, "workspace", "conversations");
|
|
408
|
+
const entries = readdirSync(conversationsDir);
|
|
409
|
+
expect(entries).toContain("2025-01-10T00-00-00.000Z_conv-jan10");
|
|
410
|
+
expect(entries).toContain("2025-01-15T00-00-00.000Z_conv-jan15");
|
|
411
|
+
expect(entries).toContain("2025-01-20T00-00-00.000Z_conv-jan20");
|
|
412
|
+
expect(entries).toContain("2025-01-25T00-00-00.000Z_conv-jan25");
|
|
413
|
+
} finally {
|
|
414
|
+
rmSync(dir, { recursive: true, force: true });
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test("treats startTime=0 and endTime=0 as no filter", async () => {
|
|
419
|
+
const res = await callExport({ startTime: 0, endTime: 0 });
|
|
420
|
+
const dir = await extractArchive(res);
|
|
421
|
+
try {
|
|
422
|
+
const conversationsDir = join(dir, "workspace", "conversations");
|
|
423
|
+
const entries = readdirSync(conversationsDir);
|
|
424
|
+
// All four canonical conversation dirs should be present (no filtering).
|
|
425
|
+
expect(entries).toContain("2025-01-10T00-00-00.000Z_conv-jan10");
|
|
426
|
+
expect(entries).toContain("2025-01-15T00-00-00.000Z_conv-jan15");
|
|
427
|
+
expect(entries).toContain("2025-01-20T00-00-00.000Z_conv-jan20");
|
|
428
|
+
expect(entries).toContain("2025-01-25T00-00-00.000Z_conv-jan25");
|
|
429
|
+
} finally {
|
|
430
|
+
rmSync(dir, { recursive: true, force: true });
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
});
|
|
@@ -45,7 +45,10 @@ mock.module("../security/secure-keys.js", () => ({
|
|
|
45
45
|
|
|
46
46
|
// Mock global fetch
|
|
47
47
|
const _originalFetch = globalThis.fetch;
|
|
48
|
-
const mockFetch = async (
|
|
48
|
+
const mockFetch = async (
|
|
49
|
+
input: string | URL | Request,
|
|
50
|
+
_init?: RequestInit,
|
|
51
|
+
) => {
|
|
49
52
|
const url =
|
|
50
53
|
typeof input === "string"
|
|
51
54
|
? input
|
|
@@ -57,14 +60,6 @@ const mockFetch = async (input: string | URL | Request, init?: RequestInit) => {
|
|
|
57
60
|
return new Response("Not Found", { status: 404 });
|
|
58
61
|
}
|
|
59
62
|
|
|
60
|
-
// Verify the Authorization header never leaks secrets into the request path
|
|
61
|
-
const authHeader = init?.headers
|
|
62
|
-
? (init.headers as Record<string, string>)["Authorization"]
|
|
63
|
-
: undefined;
|
|
64
|
-
if (authHeader && !authHeader.startsWith("Api-Key ")) {
|
|
65
|
-
throw new Error("Unexpected auth header format");
|
|
66
|
-
}
|
|
67
|
-
|
|
68
63
|
return new Response(JSON.stringify(entry.body), {
|
|
69
64
|
status: entry.status,
|
|
70
65
|
headers: { "Content-Type": "application/json" },
|
|
@@ -266,17 +261,17 @@ describe("fetchManagedCatalog", () => {
|
|
|
266
261
|
expect(descriptor.handle).toBe("platform_oauth:conn_minimal");
|
|
267
262
|
});
|
|
268
263
|
|
|
269
|
-
test("error messages never contain
|
|
264
|
+
test("error messages never contain sensitive details", async () => {
|
|
270
265
|
mockPlatformBaseUrl = "https://platform.example.com";
|
|
271
266
|
mockAssistantApiKey = "sk-super-secret-key-12345";
|
|
272
267
|
mockPlatformAssistantId = "ast-uuid-1234";
|
|
273
268
|
|
|
274
|
-
// Simulate a network error
|
|
269
|
+
// Simulate a network error whose message contains sensitive data
|
|
275
270
|
const savedFetch = globalThis.fetch;
|
|
276
271
|
const errorFetch: typeof fetch = Object.assign(
|
|
277
272
|
async () => {
|
|
278
273
|
throw new Error(
|
|
279
|
-
"Connect failed to https://platform.example.com/v1/assistants/ast-uuid-1234/oauth/managed/catalog/ with
|
|
274
|
+
"Connect failed to https://platform.example.com/v1/assistants/ast-uuid-1234/oauth/managed/catalog/ with Bearer sk-super-secret-key-12345",
|
|
280
275
|
);
|
|
281
276
|
},
|
|
282
277
|
{ preconnect: savedFetch.preconnect },
|
|
@@ -287,9 +282,12 @@ describe("fetchManagedCatalog", () => {
|
|
|
287
282
|
const result = await fetchManagedCatalog();
|
|
288
283
|
expect(result.ok).toBe(false);
|
|
289
284
|
expect(result.error).toBeDefined();
|
|
290
|
-
//
|
|
285
|
+
// Raw error message (with URL, API key, etc.) must not leak
|
|
291
286
|
expect(result.error).not.toContain("sk-super-secret-key-12345");
|
|
292
|
-
expect(result.error).toContain("
|
|
287
|
+
expect(result.error).not.toContain("platform.example.com");
|
|
288
|
+
expect(result.error).not.toContain("Connect failed");
|
|
289
|
+
// Should only contain the error class name
|
|
290
|
+
expect(result.error).toContain("Error");
|
|
293
291
|
} finally {
|
|
294
292
|
globalThis.fetch = savedFetch;
|
|
295
293
|
}
|
|
@@ -13,6 +13,7 @@ mock.module("../inbound/platform-callback-registration.js", () => ({
|
|
|
13
13
|
}));
|
|
14
14
|
|
|
15
15
|
const { McpClient } = await import("../mcp/client.js");
|
|
16
|
+
const { McpOAuthProvider } = await import("../mcp/mcp-oauth-provider.js");
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Mimics the SDK's StreamableHTTPError which has a `.code` property
|
|
@@ -67,7 +68,7 @@ describe("McpClient auth error detection", () => {
|
|
|
67
68
|
expect(client.isConnected).toBe(false);
|
|
68
69
|
});
|
|
69
70
|
|
|
70
|
-
test("
|
|
71
|
+
test("swallows non-auth StreamableHTTPError (connect never throws)", async () => {
|
|
71
72
|
const client = new McpClient("test-server");
|
|
72
73
|
|
|
73
74
|
(client as any).createTransport = () => ({});
|
|
@@ -78,9 +79,9 @@ describe("McpClient auth error detection", () => {
|
|
|
78
79
|
close: async () => {},
|
|
79
80
|
};
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
);
|
|
82
|
+
// Non-auth errors are logged but never propagated — daemon keeps running
|
|
83
|
+
await client.connect(httpTransport);
|
|
84
|
+
expect(client.isConnected).toBe(false);
|
|
84
85
|
});
|
|
85
86
|
|
|
86
87
|
test("treats error message containing 'unauthorized' as auth error", async () => {
|
|
@@ -97,4 +98,39 @@ describe("McpClient auth error detection", () => {
|
|
|
97
98
|
await client.connect(httpTransport);
|
|
98
99
|
expect(client.isConnected).toBe(false);
|
|
99
100
|
});
|
|
101
|
+
|
|
102
|
+
test("treats SDK fetchToken 'authorizationCode is required' error as auth error", async () => {
|
|
103
|
+
const client = new McpClient("test-server");
|
|
104
|
+
|
|
105
|
+
(client as any).createTransport = () => ({});
|
|
106
|
+
(client as any).client = {
|
|
107
|
+
connect: () => {
|
|
108
|
+
throw new Error(
|
|
109
|
+
"Either provider.prepareTokenRequest() or authorizationCode is required",
|
|
110
|
+
);
|
|
111
|
+
},
|
|
112
|
+
close: async () => {},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
await client.connect(httpTransport);
|
|
116
|
+
expect(client.isConnected).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe("McpOAuthProvider redirectUrl", () => {
|
|
121
|
+
test("redirectUrl is undefined until startCallbackServer() is called", () => {
|
|
122
|
+
const nonInteractive = new McpOAuthProvider(
|
|
123
|
+
"test-server",
|
|
124
|
+
"https://example.com/mcp",
|
|
125
|
+
/* interactive */ false,
|
|
126
|
+
);
|
|
127
|
+
expect(nonInteractive.redirectUrl).toBeUndefined();
|
|
128
|
+
|
|
129
|
+
const interactive = new McpOAuthProvider(
|
|
130
|
+
"test-server",
|
|
131
|
+
"https://example.com/mcp",
|
|
132
|
+
/* interactive */ true,
|
|
133
|
+
);
|
|
134
|
+
expect(interactive.redirectUrl).toBeUndefined();
|
|
135
|
+
});
|
|
100
136
|
});
|
|
@@ -3,12 +3,16 @@ import { beforeEach, describe, expect, jest, mock, test } from "bun:test";
|
|
|
3
3
|
const mockConnect = jest.fn();
|
|
4
4
|
const mockDisconnect = jest.fn();
|
|
5
5
|
let mockIsConnected = true;
|
|
6
|
+
let mockLastError: Error | null = null;
|
|
6
7
|
|
|
7
8
|
mock.module("../mcp/client.js", () => ({
|
|
8
9
|
McpClient: class {
|
|
9
10
|
get isConnected() {
|
|
10
11
|
return mockIsConnected;
|
|
11
12
|
}
|
|
13
|
+
get lastError() {
|
|
14
|
+
return mockLastError;
|
|
15
|
+
}
|
|
12
16
|
connect = mockConnect;
|
|
13
17
|
disconnect = mockDisconnect;
|
|
14
18
|
},
|
|
@@ -32,6 +36,7 @@ describe("checkServerHealth", () => {
|
|
|
32
36
|
mockConnect.mockReset();
|
|
33
37
|
mockDisconnect.mockReset();
|
|
34
38
|
mockIsConnected = true;
|
|
39
|
+
mockLastError = null;
|
|
35
40
|
});
|
|
36
41
|
|
|
37
42
|
test("returns Connected when server connects successfully", async () => {
|
|
@@ -43,7 +48,7 @@ describe("checkServerHealth", () => {
|
|
|
43
48
|
expect(mockDisconnect).toHaveBeenCalled();
|
|
44
49
|
});
|
|
45
50
|
|
|
46
|
-
test("returns Needs authentication when isConnected is false", async () => {
|
|
51
|
+
test("returns Needs authentication when isConnected is false and no lastError", async () => {
|
|
47
52
|
mockConnect.mockResolvedValue(undefined);
|
|
48
53
|
mockIsConnected = false;
|
|
49
54
|
|
|
@@ -51,8 +56,10 @@ describe("checkServerHealth", () => {
|
|
|
51
56
|
expect(result).toContain("Needs authentication");
|
|
52
57
|
});
|
|
53
58
|
|
|
54
|
-
test("returns Error when connect
|
|
55
|
-
mockConnect.
|
|
59
|
+
test("returns Error when connect fails with lastError", async () => {
|
|
60
|
+
mockConnect.mockResolvedValue(undefined);
|
|
61
|
+
mockIsConnected = false;
|
|
62
|
+
mockLastError = new Error("Connection refused");
|
|
56
63
|
mockDisconnect.mockResolvedValue(undefined);
|
|
57
64
|
|
|
58
65
|
const result = await checkServerHealth("test", serverConfig());
|
|
@@ -834,7 +834,9 @@ describe("round-trip: export -> validate -> preflight -> import", () => {
|
|
|
834
834
|
expect(sha256Hex(writtenDb)).toBe(sha256Hex(dbData));
|
|
835
835
|
|
|
836
836
|
const writtenConfig = readFileSync(testConfigPath, "utf8");
|
|
837
|
-
expect(writtenConfig).toBe(
|
|
837
|
+
expect(writtenConfig).toBe(
|
|
838
|
+
JSON.stringify({ model: "test-round-trip" }, null, 2) + "\n",
|
|
839
|
+
);
|
|
838
840
|
}
|
|
839
841
|
});
|
|
840
842
|
|
|
@@ -75,7 +75,7 @@ beforeAll(() => {
|
|
|
75
75
|
// Write test fixture files so the export reads real data
|
|
76
76
|
mkdirSync(testDbDir, { recursive: true });
|
|
77
77
|
writeFileSync(testDbPath, SQLITE_HEADER);
|
|
78
|
-
writeFileSync(testConfigPath, JSON.stringify(TEST_CONFIG, null, 2));
|
|
78
|
+
writeFileSync(testConfigPath, JSON.stringify(TEST_CONFIG, null, 2) + "\n");
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
// ---------------------------------------------------------------------------
|
|
@@ -323,7 +323,7 @@ describe("export data population", () => {
|
|
|
323
323
|
);
|
|
324
324
|
expect(configFile).toBeDefined();
|
|
325
325
|
const expectedConfigSize = Buffer.byteLength(
|
|
326
|
-
JSON.stringify(TEST_CONFIG, null, 2),
|
|
326
|
+
JSON.stringify(TEST_CONFIG, null, 2) + "\n",
|
|
327
327
|
);
|
|
328
328
|
expect(configFile!.size).toBe(expectedConfigSize);
|
|
329
329
|
});
|
|
@@ -428,6 +428,65 @@ describe("export graceful fallback", () => {
|
|
|
428
428
|
});
|
|
429
429
|
});
|
|
430
430
|
|
|
431
|
+
// ---------------------------------------------------------------------------
|
|
432
|
+
// Config sanitization tests
|
|
433
|
+
// ---------------------------------------------------------------------------
|
|
434
|
+
|
|
435
|
+
describe("export config sanitization", () => {
|
|
436
|
+
test("exported config.json has environment-specific fields stripped", async () => {
|
|
437
|
+
// Write a config with environment-specific fields that should be stripped
|
|
438
|
+
const configWithEnvFields = {
|
|
439
|
+
provider: "anthropic",
|
|
440
|
+
model: "test-model",
|
|
441
|
+
ingress: {
|
|
442
|
+
publicBaseUrl: "https://my-tunnel.example.com",
|
|
443
|
+
enabled: true,
|
|
444
|
+
port: 8080,
|
|
445
|
+
},
|
|
446
|
+
daemon: {
|
|
447
|
+
autoStart: true,
|
|
448
|
+
logLevel: "debug",
|
|
449
|
+
},
|
|
450
|
+
skills: {
|
|
451
|
+
load: {
|
|
452
|
+
extraDirs: ["/home/user/custom-skills", "/opt/skills"],
|
|
453
|
+
autoReload: true,
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
memory: { enabled: true },
|
|
457
|
+
};
|
|
458
|
+
writeFileSync(testConfigPath, JSON.stringify(configWithEnvFields, null, 2));
|
|
459
|
+
|
|
460
|
+
const req = new Request("http://localhost/v1/migrations/export", {
|
|
461
|
+
method: "POST",
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
const res = await handleMigrationExport(req);
|
|
465
|
+
const archiveData = new Uint8Array(await res.arrayBuffer());
|
|
466
|
+
const entries = parseTarEntries(archiveData);
|
|
467
|
+
|
|
468
|
+
const configEntry = entries.find((e) => e.name === "workspace/config.json");
|
|
469
|
+
expect(configEntry).toBeDefined();
|
|
470
|
+
|
|
471
|
+
const parsedConfig = JSON.parse(
|
|
472
|
+
new TextDecoder().decode(configEntry!.data),
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
// Environment-specific fields should be stripped/reset
|
|
476
|
+
expect(parsedConfig.ingress.publicBaseUrl).toBe("");
|
|
477
|
+
expect(parsedConfig.ingress.enabled).toBeUndefined();
|
|
478
|
+
expect(parsedConfig.daemon).toBeUndefined();
|
|
479
|
+
expect(parsedConfig.skills.load.extraDirs).toEqual([]);
|
|
480
|
+
|
|
481
|
+
// Non-environment-specific fields should be preserved
|
|
482
|
+
expect(parsedConfig.provider).toBe("anthropic");
|
|
483
|
+
expect(parsedConfig.model).toBe("test-model");
|
|
484
|
+
expect(parsedConfig.ingress.port).toBe(8080);
|
|
485
|
+
expect(parsedConfig.skills.load.autoReload).toBe(true);
|
|
486
|
+
expect(parsedConfig.memory.enabled).toBe(true);
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
|
|
431
490
|
// ---------------------------------------------------------------------------
|
|
432
491
|
// Auth policy registration tests
|
|
433
492
|
// ---------------------------------------------------------------------------
|
|
@@ -285,6 +285,72 @@ describe("streamExportVBundle round-trip", () => {
|
|
|
285
285
|
});
|
|
286
286
|
});
|
|
287
287
|
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
// Streaming config sanitization test
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
|
|
292
|
+
describe("streamExportVBundle config sanitization", () => {
|
|
293
|
+
test("exported config.json has environment-specific fields stripped", async () => {
|
|
294
|
+
// Write a config with environment-specific fields that should be stripped
|
|
295
|
+
const configWithEnvFields = {
|
|
296
|
+
provider: "anthropic",
|
|
297
|
+
model: "test-model",
|
|
298
|
+
ingress: {
|
|
299
|
+
publicBaseUrl: "https://my-tunnel.example.com",
|
|
300
|
+
enabled: true,
|
|
301
|
+
port: 8080,
|
|
302
|
+
},
|
|
303
|
+
daemon: {
|
|
304
|
+
autoStart: true,
|
|
305
|
+
logLevel: "debug",
|
|
306
|
+
},
|
|
307
|
+
skills: {
|
|
308
|
+
load: {
|
|
309
|
+
extraDirs: ["/home/user/custom-skills", "/opt/skills"],
|
|
310
|
+
autoReload: true,
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
memory: { enabled: true },
|
|
314
|
+
};
|
|
315
|
+
writeFileSync(testConfigPath, JSON.stringify(configWithEnvFields, null, 2));
|
|
316
|
+
|
|
317
|
+
const result = await streamExportVBundle({
|
|
318
|
+
workspaceDir: testDir,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
const archiveData = new Uint8Array(readFileSync(result.tempPath));
|
|
323
|
+
const entries = parseTarEntries(archiveData);
|
|
324
|
+
|
|
325
|
+
const configEntry = entries.find(
|
|
326
|
+
(e) => e.name === "workspace/config.json",
|
|
327
|
+
);
|
|
328
|
+
expect(configEntry).toBeDefined();
|
|
329
|
+
|
|
330
|
+
const parsedConfig = JSON.parse(
|
|
331
|
+
new TextDecoder().decode(configEntry!.data),
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
// Environment-specific fields should be stripped/reset
|
|
335
|
+
expect(parsedConfig.ingress.publicBaseUrl).toBe("");
|
|
336
|
+
expect(parsedConfig.ingress.enabled).toBeUndefined();
|
|
337
|
+
expect(parsedConfig.daemon).toBeUndefined();
|
|
338
|
+
expect(parsedConfig.skills.load.extraDirs).toEqual([]);
|
|
339
|
+
|
|
340
|
+
// Non-environment-specific fields should be preserved
|
|
341
|
+
expect(parsedConfig.provider).toBe("anthropic");
|
|
342
|
+
expect(parsedConfig.model).toBe("test-model");
|
|
343
|
+
expect(parsedConfig.ingress.port).toBe(8080);
|
|
344
|
+
expect(parsedConfig.skills.load.autoReload).toBe(true);
|
|
345
|
+
expect(parsedConfig.memory.enabled).toBe(true);
|
|
346
|
+
} finally {
|
|
347
|
+
// Restore original config for subsequent tests
|
|
348
|
+
writeFileSync(testConfigPath, JSON.stringify(TEST_CONFIG, null, 2));
|
|
349
|
+
await result.cleanup();
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
|
|
288
354
|
// ---------------------------------------------------------------------------
|
|
289
355
|
// Cleanup idempotency test
|
|
290
356
|
// ---------------------------------------------------------------------------
|