@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
|
@@ -190,6 +190,28 @@ describe("isTextMimeType", () => {
|
|
|
190
190
|
// A binary plist has a specific MIME type — extension should not override it
|
|
191
191
|
expect(isTextMimeType("application/x-plist", "Info.plist")).toBe(false);
|
|
192
192
|
});
|
|
193
|
+
|
|
194
|
+
test("application/octet-stream with .jsonl filename is text", () => {
|
|
195
|
+
expect(isTextMimeType("application/octet-stream", "messages.jsonl")).toBe(
|
|
196
|
+
true,
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("application/octet-stream with .ndjson filename is text", () => {
|
|
201
|
+
expect(isTextMimeType("application/octet-stream", "events.ndjson")).toBe(
|
|
202
|
+
true,
|
|
203
|
+
);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("application/octet-stream with .JSONL uppercase is text", () => {
|
|
207
|
+
expect(isTextMimeType("application/octet-stream", "DATA.JSONL")).toBe(true);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("application/octet-stream with .NDJSON uppercase is text", () => {
|
|
211
|
+
expect(isTextMimeType("application/octet-stream", "DATA.NDJSON")).toBe(
|
|
212
|
+
true,
|
|
213
|
+
);
|
|
214
|
+
});
|
|
193
215
|
});
|
|
194
216
|
|
|
195
217
|
// ===========================================================================
|
|
@@ -120,7 +120,14 @@ function handleWorkspaceFile(ctx: RouteContext): Response {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
const mimeType = Bun.file(resolved).type;
|
|
123
|
-
|
|
123
|
+
// Empty files with unknown MIME type default to text — there is no binary
|
|
124
|
+
// content to protect, and files created via the UI "New File" action are
|
|
125
|
+
// always 0 bytes. Without this override, extensionless files (e.g. "Test")
|
|
126
|
+
// would be classified as binary and rendered in a non-editable fallback view.
|
|
127
|
+
const isText =
|
|
128
|
+
stat.size === 0 && mimeType === "application/octet-stream"
|
|
129
|
+
? true
|
|
130
|
+
: isTextMimeType(mimeType, basename(resolved));
|
|
124
131
|
const isBinary = !isText;
|
|
125
132
|
|
|
126
133
|
let content: string | undefined = undefined;
|
|
@@ -23,6 +23,7 @@ const log = getLogger("scheduler");
|
|
|
23
23
|
|
|
24
24
|
export interface ScheduleMessageOptions {
|
|
25
25
|
trustClass?: "guardian" | "trusted_contact" | "unknown";
|
|
26
|
+
taskRunId?: string;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export type ScheduleMessageProcessor = (
|
|
@@ -196,11 +197,12 @@ async function runScheduleOnce(
|
|
|
196
197
|
source: "schedule",
|
|
197
198
|
scheduleJobId: job.id,
|
|
198
199
|
},
|
|
199
|
-
|
|
200
|
-
conversationId
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
200
|
+
async (conversationId, message, taskRunId) => {
|
|
201
|
+
await processMessage(conversationId, message, {
|
|
202
|
+
trustClass: "guardian",
|
|
203
|
+
taskRunId,
|
|
204
|
+
});
|
|
205
|
+
},
|
|
204
206
|
);
|
|
205
207
|
|
|
206
208
|
onScheduleConversationCreated?.({
|
|
@@ -190,6 +190,26 @@ export class CesCredentialBackend implements CredentialBackend {
|
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
async bulkSet(
|
|
194
|
+
credentials: Array<{ account: string; value: string }>,
|
|
195
|
+
): Promise<Array<{ account: string; ok: boolean }>> {
|
|
196
|
+
try {
|
|
197
|
+
const res = await cesRequest("POST", "/v1/credentials/bulk", {
|
|
198
|
+
credentials,
|
|
199
|
+
});
|
|
200
|
+
if (!res?.ok) {
|
|
201
|
+
return credentials.map((c) => ({ account: c.account, ok: false }));
|
|
202
|
+
}
|
|
203
|
+
const data = (await res.json()) as {
|
|
204
|
+
results: Array<{ account: string; ok: boolean }>;
|
|
205
|
+
};
|
|
206
|
+
return data.results;
|
|
207
|
+
} catch (err) {
|
|
208
|
+
log.warn({ err }, "CES credential bulk set threw unexpectedly");
|
|
209
|
+
return credentials.map((c) => ({ account: c.account, ok: false }));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
193
213
|
async list(): Promise<CredentialListResult> {
|
|
194
214
|
try {
|
|
195
215
|
const res = await cesRequest("GET", "/v1/credentials");
|
|
@@ -86,4 +86,21 @@ export class CesRpcCredentialBackend implements CredentialBackend {
|
|
|
86
86
|
return { accounts: [], unreachable: true };
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
+
|
|
90
|
+
async bulkSet(
|
|
91
|
+
credentials: Array<{ account: string; value: string }>,
|
|
92
|
+
): Promise<Array<{ account: string; ok: boolean }>> {
|
|
93
|
+
if (!this.isAvailable()) {
|
|
94
|
+
return credentials.map((c) => ({ account: c.account, ok: false }));
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const result = await this.client.call(CesRpcMethod.BulkSetCredentials, {
|
|
98
|
+
credentials,
|
|
99
|
+
});
|
|
100
|
+
return result.results;
|
|
101
|
+
} catch (err) {
|
|
102
|
+
log.warn({ err }, "CES RPC bulk credential set failed");
|
|
103
|
+
return credentials.map((c) => ({ account: c.account, ok: false }));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
89
106
|
}
|
|
@@ -47,6 +47,11 @@ export interface CredentialBackend {
|
|
|
47
47
|
|
|
48
48
|
/** List all account names. */
|
|
49
49
|
list(): Promise<CredentialListResult>;
|
|
50
|
+
|
|
51
|
+
/** Bulk-set multiple credentials. Optional — backends without native bulk support omit this. */
|
|
52
|
+
bulkSet?(
|
|
53
|
+
credentials: Array<{ account: string; value: string }>,
|
|
54
|
+
): Promise<Array<{ account: string; ok: boolean }>>;
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
// ---------------------------------------------------------------------------
|
package/src/security/oauth2.ts
CHANGED
|
@@ -33,13 +33,13 @@ export type TokenEndpointAuthMethod =
|
|
|
33
33
|
| "client_secret_post";
|
|
34
34
|
|
|
35
35
|
export interface OAuth2Config {
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
authorizeUrl: string;
|
|
37
|
+
tokenExchangeUrl: string;
|
|
38
38
|
scopes: string[];
|
|
39
39
|
clientId: string;
|
|
40
40
|
/** Client secret for providers that require it (e.g. Slack). PKCE is always used regardless. */
|
|
41
41
|
clientSecret?: string;
|
|
42
|
-
|
|
42
|
+
authorizeParams?: Record<string, string>;
|
|
43
43
|
/** URL to fetch user identity info after OAuth. If omitted, account info is not fetched. */
|
|
44
44
|
userinfoUrl?: string;
|
|
45
45
|
/**
|
|
@@ -49,6 +49,13 @@ export interface OAuth2Config {
|
|
|
49
49
|
* Defaults to `client_secret_post`.
|
|
50
50
|
*/
|
|
51
51
|
tokenEndpointAuthMethod?: TokenEndpointAuthMethod;
|
|
52
|
+
/**
|
|
53
|
+
* Separator used to join scopes in the authorize URL and split the
|
|
54
|
+
* granted-scope string returned by the token endpoint. Defaults to
|
|
55
|
+
* `" "` (space) per the OAuth 2.0 spec, but providers like Linear
|
|
56
|
+
* use `","` (comma).
|
|
57
|
+
*/
|
|
58
|
+
scopeSeparator: string;
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
export interface OAuth2TokenResult {
|
|
@@ -130,7 +137,7 @@ async function exchangeCodeForTokens(
|
|
|
130
137
|
}
|
|
131
138
|
}
|
|
132
139
|
|
|
133
|
-
const tokenResp = await fetch(config.
|
|
140
|
+
const tokenResp = await fetch(config.tokenExchangeUrl, {
|
|
134
141
|
method: "POST",
|
|
135
142
|
headers,
|
|
136
143
|
body: new URLSearchParams(tokenBody),
|
|
@@ -187,9 +194,19 @@ async function exchangeCodeForTokens(
|
|
|
187
194
|
(tokenData.token_type as string | undefined),
|
|
188
195
|
};
|
|
189
196
|
|
|
197
|
+
// Defensive split: providers (e.g. GitHub, Slack) may return comma-separated
|
|
198
|
+
// scopes in token responses regardless of the scope_separator used to join
|
|
199
|
+
// outbound authorize URLs, so we tolerate both spaces and commas here. When
|
|
200
|
+
// a provider explicitly configures a non-default separator (e.g. Linear uses
|
|
201
|
+
// ","), we honor that to keep symmetric round-tripping of configured scopes.
|
|
202
|
+
const splitPattern =
|
|
203
|
+
config.scopeSeparator === " " ? /[ ,]/ : config.scopeSeparator;
|
|
190
204
|
const grantedScopes =
|
|
191
205
|
typeof tokens.scope === "string"
|
|
192
|
-
? tokens.scope
|
|
206
|
+
? tokens.scope
|
|
207
|
+
.split(splitPattern)
|
|
208
|
+
.map((s) => s.trim())
|
|
209
|
+
.filter(Boolean)
|
|
193
210
|
: [...config.scopes];
|
|
194
211
|
|
|
195
212
|
return { tokens, grantedScopes, rawTokenResponse: tokenData };
|
|
@@ -228,18 +245,18 @@ async function runGatewayFlow(
|
|
|
228
245
|
});
|
|
229
246
|
|
|
230
247
|
const authParams = new URLSearchParams({
|
|
231
|
-
...config.
|
|
248
|
+
...config.authorizeParams,
|
|
232
249
|
client_id: config.clientId,
|
|
233
250
|
redirect_uri: redirectUri,
|
|
234
251
|
response_type: "code",
|
|
235
|
-
scope: config.scopes.join(
|
|
252
|
+
scope: config.scopes.join(config.scopeSeparator),
|
|
236
253
|
state,
|
|
237
254
|
code_challenge: codeChallenge,
|
|
238
255
|
code_challenge_method: "S256",
|
|
239
256
|
});
|
|
240
257
|
|
|
241
|
-
const
|
|
242
|
-
callbacks.openUrl(
|
|
258
|
+
const authorizeUrl = `${config.authorizeUrl}?${authParams}`;
|
|
259
|
+
callbacks.openUrl(authorizeUrl);
|
|
243
260
|
|
|
244
261
|
const code = await codePromise;
|
|
245
262
|
|
|
@@ -401,22 +418,22 @@ function startLoopbackServerAndWaitForCode(
|
|
|
401
418
|
);
|
|
402
419
|
|
|
403
420
|
const authParams = new URLSearchParams({
|
|
404
|
-
...config.
|
|
421
|
+
...config.authorizeParams,
|
|
405
422
|
client_id: config.clientId,
|
|
406
423
|
redirect_uri: boundRedirectUri,
|
|
407
424
|
response_type: "code",
|
|
408
|
-
scope: config.scopes.join(
|
|
425
|
+
scope: config.scopes.join(config.scopeSeparator),
|
|
409
426
|
state,
|
|
410
427
|
code_challenge: codeChallenge,
|
|
411
428
|
code_challenge_method: "S256",
|
|
412
429
|
});
|
|
413
430
|
|
|
414
|
-
const
|
|
431
|
+
const authorizeUrl = `${config.authorizeUrl}?${authParams}`;
|
|
415
432
|
log.info(
|
|
416
|
-
{
|
|
433
|
+
{ authorizeUrlLength: authorizeUrl.length, state },
|
|
417
434
|
"oauth2 loopback: built auth URL, calling openUrl callback",
|
|
418
435
|
);
|
|
419
|
-
callbacks.openUrl(
|
|
436
|
+
callbacks.openUrl(authorizeUrl);
|
|
420
437
|
log.info("oauth2 loopback: openUrl callback returned");
|
|
421
438
|
});
|
|
422
439
|
|
|
@@ -439,7 +456,7 @@ function startLoopbackServerAndWaitForCode(
|
|
|
439
456
|
// ---------------------------------------------------------------------------
|
|
440
457
|
|
|
441
458
|
export interface OAuth2PreparedFlow {
|
|
442
|
-
|
|
459
|
+
authorizeUrl: string;
|
|
443
460
|
state: string;
|
|
444
461
|
/** Resolves when the user completes authorization and tokens are exchanged. */
|
|
445
462
|
completion: Promise<OAuth2FlowResult>;
|
|
@@ -493,17 +510,17 @@ export async function prepareOAuth2Flow(
|
|
|
493
510
|
});
|
|
494
511
|
|
|
495
512
|
const authParams = new URLSearchParams({
|
|
496
|
-
...config.
|
|
513
|
+
...config.authorizeParams,
|
|
497
514
|
client_id: config.clientId,
|
|
498
515
|
redirect_uri: redirectUri,
|
|
499
516
|
response_type: "code",
|
|
500
|
-
scope: config.scopes.join(
|
|
517
|
+
scope: config.scopes.join(config.scopeSeparator),
|
|
501
518
|
state,
|
|
502
519
|
code_challenge: codeChallenge,
|
|
503
520
|
code_challenge_method: "S256",
|
|
504
521
|
});
|
|
505
522
|
|
|
506
|
-
const
|
|
523
|
+
const authorizeUrl = `${config.authorizeUrl}?${authParams}`;
|
|
507
524
|
|
|
508
525
|
const completion = codePromise.then(async (code) => {
|
|
509
526
|
return await exchangeCodeForTokens(config, code, redirectUri, codeVerifier);
|
|
@@ -511,7 +528,7 @@ export async function prepareOAuth2Flow(
|
|
|
511
528
|
|
|
512
529
|
log.debug({ transport: "gateway", state }, "Prepared deferred OAuth2 flow");
|
|
513
530
|
|
|
514
|
-
return {
|
|
531
|
+
return { authorizeUrl, state, completion };
|
|
515
532
|
}
|
|
516
533
|
|
|
517
534
|
/**
|
|
@@ -533,17 +550,17 @@ async function prepareLoopbackFlow(
|
|
|
533
550
|
);
|
|
534
551
|
|
|
535
552
|
const authParams = new URLSearchParams({
|
|
536
|
-
...config.
|
|
553
|
+
...config.authorizeParams,
|
|
537
554
|
client_id: config.clientId,
|
|
538
555
|
redirect_uri: redirectUri,
|
|
539
556
|
response_type: "code",
|
|
540
|
-
scope: config.scopes.join(
|
|
557
|
+
scope: config.scopes.join(config.scopeSeparator),
|
|
541
558
|
state,
|
|
542
559
|
code_challenge: codeChallenge,
|
|
543
560
|
code_challenge_method: "S256",
|
|
544
561
|
});
|
|
545
562
|
|
|
546
|
-
const
|
|
563
|
+
const authorizeUrl = `${config.authorizeUrl}?${authParams}`;
|
|
547
564
|
|
|
548
565
|
const completion = codePromise.then(async (code) => {
|
|
549
566
|
return await exchangeCodeForTokens(config, code, redirectUri, codeVerifier);
|
|
@@ -554,7 +571,7 @@ async function prepareLoopbackFlow(
|
|
|
554
571
|
"Prepared deferred OAuth2 flow (loopback)",
|
|
555
572
|
);
|
|
556
573
|
|
|
557
|
-
return {
|
|
574
|
+
return { authorizeUrl, state, completion };
|
|
558
575
|
}
|
|
559
576
|
|
|
560
577
|
/**
|
|
@@ -763,7 +780,7 @@ export async function startOAuth2Flow(
|
|
|
763
780
|
* Supports both PKCE (no secret) and client_secret flows.
|
|
764
781
|
*/
|
|
765
782
|
export async function refreshOAuth2Token(
|
|
766
|
-
|
|
783
|
+
tokenExchangeUrl: string,
|
|
767
784
|
clientId: string,
|
|
768
785
|
refreshToken: string,
|
|
769
786
|
clientSecret?: string,
|
|
@@ -792,7 +809,7 @@ export async function refreshOAuth2Token(
|
|
|
792
809
|
}
|
|
793
810
|
}
|
|
794
811
|
|
|
795
|
-
const resp = await fetch(
|
|
812
|
+
const resp = await fetch(tokenExchangeUrl, {
|
|
796
813
|
method: "POST",
|
|
797
814
|
headers,
|
|
798
815
|
body: new URLSearchParams(body),
|
|
@@ -86,6 +86,15 @@ let _cesHttpUnreachable = false;
|
|
|
86
86
|
/** Minimum interval between CES reconnection attempts. */
|
|
87
87
|
const RECONNECT_COOLDOWN_MS = 3_000;
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Hard timeout for each public credential operation (resolve + backend call).
|
|
91
|
+
* Prevents indefinite blocking when CES reconnection or backend operations hang.
|
|
92
|
+
*
|
|
93
|
+
* Set to 45s to comfortably cover the CES HTTP set worst case (~34s:
|
|
94
|
+
* 3 fetch attempts × 10s REQUEST_TIMEOUT_MS + 2 × 2s SET_RETRY_DELAY_MS).
|
|
95
|
+
*/
|
|
96
|
+
const CREDENTIAL_OP_TIMEOUT_MS = 45_000;
|
|
97
|
+
|
|
89
98
|
/** Inject a CES RPC client for credential routing. Resets the resolved backend. */
|
|
90
99
|
export function setCesClient(client: CesClient | undefined): void {
|
|
91
100
|
_cesClient = client;
|
|
@@ -308,16 +317,58 @@ function updateCesHttpReachability(
|
|
|
308
317
|
}
|
|
309
318
|
}
|
|
310
319
|
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
// Timeout helper
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
|
|
324
|
+
const CREDENTIAL_TIMEOUT_MSG = "Credential operation timed out";
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Race a credential operation against a hard deadline. If the operation
|
|
328
|
+
* does not settle within `CREDENTIAL_OP_TIMEOUT_MS`, return the supplied
|
|
329
|
+
* fallback value so callers degrade gracefully instead of hanging.
|
|
330
|
+
*
|
|
331
|
+
* Non-timeout errors from `op()` are propagated to callers rather than
|
|
332
|
+
* silently swallowed — only genuine timeouts return the fallback.
|
|
333
|
+
*/
|
|
334
|
+
async function withCredentialTimeout<T>(
|
|
335
|
+
op: () => Promise<T>,
|
|
336
|
+
fallback: T,
|
|
337
|
+
): Promise<T> {
|
|
338
|
+
return new Promise<T>((resolve, reject) => {
|
|
339
|
+
const timer = setTimeout(() => {
|
|
340
|
+
log.warn(CREDENTIAL_TIMEOUT_MSG + " — returning fallback");
|
|
341
|
+
resolve(fallback);
|
|
342
|
+
}, CREDENTIAL_OP_TIMEOUT_MS);
|
|
343
|
+
|
|
344
|
+
op().then(
|
|
345
|
+
(val) => {
|
|
346
|
+
clearTimeout(timer);
|
|
347
|
+
resolve(val);
|
|
348
|
+
},
|
|
349
|
+
(err) => {
|
|
350
|
+
clearTimeout(timer);
|
|
351
|
+
reject(err);
|
|
352
|
+
},
|
|
353
|
+
);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
311
357
|
/**
|
|
312
358
|
* List all account names from the resolved backend (async).
|
|
313
359
|
*
|
|
314
360
|
* Queries exactly one backend — no cross-store merge.
|
|
315
361
|
*/
|
|
316
362
|
export async function listSecureKeysAsync(): Promise<CredentialListResult> {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
363
|
+
return withCredentialTimeout(
|
|
364
|
+
async () => {
|
|
365
|
+
const backend = await resolveBackendAsync();
|
|
366
|
+
const result = await backend.list();
|
|
367
|
+
updateCesHttpReachability(backend, result.unreachable);
|
|
368
|
+
return result;
|
|
369
|
+
},
|
|
370
|
+
{ accounts: [], unreachable: true },
|
|
371
|
+
);
|
|
321
372
|
}
|
|
322
373
|
|
|
323
374
|
// ---------------------------------------------------------------------------
|
|
@@ -336,13 +387,18 @@ export async function listSecureKeysAsync(): Promise<CredentialListResult> {
|
|
|
336
387
|
export async function getSecureKeyResultAsync(
|
|
337
388
|
account: string,
|
|
338
389
|
): Promise<SecureKeyResult> {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
390
|
+
return withCredentialTimeout(
|
|
391
|
+
async () => {
|
|
392
|
+
const backend = await resolveBackendAsync();
|
|
393
|
+
const result = await backend.get(account);
|
|
394
|
+
updateCesHttpReachability(backend, result.unreachable);
|
|
395
|
+
if (result.value != null) {
|
|
396
|
+
return { value: result.value, unreachable: false };
|
|
397
|
+
}
|
|
398
|
+
return { value: undefined, unreachable: result.unreachable };
|
|
399
|
+
},
|
|
400
|
+
{ value: undefined, unreachable: true },
|
|
401
|
+
);
|
|
346
402
|
}
|
|
347
403
|
|
|
348
404
|
/**
|
|
@@ -364,16 +420,18 @@ export async function setSecureKeyAsync(
|
|
|
364
420
|
account: string,
|
|
365
421
|
value: string,
|
|
366
422
|
): Promise<boolean> {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
423
|
+
return withCredentialTimeout(async () => {
|
|
424
|
+
const backend = await resolveBackendAsync();
|
|
425
|
+
const ok = await backend.set(account, value);
|
|
426
|
+
if (!ok) {
|
|
427
|
+
log.warn(
|
|
428
|
+
{ account, backend: backend.name },
|
|
429
|
+
"Credential backend set failed",
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
updateCesHttpReachability(backend, !ok);
|
|
433
|
+
return ok;
|
|
434
|
+
}, false);
|
|
377
435
|
}
|
|
378
436
|
|
|
379
437
|
/**
|
|
@@ -384,10 +442,45 @@ export async function setSecureKeyAsync(
|
|
|
384
442
|
export async function deleteSecureKeyAsync(
|
|
385
443
|
account: string,
|
|
386
444
|
): Promise<DeleteResult> {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
445
|
+
return withCredentialTimeout(async () => {
|
|
446
|
+
const backend = await resolveBackendAsync();
|
|
447
|
+
const result = await backend.delete(account);
|
|
448
|
+
updateCesHttpReachability(backend, result === "error");
|
|
449
|
+
return result;
|
|
450
|
+
}, "error");
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Bulk-set multiple credentials in a single operation.
|
|
455
|
+
*
|
|
456
|
+
* Uses the backend's native `bulkSet` when available (CES RPC / HTTP),
|
|
457
|
+
* otherwise falls back to individual `set` calls.
|
|
458
|
+
*/
|
|
459
|
+
export async function bulkSetSecureKeysAsync(
|
|
460
|
+
credentials: Array<{ account: string; value: string }>,
|
|
461
|
+
): Promise<Array<{ account: string; ok: boolean }>> {
|
|
462
|
+
return withCredentialTimeout(
|
|
463
|
+
async () => {
|
|
464
|
+
const backend = await resolveBackendAsync();
|
|
465
|
+
if (backend.bulkSet) {
|
|
466
|
+
const results = await backend.bulkSet(credentials);
|
|
467
|
+
const anyFailed = results.some((r) => !r.ok);
|
|
468
|
+
updateCesHttpReachability(backend, anyFailed);
|
|
469
|
+
return results;
|
|
470
|
+
}
|
|
471
|
+
// Fallback: loop individual sets
|
|
472
|
+
const results = [];
|
|
473
|
+
let anyFailed = false;
|
|
474
|
+
for (const { account, value } of credentials) {
|
|
475
|
+
const ok = await backend.set(account, value);
|
|
476
|
+
if (!ok) anyFailed = true;
|
|
477
|
+
results.push({ account, ok });
|
|
478
|
+
}
|
|
479
|
+
updateCesHttpReachability(backend, anyFailed);
|
|
480
|
+
return results;
|
|
481
|
+
},
|
|
482
|
+
credentials.map((c) => ({ account: c.account, ok: false })),
|
|
483
|
+
);
|
|
391
484
|
}
|
|
392
485
|
|
|
393
486
|
// ---------------------------------------------------------------------------
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Token manager for OAuth2 credentials.
|
|
3
3
|
*
|
|
4
|
-
* Reads refresh configuration (
|
|
4
|
+
* Reads refresh configuration (refreshUrl with fallback to tokenExchangeUrl, clientId, authMethod) exclusively
|
|
5
5
|
* from the SQLite oauth-store (provider + app + connection rows). After a
|
|
6
6
|
* successful refresh, writes tokens to new-format secure key paths and
|
|
7
7
|
* updates the oauth_connection row.
|
|
@@ -90,7 +90,12 @@ const secureKeyBackend: SecureKeyBackend = {
|
|
|
90
90
|
|
|
91
91
|
/** Shared shape for resolved refresh configuration. */
|
|
92
92
|
interface RefreshConfig {
|
|
93
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Token endpoint used for the refresh grant. Resolved from
|
|
95
|
+
* `provider.refreshUrl` when set to a non-empty string, otherwise
|
|
96
|
+
* `provider.tokenExchangeUrl` (matching platform's Python `or` semantics).
|
|
97
|
+
*/
|
|
98
|
+
tokenExchangeUrl: string;
|
|
94
99
|
clientId: string;
|
|
95
100
|
/** OAuth client secret (optional — PKCE flows may omit it). */
|
|
96
101
|
secret?: string;
|
|
@@ -102,8 +107,9 @@ interface RefreshConfig {
|
|
|
102
107
|
/**
|
|
103
108
|
* Resolve refresh configuration from the SQLite oauth-store.
|
|
104
109
|
*
|
|
105
|
-
* Looks up connection -> app -> provider to read
|
|
106
|
-
*
|
|
110
|
+
* Looks up connection -> app -> provider to read the refresh endpoint (preferring
|
|
111
|
+
* `provider.refreshUrl`, falling back to `provider.tokenExchangeUrl`), clientId,
|
|
112
|
+
* and authMethod. Throws `TokenExpiredError` if the connection is not found
|
|
107
113
|
* or incomplete.
|
|
108
114
|
*/
|
|
109
115
|
async function resolveRefreshConfig(
|
|
@@ -126,7 +132,7 @@ async function resolveRefreshConfig(
|
|
|
126
132
|
);
|
|
127
133
|
}
|
|
128
134
|
|
|
129
|
-
const provider = getProvider(conn.
|
|
135
|
+
const provider = getProvider(conn.provider);
|
|
130
136
|
if (!provider) {
|
|
131
137
|
throw new TokenExpiredError(
|
|
132
138
|
service,
|
|
@@ -134,9 +140,16 @@ async function resolveRefreshConfig(
|
|
|
134
140
|
);
|
|
135
141
|
}
|
|
136
142
|
|
|
137
|
-
|
|
143
|
+
// Prefer provider.refreshUrl when set; fall back to tokenExchangeUrl.
|
|
144
|
+
// This mirrors platform's `oauth_app.refresh_url or oauth_app.token_exchange_url`
|
|
145
|
+
// in `token_service.py:112`, so both repos resolve the refresh endpoint
|
|
146
|
+
// identically for managed and BYO flows. We use `||` (not `??`) so empty
|
|
147
|
+
// strings fall back to tokenExchangeUrl — matching Python's `or` semantics
|
|
148
|
+
// and preventing a malformed provider row with `refreshUrl: ""` from
|
|
149
|
+
// resolving to an empty endpoint.
|
|
150
|
+
const tokenExchangeUrl = provider.refreshUrl || provider.tokenExchangeUrl;
|
|
138
151
|
const resolvedClientId = app.clientId;
|
|
139
|
-
if (!
|
|
152
|
+
if (!tokenExchangeUrl || !resolvedClientId) {
|
|
140
153
|
throw new TokenExpiredError(
|
|
141
154
|
service,
|
|
142
155
|
`Missing OAuth2 refresh config for "${service}".${recoveryHint(service)}`,
|
|
@@ -155,7 +168,7 @@ async function resolveRefreshConfig(
|
|
|
155
168
|
|
|
156
169
|
return {
|
|
157
170
|
connId: conn.id,
|
|
158
|
-
|
|
171
|
+
tokenExchangeUrl,
|
|
159
172
|
clientId: resolvedClientId,
|
|
160
173
|
secret,
|
|
161
174
|
refreshToken,
|
|
@@ -175,7 +188,7 @@ async function resolveRefreshConfig(
|
|
|
175
188
|
async function doRefresh(service: string, connId: string): Promise<string> {
|
|
176
189
|
const refreshConfig = await resolveRefreshConfig(service, connId);
|
|
177
190
|
const {
|
|
178
|
-
|
|
191
|
+
tokenExchangeUrl,
|
|
179
192
|
clientId: resolvedClientId,
|
|
180
193
|
secret,
|
|
181
194
|
authMethod,
|
|
@@ -204,7 +217,7 @@ async function doRefresh(service: string, connId: string): Promise<string> {
|
|
|
204
217
|
let result;
|
|
205
218
|
try {
|
|
206
219
|
result = await refreshOAuth2Token(
|
|
207
|
-
|
|
220
|
+
tokenExchangeUrl,
|
|
208
221
|
resolvedClientId,
|
|
209
222
|
refreshToken,
|
|
210
223
|
secret,
|