@vellumai/assistant 0.5.10 → 0.5.12
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/AGENTS.md +8 -0
- package/ARCHITECTURE.md +43 -43
- package/Dockerfile +3 -0
- package/docs/architecture/integrations.md +37 -42
- package/docs/architecture/memory.md +7 -12
- package/docs/credential-execution-service.md +9 -9
- package/docs/skills.md +1 -1
- package/node_modules/@vellumai/ces-contracts/src/__tests__/grants.test.ts +7 -7
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +5 -4
- package/node_modules/@vellumai/credential-storage/src/index.ts +3 -3
- package/node_modules/@vellumai/credential-storage/src/static-credentials.ts +1 -1
- package/openapi.yaml +7208 -0
- package/package.json +2 -1
- package/scripts/generate-openapi.ts +562 -0
- package/src/__tests__/acp-session.test.ts +239 -44
- package/src/__tests__/assistant-feature-flag-guard.test.ts +8 -8
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +5 -86
- package/src/__tests__/assistant-feature-flags-integration.test.ts +7 -14
- package/src/__tests__/browser-skill-endstate.test.ts +1 -1
- package/src/__tests__/btw-routes.test.ts +8 -0
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +10 -10
- package/src/__tests__/catalog-cache.test.ts +164 -0
- package/src/__tests__/catalog-search.test.ts +61 -0
- package/src/__tests__/channel-approvals.test.ts +7 -7
- package/src/__tests__/channel-readiness-service.test.ts +41 -0
- package/src/__tests__/cli-command-risk-guard.test.ts +181 -6
- package/src/__tests__/config-schema.test.ts +10 -2
- package/src/__tests__/context-memory-e2e.test.ts +2 -6
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +396 -0
- package/src/__tests__/conversation-error.test.ts +3 -2
- package/src/__tests__/conversation-skill-tools.test.ts +1 -3
- package/src/__tests__/conversation-title-service.test.ts +2 -15
- package/src/__tests__/credential-execution-feature-gates.test.ts +4 -8
- package/src/__tests__/credential-execution-managed-contract.test.ts +8 -8
- package/src/__tests__/credential-security-e2e.test.ts +4 -4
- package/src/__tests__/credential-security-invariants.test.ts +12 -18
- package/src/__tests__/credential-vault-unit.test.ts +32 -34
- package/src/__tests__/credential-vault.test.ts +25 -33
- package/src/__tests__/credentials-cli.test.ts +3 -3
- package/src/__tests__/daemon-credential-client.test.ts +2 -2
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -1
- package/src/__tests__/gateway-only-guard.test.ts +3 -0
- package/src/__tests__/heartbeat-service.test.ts +35 -0
- package/src/__tests__/host-bash-proxy.test.ts +79 -0
- package/src/__tests__/host-cu-proxy.test.ts +90 -0
- package/src/__tests__/host-file-proxy.test.ts +89 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -1
- package/src/__tests__/inline-skill-load-permissions.test.ts +3 -3
- package/src/__tests__/integration-status.test.ts +5 -5
- package/src/__tests__/list-messages-attachments.test.ts +171 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +64 -0
- package/src/__tests__/log-export-workspace.test.ts +1 -1
- package/src/__tests__/mcp-abort-signal.test.ts +205 -0
- package/src/__tests__/mcp-client-auth.test.ts +1 -1
- package/src/__tests__/memory-lifecycle-e2e.test.ts +2 -2
- package/src/__tests__/memory-recall-log-store.test.ts +182 -0
- package/src/__tests__/memory-recall-quality.test.ts +6 -8
- package/src/__tests__/memory-regressions.test.ts +53 -42
- package/src/__tests__/memory-retrieval.benchmark.test.ts +5 -9
- package/src/__tests__/messaging-send-tool.test.ts +5 -5
- package/src/__tests__/messaging-skill-split.test.ts +2 -17
- package/src/__tests__/notification-telegram-adapter.test.ts +125 -0
- package/src/__tests__/oauth-cli.test.ts +203 -649
- package/src/__tests__/oauth-provider-profiles.test.ts +55 -20
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/onboarding-template-contract.test.ts +2 -2
- package/src/__tests__/platform-callback-registration.test.ts +119 -0
- package/src/__tests__/secret-ingress-channel.test.ts +261 -0
- package/src/__tests__/secret-ingress-cli.test.ts +201 -0
- package/src/__tests__/secret-ingress-http.test.ts +312 -0
- package/src/__tests__/secret-ingress.test.ts +283 -0
- package/src/__tests__/secret-onetime-send.test.ts +4 -4
- package/src/__tests__/secret-routes-managed-proxy.test.ts +78 -0
- package/src/__tests__/secure-keys-managed-failover.test.ts +73 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +4 -4
- package/src/__tests__/skill-feature-flags.test.ts +11 -19
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -1
- package/src/__tests__/skill-load-inline-command.test.ts +3 -3
- package/src/__tests__/skill-load-inline-includes.test.ts +2 -2
- package/src/__tests__/skill-memory.test.ts +2 -4
- package/src/__tests__/skill-projection-feature-flag.test.ts +2 -4
- package/src/__tests__/skill-projection.benchmark.test.ts +1 -3
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/skills.test.ts +16 -2
- package/src/__tests__/slack-channel-config.test.ts +1 -1
- package/src/__tests__/slack-messaging-token-resolution.test.ts +22 -24
- package/src/__tests__/slack-share-routes.test.ts +5 -5
- package/src/__tests__/slack-skill.test.ts +5 -69
- package/src/__tests__/system-prompt.test.ts +39 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -1
- package/src/__tests__/workspace-migration-018-rekey-compound-credential-keys.test.ts +181 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +5 -4
- package/src/acp/client-handler.ts +113 -31
- package/src/acp/session-manager.ts +29 -27
- package/src/approvals/guardian-request-resolvers.ts +1 -1
- package/src/cli/AGENTS.md +113 -0
- package/src/cli/commands/autonomy.ts +3 -5
- package/src/cli/commands/browser-relay.ts +2 -17
- package/src/cli/commands/contacts.ts +6 -4
- package/src/cli/commands/conversations.ts +13 -1
- package/src/cli/commands/credential-execution.ts +17 -3
- package/src/cli/commands/credentials.ts +2 -8
- package/src/cli/commands/memory.ts +2 -3
- package/src/cli/commands/oauth/__tests__/connect.test.ts +706 -0
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +686 -0
- package/src/cli/commands/oauth/__tests__/mode.test.ts +625 -0
- package/src/cli/commands/oauth/__tests__/ping.test.ts +631 -0
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +574 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +416 -0
- package/src/cli/commands/oauth/__tests__/status.test.ts +551 -0
- package/src/cli/commands/oauth/__tests__/token.test.ts +420 -0
- package/src/cli/commands/oauth/apps.ts +87 -50
- package/src/cli/commands/oauth/connect.ts +405 -0
- package/src/cli/commands/oauth/disconnect.ts +285 -0
- package/src/cli/commands/oauth/index.ts +62 -20
- package/src/cli/commands/oauth/mode.ts +251 -0
- package/src/cli/commands/oauth/ping.ts +196 -0
- package/src/cli/commands/oauth/providers.ts +589 -55
- package/src/cli/commands/oauth/request.ts +564 -0
- package/src/cli/commands/oauth/shared.ts +114 -0
- package/src/cli/commands/oauth/status.ts +191 -0
- package/src/cli/commands/oauth/token.ts +150 -0
- package/src/cli/commands/platform/connect.ts +104 -0
- package/src/cli/commands/platform/disconnect.ts +118 -0
- package/src/cli/commands/platform/index.ts +252 -0
- package/src/cli/commands/sequence.ts +5 -4
- package/src/cli/commands/shotgun.ts +16 -0
- package/src/cli/commands/skills.ts +173 -41
- package/src/cli/commands/usage.ts +5 -11
- package/src/cli/lib/daemon-credential-client.ts +22 -38
- package/src/cli/program.ts +1 -1
- package/src/cli.ts +82 -17
- package/src/config/assistant-feature-flags.ts +77 -18
- package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -1
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
- package/src/config/bundled-skills/conversations/SKILL.md +20 -0
- package/src/config/bundled-skills/conversations/TOOLS.json +23 -0
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +66 -0
- package/src/config/bundled-skills/gmail/SKILL.md +13 -13
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +3 -3
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +1 -1
- package/src/config/bundled-skills/google-calendar/SKILL.md +10 -4
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +19 -42
- package/src/config/bundled-skills/messaging/TOOLS.json +9 -9
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +1 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +5 -6
- package/src/config/bundled-skills/notifications/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/SKILL.md +2 -2
- package/src/config/bundled-skills/settings/SKILL.md +5 -3
- package/src/config/bundled-skills/settings/TOOLS.json +17 -0
- package/src/config/bundled-skills/settings/tools/avatar-get.ts +50 -0
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +7 -0
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +6 -1
- package/src/config/bundled-skills/settings/tools/identity-avatar.ts +55 -0
- package/src/config/bundled-skills/skills-catalog/SKILL.md +3 -3
- package/src/config/bundled-skills/slack/SKILL.md +58 -44
- package/src/config/bundled-tool-registry.ts +7 -19
- package/src/config/env.ts +5 -1
- package/src/config/feature-flag-registry.json +58 -42
- package/src/config/loader.ts +4 -0
- package/src/config/schemas/platform.ts +0 -8
- package/src/config/schemas/security.ts +9 -1
- package/src/config/schemas/services.ts +1 -1
- package/src/config/skill-state.ts +1 -3
- package/src/config/skills.ts +2 -4
- package/src/credential-execution/client.ts +1 -1
- package/src/credential-execution/feature-gates.ts +9 -16
- package/src/credential-execution/process-manager.ts +12 -0
- package/src/daemon/config-watcher.ts +4 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +10 -0
- package/src/daemon/conversation-agent-loop.ts +51 -2
- package/src/daemon/conversation-error.ts +36 -6
- package/src/daemon/conversation-memory.ts +0 -1
- package/src/daemon/conversation-messaging.ts +9 -0
- package/src/daemon/conversation-runtime-assembly.ts +33 -0
- package/src/daemon/conversation-surfaces.ts +120 -14
- package/src/daemon/conversation.ts +5 -0
- package/src/daemon/handlers/config-slack-channel.ts +43 -1
- package/src/daemon/handlers/conversations.ts +41 -33
- package/src/daemon/handlers/skills.ts +148 -3
- package/src/daemon/host-bash-proxy.ts +16 -0
- package/src/daemon/host-cu-proxy.ts +16 -0
- package/src/daemon/host-file-proxy.ts +16 -0
- package/src/daemon/lifecycle.ts +73 -3
- package/src/daemon/message-types/acp.ts +0 -15
- package/src/daemon/message-types/conversations.ts +1 -0
- package/src/daemon/message-types/guardian-actions.ts +2 -0
- package/src/daemon/message-types/host-bash.ts +6 -1
- package/src/daemon/message-types/host-cu.ts +6 -1
- package/src/daemon/message-types/host-file.ts +6 -1
- package/src/daemon/message-types/integrations.ts +0 -1
- package/src/daemon/message-types/memory.ts +0 -1
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/schedules.ts +9 -0
- package/src/daemon/server.ts +48 -9
- package/src/email/feature-gate.ts +3 -3
- package/src/heartbeat/heartbeat-service.ts +48 -0
- package/src/hooks/cli.ts +74 -0
- package/src/inbound/platform-callback-registration.ts +68 -19
- package/src/mcp/client.ts +6 -1
- package/src/mcp/manager.ts +2 -1
- package/src/mcp/mcp-oauth-provider.ts +3 -3
- package/src/memory/app-store.ts +3 -3
- package/src/memory/conversation-crud.ts +213 -0
- package/src/memory/conversation-key-store.ts +26 -0
- package/src/memory/conversation-title-service.ts +7 -17
- package/src/memory/db-init.ts +24 -0
- package/src/memory/embedding-local.ts +47 -2
- package/src/memory/indexer.ts +13 -10
- package/src/memory/items-extractor.ts +12 -4
- package/src/memory/job-utils.ts +5 -0
- package/src/memory/jobs-store.ts +10 -2
- package/src/memory/journal-memory.ts +6 -2
- package/src/memory/llm-request-log-store.ts +88 -21
- package/src/memory/memory-recall-log-store.ts +128 -0
- package/src/memory/migrations/194-memory-recall-logs.ts +50 -0
- package/src/memory/migrations/195-oauth-providers-ping-config.ts +23 -0
- package/src/memory/migrations/196-messages-conversation-created-at-index.ts +9 -0
- package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +186 -0
- package/src/memory/migrations/197-oauth-providers-behavior-columns.ts +29 -0
- package/src/memory/migrations/198-drop-setup-skill-id-column.ts +11 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/retriever.test.ts +4 -5
- package/src/memory/schema/infrastructure.ts +31 -0
- package/src/memory/schema/oauth.ts +14 -0
- package/src/messaging/provider.ts +13 -12
- package/src/messaging/providers/gmail/adapter.ts +44 -35
- package/src/messaging/providers/slack/adapter.ts +63 -33
- package/src/messaging/providers/telegram-bot/adapter.ts +7 -9
- package/src/messaging/providers/whatsapp/adapter.ts +6 -8
- package/src/notifications/adapters/telegram.ts +78 -2
- package/src/oauth/__tests__/identity-verifier.test.ts +464 -0
- package/src/oauth/byo-connection.test.ts +22 -24
- package/src/oauth/connect-orchestrator.ts +79 -64
- package/src/oauth/connect-types.ts +7 -65
- package/src/oauth/connection-resolver.test.ts +13 -13
- package/src/oauth/connection-resolver.ts +3 -4
- package/src/oauth/identity-verifier.ts +177 -0
- package/src/oauth/manual-token-connection.ts +5 -5
- package/src/oauth/oauth-store.ts +251 -5
- package/src/oauth/platform-connection.test.ts +56 -6
- package/src/oauth/platform-connection.ts +8 -1
- package/src/oauth/seed-providers.ts +256 -34
- package/src/permissions/checker.ts +129 -3
- package/src/permissions/trust-client.ts +2 -2
- package/src/platform/client.ts +2 -2
- package/src/prompts/journal-context.ts +6 -1
- package/src/prompts/system-prompt.ts +43 -9
- package/src/prompts/templates/BOOTSTRAP.md +16 -5
- package/src/providers/anthropic/client.ts +139 -28
- package/src/runtime/auth/__tests__/middleware.test.ts +19 -0
- package/src/runtime/auth/route-policy.ts +0 -1
- package/src/runtime/btw-sidechain.ts +7 -1
- package/src/runtime/channel-approvals.ts +2 -2
- package/src/runtime/channel-readiness-service.ts +30 -7
- package/src/runtime/guardian-action-service.ts +7 -2
- package/src/runtime/http-router.ts +31 -0
- package/src/runtime/http-server.ts +26 -7
- package/src/runtime/http-types.ts +9 -0
- package/src/runtime/pending-interactions.ts +21 -3
- package/src/runtime/routes/acp-routes.ts +46 -28
- package/src/runtime/routes/app-management-routes.ts +123 -0
- package/src/runtime/routes/app-routes.ts +31 -0
- package/src/runtime/routes/approval-routes.ts +108 -3
- package/src/runtime/routes/attachment-routes.ts +45 -0
- package/src/runtime/routes/avatar-routes.ts +16 -0
- package/src/runtime/routes/brain-graph-routes.ts +18 -0
- package/src/runtime/routes/btw-routes.ts +20 -0
- package/src/runtime/routes/call-routes.ts +81 -0
- package/src/runtime/routes/channel-readiness-routes.ts +48 -7
- package/src/runtime/routes/channel-routes.ts +18 -0
- package/src/runtime/routes/channel-verification-routes.ts +49 -1
- package/src/runtime/routes/contact-routes.ts +77 -0
- package/src/runtime/routes/conversation-attention-routes.ts +37 -0
- package/src/runtime/routes/conversation-management-routes.ts +125 -0
- package/src/runtime/routes/conversation-query-routes.ts +78 -0
- package/src/runtime/routes/conversation-routes.ts +191 -39
- package/src/runtime/routes/conversation-starter-routes.ts +29 -0
- package/src/runtime/routes/debug-routes.ts +23 -0
- package/src/runtime/routes/diagnostics-routes.ts +30 -0
- package/src/runtime/routes/documents-routes.ts +42 -0
- package/src/runtime/routes/events-routes.ts +10 -0
- package/src/runtime/routes/global-search-routes.ts +35 -0
- package/src/runtime/routes/guardian-action-routes.ts +61 -3
- package/src/runtime/routes/guardian-approval-prompt.ts +77 -2
- package/src/runtime/routes/heartbeat-routes.ts +278 -0
- package/src/runtime/routes/host-bash-routes.ts +16 -1
- package/src/runtime/routes/host-cu-routes.ts +23 -1
- package/src/runtime/routes/host-file-routes.ts +18 -1
- package/src/runtime/routes/identity-routes.ts +35 -0
- package/src/runtime/routes/inbound-message-handler.ts +46 -25
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -8
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +30 -2
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -2
- package/src/runtime/routes/integrations/slack/share.ts +1 -1
- package/src/runtime/routes/integrations/twilio.ts +32 -22
- package/src/runtime/routes/invite-routes.ts +83 -0
- package/src/runtime/routes/log-export-routes.ts +14 -0
- package/src/runtime/routes/memory-item-routes.ts +99 -1
- package/src/runtime/routes/migration-rollback-routes.ts +25 -0
- package/src/runtime/routes/migration-routes.ts +40 -0
- package/src/runtime/routes/notification-routes.ts +20 -0
- package/src/runtime/routes/oauth-apps.ts +13 -4
- package/src/runtime/routes/pairing-routes.ts +15 -0
- package/src/runtime/routes/recording-routes.ts +72 -0
- package/src/runtime/routes/schedule-routes.ts +77 -5
- package/src/runtime/routes/secret-routes.ts +99 -14
- package/src/runtime/routes/settings-routes.ts +102 -19
- package/src/runtime/routes/skills-routes.ts +141 -18
- package/src/runtime/routes/subagents-routes.ts +38 -3
- package/src/runtime/routes/surface-action-routes.ts +66 -24
- package/src/runtime/routes/surface-content-routes.ts +20 -0
- package/src/runtime/routes/telemetry-routes.ts +12 -0
- package/src/runtime/routes/trace-event-routes.ts +25 -0
- package/src/runtime/routes/trust-rules-routes.ts +46 -0
- package/src/runtime/routes/tts-routes.ts +15 -4
- package/src/runtime/routes/upgrade-broadcast-routes.ts +38 -0
- package/src/runtime/routes/usage-routes.ts +59 -0
- package/src/runtime/routes/watch-routes.ts +28 -0
- package/src/runtime/routes/work-items-routes.ts +59 -0
- package/src/runtime/routes/workspace-commit-routes.ts +12 -0
- package/src/runtime/routes/workspace-routes.ts +102 -0
- package/src/schedule/integration-status.ts +2 -2
- package/src/schedule/scheduler.ts +7 -1
- package/src/security/AGENTS.md +7 -0
- package/src/security/ces-rpc-credential-backend.ts +19 -16
- package/src/security/credential-backend.ts +1 -1
- package/src/security/encrypted-store.ts +3 -3
- package/src/security/oauth-completion-page.ts +153 -0
- package/src/security/oauth2.ts +58 -17
- package/src/security/secret-ingress.ts +174 -0
- package/src/security/secret-patterns.ts +133 -0
- package/src/security/secret-scanner.ts +28 -117
- package/src/security/secure-keys.ts +207 -7
- package/src/security/token-manager.ts +3 -6
- package/src/signals/bash.ts +6 -1
- package/src/signals/confirm.ts +12 -8
- package/src/signals/user-message.ts +18 -3
- package/src/skills/catalog-cache.ts +44 -0
- package/src/skills/catalog-search.ts +18 -0
- package/src/skills/skill-memory.ts +1 -2
- package/src/tasks/task-runner.ts +7 -1
- package/src/tools/credentials/broker.ts +1 -1
- package/src/tools/credentials/metadata-store.ts +1 -1
- package/src/tools/credentials/post-connect-hooks.ts +1 -1
- package/src/tools/credentials/vault.ts +36 -48
- package/src/tools/host-terminal/host-shell.ts +16 -3
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/memory/definitions.ts +1 -1
- package/src/tools/memory/handlers.test.ts +2 -4
- package/src/tools/skills/load.ts +1 -1
- package/src/tools/skills/sandbox-runner.ts +16 -3
- package/src/tools/terminal/safe-env.ts +7 -0
- package/src/tools/terminal/shell.ts +16 -3
- package/src/tools/tool-manifest.ts +1 -1
- package/src/util/log-redact.ts +9 -34
- package/src/util/logger.ts +11 -1
- package/src/util/sentry-log-stream.ts +51 -0
- package/src/watcher/providers/github.ts +2 -2
- package/src/watcher/providers/gmail.ts +1 -1
- package/src/watcher/providers/google-calendar.ts +1 -1
- package/src/watcher/providers/linear.ts +2 -2
- package/src/workspace/migrations/011-backfill-installation-id.ts +5 -3
- package/src/workspace/migrations/020-rename-oauth-skill-dirs.ts +119 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/docs/architecture/keychain-broker.md +0 -68
- package/src/cli/commands/oauth/connections.ts +0 -734
- package/src/cli/commands/oauth/platform.ts +0 -525
- package/src/cli/commands/platform.ts +0 -176
- package/src/config/bundled-skills/slack/TOOLS.json +0 -272
- package/src/config/bundled-skills/slack/tools/shared.ts +0 -34
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +0 -27
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +0 -38
- package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +0 -146
- package/src/config/bundled-skills/slack/tools/slack-configure-channels.ts +0 -105
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +0 -26
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +0 -27
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +0 -25
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +0 -372
- package/src/oauth/provider-behaviors.ts +0 -634
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { createServer, type Server } from "node:http";
|
|
2
|
+
|
|
3
|
+
import type { Command } from "commander";
|
|
4
|
+
|
|
5
|
+
import { orchestrateOAuthConnect } from "../../../oauth/connect-orchestrator.js";
|
|
6
|
+
import {
|
|
7
|
+
getAppByProviderAndClientId,
|
|
8
|
+
getMostRecentAppByProvider,
|
|
9
|
+
getProvider,
|
|
10
|
+
} from "../../../oauth/oauth-store.js";
|
|
11
|
+
import { renderOAuthCompletionPage } from "../../../security/oauth-completion-page.js";
|
|
12
|
+
import { openInBrowser } from "../../../util/browser.js";
|
|
13
|
+
import { getSecureKeyViaDaemon } from "../../lib/daemon-credential-client.js";
|
|
14
|
+
import { getCliLogger } from "../../logger.js";
|
|
15
|
+
import { shouldOutputJson, writeOutput } from "../../output.js";
|
|
16
|
+
import {
|
|
17
|
+
fetchActiveConnections,
|
|
18
|
+
isManagedMode,
|
|
19
|
+
requirePlatformClient,
|
|
20
|
+
} from "./shared.js";
|
|
21
|
+
|
|
22
|
+
const log = getCliLogger("cli");
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Start a temporary loopback server to serve a nice completion page after the
|
|
26
|
+
* platform redirects the user's browser following a managed OAuth flow.
|
|
27
|
+
* Returns the base URL and a cleanup function.
|
|
28
|
+
*/
|
|
29
|
+
function startManagedRedirectServer(provider: string): Promise<{
|
|
30
|
+
redirectUrl: string;
|
|
31
|
+
cleanup: () => void;
|
|
32
|
+
}> {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const server: Server = createServer((req, res) => {
|
|
35
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
36
|
+
const error = url.searchParams.get("error");
|
|
37
|
+
const errorDesc = url.searchParams.get("error_description");
|
|
38
|
+
const providerHint = url.searchParams.get("provider") ?? provider;
|
|
39
|
+
|
|
40
|
+
if (error) {
|
|
41
|
+
const message = errorDesc ?? error;
|
|
42
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
43
|
+
res.end(renderOAuthCompletionPage(message, false, providerHint));
|
|
44
|
+
} else {
|
|
45
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
46
|
+
res.end(
|
|
47
|
+
renderOAuthCompletionPage(
|
|
48
|
+
"You can close this tab and return to your assistant.",
|
|
49
|
+
true,
|
|
50
|
+
providerHint,
|
|
51
|
+
),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
server.listen(0, "localhost", () => {
|
|
57
|
+
const addr = server.address() as { port: number };
|
|
58
|
+
const redirectUrl = `http://localhost:${addr.port}/oauth/complete`;
|
|
59
|
+
resolve({
|
|
60
|
+
redirectUrl,
|
|
61
|
+
cleanup: () => server.close(),
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
server.on("error", (err) => {
|
|
66
|
+
reject(new Error(`Failed to start redirect server: ${err.message}`));
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Command registration
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
export function registerConnectCommand(oauth: Command): void {
|
|
76
|
+
oauth
|
|
77
|
+
.command("connect <provider>")
|
|
78
|
+
.description(
|
|
79
|
+
"Initiate an OAuth authorization flow for a specified provider",
|
|
80
|
+
)
|
|
81
|
+
.option("--scopes <scopes...>", "Scopes to request for the authorization")
|
|
82
|
+
.option(
|
|
83
|
+
"--open-browser",
|
|
84
|
+
"Open the auth URL in the browser and wait for completion",
|
|
85
|
+
)
|
|
86
|
+
.option("--client-id <id>", "BYO app client ID disambiguation")
|
|
87
|
+
.addHelpText(
|
|
88
|
+
"after",
|
|
89
|
+
`
|
|
90
|
+
Arguments:
|
|
91
|
+
provider Provider name (e.g. google, slack, notion).
|
|
92
|
+
Run 'assistant oauth providers list' to see available providers.
|
|
93
|
+
|
|
94
|
+
In managed mode, --scopes must be in the provider's allowed set (use full
|
|
95
|
+
scope URLs). In BYO mode, --scopes are appended to the provider's defaults.
|
|
96
|
+
The --open-browser flag polls for a platform connection (managed) or starts
|
|
97
|
+
a local callback server (BYO).
|
|
98
|
+
|
|
99
|
+
Examples:
|
|
100
|
+
$ assistant oauth connect google
|
|
101
|
+
$ assistant oauth connect google --open-browser
|
|
102
|
+
$ assistant oauth connect google --scopes https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/calendar.events
|
|
103
|
+
$ assistant oauth connect google --client-id abc123 --open-browser`,
|
|
104
|
+
)
|
|
105
|
+
.action(
|
|
106
|
+
async (
|
|
107
|
+
provider: string,
|
|
108
|
+
opts: {
|
|
109
|
+
scopes?: string[];
|
|
110
|
+
openBrowser?: boolean;
|
|
111
|
+
clientId?: string;
|
|
112
|
+
},
|
|
113
|
+
cmd: Command,
|
|
114
|
+
) => {
|
|
115
|
+
const jsonMode = shouldOutputJson(cmd);
|
|
116
|
+
|
|
117
|
+
// Helper: write an error and set exit code
|
|
118
|
+
const writeError = (error: string): void => {
|
|
119
|
+
writeOutput(cmd, { ok: false, error });
|
|
120
|
+
process.exitCode = 1;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
// ---------------------------------------------------------------
|
|
125
|
+
// 1. Validate provider exists
|
|
126
|
+
// ---------------------------------------------------------------
|
|
127
|
+
const providerRow = getProvider(provider);
|
|
128
|
+
if (!providerRow) {
|
|
129
|
+
writeError(
|
|
130
|
+
`Unknown provider "${provider}". ` +
|
|
131
|
+
`Run 'assistant oauth providers list' to see available providers.`,
|
|
132
|
+
);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ---------------------------------------------------------------
|
|
137
|
+
// 3. Detect mode
|
|
138
|
+
// ---------------------------------------------------------------
|
|
139
|
+
const managed = isManagedMode(provider);
|
|
140
|
+
|
|
141
|
+
if (managed) {
|
|
142
|
+
// =============================================================
|
|
143
|
+
// MANAGED PATH
|
|
144
|
+
// =============================================================
|
|
145
|
+
|
|
146
|
+
// Warn about --client-id being ignored in managed mode
|
|
147
|
+
if (opts.clientId) {
|
|
148
|
+
log.info(
|
|
149
|
+
`Warning: --client-id is ignored for platform-managed providers. The platform manages OAuth apps for "${provider}".`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const client = await requirePlatformClient(cmd);
|
|
154
|
+
if (!client) return;
|
|
155
|
+
|
|
156
|
+
// Call the platform's OAuth start endpoint
|
|
157
|
+
const startPath = `/v1/assistants/${encodeURIComponent(client.platformAssistantId)}/oauth/${encodeURIComponent(provider)}/start/`;
|
|
158
|
+
|
|
159
|
+
const body: Record<string, unknown> = {};
|
|
160
|
+
if (opts.scopes && opts.scopes.length > 0) {
|
|
161
|
+
body.requested_scopes = opts.scopes;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// When opening the browser, start a local server to show a nice
|
|
165
|
+
// completion page instead of redirecting to the platform website.
|
|
166
|
+
let redirectServer:
|
|
167
|
+
| { redirectUrl: string; cleanup: () => void }
|
|
168
|
+
| undefined;
|
|
169
|
+
if (opts.openBrowser) {
|
|
170
|
+
try {
|
|
171
|
+
redirectServer = await startManagedRedirectServer(provider);
|
|
172
|
+
body.redirect_after_connect = redirectServer.redirectUrl;
|
|
173
|
+
} catch {
|
|
174
|
+
// Non-fatal — fall back to platform default redirect
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const response = await client.fetch(startPath, {
|
|
180
|
+
method: "POST",
|
|
181
|
+
headers: { "Content-Type": "application/json" },
|
|
182
|
+
body: JSON.stringify(body),
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
const errorText = await response.text().catch(() => "");
|
|
187
|
+
writeError(
|
|
188
|
+
`Platform returned HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`,
|
|
189
|
+
);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const result = (await response.json()) as {
|
|
194
|
+
connect_url?: string;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
if (!result.connect_url) {
|
|
198
|
+
writeError(
|
|
199
|
+
"Platform did not return a connect URL — the OAuth flow could not be started",
|
|
200
|
+
);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (opts.openBrowser) {
|
|
205
|
+
// Snapshot existing connection IDs before opening browser
|
|
206
|
+
const snapshotEntries = await fetchActiveConnections(
|
|
207
|
+
client,
|
|
208
|
+
provider,
|
|
209
|
+
cmd,
|
|
210
|
+
);
|
|
211
|
+
if (!snapshotEntries) {
|
|
212
|
+
// fetchActiveConnections already wrote the error output
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const snapshotIds = new Set(snapshotEntries.map((e) => e.id));
|
|
216
|
+
|
|
217
|
+
openInBrowser(result.connect_url);
|
|
218
|
+
|
|
219
|
+
if (!jsonMode) {
|
|
220
|
+
log.info(
|
|
221
|
+
`Opening browser to connect ${provider}. Waiting for authorization...`,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Poll for a new connection every 2s for up to 5 minutes
|
|
226
|
+
const pollIntervalMs = 2000;
|
|
227
|
+
const timeoutMs = 5 * 60 * 1000;
|
|
228
|
+
const deadline = Date.now() + timeoutMs;
|
|
229
|
+
let newConnection: {
|
|
230
|
+
id: string;
|
|
231
|
+
account_label?: string;
|
|
232
|
+
scopes_granted?: string[];
|
|
233
|
+
} | null = null;
|
|
234
|
+
|
|
235
|
+
while (Date.now() < deadline) {
|
|
236
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
237
|
+
|
|
238
|
+
const currentEntries = await fetchActiveConnections(
|
|
239
|
+
client,
|
|
240
|
+
provider,
|
|
241
|
+
cmd,
|
|
242
|
+
{ silent: true },
|
|
243
|
+
);
|
|
244
|
+
if (!currentEntries) continue;
|
|
245
|
+
|
|
246
|
+
const found = currentEntries.find(
|
|
247
|
+
(e) => !snapshotIds.has(e.id),
|
|
248
|
+
);
|
|
249
|
+
if (found) {
|
|
250
|
+
newConnection = found;
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (newConnection) {
|
|
256
|
+
// Success — new connection found
|
|
257
|
+
if (jsonMode) {
|
|
258
|
+
writeOutput(cmd, {
|
|
259
|
+
ok: true,
|
|
260
|
+
provider: provider,
|
|
261
|
+
connectionId: newConnection.id,
|
|
262
|
+
accountLabel: newConnection.account_label ?? null,
|
|
263
|
+
scopesGranted: newConnection.scopes_granted ?? [],
|
|
264
|
+
});
|
|
265
|
+
} else {
|
|
266
|
+
const label = newConnection.account_label
|
|
267
|
+
? ` as ${newConnection.account_label}`
|
|
268
|
+
: "";
|
|
269
|
+
process.stdout.write(`Connected to ${provider}${label}\n`);
|
|
270
|
+
}
|
|
271
|
+
} else {
|
|
272
|
+
// Timeout — authorization may still be in progress
|
|
273
|
+
if (jsonMode) {
|
|
274
|
+
writeOutput(cmd, {
|
|
275
|
+
ok: true,
|
|
276
|
+
deferred: true,
|
|
277
|
+
provider: provider,
|
|
278
|
+
connectUrl: result.connect_url,
|
|
279
|
+
message:
|
|
280
|
+
"Authorization may still be in progress. Check with 'assistant oauth status <provider>'.",
|
|
281
|
+
});
|
|
282
|
+
} else {
|
|
283
|
+
process.stdout.write(
|
|
284
|
+
`Timed out waiting for authorization. It may still be in progress.\n` +
|
|
285
|
+
`Check with: assistant oauth status ${provider}\n`,
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
// No --open-browser: output the connect URL
|
|
291
|
+
if (jsonMode) {
|
|
292
|
+
writeOutput(cmd, {
|
|
293
|
+
ok: true,
|
|
294
|
+
deferred: true,
|
|
295
|
+
connectUrl: result.connect_url,
|
|
296
|
+
provider: provider,
|
|
297
|
+
});
|
|
298
|
+
} else {
|
|
299
|
+
process.stdout.write(result.connect_url + "\n");
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
} finally {
|
|
303
|
+
redirectServer?.cleanup();
|
|
304
|
+
}
|
|
305
|
+
} else {
|
|
306
|
+
// =============================================================
|
|
307
|
+
// BYO PATH
|
|
308
|
+
// =============================================================
|
|
309
|
+
|
|
310
|
+
// a. Resolve client credentials from the DB
|
|
311
|
+
const dbApp = opts.clientId
|
|
312
|
+
? getAppByProviderAndClientId(provider, opts.clientId)
|
|
313
|
+
: getMostRecentAppByProvider(provider);
|
|
314
|
+
|
|
315
|
+
let clientId = opts.clientId;
|
|
316
|
+
let clientSecret: string | undefined;
|
|
317
|
+
|
|
318
|
+
if (dbApp) {
|
|
319
|
+
if (!clientId) clientId = dbApp.clientId;
|
|
320
|
+
const storedSecret = await getSecureKeyViaDaemon(
|
|
321
|
+
dbApp.clientSecretCredentialPath,
|
|
322
|
+
);
|
|
323
|
+
if (storedSecret) clientSecret = storedSecret;
|
|
324
|
+
} else if (opts.clientId) {
|
|
325
|
+
// --client-id was explicitly provided but no matching app exists
|
|
326
|
+
writeError(
|
|
327
|
+
`No registered app found for "${provider}" with client ID "${opts.clientId}". ` +
|
|
328
|
+
`Register one with 'assistant oauth apps upsert'.`,
|
|
329
|
+
);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// c. Validate client_id exists
|
|
334
|
+
if (!clientId) {
|
|
335
|
+
writeError(
|
|
336
|
+
`No client_id found for "${provider}". ` +
|
|
337
|
+
`Register one with 'assistant oauth apps upsert'.`,
|
|
338
|
+
);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// d. Check if client_secret is required but missing
|
|
343
|
+
if (clientSecret === undefined) {
|
|
344
|
+
const requiresSecret = !!providerRow?.requiresClientSecret;
|
|
345
|
+
|
|
346
|
+
if (requiresSecret) {
|
|
347
|
+
writeError(
|
|
348
|
+
`client_secret is required for ${provider} but not found. ` +
|
|
349
|
+
`Store it with 'assistant oauth apps upsert --client-secret'.`,
|
|
350
|
+
);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// e. Call the orchestrator
|
|
356
|
+
const result = await orchestrateOAuthConnect({
|
|
357
|
+
service: provider,
|
|
358
|
+
clientId,
|
|
359
|
+
clientSecret,
|
|
360
|
+
isInteractive: !!opts.openBrowser,
|
|
361
|
+
openUrl: opts.openBrowser ? openInBrowser : undefined,
|
|
362
|
+
...(opts.scopes ? { requestedScopes: opts.scopes } : {}),
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// f. Handle results
|
|
366
|
+
if (!result.success) {
|
|
367
|
+
writeError(result.error ?? "OAuth connect failed");
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (result.deferred) {
|
|
372
|
+
if (jsonMode) {
|
|
373
|
+
writeOutput(cmd, {
|
|
374
|
+
ok: true,
|
|
375
|
+
deferred: true,
|
|
376
|
+
authUrl: result.authUrl,
|
|
377
|
+
service: result.service,
|
|
378
|
+
});
|
|
379
|
+
} else {
|
|
380
|
+
process.stdout.write(
|
|
381
|
+
`\nAuthorize with ${provider}:\n\n${result.authUrl}\n\nThe connection will complete automatically once you authorize.\n`,
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Interactive mode completed
|
|
388
|
+
if (jsonMode) {
|
|
389
|
+
writeOutput(cmd, {
|
|
390
|
+
ok: true,
|
|
391
|
+
grantedScopes: result.grantedScopes,
|
|
392
|
+
accountInfo: result.accountInfo,
|
|
393
|
+
});
|
|
394
|
+
} else {
|
|
395
|
+
const msg = `Connected to ${provider}${result.accountInfo ? ` as ${result.accountInfo}` : ""}`;
|
|
396
|
+
process.stdout.write(msg + "\n");
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
} catch (err) {
|
|
400
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
401
|
+
writeError(message);
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
);
|
|
405
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
disconnectOAuthProvider,
|
|
5
|
+
getActiveConnection,
|
|
6
|
+
getConnection,
|
|
7
|
+
getProvider,
|
|
8
|
+
listActiveConnectionsByProvider,
|
|
9
|
+
} from "../../../oauth/oauth-store.js";
|
|
10
|
+
import { getCliLogger } from "../../logger.js";
|
|
11
|
+
import { shouldOutputJson, writeOutput } from "../../output.js";
|
|
12
|
+
import {
|
|
13
|
+
fetchActiveConnections,
|
|
14
|
+
isManagedMode,
|
|
15
|
+
requirePlatformClient,
|
|
16
|
+
} from "./shared.js";
|
|
17
|
+
|
|
18
|
+
const log = getCliLogger("cli");
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Command registration
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
export function registerDisconnectCommand(oauth: Command): void {
|
|
25
|
+
oauth
|
|
26
|
+
.command("disconnect <provider>")
|
|
27
|
+
.description(
|
|
28
|
+
"Disconnect an OAuth provider and remove associated credentials",
|
|
29
|
+
)
|
|
30
|
+
.option(
|
|
31
|
+
"--account <identifier>",
|
|
32
|
+
"Account identifier to disconnect (e.g. email address)",
|
|
33
|
+
)
|
|
34
|
+
.option("--connection-id <id>", "Exact connection ID to disconnect")
|
|
35
|
+
.addHelpText(
|
|
36
|
+
"after",
|
|
37
|
+
`
|
|
38
|
+
Arguments:
|
|
39
|
+
provider Provider name (e.g. google, slack, notion).
|
|
40
|
+
Run 'assistant oauth providers list' to see available providers.
|
|
41
|
+
|
|
42
|
+
At most one of --account or --connection-id may be specified. Use the values
|
|
43
|
+
shown by 'assistant oauth status <provider>' to find the right identifier.
|
|
44
|
+
|
|
45
|
+
When a provider has multiple active connections and neither flag is given,
|
|
46
|
+
the command errors with a list of connections and a hint to disambiguate.
|
|
47
|
+
|
|
48
|
+
Examples:
|
|
49
|
+
$ assistant oauth disconnect google
|
|
50
|
+
$ assistant oauth disconnect google --account user@gmail.com
|
|
51
|
+
$ assistant oauth disconnect google --connection-id conn_abc123`,
|
|
52
|
+
)
|
|
53
|
+
.action(
|
|
54
|
+
async (
|
|
55
|
+
provider: string,
|
|
56
|
+
opts: { account?: string; connectionId?: string },
|
|
57
|
+
cmd: Command,
|
|
58
|
+
) => {
|
|
59
|
+
const jsonMode = shouldOutputJson(cmd);
|
|
60
|
+
|
|
61
|
+
// Helper: write an error and set exit code
|
|
62
|
+
const writeError = (
|
|
63
|
+
error: string,
|
|
64
|
+
extra?: Record<string, unknown>,
|
|
65
|
+
): void => {
|
|
66
|
+
writeOutput(cmd, { ok: false, error, ...extra });
|
|
67
|
+
process.exitCode = 1;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
// -------------------------------------------------------------------
|
|
72
|
+
// 1. Validate provider
|
|
73
|
+
// -------------------------------------------------------------------
|
|
74
|
+
const providerRow = getProvider(provider);
|
|
75
|
+
if (!providerRow) {
|
|
76
|
+
writeError(
|
|
77
|
+
`Unknown provider "${provider}".\n\n` +
|
|
78
|
+
`Run 'assistant oauth providers list' to see available providers.\n` +
|
|
79
|
+
`If this is a custom provider, register it first with 'assistant oauth providers register --help'.`,
|
|
80
|
+
);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// -------------------------------------------------------------------
|
|
85
|
+
// 2. Validate mutual exclusivity
|
|
86
|
+
// -------------------------------------------------------------------
|
|
87
|
+
if (opts.account && opts.connectionId) {
|
|
88
|
+
writeError(
|
|
89
|
+
`Cannot specify both --account and --connection-id. Use one or the other.\n\n` +
|
|
90
|
+
`Run 'assistant oauth status ${provider}' to see connected accounts and IDs.`,
|
|
91
|
+
);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// -------------------------------------------------------------------
|
|
96
|
+
// 3. Detect mode
|
|
97
|
+
// -------------------------------------------------------------------
|
|
98
|
+
const managed = isManagedMode(provider);
|
|
99
|
+
|
|
100
|
+
if (managed) {
|
|
101
|
+
// -----------------------------------------------------------------
|
|
102
|
+
// Managed path
|
|
103
|
+
// -----------------------------------------------------------------
|
|
104
|
+
const client = await requirePlatformClient(cmd);
|
|
105
|
+
if (!client) return;
|
|
106
|
+
|
|
107
|
+
const entries = await fetchActiveConnections(client, provider, cmd);
|
|
108
|
+
if (!entries) return;
|
|
109
|
+
|
|
110
|
+
let connectionId: string | undefined;
|
|
111
|
+
let accountLabel: string | undefined;
|
|
112
|
+
|
|
113
|
+
if (opts.account) {
|
|
114
|
+
// Filter connections by account_label matching the account value
|
|
115
|
+
const matching = entries.filter(
|
|
116
|
+
(c) => c.account_label === opts.account,
|
|
117
|
+
);
|
|
118
|
+
if (matching.length === 0) {
|
|
119
|
+
writeError(
|
|
120
|
+
`No active connection found for "${provider}" with account "${opts.account}".\n\n` +
|
|
121
|
+
`Run 'assistant oauth status ${provider}' to see connected accounts.`,
|
|
122
|
+
);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
connectionId = matching[0].id;
|
|
126
|
+
accountLabel = matching[0].account_label;
|
|
127
|
+
} else if (opts.connectionId) {
|
|
128
|
+
// Verify the supplied ID belongs to this provider
|
|
129
|
+
const match = entries.find((c) => c.id === opts.connectionId);
|
|
130
|
+
if (!match) {
|
|
131
|
+
writeError(
|
|
132
|
+
`Connection "${opts.connectionId}" is not an active ${provider} connection.\n\n` +
|
|
133
|
+
`Run 'assistant oauth status ${provider}' to see active connections.`,
|
|
134
|
+
);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
connectionId = match.id;
|
|
138
|
+
accountLabel = match.account_label;
|
|
139
|
+
} else {
|
|
140
|
+
// Neither specified — auto-resolve
|
|
141
|
+
if (entries.length === 0) {
|
|
142
|
+
writeError(
|
|
143
|
+
`No active connections found for "${provider}".\n\n` +
|
|
144
|
+
`Run 'assistant oauth status ${provider}' to check connection status.`,
|
|
145
|
+
);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (entries.length > 1) {
|
|
150
|
+
const connectionList = entries.map((c) => ({
|
|
151
|
+
id: c.id,
|
|
152
|
+
account: c.account_label ?? null,
|
|
153
|
+
}));
|
|
154
|
+
writeError(
|
|
155
|
+
`Multiple active connections for "${provider}". ` +
|
|
156
|
+
`Specify which one to disconnect with --account or --connection-id.\n\n` +
|
|
157
|
+
`Run 'assistant oauth status ${provider}' to see connected accounts and IDs.`,
|
|
158
|
+
{ connections: connectionList },
|
|
159
|
+
);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
connectionId = entries[0].id;
|
|
164
|
+
accountLabel = entries[0].account_label;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Call platform /disconnect/ endpoint
|
|
168
|
+
const disconnectPath = `/v1/assistants/${encodeURIComponent(client.platformAssistantId)}/oauth/connections/${encodeURIComponent(connectionId)}/disconnect/`;
|
|
169
|
+
const disconnectResponse = await client.fetch(disconnectPath, {
|
|
170
|
+
method: "POST",
|
|
171
|
+
headers: { "Content-Type": "application/json" },
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
if (!disconnectResponse.ok) {
|
|
175
|
+
const errorText = await disconnectResponse.text().catch(() => "");
|
|
176
|
+
writeError(
|
|
177
|
+
`Platform returned HTTP ${disconnectResponse.status}${errorText ? `: ${errorText}` : ""}`,
|
|
178
|
+
);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const result: Record<string, unknown> = {
|
|
183
|
+
ok: true,
|
|
184
|
+
provider: provider,
|
|
185
|
+
connectionId,
|
|
186
|
+
};
|
|
187
|
+
if (accountLabel) result.account = accountLabel;
|
|
188
|
+
writeOutput(cmd, result);
|
|
189
|
+
|
|
190
|
+
if (!jsonMode) {
|
|
191
|
+
log.info(`Disconnected ${provider} connection ${connectionId}`);
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
// -----------------------------------------------------------------
|
|
195
|
+
// BYO path
|
|
196
|
+
// -----------------------------------------------------------------
|
|
197
|
+
let connectionId: string | undefined;
|
|
198
|
+
let accountLabel: string | undefined;
|
|
199
|
+
|
|
200
|
+
if (opts.account) {
|
|
201
|
+
const conn = getActiveConnection(provider, {
|
|
202
|
+
account: opts.account,
|
|
203
|
+
});
|
|
204
|
+
if (!conn) {
|
|
205
|
+
writeError(
|
|
206
|
+
`No active connection found for "${provider}" with account "${opts.account}".\n\n` +
|
|
207
|
+
`Run 'assistant oauth status ${provider}' to see connected accounts.`,
|
|
208
|
+
);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
connectionId = conn.id;
|
|
212
|
+
accountLabel = conn.accountInfo ?? undefined;
|
|
213
|
+
} else if (opts.connectionId) {
|
|
214
|
+
const conn = getConnection(opts.connectionId);
|
|
215
|
+
if (!conn || conn.providerKey !== provider) {
|
|
216
|
+
writeError(
|
|
217
|
+
`Connection "${opts.connectionId}" is not an active ${provider} connection.\n\n` +
|
|
218
|
+
`Run 'assistant oauth status ${provider}' to see active connections.`,
|
|
219
|
+
);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
connectionId = conn.id;
|
|
223
|
+
accountLabel = conn.accountInfo ?? undefined;
|
|
224
|
+
} else {
|
|
225
|
+
// Neither specified — auto-resolve
|
|
226
|
+
const active = listActiveConnectionsByProvider(provider);
|
|
227
|
+
|
|
228
|
+
if (active.length === 0) {
|
|
229
|
+
writeError(
|
|
230
|
+
`No active connections found for "${provider}".\n\n` +
|
|
231
|
+
`Run 'assistant oauth status ${provider}' to check connection status.`,
|
|
232
|
+
);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (active.length > 1) {
|
|
237
|
+
const connectionList = active.map((c) => ({
|
|
238
|
+
id: c.id,
|
|
239
|
+
account: c.accountInfo ?? null,
|
|
240
|
+
}));
|
|
241
|
+
writeError(
|
|
242
|
+
`Multiple active connections for "${provider}". ` +
|
|
243
|
+
`Specify which one to disconnect with --account or --connection-id.\n\n` +
|
|
244
|
+
`Run 'assistant oauth status ${provider}' to see connected accounts and IDs.`,
|
|
245
|
+
{ connections: connectionList },
|
|
246
|
+
);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
connectionId = active[0].id;
|
|
251
|
+
accountLabel = active[0].accountInfo ?? undefined;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Disconnect the OAuth connection (tokens + connection row)
|
|
255
|
+
const oauthResult = await disconnectOAuthProvider(
|
|
256
|
+
provider,
|
|
257
|
+
undefined,
|
|
258
|
+
connectionId,
|
|
259
|
+
);
|
|
260
|
+
if (oauthResult === "error") {
|
|
261
|
+
writeError(
|
|
262
|
+
`Failed to disconnect OAuth provider "${provider}" — please try again.`,
|
|
263
|
+
);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const result: Record<string, unknown> = {
|
|
268
|
+
ok: true,
|
|
269
|
+
provider: provider,
|
|
270
|
+
connectionId,
|
|
271
|
+
};
|
|
272
|
+
if (accountLabel) result.account = accountLabel;
|
|
273
|
+
writeOutput(cmd, result);
|
|
274
|
+
|
|
275
|
+
if (!jsonMode) {
|
|
276
|
+
log.info(`Disconnected ${provider} connection ${connectionId}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
} catch (err) {
|
|
280
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
281
|
+
writeError(message);
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
);
|
|
285
|
+
}
|