@vellumai/assistant 0.4.48 → 0.4.50
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 +26 -35
- package/README.md +5 -26
- package/docs/architecture/integrations.md +45 -41
- package/docs/architecture/keychain-broker.md +3 -3
- package/docs/architecture/memory.md +180 -119
- 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 +2 -2
- package/src/__tests__/actor-token-service.test.ts +0 -1
- package/src/__tests__/agent-loop.test.ts +3 -1
- package/src/__tests__/anthropic-provider.test.ts +249 -2
- package/src/__tests__/approval-cascade.test.ts +796 -0
- package/src/__tests__/approval-primitive.test.ts +0 -1
- package/src/__tests__/approval-routes-http.test.ts +4 -0
- package/src/__tests__/assistant-attachments.test.ts +12 -34
- package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
- 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-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/canonical-guardian-store.test.ts +95 -0
- package/src/__tests__/channel-guardian.test.ts +0 -2
- package/src/__tests__/channel-readiness-routes.test.ts +15 -6
- package/src/__tests__/channel-readiness-service.test.ts +10 -9
- package/src/__tests__/checker.test.ts +13 -20
- 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-schema.test.ts +1 -68
- 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-memory-e2e.test.ts +11 -100
- 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 +152 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -0
- package/src/__tests__/credential-metadata-store.test.ts +64 -73
- package/src/__tests__/credential-security-e2e.test.ts +1 -0
- package/src/__tests__/credential-security-invariants.test.ts +13 -7
- package/src/__tests__/credential-vault-unit.test.ts +284 -49
- package/src/__tests__/credential-vault.test.ts +150 -16
- package/src/__tests__/credentials-cli.test.ts +71 -0
- package/src/__tests__/cu-unified-flow.test.ts +532 -0
- package/src/__tests__/date-context.test.ts +93 -77
- package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
- 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-guard.test.ts +0 -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-routing-invariants.test.ts +93 -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__/history-repair.test.ts +245 -0
- package/src/__tests__/host-cu-proxy.test.ts +791 -0
- package/src/__tests__/host-shell-tool.test.ts +27 -15
- package/src/__tests__/http-user-message-parity.test.ts +2 -0
- package/src/__tests__/ingress-url-consistency.test.ts +14 -21
- package/src/__tests__/integration-status.test.ts +32 -51
- package/src/__tests__/intent-routing.test.ts +0 -1
- package/src/__tests__/invite-redemption-service.test.ts +65 -1
- package/src/__tests__/invite-routes-http.test.ts +10 -9
- package/src/__tests__/keychain-broker-client.test.ts +14 -46
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
- package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
- package/src/__tests__/memory-recall-quality.test.ts +244 -407
- package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
- package/src/__tests__/memory-regressions.test.ts +477 -2841
- package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
- package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
- package/src/__tests__/mime-builder.test.ts +28 -0
- package/src/__tests__/native-web-search.test.ts +1 -0
- package/src/__tests__/notification-routing-intent.test.ts +0 -1
- package/src/__tests__/oauth-cli.test.ts +941 -15
- 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 +870 -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-streaming.benchmark.test.ts +0 -1
- package/src/__tests__/public-ingress-urls.test.ts +15 -21
- package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
- package/src/__tests__/recording-handler.test.ts +3 -4
- package/src/__tests__/registry.test.ts +2 -3
- package/src/__tests__/relay-server.test.ts +46 -1
- package/src/__tests__/runtime-events-sse.test.ts +55 -7
- package/src/__tests__/schedule-store.test.ts +0 -1
- package/src/__tests__/schedule-tools.test.ts +32 -0
- package/src/__tests__/scheduler-recurrence.test.ts +0 -1
- 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-certs.test.ts +1 -1
- package/src/__tests__/secret-ingress-handler.test.ts +0 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -0
- package/src/__tests__/secure-keys.test.ts +7 -2
- package/src/__tests__/send-endpoint-busy.test.ts +24 -6
- package/src/__tests__/sequence-store.test.ts +0 -1
- package/src/__tests__/session-abort-tool-results.test.ts +1 -14
- package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
- package/src/__tests__/session-agent-loop.test.ts +19 -15
- package/src/__tests__/session-confirmation-signals.test.ts +1 -15
- package/src/__tests__/session-error.test.ts +124 -2
- package/src/__tests__/session-history-web-search.test.ts +918 -0
- package/src/__tests__/session-init.benchmark.test.ts +4 -5
- package/src/__tests__/session-pre-run-repair.test.ts +1 -14
- package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
- package/src/__tests__/session-queue.test.ts +37 -27
- package/src/__tests__/session-runtime-assembly.test.ts +54 -0
- package/src/__tests__/session-slash-known.test.ts +1 -15
- package/src/__tests__/session-slash-queue.test.ts +1 -15
- package/src/__tests__/session-slash-unknown.test.ts +1 -15
- package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
- package/src/__tests__/session-workspace-injection.test.ts +3 -37
- package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
- 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-install-extract.test.ts +93 -0
- package/src/__tests__/skills-uninstall.test.ts +1 -1
- package/src/__tests__/skills.test.ts +3 -3
- package/src/__tests__/skillssh-registry.test.ts +451 -0
- package/src/__tests__/slack-channel-config.test.ts +67 -3
- package/src/__tests__/slack-share-routes.test.ts +17 -19
- package/src/__tests__/system-prompt.test.ts +0 -1
- 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 +7 -13
- 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-routes.test.ts +0 -16
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
- package/src/__tests__/voice-invite-redemption.test.ts +32 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/agent/ax-tree-compaction.test.ts +286 -0
- package/src/agent/loop.ts +104 -131
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-request-resolvers.ts +14 -2
- package/src/bundler/compiler-tools.ts +66 -2
- package/src/calls/call-domain.ts +133 -6
- package/src/calls/call-store.ts +6 -0
- package/src/calls/relay-server.ts +52 -18
- package/src/calls/relay-setup-router.ts +17 -1
- package/src/calls/twilio-config.ts +3 -8
- package/src/calls/twilio-routes.ts +1 -2
- package/src/calls/types.ts +3 -1
- package/src/calls/voice-ingress-preflight.ts +1 -1
- package/src/cli/commands/browser-relay.ts +18 -12
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/credentials.ts +101 -15
- package/src/cli/commands/doctor.ts +4 -3
- package/src/cli/commands/mcp.ts +46 -59
- package/src/cli/commands/memory.ts +16 -165
- package/src/cli/commands/oauth/apps.ts +284 -0
- package/src/cli/commands/oauth/connections.ts +633 -0
- package/src/cli/commands/oauth/index.ts +52 -0
- package/src/cli/commands/oauth/providers.ts +256 -0
- package/src/cli/commands/sessions.ts +5 -2
- package/src/cli/commands/skills.ts +177 -339
- package/src/cli/http-client.ts +0 -20
- package/src/cli/main-screen.tsx +2 -2
- package/src/cli/program.ts +6 -11
- package/src/cli/reference.ts +1 -3
- package/src/cli.ts +4 -10
- 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/computer-use/SKILL.md +3 -6
- package/src/config/bundled-skills/computer-use/TOOLS.json +23 -5
- package/src/config/bundled-skills/computer-use/tools/{computer-use-request-control.ts → computer-use-observe.ts} +1 -5
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +21 -16
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -4
- 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-tool-registry.ts +2 -5
- 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/loader.ts +0 -6
- package/src/config/schema.ts +4 -13
- package/src/config/schemas/memory-lifecycle.ts +0 -9
- package/src/config/schemas/memory-processing.ts +0 -180
- package/src/config/schemas/memory-retrieval.ts +32 -104
- package/src/config/schemas/memory.ts +0 -10
- package/src/config/skills.ts +21 -2
- package/src/config/types.ts +0 -4
- package/src/context/image-dimensions.ts +229 -0
- package/src/context/token-estimator.ts +75 -12
- package/src/context/window-manager.ts +53 -11
- package/src/daemon/assistant-attachments.ts +1 -13
- package/src/daemon/config-watcher.ts +61 -3
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/date-context.ts +114 -31
- package/src/daemon/handlers/config-ingress.ts +8 -33
- package/src/daemon/handlers/config-slack-channel.ts +49 -46
- package/src/daemon/handlers/config-telegram.ts +32 -16
- package/src/daemon/handlers/sessions.ts +27 -36
- package/src/daemon/handlers/shared.ts +0 -130
- package/src/daemon/handlers/skills.ts +20 -1
- package/src/daemon/history-repair.ts +72 -8
- package/src/daemon/host-cu-proxy.ts +430 -0
- package/src/daemon/lifecycle.ts +67 -71
- package/src/daemon/mcp-reload-service.ts +2 -2
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/computer-use.ts +1 -129
- package/src/daemon/message-types/host-cu.ts +19 -0
- package/src/daemon/message-types/memory.ts +4 -16
- package/src/daemon/message-types/messages.ts +4 -0
- package/src/daemon/message-types/sessions.ts +4 -0
- package/src/daemon/server.ts +25 -21
- package/src/daemon/session-agent-loop-handlers.ts +40 -0
- package/src/daemon/session-agent-loop.ts +334 -48
- package/src/daemon/session-attachments.ts +1 -2
- package/src/daemon/session-error.ts +89 -6
- package/src/daemon/session-history.ts +17 -7
- package/src/daemon/session-media-retry.ts +6 -2
- package/src/daemon/session-memory.ts +69 -149
- package/src/daemon/session-process.ts +10 -1
- package/src/daemon/session-runtime-assembly.ts +49 -19
- package/src/daemon/session-slash.ts +1 -1
- package/src/daemon/session-surfaces.ts +43 -28
- package/src/daemon/session-tool-setup.ts +9 -10
- package/src/daemon/session.ts +150 -17
- package/src/daemon/tool-side-effects.ts +2 -8
- package/src/daemon/watch-handler.ts +2 -2
- 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 +61 -1
- package/src/logfire.ts +16 -5
- package/src/memory/admin.ts +2 -191
- package/src/memory/canonical-guardian-store.ts +38 -2
- package/src/memory/conversation-crud.ts +0 -33
- package/src/memory/conversation-key-store.ts +21 -0
- package/src/memory/conversation-queries.ts +22 -3
- package/src/memory/db-init.ts +32 -0
- package/src/memory/embedding-backend.ts +84 -8
- package/src/memory/embedding-types.ts +9 -1
- package/src/memory/indexer.ts +7 -46
- package/src/memory/items-extractor.ts +274 -76
- package/src/memory/job-handlers/backfill.ts +2 -127
- package/src/memory/job-handlers/cleanup.ts +2 -16
- package/src/memory/job-handlers/extraction.ts +2 -138
- package/src/memory/job-handlers/index-maintenance.ts +1 -6
- package/src/memory/job-handlers/summarization.ts +3 -148
- package/src/memory/job-utils.ts +21 -59
- package/src/memory/jobs-store.ts +1 -159
- package/src/memory/jobs-worker.ts +9 -52
- package/src/memory/migrations/104-core-indexes.ts +3 -3
- package/src/memory/migrations/149-oauth-tables.ts +62 -0
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
- package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
- package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
- package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
- package/src/memory/migrations/154-drop-fts.ts +20 -0
- package/src/memory/migrations/155-drop-conflicts.ts +7 -0
- package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
- package/src/memory/migrations/index.ts +8 -0
- package/src/memory/qdrant-client.ts +148 -51
- package/src/memory/raw-query.ts +1 -1
- package/src/memory/retriever.test.ts +294 -273
- package/src/memory/retriever.ts +421 -645
- package/src/memory/schema/calls.ts +2 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/memory-core.ts +3 -48
- package/src/memory/schema/oauth.ts +67 -0
- package/src/memory/search/formatting.ts +263 -176
- package/src/memory/search/lexical.ts +1 -254
- package/src/memory/search/ranking.ts +0 -455
- package/src/memory/search/semantic.ts +100 -14
- package/src/memory/search/staleness.ts +47 -0
- package/src/memory/search/tier-classifier.ts +21 -0
- package/src/memory/search/types.ts +15 -77
- package/src/memory/task-memory-cleanup.ts +4 -6
- package/src/messaging/provider.ts +4 -4
- package/src/messaging/providers/gmail/client.ts +82 -2
- package/src/messaging/providers/gmail/mime-builder.ts +17 -7
- package/src/messaging/providers/gmail/people-client.ts +10 -10
- package/src/messaging/providers/telegram-bot/adapter.ts +17 -17
- package/src/messaging/providers/whatsapp/adapter.ts +11 -8
- package/src/messaging/registry.ts +2 -32
- package/src/notifications/copy-composer.ts +0 -5
- package/src/notifications/signal.ts +4 -5
- package/src/oauth/byo-connection.test.ts +133 -25
- package/src/oauth/byo-connection.ts +22 -6
- package/src/oauth/connect-orchestrator.ts +113 -57
- package/src/oauth/connect-types.ts +17 -23
- package/src/oauth/connection-resolver.ts +35 -11
- package/src/oauth/connection.ts +1 -1
- package/src/oauth/manual-token-connection.ts +104 -0
- package/src/oauth/oauth-store.ts +582 -0
- package/src/oauth/platform-connection.test.ts +29 -0
- package/src/oauth/platform-connection.ts +6 -5
- package/src/oauth/provider-behaviors.ts +124 -0
- package/src/oauth/scope-policy.ts +9 -2
- package/src/oauth/seed-providers.ts +167 -0
- package/src/oauth/token-persistence.ts +81 -77
- package/src/permissions/checker.ts +3 -3
- package/src/permissions/defaults.ts +1 -1
- package/src/permissions/prompter.ts +10 -1
- package/src/permissions/trust-store.ts +36 -1
- package/src/playbooks/playbook-compiler.ts +1 -1
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
- package/src/prompts/system-prompt.ts +46 -42
- package/src/providers/anthropic/client.ts +59 -20
- package/src/providers/retry.ts +1 -27
- package/src/providers/types.ts +7 -1
- package/src/runtime/AGENTS.md +9 -0
- package/src/runtime/auth/route-policy.ts +6 -6
- package/src/runtime/channel-reply-delivery.ts +0 -40
- package/src/runtime/gateway-client.ts +0 -7
- package/src/runtime/guardian-reply-router.ts +24 -22
- package/src/runtime/http-server.ts +10 -8
- package/src/runtime/http-types.ts +2 -2
- package/src/runtime/invite-redemption-service.ts +19 -1
- package/src/runtime/invite-service.ts +25 -0
- package/src/runtime/middleware/twilio-validation.ts +1 -11
- package/src/runtime/pending-interactions.ts +14 -12
- package/src/runtime/routes/brain-graph-routes.ts +10 -90
- package/src/runtime/routes/channel-delivery-routes.ts +0 -1
- package/src/runtime/routes/conversation-routes.ts +81 -19
- package/src/runtime/routes/events-routes.ts +21 -11
- package/src/runtime/routes/host-cu-routes.ts +97 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
- package/src/runtime/routes/integrations/slack/share.ts +6 -7
- package/src/runtime/routes/log-export-routes.ts +126 -8
- package/src/runtime/routes/memory-item-routes.test.ts +754 -0
- package/src/runtime/routes/memory-item-routes.ts +503 -0
- package/src/runtime/routes/session-management-routes.ts +3 -3
- package/src/runtime/routes/settings-routes.ts +55 -48
- package/src/runtime/routes/surface-action-routes.ts +1 -1
- package/src/runtime/routes/trust-rules-routes.ts +14 -0
- package/src/runtime/routes/watch-routes.ts +128 -0
- package/src/runtime/routes/workspace-routes.ts +2 -1
- package/src/schedule/integration-status.ts +10 -9
- package/src/security/credential-key.ts +0 -156
- package/src/security/keychain-broker-client.ts +22 -10
- package/src/security/oauth2.ts +1 -1
- package/src/security/secure-keys.ts +25 -3
- package/src/security/token-manager.ts +137 -64
- package/src/skills/catalog-install.ts +414 -0
- package/src/skills/include-graph.ts +32 -0
- package/src/skills/skillssh-registry.ts +503 -0
- package/src/telegram/bot-username.ts +2 -3
- package/src/tools/assets/search.ts +5 -1
- package/src/tools/browser/network-recorder.ts +1 -1
- package/src/tools/browser/network-recording-types.ts +1 -1
- package/src/tools/computer-use/definitions.ts +36 -11
- package/src/tools/computer-use/registry.ts +5 -6
- package/src/tools/credentials/broker.ts +1 -2
- package/src/tools/credentials/metadata-store.ts +17 -121
- package/src/tools/credentials/vault.ts +92 -167
- package/src/tools/memory/definitions.ts +4 -13
- package/src/tools/memory/handlers.test.ts +83 -103
- package/src/tools/memory/handlers.ts +50 -85
- package/src/tools/registry.ts +2 -7
- package/src/tools/schedule/create.ts +8 -1
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/skills/load.ts +85 -3
- 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/watcher/providers/google-calendar.ts +2 -1
- package/src/__tests__/clarification-resolver.test.ts +0 -193
- 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__/conflict-intent-tokenization.test.ts +0 -160
- package/src/__tests__/conflict-policy.test.ts +0 -269
- package/src/__tests__/conflict-store.test.ts +0 -372
- package/src/__tests__/contradiction-checker.test.ts +0 -361
- package/src/__tests__/entity-extractor.test.ts +0 -211
- package/src/__tests__/entity-search.test.ts +0 -1117
- package/src/__tests__/profile-compiler.test.ts +0 -392
- package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
- package/src/__tests__/session-conflict-gate.test.ts +0 -1228
- package/src/__tests__/session-profile-injection.test.ts +0 -557
- 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/knowledge-graph/SKILL.md +0 -25
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
- package/src/daemon/computer-use-session.ts +0 -1026
- package/src/daemon/ride-shotgun-handler.ts +0 -569
- package/src/daemon/session-conflict-gate.ts +0 -167
- package/src/daemon/session-dynamic-profile.ts +0 -77
- package/src/memory/clarification-resolver.ts +0 -417
- package/src/memory/conflict-intent.ts +0 -205
- package/src/memory/conflict-policy.ts +0 -127
- package/src/memory/conflict-store.ts +0 -410
- package/src/memory/contradiction-checker.ts +0 -508
- package/src/memory/entity-extractor.ts +0 -535
- package/src/memory/format-recall.ts +0 -47
- package/src/memory/fts-reconciler.ts +0 -165
- package/src/memory/job-handlers/conflict.ts +0 -200
- package/src/memory/profile-compiler.ts +0 -195
- package/src/memory/recall-cache.ts +0 -117
- package/src/memory/search/entity.ts +0 -535
- package/src/memory/search/query-expansion.test.ts +0 -70
- package/src/memory/search/query-expansion.ts +0 -118
- package/src/oauth/provider-base-urls.ts +0 -21
- 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/routes/mcp-routes.ts +0 -20
- package/src/runtime/telegram-streaming-delivery.test.ts +0 -729
- package/src/runtime/telegram-streaming-delivery.ts +0 -393
- package/src/tools/computer-use/request-computer-control.ts +0 -56
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRUD store for OAuth providers, apps, and connections.
|
|
3
|
+
*
|
|
4
|
+
* Backed by Drizzle + SQLite. All JSON fields (default_scopes, scope_policy,
|
|
5
|
+
* extra_params, granted_scopes, metadata) are stored as serialized JSON strings.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { and, desc, eq, sql } from "drizzle-orm";
|
|
9
|
+
import { v4 as uuid } from "uuid";
|
|
10
|
+
|
|
11
|
+
import { getDb, rawChanges } from "../memory/db.js";
|
|
12
|
+
import {
|
|
13
|
+
oauthApps,
|
|
14
|
+
oauthConnections,
|
|
15
|
+
oauthProviders,
|
|
16
|
+
} from "../memory/schema/oauth.js";
|
|
17
|
+
import {
|
|
18
|
+
deleteSecureKeyAsync,
|
|
19
|
+
getSecureKey,
|
|
20
|
+
getSecureKeyAsync,
|
|
21
|
+
setSecureKeyAsync,
|
|
22
|
+
} from "../security/secure-keys.js";
|
|
23
|
+
import { getLogger } from "../util/logger.js";
|
|
24
|
+
|
|
25
|
+
const log = getLogger("oauth-store");
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Row types
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
export type OAuthProviderRow = typeof oauthProviders.$inferSelect;
|
|
32
|
+
export type OAuthAppRow = typeof oauthApps.$inferSelect;
|
|
33
|
+
export type OAuthConnectionRow = typeof oauthConnections.$inferSelect;
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Provider operations
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Seed well-known provider profiles into the database. Uses INSERT … ON
|
|
41
|
+
* CONFLICT DO UPDATE so that corrections to seed data (e.g. a fixed baseUrl)
|
|
42
|
+
* propagate to existing installations on the next startup.
|
|
43
|
+
*/
|
|
44
|
+
export function seedProviders(
|
|
45
|
+
profiles: Array<{
|
|
46
|
+
providerKey: string;
|
|
47
|
+
authUrl: string;
|
|
48
|
+
tokenUrl: string;
|
|
49
|
+
tokenEndpointAuthMethod?: string;
|
|
50
|
+
userinfoUrl?: string;
|
|
51
|
+
pingUrl?: string;
|
|
52
|
+
baseUrl?: string;
|
|
53
|
+
defaultScopes: string[];
|
|
54
|
+
scopePolicy: Record<string, unknown>;
|
|
55
|
+
extraParams?: Record<string, string>;
|
|
56
|
+
callbackTransport?: string;
|
|
57
|
+
loopbackPort?: number;
|
|
58
|
+
}>,
|
|
59
|
+
): void {
|
|
60
|
+
const db = getDb();
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
for (const p of profiles) {
|
|
63
|
+
const authUrl = p.authUrl;
|
|
64
|
+
const tokenUrl = p.tokenUrl;
|
|
65
|
+
const tokenEndpointAuthMethod = p.tokenEndpointAuthMethod ?? null;
|
|
66
|
+
const userinfoUrl = p.userinfoUrl ?? null;
|
|
67
|
+
const pingUrl = p.pingUrl ?? null;
|
|
68
|
+
const baseUrl = p.baseUrl ?? null;
|
|
69
|
+
const defaultScopes = JSON.stringify(p.defaultScopes);
|
|
70
|
+
const scopePolicy = JSON.stringify(p.scopePolicy);
|
|
71
|
+
const extraParams = p.extraParams ? JSON.stringify(p.extraParams) : null;
|
|
72
|
+
const callbackTransport = p.callbackTransport ?? null;
|
|
73
|
+
const loopbackPort = p.loopbackPort ?? null;
|
|
74
|
+
|
|
75
|
+
db.insert(oauthProviders)
|
|
76
|
+
.values({
|
|
77
|
+
providerKey: p.providerKey,
|
|
78
|
+
authUrl,
|
|
79
|
+
tokenUrl,
|
|
80
|
+
tokenEndpointAuthMethod,
|
|
81
|
+
userinfoUrl,
|
|
82
|
+
baseUrl,
|
|
83
|
+
defaultScopes,
|
|
84
|
+
scopePolicy,
|
|
85
|
+
extraParams,
|
|
86
|
+
callbackTransport,
|
|
87
|
+
loopbackPort,
|
|
88
|
+
pingUrl,
|
|
89
|
+
createdAt: now,
|
|
90
|
+
updatedAt: now,
|
|
91
|
+
})
|
|
92
|
+
.onConflictDoUpdate({
|
|
93
|
+
target: oauthProviders.providerKey,
|
|
94
|
+
set: {
|
|
95
|
+
authUrl,
|
|
96
|
+
tokenUrl,
|
|
97
|
+
tokenEndpointAuthMethod,
|
|
98
|
+
userinfoUrl,
|
|
99
|
+
baseUrl,
|
|
100
|
+
defaultScopes,
|
|
101
|
+
scopePolicy,
|
|
102
|
+
extraParams,
|
|
103
|
+
callbackTransport,
|
|
104
|
+
loopbackPort,
|
|
105
|
+
pingUrl,
|
|
106
|
+
updatedAt: now,
|
|
107
|
+
},
|
|
108
|
+
})
|
|
109
|
+
.run();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Look up a provider by its primary key. */
|
|
114
|
+
export function getProvider(providerKey: string): OAuthProviderRow | undefined {
|
|
115
|
+
const db = getDb();
|
|
116
|
+
return db
|
|
117
|
+
.select()
|
|
118
|
+
.from(oauthProviders)
|
|
119
|
+
.where(eq(oauthProviders.providerKey, providerKey))
|
|
120
|
+
.get();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Return all registered providers. */
|
|
124
|
+
export function listProviders(): OAuthProviderRow[] {
|
|
125
|
+
const db = getDb();
|
|
126
|
+
return db.select().from(oauthProviders).all();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Register a new provider (for dynamic registration). Throws if the
|
|
131
|
+
* provider_key already exists.
|
|
132
|
+
*/
|
|
133
|
+
export function registerProvider(params: {
|
|
134
|
+
providerKey: string;
|
|
135
|
+
authUrl: string;
|
|
136
|
+
tokenUrl: string;
|
|
137
|
+
tokenEndpointAuthMethod?: string;
|
|
138
|
+
userinfoUrl?: string;
|
|
139
|
+
pingUrl?: string;
|
|
140
|
+
baseUrl?: string;
|
|
141
|
+
defaultScopes: string[];
|
|
142
|
+
scopePolicy: Record<string, unknown>;
|
|
143
|
+
extraParams?: Record<string, string>;
|
|
144
|
+
callbackTransport?: string;
|
|
145
|
+
loopbackPort?: number;
|
|
146
|
+
}): OAuthProviderRow {
|
|
147
|
+
const db = getDb();
|
|
148
|
+
const now = Date.now();
|
|
149
|
+
|
|
150
|
+
const existing = getProvider(params.providerKey);
|
|
151
|
+
if (existing) {
|
|
152
|
+
throw new Error(`OAuth provider already exists: ${params.providerKey}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const row = {
|
|
156
|
+
providerKey: params.providerKey,
|
|
157
|
+
authUrl: params.authUrl,
|
|
158
|
+
tokenUrl: params.tokenUrl,
|
|
159
|
+
tokenEndpointAuthMethod: params.tokenEndpointAuthMethod ?? null,
|
|
160
|
+
userinfoUrl: params.userinfoUrl ?? null,
|
|
161
|
+
baseUrl: params.baseUrl ?? null,
|
|
162
|
+
defaultScopes: JSON.stringify(params.defaultScopes),
|
|
163
|
+
scopePolicy: JSON.stringify(params.scopePolicy),
|
|
164
|
+
extraParams: params.extraParams ? JSON.stringify(params.extraParams) : null,
|
|
165
|
+
callbackTransport: params.callbackTransport ?? null,
|
|
166
|
+
loopbackPort: params.loopbackPort ?? null,
|
|
167
|
+
pingUrl: params.pingUrl ?? null,
|
|
168
|
+
createdAt: now,
|
|
169
|
+
updatedAt: now,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
db.insert(oauthProviders).values(row).run();
|
|
173
|
+
|
|
174
|
+
return row;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// App operations
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Insert or return an existing app by (provider_key, client_id).
|
|
183
|
+
* Generates a UUID on insert.
|
|
184
|
+
*/
|
|
185
|
+
export async function upsertApp(
|
|
186
|
+
providerKey: string,
|
|
187
|
+
clientId: string,
|
|
188
|
+
clientSecretOpts?: {
|
|
189
|
+
clientSecretValue?: string;
|
|
190
|
+
clientSecretCredentialPath?: string;
|
|
191
|
+
},
|
|
192
|
+
): Promise<OAuthAppRow> {
|
|
193
|
+
const { clientSecretValue, clientSecretCredentialPath } =
|
|
194
|
+
clientSecretOpts ?? {};
|
|
195
|
+
|
|
196
|
+
if (clientSecretValue && clientSecretCredentialPath) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
"Cannot provide both clientSecretValue and clientSecretCredentialPath",
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const defaultCredPath = (appId: string) => `oauth_app/${appId}/client_secret`;
|
|
203
|
+
|
|
204
|
+
// Verify the credential path points to an existing secret.
|
|
205
|
+
if (clientSecretCredentialPath) {
|
|
206
|
+
const existing = await getSecureKeyAsync(clientSecretCredentialPath);
|
|
207
|
+
if (existing === undefined) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
`No secret found at credential path: ${clientSecretCredentialPath}`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const db = getDb();
|
|
215
|
+
|
|
216
|
+
const existingRow = db
|
|
217
|
+
.select()
|
|
218
|
+
.from(oauthApps)
|
|
219
|
+
.where(
|
|
220
|
+
and(
|
|
221
|
+
eq(oauthApps.providerKey, providerKey),
|
|
222
|
+
eq(oauthApps.clientId, clientId),
|
|
223
|
+
),
|
|
224
|
+
)
|
|
225
|
+
.get();
|
|
226
|
+
|
|
227
|
+
if (existingRow) {
|
|
228
|
+
if (clientSecretValue) {
|
|
229
|
+
const stored = await setSecureKeyAsync(
|
|
230
|
+
existingRow.clientSecretCredentialPath,
|
|
231
|
+
clientSecretValue,
|
|
232
|
+
);
|
|
233
|
+
if (!stored) {
|
|
234
|
+
throw new Error("Failed to store client_secret in secure storage");
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (clientSecretCredentialPath) {
|
|
238
|
+
db.update(oauthApps)
|
|
239
|
+
.set({
|
|
240
|
+
clientSecretCredentialPath,
|
|
241
|
+
updatedAt: Date.now(),
|
|
242
|
+
})
|
|
243
|
+
.where(eq(oauthApps.id, existingRow.id))
|
|
244
|
+
.run();
|
|
245
|
+
return db
|
|
246
|
+
.select()
|
|
247
|
+
.from(oauthApps)
|
|
248
|
+
.where(eq(oauthApps.id, existingRow.id))
|
|
249
|
+
.get()!;
|
|
250
|
+
}
|
|
251
|
+
return existingRow;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const now = Date.now();
|
|
255
|
+
const id = uuid();
|
|
256
|
+
const credPath = clientSecretCredentialPath ?? defaultCredPath(id);
|
|
257
|
+
|
|
258
|
+
if (clientSecretValue) {
|
|
259
|
+
const stored = await setSecureKeyAsync(credPath, clientSecretValue);
|
|
260
|
+
if (!stored) {
|
|
261
|
+
throw new Error("Failed to store client_secret in secure storage");
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const row = {
|
|
266
|
+
id,
|
|
267
|
+
providerKey,
|
|
268
|
+
clientId,
|
|
269
|
+
clientSecretCredentialPath: credPath,
|
|
270
|
+
createdAt: now,
|
|
271
|
+
updatedAt: now,
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
db.insert(oauthApps).values(row).run();
|
|
275
|
+
|
|
276
|
+
return row;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Look up an app by its primary key. */
|
|
280
|
+
export function getApp(id: string): OAuthAppRow | undefined {
|
|
281
|
+
const db = getDb();
|
|
282
|
+
return db.select().from(oauthApps).where(eq(oauthApps.id, id)).get();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Look up an app by (provider_key, client_id). */
|
|
286
|
+
export function getAppByProviderAndClientId(
|
|
287
|
+
providerKey: string,
|
|
288
|
+
clientId: string,
|
|
289
|
+
): OAuthAppRow | undefined {
|
|
290
|
+
const db = getDb();
|
|
291
|
+
return db
|
|
292
|
+
.select()
|
|
293
|
+
.from(oauthApps)
|
|
294
|
+
.where(
|
|
295
|
+
and(
|
|
296
|
+
eq(oauthApps.providerKey, providerKey),
|
|
297
|
+
eq(oauthApps.clientId, clientId),
|
|
298
|
+
),
|
|
299
|
+
)
|
|
300
|
+
.get();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Get the most recently created app for a provider.
|
|
305
|
+
* Returns undefined if no app exists for this provider.
|
|
306
|
+
*/
|
|
307
|
+
export function getMostRecentAppByProvider(
|
|
308
|
+
providerKey: string,
|
|
309
|
+
): OAuthAppRow | undefined {
|
|
310
|
+
const db = getDb();
|
|
311
|
+
return db
|
|
312
|
+
.select()
|
|
313
|
+
.from(oauthApps)
|
|
314
|
+
.where(eq(oauthApps.providerKey, providerKey))
|
|
315
|
+
.orderBy(desc(oauthApps.createdAt))
|
|
316
|
+
.limit(1)
|
|
317
|
+
.get();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** Return all OAuth apps. */
|
|
321
|
+
export function listApps(): OAuthAppRow[] {
|
|
322
|
+
const db = getDb();
|
|
323
|
+
return db.select().from(oauthApps).all();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** Delete an app by ID. Cleans up the client_secret from secure storage. Returns true if a row was deleted. */
|
|
327
|
+
export async function deleteApp(id: string): Promise<boolean> {
|
|
328
|
+
const db = getDb();
|
|
329
|
+
|
|
330
|
+
const app = db.select().from(oauthApps).where(eq(oauthApps.id, id)).get();
|
|
331
|
+
if (!app) return false;
|
|
332
|
+
|
|
333
|
+
// Delete the DB row first so that if it fails (e.g. FK constraint from
|
|
334
|
+
// existing connections), the secret in secure storage remains intact.
|
|
335
|
+
db.delete(oauthApps).where(eq(oauthApps.id, id)).run();
|
|
336
|
+
|
|
337
|
+
const result = await deleteSecureKeyAsync(app.clientSecretCredentialPath);
|
|
338
|
+
if (result === "error") {
|
|
339
|
+
throw new Error(
|
|
340
|
+
`Deleted app ${id} but failed to remove client_secret from secure storage`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
// Connection operations
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Create a new OAuth connection. Generates a UUID and sets status='active'.
|
|
353
|
+
* `metadata` is an optional JSON object for provider-specific token response data.
|
|
354
|
+
*/
|
|
355
|
+
export function createConnection(params: {
|
|
356
|
+
oauthAppId: string;
|
|
357
|
+
providerKey: string;
|
|
358
|
+
accountInfo?: string;
|
|
359
|
+
grantedScopes: string[];
|
|
360
|
+
expiresAt?: number;
|
|
361
|
+
hasRefreshToken: boolean;
|
|
362
|
+
label?: string;
|
|
363
|
+
metadata?: Record<string, unknown>;
|
|
364
|
+
/** Override the creation timestamp. Useful in tests to ensure deterministic ordering. */
|
|
365
|
+
createdAt?: number;
|
|
366
|
+
}): OAuthConnectionRow {
|
|
367
|
+
const db = getDb();
|
|
368
|
+
const now = params.createdAt ?? Date.now();
|
|
369
|
+
const id = uuid();
|
|
370
|
+
|
|
371
|
+
const row = {
|
|
372
|
+
id,
|
|
373
|
+
oauthAppId: params.oauthAppId,
|
|
374
|
+
providerKey: params.providerKey,
|
|
375
|
+
accountInfo: params.accountInfo ?? null,
|
|
376
|
+
grantedScopes: JSON.stringify(params.grantedScopes),
|
|
377
|
+
expiresAt: params.expiresAt ?? null,
|
|
378
|
+
hasRefreshToken: params.hasRefreshToken ? 1 : 0,
|
|
379
|
+
status: "active" as const,
|
|
380
|
+
label: params.label ?? null,
|
|
381
|
+
metadata: params.metadata ? JSON.stringify(params.metadata) : null,
|
|
382
|
+
createdAt: now,
|
|
383
|
+
updatedAt: now,
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
db.insert(oauthConnections).values(row).run();
|
|
387
|
+
|
|
388
|
+
return row;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** Look up a connection by its primary key. */
|
|
392
|
+
export function getConnection(id: string): OAuthConnectionRow | undefined {
|
|
393
|
+
const db = getDb();
|
|
394
|
+
return db
|
|
395
|
+
.select()
|
|
396
|
+
.from(oauthConnections)
|
|
397
|
+
.where(eq(oauthConnections.id, id))
|
|
398
|
+
.get();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Get the most recent active connection for a provider.
|
|
403
|
+
* When `clientId` is provided, only connections linked to the matching app are considered.
|
|
404
|
+
* Returns undefined if no active connection exists.
|
|
405
|
+
*/
|
|
406
|
+
export function getConnectionByProvider(
|
|
407
|
+
providerKey: string,
|
|
408
|
+
clientId?: string,
|
|
409
|
+
): OAuthConnectionRow | undefined {
|
|
410
|
+
const db = getDb();
|
|
411
|
+
|
|
412
|
+
if (clientId) {
|
|
413
|
+
const app = getAppByProviderAndClientId(providerKey, clientId);
|
|
414
|
+
if (!app) return undefined;
|
|
415
|
+
return db
|
|
416
|
+
.select()
|
|
417
|
+
.from(oauthConnections)
|
|
418
|
+
.where(
|
|
419
|
+
and(
|
|
420
|
+
eq(oauthConnections.providerKey, providerKey),
|
|
421
|
+
eq(oauthConnections.oauthAppId, app.id),
|
|
422
|
+
eq(oauthConnections.status, "active"),
|
|
423
|
+
),
|
|
424
|
+
)
|
|
425
|
+
.orderBy(desc(oauthConnections.createdAt), sql`rowid DESC`)
|
|
426
|
+
.limit(1)
|
|
427
|
+
.get();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return db
|
|
431
|
+
.select()
|
|
432
|
+
.from(oauthConnections)
|
|
433
|
+
.where(
|
|
434
|
+
and(
|
|
435
|
+
eq(oauthConnections.providerKey, providerKey),
|
|
436
|
+
eq(oauthConnections.status, "active"),
|
|
437
|
+
),
|
|
438
|
+
)
|
|
439
|
+
.orderBy(desc(oauthConnections.createdAt), sql`rowid DESC`)
|
|
440
|
+
.limit(1)
|
|
441
|
+
.get();
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Check whether a provider has a usable OAuth connection: an active row in the
|
|
446
|
+
* database AND a corresponding access token in secure storage.
|
|
447
|
+
*
|
|
448
|
+
* This guards against the edge case where the connection row was created/updated
|
|
449
|
+
* but the secure-key write for the access token failed, which would make
|
|
450
|
+
* `resolveOAuthConnection()` throw at usage time.
|
|
451
|
+
*/
|
|
452
|
+
export function isProviderConnected(providerKey: string): boolean {
|
|
453
|
+
const conn = getConnectionByProvider(providerKey);
|
|
454
|
+
if (!conn || conn.status !== "active") return false;
|
|
455
|
+
return getSecureKey(`oauth_connection/${conn.id}/access_token`) !== undefined;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Update fields on an existing connection. Returns true if a row was updated.
|
|
460
|
+
*/
|
|
461
|
+
export function updateConnection(
|
|
462
|
+
id: string,
|
|
463
|
+
updates: Partial<{
|
|
464
|
+
oauthAppId: string;
|
|
465
|
+
accountInfo: string;
|
|
466
|
+
grantedScopes: string[];
|
|
467
|
+
/** Pass `null` to explicitly clear a stale expiresAt in the DB. */
|
|
468
|
+
expiresAt: number | null;
|
|
469
|
+
hasRefreshToken: boolean;
|
|
470
|
+
status: string;
|
|
471
|
+
label: string;
|
|
472
|
+
metadata: Record<string, unknown>;
|
|
473
|
+
}>,
|
|
474
|
+
): boolean {
|
|
475
|
+
const db = getDb();
|
|
476
|
+
const now = Date.now();
|
|
477
|
+
|
|
478
|
+
// Build the set clause, serializing JSON fields and converting booleans.
|
|
479
|
+
// For expiresAt, null means "clear the column" so we check for undefined
|
|
480
|
+
// explicitly rather than truthiness.
|
|
481
|
+
const set: Record<string, unknown> = { updatedAt: now };
|
|
482
|
+
if (updates.oauthAppId !== undefined) set.oauthAppId = updates.oauthAppId;
|
|
483
|
+
if (updates.accountInfo !== undefined) set.accountInfo = updates.accountInfo;
|
|
484
|
+
if (updates.grantedScopes !== undefined)
|
|
485
|
+
set.grantedScopes = JSON.stringify(updates.grantedScopes);
|
|
486
|
+
if (updates.expiresAt !== undefined) set.expiresAt = updates.expiresAt;
|
|
487
|
+
if (updates.hasRefreshToken !== undefined)
|
|
488
|
+
set.hasRefreshToken = updates.hasRefreshToken ? 1 : 0;
|
|
489
|
+
if (updates.status !== undefined) set.status = updates.status;
|
|
490
|
+
if (updates.label !== undefined) set.label = updates.label;
|
|
491
|
+
if (updates.metadata !== undefined)
|
|
492
|
+
set.metadata = JSON.stringify(updates.metadata);
|
|
493
|
+
|
|
494
|
+
db.update(oauthConnections).set(set).where(eq(oauthConnections.id, id)).run();
|
|
495
|
+
|
|
496
|
+
return rawChanges() > 0;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/** List connections, optionally filtered by provider key and/or client ID. */
|
|
500
|
+
export function listConnections(
|
|
501
|
+
providerKey?: string,
|
|
502
|
+
clientId?: string,
|
|
503
|
+
): OAuthConnectionRow[] {
|
|
504
|
+
const db = getDb();
|
|
505
|
+
|
|
506
|
+
let rows: OAuthConnectionRow[];
|
|
507
|
+
if (providerKey) {
|
|
508
|
+
rows = db
|
|
509
|
+
.select()
|
|
510
|
+
.from(oauthConnections)
|
|
511
|
+
.where(eq(oauthConnections.providerKey, providerKey))
|
|
512
|
+
.all();
|
|
513
|
+
} else {
|
|
514
|
+
rows = db.select().from(oauthConnections).all();
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (clientId) {
|
|
518
|
+
const matchingAppIds = new Set(
|
|
519
|
+
db
|
|
520
|
+
.select({ id: oauthApps.id })
|
|
521
|
+
.from(oauthApps)
|
|
522
|
+
.where(eq(oauthApps.clientId, clientId))
|
|
523
|
+
.all()
|
|
524
|
+
.map((a) => a.id),
|
|
525
|
+
);
|
|
526
|
+
return rows.filter((r) => matchingAppIds.has(r.oauthAppId));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return rows;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/** Delete a connection by ID. Returns true if a row was deleted. */
|
|
533
|
+
export function deleteConnection(id: string): boolean {
|
|
534
|
+
const db = getDb();
|
|
535
|
+
db.delete(oauthConnections).where(eq(oauthConnections.id, id)).run();
|
|
536
|
+
return rawChanges() > 0;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// ---------------------------------------------------------------------------
|
|
540
|
+
// Disconnect (full cleanup)
|
|
541
|
+
// ---------------------------------------------------------------------------
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Fully disconnect an OAuth provider: delete the new-format secure keys
|
|
545
|
+
* (access_token and refresh_token) and remove the connection row from SQLite.
|
|
546
|
+
*
|
|
547
|
+
* Returns `"disconnected"` if a connection was found and cleaned up,
|
|
548
|
+
* `"not-found"` if no active connection existed for the given provider,
|
|
549
|
+
* or `"error"` if secure key deletion failed (connection row is preserved
|
|
550
|
+
* to avoid orphaning secrets).
|
|
551
|
+
*/
|
|
552
|
+
export async function disconnectOAuthProvider(
|
|
553
|
+
providerKey: string,
|
|
554
|
+
clientId?: string,
|
|
555
|
+
): Promise<"disconnected" | "not-found" | "error"> {
|
|
556
|
+
const conn = getConnectionByProvider(providerKey, clientId);
|
|
557
|
+
if (!conn) return "not-found";
|
|
558
|
+
|
|
559
|
+
const r1 = await deleteSecureKeyAsync(
|
|
560
|
+
`oauth_connection/${conn.id}/access_token`,
|
|
561
|
+
);
|
|
562
|
+
const r2 = await deleteSecureKeyAsync(
|
|
563
|
+
`oauth_connection/${conn.id}/refresh_token`,
|
|
564
|
+
);
|
|
565
|
+
|
|
566
|
+
if (r1 === "error" || r2 === "error") {
|
|
567
|
+
log.warn(
|
|
568
|
+
{
|
|
569
|
+
providerKey,
|
|
570
|
+
connectionId: conn.id,
|
|
571
|
+
accessTokenResult: r1,
|
|
572
|
+
refreshTokenResult: r2,
|
|
573
|
+
},
|
|
574
|
+
"Failed to delete OAuth secure keys — skipping connection row deletion to avoid orphaning secrets",
|
|
575
|
+
);
|
|
576
|
+
return "error";
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
deleteConnection(conn.id);
|
|
580
|
+
|
|
581
|
+
return "disconnected";
|
|
582
|
+
}
|