@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
|
@@ -44,6 +44,62 @@ mock.module("../tools/registry.js", () => ({
|
|
|
44
44
|
registerTool: () => {},
|
|
45
45
|
}));
|
|
46
46
|
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Mock oauth-store to avoid SQLite dependency in unit tests
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
let mockGetMostRecentAppByProvider: ReturnType<
|
|
52
|
+
typeof mock<(key: string) => unknown>
|
|
53
|
+
>;
|
|
54
|
+
let mockGetAppByProviderAndClientId: ReturnType<
|
|
55
|
+
typeof mock<(key: string, clientId: string) => unknown>
|
|
56
|
+
>;
|
|
57
|
+
let mockGetProvider: ReturnType<typeof mock<(key: string) => unknown>>;
|
|
58
|
+
|
|
59
|
+
mock.module("../oauth/oauth-store.js", () => {
|
|
60
|
+
mockGetMostRecentAppByProvider = mock(() => undefined);
|
|
61
|
+
mockGetAppByProviderAndClientId = mock(() => undefined);
|
|
62
|
+
mockGetProvider = mock(() => undefined);
|
|
63
|
+
return {
|
|
64
|
+
getMostRecentAppByProvider: mockGetMostRecentAppByProvider,
|
|
65
|
+
getAppByProviderAndClientId: mockGetAppByProviderAndClientId,
|
|
66
|
+
getProvider: mockGetProvider,
|
|
67
|
+
listConnections: mock(() => []),
|
|
68
|
+
seedProviders: mock(() => {}),
|
|
69
|
+
disconnectOAuthProvider: mock(async () => "not-found" as const),
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Mock public ingress URL — not available in unit tests. The connect
|
|
75
|
+
// orchestrator dynamically imports this for non-interactive flows.
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
mock.module("../inbound/public-ingress-urls.js", () => ({
|
|
79
|
+
getPublicBaseUrl: () => {
|
|
80
|
+
throw new Error("No public ingress URL configured");
|
|
81
|
+
},
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Mock prepareOAuth2Flow — unit tests should not start real loopback HTTP
|
|
86
|
+
// servers. The connect orchestrator still runs its own validation logic
|
|
87
|
+
// (scope policy, non-interactive ingress checks, etc.) but the actual
|
|
88
|
+
// OAuth flow setup is stubbed.
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
mock.module("../security/oauth2.js", () => ({
|
|
92
|
+
prepareOAuth2Flow: mock(async () => ({
|
|
93
|
+
authUrl: "https://mock-auth-url.example.com/authorize",
|
|
94
|
+
state: "mock-state",
|
|
95
|
+
completion: new Promise(() => {}),
|
|
96
|
+
})),
|
|
97
|
+
startOAuth2Flow: mock(async () => ({
|
|
98
|
+
grantedScopes: [],
|
|
99
|
+
tokens: { access_token: "mock-token" },
|
|
100
|
+
})),
|
|
101
|
+
}));
|
|
102
|
+
|
|
47
103
|
// ---------------------------------------------------------------------------
|
|
48
104
|
// Imports under test
|
|
49
105
|
// ---------------------------------------------------------------------------
|
|
@@ -473,18 +529,50 @@ describe("credential_store tool — prompt action", () => {
|
|
|
473
529
|
// ---------------------------------------------------------------------------
|
|
474
530
|
|
|
475
531
|
describe("credential_store tool — oauth2_connect error paths", () => {
|
|
532
|
+
/** Well-known provider rows returned by the mocked getProvider */
|
|
533
|
+
const wellKnownProviders: Record<string, object> = {
|
|
534
|
+
"integration:gmail": {
|
|
535
|
+
key: "integration:gmail",
|
|
536
|
+
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
537
|
+
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
538
|
+
defaultScopes: JSON.stringify(["https://mail.google.com/"]),
|
|
539
|
+
scopePolicy: JSON.stringify({}),
|
|
540
|
+
callbackTransport: "loopback",
|
|
541
|
+
loopbackPort: 8756,
|
|
542
|
+
},
|
|
543
|
+
"integration:slack": {
|
|
544
|
+
key: "integration:slack",
|
|
545
|
+
authUrl: "https://slack.com/oauth/v2/authorize",
|
|
546
|
+
tokenUrl: "https://slack.com/api/oauth.v2.access",
|
|
547
|
+
defaultScopes: JSON.stringify(["channels:read"]),
|
|
548
|
+
scopePolicy: JSON.stringify({}),
|
|
549
|
+
},
|
|
550
|
+
};
|
|
551
|
+
|
|
476
552
|
beforeEach(() => {
|
|
477
553
|
if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
|
|
478
554
|
mkdirSync(TEST_DIR, { recursive: true });
|
|
479
555
|
_setStorePath(STORE_PATH);
|
|
480
556
|
_resetBackend();
|
|
481
557
|
_setMetadataPath(join(TEST_DIR, "metadata.json"));
|
|
558
|
+
// Return well-known provider rows so vault.ts knows gmail/slack are
|
|
559
|
+
// registered, and custom providers return undefined.
|
|
560
|
+
mockGetProvider.mockImplementation(
|
|
561
|
+
(key: string) => wellKnownProviders[key] ?? undefined,
|
|
562
|
+
);
|
|
563
|
+
mockGetMostRecentAppByProvider.mockClear();
|
|
564
|
+
mockGetMostRecentAppByProvider.mockImplementation(() => undefined);
|
|
565
|
+
mockGetAppByProviderAndClientId.mockClear();
|
|
566
|
+
mockGetAppByProviderAndClientId.mockImplementation(() => undefined);
|
|
482
567
|
});
|
|
483
568
|
|
|
484
569
|
afterEach(() => {
|
|
485
570
|
_setMetadataPath(null);
|
|
486
571
|
_setStorePath(null);
|
|
487
572
|
_resetBackend();
|
|
573
|
+
mockGetProvider.mockImplementation(() => undefined);
|
|
574
|
+
mockGetMostRecentAppByProvider.mockImplementation(() => undefined);
|
|
575
|
+
mockGetAppByProviderAndClientId.mockImplementation(() => undefined);
|
|
488
576
|
});
|
|
489
577
|
|
|
490
578
|
test("requires service parameter", async () => {
|
|
@@ -496,55 +584,38 @@ describe("credential_store tool — oauth2_connect error paths", () => {
|
|
|
496
584
|
expect(result.content).toContain("service is required");
|
|
497
585
|
});
|
|
498
586
|
|
|
499
|
-
test("
|
|
500
|
-
const result = await credentialStoreTool.execute(
|
|
501
|
-
{
|
|
502
|
-
action: "oauth2_connect",
|
|
503
|
-
service: "custom-svc",
|
|
504
|
-
token_url: "https://t",
|
|
505
|
-
scopes: ["read"],
|
|
506
|
-
},
|
|
507
|
-
_ctx,
|
|
508
|
-
);
|
|
509
|
-
expect(result.isError).toBe(true);
|
|
510
|
-
expect(result.content).toContain("auth_url is required");
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
test("requires token_url for unknown service", async () => {
|
|
514
|
-
const result = await credentialStoreTool.execute(
|
|
515
|
-
{
|
|
516
|
-
action: "oauth2_connect",
|
|
517
|
-
service: "custom-svc",
|
|
518
|
-
auth_url: "https://a",
|
|
519
|
-
scopes: ["read"],
|
|
520
|
-
},
|
|
521
|
-
_ctx,
|
|
522
|
-
);
|
|
523
|
-
expect(result.isError).toBe(true);
|
|
524
|
-
expect(result.content).toContain("token_url is required");
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
test("requires scopes for unknown service", async () => {
|
|
587
|
+
test("rejects unknown service without registered provider", async () => {
|
|
528
588
|
const result = await credentialStoreTool.execute(
|
|
529
589
|
{
|
|
530
590
|
action: "oauth2_connect",
|
|
531
591
|
service: "custom-svc",
|
|
532
592
|
auth_url: "https://a",
|
|
533
593
|
token_url: "https://t",
|
|
594
|
+
scopes: ["read"],
|
|
534
595
|
},
|
|
535
596
|
_ctx,
|
|
536
597
|
);
|
|
537
598
|
expect(result.isError).toBe(true);
|
|
538
|
-
expect(result.content).toContain("
|
|
599
|
+
expect(result.content).toContain("no OAuth provider registered");
|
|
539
600
|
});
|
|
540
601
|
|
|
541
602
|
test("requires client_id", async () => {
|
|
603
|
+
mockGetProvider.mockImplementation((key: string) => {
|
|
604
|
+
if (key === "custom-svc") {
|
|
605
|
+
return {
|
|
606
|
+
key: "custom-svc",
|
|
607
|
+
authUrl: "https://auth.example.com",
|
|
608
|
+
tokenUrl: "https://token.example.com",
|
|
609
|
+
defaultScopes: JSON.stringify(["read"]),
|
|
610
|
+
scopePolicy: JSON.stringify({}),
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
return wellKnownProviders[key] ?? undefined;
|
|
614
|
+
});
|
|
542
615
|
const result = await credentialStoreTool.execute(
|
|
543
616
|
{
|
|
544
617
|
action: "oauth2_connect",
|
|
545
618
|
service: "custom-svc",
|
|
546
|
-
auth_url: "https://auth.example.com",
|
|
547
|
-
token_url: "https://token.example.com",
|
|
548
619
|
scopes: ["read"],
|
|
549
620
|
},
|
|
550
621
|
_ctx,
|
|
@@ -554,6 +625,21 @@ describe("credential_store tool — oauth2_connect error paths", () => {
|
|
|
554
625
|
});
|
|
555
626
|
|
|
556
627
|
test("requires interactive context", async () => {
|
|
628
|
+
// Register custom-svc as a provider so the orchestrator finds it
|
|
629
|
+
// and reaches the non-interactive check (gateway transport).
|
|
630
|
+
mockGetProvider.mockImplementation((key: string) => {
|
|
631
|
+
if (key === "custom-svc") {
|
|
632
|
+
return {
|
|
633
|
+
key: "custom-svc",
|
|
634
|
+
authUrl: "https://auth.example.com",
|
|
635
|
+
tokenUrl: "https://token.example.com",
|
|
636
|
+
defaultScopes: JSON.stringify(["read"]),
|
|
637
|
+
scopePolicy: JSON.stringify({}),
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
return wellKnownProviders[key] ?? undefined;
|
|
641
|
+
});
|
|
642
|
+
|
|
557
643
|
const result = await credentialStoreTool.execute(
|
|
558
644
|
{
|
|
559
645
|
action: "oauth2_connect",
|
|
@@ -596,18 +682,26 @@ describe("credential_store tool — oauth2_connect error paths", () => {
|
|
|
596
682
|
expect(result.content).toContain("client_id is required");
|
|
597
683
|
});
|
|
598
684
|
|
|
599
|
-
test("uses stored client_id from
|
|
600
|
-
//
|
|
601
|
-
// in the secure store
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
685
|
+
test("uses stored client_id from oauth-store DB", async () => {
|
|
686
|
+
// Mock getMostRecentAppByProvider to return an app with a client_id
|
|
687
|
+
// and store client_secret in the secure store.
|
|
688
|
+
mockGetMostRecentAppByProvider.mockImplementation(() => ({
|
|
689
|
+
id: "test-app-id",
|
|
690
|
+
providerKey: "integration:gmail",
|
|
691
|
+
clientId: "stored-client-id-123",
|
|
692
|
+
clientSecretCredentialPath: "oauth_app/test-app-id/client_secret",
|
|
693
|
+
createdAt: Date.now(),
|
|
694
|
+
}));
|
|
695
|
+
mockGetProvider.mockImplementation(() => ({
|
|
696
|
+
key: "integration:gmail",
|
|
697
|
+
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
698
|
+
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
699
|
+
defaultScopes: JSON.stringify(["https://mail.google.com/"]),
|
|
700
|
+
scopePolicy: JSON.stringify({}),
|
|
701
|
+
callbackTransport: "loopback",
|
|
702
|
+
loopbackPort: 8756,
|
|
703
|
+
}));
|
|
704
|
+
setSecureKey("oauth_app/test-app-id/client_secret", "test-secret");
|
|
611
705
|
|
|
612
706
|
const result = await credentialStoreTool.execute(
|
|
613
707
|
{
|
|
@@ -624,14 +718,151 @@ describe("credential_store tool — oauth2_connect error paths", () => {
|
|
|
624
718
|
expect(result.content).toContain("To connect gmail, open this link");
|
|
625
719
|
expect(result.content).not.toContain("client_id is required");
|
|
626
720
|
expect(result.content).not.toContain("client_secret is required");
|
|
721
|
+
|
|
722
|
+
// Reset mocks
|
|
723
|
+
mockGetMostRecentAppByProvider.mockImplementation(() => undefined);
|
|
724
|
+
mockGetProvider.mockImplementation(() => undefined);
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
test("uses getAppByProviderAndClientId when client_id is provided without client_secret", async () => {
|
|
728
|
+
// When client_id is supplied but client_secret is not, the vault should
|
|
729
|
+
// look up the matching app via getAppByProviderAndClientId (not the
|
|
730
|
+
// most-recent-app heuristic) so the secret comes from the correct app.
|
|
731
|
+
mockGetAppByProviderAndClientId.mockImplementation(
|
|
732
|
+
(providerKey: string, cId: string) => {
|
|
733
|
+
if (
|
|
734
|
+
providerKey === "integration:gmail" &&
|
|
735
|
+
cId === "caller-supplied-client-id"
|
|
736
|
+
) {
|
|
737
|
+
return {
|
|
738
|
+
id: "matched-app-id",
|
|
739
|
+
providerKey: "integration:gmail",
|
|
740
|
+
clientId: "caller-supplied-client-id",
|
|
741
|
+
clientSecretCredentialPath:
|
|
742
|
+
"oauth_app/matched-app-id/client_secret",
|
|
743
|
+
createdAt: Date.now(),
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
return undefined;
|
|
747
|
+
},
|
|
748
|
+
);
|
|
749
|
+
mockGetProvider.mockImplementation(() => ({
|
|
750
|
+
key: "integration:gmail",
|
|
751
|
+
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
752
|
+
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
753
|
+
defaultScopes: JSON.stringify(["https://mail.google.com/"]),
|
|
754
|
+
scopePolicy: JSON.stringify({}),
|
|
755
|
+
callbackTransport: "loopback",
|
|
756
|
+
loopbackPort: 8756,
|
|
757
|
+
}));
|
|
758
|
+
setSecureKey("oauth_app/matched-app-id/client_secret", "matched-secret");
|
|
759
|
+
|
|
760
|
+
const result = await credentialStoreTool.execute(
|
|
761
|
+
{
|
|
762
|
+
action: "oauth2_connect",
|
|
763
|
+
service: "gmail",
|
|
764
|
+
client_id: "caller-supplied-client-id",
|
|
765
|
+
},
|
|
766
|
+
{ ..._ctx, isInteractive: false },
|
|
767
|
+
);
|
|
768
|
+
|
|
769
|
+
// Should succeed — client_secret resolved from the matched app
|
|
770
|
+
expect(result.isError).toBe(false);
|
|
771
|
+
expect(result.content).toContain("To connect gmail, open this link");
|
|
772
|
+
// getMostRecentAppByProvider should NOT have been called since client_id was known
|
|
773
|
+
expect(mockGetMostRecentAppByProvider).not.toHaveBeenCalled();
|
|
774
|
+
|
|
775
|
+
// Reset mocks
|
|
776
|
+
mockGetAppByProviderAndClientId.mockImplementation(() => undefined);
|
|
777
|
+
mockGetProvider.mockImplementation(() => undefined);
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
test("falls back to getMostRecentAppByProvider when client_id is not provided", async () => {
|
|
781
|
+
// When neither client_id nor client_secret is provided, the vault should
|
|
782
|
+
// use getMostRecentAppByProvider (the fallback heuristic).
|
|
783
|
+
mockGetMostRecentAppByProvider.mockImplementation(() => ({
|
|
784
|
+
id: "recent-app-id",
|
|
785
|
+
providerKey: "integration:gmail",
|
|
786
|
+
clientId: "recent-client-id",
|
|
787
|
+
clientSecretCredentialPath: "oauth_app/recent-app-id/client_secret",
|
|
788
|
+
createdAt: Date.now(),
|
|
789
|
+
}));
|
|
790
|
+
mockGetProvider.mockImplementation(() => ({
|
|
791
|
+
key: "integration:gmail",
|
|
792
|
+
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
793
|
+
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
794
|
+
defaultScopes: JSON.stringify(["https://mail.google.com/"]),
|
|
795
|
+
scopePolicy: JSON.stringify({}),
|
|
796
|
+
callbackTransport: "loopback",
|
|
797
|
+
loopbackPort: 8756,
|
|
798
|
+
}));
|
|
799
|
+
setSecureKey("oauth_app/recent-app-id/client_secret", "recent-secret");
|
|
800
|
+
|
|
801
|
+
const result = await credentialStoreTool.execute(
|
|
802
|
+
{
|
|
803
|
+
action: "oauth2_connect",
|
|
804
|
+
service: "gmail",
|
|
805
|
+
},
|
|
806
|
+
{ ..._ctx, isInteractive: false },
|
|
807
|
+
);
|
|
808
|
+
|
|
809
|
+
expect(result.isError).toBe(false);
|
|
810
|
+
expect(result.content).toContain("To connect gmail, open this link");
|
|
811
|
+
// getAppByProviderAndClientId should NOT have been called since client_id was unknown
|
|
812
|
+
expect(mockGetAppByProviderAndClientId).not.toHaveBeenCalled();
|
|
813
|
+
|
|
814
|
+
// Reset mocks
|
|
815
|
+
mockGetMostRecentAppByProvider.mockImplementation(() => undefined);
|
|
816
|
+
mockGetProvider.mockImplementation(() => undefined);
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
test("getAppByProviderAndClientId returning undefined leaves client_secret unresolved", async () => {
|
|
820
|
+
// When client_id is provided but getAppByProviderAndClientId returns no
|
|
821
|
+
// matching app, client_secret remains unresolved and the vault should
|
|
822
|
+
// report the missing secret error.
|
|
823
|
+
mockGetAppByProviderAndClientId.mockImplementation(() => undefined);
|
|
824
|
+
mockGetProvider.mockImplementation(() => ({
|
|
825
|
+
key: "integration:gmail",
|
|
826
|
+
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
827
|
+
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
828
|
+
defaultScopes: JSON.stringify(["https://mail.google.com/"]),
|
|
829
|
+
}));
|
|
830
|
+
|
|
831
|
+
const result = await credentialStoreTool.execute(
|
|
832
|
+
{
|
|
833
|
+
action: "oauth2_connect",
|
|
834
|
+
service: "gmail",
|
|
835
|
+
client_id: "unknown-client-id",
|
|
836
|
+
},
|
|
837
|
+
_ctx,
|
|
838
|
+
);
|
|
839
|
+
|
|
840
|
+
expect(result.isError).toBe(true);
|
|
841
|
+
expect(result.content).toContain("client_secret is required for gmail");
|
|
842
|
+
// getMostRecentAppByProvider should NOT have been called
|
|
843
|
+
expect(mockGetMostRecentAppByProvider).not.toHaveBeenCalled();
|
|
844
|
+
|
|
845
|
+
// Reset mocks
|
|
846
|
+
mockGetAppByProviderAndClientId.mockImplementation(() => undefined);
|
|
847
|
+
mockGetProvider.mockImplementation(() => undefined);
|
|
627
848
|
});
|
|
628
849
|
|
|
629
850
|
test("rejects when client_secret is missing for service that requires it", async () => {
|
|
630
|
-
//
|
|
631
|
-
//
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
851
|
+
// Mock getMostRecentAppByProvider to return an app with client_id but
|
|
852
|
+
// no client_secret in secure storage — validates the requiresClientSecret
|
|
853
|
+
// guardrail.
|
|
854
|
+
mockGetMostRecentAppByProvider.mockImplementation(() => ({
|
|
855
|
+
id: "test-app-id-no-secret",
|
|
856
|
+
providerKey: "integration:gmail",
|
|
857
|
+
clientId: "stored-client-id-456",
|
|
858
|
+
createdAt: Date.now(),
|
|
859
|
+
}));
|
|
860
|
+
mockGetProvider.mockImplementation(() => ({
|
|
861
|
+
key: "integration:gmail",
|
|
862
|
+
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
863
|
+
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
864
|
+
defaultScopes: JSON.stringify(["https://mail.google.com/"]),
|
|
865
|
+
}));
|
|
635
866
|
|
|
636
867
|
const result = await credentialStoreTool.execute(
|
|
637
868
|
{
|
|
@@ -643,6 +874,10 @@ describe("credential_store tool — oauth2_connect error paths", () => {
|
|
|
643
874
|
|
|
644
875
|
expect(result.isError).toBe(true);
|
|
645
876
|
expect(result.content).toContain("client_secret is required for gmail");
|
|
877
|
+
|
|
878
|
+
// Reset mocks
|
|
879
|
+
mockGetMostRecentAppByProvider.mockImplementation(() => undefined);
|
|
880
|
+
mockGetProvider.mockImplementation(() => undefined);
|
|
646
881
|
});
|
|
647
882
|
});
|
|
648
883
|
|
|
@@ -65,6 +65,69 @@ mock.module("../security/oauth2.js", () => {
|
|
|
65
65
|
};
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Mock oauth-store — token-manager reads refresh config from SQLite
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
/** Mutable per-test map of provider connections for getConnectionByProvider */
|
|
73
|
+
const mockConnections = new Map<
|
|
74
|
+
string,
|
|
75
|
+
{
|
|
76
|
+
id: string;
|
|
77
|
+
providerKey: string;
|
|
78
|
+
oauthAppId: string;
|
|
79
|
+
expiresAt: number | null;
|
|
80
|
+
}
|
|
81
|
+
>();
|
|
82
|
+
const mockApps = new Map<
|
|
83
|
+
string,
|
|
84
|
+
{
|
|
85
|
+
id: string;
|
|
86
|
+
providerKey: string;
|
|
87
|
+
clientId: string;
|
|
88
|
+
clientSecretCredentialPath: string;
|
|
89
|
+
}
|
|
90
|
+
>();
|
|
91
|
+
const mockProviders = new Map<
|
|
92
|
+
string,
|
|
93
|
+
{
|
|
94
|
+
key: string;
|
|
95
|
+
tokenUrl: string;
|
|
96
|
+
tokenEndpointAuthMethod?: string;
|
|
97
|
+
}
|
|
98
|
+
>();
|
|
99
|
+
|
|
100
|
+
let mockDisconnectOAuthProvider: ReturnType<
|
|
101
|
+
typeof mock<
|
|
102
|
+
(providerKey: string) => Promise<"disconnected" | "not-found" | "error">
|
|
103
|
+
>
|
|
104
|
+
>;
|
|
105
|
+
|
|
106
|
+
mock.module("../oauth/oauth-store.js", () => {
|
|
107
|
+
mockDisconnectOAuthProvider = mock((providerKey: string) =>
|
|
108
|
+
Promise.resolve(
|
|
109
|
+
mockConnections.has(providerKey)
|
|
110
|
+
? ("disconnected" as const)
|
|
111
|
+
: ("not-found" as const),
|
|
112
|
+
),
|
|
113
|
+
);
|
|
114
|
+
return {
|
|
115
|
+
disconnectOAuthProvider: mockDisconnectOAuthProvider,
|
|
116
|
+
getConnectionByProvider: (service: string) => mockConnections.get(service),
|
|
117
|
+
getConnection: (id: string) => {
|
|
118
|
+
for (const conn of mockConnections.values()) {
|
|
119
|
+
if (conn.id === id) return conn;
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
},
|
|
123
|
+
getApp: (id: string) => mockApps.get(id),
|
|
124
|
+
getProvider: (key: string) => mockProviders.get(key),
|
|
125
|
+
updateConnection: () => {},
|
|
126
|
+
getMostRecentAppByProvider: () => undefined,
|
|
127
|
+
listConnections: () => [],
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
|
|
68
131
|
// ---------------------------------------------------------------------------
|
|
69
132
|
// Import the module under test
|
|
70
133
|
// ---------------------------------------------------------------------------
|
|
@@ -85,7 +148,6 @@ import {
|
|
|
85
148
|
import {
|
|
86
149
|
_setMetadataPath,
|
|
87
150
|
getCredentialMetadata,
|
|
88
|
-
upsertCredentialMetadata,
|
|
89
151
|
} from "../tools/credentials/metadata-store.js";
|
|
90
152
|
import { credentialStoreTool } from "../tools/credentials/vault.js";
|
|
91
153
|
import type { ToolContext } from "../tools/types.js";
|
|
@@ -214,12 +276,15 @@ describe("credential_store tool", () => {
|
|
|
214
276
|
}
|
|
215
277
|
_setStorePath(STORE_PATH);
|
|
216
278
|
_setMetadataPath(join(TEST_DIR, "metadata.json"));
|
|
279
|
+
mockDisconnectOAuthProvider.mockClear();
|
|
280
|
+
mockConnections.clear();
|
|
217
281
|
});
|
|
218
282
|
|
|
219
283
|
afterEach(() => {
|
|
220
284
|
_setMetadataPath(null);
|
|
221
285
|
_setStorePath(null);
|
|
222
286
|
_resetBackend();
|
|
287
|
+
mockConnections.clear();
|
|
223
288
|
});
|
|
224
289
|
|
|
225
290
|
afterAll(() => {
|
|
@@ -664,6 +729,44 @@ describe("credential_store tool", () => {
|
|
|
664
729
|
expect(result.isError).toBe(true);
|
|
665
730
|
expect(result.content).toContain("field is required");
|
|
666
731
|
});
|
|
732
|
+
|
|
733
|
+
test("delete also disconnects OAuth connection for the service", async () => {
|
|
734
|
+
// Store a credential via the real tool so metadata exists
|
|
735
|
+
await credentialStoreTool.execute(
|
|
736
|
+
{
|
|
737
|
+
action: "store",
|
|
738
|
+
service: "integration:gmail",
|
|
739
|
+
field: "api_key",
|
|
740
|
+
value: "test-value",
|
|
741
|
+
},
|
|
742
|
+
_ctx,
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
// Simulate an active OAuth connection for this service
|
|
746
|
+
mockConnections.set("integration:gmail", {
|
|
747
|
+
id: "conn-gmail",
|
|
748
|
+
providerKey: "integration:gmail",
|
|
749
|
+
oauthAppId: "app-gmail",
|
|
750
|
+
expiresAt: Date.now() + 3600_000,
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
const result = await credentialStoreTool.execute(
|
|
754
|
+
{
|
|
755
|
+
action: "delete",
|
|
756
|
+
service: "integration:gmail",
|
|
757
|
+
field: "api_key",
|
|
758
|
+
},
|
|
759
|
+
_ctx,
|
|
760
|
+
);
|
|
761
|
+
|
|
762
|
+
expect(result.isError).toBe(false);
|
|
763
|
+
expect(result.content).toContain("Deleted credential");
|
|
764
|
+
// Verify disconnectOAuthProvider was called with the service name
|
|
765
|
+
expect(mockDisconnectOAuthProvider).toHaveBeenCalledTimes(1);
|
|
766
|
+
expect(mockDisconnectOAuthProvider).toHaveBeenCalledWith(
|
|
767
|
+
"integration:gmail",
|
|
768
|
+
);
|
|
769
|
+
});
|
|
667
770
|
});
|
|
668
771
|
|
|
669
772
|
// -----------------------------------------------------------------------
|
|
@@ -1172,6 +1275,10 @@ describe("withValidToken refresh deduplication", () => {
|
|
|
1172
1275
|
_resetRefreshBreakers();
|
|
1173
1276
|
_resetInflightRefreshes();
|
|
1174
1277
|
mockRefreshOAuth2Token.mockClear();
|
|
1278
|
+
// Clear mock oauth-store maps
|
|
1279
|
+
mockConnections.clear();
|
|
1280
|
+
mockApps.clear();
|
|
1281
|
+
mockProviders.clear();
|
|
1175
1282
|
});
|
|
1176
1283
|
|
|
1177
1284
|
afterEach(() => {
|
|
@@ -1180,6 +1287,9 @@ describe("withValidToken refresh deduplication", () => {
|
|
|
1180
1287
|
_resetBackend();
|
|
1181
1288
|
_resetRefreshBreakers();
|
|
1182
1289
|
_resetInflightRefreshes();
|
|
1290
|
+
mockConnections.clear();
|
|
1291
|
+
mockApps.clear();
|
|
1292
|
+
mockProviders.clear();
|
|
1183
1293
|
});
|
|
1184
1294
|
|
|
1185
1295
|
afterAll(() => {
|
|
@@ -1187,26 +1297,49 @@ describe("withValidToken refresh deduplication", () => {
|
|
|
1187
1297
|
});
|
|
1188
1298
|
|
|
1189
1299
|
/**
|
|
1190
|
-
* Helper: set up a service with an access token, refresh token, and
|
|
1191
|
-
*
|
|
1300
|
+
* Helper: set up a service with an access token, refresh token, and
|
|
1301
|
+
* mock DB data so that token refresh can proceed through doRefresh().
|
|
1302
|
+
*
|
|
1303
|
+
* OAuth-specific fields (tokenUrl, clientId, expiresAt) are now stored
|
|
1304
|
+
* in the SQLite oauth-store. The mock maps simulate the DB layer.
|
|
1192
1305
|
*/
|
|
1193
1306
|
function setupService(
|
|
1194
1307
|
service: string,
|
|
1195
1308
|
opts?: { expired?: boolean; accessToken?: string },
|
|
1196
1309
|
) {
|
|
1197
1310
|
const accessToken = opts?.accessToken ?? "old-access-token";
|
|
1198
|
-
|
|
1311
|
+
|
|
1312
|
+
// Seed mock oauth-store maps so token-manager can resolve refresh config
|
|
1313
|
+
const appId = `app-${service}`;
|
|
1314
|
+
const connId = `conn-${service}`;
|
|
1315
|
+
|
|
1316
|
+
// Store access token under the oauth_connection key path that
|
|
1317
|
+
// withValidToken reads (not the legacy credentialKey path).
|
|
1318
|
+
setSecureKey(`oauth_connection/${connId}/access_token`, accessToken);
|
|
1319
|
+
mockProviders.set(service, {
|
|
1320
|
+
key: service,
|
|
1321
|
+
tokenUrl: "https://oauth.example.com/token",
|
|
1322
|
+
});
|
|
1323
|
+
mockApps.set(appId, {
|
|
1324
|
+
id: appId,
|
|
1325
|
+
providerKey: service,
|
|
1326
|
+
clientId: "test-client-id",
|
|
1327
|
+
clientSecretCredentialPath: `oauth_app/${appId}/client_secret`,
|
|
1328
|
+
});
|
|
1329
|
+
mockConnections.set(service, {
|
|
1330
|
+
id: connId,
|
|
1331
|
+
providerKey: service,
|
|
1332
|
+
oauthAppId: appId,
|
|
1333
|
+
expiresAt: opts?.expired
|
|
1334
|
+
? Date.now() - 60_000 // expired 1 minute ago
|
|
1335
|
+
: Date.now() + 3600_000, // expires in 1 hour
|
|
1336
|
+
});
|
|
1337
|
+
// Store refresh token and client_secret in secure keys (token-manager reads them)
|
|
1199
1338
|
setSecureKey(
|
|
1200
|
-
|
|
1339
|
+
`oauth_connection/${connId}/refresh_token`,
|
|
1201
1340
|
"valid-refresh-token",
|
|
1202
1341
|
);
|
|
1203
|
-
|
|
1204
|
-
oauth2TokenUrl: "https://oauth.example.com/token",
|
|
1205
|
-
oauth2ClientId: "test-client-id",
|
|
1206
|
-
...(opts?.expired
|
|
1207
|
-
? { expiresAt: Date.now() - 60_000 } // expired 1 minute ago
|
|
1208
|
-
: { expiresAt: Date.now() + 3600_000 }), // expires in 1 hour
|
|
1209
|
-
});
|
|
1342
|
+
setSecureKey(`oauth_app/${appId}/client_secret`, "test-client-secret");
|
|
1210
1343
|
}
|
|
1211
1344
|
|
|
1212
1345
|
test("3 concurrent 401 refreshes for the same service call doRefresh exactly once", async () => {
|
|
@@ -1335,22 +1468,23 @@ describe("withValidToken refresh deduplication", () => {
|
|
|
1335
1468
|
|
|
1336
1469
|
const err401 = Object.assign(new Error("Unauthorized"), { status: 401 });
|
|
1337
1470
|
|
|
1338
|
-
// First call triggers a refresh
|
|
1471
|
+
// First call triggers a refresh (old token → 401 → refresh → token-1)
|
|
1339
1472
|
const r1 = await withValidToken(
|
|
1340
1473
|
"integration:gmail",
|
|
1341
1474
|
async (token: string) => {
|
|
1342
|
-
if (token
|
|
1475
|
+
if (token !== "token-1") throw err401;
|
|
1343
1476
|
return token;
|
|
1344
1477
|
},
|
|
1345
1478
|
);
|
|
1346
1479
|
expect(r1).toBe("token-1");
|
|
1347
1480
|
expect(refreshCount).toBe(1);
|
|
1348
1481
|
|
|
1349
|
-
//
|
|
1482
|
+
// Second call also triggers a 401 to verify dedup state was cleaned up
|
|
1483
|
+
// and a new refresh is allowed (not deduplicated with the first).
|
|
1350
1484
|
const r2 = await withValidToken(
|
|
1351
1485
|
"integration:gmail",
|
|
1352
1486
|
async (token: string) => {
|
|
1353
|
-
if (token
|
|
1487
|
+
if (token !== "token-2") throw err401;
|
|
1354
1488
|
return token;
|
|
1355
1489
|
},
|
|
1356
1490
|
);
|