@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
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BYO (Bring-Your-Own) OAuth connection implementation.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the existing `withValidToken` token management infrastructure to
|
|
5
|
+
* provide the OAuthConnection interface. Delegates all token resolution,
|
|
6
|
+
* proactive refresh, circuit breaker, and retry-on-401 logic to
|
|
7
|
+
* `withValidToken` from `token-manager.ts`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { withValidToken } from "../security/token-manager.js";
|
|
11
|
+
import { getLogger } from "../util/logger.js";
|
|
12
|
+
import type {
|
|
13
|
+
OAuthConnection,
|
|
14
|
+
OAuthConnectionRequest,
|
|
15
|
+
OAuthConnectionResponse,
|
|
16
|
+
} from "./connection.js";
|
|
17
|
+
|
|
18
|
+
const log = getLogger("byo-oauth-connection");
|
|
19
|
+
|
|
20
|
+
/** Default per-request timeout to prevent hung requests from blocking indefinitely. */
|
|
21
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
22
|
+
|
|
23
|
+
export interface BYOOAuthConnectionOptions {
|
|
24
|
+
id: string;
|
|
25
|
+
providerKey: string;
|
|
26
|
+
baseUrl: string;
|
|
27
|
+
accountInfo: string | null;
|
|
28
|
+
grantedScopes: string[];
|
|
29
|
+
credentialService: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class BYOOAuthConnection implements OAuthConnection {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly providerKey: string;
|
|
35
|
+
readonly accountInfo: string | null;
|
|
36
|
+
readonly grantedScopes: string[];
|
|
37
|
+
|
|
38
|
+
private readonly baseUrl: string;
|
|
39
|
+
private readonly credentialService: string;
|
|
40
|
+
|
|
41
|
+
constructor(opts: BYOOAuthConnectionOptions) {
|
|
42
|
+
this.id = opts.id;
|
|
43
|
+
this.providerKey = opts.providerKey;
|
|
44
|
+
this.baseUrl = opts.baseUrl;
|
|
45
|
+
this.accountInfo = opts.accountInfo;
|
|
46
|
+
this.grantedScopes = opts.grantedScopes;
|
|
47
|
+
this.credentialService = opts.credentialService;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async request(req: OAuthConnectionRequest): Promise<OAuthConnectionResponse> {
|
|
51
|
+
return withValidToken(this.credentialService, async (token) => {
|
|
52
|
+
const effectiveBaseUrl = req.baseUrl ?? this.baseUrl;
|
|
53
|
+
let fullUrl = `${effectiveBaseUrl}${req.path}`;
|
|
54
|
+
|
|
55
|
+
if (req.query && Object.keys(req.query).length > 0) {
|
|
56
|
+
const params = new URLSearchParams();
|
|
57
|
+
for (const [key, value] of Object.entries(req.query)) {
|
|
58
|
+
if (Array.isArray(value)) {
|
|
59
|
+
for (const v of value) params.append(key, v);
|
|
60
|
+
} else {
|
|
61
|
+
params.append(key, value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
fullUrl += `?${params.toString()}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
log.debug(
|
|
68
|
+
{ method: req.method, url: fullUrl, provider: this.providerKey },
|
|
69
|
+
"Making authenticated request",
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Use the Headers API for case-insensitive merging. Set defaults
|
|
73
|
+
// first so caller-supplied headers (in any casing) override them.
|
|
74
|
+
const headers = new Headers();
|
|
75
|
+
if (req.body) {
|
|
76
|
+
headers.set("Content-Type", "application/json");
|
|
77
|
+
}
|
|
78
|
+
if (req.headers) {
|
|
79
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
80
|
+
headers.set(key, value);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
84
|
+
|
|
85
|
+
const resp = await fetch(fullUrl, {
|
|
86
|
+
method: req.method,
|
|
87
|
+
headers,
|
|
88
|
+
body: req.body ? JSON.stringify(req.body) : undefined,
|
|
89
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (resp.status === 401) {
|
|
93
|
+
// Throw with a status property so withValidToken detects the 401
|
|
94
|
+
// and triggers its refresh-and-retry logic.
|
|
95
|
+
const err = new Error(`HTTP 401 from ${this.providerKey}`);
|
|
96
|
+
(err as Error & { status: number }).status = 401;
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return buildResponse(resp);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async withToken<T>(fn: (token: string) => Promise<T>): Promise<T> {
|
|
105
|
+
return withValidToken(this.credentialService, fn);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function buildResponse(resp: Response): Promise<OAuthConnectionResponse> {
|
|
110
|
+
const headers: Record<string, string> = {};
|
|
111
|
+
resp.headers.forEach((value, key) => {
|
|
112
|
+
headers[key] = value;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
let body: unknown;
|
|
116
|
+
const text = await resp.text().catch(() => "");
|
|
117
|
+
if (text) {
|
|
118
|
+
try {
|
|
119
|
+
body = JSON.parse(text);
|
|
120
|
+
} catch {
|
|
121
|
+
body = text;
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
body = null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { status: resp.status, headers, body };
|
|
128
|
+
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - Ensuring metadata is writable (assertMetadataWritable)
|
|
13
13
|
*
|
|
14
14
|
* The orchestrator handles:
|
|
15
|
-
* - Provider
|
|
15
|
+
* - Provider config resolution (from DB)
|
|
16
16
|
* - Scope policy enforcement
|
|
17
17
|
* - Building the OAuth2Config
|
|
18
18
|
* - Running the interactive or deferred flow
|
|
@@ -23,13 +23,54 @@
|
|
|
23
23
|
import type { TokenEndpointAuthMethod } from "../security/oauth2.js";
|
|
24
24
|
import { prepareOAuth2Flow, startOAuth2Flow } from "../security/oauth2.js";
|
|
25
25
|
import { getLogger } from "../util/logger.js";
|
|
26
|
-
import type {
|
|
27
|
-
|
|
26
|
+
import type {
|
|
27
|
+
OAuthConnectResult,
|
|
28
|
+
OAuthProviderBehavior,
|
|
29
|
+
OAuthScopePolicy,
|
|
30
|
+
} from "./connect-types.js";
|
|
31
|
+
import { getProvider } from "./oauth-store.js";
|
|
32
|
+
import { getProviderBehavior, resolveService } from "./provider-behaviors.js";
|
|
28
33
|
import { resolveScopes } from "./scope-policy.js";
|
|
29
34
|
import { storeOAuth2Tokens } from "./token-persistence.js";
|
|
30
35
|
|
|
31
36
|
const log = getLogger("oauth-connect-orchestrator");
|
|
32
37
|
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Helpers
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Look up the code-side behavioral fields for a provider.
|
|
44
|
+
* Returns an empty object when no behavior is registered.
|
|
45
|
+
*/
|
|
46
|
+
function resolveBehavior(providerKey: string): {
|
|
47
|
+
identityVerifier?: OAuthProviderBehavior["identityVerifier"];
|
|
48
|
+
setup?: OAuthProviderBehavior["setup"];
|
|
49
|
+
setupSkillId?: string;
|
|
50
|
+
postConnectHookId?: string;
|
|
51
|
+
injectionTemplates?: OAuthProviderBehavior["injectionTemplates"];
|
|
52
|
+
} {
|
|
53
|
+
const behavior = getProviderBehavior(providerKey);
|
|
54
|
+
if (!behavior) return {};
|
|
55
|
+
return {
|
|
56
|
+
identityVerifier: behavior.identityVerifier,
|
|
57
|
+
setup: behavior.setup,
|
|
58
|
+
setupSkillId: behavior.setupSkillId,
|
|
59
|
+
postConnectHookId: behavior.postConnectHookId,
|
|
60
|
+
injectionTemplates: behavior.injectionTemplates,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Safely parse a JSON string, returning a fallback on failure or null/undefined input. */
|
|
65
|
+
function safeJsonParse<T>(value: string | null | undefined, fallback: T): T {
|
|
66
|
+
if (value == null) return fallback;
|
|
67
|
+
try {
|
|
68
|
+
return JSON.parse(value) as T;
|
|
69
|
+
} catch {
|
|
70
|
+
return fallback;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
33
74
|
// ---------------------------------------------------------------------------
|
|
34
75
|
// Options
|
|
35
76
|
// ---------------------------------------------------------------------------
|
|
@@ -52,14 +93,17 @@ export interface OAuthConnectOptions {
|
|
|
52
93
|
/** Tools allowed to use the resulting credential. */
|
|
53
94
|
allowedTools?: string[];
|
|
54
95
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Called when the deferred (non-interactive) flow completes — either
|
|
98
|
+
* successfully after tokens are stored, or on failure. Lets callers
|
|
99
|
+
* surface the outcome via SSE events, logs, etc.
|
|
100
|
+
*/
|
|
101
|
+
onDeferredComplete?: (result: {
|
|
102
|
+
success: boolean;
|
|
103
|
+
service: string;
|
|
104
|
+
accountInfo?: string;
|
|
105
|
+
error?: string;
|
|
106
|
+
}) => void;
|
|
63
107
|
}
|
|
64
108
|
|
|
65
109
|
// ---------------------------------------------------------------------------
|
|
@@ -78,42 +122,69 @@ export async function orchestrateOAuthConnect(
|
|
|
78
122
|
options: OAuthConnectOptions,
|
|
79
123
|
): Promise<OAuthConnectResult> {
|
|
80
124
|
const resolvedService = resolveService(options.service);
|
|
81
|
-
const profile = getProviderProfile(resolvedService);
|
|
82
125
|
|
|
83
|
-
//
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
126
|
+
// Read provider config from the DB
|
|
127
|
+
const providerRow = getProvider(resolvedService);
|
|
128
|
+
if (!providerRow) {
|
|
129
|
+
return {
|
|
130
|
+
success: false,
|
|
131
|
+
error: `No OAuth provider registered for "${resolvedService}". Ensure the provider is seeded in the database.`,
|
|
132
|
+
safeError: true,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Behavioral/code-side fields come from the behavior registry
|
|
137
|
+
const behavior = resolveBehavior(resolvedService);
|
|
90
138
|
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
139
|
+
// Deserialize JSON fields from the DB row
|
|
140
|
+
const dbDefaultScopes = safeJsonParse<string[]>(
|
|
141
|
+
providerRow.defaultScopes,
|
|
142
|
+
[],
|
|
143
|
+
);
|
|
144
|
+
const dbScopePolicy = safeJsonParse<OAuthScopePolicy>(
|
|
145
|
+
providerRow.scopePolicy,
|
|
146
|
+
{
|
|
147
|
+
allowAdditionalScopes: false,
|
|
148
|
+
allowedOptionalScopes: [],
|
|
149
|
+
forbiddenScopes: [],
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
const dbExtraParams = safeJsonParse<Record<string, string> | undefined>(
|
|
153
|
+
providerRow.extraParams,
|
|
154
|
+
undefined,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Resolve all protocol-level config from the DB
|
|
158
|
+
const authUrl = providerRow.authUrl;
|
|
159
|
+
const tokenUrl = providerRow.tokenUrl;
|
|
160
|
+
const extraParams = dbExtraParams;
|
|
161
|
+
const userinfoUrl = providerRow.userinfoUrl ?? undefined;
|
|
162
|
+
const tokenEndpointAuthMethod = providerRow.tokenEndpointAuthMethod as
|
|
163
|
+
| TokenEndpointAuthMethod
|
|
164
|
+
| undefined;
|
|
165
|
+
const callbackTransport =
|
|
166
|
+
(providerRow.callbackTransport as "loopback" | "gateway" | null) ??
|
|
167
|
+
"gateway";
|
|
168
|
+
const loopbackPort = providerRow.loopbackPort;
|
|
169
|
+
|
|
170
|
+
// Resolve scopes via the scope policy engine
|
|
171
|
+
const scopeProfile = {
|
|
172
|
+
service: resolvedService,
|
|
173
|
+
defaultScopes: dbDefaultScopes,
|
|
174
|
+
scopePolicy: dbScopePolicy,
|
|
175
|
+
};
|
|
176
|
+
const scopeResult = resolveScopes(scopeProfile, options.requestedScopes);
|
|
177
|
+
if (!scopeResult.ok) {
|
|
178
|
+
const guidance = scopeResult.allowedScopes
|
|
179
|
+
? ` Allowed scopes: ${scopeResult.allowedScopes.join(", ")}`
|
|
180
|
+
: "";
|
|
111
181
|
return {
|
|
112
182
|
success: false,
|
|
113
|
-
error:
|
|
183
|
+
error: `${scopeResult.error}${guidance}`,
|
|
114
184
|
safeError: true,
|
|
115
185
|
};
|
|
116
186
|
}
|
|
187
|
+
const finalScopes = scopeResult.scopes;
|
|
117
188
|
|
|
118
189
|
if (!authUrl) {
|
|
119
190
|
return {
|
|
@@ -149,7 +220,7 @@ export async function orchestrateOAuthConnect(
|
|
|
149
220
|
tokenEndpointAuthMethod,
|
|
150
221
|
userinfoUrl,
|
|
151
222
|
allowedTools: options.allowedTools,
|
|
152
|
-
wellKnownInjectionTemplates:
|
|
223
|
+
wellKnownInjectionTemplates: behavior.injectionTemplates,
|
|
153
224
|
};
|
|
154
225
|
|
|
155
226
|
// -----------------------------------------------------------------------
|
|
@@ -157,8 +228,6 @@ export async function orchestrateOAuthConnect(
|
|
|
157
228
|
// -----------------------------------------------------------------------
|
|
158
229
|
if (!options.isInteractive) {
|
|
159
230
|
try {
|
|
160
|
-
const callbackTransport = profile?.callbackTransport ?? "gateway";
|
|
161
|
-
|
|
162
231
|
// Gateway transport needs a public ingress URL
|
|
163
232
|
if (callbackTransport !== "loopback") {
|
|
164
233
|
const { loadConfig } = await import("../config/loader.js");
|
|
@@ -179,7 +248,7 @@ export async function orchestrateOAuthConnect(
|
|
|
179
248
|
const prepared = await prepareOAuth2Flow(
|
|
180
249
|
oauthConfig,
|
|
181
250
|
callbackTransport === "loopback"
|
|
182
|
-
? { callbackTransport, loopbackPort:
|
|
251
|
+
? { callbackTransport, loopbackPort: loopbackPort ?? undefined }
|
|
183
252
|
: undefined,
|
|
184
253
|
);
|
|
185
254
|
|
|
@@ -189,10 +258,10 @@ export async function orchestrateOAuthConnect(
|
|
|
189
258
|
try {
|
|
190
259
|
let accountInfo: string | undefined;
|
|
191
260
|
|
|
192
|
-
// Run identity verifier if available
|
|
193
|
-
if (
|
|
261
|
+
// Run identity verifier if available (code-side behavior)
|
|
262
|
+
if (behavior.identityVerifier) {
|
|
194
263
|
try {
|
|
195
|
-
accountInfo = await
|
|
264
|
+
accountInfo = await behavior.identityVerifier(
|
|
196
265
|
result.tokens.accessToken,
|
|
197
266
|
);
|
|
198
267
|
} catch {
|
|
@@ -214,11 +283,21 @@ export async function orchestrateOAuthConnect(
|
|
|
214
283
|
},
|
|
215
284
|
"Deferred OAuth2 flow completed — tokens stored",
|
|
216
285
|
);
|
|
286
|
+
options.onDeferredComplete?.({
|
|
287
|
+
success: true,
|
|
288
|
+
service: resolvedService,
|
|
289
|
+
accountInfo: stored.accountInfo ?? accountInfo,
|
|
290
|
+
});
|
|
217
291
|
} catch (err) {
|
|
218
292
|
log.error(
|
|
219
293
|
{ err, service: resolvedService },
|
|
220
294
|
"Failed to store tokens from deferred OAuth2 flow",
|
|
221
295
|
);
|
|
296
|
+
options.onDeferredComplete?.({
|
|
297
|
+
success: false,
|
|
298
|
+
service: resolvedService,
|
|
299
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
300
|
+
});
|
|
222
301
|
}
|
|
223
302
|
})
|
|
224
303
|
.catch((err) => {
|
|
@@ -226,6 +305,11 @@ export async function orchestrateOAuthConnect(
|
|
|
226
305
|
{ err, service: resolvedService },
|
|
227
306
|
"Deferred OAuth2 flow failed",
|
|
228
307
|
);
|
|
308
|
+
options.onDeferredComplete?.({
|
|
309
|
+
success: false,
|
|
310
|
+
service: resolvedService,
|
|
311
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
312
|
+
});
|
|
229
313
|
});
|
|
230
314
|
|
|
231
315
|
return {
|
|
@@ -266,19 +350,18 @@ export async function orchestrateOAuthConnect(
|
|
|
266
350
|
}
|
|
267
351
|
},
|
|
268
352
|
},
|
|
269
|
-
|
|
270
|
-
? {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
: undefined,
|
|
353
|
+
callbackTransport === "loopback"
|
|
354
|
+
? { callbackTransport, loopbackPort: loopbackPort ?? undefined }
|
|
355
|
+
: callbackTransport === "gateway"
|
|
356
|
+
? { callbackTransport }
|
|
357
|
+
: undefined,
|
|
275
358
|
);
|
|
276
359
|
|
|
277
|
-
// Run identity verifier if available
|
|
360
|
+
// Run identity verifier if available (code-side behavior)
|
|
278
361
|
let verifiedIdentity: string | undefined;
|
|
279
|
-
if (
|
|
362
|
+
if (behavior.identityVerifier) {
|
|
280
363
|
try {
|
|
281
|
-
verifiedIdentity = await
|
|
364
|
+
verifiedIdentity = await behavior.identityVerifier(tokens.accessToken);
|
|
282
365
|
} catch {
|
|
283
366
|
// Non-fatal
|
|
284
367
|
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared types for the OAuth provider extensibility layer.
|
|
3
3
|
*
|
|
4
|
-
* These types are consumed by the provider
|
|
4
|
+
* These types are consumed by the provider behavior registry, the token
|
|
5
5
|
* persistence module, and the credential vault orchestrator.
|
|
6
|
+
*
|
|
7
|
+
* Protocol-level OAuth config (authUrl, tokenUrl, scopes, etc.) is now
|
|
8
|
+
* stored exclusively in the `oauth_providers` SQLite table. This file
|
|
9
|
+
* only defines code-side behavioral types that cannot be serialised to
|
|
10
|
+
* a DB row (functions, templates, UI metadata).
|
|
6
11
|
*/
|
|
7
12
|
|
|
8
|
-
import type { TokenEndpointAuthMethod } from "../security/oauth2.js";
|
|
9
13
|
import type { CredentialInjectionTemplate } from "../tools/credentials/policy-types.js";
|
|
10
14
|
|
|
11
15
|
// ---------------------------------------------------------------------------
|
|
@@ -23,31 +27,21 @@ export interface OAuthScopePolicy {
|
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
// ---------------------------------------------------------------------------
|
|
26
|
-
// Provider
|
|
30
|
+
// Provider behavior
|
|
27
31
|
// ---------------------------------------------------------------------------
|
|
28
32
|
|
|
29
|
-
/**
|
|
30
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Code-side behavioral configuration for a well-known OAuth provider.
|
|
35
|
+
*
|
|
36
|
+
* Protocol-level fields (authUrl, tokenUrl, defaultScopes, scopePolicy,
|
|
37
|
+
* tokenEndpointAuthMethod, callbackTransport, loopbackPort, userinfoUrl,
|
|
38
|
+
* extraParams) are stored in the `oauth_providers` DB table. This
|
|
39
|
+
* interface contains only fields that require code references (functions,
|
|
40
|
+
* templates, skill IDs) and cannot be serialised to a DB row.
|
|
41
|
+
*/
|
|
42
|
+
export interface OAuthProviderBehavior {
|
|
31
43
|
/** Canonical service key (e.g. "integration:twitter"). */
|
|
32
44
|
service: string;
|
|
33
|
-
/** OAuth2 authorization endpoint. */
|
|
34
|
-
authUrl: string;
|
|
35
|
-
/** OAuth2 token endpoint. */
|
|
36
|
-
tokenUrl: string;
|
|
37
|
-
/** Default scopes requested during authorization. */
|
|
38
|
-
defaultScopes: string[];
|
|
39
|
-
/** Policy governing additional/forbidden scopes. */
|
|
40
|
-
scopePolicy: OAuthScopePolicy;
|
|
41
|
-
/** How to send client credentials at the token endpoint. */
|
|
42
|
-
tokenEndpointAuthMethod?: TokenEndpointAuthMethod;
|
|
43
|
-
/** Force a specific callback transport. */
|
|
44
|
-
callbackTransport?: "loopback" | "gateway";
|
|
45
|
-
/** Fixed port for loopback transport (e.g. Slack). */
|
|
46
|
-
loopbackPort?: number;
|
|
47
|
-
/** Endpoint to fetch user identity info after authorization. */
|
|
48
|
-
userinfoUrl?: string;
|
|
49
|
-
/** Extra query parameters appended to the authorization URL. */
|
|
50
|
-
extraParams?: Record<string, string>;
|
|
51
45
|
/**
|
|
52
46
|
* Async function that verifies the user's identity after a successful
|
|
53
47
|
* token exchange. Returns a human-readable account identifier (e.g.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { getSecureKey } from "../security/secure-keys.js";
|
|
2
|
+
import { BYOOAuthConnection } from "./byo-connection.js";
|
|
3
|
+
import type { OAuthConnection } from "./connection.js";
|
|
4
|
+
import { getApp, getConnectionByProvider, getProvider } from "./oauth-store.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resolve an OAuthConnection for a given credential service.
|
|
8
|
+
*
|
|
9
|
+
* Reads exclusively from the SQLite oauth-store. Throws if no connection
|
|
10
|
+
* exists (authorization required).
|
|
11
|
+
*/
|
|
12
|
+
export function resolveOAuthConnection(
|
|
13
|
+
credentialService: string,
|
|
14
|
+
): OAuthConnection {
|
|
15
|
+
const conn = getConnectionByProvider(credentialService);
|
|
16
|
+
if (!conn) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`No credential found for "${credentialService}". Authorization required.`,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const accessToken = getSecureKey(`oauth_connection/${conn.id}/access_token`);
|
|
23
|
+
|
|
24
|
+
if (!accessToken) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`No access token found for "${credentialService}". Authorization required.`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Look up the provider by credentialService first; fall back to the
|
|
31
|
+
// connection's app's canonical providerKey so custom credential_service
|
|
32
|
+
// overrides (e.g. "integration:github-work") still resolve to the well-known
|
|
33
|
+
// provider's base URL. We traverse conn -> oauthApp -> providerKey because
|
|
34
|
+
// conn.providerKey equals credentialService (getConnectionByProvider queries
|
|
35
|
+
// WHERE providerKey = credentialService), whereas the app's providerKey is a
|
|
36
|
+
// foreign key to the oauthProviders table.
|
|
37
|
+
const provider =
|
|
38
|
+
getProvider(credentialService) ??
|
|
39
|
+
getProvider(getApp(conn.oauthAppId)?.providerKey ?? "");
|
|
40
|
+
const baseUrl = provider?.baseUrl;
|
|
41
|
+
|
|
42
|
+
if (!baseUrl) {
|
|
43
|
+
throw new Error(`No base URL configured for "${credentialService}".`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const grantedScopes: string[] = conn.grantedScopes
|
|
47
|
+
? JSON.parse(conn.grantedScopes)
|
|
48
|
+
: [];
|
|
49
|
+
|
|
50
|
+
return new BYOOAuthConnection({
|
|
51
|
+
id: conn.id,
|
|
52
|
+
providerKey: conn.providerKey,
|
|
53
|
+
baseUrl,
|
|
54
|
+
accountInfo: conn.accountInfo,
|
|
55
|
+
grantedScopes,
|
|
56
|
+
credentialService,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface OAuthConnectionRequest {
|
|
2
|
+
method: string;
|
|
3
|
+
path: string; // relative, e.g. "/2/tweets"
|
|
4
|
+
query?: Record<string, string | string[]>;
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
body?: unknown; // JSON-serializable
|
|
7
|
+
/**
|
|
8
|
+
* Override the connection's default base URL for this request.
|
|
9
|
+
* Required for providers that span multiple API hosts sharing
|
|
10
|
+
* one OAuth token (e.g. Google: Gmail, Calendar, People all
|
|
11
|
+
* use the same credential but different base URLs).
|
|
12
|
+
*/
|
|
13
|
+
baseUrl?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface OAuthConnectionResponse {
|
|
17
|
+
status: number;
|
|
18
|
+
headers: Record<string, string>;
|
|
19
|
+
body: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface OAuthConnection {
|
|
23
|
+
/** Make an authenticated HTTP request through this connection. */
|
|
24
|
+
request(req: OAuthConnectionRequest): Promise<OAuthConnectionResponse>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Execute a callback with a valid raw access token. This is an escape hatch
|
|
28
|
+
* for provider-specific endpoints that don't fit the relative-path model
|
|
29
|
+
* (e.g. Gmail batch API on a different host). Throws for platform connections
|
|
30
|
+
* where raw tokens are not available locally.
|
|
31
|
+
*/
|
|
32
|
+
withToken<T>(fn: (token: string) => Promise<T>): Promise<T>;
|
|
33
|
+
|
|
34
|
+
readonly id: string;
|
|
35
|
+
readonly providerKey: string;
|
|
36
|
+
readonly accountInfo: string | null;
|
|
37
|
+
readonly grantedScopes: string[];
|
|
38
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for managing oauth_connection records for non-OAuth (manual-token)
|
|
3
|
+
* providers like slack_channel and telegram.
|
|
4
|
+
*
|
|
5
|
+
* These providers store credentials via the keychain (setSecureKeyAsync) but
|
|
6
|
+
* also maintain an oauth_connection row so that getConnectionByProvider() can
|
|
7
|
+
* be used as the single source of truth for connection status across the
|
|
8
|
+
* codebase.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { credentialKey } from "../security/credential-key.js";
|
|
12
|
+
import { getSecureKey } from "../security/secure-keys.js";
|
|
13
|
+
import {
|
|
14
|
+
createConnection,
|
|
15
|
+
deleteConnection,
|
|
16
|
+
getConnectionByProvider,
|
|
17
|
+
updateConnection,
|
|
18
|
+
upsertApp,
|
|
19
|
+
} from "./oauth-store.js";
|
|
20
|
+
|
|
21
|
+
/** Sentinel client_id used for non-OAuth providers that don't have a real app. */
|
|
22
|
+
const MANUAL_TOKEN_CLIENT_ID = "manual-config";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Ensure an active oauth_connection row exists for the given manual-token
|
|
26
|
+
* provider. Creates the synthetic oauth_app row on first use.
|
|
27
|
+
*
|
|
28
|
+
* @param providerKey - The provider key (e.g. "slack_channel", "telegram")
|
|
29
|
+
* @param accountInfo - Optional account info to store (e.g. team name, bot username)
|
|
30
|
+
*/
|
|
31
|
+
export async function ensureManualTokenConnection(
|
|
32
|
+
providerKey: string,
|
|
33
|
+
accountInfo?: string,
|
|
34
|
+
): Promise<void> {
|
|
35
|
+
const existing = getConnectionByProvider(providerKey);
|
|
36
|
+
if (existing) {
|
|
37
|
+
// Update account info if provided
|
|
38
|
+
if (accountInfo !== undefined) {
|
|
39
|
+
updateConnection(existing.id, { accountInfo });
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Create synthetic app + connection
|
|
45
|
+
const app = await upsertApp(providerKey, MANUAL_TOKEN_CLIENT_ID);
|
|
46
|
+
|
|
47
|
+
createConnection({
|
|
48
|
+
oauthAppId: app.id,
|
|
49
|
+
providerKey,
|
|
50
|
+
accountInfo,
|
|
51
|
+
grantedScopes: [],
|
|
52
|
+
hasRefreshToken: false,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Remove the oauth_connection row for a manual-token provider.
|
|
58
|
+
*
|
|
59
|
+
* Note: This only removes the oauth_connection row. The caller is still
|
|
60
|
+
* responsible for deleting the keychain credentials separately.
|
|
61
|
+
*/
|
|
62
|
+
export function removeManualTokenConnection(providerKey: string): void {
|
|
63
|
+
const conn = getConnectionByProvider(providerKey);
|
|
64
|
+
if (!conn) return;
|
|
65
|
+
deleteConnection(conn.id);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Backfill oauth_connection rows for manual-token providers that already
|
|
70
|
+
* have valid keychain credentials but are missing connection records.
|
|
71
|
+
*
|
|
72
|
+
* This handles the upgrade path from installations that stored credentials
|
|
73
|
+
* before the oauth_connection migration. Without this, existing Telegram
|
|
74
|
+
* and Slack channel integrations would appear disconnected after upgrading
|
|
75
|
+
* until the user reconfigures them.
|
|
76
|
+
*
|
|
77
|
+
* Safe to call on every startup — skips providers that already have a
|
|
78
|
+
* connection row.
|
|
79
|
+
*/
|
|
80
|
+
export async function backfillManualTokenConnections(): Promise<void> {
|
|
81
|
+
// Telegram: requires both bot_token and webhook_secret
|
|
82
|
+
if (!getConnectionByProvider("telegram")) {
|
|
83
|
+
const hasBotToken = !!getSecureKey(credentialKey("telegram", "bot_token"));
|
|
84
|
+
const hasWebhookSecret = !!getSecureKey(
|
|
85
|
+
credentialKey("telegram", "webhook_secret"),
|
|
86
|
+
);
|
|
87
|
+
if (hasBotToken && hasWebhookSecret) {
|
|
88
|
+
await ensureManualTokenConnection("telegram");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Slack channel: requires both bot_token and app_token
|
|
93
|
+
if (!getConnectionByProvider("slack_channel")) {
|
|
94
|
+
const hasBotToken = !!getSecureKey(
|
|
95
|
+
credentialKey("slack_channel", "bot_token"),
|
|
96
|
+
);
|
|
97
|
+
const hasAppToken = !!getSecureKey(
|
|
98
|
+
credentialKey("slack_channel", "app_token"),
|
|
99
|
+
);
|
|
100
|
+
if (hasBotToken && hasAppToken) {
|
|
101
|
+
await ensureManualTokenConnection("slack_channel");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|