@vellumai/assistant 0.9.0 → 0.10.0-staging.2
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 +18 -34
- package/bun.lock +7 -8
- package/docs/activation-funnel-telemetry.md +28 -22
- package/docs/architecture/security.md +29 -28
- package/docs/stt-provider-onboarding.md +3 -5
- package/docs/workflows-testing.md +13 -44
- package/docs/workflows.md +3 -5
- package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
- package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
- package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
- package/openapi.yaml +976 -63
- package/package.json +2 -1
- package/scripts/sync-llm-catalog.ts +6 -15
- package/scripts/sync-web-search-catalog.ts +3 -11
- package/src/__tests__/access-request-card-view.test.ts +98 -0
- package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
- package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
- package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
- package/src/__tests__/app-compiler.test.ts +15 -1
- package/src/__tests__/app-dir-path-guard.test.ts +0 -1
- package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
- package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
- package/src/__tests__/avatar-identity-sync.test.ts +2 -27
- package/src/__tests__/btw-routes.test.ts +6 -8
- package/src/__tests__/call-pointer-messages.test.ts +28 -0
- package/src/__tests__/cancel-clears-processing.test.ts +89 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -4
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
- package/src/__tests__/checker.test.ts +0 -3
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
- package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
- package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
- package/src/__tests__/config-loader-backfill.test.ts +268 -27
- package/src/__tests__/config-schema.test.ts +35 -0
- package/src/__tests__/config-watcher.test.ts +0 -18
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +0 -6
- package/src/__tests__/contacts-tools.test.ts +29 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +58 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-lifecycle.test.ts +7 -9
- package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
- package/src/__tests__/conversation-title-service.test.ts +62 -0
- package/src/__tests__/credential-broker.test.ts +449 -1
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
- package/src/__tests__/credential-execution-tools.test.ts +0 -1
- package/src/__tests__/credential-prompt-route.test.ts +4 -4
- package/src/__tests__/credential-routes.test.ts +360 -0
- package/src/__tests__/credential-security-invariants.test.ts +4 -13
- package/src/__tests__/disk-pressure-policy.test.ts +12 -0
- package/src/__tests__/disk-usage.test.ts +65 -0
- package/src/__tests__/dynamic-page-surface.test.ts +152 -1
- package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
- package/src/__tests__/gateway-flag-listener.test.ts +110 -1
- package/src/__tests__/gateway-only-guard.test.ts +3 -7
- package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
- package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
- package/src/__tests__/guardian-grant-minting.test.ts +3 -35
- package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
- package/src/__tests__/guardian-routing-state.test.ts +0 -1
- package/src/__tests__/headless-browser-mode.test.ts +10 -0
- package/src/__tests__/headless-browser-navigate.test.ts +8 -3
- package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
- package/src/__tests__/host-browser-proxy.test.ts +87 -0
- package/src/__tests__/identity-routes.test.ts +0 -189
- package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
- package/src/__tests__/injector-v3-suppression.test.ts +27 -20
- package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
- package/src/__tests__/invite-redemption-service.test.ts +4 -7
- package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +30 -23
- package/src/__tests__/llm-resolver.test.ts +70 -24
- package/src/__tests__/llm-schema.test.ts +1 -0
- package/src/__tests__/managed-profile-guard.test.ts +163 -4
- package/src/__tests__/mcp-health-check.test.ts +6 -7
- package/src/__tests__/media-stream-server-integration.test.ts +317 -13
- package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
- package/src/__tests__/onboarding-persona-write.test.ts +1 -1
- package/src/__tests__/path-policy.test.ts +34 -0
- package/src/__tests__/persona-resolver.test.ts +49 -14
- package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
- package/src/__tests__/plugin-api-provider.test.ts +24 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
- package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
- package/src/__tests__/reaction-persistence.test.ts +150 -29
- package/src/__tests__/registry.test.ts +2 -7
- package/src/__tests__/relay-server.test.ts +285 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
- package/src/__tests__/schedule-routes.test.ts +0 -30
- package/src/__tests__/schedule-tools.test.ts +2 -18
- package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
- package/src/__tests__/skill-execute-input.test.ts +51 -1
- package/src/__tests__/skill-runtime-path.test.ts +2 -3
- package/src/__tests__/skills.test.ts +51 -0
- package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
- package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
- package/src/__tests__/subagent-tools.test.ts +266 -0
- package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
- package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
- package/src/__tests__/title-generate-hook.test.ts +100 -3
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
- package/src/__tests__/token-manager.test.ts +519 -0
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
- package/src/__tests__/tool-audit-listener.test.ts +7 -7
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
- package/src/__tests__/tool-executor.test.ts +0 -79
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
- package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
- package/src/__tests__/trusted-contact-verification.test.ts +8 -10
- package/src/__tests__/twilio-routes.test.ts +81 -1
- package/src/__tests__/voice-invite-redemption.test.ts +2 -3
- package/src/__tests__/weak-open-model.test.ts +30 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
- package/src/__tests__/workspace-greetings.test.ts +152 -0
- package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
- package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
- package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
- package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
- package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
- package/src/agent/loop.ts +49 -29
- package/src/api/README.md +6 -6
- package/src/api/events/tool-result.ts +6 -0
- package/src/api/events/workflow-completed.ts +53 -0
- package/src/api/events/workflow-leaf-finished.ts +38 -0
- package/src/api/events/workflow-leaf-started.ts +35 -0
- package/src/api/events/workflow-progress.ts +32 -0
- package/src/api/events/workflow-started.ts +31 -0
- package/src/api/index.ts +40 -0
- package/src/api/responses/conversation-message.ts +28 -4
- package/src/api/responses/home.ts +26 -4
- package/src/api/responses/workflow-journal.ts +53 -0
- package/src/approvals/guardian-card-withdrawal.ts +145 -0
- package/src/approvals/guardian-decision-primitive.ts +26 -3
- package/src/approvals/guardian-request-resolvers.ts +183 -80
- package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
- package/src/calls/call-pointer-messages.ts +10 -4
- package/src/calls/channel-admission-reader.ts +104 -0
- package/src/calls/guardian-dispatch.ts +17 -45
- package/src/calls/media-stream-server.ts +84 -2
- package/src/calls/relay-access-wait.ts +1 -1
- package/src/calls/relay-server.ts +66 -0
- package/src/calls/relay-setup-router.ts +82 -1
- package/src/calls/twilio-routes.ts +17 -8
- package/src/calls/voice-session-bridge.ts +2 -2
- package/src/cli/commands/clients.ts +3 -0
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
- package/src/cli/commands/memory/index.ts +30 -0
- package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
- package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
- package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
- package/src/cli/commands/oauth/status.test.ts +36 -0
- package/src/cli/commands/oauth/status.ts +23 -3
- package/src/cli/commands/plugins.ts +197 -4
- package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
- package/src/cli/lib/diff-plugin.ts +346 -0
- package/src/cli/lib/inspect-plugin.ts +12 -1
- package/src/cli/lib/install-from-github.ts +105 -17
- package/src/cli/lib/merge-plugin-tree.ts +328 -0
- package/src/cli/lib/plugin-fingerprint.ts +14 -0
- package/src/cli/lib/plugin-surfaces.ts +104 -0
- package/src/cli/lib/upgrade-plugin.ts +298 -10
- package/src/cli/program.ts +2 -6
- package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
- package/src/config/assistant-feature-flags.ts +22 -7
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
- package/src/config/bundled-skills/messaging/SKILL.md +6 -4
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
- package/src/config/bundled-skills/subagent/SKILL.md +4 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
- package/src/config/bundled-skills/workflows/SKILL.md +14 -8
- package/src/config/bundled-tool-registry.ts +2 -7
- package/src/config/call-site-defaults.ts +15 -2
- package/src/config/feature-flag-registry.json +46 -31
- package/src/config/inference-profile-validation.ts +26 -0
- package/src/config/llm-resolver.ts +3 -0
- package/src/config/loader.ts +4 -0
- package/src/config/memory-v3-gate.ts +11 -0
- package/src/config/profile-order.ts +28 -0
- package/src/config/schema.ts +8 -6
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/channels.ts +11 -0
- package/src/config/schemas/elevenlabs.ts +0 -1
- package/src/config/schemas/llm.ts +31 -0
- package/src/config/schemas/memory-lifecycle.ts +3 -7
- package/src/config/schemas/memory-v3.ts +6 -0
- package/src/config/schemas/platform.ts +0 -8
- package/src/config/schemas/services.ts +18 -0
- package/src/config/seed-inference-profiles.ts +109 -44
- package/src/config/skills.ts +21 -0
- package/src/config/sync-gated-profiles.ts +220 -0
- package/src/contacts/contact-store.ts +89 -106
- package/src/contacts/contacts-write.ts +5 -22
- package/src/contacts/types.ts +0 -1
- package/src/context/compactor.ts +88 -54
- package/src/context/strip-injections.ts +58 -10
- package/src/context/token-estimator.ts +1 -1
- package/src/credential-execution/process-manager.ts +55 -14
- package/src/credential-execution/prompted-credential.ts +2 -3
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
- package/src/daemon/config-watcher.ts +0 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
- package/src/daemon/conversation-agent-loop.ts +114 -22
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-lifecycle.ts +3 -5
- package/src/daemon/conversation-process.ts +13 -5
- package/src/daemon/conversation-runtime-assembly.ts +13 -15
- package/src/daemon/conversation-slash.ts +2 -23
- package/src/daemon/conversation-surfaces.ts +26 -0
- package/src/daemon/conversation-tool-setup.ts +27 -14
- package/src/daemon/conversation.ts +66 -14
- package/src/daemon/disk-pressure-policy.ts +5 -3
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
- package/src/daemon/handlers/config-a2a.ts +0 -2
- package/src/daemon/handlers/config-channels.ts +15 -16
- package/src/daemon/handlers/config-slack-channel.ts +22 -3
- package/src/daemon/handlers/conversations.ts +107 -0
- package/src/daemon/host-browser-proxy.ts +41 -0
- package/src/daemon/lifecycle.ts +55 -27
- package/src/daemon/message-provenance.ts +2 -0
- package/src/daemon/message-types/contacts.ts +0 -1
- package/src/daemon/message-types/conversations.ts +3 -3
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/message-types/web-activity.ts +7 -1
- package/src/daemon/message-types/workflows.ts +83 -1
- package/src/daemon/orphan-reaper.test.ts +0 -19
- package/src/daemon/orphan-reaper.ts +2 -24
- package/src/daemon/server.ts +0 -10
- package/src/daemon/tool-setup-types.ts +4 -0
- package/src/daemon/trust-context.ts +1 -1
- package/src/events/tool-audit-listener.ts +2 -2
- package/src/home/feed-source-enrichment.test.ts +151 -0
- package/src/home/feed-source-enrichment.ts +176 -0
- package/src/home/relationship-state.ts +2 -4
- package/src/instrument.ts +18 -6
- package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
- package/src/ipc/assistant-server.ts +37 -4
- package/src/ipc/gateway-flag-listener.ts +18 -2
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
- package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
- package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
- package/src/memory/auth-fallback-events-store.ts +2 -2
- package/src/memory/auto-analysis-enqueue.ts +3 -5
- package/src/memory/bookmark-crud.ts +1 -2
- package/src/memory/canonical-guardian-store.ts +39 -1
- package/src/memory/conversation-crud.ts +9 -4
- package/src/memory/conversation-key-store.ts +17 -2
- package/src/memory/conversation-title-service.ts +64 -7
- package/src/memory/db-init.ts +17 -17
- package/src/memory/embedding-backend.ts +38 -1
- package/src/memory/embedding-billing-breaker.ts +96 -0
- package/src/memory/jobs-store.ts +25 -13
- package/src/memory/jobs-worker.ts +54 -1
- package/src/memory/lifecycle-events-store.ts +2 -2
- package/src/memory/memory-retrospective-constants.ts +4 -4
- package/src/memory/memory-retrospective-enqueue.ts +31 -6
- package/src/memory/memory-retrospective-job.ts +28 -227
- package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
- package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
- package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
- package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
- package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
- package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
- package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
- package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/run-migrations.ts +41 -0
- package/src/memory/migrations/validate-migration-state.ts +1 -1
- package/src/memory/onboarding-events-store.ts +3 -3
- package/src/memory/schema/contacts.ts +0 -5
- package/src/memory/skill-loaded-events-store.test.ts +7 -15
- package/src/memory/skill-loaded-events-store.ts +2 -2
- package/src/memory/tool-executed-events-store.test.ts +7 -7
- package/src/memory/turn-trace-store.test.ts +736 -0
- package/src/memory/turn-trace-store.ts +364 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
- package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
- package/src/memory/v2/consolidation-job.ts +2 -2
- package/src/memory/v2/skill-content.ts +25 -7
- package/src/memory/v2/skill-store.ts +7 -1
- package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
- package/src/memory/v3-eval/eval-packets.ts +546 -0
- package/src/messaging/providers/slack/adapter.ts +1 -1
- package/src/messaging/providers/slack/api.ts +31 -0
- package/src/messaging/providers/slack/send.test.ts +114 -2
- package/src/messaging/providers/slack/send.ts +30 -7
- package/src/messaging/providers/slack/withdraw.test.ts +200 -0
- package/src/messaging/providers/slack/withdraw.ts +161 -0
- package/src/notifications/AGENTS.md +2 -0
- package/src/notifications/access-request-copy.ts +72 -59
- package/src/notifications/adapters/shared.ts +29 -0
- package/src/notifications/adapters/slack.ts +58 -103
- package/src/notifications/adapters/telegram.ts +2 -20
- package/src/notifications/approval-card-data.ts +333 -0
- package/src/notifications/broadcaster.ts +16 -3
- package/src/notifications/canonical-delivery-recorder.ts +139 -0
- package/src/notifications/copy-composer.ts +3 -3
- package/src/notifications/decision-engine.ts +4 -2
- package/src/notifications/destination-resolver.ts +4 -6
- package/src/notifications/guardian-question-mode.ts +10 -0
- package/src/notifications/home-feed-side-effect.ts +7 -16
- package/src/notifications/notification-utils.ts +19 -20
- package/src/notifications/signal.ts +79 -43
- package/src/notifications/types.ts +98 -121
- package/src/oauth/AGENTS.md +5 -24
- package/src/permissions/checker.test.ts +51 -0
- package/src/permissions/checker.ts +185 -26
- package/src/permissions/ipc-risk-types.ts +24 -0
- package/src/permissions/question-prompter.test.ts +27 -0
- package/src/permissions/question-prompter.ts +4 -0
- package/src/platform/client.test.ts +119 -0
- package/src/platform/client.ts +66 -0
- package/src/platform/consent-cache.test.ts +267 -0
- package/src/platform/consent-cache.ts +174 -0
- package/src/plugin-api/constants.ts +1 -1
- package/src/plugin-api/index.ts +33 -1
- package/src/plugin-api/model-profiles.ts +33 -0
- package/src/plugin-api/types.ts +50 -2
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
- package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
- package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
- package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
- package/src/plugins/defaults/advisor/config.ts +21 -0
- package/src/plugins/defaults/advisor/consult.ts +93 -0
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/advisor/package.json +14 -0
- package/src/plugins/defaults/advisor/steering.ts +67 -0
- package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
- package/src/plugins/defaults/advisor/transcript.ts +76 -0
- package/src/plugins/defaults/index.ts +60 -0
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
- package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
- package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
- package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
- package/src/prompts/persona-resolver.ts +14 -4
- package/src/prompts/templates/system-sections.ts +7 -2
- package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
- package/src/providers/__tests__/retry-callsite.test.ts +176 -0
- package/src/providers/atlascloud/client.ts +85 -0
- package/src/providers/fetch-provider-catalog.ts +85 -0
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/model-catalog.ts +58 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
- package/src/providers/openai/chat-completions-provider.ts +7 -0
- package/src/providers/openai/responses-provider.ts +10 -0
- package/src/providers/provider-send-message.ts +11 -3
- package/src/providers/retry.ts +53 -12
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/weak-open-model.ts +22 -0
- package/src/runtime/AGENTS.md +0 -1
- package/src/runtime/__tests__/agent-wake.test.ts +181 -0
- package/src/runtime/__tests__/client-health.test.ts +44 -0
- package/src/runtime/access-request-helper.ts +21 -53
- package/src/runtime/actor-trust-resolver.ts +59 -63
- package/src/runtime/agent-wake.ts +52 -0
- package/src/runtime/assistant-event-hub.ts +18 -4
- package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
- package/src/runtime/auth/require-bound-guardian.ts +1 -4
- package/src/runtime/btw-sidechain.ts +3 -6
- package/src/runtime/capabilities.test.ts +120 -0
- package/src/runtime/capabilities.ts +197 -0
- package/src/runtime/channel-approval-types.ts +22 -45
- package/src/runtime/channel-invite-transports/telegram.ts +4 -4
- package/src/runtime/channel-retry-sweep.ts +1 -0
- package/src/runtime/channel-verification-service.ts +3 -3
- package/src/runtime/client-health.ts +26 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
- package/src/runtime/effective-capabilities.test.ts +128 -0
- package/src/runtime/effective-capabilities.ts +84 -0
- package/src/runtime/guardian-reply-router.ts +106 -21
- package/src/runtime/invite-redemption-service.ts +9 -25
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
- package/src/runtime/migrations/vbundle-builder.ts +49 -20
- package/src/runtime/pending-interactions.ts +15 -0
- package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +240 -1
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
- package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
- package/src/runtime/routes/browser-tabs-routes.ts +9 -0
- package/src/runtime/routes/btw-routes.ts +1 -27
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
- package/src/runtime/routes/client-routes.ts +10 -0
- package/src/runtime/routes/contact-routes.ts +31 -8
- package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
- package/src/runtime/routes/conversation-management-routes.ts +80 -1
- package/src/runtime/routes/conversation-query-routes.ts +68 -22
- package/src/runtime/routes/conversation-routes.ts +39 -14
- package/src/runtime/routes/credential-routes.ts +40 -16
- package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
- package/src/runtime/routes/events-routes.ts +1 -3
- package/src/runtime/routes/guardian-approval-interception.ts +14 -73
- package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
- package/src/runtime/routes/home-feed-routes.ts +8 -3
- package/src/runtime/routes/identity-routes.ts +1 -296
- package/src/runtime/routes/inbound-message-handler.ts +214 -228
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
- package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
- package/src/runtime/routes/integrations/slack/channel.ts +36 -0
- package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
- package/src/runtime/routes/mcp-auth-routes.ts +233 -41
- package/src/runtime/routes/memory-eval-routes.ts +87 -0
- package/src/runtime/routes/notification-routes.ts +122 -133
- package/src/runtime/routes/platform-routes.ts +2 -2
- package/src/runtime/routes/plugins-routes.ts +202 -3
- package/src/runtime/routes/schedule-routes.ts +0 -22
- package/src/runtime/routes/secret-routes.ts +10 -0
- package/src/runtime/routes/surface-action-routes.ts +2 -1
- package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
- package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
- package/src/runtime/routes/workflow-routes.test.ts +229 -44
- package/src/runtime/routes/workflow-routes.ts +131 -29
- package/src/runtime/routes/workspace-greetings.ts +55 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -11
- package/src/runtime/tool-grant-request-helper.ts +18 -16
- package/src/runtime/trust-context-resolver.ts +8 -5
- package/src/schedule/inference-profile.ts +2 -14
- package/src/schedule/schedule-store.ts +1 -1
- package/src/schedule/scheduler-types.ts +5 -1
- package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
- package/src/security/secret-patterns.ts +3 -0
- package/src/subagent/manager.ts +17 -4
- package/src/subagent/types.ts +6 -0
- package/src/telemetry/trace-collection-policy.test.ts +28 -0
- package/src/telemetry/trace-collection-policy.ts +30 -0
- package/src/telemetry/types.ts +89 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
- package/src/telemetry/usage-telemetry-reporter.ts +148 -41
- package/src/tools/AGENTS.md +3 -3
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
- package/src/tools/browser/browser-execution.ts +30 -19
- package/src/tools/document/document-tool.ts +2 -3
- package/src/tools/executor.ts +5 -3
- package/src/tools/host-terminal/host-shell.ts +5 -4
- package/src/tools/memory/register.ts +2 -2
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
- package/src/tools/network/__tests__/web-search.test.ts +143 -0
- package/src/tools/network/web-fetch.ts +372 -1
- package/src/tools/network/web-search-error.ts +1 -1
- package/src/tools/network/web-search.ts +213 -10
- package/src/tools/permission-checker.ts +4 -3
- package/src/tools/registry.ts +20 -0
- package/src/tools/schedule/create.ts +7 -12
- package/src/tools/schedule/update.ts +4 -11
- package/src/tools/shared/filesystem/path-policy.ts +39 -13
- package/src/tools/side-effects.ts +2 -17
- package/src/tools/skills/execute.ts +33 -0
- package/src/tools/subagent/spawn.ts +61 -12
- package/src/tools/terminal/shell.ts +10 -4
- package/src/tools/tool-approval-handler.ts +18 -13
- package/src/tools/tool-manifest.ts +0 -2
- package/src/tools/types.ts +9 -0
- package/src/tools/ui-surface/definitions.ts +64 -3
- package/src/tools/verification-control-plane-policy.ts +3 -1
- package/src/tools/workflows/run-workflow.test.ts +8 -18
- package/src/tools/workflows/run-workflow.ts +1 -0
- package/src/util/disk-usage.ts +78 -23
- package/src/util/platform.ts +10 -3
- package/src/watcher/telemetry.ts +2 -2
- package/src/workflows/capabilities.ts +2 -3
- package/src/workflows/engine.test.ts +175 -1
- package/src/workflows/engine.ts +82 -0
- package/src/workflows/journal-store.test.ts +70 -0
- package/src/workflows/journal-store.ts +18 -3
- package/src/workflows/run-manager.test.ts +171 -28
- package/src/workflows/run-manager.ts +66 -24
- package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
- package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
- package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
- package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
- package/src/__tests__/credential-security-e2e.test.ts +0 -362
- package/src/__tests__/credential-vault-unit.test.ts +0 -1528
- package/src/__tests__/credential-vault.test.ts +0 -1706
- package/src/__tests__/identity-intro-cache.test.ts +0 -315
- package/src/__tests__/secret-onetime-send.test.ts +0 -182
- package/src/cli/commands/__tests__/task.test.ts +0 -914
- package/src/cli/commands/task.ts +0 -771
- package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
- package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
- package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
- package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
- package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
- package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
- package/src/memory/preloaded-apps.ts +0 -116
- package/src/notifications/tool-approval-copy.ts +0 -142
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
- package/src/runtime/routes/identity-intro-cache.ts +0 -172
- package/src/tools/credentials/vault.ts +0 -712
|
@@ -1,1528 +0,0 @@
|
|
|
1
|
-
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import {
|
|
6
|
-
afterAll,
|
|
7
|
-
afterEach,
|
|
8
|
-
beforeEach,
|
|
9
|
-
describe,
|
|
10
|
-
expect,
|
|
11
|
-
mock,
|
|
12
|
-
test,
|
|
13
|
-
} from "bun:test";
|
|
14
|
-
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
// Mock logger
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
|
|
19
|
-
mock.module("../util/logger.js", () => ({
|
|
20
|
-
getLogger: () =>
|
|
21
|
-
new Proxy({} as Record<string, unknown>, {
|
|
22
|
-
get: () => () => {},
|
|
23
|
-
}),
|
|
24
|
-
}));
|
|
25
|
-
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
// Use encrypted backend with a temp store path
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
|
|
30
|
-
import { _resetBackend } from "../security/secure-keys.js";
|
|
31
|
-
import { setStorePathForTesting } from "./encrypted-store-test-helpers.js";
|
|
32
|
-
|
|
33
|
-
const TEST_DIR = join(
|
|
34
|
-
tmpdir(),
|
|
35
|
-
`vellum-credvault-unit-${randomBytes(4).toString("hex")}`,
|
|
36
|
-
);
|
|
37
|
-
const STORE_PATH = join(TEST_DIR, "keys.enc");
|
|
38
|
-
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
// Mock registry to avoid double-registration
|
|
41
|
-
// ---------------------------------------------------------------------------
|
|
42
|
-
|
|
43
|
-
mock.module("../tools/registry.js", () => ({
|
|
44
|
-
registerTool: () => {},
|
|
45
|
-
}));
|
|
46
|
-
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
// Mock oauth-store to avoid SQLite dependency in unit tests
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
|
|
51
|
-
let disconnectOAuthProviderCalls: string[] = [];
|
|
52
|
-
|
|
53
|
-
mock.module("../oauth/oauth-store.js", () => ({
|
|
54
|
-
disconnectOAuthProvider: mock(async (provider: string) => {
|
|
55
|
-
disconnectOAuthProviderCalls.push(provider);
|
|
56
|
-
return "not-found" as const;
|
|
57
|
-
}),
|
|
58
|
-
getActiveConnection: mock(() => undefined),
|
|
59
|
-
}));
|
|
60
|
-
|
|
61
|
-
let manualConnectionStore: Record<string, string> = {};
|
|
62
|
-
let slackChannelConfigCalls: Array<{
|
|
63
|
-
botToken?: string;
|
|
64
|
-
appToken?: string;
|
|
65
|
-
userToken?: string;
|
|
66
|
-
}> = [];
|
|
67
|
-
let clearSlackUserTokenCalls = 0;
|
|
68
|
-
|
|
69
|
-
mock.module("../oauth/manual-token-connection.js", () => ({
|
|
70
|
-
syncManualTokenConnection: async (provider: string) => {
|
|
71
|
-
const { credentialKey } = await import("../security/credential-key.js");
|
|
72
|
-
const { getSecureKeyAsync } = await import("../security/secure-keys.js");
|
|
73
|
-
|
|
74
|
-
if (provider === "slack_channel") {
|
|
75
|
-
const hasBotToken = !!(await getSecureKeyAsync(
|
|
76
|
-
credentialKey("slack_channel", "bot_token"),
|
|
77
|
-
));
|
|
78
|
-
const hasAppToken = !!(await getSecureKeyAsync(
|
|
79
|
-
credentialKey("slack_channel", "app_token"),
|
|
80
|
-
));
|
|
81
|
-
if (hasBotToken && hasAppToken) {
|
|
82
|
-
manualConnectionStore[provider] = "active";
|
|
83
|
-
} else {
|
|
84
|
-
delete manualConnectionStore[provider];
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
}));
|
|
89
|
-
|
|
90
|
-
mock.module("../daemon/handlers/config-slack-channel.js", () => ({
|
|
91
|
-
setSlackChannelConfig: async (
|
|
92
|
-
botToken?: string,
|
|
93
|
-
appToken?: string,
|
|
94
|
-
userToken?: string,
|
|
95
|
-
) => {
|
|
96
|
-
slackChannelConfigCalls.push({ botToken, appToken, userToken });
|
|
97
|
-
|
|
98
|
-
const { credentialKey } = await import("../security/credential-key.js");
|
|
99
|
-
const { getSecureKeyAsync, setSecureKeyAsync } =
|
|
100
|
-
await import("../security/secure-keys.js");
|
|
101
|
-
const { upsertCredentialMetadata } =
|
|
102
|
-
await import("../tools/credentials/metadata-store.js");
|
|
103
|
-
|
|
104
|
-
const hasExistingBotToken = !!(await getSecureKeyAsync(
|
|
105
|
-
credentialKey("slack_channel", "bot_token"),
|
|
106
|
-
));
|
|
107
|
-
const hasExistingAppToken = !!(await getSecureKeyAsync(
|
|
108
|
-
credentialKey("slack_channel", "app_token"),
|
|
109
|
-
));
|
|
110
|
-
const hasExistingUserToken = !!(await getSecureKeyAsync(
|
|
111
|
-
credentialKey("slack_channel", "user_token"),
|
|
112
|
-
));
|
|
113
|
-
|
|
114
|
-
if (appToken && !appToken.startsWith("xapp-")) {
|
|
115
|
-
return {
|
|
116
|
-
success: false,
|
|
117
|
-
hasBotToken: hasExistingBotToken,
|
|
118
|
-
hasAppToken: hasExistingAppToken,
|
|
119
|
-
hasUserToken: hasExistingUserToken,
|
|
120
|
-
connected: hasExistingBotToken && hasExistingAppToken,
|
|
121
|
-
error: 'Invalid app token: must start with "xapp-"',
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (userToken && !userToken.startsWith("xoxp-")) {
|
|
126
|
-
return {
|
|
127
|
-
success: false,
|
|
128
|
-
hasBotToken: hasExistingBotToken,
|
|
129
|
-
hasAppToken: hasExistingAppToken,
|
|
130
|
-
hasUserToken: hasExistingUserToken,
|
|
131
|
-
connected: hasExistingBotToken && hasExistingAppToken,
|
|
132
|
-
error: 'Invalid user token: must start with "xoxp-"',
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (botToken === "xoxb-invalid-token") {
|
|
137
|
-
return {
|
|
138
|
-
success: false,
|
|
139
|
-
hasBotToken: hasExistingBotToken,
|
|
140
|
-
hasAppToken: hasExistingAppToken,
|
|
141
|
-
hasUserToken: hasExistingUserToken,
|
|
142
|
-
connected: hasExistingBotToken && hasExistingAppToken,
|
|
143
|
-
error: "Slack API validation failed: invalid_auth",
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (botToken) {
|
|
148
|
-
await setSecureKeyAsync(
|
|
149
|
-
credentialKey("slack_channel", "bot_token"),
|
|
150
|
-
botToken,
|
|
151
|
-
);
|
|
152
|
-
upsertCredentialMetadata("slack_channel", "bot_token", {});
|
|
153
|
-
}
|
|
154
|
-
if (appToken) {
|
|
155
|
-
await setSecureKeyAsync(
|
|
156
|
-
credentialKey("slack_channel", "app_token"),
|
|
157
|
-
appToken,
|
|
158
|
-
);
|
|
159
|
-
upsertCredentialMetadata("slack_channel", "app_token", {});
|
|
160
|
-
}
|
|
161
|
-
if (userToken) {
|
|
162
|
-
await setSecureKeyAsync(
|
|
163
|
-
credentialKey("slack_channel", "user_token"),
|
|
164
|
-
userToken,
|
|
165
|
-
);
|
|
166
|
-
upsertCredentialMetadata("slack_channel", "user_token", {});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const hasBotToken = !!(await getSecureKeyAsync(
|
|
170
|
-
credentialKey("slack_channel", "bot_token"),
|
|
171
|
-
));
|
|
172
|
-
const hasAppToken = !!(await getSecureKeyAsync(
|
|
173
|
-
credentialKey("slack_channel", "app_token"),
|
|
174
|
-
));
|
|
175
|
-
const hasUserToken = !!(await getSecureKeyAsync(
|
|
176
|
-
credentialKey("slack_channel", "user_token"),
|
|
177
|
-
));
|
|
178
|
-
|
|
179
|
-
if (hasBotToken && hasAppToken) {
|
|
180
|
-
manualConnectionStore["slack_channel"] = "active";
|
|
181
|
-
} else {
|
|
182
|
-
delete manualConnectionStore["slack_channel"];
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const warning =
|
|
186
|
-
hasBotToken && !hasAppToken
|
|
187
|
-
? "Bot token stored but app token is missing - connection incomplete."
|
|
188
|
-
: !hasBotToken && hasAppToken
|
|
189
|
-
? "App token stored but bot token is missing - connection incomplete."
|
|
190
|
-
: undefined;
|
|
191
|
-
|
|
192
|
-
return {
|
|
193
|
-
success: true,
|
|
194
|
-
hasBotToken,
|
|
195
|
-
hasAppToken,
|
|
196
|
-
hasUserToken,
|
|
197
|
-
connected: hasBotToken && hasAppToken,
|
|
198
|
-
teamName: hasBotToken ? "Test Team" : undefined,
|
|
199
|
-
botUsername: hasBotToken ? "testbot" : undefined,
|
|
200
|
-
warning,
|
|
201
|
-
};
|
|
202
|
-
},
|
|
203
|
-
clearSlackUserToken: async () => {
|
|
204
|
-
clearSlackUserTokenCalls++;
|
|
205
|
-
|
|
206
|
-
const { credentialKey } = await import("../security/credential-key.js");
|
|
207
|
-
const { deleteSecureKeyAsync, getSecureKeyAsync } =
|
|
208
|
-
await import("../security/secure-keys.js");
|
|
209
|
-
const { deleteCredentialMetadata } =
|
|
210
|
-
await import("../tools/credentials/metadata-store.js");
|
|
211
|
-
|
|
212
|
-
await deleteSecureKeyAsync(credentialKey("slack_channel", "user_token"));
|
|
213
|
-
deleteCredentialMetadata("slack_channel", "user_token");
|
|
214
|
-
|
|
215
|
-
const hasBotToken = !!(await getSecureKeyAsync(
|
|
216
|
-
credentialKey("slack_channel", "bot_token"),
|
|
217
|
-
));
|
|
218
|
-
const hasAppToken = !!(await getSecureKeyAsync(
|
|
219
|
-
credentialKey("slack_channel", "app_token"),
|
|
220
|
-
));
|
|
221
|
-
|
|
222
|
-
return {
|
|
223
|
-
success: true,
|
|
224
|
-
hasBotToken,
|
|
225
|
-
hasAppToken,
|
|
226
|
-
hasUserToken: false,
|
|
227
|
-
connected:
|
|
228
|
-
manualConnectionStore["slack_channel"] === "active" &&
|
|
229
|
-
hasBotToken &&
|
|
230
|
-
hasAppToken,
|
|
231
|
-
};
|
|
232
|
-
},
|
|
233
|
-
}));
|
|
234
|
-
|
|
235
|
-
// ---------------------------------------------------------------------------
|
|
236
|
-
// Imports under test
|
|
237
|
-
// ---------------------------------------------------------------------------
|
|
238
|
-
|
|
239
|
-
import { credentialKey } from "../security/credential-key.js";
|
|
240
|
-
import {
|
|
241
|
-
getSecureKeyAsync,
|
|
242
|
-
setSecureKeyAsync,
|
|
243
|
-
} from "../security/secure-keys.js";
|
|
244
|
-
import { CredentialBroker } from "../tools/credentials/broker.js";
|
|
245
|
-
import {
|
|
246
|
-
_setMetadataPath,
|
|
247
|
-
upsertCredentialMetadata,
|
|
248
|
-
} from "../tools/credentials/metadata-store.js";
|
|
249
|
-
import { BROWSER_FILL_CAPABILITY } from "../tools/credentials/tool-policy.js";
|
|
250
|
-
import { credentialStoreTool } from "../tools/credentials/vault.js";
|
|
251
|
-
import type { ToolContext } from "../tools/types.js";
|
|
252
|
-
|
|
253
|
-
const _ctx: ToolContext = {
|
|
254
|
-
workingDir: "/tmp",
|
|
255
|
-
conversationId: "test-conv",
|
|
256
|
-
trustClass: "guardian",
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
beforeEach(() => {
|
|
260
|
-
manualConnectionStore = {};
|
|
261
|
-
slackChannelConfigCalls = [];
|
|
262
|
-
clearSlackUserTokenCalls = 0;
|
|
263
|
-
disconnectOAuthProviderCalls = [];
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
afterAll(() => {
|
|
267
|
-
mock.restore();
|
|
268
|
-
if (existsSync(TEST_DIR)) {
|
|
269
|
-
rmSync(TEST_DIR, { recursive: true });
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
// ---------------------------------------------------------------------------
|
|
274
|
-
// 1. Broker — Transient (one-time) credential injection and consumption
|
|
275
|
-
// ---------------------------------------------------------------------------
|
|
276
|
-
|
|
277
|
-
describe("CredentialBroker transient credentials", () => {
|
|
278
|
-
let broker: CredentialBroker;
|
|
279
|
-
|
|
280
|
-
beforeEach(() => {
|
|
281
|
-
if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
|
|
282
|
-
mkdirSync(TEST_DIR, { recursive: true });
|
|
283
|
-
setStorePathForTesting(STORE_PATH);
|
|
284
|
-
_resetBackend();
|
|
285
|
-
_setMetadataPath(join(TEST_DIR, "metadata.json"));
|
|
286
|
-
broker = new CredentialBroker();
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
afterEach(() => {
|
|
290
|
-
_setMetadataPath(null);
|
|
291
|
-
setStorePathForTesting(null);
|
|
292
|
-
_resetBackend();
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
test("consume returns transient value and deletes it", () => {
|
|
296
|
-
upsertCredentialMetadata("svc", "key", { allowedTools: ["tool1"] });
|
|
297
|
-
broker.injectTransient("svc", "key", "one-time-secret");
|
|
298
|
-
|
|
299
|
-
const auth = broker.authorize({
|
|
300
|
-
service: "svc",
|
|
301
|
-
field: "key",
|
|
302
|
-
toolName: "tool1",
|
|
303
|
-
});
|
|
304
|
-
expect(auth.authorized).toBe(true);
|
|
305
|
-
if (!auth.authorized) return;
|
|
306
|
-
|
|
307
|
-
const result = broker.consume(auth.token.tokenId);
|
|
308
|
-
expect(result.success).toBe(true);
|
|
309
|
-
expect(result.value).toBe("one-time-secret");
|
|
310
|
-
expect(result.storageKey).toBe(credentialKey("svc", "key"));
|
|
311
|
-
|
|
312
|
-
// Second authorize + consume should NOT have the transient value
|
|
313
|
-
const auth2 = broker.authorize({
|
|
314
|
-
service: "svc",
|
|
315
|
-
field: "key",
|
|
316
|
-
toolName: "tool1",
|
|
317
|
-
});
|
|
318
|
-
expect(auth2.authorized).toBe(true);
|
|
319
|
-
if (!auth2.authorized) return;
|
|
320
|
-
const result2 = broker.consume(auth2.token.tokenId);
|
|
321
|
-
expect(result2.success).toBe(true);
|
|
322
|
-
// No transient value — falls back to storage key only
|
|
323
|
-
expect(result2.value).toBeUndefined();
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
test("browserFill uses transient value when available", async () => {
|
|
327
|
-
upsertCredentialMetadata("github", "token", {
|
|
328
|
-
allowedTools: ["browser_fill_credential"],
|
|
329
|
-
});
|
|
330
|
-
broker.injectTransient("github", "token", "transient-ghp-123");
|
|
331
|
-
|
|
332
|
-
let filledValue: string | undefined;
|
|
333
|
-
const result = await broker.browserFill({
|
|
334
|
-
service: "github",
|
|
335
|
-
field: "token",
|
|
336
|
-
toolName: "browser_fill_credential",
|
|
337
|
-
fill: async (v) => {
|
|
338
|
-
filledValue = v;
|
|
339
|
-
},
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
expect(result.success).toBe(true);
|
|
343
|
-
expect(filledValue).toBe("transient-ghp-123");
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
test("browserFill consumes transient value — second fill falls back to stored", async () => {
|
|
347
|
-
upsertCredentialMetadata("github", "token", {
|
|
348
|
-
allowedTools: ["browser_fill_credential"],
|
|
349
|
-
});
|
|
350
|
-
await setSecureKeyAsync(credentialKey("github", "token"), "stored-value");
|
|
351
|
-
broker.injectTransient("github", "token", "transient-value");
|
|
352
|
-
|
|
353
|
-
// First fill uses transient
|
|
354
|
-
let filled1: string | undefined;
|
|
355
|
-
await broker.browserFill({
|
|
356
|
-
service: "github",
|
|
357
|
-
field: "token",
|
|
358
|
-
toolName: "browser_fill_credential",
|
|
359
|
-
fill: async (v) => {
|
|
360
|
-
filled1 = v;
|
|
361
|
-
},
|
|
362
|
-
});
|
|
363
|
-
expect(filled1).toBe("transient-value");
|
|
364
|
-
|
|
365
|
-
// Second fill falls back to stored value
|
|
366
|
-
let filled2: string | undefined;
|
|
367
|
-
await broker.browserFill({
|
|
368
|
-
service: "github",
|
|
369
|
-
field: "token",
|
|
370
|
-
toolName: "browser_fill_credential",
|
|
371
|
-
fill: async (v) => {
|
|
372
|
-
filled2 = v;
|
|
373
|
-
},
|
|
374
|
-
});
|
|
375
|
-
expect(filled2).toBe("stored-value");
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
test("browserFill preserves transient value on fill failure", async () => {
|
|
379
|
-
upsertCredentialMetadata("github", "token", {
|
|
380
|
-
allowedTools: ["browser_fill_credential"],
|
|
381
|
-
});
|
|
382
|
-
broker.injectTransient("github", "token", "transient-preserved");
|
|
383
|
-
|
|
384
|
-
// First fill fails
|
|
385
|
-
const result1 = await broker.browserFill({
|
|
386
|
-
service: "github",
|
|
387
|
-
field: "token",
|
|
388
|
-
toolName: "browser_fill_credential",
|
|
389
|
-
fill: async () => {
|
|
390
|
-
throw new Error("Playwright timeout");
|
|
391
|
-
},
|
|
392
|
-
});
|
|
393
|
-
expect(result1.success).toBe(false);
|
|
394
|
-
|
|
395
|
-
// Second fill should still have the transient value
|
|
396
|
-
let filled: string | undefined;
|
|
397
|
-
const result2 = await broker.browserFill({
|
|
398
|
-
service: "github",
|
|
399
|
-
field: "token",
|
|
400
|
-
toolName: "browser_fill_credential",
|
|
401
|
-
fill: async (v) => {
|
|
402
|
-
filled = v;
|
|
403
|
-
},
|
|
404
|
-
});
|
|
405
|
-
expect(result2.success).toBe(true);
|
|
406
|
-
expect(filled).toBe("transient-preserved");
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
test("serverUse uses transient value when available", async () => {
|
|
410
|
-
upsertCredentialMetadata("vercel", "api_token", {
|
|
411
|
-
allowedTools: ["deploy"],
|
|
412
|
-
});
|
|
413
|
-
broker.injectTransient("vercel", "api_token", "transient-vercel-tok");
|
|
414
|
-
|
|
415
|
-
const result = await broker.serverUse({
|
|
416
|
-
service: "vercel",
|
|
417
|
-
field: "api_token",
|
|
418
|
-
toolName: "deploy",
|
|
419
|
-
execute: async (v) => {
|
|
420
|
-
expect(v).toBe("transient-vercel-tok");
|
|
421
|
-
return "deployed";
|
|
422
|
-
},
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
expect(result.success).toBe(true);
|
|
426
|
-
expect(result.result).toBe("deployed");
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
test("serverUse consumes transient — subsequent call has no value without stored key", async () => {
|
|
430
|
-
upsertCredentialMetadata("vercel", "api_token", {
|
|
431
|
-
allowedTools: ["deploy"],
|
|
432
|
-
});
|
|
433
|
-
// Only transient, no stored value
|
|
434
|
-
broker.injectTransient("vercel", "api_token", "transient-only");
|
|
435
|
-
|
|
436
|
-
await broker.serverUse({
|
|
437
|
-
service: "vercel",
|
|
438
|
-
field: "api_token",
|
|
439
|
-
toolName: "deploy",
|
|
440
|
-
execute: async () => "ok",
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
// Second call: no transient, no stored value
|
|
444
|
-
const result = await broker.serverUse({
|
|
445
|
-
service: "vercel",
|
|
446
|
-
field: "api_token",
|
|
447
|
-
toolName: "deploy",
|
|
448
|
-
execute: async () => {
|
|
449
|
-
throw new Error("should not be called");
|
|
450
|
-
},
|
|
451
|
-
});
|
|
452
|
-
expect(result.success).toBe(false);
|
|
453
|
-
expect(result.reason).toContain("no stored value");
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
test("injectTransient replaces previous transient for same key", () => {
|
|
457
|
-
upsertCredentialMetadata("svc", "key", { allowedTools: ["t"] });
|
|
458
|
-
broker.injectTransient("svc", "key", "first");
|
|
459
|
-
broker.injectTransient("svc", "key", "second");
|
|
460
|
-
|
|
461
|
-
const auth = broker.authorize({
|
|
462
|
-
service: "svc",
|
|
463
|
-
field: "key",
|
|
464
|
-
toolName: "t",
|
|
465
|
-
});
|
|
466
|
-
if (!auth.authorized) return;
|
|
467
|
-
const result = broker.consume(auth.token.tokenId);
|
|
468
|
-
expect(result.value).toBe("second");
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
test("transient value for one credential does not affect another", () => {
|
|
472
|
-
upsertCredentialMetadata("svcA", "key", { allowedTools: ["t"] });
|
|
473
|
-
upsertCredentialMetadata("svcB", "key", { allowedTools: ["t"] });
|
|
474
|
-
broker.injectTransient("svcA", "key", "val-a");
|
|
475
|
-
|
|
476
|
-
// svcB should not have a transient value — consume returns storageKey only
|
|
477
|
-
const authB = broker.authorize({
|
|
478
|
-
service: "svcB",
|
|
479
|
-
field: "key",
|
|
480
|
-
toolName: "t",
|
|
481
|
-
});
|
|
482
|
-
if (!authB.authorized) return;
|
|
483
|
-
const resultB = broker.consume(authB.token.tokenId);
|
|
484
|
-
expect(resultB.success).toBe(true);
|
|
485
|
-
expect(resultB.value).toBeUndefined();
|
|
486
|
-
|
|
487
|
-
// svcA should have the transient
|
|
488
|
-
const authA = broker.authorize({
|
|
489
|
-
service: "svcA",
|
|
490
|
-
field: "key",
|
|
491
|
-
toolName: "t",
|
|
492
|
-
});
|
|
493
|
-
if (!authA.authorized) return;
|
|
494
|
-
const resultA = broker.consume(authA.token.tokenId);
|
|
495
|
-
expect(resultA.value).toBe("val-a");
|
|
496
|
-
});
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
// ---------------------------------------------------------------------------
|
|
500
|
-
// 2. Vault — unknown action handling
|
|
501
|
-
// ---------------------------------------------------------------------------
|
|
502
|
-
|
|
503
|
-
describe("credential_store tool — unknown action", () => {
|
|
504
|
-
beforeEach(() => {
|
|
505
|
-
if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
|
|
506
|
-
mkdirSync(TEST_DIR, { recursive: true });
|
|
507
|
-
setStorePathForTesting(STORE_PATH);
|
|
508
|
-
_resetBackend();
|
|
509
|
-
_setMetadataPath(join(TEST_DIR, "metadata.json"));
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
afterEach(() => {
|
|
513
|
-
_setMetadataPath(null);
|
|
514
|
-
setStorePathForTesting(null);
|
|
515
|
-
_resetBackend();
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
test("returns error for unknown action", async () => {
|
|
519
|
-
const result = await credentialStoreTool.execute(
|
|
520
|
-
{ action: "unknown_action" },
|
|
521
|
-
_ctx,
|
|
522
|
-
);
|
|
523
|
-
expect(result.isError).toBe(true);
|
|
524
|
-
expect(result.content).toContain("unknown action");
|
|
525
|
-
expect(result.content).toContain("unknown_action");
|
|
526
|
-
});
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
// ---------------------------------------------------------------------------
|
|
530
|
-
// 3. Vault — prompt action edge cases
|
|
531
|
-
// ---------------------------------------------------------------------------
|
|
532
|
-
|
|
533
|
-
describe("credential_store tool — prompt action", () => {
|
|
534
|
-
beforeEach(() => {
|
|
535
|
-
if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
|
|
536
|
-
mkdirSync(TEST_DIR, { recursive: true });
|
|
537
|
-
setStorePathForTesting(STORE_PATH);
|
|
538
|
-
_resetBackend();
|
|
539
|
-
_setMetadataPath(join(TEST_DIR, "metadata.json"));
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
afterEach(() => {
|
|
543
|
-
_setMetadataPath(null);
|
|
544
|
-
setStorePathForTesting(null);
|
|
545
|
-
_resetBackend();
|
|
546
|
-
});
|
|
547
|
-
|
|
548
|
-
test("returns error when requestSecret is not available", async () => {
|
|
549
|
-
const result = await credentialStoreTool.execute(
|
|
550
|
-
{ action: "prompt", service: "svc", field: "key", label: "API Key" },
|
|
551
|
-
_ctx, // no requestSecret
|
|
552
|
-
);
|
|
553
|
-
expect(result.isError).toBe(true);
|
|
554
|
-
expect(result.content).toContain("not available");
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
test("returns error when service is missing for prompt", async () => {
|
|
558
|
-
const result = await credentialStoreTool.execute(
|
|
559
|
-
{ action: "prompt", field: "key" },
|
|
560
|
-
_ctx,
|
|
561
|
-
);
|
|
562
|
-
expect(result.isError).toBe(true);
|
|
563
|
-
expect(result.content).toContain("service is required");
|
|
564
|
-
});
|
|
565
|
-
|
|
566
|
-
test("returns error when field is missing for prompt", async () => {
|
|
567
|
-
const result = await credentialStoreTool.execute(
|
|
568
|
-
{ action: "prompt", service: "svc" },
|
|
569
|
-
_ctx,
|
|
570
|
-
);
|
|
571
|
-
expect(result.isError).toBe(true);
|
|
572
|
-
expect(result.content).toContain("field is required");
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
test("handles user cancellation (null value)", async () => {
|
|
576
|
-
const ctxWithPrompt: ToolContext = {
|
|
577
|
-
..._ctx,
|
|
578
|
-
requestSecret: async () => ({
|
|
579
|
-
value: null as unknown as string,
|
|
580
|
-
delivery: "store" as const,
|
|
581
|
-
}),
|
|
582
|
-
};
|
|
583
|
-
const result = await credentialStoreTool.execute(
|
|
584
|
-
{ action: "prompt", service: "svc", field: "key", label: "Test" },
|
|
585
|
-
ctxWithPrompt,
|
|
586
|
-
);
|
|
587
|
-
expect(result.isError).toBe(false);
|
|
588
|
-
expect(result.content).toContain("cancelled");
|
|
589
|
-
});
|
|
590
|
-
|
|
591
|
-
test("stores credential when user provides value via prompt", async () => {
|
|
592
|
-
const ctxWithPrompt: ToolContext = {
|
|
593
|
-
..._ctx,
|
|
594
|
-
requestSecret: async () => ({
|
|
595
|
-
value: "prompt-secret-val",
|
|
596
|
-
delivery: "store" as const,
|
|
597
|
-
}),
|
|
598
|
-
};
|
|
599
|
-
const result = await credentialStoreTool.execute(
|
|
600
|
-
{
|
|
601
|
-
action: "prompt",
|
|
602
|
-
service: "test-prompt",
|
|
603
|
-
field: "api_key",
|
|
604
|
-
label: "API Key",
|
|
605
|
-
},
|
|
606
|
-
ctxWithPrompt,
|
|
607
|
-
);
|
|
608
|
-
expect(result.isError).toBe(false);
|
|
609
|
-
expect(result.content).toContain("test-prompt/api_key");
|
|
610
|
-
expect(result.content).not.toContain("prompt-secret-val");
|
|
611
|
-
|
|
612
|
-
// Verify stored
|
|
613
|
-
expect(
|
|
614
|
-
await getSecureKeyAsync(credentialKey("test-prompt", "api_key")),
|
|
615
|
-
).toBe("prompt-secret-val");
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
test("prompt with policy fields persists metadata", async () => {
|
|
619
|
-
const ctxWithPrompt: ToolContext = {
|
|
620
|
-
..._ctx,
|
|
621
|
-
requestSecret: async () => ({
|
|
622
|
-
value: "prompt-val",
|
|
623
|
-
delivery: "store" as const,
|
|
624
|
-
}),
|
|
625
|
-
};
|
|
626
|
-
const result = await credentialStoreTool.execute(
|
|
627
|
-
{
|
|
628
|
-
action: "prompt",
|
|
629
|
-
service: "github",
|
|
630
|
-
field: "token",
|
|
631
|
-
label: "GitHub Token",
|
|
632
|
-
allowed_tools: ["browser_fill_credential"],
|
|
633
|
-
allowed_domains: ["github.com"],
|
|
634
|
-
usage_description: "GitHub login",
|
|
635
|
-
},
|
|
636
|
-
ctxWithPrompt,
|
|
637
|
-
);
|
|
638
|
-
expect(result.isError).toBe(false);
|
|
639
|
-
|
|
640
|
-
const { getCredentialMetadata } =
|
|
641
|
-
await import("../tools/credentials/metadata-store.js");
|
|
642
|
-
const meta = getCredentialMetadata("github", "token");
|
|
643
|
-
expect(meta).toBeDefined();
|
|
644
|
-
expect(meta!.allowedTools).toEqual(["browser_fill_credential"]);
|
|
645
|
-
expect(meta!.allowedDomains).toEqual(["github.com"]);
|
|
646
|
-
expect(meta!.usageDescription).toBe("GitHub login");
|
|
647
|
-
});
|
|
648
|
-
|
|
649
|
-
test("chat-style slack_channel prompts create the manual connection once both tokens exist", async () => {
|
|
650
|
-
const promptValues = ["xapp-test-token", "xoxb-test-token"];
|
|
651
|
-
const ctxWithPrompt: ToolContext = {
|
|
652
|
-
..._ctx,
|
|
653
|
-
requestSecret: async () => ({
|
|
654
|
-
value: promptValues.shift() ?? "",
|
|
655
|
-
delivery: "store" as const,
|
|
656
|
-
}),
|
|
657
|
-
};
|
|
658
|
-
|
|
659
|
-
const appResult = await credentialStoreTool.execute(
|
|
660
|
-
{
|
|
661
|
-
action: "prompt",
|
|
662
|
-
service: "slack_channel",
|
|
663
|
-
field: "app_token",
|
|
664
|
-
label: "App-Level Token",
|
|
665
|
-
},
|
|
666
|
-
ctxWithPrompt,
|
|
667
|
-
);
|
|
668
|
-
expect(appResult.isError).toBe(false);
|
|
669
|
-
expect(manualConnectionStore["slack_channel"]).toBeUndefined();
|
|
670
|
-
expect(slackChannelConfigCalls).toEqual([{ appToken: "xapp-test-token" }]);
|
|
671
|
-
expect(appResult.content).toContain("connection incomplete");
|
|
672
|
-
|
|
673
|
-
const botResult = await credentialStoreTool.execute(
|
|
674
|
-
{
|
|
675
|
-
action: "prompt",
|
|
676
|
-
service: "slack_channel",
|
|
677
|
-
field: "bot_token",
|
|
678
|
-
label: "Bot User OAuth Token",
|
|
679
|
-
},
|
|
680
|
-
ctxWithPrompt,
|
|
681
|
-
);
|
|
682
|
-
expect(botResult.isError).toBe(false);
|
|
683
|
-
expect(manualConnectionStore["slack_channel"]).toBe("active");
|
|
684
|
-
expect(slackChannelConfigCalls).toEqual([
|
|
685
|
-
{ appToken: "xapp-test-token" },
|
|
686
|
-
{ botToken: "xoxb-test-token" },
|
|
687
|
-
]);
|
|
688
|
-
expect(botResult.content).toContain(
|
|
689
|
-
"Slack channel connected to Test Team (@testbot).",
|
|
690
|
-
);
|
|
691
|
-
});
|
|
692
|
-
|
|
693
|
-
test("slack_channel prompt rejects transient send delivery", async () => {
|
|
694
|
-
const ctxWithPrompt: ToolContext = {
|
|
695
|
-
..._ctx,
|
|
696
|
-
requestSecret: async () => ({
|
|
697
|
-
value: "xapp-test-token",
|
|
698
|
-
delivery: "transient_send" as const,
|
|
699
|
-
}),
|
|
700
|
-
};
|
|
701
|
-
|
|
702
|
-
const result = await credentialStoreTool.execute(
|
|
703
|
-
{
|
|
704
|
-
action: "prompt",
|
|
705
|
-
service: "slack_channel",
|
|
706
|
-
field: "app_token",
|
|
707
|
-
label: "App-Level Token",
|
|
708
|
-
},
|
|
709
|
-
ctxWithPrompt,
|
|
710
|
-
);
|
|
711
|
-
|
|
712
|
-
expect(result.isError).toBe(true);
|
|
713
|
-
expect(result.content).toContain("must be saved to secure storage");
|
|
714
|
-
expect(slackChannelConfigCalls).toEqual([]);
|
|
715
|
-
});
|
|
716
|
-
|
|
717
|
-
test("slack_channel bot token prompt fails through the settings handler", async () => {
|
|
718
|
-
const promptValues = ["xapp-test-token", "xoxb-invalid-token"];
|
|
719
|
-
const ctxWithPrompt: ToolContext = {
|
|
720
|
-
..._ctx,
|
|
721
|
-
requestSecret: async () => ({
|
|
722
|
-
value: promptValues.shift() ?? "",
|
|
723
|
-
delivery: "store" as const,
|
|
724
|
-
}),
|
|
725
|
-
};
|
|
726
|
-
|
|
727
|
-
const appResult = await credentialStoreTool.execute(
|
|
728
|
-
{
|
|
729
|
-
action: "prompt",
|
|
730
|
-
service: "slack_channel",
|
|
731
|
-
field: "app_token",
|
|
732
|
-
label: "App-Level Token",
|
|
733
|
-
},
|
|
734
|
-
ctxWithPrompt,
|
|
735
|
-
);
|
|
736
|
-
expect(appResult.isError).toBe(false);
|
|
737
|
-
|
|
738
|
-
const botResult = await credentialStoreTool.execute(
|
|
739
|
-
{
|
|
740
|
-
action: "prompt",
|
|
741
|
-
service: "slack_channel",
|
|
742
|
-
field: "bot_token",
|
|
743
|
-
label: "Bot User OAuth Token",
|
|
744
|
-
},
|
|
745
|
-
ctxWithPrompt,
|
|
746
|
-
);
|
|
747
|
-
|
|
748
|
-
expect(botResult.isError).toBe(true);
|
|
749
|
-
expect(botResult.content).toContain("invalid_auth");
|
|
750
|
-
expect(
|
|
751
|
-
await getSecureKeyAsync(credentialKey("slack_channel", "bot_token")),
|
|
752
|
-
).toBeUndefined();
|
|
753
|
-
});
|
|
754
|
-
|
|
755
|
-
test("slack_channel user_token prompt routes through the settings handler", async () => {
|
|
756
|
-
const ctxWithPrompt: ToolContext = {
|
|
757
|
-
..._ctx,
|
|
758
|
-
requestSecret: async () => ({
|
|
759
|
-
value: "xoxp-valid-user-token",
|
|
760
|
-
delivery: "store" as const,
|
|
761
|
-
}),
|
|
762
|
-
};
|
|
763
|
-
|
|
764
|
-
const result = await credentialStoreTool.execute(
|
|
765
|
-
{
|
|
766
|
-
action: "prompt",
|
|
767
|
-
service: "slack_channel",
|
|
768
|
-
field: "user_token",
|
|
769
|
-
label: "User OAuth Token",
|
|
770
|
-
},
|
|
771
|
-
ctxWithPrompt,
|
|
772
|
-
);
|
|
773
|
-
|
|
774
|
-
expect(result.isError).toBe(false);
|
|
775
|
-
// Routed to handler as third positional argument.
|
|
776
|
-
expect(slackChannelConfigCalls).toEqual([
|
|
777
|
-
{
|
|
778
|
-
botToken: undefined,
|
|
779
|
-
appToken: undefined,
|
|
780
|
-
userToken: "xoxp-valid-user-token",
|
|
781
|
-
},
|
|
782
|
-
]);
|
|
783
|
-
// Stored via the handler's mock, NOT via the generic setSecureKeyAsync path.
|
|
784
|
-
expect(
|
|
785
|
-
await getSecureKeyAsync(credentialKey("slack_channel", "user_token")),
|
|
786
|
-
).toBe("xoxp-valid-user-token");
|
|
787
|
-
});
|
|
788
|
-
|
|
789
|
-
test("slack_channel user_token prompt surfaces handler rejection for malformed token", async () => {
|
|
790
|
-
const ctxWithPrompt: ToolContext = {
|
|
791
|
-
..._ctx,
|
|
792
|
-
requestSecret: async () => ({
|
|
793
|
-
value: "abc-123",
|
|
794
|
-
delivery: "store" as const,
|
|
795
|
-
}),
|
|
796
|
-
};
|
|
797
|
-
|
|
798
|
-
const result = await credentialStoreTool.execute(
|
|
799
|
-
{
|
|
800
|
-
action: "prompt",
|
|
801
|
-
service: "slack_channel",
|
|
802
|
-
field: "user_token",
|
|
803
|
-
label: "User OAuth Token",
|
|
804
|
-
},
|
|
805
|
-
ctxWithPrompt,
|
|
806
|
-
);
|
|
807
|
-
|
|
808
|
-
expect(result.isError).toBe(true);
|
|
809
|
-
expect(result.content).toContain('must start with "xoxp-"');
|
|
810
|
-
// Handler was called with malformed token.
|
|
811
|
-
expect(slackChannelConfigCalls).toEqual([
|
|
812
|
-
{ botToken: undefined, appToken: undefined, userToken: "abc-123" },
|
|
813
|
-
]);
|
|
814
|
-
// Value was NOT persisted.
|
|
815
|
-
expect(
|
|
816
|
-
await getSecureKeyAsync(credentialKey("slack_channel", "user_token")),
|
|
817
|
-
).toBeUndefined();
|
|
818
|
-
});
|
|
819
|
-
|
|
820
|
-
test("prompt rejects invalid policy input", async () => {
|
|
821
|
-
const ctxWithPrompt: ToolContext = {
|
|
822
|
-
..._ctx,
|
|
823
|
-
requestSecret: async () => ({ value: "val", delivery: "store" as const }),
|
|
824
|
-
};
|
|
825
|
-
const result = await credentialStoreTool.execute(
|
|
826
|
-
{
|
|
827
|
-
action: "prompt",
|
|
828
|
-
service: "svc",
|
|
829
|
-
field: "key",
|
|
830
|
-
label: "Test",
|
|
831
|
-
allowed_tools: "not-an-array",
|
|
832
|
-
},
|
|
833
|
-
ctxWithPrompt,
|
|
834
|
-
);
|
|
835
|
-
expect(result.isError).toBe(true);
|
|
836
|
-
expect(result.content).toContain("allowed_tools must be an array");
|
|
837
|
-
});
|
|
838
|
-
});
|
|
839
|
-
|
|
840
|
-
// ---------------------------------------------------------------------------
|
|
841
|
-
// 3b. Vault — slack_channel store routing
|
|
842
|
-
// ---------------------------------------------------------------------------
|
|
843
|
-
|
|
844
|
-
describe("credential_store tool — slack_channel store routing", () => {
|
|
845
|
-
beforeEach(() => {
|
|
846
|
-
if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
|
|
847
|
-
mkdirSync(TEST_DIR, { recursive: true });
|
|
848
|
-
setStorePathForTesting(STORE_PATH);
|
|
849
|
-
_resetBackend();
|
|
850
|
-
_setMetadataPath(join(TEST_DIR, "metadata.json"));
|
|
851
|
-
});
|
|
852
|
-
|
|
853
|
-
afterEach(() => {
|
|
854
|
-
_setMetadataPath(null);
|
|
855
|
-
setStorePathForTesting(null);
|
|
856
|
-
_resetBackend();
|
|
857
|
-
});
|
|
858
|
-
|
|
859
|
-
test("store with user_token routes to setSlackChannelConfig as third positional arg", async () => {
|
|
860
|
-
const result = await credentialStoreTool.execute(
|
|
861
|
-
{
|
|
862
|
-
action: "store",
|
|
863
|
-
service: "slack_channel",
|
|
864
|
-
field: "user_token",
|
|
865
|
-
value: "xoxp-valid-user-token",
|
|
866
|
-
},
|
|
867
|
-
_ctx,
|
|
868
|
-
);
|
|
869
|
-
|
|
870
|
-
expect(result.isError).toBe(false);
|
|
871
|
-
// Exactly one handler call with (undefined, undefined, token).
|
|
872
|
-
expect(slackChannelConfigCalls).toEqual([
|
|
873
|
-
{
|
|
874
|
-
botToken: undefined,
|
|
875
|
-
appToken: undefined,
|
|
876
|
-
userToken: "xoxp-valid-user-token",
|
|
877
|
-
},
|
|
878
|
-
]);
|
|
879
|
-
// Stored through the handler's mock path, not the generic setSecureKeyAsync path.
|
|
880
|
-
expect(
|
|
881
|
-
await getSecureKeyAsync(credentialKey("slack_channel", "user_token")),
|
|
882
|
-
).toBe("xoxp-valid-user-token");
|
|
883
|
-
});
|
|
884
|
-
|
|
885
|
-
test("store with user_token surfaces handler rejection for malformed token", async () => {
|
|
886
|
-
const result = await credentialStoreTool.execute(
|
|
887
|
-
{
|
|
888
|
-
action: "store",
|
|
889
|
-
service: "slack_channel",
|
|
890
|
-
field: "user_token",
|
|
891
|
-
value: "abc-123",
|
|
892
|
-
},
|
|
893
|
-
_ctx,
|
|
894
|
-
);
|
|
895
|
-
|
|
896
|
-
expect(result.isError).toBe(true);
|
|
897
|
-
expect(result.content).toContain('must start with "xoxp-"');
|
|
898
|
-
// Handler was called with the malformed value.
|
|
899
|
-
expect(slackChannelConfigCalls).toEqual([
|
|
900
|
-
{ botToken: undefined, appToken: undefined, userToken: "abc-123" },
|
|
901
|
-
]);
|
|
902
|
-
// Nothing was persisted.
|
|
903
|
-
expect(
|
|
904
|
-
await getSecureKeyAsync(credentialKey("slack_channel", "user_token")),
|
|
905
|
-
).toBeUndefined();
|
|
906
|
-
});
|
|
907
|
-
|
|
908
|
-
test("store with bot_token still routes via first positional arg", async () => {
|
|
909
|
-
const result = await credentialStoreTool.execute(
|
|
910
|
-
{
|
|
911
|
-
action: "store",
|
|
912
|
-
service: "slack_channel",
|
|
913
|
-
field: "bot_token",
|
|
914
|
-
value: "xoxb-valid-bot-token",
|
|
915
|
-
},
|
|
916
|
-
_ctx,
|
|
917
|
-
);
|
|
918
|
-
|
|
919
|
-
expect(result.isError).toBe(false);
|
|
920
|
-
expect(slackChannelConfigCalls).toEqual([
|
|
921
|
-
{
|
|
922
|
-
botToken: "xoxb-valid-bot-token",
|
|
923
|
-
appToken: undefined,
|
|
924
|
-
userToken: undefined,
|
|
925
|
-
},
|
|
926
|
-
]);
|
|
927
|
-
});
|
|
928
|
-
|
|
929
|
-
test("store with app_token still routes via second positional arg", async () => {
|
|
930
|
-
const result = await credentialStoreTool.execute(
|
|
931
|
-
{
|
|
932
|
-
action: "store",
|
|
933
|
-
service: "slack_channel",
|
|
934
|
-
field: "app_token",
|
|
935
|
-
value: "xapp-valid-app-token",
|
|
936
|
-
},
|
|
937
|
-
_ctx,
|
|
938
|
-
);
|
|
939
|
-
|
|
940
|
-
expect(result.isError).toBe(false);
|
|
941
|
-
expect(slackChannelConfigCalls).toEqual([
|
|
942
|
-
{
|
|
943
|
-
botToken: undefined,
|
|
944
|
-
appToken: "xapp-valid-app-token",
|
|
945
|
-
userToken: undefined,
|
|
946
|
-
},
|
|
947
|
-
]);
|
|
948
|
-
});
|
|
949
|
-
});
|
|
950
|
-
|
|
951
|
-
// ---------------------------------------------------------------------------
|
|
952
|
-
// 4. Vault — store action validation edge cases
|
|
953
|
-
// ---------------------------------------------------------------------------
|
|
954
|
-
|
|
955
|
-
describe("credential_store tool — store validation edge cases", () => {
|
|
956
|
-
beforeEach(() => {
|
|
957
|
-
if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
|
|
958
|
-
mkdirSync(TEST_DIR, { recursive: true });
|
|
959
|
-
setStorePathForTesting(STORE_PATH);
|
|
960
|
-
_resetBackend();
|
|
961
|
-
_setMetadataPath(join(TEST_DIR, "metadata.json"));
|
|
962
|
-
});
|
|
963
|
-
|
|
964
|
-
afterEach(() => {
|
|
965
|
-
_setMetadataPath(null);
|
|
966
|
-
setStorePathForTesting(null);
|
|
967
|
-
_resetBackend();
|
|
968
|
-
});
|
|
969
|
-
|
|
970
|
-
test("rejects alias that is not a string", async () => {
|
|
971
|
-
const result = await credentialStoreTool.execute(
|
|
972
|
-
{
|
|
973
|
-
action: "store",
|
|
974
|
-
service: "svc",
|
|
975
|
-
field: "key",
|
|
976
|
-
value: "val",
|
|
977
|
-
alias: 42,
|
|
978
|
-
},
|
|
979
|
-
_ctx,
|
|
980
|
-
);
|
|
981
|
-
expect(result.isError).toBe(true);
|
|
982
|
-
expect(result.content).toContain("alias must be a string");
|
|
983
|
-
});
|
|
984
|
-
|
|
985
|
-
test("rejects injection_templates that is not an array", async () => {
|
|
986
|
-
const result = await credentialStoreTool.execute(
|
|
987
|
-
{
|
|
988
|
-
action: "store",
|
|
989
|
-
service: "svc",
|
|
990
|
-
field: "key",
|
|
991
|
-
value: "val",
|
|
992
|
-
injection_templates: "not-an-array",
|
|
993
|
-
},
|
|
994
|
-
_ctx,
|
|
995
|
-
);
|
|
996
|
-
expect(result.isError).toBe(true);
|
|
997
|
-
expect(result.content).toContain("injection_templates must be an array");
|
|
998
|
-
});
|
|
999
|
-
|
|
1000
|
-
test("rejects template with invalid injectionType", async () => {
|
|
1001
|
-
const result = await credentialStoreTool.execute(
|
|
1002
|
-
{
|
|
1003
|
-
action: "store",
|
|
1004
|
-
service: "svc",
|
|
1005
|
-
field: "key",
|
|
1006
|
-
value: "val",
|
|
1007
|
-
injection_templates: [
|
|
1008
|
-
{ hostPattern: "*.example.com", injectionType: "cookie" },
|
|
1009
|
-
],
|
|
1010
|
-
},
|
|
1011
|
-
_ctx,
|
|
1012
|
-
);
|
|
1013
|
-
expect(result.isError).toBe(true);
|
|
1014
|
-
expect(result.content).toContain(
|
|
1015
|
-
"injectionType must be 'header' or 'query'",
|
|
1016
|
-
);
|
|
1017
|
-
});
|
|
1018
|
-
|
|
1019
|
-
test("rejects template with empty hostPattern", async () => {
|
|
1020
|
-
const result = await credentialStoreTool.execute(
|
|
1021
|
-
{
|
|
1022
|
-
action: "store",
|
|
1023
|
-
service: "svc",
|
|
1024
|
-
field: "key",
|
|
1025
|
-
value: "val",
|
|
1026
|
-
injection_templates: [
|
|
1027
|
-
{
|
|
1028
|
-
hostPattern: " ",
|
|
1029
|
-
injectionType: "header",
|
|
1030
|
-
headerName: "Authorization",
|
|
1031
|
-
},
|
|
1032
|
-
],
|
|
1033
|
-
},
|
|
1034
|
-
_ctx,
|
|
1035
|
-
);
|
|
1036
|
-
expect(result.isError).toBe(true);
|
|
1037
|
-
expect(result.content).toContain("hostPattern must be a non-empty string");
|
|
1038
|
-
});
|
|
1039
|
-
|
|
1040
|
-
test("rejects template with non-string valuePrefix", async () => {
|
|
1041
|
-
const result = await credentialStoreTool.execute(
|
|
1042
|
-
{
|
|
1043
|
-
action: "store",
|
|
1044
|
-
service: "svc",
|
|
1045
|
-
field: "key",
|
|
1046
|
-
value: "val",
|
|
1047
|
-
injection_templates: [
|
|
1048
|
-
{
|
|
1049
|
-
hostPattern: "*.example.com",
|
|
1050
|
-
injectionType: "header",
|
|
1051
|
-
headerName: "Auth",
|
|
1052
|
-
valuePrefix: 42,
|
|
1053
|
-
},
|
|
1054
|
-
],
|
|
1055
|
-
},
|
|
1056
|
-
_ctx,
|
|
1057
|
-
);
|
|
1058
|
-
expect(result.isError).toBe(true);
|
|
1059
|
-
expect(result.content).toContain("valuePrefix must be a string");
|
|
1060
|
-
});
|
|
1061
|
-
|
|
1062
|
-
test("reports multiple template errors at once", async () => {
|
|
1063
|
-
const result = await credentialStoreTool.execute(
|
|
1064
|
-
{
|
|
1065
|
-
action: "store",
|
|
1066
|
-
service: "svc",
|
|
1067
|
-
field: "key",
|
|
1068
|
-
value: "val",
|
|
1069
|
-
injection_templates: [
|
|
1070
|
-
{ hostPattern: "", injectionType: "header", headerName: "X-Key" },
|
|
1071
|
-
{ hostPattern: "*.example.com", injectionType: "query" }, // missing queryParamName
|
|
1072
|
-
],
|
|
1073
|
-
},
|
|
1074
|
-
_ctx,
|
|
1075
|
-
);
|
|
1076
|
-
expect(result.isError).toBe(true);
|
|
1077
|
-
expect(result.content).toContain("hostPattern");
|
|
1078
|
-
expect(result.content).toContain("queryParamName");
|
|
1079
|
-
});
|
|
1080
|
-
|
|
1081
|
-
test("delete removes both secret and metadata", async () => {
|
|
1082
|
-
await credentialStoreTool.execute(
|
|
1083
|
-
{
|
|
1084
|
-
action: "store",
|
|
1085
|
-
service: "del-test",
|
|
1086
|
-
field: "key",
|
|
1087
|
-
value: "secret",
|
|
1088
|
-
},
|
|
1089
|
-
_ctx,
|
|
1090
|
-
);
|
|
1091
|
-
|
|
1092
|
-
// Verify stored
|
|
1093
|
-
expect(await getSecureKeyAsync(credentialKey("del-test", "key"))).toBe(
|
|
1094
|
-
"secret",
|
|
1095
|
-
);
|
|
1096
|
-
const { getCredentialMetadata } =
|
|
1097
|
-
await import("../tools/credentials/metadata-store.js");
|
|
1098
|
-
expect(getCredentialMetadata("del-test", "key")).toBeDefined();
|
|
1099
|
-
|
|
1100
|
-
// Delete
|
|
1101
|
-
const result = await credentialStoreTool.execute(
|
|
1102
|
-
{
|
|
1103
|
-
action: "delete",
|
|
1104
|
-
service: "del-test",
|
|
1105
|
-
field: "key",
|
|
1106
|
-
},
|
|
1107
|
-
_ctx,
|
|
1108
|
-
);
|
|
1109
|
-
expect(result.isError).toBe(false);
|
|
1110
|
-
|
|
1111
|
-
// Both should be gone
|
|
1112
|
-
expect(
|
|
1113
|
-
await getSecureKeyAsync(credentialKey("del-test", "key")),
|
|
1114
|
-
).toBeUndefined();
|
|
1115
|
-
expect(getCredentialMetadata("del-test", "key")).toBeUndefined();
|
|
1116
|
-
});
|
|
1117
|
-
});
|
|
1118
|
-
|
|
1119
|
-
// ---------------------------------------------------------------------------
|
|
1120
|
-
// 4b. Vault — slack_channel delete routing
|
|
1121
|
-
// ---------------------------------------------------------------------------
|
|
1122
|
-
|
|
1123
|
-
describe("credential_store tool — slack_channel delete routing", () => {
|
|
1124
|
-
beforeEach(() => {
|
|
1125
|
-
if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
|
|
1126
|
-
mkdirSync(TEST_DIR, { recursive: true });
|
|
1127
|
-
setStorePathForTesting(STORE_PATH);
|
|
1128
|
-
_resetBackend();
|
|
1129
|
-
_setMetadataPath(join(TEST_DIR, "metadata.json"));
|
|
1130
|
-
});
|
|
1131
|
-
|
|
1132
|
-
afterEach(() => {
|
|
1133
|
-
_setMetadataPath(null);
|
|
1134
|
-
setStorePathForTesting(null);
|
|
1135
|
-
_resetBackend();
|
|
1136
|
-
});
|
|
1137
|
-
|
|
1138
|
-
test("delete with user_token leaves bot+app tokens and oauth_connection intact", async () => {
|
|
1139
|
-
// Seed all three Slack tokens + metadata, with the manual connection active.
|
|
1140
|
-
await setSecureKeyAsync(
|
|
1141
|
-
credentialKey("slack_channel", "bot_token"),
|
|
1142
|
-
"xoxb-bot",
|
|
1143
|
-
);
|
|
1144
|
-
await setSecureKeyAsync(
|
|
1145
|
-
credentialKey("slack_channel", "app_token"),
|
|
1146
|
-
"xapp-app",
|
|
1147
|
-
);
|
|
1148
|
-
await setSecureKeyAsync(
|
|
1149
|
-
credentialKey("slack_channel", "user_token"),
|
|
1150
|
-
"xoxp-user",
|
|
1151
|
-
);
|
|
1152
|
-
upsertCredentialMetadata("slack_channel", "bot_token", {});
|
|
1153
|
-
upsertCredentialMetadata("slack_channel", "app_token", {});
|
|
1154
|
-
upsertCredentialMetadata("slack_channel", "user_token", {});
|
|
1155
|
-
manualConnectionStore["slack_channel"] = "active";
|
|
1156
|
-
|
|
1157
|
-
const result = await credentialStoreTool.execute(
|
|
1158
|
-
{
|
|
1159
|
-
action: "delete",
|
|
1160
|
-
service: "slack_channel",
|
|
1161
|
-
field: "user_token",
|
|
1162
|
-
},
|
|
1163
|
-
_ctx,
|
|
1164
|
-
);
|
|
1165
|
-
|
|
1166
|
-
expect(result.isError).toBe(false);
|
|
1167
|
-
// Routed through the surgical helper.
|
|
1168
|
-
expect(clearSlackUserTokenCalls).toBe(1);
|
|
1169
|
-
// oauth_connection row was never disconnected.
|
|
1170
|
-
expect(disconnectOAuthProviderCalls).toEqual([]);
|
|
1171
|
-
// user_token key + metadata removed.
|
|
1172
|
-
expect(
|
|
1173
|
-
await getSecureKeyAsync(credentialKey("slack_channel", "user_token")),
|
|
1174
|
-
).toBeUndefined();
|
|
1175
|
-
const { getCredentialMetadata } =
|
|
1176
|
-
await import("../tools/credentials/metadata-store.js");
|
|
1177
|
-
expect(
|
|
1178
|
-
getCredentialMetadata("slack_channel", "user_token"),
|
|
1179
|
-
).toBeUndefined();
|
|
1180
|
-
// bot + app tokens + their metadata + manual connection still present.
|
|
1181
|
-
expect(
|
|
1182
|
-
await getSecureKeyAsync(credentialKey("slack_channel", "bot_token")),
|
|
1183
|
-
).toBe("xoxb-bot");
|
|
1184
|
-
expect(
|
|
1185
|
-
await getSecureKeyAsync(credentialKey("slack_channel", "app_token")),
|
|
1186
|
-
).toBe("xapp-app");
|
|
1187
|
-
expect(getCredentialMetadata("slack_channel", "bot_token")).toBeDefined();
|
|
1188
|
-
expect(getCredentialMetadata("slack_channel", "app_token")).toBeDefined();
|
|
1189
|
-
expect(manualConnectionStore["slack_channel"]).toBe("active");
|
|
1190
|
-
});
|
|
1191
|
-
|
|
1192
|
-
test("delete with bot_token still tears down the oauth connection (regression guard)", async () => {
|
|
1193
|
-
await setSecureKeyAsync(
|
|
1194
|
-
credentialKey("slack_channel", "bot_token"),
|
|
1195
|
-
"xoxb-bot",
|
|
1196
|
-
);
|
|
1197
|
-
await setSecureKeyAsync(
|
|
1198
|
-
credentialKey("slack_channel", "app_token"),
|
|
1199
|
-
"xapp-app",
|
|
1200
|
-
);
|
|
1201
|
-
upsertCredentialMetadata("slack_channel", "bot_token", {});
|
|
1202
|
-
upsertCredentialMetadata("slack_channel", "app_token", {});
|
|
1203
|
-
manualConnectionStore["slack_channel"] = "active";
|
|
1204
|
-
|
|
1205
|
-
const result = await credentialStoreTool.execute(
|
|
1206
|
-
{
|
|
1207
|
-
action: "delete",
|
|
1208
|
-
service: "slack_channel",
|
|
1209
|
-
field: "bot_token",
|
|
1210
|
-
},
|
|
1211
|
-
_ctx,
|
|
1212
|
-
);
|
|
1213
|
-
|
|
1214
|
-
expect(result.isError).toBe(false);
|
|
1215
|
-
// Surgical helper was not used.
|
|
1216
|
-
expect(clearSlackUserTokenCalls).toBe(0);
|
|
1217
|
-
// Full teardown path called disconnectOAuthProvider for the slack_channel.
|
|
1218
|
-
expect(disconnectOAuthProviderCalls).toContain("slack_channel");
|
|
1219
|
-
// bot_token key + metadata removed.
|
|
1220
|
-
expect(
|
|
1221
|
-
await getSecureKeyAsync(credentialKey("slack_channel", "bot_token")),
|
|
1222
|
-
).toBeUndefined();
|
|
1223
|
-
const { getCredentialMetadata } =
|
|
1224
|
-
await import("../tools/credentials/metadata-store.js");
|
|
1225
|
-
expect(getCredentialMetadata("slack_channel", "bot_token")).toBeUndefined();
|
|
1226
|
-
});
|
|
1227
|
-
|
|
1228
|
-
test("delete with app_token still tears down the oauth connection (regression guard)", async () => {
|
|
1229
|
-
await setSecureKeyAsync(
|
|
1230
|
-
credentialKey("slack_channel", "bot_token"),
|
|
1231
|
-
"xoxb-bot",
|
|
1232
|
-
);
|
|
1233
|
-
await setSecureKeyAsync(
|
|
1234
|
-
credentialKey("slack_channel", "app_token"),
|
|
1235
|
-
"xapp-app",
|
|
1236
|
-
);
|
|
1237
|
-
upsertCredentialMetadata("slack_channel", "bot_token", {});
|
|
1238
|
-
upsertCredentialMetadata("slack_channel", "app_token", {});
|
|
1239
|
-
manualConnectionStore["slack_channel"] = "active";
|
|
1240
|
-
|
|
1241
|
-
const result = await credentialStoreTool.execute(
|
|
1242
|
-
{
|
|
1243
|
-
action: "delete",
|
|
1244
|
-
service: "slack_channel",
|
|
1245
|
-
field: "app_token",
|
|
1246
|
-
},
|
|
1247
|
-
_ctx,
|
|
1248
|
-
);
|
|
1249
|
-
|
|
1250
|
-
expect(result.isError).toBe(false);
|
|
1251
|
-
expect(clearSlackUserTokenCalls).toBe(0);
|
|
1252
|
-
expect(disconnectOAuthProviderCalls).toContain("slack_channel");
|
|
1253
|
-
expect(
|
|
1254
|
-
await getSecureKeyAsync(credentialKey("slack_channel", "app_token")),
|
|
1255
|
-
).toBeUndefined();
|
|
1256
|
-
});
|
|
1257
|
-
});
|
|
1258
|
-
|
|
1259
|
-
// ---------------------------------------------------------------------------
|
|
1260
|
-
// 5. Vault — tool definition schema
|
|
1261
|
-
// ---------------------------------------------------------------------------
|
|
1262
|
-
|
|
1263
|
-
describe("credential_store tool — tool definition", () => {
|
|
1264
|
-
test("tool name and category are correct", () => {
|
|
1265
|
-
expect(credentialStoreTool.name).toBe("credential_store");
|
|
1266
|
-
expect(credentialStoreTool.category).toBe("credentials");
|
|
1267
|
-
});
|
|
1268
|
-
|
|
1269
|
-
test("getDefinition returns valid schema with required action", () => {
|
|
1270
|
-
const def = credentialStoreTool;
|
|
1271
|
-
expect(def.name).toBe("credential_store");
|
|
1272
|
-
const schema = def.input_schema as Record<string, unknown>;
|
|
1273
|
-
expect(schema.type).toBe("object");
|
|
1274
|
-
expect(schema.required).toContain("action");
|
|
1275
|
-
const props = schema.properties as Record<string, Record<string, unknown>>;
|
|
1276
|
-
expect(props.action.enum).toEqual(["store", "list", "delete", "prompt"]);
|
|
1277
|
-
});
|
|
1278
|
-
|
|
1279
|
-
test("getDefinition includes injection_templates schema", () => {
|
|
1280
|
-
const def = credentialStoreTool;
|
|
1281
|
-
const schemaProps = (def.input_schema as Record<string, unknown>)
|
|
1282
|
-
.properties as Record<string, Record<string, unknown>>;
|
|
1283
|
-
const templates = schemaProps.injection_templates as Record<
|
|
1284
|
-
string,
|
|
1285
|
-
unknown
|
|
1286
|
-
>;
|
|
1287
|
-
expect(templates).toBeDefined();
|
|
1288
|
-
expect(templates.type).toBe("array");
|
|
1289
|
-
const items = (templates.items as Record<string, unknown>)
|
|
1290
|
-
.properties as Record<string, Record<string, unknown>>;
|
|
1291
|
-
expect(items.hostPattern).toBeDefined();
|
|
1292
|
-
expect(items.injectionType.enum).toEqual(["header", "query"]);
|
|
1293
|
-
});
|
|
1294
|
-
});
|
|
1295
|
-
|
|
1296
|
-
// ---------------------------------------------------------------------------
|
|
1297
|
-
// 6. Broker — serverUseById with transient not supported
|
|
1298
|
-
// (transient is scoped to authorize+consume and browserFill/serverUse)
|
|
1299
|
-
// ---------------------------------------------------------------------------
|
|
1300
|
-
|
|
1301
|
-
describe("CredentialBroker — serverUseById edge cases", () => {
|
|
1302
|
-
let broker: CredentialBroker;
|
|
1303
|
-
|
|
1304
|
-
beforeEach(() => {
|
|
1305
|
-
if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
|
|
1306
|
-
mkdirSync(TEST_DIR, { recursive: true });
|
|
1307
|
-
setStorePathForTesting(STORE_PATH);
|
|
1308
|
-
_resetBackend();
|
|
1309
|
-
_setMetadataPath(join(TEST_DIR, "metadata.json"));
|
|
1310
|
-
broker = new CredentialBroker();
|
|
1311
|
-
});
|
|
1312
|
-
|
|
1313
|
-
afterEach(() => {
|
|
1314
|
-
_setMetadataPath(null);
|
|
1315
|
-
setStorePathForTesting(null);
|
|
1316
|
-
_resetBackend();
|
|
1317
|
-
});
|
|
1318
|
-
|
|
1319
|
-
test("serverUseById with multiple injection templates returns all", async () => {
|
|
1320
|
-
const meta = upsertCredentialMetadata("multi", "api_key", {
|
|
1321
|
-
allowedTools: ["proxy"],
|
|
1322
|
-
injectionTemplates: [
|
|
1323
|
-
{
|
|
1324
|
-
hostPattern: "*.fal.ai",
|
|
1325
|
-
injectionType: "header",
|
|
1326
|
-
headerName: "Authorization",
|
|
1327
|
-
valuePrefix: "Key ",
|
|
1328
|
-
},
|
|
1329
|
-
{
|
|
1330
|
-
hostPattern: "gateway.fal.ai",
|
|
1331
|
-
injectionType: "header",
|
|
1332
|
-
headerName: "X-Fal-Key",
|
|
1333
|
-
},
|
|
1334
|
-
],
|
|
1335
|
-
});
|
|
1336
|
-
await setSecureKeyAsync(credentialKey("multi", "api_key"), "multi-secret");
|
|
1337
|
-
|
|
1338
|
-
const result = await broker.serverUseById({
|
|
1339
|
-
credentialId: meta.credentialId,
|
|
1340
|
-
requestingTool: "proxy",
|
|
1341
|
-
});
|
|
1342
|
-
|
|
1343
|
-
expect(result.success).toBe(true);
|
|
1344
|
-
if (!result.success) return;
|
|
1345
|
-
expect(result.injectionTemplates).toHaveLength(2);
|
|
1346
|
-
expect(result.injectionTemplates[0].hostPattern).toBe("*.fal.ai");
|
|
1347
|
-
expect(result.injectionTemplates[1].hostPattern).toBe("gateway.fal.ai");
|
|
1348
|
-
// No secret value in result
|
|
1349
|
-
expect(JSON.stringify(result)).not.toContain("multi-secret");
|
|
1350
|
-
});
|
|
1351
|
-
|
|
1352
|
-
test("serverUseById verifies secret exists in storage (fail-closed)", async () => {
|
|
1353
|
-
const meta = upsertCredentialMetadata("fal", "api_key", {
|
|
1354
|
-
allowedTools: ["proxy"],
|
|
1355
|
-
});
|
|
1356
|
-
// No setSecureKeyAsync — metadata exists but value doesn't
|
|
1357
|
-
|
|
1358
|
-
const result = await broker.serverUseById({
|
|
1359
|
-
credentialId: meta.credentialId,
|
|
1360
|
-
requestingTool: "proxy",
|
|
1361
|
-
});
|
|
1362
|
-
|
|
1363
|
-
expect(result.success).toBe(false);
|
|
1364
|
-
if (result.success) return;
|
|
1365
|
-
expect(result.reason).toContain("no stored value");
|
|
1366
|
-
});
|
|
1367
|
-
});
|
|
1368
|
-
|
|
1369
|
-
// ---------------------------------------------------------------------------
|
|
1370
|
-
// 7. Broker — revokeAll clears transient values indirectly via token cleanup
|
|
1371
|
-
// ---------------------------------------------------------------------------
|
|
1372
|
-
|
|
1373
|
-
describe("CredentialBroker — revokeAll", () => {
|
|
1374
|
-
let broker: CredentialBroker;
|
|
1375
|
-
|
|
1376
|
-
beforeEach(() => {
|
|
1377
|
-
if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
|
|
1378
|
-
mkdirSync(TEST_DIR, { recursive: true });
|
|
1379
|
-
setStorePathForTesting(STORE_PATH);
|
|
1380
|
-
_resetBackend();
|
|
1381
|
-
_setMetadataPath(join(TEST_DIR, "metadata.json"));
|
|
1382
|
-
broker = new CredentialBroker();
|
|
1383
|
-
});
|
|
1384
|
-
|
|
1385
|
-
afterEach(() => {
|
|
1386
|
-
_setMetadataPath(null);
|
|
1387
|
-
setStorePathForTesting(null);
|
|
1388
|
-
_resetBackend();
|
|
1389
|
-
});
|
|
1390
|
-
|
|
1391
|
-
test("revokeAll clears all tokens and subsequent consume fails", () => {
|
|
1392
|
-
upsertCredentialMetadata("svc", "key", { allowedTools: ["t1", "t2"] });
|
|
1393
|
-
const a1 = broker.authorize({
|
|
1394
|
-
service: "svc",
|
|
1395
|
-
field: "key",
|
|
1396
|
-
toolName: "t1",
|
|
1397
|
-
});
|
|
1398
|
-
const a2 = broker.authorize({
|
|
1399
|
-
service: "svc",
|
|
1400
|
-
field: "key",
|
|
1401
|
-
toolName: "t2",
|
|
1402
|
-
});
|
|
1403
|
-
expect(broker.activeTokenCount).toBe(2);
|
|
1404
|
-
|
|
1405
|
-
broker.revokeAll();
|
|
1406
|
-
expect(broker.activeTokenCount).toBe(0);
|
|
1407
|
-
|
|
1408
|
-
if (a1.authorized) {
|
|
1409
|
-
const r = broker.consume(a1.token.tokenId);
|
|
1410
|
-
expect(r.success).toBe(false);
|
|
1411
|
-
}
|
|
1412
|
-
if (a2.authorized) {
|
|
1413
|
-
const r = broker.consume(a2.token.tokenId);
|
|
1414
|
-
expect(r.success).toBe(false);
|
|
1415
|
-
}
|
|
1416
|
-
});
|
|
1417
|
-
|
|
1418
|
-
test("revokeAll on empty broker is a no-op", () => {
|
|
1419
|
-
expect(broker.activeTokenCount).toBe(0);
|
|
1420
|
-
broker.revokeAll();
|
|
1421
|
-
expect(broker.activeTokenCount).toBe(0);
|
|
1422
|
-
});
|
|
1423
|
-
});
|
|
1424
|
-
|
|
1425
|
-
// ---------------------------------------------------------------------------
|
|
1426
|
-
// 8. Broker — canonical capability key and legacy alias
|
|
1427
|
-
// ---------------------------------------------------------------------------
|
|
1428
|
-
|
|
1429
|
-
describe("CredentialBroker — canonical capability key", () => {
|
|
1430
|
-
let broker: CredentialBroker;
|
|
1431
|
-
|
|
1432
|
-
beforeEach(() => {
|
|
1433
|
-
if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
|
|
1434
|
-
mkdirSync(TEST_DIR, { recursive: true });
|
|
1435
|
-
setStorePathForTesting(STORE_PATH);
|
|
1436
|
-
_resetBackend();
|
|
1437
|
-
_setMetadataPath(join(TEST_DIR, "metadata.json"));
|
|
1438
|
-
broker = new CredentialBroker();
|
|
1439
|
-
});
|
|
1440
|
-
|
|
1441
|
-
afterEach(() => {
|
|
1442
|
-
_setMetadataPath(null);
|
|
1443
|
-
setStorePathForTesting(null);
|
|
1444
|
-
_resetBackend();
|
|
1445
|
-
});
|
|
1446
|
-
|
|
1447
|
-
test("authorize succeeds with canonical key when metadata has canonical key", () => {
|
|
1448
|
-
upsertCredentialMetadata("github", "token", {
|
|
1449
|
-
allowedTools: [BROWSER_FILL_CAPABILITY],
|
|
1450
|
-
});
|
|
1451
|
-
|
|
1452
|
-
const result = broker.authorize({
|
|
1453
|
-
service: "github",
|
|
1454
|
-
field: "token",
|
|
1455
|
-
toolName: BROWSER_FILL_CAPABILITY,
|
|
1456
|
-
});
|
|
1457
|
-
expect(result.authorized).toBe(true);
|
|
1458
|
-
});
|
|
1459
|
-
|
|
1460
|
-
test("authorize succeeds with canonical key when metadata has legacy alias", () => {
|
|
1461
|
-
upsertCredentialMetadata("github", "token", {
|
|
1462
|
-
allowedTools: ["browser_fill_credential"],
|
|
1463
|
-
});
|
|
1464
|
-
|
|
1465
|
-
const result = broker.authorize({
|
|
1466
|
-
service: "github",
|
|
1467
|
-
field: "token",
|
|
1468
|
-
toolName: BROWSER_FILL_CAPABILITY,
|
|
1469
|
-
});
|
|
1470
|
-
expect(result.authorized).toBe(true);
|
|
1471
|
-
});
|
|
1472
|
-
|
|
1473
|
-
test("authorize succeeds with legacy alias when metadata has canonical key", () => {
|
|
1474
|
-
upsertCredentialMetadata("github", "token", {
|
|
1475
|
-
allowedTools: [BROWSER_FILL_CAPABILITY],
|
|
1476
|
-
});
|
|
1477
|
-
|
|
1478
|
-
const result = broker.authorize({
|
|
1479
|
-
service: "github",
|
|
1480
|
-
field: "token",
|
|
1481
|
-
toolName: "browser_fill_credential",
|
|
1482
|
-
});
|
|
1483
|
-
expect(result.authorized).toBe(true);
|
|
1484
|
-
});
|
|
1485
|
-
|
|
1486
|
-
test("serverUse with canonical key works when metadata has legacy alias", async () => {
|
|
1487
|
-
upsertCredentialMetadata("vercel", "api_token", {
|
|
1488
|
-
allowedTools: ["browser_fill_credential"],
|
|
1489
|
-
});
|
|
1490
|
-
await setSecureKeyAsync(credentialKey("vercel", "api_token"), "vercel-tok");
|
|
1491
|
-
|
|
1492
|
-
const result = await broker.serverUse({
|
|
1493
|
-
service: "vercel",
|
|
1494
|
-
field: "api_token",
|
|
1495
|
-
toolName: BROWSER_FILL_CAPABILITY,
|
|
1496
|
-
execute: async (v) => v,
|
|
1497
|
-
});
|
|
1498
|
-
|
|
1499
|
-
expect(result.success).toBe(true);
|
|
1500
|
-
expect(result.result).toBe("vercel-tok");
|
|
1501
|
-
});
|
|
1502
|
-
|
|
1503
|
-
test("non-aliased tool names are unaffected by alias resolution", () => {
|
|
1504
|
-
upsertCredentialMetadata("svc", "key", {
|
|
1505
|
-
allowedTools: ["custom_tool"],
|
|
1506
|
-
});
|
|
1507
|
-
|
|
1508
|
-
const result = broker.authorize({
|
|
1509
|
-
service: "svc",
|
|
1510
|
-
field: "key",
|
|
1511
|
-
toolName: "custom_tool",
|
|
1512
|
-
});
|
|
1513
|
-
expect(result.authorized).toBe(true);
|
|
1514
|
-
});
|
|
1515
|
-
|
|
1516
|
-
test("non-aliased tool denied when only canonical key is allowed", () => {
|
|
1517
|
-
upsertCredentialMetadata("svc", "key", {
|
|
1518
|
-
allowedTools: [BROWSER_FILL_CAPABILITY],
|
|
1519
|
-
});
|
|
1520
|
-
|
|
1521
|
-
const result = broker.authorize({
|
|
1522
|
-
service: "svc",
|
|
1523
|
-
field: "key",
|
|
1524
|
-
toolName: "unrelated_tool",
|
|
1525
|
-
});
|
|
1526
|
-
expect(result.authorized).toBe(false);
|
|
1527
|
-
});
|
|
1528
|
-
});
|