@vellumai/assistant 0.4.46 → 0.4.49
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/ARCHITECTURE.md +7 -7
- package/README.md +2 -23
- package/docs/architecture/integrations.md +45 -41
- package/docs/architecture/keychain-broker.md +3 -3
- package/docs/architecture/security.md +5 -5
- package/docs/runbook-trusted-contacts.md +3 -8
- package/hook-templates/debug-prompt-logger/hook.json +1 -1
- package/hook-templates/debug-prompt-logger/run.sh +1 -3
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +0 -1
- package/src/__tests__/anthropic-provider.test.ts +156 -0
- package/src/__tests__/approval-cascade.test.ts +810 -0
- package/src/__tests__/approval-primitive.test.ts +0 -1
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-attachments.test.ts +12 -34
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
- package/src/__tests__/browser-fill-credential.test.ts +5 -2
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +2 -1
- package/src/__tests__/channel-guardian.test.ts +0 -2
- package/src/__tests__/channel-readiness-routes.test.ts +35 -25
- package/src/__tests__/channel-readiness-service.test.ts +10 -9
- package/src/__tests__/checker.test.ts +9 -29
- package/src/__tests__/cli.test.ts +23 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-tools.test.ts +2 -19
- package/src/__tests__/config-watcher.test.ts +0 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/context-image-dimensions.test.ts +332 -0
- package/src/__tests__/context-token-estimator.test.ts +196 -13
- package/src/__tests__/conversation-attention-store.test.ts +0 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-broker-browser-fill.test.ts +23 -22
- package/src/__tests__/credential-broker-server-use.test.ts +22 -21
- package/src/__tests__/credential-broker.test.ts +2 -1
- package/src/__tests__/credential-metadata-store.test.ts +239 -26
- package/src/__tests__/credential-resolve.test.ts +5 -4
- package/src/__tests__/credential-security-e2e.test.ts +8 -8
- package/src/__tests__/credential-security-invariants.test.ts +111 -7
- package/src/__tests__/credential-vault-unit.test.ts +287 -54
- package/src/__tests__/credential-vault.test.ts +406 -12
- package/src/__tests__/credentials-cli.test.ts +82 -6
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/ephemeral-permissions.test.ts +3 -3
- package/src/__tests__/gateway-only-enforcement.test.ts +4 -2
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/gemini-image-service.test.ts +75 -45
- package/src/__tests__/gemini-provider.test.ts +9 -6
- package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -33
- package/src/__tests__/guardian-action-copy-generator.test.ts +0 -20
- package/src/__tests__/guardian-action-followup-executor.test.ts +1 -28
- package/src/__tests__/guardian-action-followup-store.test.ts +1 -1
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
- package/src/__tests__/guardian-grant-minting.test.ts +35 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
- package/src/__tests__/heartbeat-service.test.ts +0 -1
- package/src/__tests__/host-cu-proxy.test.ts +629 -0
- package/src/__tests__/host-shell-tool.test.ts +27 -15
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/ingress-url-consistency.test.ts +14 -21
- package/src/__tests__/integration-status.test.ts +38 -25
- package/src/__tests__/intent-routing.test.ts +0 -1
- package/src/__tests__/invite-routes-http.test.ts +10 -9
- package/src/__tests__/keychain-broker-client.test.ts +11 -43
- package/src/__tests__/managed-proxy-context.test.ts +5 -3
- package/src/__tests__/media-generate-image.test.ts +63 -2
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -3
- package/src/__tests__/messaging-send-tool.test.ts +4 -6
- package/src/__tests__/notification-routing-intent.test.ts +0 -1
- package/src/__tests__/oauth-cli.test.ts +373 -14
- package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/oauth-store.test.ts +756 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
- package/src/__tests__/provider-error-scenarios.test.ts +0 -1
- package/src/__tests__/provider-fail-open-selection.test.ts +3 -1
- package/src/__tests__/provider-managed-proxy-integration.test.ts +70 -6
- package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
- package/src/__tests__/public-ingress-urls.test.ts +15 -21
- package/src/__tests__/recording-handler.test.ts +3 -4
- package/src/__tests__/registry.test.ts +2 -2
- package/src/__tests__/runtime-events-sse.test.ts +55 -7
- package/src/__tests__/schedule-store.test.ts +0 -1
- package/src/__tests__/scheduler-recurrence.test.ts +0 -1
- package/src/__tests__/schema-transforms.test.ts +226 -0
- package/src/__tests__/scoped-approval-grants.test.ts +0 -1
- package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
- package/src/__tests__/script-proxy-injection-runtime.test.ts +23 -13
- package/src/__tests__/script-proxy-policy-runtime.test.ts +1 -1
- package/src/__tests__/script-proxy-session-manager.test.ts +1 -1
- package/src/__tests__/secret-ingress-handler.test.ts +0 -1
- package/src/__tests__/secret-onetime-send.test.ts +5 -3
- package/src/__tests__/send-endpoint-busy.test.ts +21 -6
- package/src/__tests__/sequence-store.test.ts +0 -1
- package/src/__tests__/session-init.benchmark.test.ts +4 -5
- package/src/__tests__/session-messaging-secret-redirect.test.ts +5 -4
- package/src/__tests__/skill-include-graph.test.ts +66 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
- package/src/__tests__/skill-load-tool.test.ts +149 -1
- package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
- package/src/__tests__/skills-uninstall.test.ts +3 -3
- package/src/__tests__/skills.test.ts +3 -12
- package/src/__tests__/slack-channel-config.test.ts +76 -11
- package/src/__tests__/slack-share-routes.test.ts +17 -14
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/telegram-bot-username-resolution.test.ts +3 -0
- package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
- package/src/__tests__/terminal-tools.test.ts +4 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/tool-approval-handler.test.ts +0 -1
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
- package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
- package/src/__tests__/trust-store.test.ts +1 -22
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
- package/src/__tests__/twilio-config.test.ts +2 -1
- package/src/__tests__/twilio-provider.test.ts +4 -2
- package/src/__tests__/twilio-routes.test.ts +5 -20
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/agent/ax-tree-compaction.test.ts +235 -0
- package/src/agent/loop.ts +76 -130
- package/src/calls/call-domain.ts +8 -10
- package/src/calls/relay-server.ts +9 -13
- package/src/calls/twilio-config.ts +4 -8
- package/src/calls/twilio-provider.ts +2 -1
- package/src/calls/twilio-rest.ts +2 -1
- package/src/calls/twilio-routes.ts +1 -2
- package/src/calls/voice-ingress-preflight.ts +1 -1
- package/src/cli/commands/browser-relay.ts +46 -15
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/credentials.ts +110 -23
- package/src/cli/commands/oauth/apps.ts +255 -0
- package/src/cli/commands/oauth/connections.ts +299 -0
- package/src/cli/commands/oauth/index.ts +52 -0
- package/src/cli/commands/oauth/providers.ts +242 -0
- package/src/cli/commands/skills.ts +4 -338
- package/src/cli/program.ts +1 -5
- package/src/cli/reference.ts +1 -3
- package/src/cli.ts +3 -2
- package/src/config/assistant-feature-flags.ts +0 -3
- package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
- package/src/config/bundled-skills/claude-code/TOOLS.json +0 -4
- package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
- package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +29 -32
- package/src/config/bundled-skills/gmail/SKILL.md +4 -4
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +54 -61
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +25 -28
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +14 -17
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +39 -44
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +61 -58
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +50 -49
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +11 -13
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +148 -146
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +4 -7
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +175 -173
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +4 -7
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +71 -76
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +32 -38
- package/src/config/bundled-skills/google-calendar/SKILL.md +2 -2
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +90 -44
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +9 -10
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +5 -6
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +4 -5
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +14 -15
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +37 -37
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +4 -9
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +24 -3
- package/src/config/bundled-skills/messaging/SKILL.md +6 -6
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +62 -63
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +15 -16
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +4 -5
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +6 -7
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +4 -5
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +14 -15
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +4 -5
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +128 -128
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +33 -34
- package/src/config/bundled-skills/messaging/tools/shared.ts +12 -15
- package/src/config/bundled-skills/settings/SKILL.md +1 -1
- package/src/config/bundled-skills/settings/TOOLS.json +2 -8
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
- package/src/config/bundled-skills/slack/tools/shared.ts +4 -10
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +15 -16
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +95 -92
- package/src/config/env-registry.ts +14 -83
- package/src/config/env.ts +11 -50
- package/src/config/feature-flag-registry.json +16 -16
- package/src/config/schema.ts +3 -1
- package/src/config/skills.ts +21 -2
- package/src/context/image-dimensions.ts +229 -0
- package/src/context/token-estimator.ts +75 -12
- package/src/context/window-manager.ts +49 -10
- package/src/daemon/assistant-attachments.ts +1 -13
- package/src/daemon/guardian-action-generators.ts +4 -5
- package/src/daemon/handlers/config-ingress.ts +8 -33
- package/src/daemon/handlers/config-slack-channel.ts +76 -56
- package/src/daemon/handlers/config-telegram.ts +53 -24
- package/src/daemon/handlers/sessions.ts +10 -24
- package/src/daemon/handlers/shared.ts +0 -130
- package/src/daemon/host-cu-proxy.ts +401 -0
- package/src/daemon/lifecycle.ts +39 -63
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/computer-use.ts +2 -119
- package/src/daemon/message-types/host-cu.ts +19 -0
- package/src/daemon/message-types/integrations.ts +1 -0
- package/src/daemon/message-types/messages.ts +3 -0
- package/src/daemon/server.ts +14 -21
- package/src/daemon/session-agent-loop-handlers.ts +2 -0
- package/src/daemon/session-attachments.ts +1 -2
- package/src/daemon/session-messaging.ts +3 -1
- package/src/daemon/session-slash.ts +1 -1
- package/src/daemon/session-surfaces.ts +40 -28
- package/src/daemon/session-tool-setup.ts +20 -11
- package/src/daemon/session.ts +139 -16
- package/src/daemon/tool-side-effects.ts +2 -8
- package/src/daemon/watch-handler.ts +2 -2
- package/src/email/providers/index.ts +2 -1
- package/src/events/tool-metrics-listener.ts +2 -2
- package/src/hooks/manager.ts +1 -4
- package/src/inbound/public-ingress-urls.ts +7 -7
- package/src/instrument.ts +15 -1
- package/src/logfire.ts +16 -5
- package/src/media/app-icon-generator.ts +30 -4
- package/src/media/avatar-router.ts +26 -3
- package/src/media/gemini-image-service.ts +28 -2
- package/src/memory/conversation-key-store.ts +21 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/guardian-action-store.ts +1 -1
- package/src/memory/migrations/149-oauth-tables.ts +60 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema/guardian.ts +1 -1
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/oauth.ts +65 -0
- package/src/messaging/provider.ts +19 -13
- package/src/messaging/providers/gmail/adapter.ts +40 -23
- package/src/messaging/providers/gmail/client.ts +283 -122
- package/src/messaging/providers/gmail/people-client.ts +32 -24
- package/src/messaging/providers/slack/adapter.ts +29 -19
- package/src/messaging/providers/slack/client.ts +265 -78
- package/src/messaging/providers/telegram-bot/adapter.ts +19 -18
- package/src/messaging/providers/whatsapp/adapter.ts +17 -11
- package/src/messaging/registry.ts +2 -31
- package/src/notifications/copy-composer.ts +0 -5
- package/src/notifications/signal.ts +4 -5
- package/src/oauth/byo-connection.test.ts +537 -0
- package/src/oauth/byo-connection.ts +128 -0
- package/src/oauth/connect-orchestrator.ts +139 -56
- package/src/oauth/connect-types.ts +17 -23
- package/src/oauth/connection-resolver.ts +58 -0
- package/src/oauth/connection.ts +38 -0
- package/src/oauth/manual-token-connection.ts +104 -0
- package/src/oauth/oauth-store.ts +496 -0
- package/src/oauth/platform-connection.test.ts +192 -0
- package/src/oauth/platform-connection.ts +111 -0
- package/src/oauth/provider-behaviors.ts +124 -0
- package/src/oauth/scope-policy.ts +9 -2
- package/src/oauth/seed-providers.ts +161 -0
- package/src/oauth/token-persistence.ts +74 -78
- package/src/permissions/checker.ts +8 -4
- package/src/permissions/defaults.ts +0 -1
- package/src/permissions/prompter.ts +10 -1
- package/src/permissions/trust-store.ts +13 -0
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
- package/src/prompts/system-prompt.ts +70 -45
- package/src/providers/anthropic/client.ts +133 -24
- package/src/providers/gemini/client.ts +15 -6
- package/src/providers/managed-proxy/constants.ts +2 -2
- package/src/providers/managed-proxy/context.ts +5 -1
- package/src/providers/ratelimit.ts +17 -0
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +1 -27
- package/src/runtime/AGENTS.md +17 -0
- package/src/runtime/auth/route-policy.ts +0 -3
- package/src/runtime/channel-invite-transports/telegram.ts +2 -1
- package/src/runtime/channel-readiness-service.ts +168 -195
- package/src/runtime/channel-readiness-types.ts +4 -0
- package/src/runtime/channel-reply-delivery.ts +0 -40
- package/src/runtime/gateway-client.ts +0 -7
- package/src/runtime/guardian-action-conversation-turn.ts +1 -3
- package/src/runtime/guardian-action-followup-executor.ts +1 -1
- package/src/runtime/guardian-action-message-composer.ts +3 -23
- package/src/runtime/http-server.ts +17 -10
- package/src/runtime/http-types.ts +2 -3
- package/src/runtime/middleware/rate-limiter.ts +74 -20
- package/src/runtime/middleware/twilio-validation.ts +1 -11
- package/src/runtime/pending-interactions.ts +14 -12
- package/src/runtime/routes/channel-delivery-routes.ts +0 -1
- package/src/runtime/routes/channel-readiness-routes.ts +2 -0
- package/src/runtime/routes/conversation-routes.ts +73 -19
- package/src/runtime/routes/diagnostics-routes.ts +11 -9
- package/src/runtime/routes/events-routes.ts +21 -11
- package/src/runtime/routes/guardian-approval-interception.ts +20 -5
- package/src/runtime/routes/host-cu-routes.ts +97 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
- package/src/runtime/routes/integrations/slack/share.ts +6 -6
- package/src/runtime/routes/integrations/twilio.ts +6 -5
- package/src/runtime/routes/log-export-routes.ts +126 -8
- package/src/runtime/routes/secret-routes.ts +3 -2
- package/src/runtime/routes/settings-routes.ts +113 -48
- package/src/runtime/routes/surface-action-routes.ts +1 -1
- package/src/runtime/routes/watch-routes.ts +128 -0
- package/src/schedule/integration-status.ts +10 -8
- package/src/security/credential-key.ts +14 -0
- package/src/security/keychain-broker-client.ts +5 -6
- package/src/security/oauth2.ts +1 -1
- package/src/security/token-manager.ts +145 -43
- package/src/skills/catalog-install.ts +358 -0
- package/src/skills/include-graph.ts +32 -0
- package/src/telegram/bot-username.ts +2 -3
- package/src/tools/apps/definitions.ts +0 -5
- package/src/tools/assets/materialize.ts +0 -5
- package/src/tools/assets/search.ts +0 -5
- package/src/tools/browser/headless-browser.ts +1 -67
- package/src/tools/browser/network-recorder.ts +1 -1
- package/src/tools/browser/network-recording-types.ts +1 -1
- package/src/tools/claude-code/claude-code.ts +0 -5
- package/src/tools/computer-use/definitions.ts +46 -11
- package/src/tools/computer-use/registry.ts +4 -5
- package/src/tools/credentials/broker.ts +5 -4
- package/src/tools/credentials/metadata-store.ts +22 -74
- package/src/tools/credentials/resolve.ts +2 -1
- package/src/tools/credentials/vault.ts +139 -151
- package/src/tools/filesystem/edit.ts +1 -6
- package/src/tools/filesystem/read.ts +0 -5
- package/src/tools/filesystem/write.ts +1 -6
- package/src/tools/host-filesystem/edit.ts +1 -6
- package/src/tools/host-filesystem/read.ts +1 -6
- package/src/tools/host-filesystem/write.ts +1 -6
- package/src/tools/mcp/mcp-tool-factory.ts +18 -1
- package/src/tools/memory/definitions.ts +0 -5
- package/src/tools/network/web-fetch.ts +0 -5
- package/src/tools/network/web-search.ts +0 -5
- package/src/tools/registry.ts +2 -7
- package/src/tools/schema-transforms.ts +99 -0
- package/src/tools/skills/load.ts +62 -8
- package/src/tools/swarm/delegate.ts +0 -5
- package/src/tools/system/avatar-generator.ts +0 -5
- package/src/tools/ui-surface/definitions.ts +0 -15
- package/src/tools/watch/screen-watch.ts +0 -5
- package/src/tools/watch/watch-state.ts +0 -12
- package/src/util/logger.ts +7 -41
- package/src/util/platform.ts +9 -28
- package/src/version.ts +10 -0
- package/src/watcher/providers/github.ts +51 -52
- package/src/watcher/providers/gmail.ts +88 -80
- package/src/watcher/providers/google-calendar.ts +94 -86
- package/src/watcher/providers/linear.ts +87 -93
- package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
- package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
- package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
- package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
- package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
- package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
- package/src/cli/commands/dev.ts +0 -129
- package/src/cli/commands/map.ts +0 -391
- package/src/cli/commands/oauth.ts +0 -77
- package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
- package/src/daemon/computer-use-session.ts +0 -1020
- package/src/daemon/ride-shotgun-handler.ts +0 -567
- package/src/oauth/provider-profiles.ts +0 -192
- package/src/prompts/computer-use-prompt.ts +0 -98
- package/src/runtime/routes/computer-use-routes.ts +0 -641
- package/src/runtime/telegram-streaming-delivery.test.ts +0 -597
- package/src/runtime/telegram-streaming-delivery.ts +0 -383
- package/src/tools/computer-use/request-computer-control.ts +0 -61
|
@@ -294,6 +294,38 @@ describe("credential metadata store", () => {
|
|
|
294
294
|
});
|
|
295
295
|
});
|
|
296
296
|
|
|
297
|
+
// ── v5 Schema: OAuth fields removed ─────────────────────────────────
|
|
298
|
+
|
|
299
|
+
describe("v5 schema — OAuth fields removed from CredentialMetadata", () => {
|
|
300
|
+
test("CredentialMetadata does not include hasRefreshToken", () => {
|
|
301
|
+
const record = upsertCredentialMetadata("github", "access_token", {
|
|
302
|
+
allowedTools: ["api_request"],
|
|
303
|
+
});
|
|
304
|
+
// hasRefreshToken was removed in v5 — field should not exist
|
|
305
|
+
expect("hasRefreshToken" in record).toBe(false);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test("CredentialMetadata does not include oauth2TokenUrl", () => {
|
|
309
|
+
const record = upsertCredentialMetadata("github", "access_token");
|
|
310
|
+
expect("oauth2TokenUrl" in record).toBe(false);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test("CredentialMetadata does not include oauth2ClientId", () => {
|
|
314
|
+
const record = upsertCredentialMetadata("github", "access_token");
|
|
315
|
+
expect("oauth2ClientId" in record).toBe(false);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test("CredentialMetadata does not include expiresAt", () => {
|
|
319
|
+
const record = upsertCredentialMetadata("github", "access_token");
|
|
320
|
+
expect("expiresAt" in record).toBe(false);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test("CredentialMetadata does not include grantedScopes", () => {
|
|
324
|
+
const record = upsertCredentialMetadata("github", "access_token");
|
|
325
|
+
expect("grantedScopes" in record).toBe(false);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
297
329
|
// ── Version migration ──────────────────────────────────────────────
|
|
298
330
|
|
|
299
331
|
describe("version migration", () => {
|
|
@@ -371,7 +403,7 @@ describe("credential metadata store", () => {
|
|
|
371
403
|
expect(record!.credentialId).toBe("cred-stable-id");
|
|
372
404
|
});
|
|
373
405
|
|
|
374
|
-
test("v2 file is
|
|
406
|
+
test("v2 file is migrated to v5 (strips oauth2ClientSecret and OAuth fields)", () => {
|
|
375
407
|
const v2Data = {
|
|
376
408
|
version: 2,
|
|
377
409
|
credentials: [
|
|
@@ -382,6 +414,7 @@ describe("credential metadata store", () => {
|
|
|
382
414
|
allowedTools: ["api_request"],
|
|
383
415
|
allowedDomains: ["fal.ai"],
|
|
384
416
|
alias: "fal-primary",
|
|
417
|
+
oauth2ClientSecret: "should-be-stripped",
|
|
385
418
|
injectionTemplates: [
|
|
386
419
|
{
|
|
387
420
|
hostPattern: "*.fal.ai",
|
|
@@ -402,9 +435,191 @@ describe("credential metadata store", () => {
|
|
|
402
435
|
expect(record!.alias).toBe("fal-primary");
|
|
403
436
|
expect(record!.injectionTemplates).toHaveLength(1);
|
|
404
437
|
expect(record!.injectionTemplates![0].hostPattern).toBe("*.fal.ai");
|
|
438
|
+
// oauth2ClientSecret must be stripped by migration
|
|
439
|
+
expect("oauth2ClientSecret" in record!).toBe(false);
|
|
440
|
+
|
|
441
|
+
// On-disk file should be upgraded to v5
|
|
442
|
+
const raw = JSON.parse(readFileSync(META_PATH, "utf-8"));
|
|
443
|
+
expect(raw.version).toBe(5);
|
|
444
|
+
expect(raw.credentials[0]).not.toHaveProperty("oauth2ClientSecret");
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test("v3 file is migrated to v5 (removes ghost refresh_token records)", () => {
|
|
448
|
+
const v3Data = {
|
|
449
|
+
version: 3,
|
|
450
|
+
credentials: [
|
|
451
|
+
{
|
|
452
|
+
credentialId: "cred-v3-at",
|
|
453
|
+
service: "github",
|
|
454
|
+
field: "access_token",
|
|
455
|
+
allowedTools: ["api_request"],
|
|
456
|
+
allowedDomains: ["github.com"],
|
|
457
|
+
createdAt: 1700000000000,
|
|
458
|
+
updatedAt: 1700000000000,
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
credentialId: "cred-v3-rt",
|
|
462
|
+
service: "github",
|
|
463
|
+
field: "refresh_token",
|
|
464
|
+
allowedTools: [],
|
|
465
|
+
allowedDomains: [],
|
|
466
|
+
createdAt: 1700000000000,
|
|
467
|
+
updatedAt: 1700000000000,
|
|
468
|
+
},
|
|
469
|
+
],
|
|
470
|
+
};
|
|
471
|
+
writeFileSync(META_PATH, JSON.stringify(v3Data, null, 2), "utf-8");
|
|
472
|
+
|
|
473
|
+
const records = listCredentialMetadata();
|
|
474
|
+
// Ghost refresh_token record removed
|
|
475
|
+
expect(records).toHaveLength(1);
|
|
476
|
+
expect(records[0].field).toBe("access_token");
|
|
477
|
+
// hasRefreshToken is stripped in v5 migration (OAuth fields moved to SQLite)
|
|
478
|
+
expect("hasRefreshToken" in records[0]).toBe(false);
|
|
479
|
+
|
|
480
|
+
// On-disk file should be upgraded to v5
|
|
481
|
+
const raw = JSON.parse(readFileSync(META_PATH, "utf-8"));
|
|
482
|
+
expect(raw.version).toBe(5);
|
|
483
|
+
expect(raw.credentials).toHaveLength(1);
|
|
484
|
+
expect(raw.credentials[0].field).toBe("access_token");
|
|
405
485
|
});
|
|
406
486
|
|
|
407
|
-
test("
|
|
487
|
+
test("v3 file without refresh_token records migrates cleanly", () => {
|
|
488
|
+
const v3Data = {
|
|
489
|
+
version: 3,
|
|
490
|
+
credentials: [
|
|
491
|
+
{
|
|
492
|
+
credentialId: "cred-v3-001",
|
|
493
|
+
service: "fal-ai",
|
|
494
|
+
field: "api_key",
|
|
495
|
+
allowedTools: ["api_request"],
|
|
496
|
+
allowedDomains: ["fal.ai"],
|
|
497
|
+
alias: "fal-primary",
|
|
498
|
+
injectionTemplates: [
|
|
499
|
+
{
|
|
500
|
+
hostPattern: "*.fal.ai",
|
|
501
|
+
injectionType: "header",
|
|
502
|
+
headerName: "Authorization",
|
|
503
|
+
valuePrefix: "Key ",
|
|
504
|
+
},
|
|
505
|
+
],
|
|
506
|
+
createdAt: 1700000000000,
|
|
507
|
+
updatedAt: 1700000000000,
|
|
508
|
+
},
|
|
509
|
+
],
|
|
510
|
+
};
|
|
511
|
+
writeFileSync(META_PATH, JSON.stringify(v3Data, null, 2), "utf-8");
|
|
512
|
+
|
|
513
|
+
const record = getCredentialMetadata("fal-ai", "api_key");
|
|
514
|
+
expect(record).toBeDefined();
|
|
515
|
+
expect(record!.alias).toBe("fal-primary");
|
|
516
|
+
expect(record!.injectionTemplates).toHaveLength(1);
|
|
517
|
+
expect(record!.injectionTemplates![0].hostPattern).toBe("*.fal.ai");
|
|
518
|
+
// hasRefreshToken is stripped in v5 migration
|
|
519
|
+
expect("hasRefreshToken" in record!).toBe(false);
|
|
520
|
+
|
|
521
|
+
// On-disk file should be upgraded to v5
|
|
522
|
+
const raw = JSON.parse(readFileSync(META_PATH, "utf-8"));
|
|
523
|
+
expect(raw.version).toBe(5);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
test("v3 migration handles multiple services with ghost records", () => {
|
|
527
|
+
const v3Data = {
|
|
528
|
+
version: 3,
|
|
529
|
+
credentials: [
|
|
530
|
+
{
|
|
531
|
+
credentialId: "at-github",
|
|
532
|
+
service: "github",
|
|
533
|
+
field: "access_token",
|
|
534
|
+
allowedTools: [],
|
|
535
|
+
allowedDomains: [],
|
|
536
|
+
createdAt: 1700000000000,
|
|
537
|
+
updatedAt: 1700000000000,
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
credentialId: "rt-github",
|
|
541
|
+
service: "github",
|
|
542
|
+
field: "refresh_token",
|
|
543
|
+
allowedTools: [],
|
|
544
|
+
allowedDomains: [],
|
|
545
|
+
createdAt: 1700000000000,
|
|
546
|
+
updatedAt: 1700000000000,
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
credentialId: "at-slack",
|
|
550
|
+
service: "slack",
|
|
551
|
+
field: "access_token",
|
|
552
|
+
allowedTools: [],
|
|
553
|
+
allowedDomains: [],
|
|
554
|
+
createdAt: 1700000000000,
|
|
555
|
+
updatedAt: 1700000000000,
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
credentialId: "at-stripe",
|
|
559
|
+
service: "stripe",
|
|
560
|
+
field: "access_token",
|
|
561
|
+
allowedTools: [],
|
|
562
|
+
allowedDomains: [],
|
|
563
|
+
createdAt: 1700000000000,
|
|
564
|
+
updatedAt: 1700000000000,
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
credentialId: "rt-stripe",
|
|
568
|
+
service: "stripe",
|
|
569
|
+
field: "refresh_token",
|
|
570
|
+
allowedTools: [],
|
|
571
|
+
allowedDomains: [],
|
|
572
|
+
createdAt: 1700000000000,
|
|
573
|
+
updatedAt: 1700000000000,
|
|
574
|
+
},
|
|
575
|
+
],
|
|
576
|
+
};
|
|
577
|
+
writeFileSync(META_PATH, JSON.stringify(v3Data, null, 2), "utf-8");
|
|
578
|
+
|
|
579
|
+
const records = listCredentialMetadata();
|
|
580
|
+
// refresh_token records removed, only access_token records remain
|
|
581
|
+
expect(records).toHaveLength(3);
|
|
582
|
+
expect(records.every((r) => r.field !== "refresh_token")).toBe(true);
|
|
583
|
+
// hasRefreshToken is stripped in v5 migration — none should have it
|
|
584
|
+
const github = records.find((r) => r.service === "github");
|
|
585
|
+
const slack = records.find((r) => r.service === "slack");
|
|
586
|
+
const stripe = records.find((r) => r.service === "stripe");
|
|
587
|
+
expect("hasRefreshToken" in github!).toBe(false);
|
|
588
|
+
expect("hasRefreshToken" in slack!).toBe(false);
|
|
589
|
+
expect("hasRefreshToken" in stripe!).toBe(false);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
test("v4 file is migrated to v5 (strips hasRefreshToken and OAuth fields)", () => {
|
|
593
|
+
const v4Data = {
|
|
594
|
+
version: 4,
|
|
595
|
+
credentials: [
|
|
596
|
+
{
|
|
597
|
+
credentialId: "cred-v4-001",
|
|
598
|
+
service: "fal-ai",
|
|
599
|
+
field: "api_key",
|
|
600
|
+
allowedTools: ["api_request"],
|
|
601
|
+
allowedDomains: ["fal.ai"],
|
|
602
|
+
alias: "fal-primary",
|
|
603
|
+
hasRefreshToken: true,
|
|
604
|
+
createdAt: 1700000000000,
|
|
605
|
+
updatedAt: 1700000000000,
|
|
606
|
+
},
|
|
607
|
+
],
|
|
608
|
+
};
|
|
609
|
+
writeFileSync(META_PATH, JSON.stringify(v4Data, null, 2), "utf-8");
|
|
610
|
+
|
|
611
|
+
const record = getCredentialMetadata("fal-ai", "api_key");
|
|
612
|
+
expect(record).toBeDefined();
|
|
613
|
+
expect(record!.alias).toBe("fal-primary");
|
|
614
|
+
// hasRefreshToken is stripped in v5 migration
|
|
615
|
+
expect("hasRefreshToken" in record!).toBe(false);
|
|
616
|
+
|
|
617
|
+
// On-disk file should be upgraded to v5
|
|
618
|
+
const raw = JSON.parse(readFileSync(META_PATH, "utf-8"));
|
|
619
|
+
expect(raw.version).toBe(5);
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
test("future version (v6+) returns unknown version and refuses writes", () => {
|
|
408
623
|
const futureData = {
|
|
409
624
|
version: 99,
|
|
410
625
|
credentials: [],
|
|
@@ -439,7 +654,7 @@ describe("credential metadata store", () => {
|
|
|
439
654
|
},
|
|
440
655
|
);
|
|
441
656
|
|
|
442
|
-
test("upsert on migrated v1 file saves as
|
|
657
|
+
test("upsert on migrated v1 file saves as v5", () => {
|
|
443
658
|
const v1Data = {
|
|
444
659
|
version: 1,
|
|
445
660
|
credentials: [
|
|
@@ -459,13 +674,13 @@ describe("credential metadata store", () => {
|
|
|
459
674
|
// Upsert triggers load (migration) + save (at current version)
|
|
460
675
|
upsertCredentialMetadata("github", "token", { alias: "gh-main" });
|
|
461
676
|
|
|
462
|
-
// Verify on-disk file is now
|
|
677
|
+
// Verify on-disk file is now v5
|
|
463
678
|
const raw = JSON.parse(readFileSync(META_PATH, "utf-8"));
|
|
464
|
-
expect(raw.version).toBe(
|
|
679
|
+
expect(raw.version).toBe(5);
|
|
465
680
|
expect(raw.credentials[0].alias).toBe("gh-main");
|
|
466
681
|
});
|
|
467
682
|
|
|
468
|
-
test("v1 load auto-persists as
|
|
683
|
+
test("v1 load auto-persists as v5 on disk without requiring a write", () => {
|
|
469
684
|
const v1Data = {
|
|
470
685
|
version: 1,
|
|
471
686
|
credentials: [
|
|
@@ -482,15 +697,15 @@ describe("credential metadata store", () => {
|
|
|
482
697
|
};
|
|
483
698
|
writeFileSync(META_PATH, JSON.stringify(v1Data, null, 2), "utf-8");
|
|
484
699
|
|
|
485
|
-
// A read-only operation should still persist the
|
|
700
|
+
// A read-only operation should still persist the v5 upgrade
|
|
486
701
|
listCredentialMetadata();
|
|
487
702
|
|
|
488
703
|
const raw = JSON.parse(readFileSync(META_PATH, "utf-8"));
|
|
489
|
-
expect(raw.version).toBe(
|
|
704
|
+
expect(raw.version).toBe(5);
|
|
490
705
|
expect(raw.credentials[0].credentialId).toBe("cred-autopersist");
|
|
491
706
|
});
|
|
492
707
|
|
|
493
|
-
test("v1 file with multiple credentials migrates all records", () => {
|
|
708
|
+
test("v1 file with multiple credentials migrates all records (strips OAuth fields)", () => {
|
|
494
709
|
const v1Data = {
|
|
495
710
|
version: 1,
|
|
496
711
|
credentials: [
|
|
@@ -539,13 +754,11 @@ describe("credential metadata store", () => {
|
|
|
539
754
|
expect(r.injectionTemplates).toBeUndefined();
|
|
540
755
|
}
|
|
541
756
|
|
|
542
|
-
//
|
|
543
|
-
expect(records[0]
|
|
544
|
-
expect(records[1]
|
|
545
|
-
expect(records[2]
|
|
546
|
-
|
|
547
|
-
);
|
|
548
|
-
expect(records[2].oauth2ClientId).toBe("ca_test123");
|
|
757
|
+
// OAuth-specific fields are stripped by v5 migration
|
|
758
|
+
expect("grantedScopes" in records[0]).toBe(false);
|
|
759
|
+
expect("expiresAt" in records[1]).toBe(false);
|
|
760
|
+
expect("oauth2TokenUrl" in records[2]).toBe(false);
|
|
761
|
+
expect("oauth2ClientId" in records[2]).toBe(false);
|
|
549
762
|
});
|
|
550
763
|
});
|
|
551
764
|
|
|
@@ -586,20 +799,20 @@ describe("credential metadata store", () => {
|
|
|
586
799
|
test("file with non-array credentials field is treated as empty list", () => {
|
|
587
800
|
writeFileSync(
|
|
588
801
|
META_PATH,
|
|
589
|
-
JSON.stringify({ version:
|
|
802
|
+
JSON.stringify({ version: 5, credentials: "not-an-array" }),
|
|
590
803
|
"utf-8",
|
|
591
804
|
);
|
|
592
805
|
expect(listCredentialMetadata()).toEqual([]);
|
|
593
806
|
});
|
|
594
807
|
|
|
595
808
|
test("file with missing credentials field is treated as empty list", () => {
|
|
596
|
-
writeFileSync(META_PATH, JSON.stringify({ version:
|
|
809
|
+
writeFileSync(META_PATH, JSON.stringify({ version: 5 }), "utf-8");
|
|
597
810
|
expect(listCredentialMetadata()).toEqual([]);
|
|
598
811
|
});
|
|
599
812
|
|
|
600
813
|
test("malformed records within credentials array are filtered out", () => {
|
|
601
814
|
const data = {
|
|
602
|
-
version:
|
|
815
|
+
version: 5,
|
|
603
816
|
credentials: [
|
|
604
817
|
// Valid record
|
|
605
818
|
{
|
|
@@ -709,7 +922,7 @@ describe("credential metadata store", () => {
|
|
|
709
922
|
|
|
710
923
|
const raw = readFileSync(META_PATH, "utf-8");
|
|
711
924
|
const parsed = JSON.parse(raw);
|
|
712
|
-
expect(parsed.version).toBe(
|
|
925
|
+
expect(parsed.version).toBe(5);
|
|
713
926
|
expect(parsed.credentials).toHaveLength(1);
|
|
714
927
|
expect(parsed.credentials[0].service).toBe("slack");
|
|
715
928
|
});
|
|
@@ -717,23 +930,23 @@ describe("credential metadata store", () => {
|
|
|
717
930
|
test("file written by saveFile has version field", () => {
|
|
718
931
|
upsertCredentialMetadata("test", "key");
|
|
719
932
|
const raw = JSON.parse(readFileSync(META_PATH, "utf-8"));
|
|
720
|
-
expect(raw.version).toBe(
|
|
933
|
+
expect(raw.version).toBe(5);
|
|
721
934
|
});
|
|
722
935
|
});
|
|
723
936
|
|
|
724
937
|
// ── Empty credential lists ────────────────────────────────────────
|
|
725
938
|
|
|
726
939
|
describe("empty credential lists", () => {
|
|
727
|
-
test("empty
|
|
940
|
+
test("empty v5 file returns empty array", () => {
|
|
728
941
|
writeFileSync(
|
|
729
942
|
META_PATH,
|
|
730
|
-
JSON.stringify({ version:
|
|
943
|
+
JSON.stringify({ version: 5, credentials: [] }, null, 2),
|
|
731
944
|
"utf-8",
|
|
732
945
|
);
|
|
733
946
|
expect(listCredentialMetadata()).toEqual([]);
|
|
734
947
|
});
|
|
735
948
|
|
|
736
|
-
test("empty v1 file is migrated to
|
|
949
|
+
test("empty v1 file is migrated to v5 with empty credentials", () => {
|
|
737
950
|
writeFileSync(
|
|
738
951
|
META_PATH,
|
|
739
952
|
JSON.stringify({ version: 1, credentials: [] }, null, 2),
|
|
@@ -741,9 +954,9 @@ describe("credential metadata store", () => {
|
|
|
741
954
|
);
|
|
742
955
|
expect(listCredentialMetadata()).toEqual([]);
|
|
743
956
|
|
|
744
|
-
// Should be persisted as
|
|
957
|
+
// Should be persisted as v5
|
|
745
958
|
const raw = JSON.parse(readFileSync(META_PATH, "utf-8"));
|
|
746
|
-
expect(raw.version).toBe(
|
|
959
|
+
expect(raw.version).toBe(5);
|
|
747
960
|
expect(raw.credentials).toEqual([]);
|
|
748
961
|
});
|
|
749
962
|
|
|
@@ -12,6 +12,7 @@ mock.module("../util/logger.js", () => ({
|
|
|
12
12
|
}),
|
|
13
13
|
}));
|
|
14
14
|
|
|
15
|
+
import { credentialKey } from "../security/credential-key.js";
|
|
15
16
|
import {
|
|
16
17
|
_setMetadataPath,
|
|
17
18
|
upsertCredentialMetadata,
|
|
@@ -56,7 +57,7 @@ describe("credential resolver", () => {
|
|
|
56
57
|
expect(result!.credentialId).toBe(created.credentialId);
|
|
57
58
|
expect(result!.service).toBe("github");
|
|
58
59
|
expect(result!.field).toBe("token");
|
|
59
|
-
expect(result!.storageKey).toBe("
|
|
60
|
+
expect(result!.storageKey).toBe(credentialKey("github", "token"));
|
|
60
61
|
expect(result!.metadata.allowedTools).toEqual([
|
|
61
62
|
"browser_fill_credential",
|
|
62
63
|
]);
|
|
@@ -111,7 +112,7 @@ describe("credential resolver", () => {
|
|
|
111
112
|
expect(result!.credentialId).toBe(created.credentialId);
|
|
112
113
|
expect(result!.service).toBe("gmail");
|
|
113
114
|
expect(result!.field).toBe("password");
|
|
114
|
-
expect(result!.storageKey).toBe("
|
|
115
|
+
expect(result!.storageKey).toBe(credentialKey("gmail", "password"));
|
|
115
116
|
});
|
|
116
117
|
|
|
117
118
|
test("returns undefined for non-existent ID", () => {
|
|
@@ -190,10 +191,10 @@ describe("credential resolver", () => {
|
|
|
190
191
|
});
|
|
191
192
|
|
|
192
193
|
describe("storage key format", () => {
|
|
193
|
-
test("storage key follows credential
|
|
194
|
+
test("storage key follows credential/{service}/{field} format", () => {
|
|
194
195
|
upsertCredentialMetadata("my-service", "api-key");
|
|
195
196
|
const result = resolveByServiceField("my-service", "api-key");
|
|
196
|
-
expect(result!.storageKey).toBe("
|
|
197
|
+
expect(result!.storageKey).toBe(credentialKey("my-service", "api-key"));
|
|
197
198
|
});
|
|
198
199
|
});
|
|
199
200
|
|
|
@@ -147,12 +147,12 @@ describe("E2E: secure store and list lifecycle", () => {
|
|
|
147
147
|
// Value must NOT appear in tool output (invariant 1)
|
|
148
148
|
expect(result.content).not.toContain("ghp_abc123");
|
|
149
149
|
// Value must be in keychain
|
|
150
|
-
expect(storedKeys.get("credential
|
|
150
|
+
expect(storedKeys.get("credential/github/token")).toBe("ghp_abc123");
|
|
151
151
|
});
|
|
152
152
|
|
|
153
153
|
test("list returns service/field pairs without secret values", async () => {
|
|
154
|
-
storedKeys.set("credential
|
|
155
|
-
storedKeys.set("credential
|
|
154
|
+
storedKeys.set("credential/github/token", "secret1");
|
|
155
|
+
storedKeys.set("credential/aws/access_key", "secret2");
|
|
156
156
|
metadataStore.set("github:token", {
|
|
157
157
|
credentialId: "cred-github-token",
|
|
158
158
|
service: "github",
|
|
@@ -177,14 +177,14 @@ describe("E2E: secure store and list lifecycle", () => {
|
|
|
177
177
|
});
|
|
178
178
|
|
|
179
179
|
test("delete removes credential from keychain", async () => {
|
|
180
|
-
storedKeys.set("credential
|
|
180
|
+
storedKeys.set("credential/github/token", "secret1");
|
|
181
181
|
|
|
182
182
|
const result = await credentialStoreTool.execute(
|
|
183
183
|
{ action: "delete", service: "github", field: "token" },
|
|
184
184
|
makeContext(),
|
|
185
185
|
);
|
|
186
186
|
expect(result.isError).toBe(false);
|
|
187
|
-
expect(storedKeys.has("credential
|
|
187
|
+
expect(storedKeys.has("credential/github/token")).toBe(false);
|
|
188
188
|
});
|
|
189
189
|
});
|
|
190
190
|
|
|
@@ -267,7 +267,7 @@ describe("E2E: one-time send override", () => {
|
|
|
267
267
|
);
|
|
268
268
|
expect(result.isError).toBe(true);
|
|
269
269
|
expect(result.content).toContain("not enabled");
|
|
270
|
-
expect(storedKeys.has("credential
|
|
270
|
+
expect(storedKeys.has("credential/svc/key")).toBe(false);
|
|
271
271
|
});
|
|
272
272
|
|
|
273
273
|
test("accepts transient_send when config gate is on", async () => {
|
|
@@ -285,7 +285,7 @@ describe("E2E: one-time send override", () => {
|
|
|
285
285
|
expect(result.isError).toBe(false);
|
|
286
286
|
expect(result.content).toContain("NOT saved");
|
|
287
287
|
// Value must NOT be in keychain
|
|
288
|
-
expect(storedKeys.has("credential
|
|
288
|
+
expect(storedKeys.has("credential/svc/key")).toBe(false);
|
|
289
289
|
// Value must NOT appear in output
|
|
290
290
|
expect(result.content).not.toContain("tmp1");
|
|
291
291
|
});
|
|
@@ -303,7 +303,7 @@ describe("E2E: one-time send override", () => {
|
|
|
303
303
|
ctx,
|
|
304
304
|
);
|
|
305
305
|
expect(result.isError).toBe(false);
|
|
306
|
-
expect(storedKeys.has("credential
|
|
306
|
+
expect(storedKeys.has("credential/svc/key")).toBe(true);
|
|
307
307
|
});
|
|
308
308
|
});
|
|
309
309
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { mkdirSync, rmSync } from "node:fs";
|
|
2
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
@@ -63,11 +63,13 @@ mock.module("../tools/registry.js", () => ({
|
|
|
63
63
|
// ---------------------------------------------------------------------------
|
|
64
64
|
|
|
65
65
|
import { DEFAULT_CONFIG } from "../config/defaults.js";
|
|
66
|
+
import { credentialKey } from "../security/credential-key.js";
|
|
66
67
|
import { redactSensitiveFields } from "../security/redaction.js";
|
|
67
|
-
import { setSecureKey } from "../security/secure-keys.js";
|
|
68
|
+
import { getSecureKey, setSecureKey } from "../security/secure-keys.js";
|
|
68
69
|
import { CredentialBroker } from "../tools/credentials/broker.js";
|
|
69
70
|
import {
|
|
70
71
|
_setMetadataPath,
|
|
72
|
+
getCredentialMetadata,
|
|
71
73
|
upsertCredentialMetadata,
|
|
72
74
|
} from "../tools/credentials/metadata-store.js";
|
|
73
75
|
|
|
@@ -199,6 +201,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
|
|
|
199
201
|
// Hard boundary: only these production files may import from secure-keys.
|
|
200
202
|
// Any new import must be reviewed for secret-leak risk and added here.
|
|
201
203
|
const ALLOWED_IMPORTERS = new Set([
|
|
204
|
+
"security/credential-key.ts", // credential key builder + migration
|
|
202
205
|
"config/loader.ts", // config management (API keys)
|
|
203
206
|
"tools/credentials/vault.ts", // credential store tool
|
|
204
207
|
"tools/credentials/broker.ts", // brokered credential access
|
|
@@ -226,10 +229,13 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
|
|
|
226
229
|
"runtime/routes/integrations/slack/share.ts", // Slack share routes credential lookup
|
|
227
230
|
"mcp/client.ts", // MCP client cached-token lookup
|
|
228
231
|
"oauth/token-persistence.ts", // OAuth token persistence (set/delete tokens)
|
|
232
|
+
"oauth/connection-resolver.ts", // resolve OAuthConnection from oauth-store (access_token lookup)
|
|
229
233
|
"runtime/routes/secret-routes.ts", // HTTP secret management routes (set/delete secrets)
|
|
230
|
-
"daemon/ride-shotgun-handler.ts", // learn session cookie persistence
|
|
231
234
|
"daemon/session-messaging.ts", // credential storage during session messaging
|
|
232
|
-
"runtime/routes/settings-routes.ts", // settings routes OAuth credential lookup (
|
|
235
|
+
"runtime/routes/settings-routes.ts", // settings routes OAuth credential lookup (client_secret)
|
|
236
|
+
"oauth/oauth-store.ts", // OAuth provider disconnect (delete stored tokens)
|
|
237
|
+
"cli/commands/oauth/connections.ts", // CLI OAuth connection delete (legacy credential cleanup)
|
|
238
|
+
"oauth/manual-token-connection.ts", // manual-token provider backfill (keychain credential existence check)
|
|
233
239
|
]);
|
|
234
240
|
|
|
235
241
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -245,7 +251,11 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
|
|
|
245
251
|
const s = statSync(full);
|
|
246
252
|
if (s.isDirectory()) {
|
|
247
253
|
collectTsFiles(full, files);
|
|
248
|
-
} else if (
|
|
254
|
+
} else if (
|
|
255
|
+
entry.endsWith(".ts") &&
|
|
256
|
+
!entry.endsWith(".d.ts") &&
|
|
257
|
+
!entry.endsWith(".test.ts")
|
|
258
|
+
) {
|
|
249
259
|
files.push(full);
|
|
250
260
|
}
|
|
251
261
|
}
|
|
@@ -413,7 +423,10 @@ describe("Invariant 4: credentials only used for allowed purpose", () => {
|
|
|
413
423
|
allowedTools: tc.allowedTools,
|
|
414
424
|
allowedDomains: tc.allowedDomains,
|
|
415
425
|
});
|
|
416
|
-
setSecureKey(
|
|
426
|
+
setSecureKey(
|
|
427
|
+
credentialKey(tc.credentialId, "token"),
|
|
428
|
+
"test-secret-value",
|
|
429
|
+
);
|
|
417
430
|
|
|
418
431
|
const result = await broker.browserFill({
|
|
419
432
|
service: tc.credentialId,
|
|
@@ -438,7 +451,7 @@ describe("Invariant 4: credentials only used for allowed purpose", () => {
|
|
|
438
451
|
allowedTools: ["browser_fill_credential"],
|
|
439
452
|
allowedDomains: ["github.com"],
|
|
440
453
|
});
|
|
441
|
-
setSecureKey("
|
|
454
|
+
setSecureKey(credentialKey("github", "token"), "ghp_secret123");
|
|
442
455
|
|
|
443
456
|
const result = await broker.browserFill({
|
|
444
457
|
service: "github",
|
|
@@ -470,6 +483,97 @@ describe("Invariant 4: credentials only used for allowed purpose", () => {
|
|
|
470
483
|
});
|
|
471
484
|
});
|
|
472
485
|
|
|
486
|
+
// ---------------------------------------------------------------------------
|
|
487
|
+
// Invariant 6 — oauth2ClientSecret never in plaintext metadata
|
|
488
|
+
// ---------------------------------------------------------------------------
|
|
489
|
+
|
|
490
|
+
describe("Invariant 6: oauth2ClientSecret not in metadata, only in secure store", () => {
|
|
491
|
+
beforeEach(() => {
|
|
492
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
493
|
+
_setStorePath(STORE_PATH);
|
|
494
|
+
_resetBackend();
|
|
495
|
+
_setMetadataPath(join(TEST_DIR, "metadata.json"));
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
afterEach(() => {
|
|
499
|
+
_setMetadataPath(null);
|
|
500
|
+
_setStorePath(null);
|
|
501
|
+
_resetBackend();
|
|
502
|
+
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
test("upsertCredentialMetadata does not accept oauth2ClientSecret or other OAuth fields", () => {
|
|
506
|
+
const record = upsertCredentialMetadata(
|
|
507
|
+
"integration:gmail",
|
|
508
|
+
"access_token",
|
|
509
|
+
{
|
|
510
|
+
allowedTools: ["api_request"],
|
|
511
|
+
},
|
|
512
|
+
);
|
|
513
|
+
expect("oauth2ClientSecret" in record).toBe(false);
|
|
514
|
+
expect("oauth2TokenUrl" in record).toBe(false);
|
|
515
|
+
expect("oauth2ClientId" in record).toBe(false);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
test("client secret is read from secure store, not metadata", () => {
|
|
519
|
+
setSecureKey(
|
|
520
|
+
credentialKey("integration:gmail", "client_secret"),
|
|
521
|
+
"my-secret",
|
|
522
|
+
);
|
|
523
|
+
upsertCredentialMetadata("integration:gmail", "access_token", {
|
|
524
|
+
allowedTools: ["api_request"],
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
const meta = getCredentialMetadata("integration:gmail", "access_token");
|
|
528
|
+
expect(meta).toBeDefined();
|
|
529
|
+
expect("oauth2ClientSecret" in meta!).toBe(false);
|
|
530
|
+
// OAuth-specific fields are no longer in metadata (v5)
|
|
531
|
+
expect("oauth2TokenUrl" in meta!).toBe(false);
|
|
532
|
+
expect("oauth2ClientId" in meta!).toBe(false);
|
|
533
|
+
|
|
534
|
+
// Secret is in secure store
|
|
535
|
+
expect(
|
|
536
|
+
getSecureKey(credentialKey("integration:gmail", "client_secret")),
|
|
537
|
+
).toBe("my-secret");
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
test("v2 metadata with oauth2ClientSecret is stripped on migration", () => {
|
|
541
|
+
const v2Data = {
|
|
542
|
+
version: 2,
|
|
543
|
+
credentials: [
|
|
544
|
+
{
|
|
545
|
+
credentialId: "cred-v2-secret",
|
|
546
|
+
service: "integration:gmail",
|
|
547
|
+
field: "access_token",
|
|
548
|
+
allowedTools: [],
|
|
549
|
+
allowedDomains: [],
|
|
550
|
+
oauth2TokenUrl: "https://oauth2.googleapis.com/token",
|
|
551
|
+
oauth2ClientId: "test-client-id",
|
|
552
|
+
oauth2ClientSecret: "plaintext-secret-should-be-stripped",
|
|
553
|
+
createdAt: 1700000000000,
|
|
554
|
+
updatedAt: 1700000000000,
|
|
555
|
+
},
|
|
556
|
+
],
|
|
557
|
+
};
|
|
558
|
+
writeFileSync(
|
|
559
|
+
join(TEST_DIR, "metadata.json"),
|
|
560
|
+
JSON.stringify(v2Data, null, 2),
|
|
561
|
+
"utf-8",
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
const meta = getCredentialMetadata("integration:gmail", "access_token");
|
|
565
|
+
expect(meta).toBeDefined();
|
|
566
|
+
expect("oauth2ClientSecret" in meta!).toBe(false);
|
|
567
|
+
|
|
568
|
+
// Verify on-disk file no longer contains the secret
|
|
569
|
+
const raw = JSON.parse(
|
|
570
|
+
readFileSync(join(TEST_DIR, "metadata.json"), "utf-8"),
|
|
571
|
+
);
|
|
572
|
+
expect(raw.credentials[0]).not.toHaveProperty("oauth2ClientSecret");
|
|
573
|
+
expect(raw.version).toBe(5);
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
|
|
473
577
|
// ---------------------------------------------------------------------------
|
|
474
578
|
// Cross-Cutting — One-Time Send Override
|
|
475
579
|
// ---------------------------------------------------------------------------
|