@vellumai/assistant 0.12.1 → 0.12.2-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/AGENTS.md +4 -0
- package/ARCHITECTURE.md +20 -21
- package/docs/architecture/turn-actor.md +15 -8
- package/docs/guardian-request-flow.md +16 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +13 -8
- package/node_modules/@vellumai/slack-text/src/label-resolution-entities.test.ts +95 -0
- package/openapi.yaml +360 -51
- package/package.json +3 -1
- package/scripts/smoke-desktop-window-theme.ts +186 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
- package/src/__tests__/anthropic-provider.test.ts +0 -63
- package/src/__tests__/app-compiler.test.ts +6 -0
- package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
- package/src/__tests__/app-dir-path-guard.test.ts +1 -3
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
- package/src/__tests__/assistant-event-hub.test.ts +44 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
- package/src/__tests__/computer-use-tools.test.ts +19 -7
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +28 -29
- package/src/__tests__/config-watcher.test.ts +23 -8
- package/src/__tests__/connection-model-compat.test.ts +2 -2
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +26 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
- package/src/__tests__/conversation-queue.test.ts +48 -207
- package/src/__tests__/conversation-retry-discard.test.ts +54 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +28 -0
- package/src/__tests__/conversation-skill-tools.test.ts +27 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
- package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-point-at-capability.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-title-service.test.ts +14 -10
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
- package/src/__tests__/credential-record-retire.test.ts +171 -1
- package/src/__tests__/credential-routes.test.ts +250 -3
- package/src/__tests__/credential-security-invariants.test.ts +5 -3
- package/src/__tests__/cu-unified-flow.test.ts +14 -4
- package/src/__tests__/document-sync-tags.test.ts +25 -0
- package/src/__tests__/drain-kick-guard.test.ts +0 -2
- package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
- package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
- package/src/__tests__/git-porcelain.test.ts +47 -0
- package/src/__tests__/heartbeat-service.test.ts +56 -40
- package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
- package/src/__tests__/host-bash-proxy.test.ts +26 -45
- package/src/__tests__/host-browser-proxy.test.ts +6 -17
- package/src/__tests__/host-cu-proxy.test.ts +596 -0
- package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
- package/src/__tests__/host-file-proxy.test.ts +11 -23
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
- package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
- package/src/__tests__/llm-catalog-parity.test.ts +25 -0
- package/src/__tests__/mcp-auth-routes.test.ts +6 -9
- package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
- package/src/__tests__/mcp-health-check.test.ts +8 -11
- package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
- package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
- package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
- package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +355 -548
- package/src/__tests__/model-intents.test.ts +3 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
- package/src/__tests__/oauth-commands-routes.test.ts +168 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +22 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/openai-provider.test.ts +1 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-callback-registration.test.ts +8 -16
- package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
- package/src/__tests__/qdrant-manager.test.ts +132 -0
- package/src/__tests__/resolve-app.test.ts +135 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
- package/src/__tests__/script-proxy-certs.test.ts +35 -0
- package/src/__tests__/secret-routes-acp-guard.test.ts +59 -1
- package/src/__tests__/service-account-token.test.ts +247 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/skills.test.ts +9 -4
- package/src/__tests__/source-fingerprint.test.ts +184 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +70 -3
- package/src/__tests__/subagent-tools.test.ts +3 -2
- package/src/__tests__/telephony-tts-guard.test.ts +38 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/ui-channel-variants.test.ts +1 -56
- package/src/__tests__/unicode.test.ts +13 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
- package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
- package/src/__tests__/workspace-git-service.test.ts +26 -0
- package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +257 -6
- package/src/acp/__tests__/acp-credentials.test.ts +13 -0
- package/src/acp/__tests__/agent-process.test.ts +258 -4
- package/src/acp/__tests__/auth-required.test.ts +96 -0
- package/src/acp/__tests__/claude-token-refresh.test.ts +257 -0
- package/src/acp/__tests__/client-handler.test.ts +90 -2
- package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +60 -1
- package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
- package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
- package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
- package/src/acp/__tests__/session-manager.test.ts +895 -4
- package/src/acp/acp-claude-oauth.ts +328 -14
- package/src/acp/acp-credentials.ts +19 -0
- package/src/acp/agent-process.test.ts +4 -1
- package/src/acp/agent-process.ts +96 -10
- package/src/acp/auth-required.ts +50 -1
- package/src/acp/auto-install.test.ts +9 -7
- package/src/acp/claude-token-refresh.ts +150 -0
- package/src/acp/client-handler.ts +16 -3
- package/src/acp/model-config.test.ts +166 -0
- package/src/acp/model-config.ts +138 -0
- package/src/acp/prepare-agent-env.ts +21 -6
- package/src/acp/resolve-agent.test.ts +220 -6
- package/src/acp/resolve-agent.ts +88 -23
- package/src/acp/session-manager.test.ts +196 -3
- package/src/acp/session-manager.ts +441 -18
- package/src/acp/types.ts +50 -5
- package/src/agent/loop.ts +1 -0
- package/src/api/events/acp-session-model-update.test.ts +88 -0
- package/src/api/events/acp-session-model-update.ts +46 -0
- package/src/api/events/app-files-changed.ts +4 -3
- package/src/api/events/notification-intent.ts +2 -0
- package/src/api/events/user-message-echo.ts +7 -1
- package/src/api/index.ts +6 -0
- package/src/api/responses/conversation-message.ts +5 -0
- package/src/apps/app-store.ts +1 -1
- package/src/apps/resolve-app.ts +81 -0
- package/src/apps/source-fingerprint.ts +266 -0
- package/src/avatar/identity-avatar.ts +7 -2
- package/src/bundler/app-compiler.ts +15 -0
- package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
- package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
- package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
- package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
- package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
- package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
- package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
- package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
- package/src/calls/__tests__/voice-control-protocol.test.ts +120 -25
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
- package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
- package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
- package/src/calls/__tests__/voice-session-bridge.test.ts +56 -1
- package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
- package/src/calls/barge-in-guard.ts +118 -0
- package/src/calls/call-constants.ts +24 -4
- package/src/calls/call-controller.ts +499 -199
- package/src/calls/call-transport.ts +16 -0
- package/src/calls/media-stream-output.ts +32 -0
- package/src/calls/media-stream-server.ts +82 -30
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/{live-voice → calls}/progress-narration.ts +2 -2
- package/src/calls/resolve-call-tts-provider.ts +33 -2
- package/src/calls/tts-call-strategy.ts +9 -5
- package/src/calls/types.ts +2 -0
- package/src/calls/voice-control-protocol.ts +155 -21
- package/src/calls/voice-leg-coordinator.ts +189 -0
- package/src/calls/voice-progress-cadence.ts +423 -0
- package/src/calls/voice-session-bridge.ts +92 -72
- package/src/calls/voice-triage-escalate.ts +178 -59
- package/src/cli/commands/__tests__/apps.test.ts +245 -0
- package/src/cli/commands/__tests__/bash.test.ts +53 -0
- package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
- package/src/cli/commands/apps.help.ts +67 -13
- package/src/cli/commands/apps.ts +183 -0
- package/src/cli/commands/bash.help.ts +8 -13
- package/src/cli/commands/bash.ts +7 -25
- package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
- package/src/cli/commands/channels/index.help.ts +70 -2
- package/src/cli/commands/channels/index.ts +16 -3
- package/src/cli/commands/channels/send.ts +128 -0
- package/src/cli/commands/credentials.help.ts +6 -2
- package/src/cli/commands/credentials.ts +2 -2
- package/src/cli/commands/mcp.help.ts +8 -8
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/oauth/status.ts +5 -0
- package/src/cli/commands/roadmap.help.ts +5 -1
- package/src/cli/commands/roadmap.ts +1 -1
- package/src/cli/lib/bundled-marketplace.json +103 -90
- package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
- package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
- package/src/config/__tests__/platform-identity.test.ts +255 -0
- package/src/config/__tests__/platform-rehydration.test.ts +99 -9
- package/src/config/acp-agent-write.ts +43 -0
- package/src/config/acp-defaults.test.ts +13 -3
- package/src/config/acp-defaults.ts +23 -6
- package/src/config/acp-schema.test.ts +81 -0
- package/src/config/acp-schema.ts +30 -2
- package/src/config/bundled-skills/acp/SKILL.md +22 -14
- package/src/config/bundled-skills/acp/TOOLS.json +5 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +29 -2
- package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
- package/src/config/bundled-skills/schedule/SKILL.md +3 -4
- package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +33 -13
- package/src/config/loader.ts +2 -0
- package/src/config/memory-v3-gate.test.ts +31 -0
- package/src/config/memory-v3-gate.ts +20 -16
- package/src/config/platform-identity.ts +201 -0
- package/src/config/platform-rehydration.ts +11 -27
- package/src/config/schema.ts +2 -2
- package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
- package/src/config/schemas/__tests__/voice.test.ts +148 -0
- package/src/config/schemas/calls.ts +1 -28
- package/src/config/schemas/live-voice.ts +1 -173
- package/src/config/schemas/mcp.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +10 -0
- package/src/config/schemas/services.ts +18 -0
- package/src/config/schemas/skills.ts +0 -14
- package/src/config/schemas/voice.ts +150 -0
- package/src/credential-execution/process-manager.test.ts +18 -19
- package/src/credential-health/credential-health-service.ts +11 -17
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
- package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
- package/src/daemon/app-change-notify.ts +45 -0
- package/src/daemon/config-watcher.ts +13 -194
- package/src/daemon/conversation-error.ts +3 -2
- package/src/daemon/conversation-media-retry.ts +2 -1
- package/src/daemon/conversation-process.ts +35 -64
- package/src/daemon/conversation-runtime-assembly.ts +20 -7
- package/src/daemon/conversation-skill-tools.ts +13 -31
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +21 -10
- package/src/daemon/conversation-tool-setup.ts +45 -59
- package/src/daemon/conversation.ts +0 -3
- package/src/daemon/dictation-profile-store.ts +2 -1
- package/src/daemon/handlers/shared.ts +2 -1
- package/src/daemon/handlers/skills.ts +6 -3
- package/src/daemon/host-bash-proxy.ts +8 -12
- package/src/daemon/host-browser-proxy.ts +15 -1
- package/src/daemon/host-cu-proxy.ts +224 -19
- package/src/daemon/host-file-proxy.ts +8 -10
- package/src/daemon/host-proxy-preactivation.ts +2 -1
- package/src/daemon/host-transfer-proxy.ts +14 -18
- package/src/daemon/lifecycle.ts +0 -5
- package/src/daemon/mcp-reload-service.ts +5 -5
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/orphan-reaper.ts +1 -1
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/reaction-history-render.ts +2 -1
- package/src/daemon/shutdown-handlers.ts +1 -9
- package/src/daemon/skill-memory-refresh.ts +64 -0
- package/src/daemon/tool-side-effects.ts +7 -18
- package/src/desktop/__tests__/fake-desktop.ts +122 -0
- package/src/desktop/desktop-chrome-policy.test.ts +79 -0
- package/src/desktop/desktop-chrome-policy.ts +47 -0
- package/src/desktop/desktop-chrome-session.ts +37 -0
- package/src/desktop/desktop-dependencies.test.ts +102 -0
- package/src/desktop/desktop-dependencies.ts +308 -0
- package/src/desktop/desktop-feature.test.ts +18 -0
- package/src/desktop/desktop-feature.ts +19 -0
- package/src/desktop/desktop-panel-config.test.ts +135 -0
- package/src/desktop/desktop-panel-config.ts +149 -0
- package/src/desktop/desktop-session-manager.test.ts +730 -0
- package/src/desktop/desktop-session-manager.ts +850 -0
- package/src/desktop/desktop-stream-bridge.test.ts +395 -0
- package/src/desktop/desktop-stream-bridge.ts +214 -0
- package/src/desktop/desktop-wallpaper.test.ts +191 -0
- package/src/desktop/desktop-wallpaper.ts +113 -0
- package/src/desktop/desktop-window-drag.ts +177 -0
- package/src/desktop/desktop-window-manager-config.test.ts +230 -0
- package/src/desktop/desktop-window-manager-config.ts +238 -0
- package/src/desktop/desktop-window-theme.ts +170 -0
- package/src/heartbeat/heartbeat-service.ts +21 -3
- package/src/home/__tests__/feed-writer.test.ts +22 -0
- package/src/home/feed-writer.ts +11 -2
- package/src/hooks/types.ts +33 -0
- package/src/i18n/AGENTS.md +22 -0
- package/src/i18n/__tests__/i18n.test.ts +108 -0
- package/src/i18n/index.ts +16 -0
- package/src/i18n/locales.ts +93 -0
- package/src/i18n/messages.ts +61 -0
- package/src/i18n/title.ts +25 -0
- package/src/inbound/platform-callback-registration.ts +4 -5
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +121 -0
- package/src/live-voice/__tests__/live-voice-events.test.ts +8 -7
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
- package/src/live-voice/__tests__/live-voice-progress.test.ts +94 -15
- package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
- package/src/live-voice/__tests__/protocol.test.ts +39 -0
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
- package/src/live-voice/__tests__/session-controls.test.ts +79 -0
- package/src/live-voice/live-voice-photo.ts +25 -6
- package/src/live-voice/live-voice-session.ts +297 -699
- package/src/live-voice/protocol.ts +60 -2
- package/src/live-voice/session-controls.ts +111 -0
- package/src/mcp/__tests__/effective-config.test.ts +2 -2
- package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
- package/src/mcp/effective-config.ts +3 -3
- package/src/mcp/mcp-header-store.ts +29 -45
- package/src/mcp/spec-schema.ts +111 -0
- package/src/mcp/startup.ts +8 -4
- package/src/mcp/workspace-mcp-config.ts +188 -0
- package/src/messaging/provider.ts +10 -3
- package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
- package/src/messaging/providers/callback-routing.ts +18 -0
- package/src/messaging/providers/channel-transport.ts +56 -0
- package/src/messaging/providers/discord/transport.ts +17 -0
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
- package/src/messaging/providers/slack/adapter.ts +7 -44
- package/src/messaging/providers/slack/transport.ts +16 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
- package/src/messaging/providers/telegram-bot/client.ts +0 -26
- package/src/messaging/providers/telegram-bot/transport.ts +20 -0
- package/src/messaging/providers/whatsapp/adapter.ts +1 -48
- package/src/messaging/providers/whatsapp/transport.ts +18 -0
- package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
- package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
- package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
- package/src/notifications/adapters/macos.ts +3 -0
- package/src/notifications/assistant-reply-producer.ts +8 -3
- package/src/notifications/conversation-pairing.ts +36 -16
- package/src/notifications/delivered-post-record.ts +11 -1
- package/src/notifications/guardian-feed-projection.ts +125 -5
- package/src/notifications/home-feed-side-effect.ts +55 -7
- package/src/notifications/schedule-result-producer.ts +66 -22
- package/src/oauth/AGENTS.md +2 -0
- package/src/oauth/__tests__/seed-providers-managed.test.ts +149 -0
- package/src/oauth/byo-connection.test.ts +105 -0
- package/src/oauth/byo-connection.ts +36 -6
- package/src/oauth/connection-resolver.test.ts +100 -0
- package/src/oauth/connection-resolver.ts +120 -3
- package/src/oauth/provider-serializer.ts +9 -0
- package/src/oauth/scope-utils.ts +24 -0
- package/src/oauth/seed-providers.ts +314 -27
- package/src/outbound-proxy/certs.ts +3 -5
- package/src/permissions/checker.test.ts +11 -6
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/conversation-crud.ts +71 -63
- package/src/persistence/conversation-lineage.ts +12 -4
- package/src/persistence/conversation-queries.ts +2 -2
- package/src/persistence/conversation-title-placeholders.ts +23 -0
- package/src/persistence/conversation-title-service.ts +9 -26
- package/src/persistence/embeddings/qdrant-manager.ts +163 -18
- package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
- package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
- package/src/persistence/message-cursor.ts +78 -0
- package/src/persistence/message-reads.ts +30 -0
- package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
- package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
- package/src/persistence/schema/memory-core.ts +5 -0
- package/src/platform/client.test.ts +4 -2
- package/src/platform/client.ts +2 -10
- package/src/plugin-api/constants.ts +2 -0
- package/src/plugin-api/index.ts +1 -0
- package/src/plugin-api/types.ts +2 -0
- package/src/plugins/defaults/index.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
- package/src/plugins/defaults/memory/context-search/format.ts +3 -2
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
- package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
- package/src/plugins/defaults/memory/hooks/init.ts +10 -0
- package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
- package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
- package/src/plugins/defaults/memory/host-utils.ts +10 -0
- package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
- package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
- package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
- package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
- package/src/plugins/defaults/memory/startup.ts +28 -2
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
- package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
- package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
- package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
- package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
- package/src/plugins/defaults/memory/v3/card.ts +3 -2
- package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
- package/src/plugins/defaults/memory/v3/sections.ts +9 -3
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
- package/src/plugins/defaults/memory/worker.ts +6 -0
- package/src/plugins/mcp-servers.ts +15 -75
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
- package/src/providers/__tests__/retry-callsite.test.ts +41 -0
- package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
- package/src/providers/anthropic/client.ts +0 -47
- package/src/providers/gemini/client.ts +9 -23
- package/src/providers/gemini-thought-signature.test.ts +63 -0
- package/src/providers/gemini-thought-signature.ts +44 -0
- package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
- package/src/providers/inference/auth.ts +2 -3
- package/src/providers/inference/connection-availability.ts +27 -8
- package/src/providers/inference/endpoint-probe.ts +10 -1
- package/src/providers/inference/resolve-auth.ts +22 -4
- package/src/providers/inference/service-account-token.ts +248 -0
- package/src/providers/model-catalog.ts +34 -0
- package/src/providers/model-intents.ts +10 -5
- package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
- package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
- package/src/providers/openai/chat-completions-provider.ts +212 -79
- package/src/providers/openai/codex-models.ts +0 -4
- package/src/providers/openai/google-thought-signature.ts +320 -0
- package/src/providers/opencode/client.test.ts +73 -4
- package/src/providers/opencode/client.ts +60 -3
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/outbound-request-sanitize.ts +96 -0
- package/src/providers/retry.ts +30 -19
- package/src/providers/unparseable-tool-args.ts +5 -1
- package/src/runtime/AGENTS.md +3 -1
- package/src/runtime/__tests__/channel-send.test.ts +443 -0
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
- package/src/runtime/__tests__/host-observe.test.ts +40 -1
- package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
- package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
- package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
- package/src/runtime/agent-wake.ts +2 -1
- package/src/runtime/assistant-event-hub.ts +21 -3
- package/src/runtime/auth/same-actor.ts +38 -3
- package/src/runtime/channel-send.ts +316 -0
- package/src/runtime/host-observe.ts +11 -1
- package/src/runtime/http-server.ts +160 -151
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +16 -5
- package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +275 -0
- package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
- package/src/runtime/routes/acp-claude-auth-routes.ts +11 -3
- package/src/runtime/routes/acp-routes.test.ts +15 -0
- package/src/runtime/routes/acp-routes.ts +72 -9
- package/src/runtime/routes/app-management-routes.ts +90 -2
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/channel-send-routes.ts +85 -0
- package/src/runtime/routes/conversation-list-routes.ts +14 -3
- package/src/runtime/routes/conversation-query-routes.ts +40 -0
- package/src/runtime/routes/conversation-routes.ts +16 -3
- package/src/runtime/routes/credential-routes.ts +20 -14
- package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
- package/src/runtime/routes/desktop-setup-routes.ts +35 -0
- package/src/runtime/routes/diagnostics-routes.ts +61 -4
- package/src/runtime/routes/events-routes.ts +4 -0
- package/src/runtime/routes/host-cu-routes.ts +6 -0
- package/src/runtime/routes/index.ts +4 -2
- package/src/runtime/routes/log-export-routes.ts +2 -0
- package/src/runtime/routes/mcp-auth-routes.ts +39 -47
- package/src/runtime/routes/migration-routes.ts +6 -20
- package/src/runtime/routes/oauth-commands-routes.ts +50 -0
- package/src/runtime/routes/oauth-providers.ts +7 -0
- package/src/runtime/routes/platform-routes.ts +18 -15
- package/src/runtime/routes/workspace-routes.test.ts +55 -26
- package/src/runtime/routes/workspace-routes.ts +46 -63
- package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
- package/src/runtime/routes/workspace-tree-walk.ts +240 -0
- package/src/runtime/routes/workspace-utils.ts +17 -0
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +16 -3
- package/src/runtime/sync/resource-sync-events.ts +2 -1
- package/src/schedule/run-script.ts +2 -1
- package/src/schedule/schedule-store.ts +9 -2
- package/src/schedule/worker.ts +1 -1
- package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
- package/src/security/ces-http-record-backend.ts +120 -0
- package/src/security/ces-rpc-record-backend.ts +7 -0
- package/src/security/untrusted-content.ts +3 -1
- package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
- package/src/skills/workspace-skill-md-signature.ts +50 -0
- package/src/subagent/consult-context.ts +12 -25
- package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
- package/src/tools/acp/list-agents.test.ts +3 -1
- package/src/tools/acp/spawn.test.ts +146 -11
- package/src/tools/acp/spawn.ts +21 -3
- package/src/tools/acp/status.test.ts +94 -0
- package/src/tools/acp/status.ts +40 -3
- package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
- package/src/tools/browser/network-recorder.ts +2 -1
- package/src/tools/capability-offer.test.ts +22 -2
- package/src/tools/capability-offer.ts +16 -1
- package/src/tools/computer-use/definitions.ts +121 -2
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
- package/src/tools/credentials/metadata-store.ts +100 -0
- package/src/tools/filesystem/search.ts +2 -1
- package/src/tools/host-filesystem/edit.test.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.test.ts +2 -1
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.test.ts +2 -1
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.test.ts +2 -1
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +16 -0
- package/src/tools/host-terminal/host-shell.ts +4 -20
- package/src/tools/network/web-search.ts +6 -1
- package/src/tools/policy-context.ts +6 -7
- package/src/tools/skills/scaffold-managed.ts +61 -3
- package/src/tools/subagent/spawn.ts +5 -8
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
- package/src/tools/terminal/sanitized-bash.ts +105 -0
- package/src/tools/ui-surface/channel-variants.ts +10 -59
- package/src/tools/watch/watch-retro-report.ts +0 -8
- package/src/types/host-capabilities.ts +5 -3
- package/src/util/__tests__/language-subtag.test.ts +23 -1
- package/src/util/ca-bundle.ts +13 -0
- package/src/util/language-subtag.ts +16 -0
- package/src/util/platform.ts +8 -0
- package/src/util/unicode.ts +15 -1
- package/src/watch/watch-retro.ts +6 -8
- package/src/watch/watch-timeline.ts +2 -2
- package/src/workflows/run-manager.ts +2 -1
- package/src/workspace/git-porcelain.ts +66 -0
- package/src/workspace/git-service.ts +6 -55
- package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
- package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
- package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
- package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/app-source-watcher.test.ts +0 -218
- package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
- package/src/__tests__/speaker-identification.test.ts +0 -56
- package/src/calls/speaker-identification.ts +0 -233
- package/src/daemon/app-source-watcher.ts +0 -218
- package/src/messaging/providers/whatsapp/client.ts +0 -29
- package/src/runtime/routes/debug-bash-routes.ts +0 -173
- package/src/util/fs-watcher-error.ts +0 -36
- /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
|
@@ -32,6 +32,7 @@ import { getAttentionStateByConversationIds } from "../persistence/conversation-
|
|
|
32
32
|
import {
|
|
33
33
|
getAssistantMessageIdsInTurn,
|
|
34
34
|
getMessageById,
|
|
35
|
+
getMessagesAfter,
|
|
35
36
|
type MessageRow,
|
|
36
37
|
} from "../persistence/conversation-crud.js";
|
|
37
38
|
import { stringifyMessageContent } from "../persistence/message-content.js";
|
|
@@ -39,7 +40,6 @@ import {
|
|
|
39
40
|
isPrivateAssistantText,
|
|
40
41
|
projectPersistedAssistantContent,
|
|
41
42
|
} from "../persistence/user-facing-content.js";
|
|
42
|
-
import type { ContentBlock } from "../providers/types.js";
|
|
43
43
|
import { emitNotificationSignal } from "./emit-signal.js";
|
|
44
44
|
import { hasNotifiedSourceContextSince } from "./events-store.js";
|
|
45
45
|
import {
|
|
@@ -79,28 +79,72 @@ export interface ScheduleResultNotificationParams {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/**
|
|
82
|
-
* Whether
|
|
83
|
-
*
|
|
82
|
+
* Whether the run delivered its result through the messaging tool, and the
|
|
83
|
+
* call succeeded.
|
|
84
84
|
*
|
|
85
|
-
* The
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* cannot see them. Without this check a well-authored Slack digest would post
|
|
85
|
+
* The messaging tool is the route the schedule skill prescribes for rich
|
|
86
|
+
* content, and it writes no `notification_events` row, so the pipeline probe
|
|
87
|
+
* cannot see it. Without this check a well-authored Slack digest would post
|
|
89
88
|
* its summary and then get a second notification whose body is "Posted the
|
|
90
|
-
* digest to #general."
|
|
91
|
-
*
|
|
92
|
-
*
|
|
89
|
+
* digest to #general."
|
|
90
|
+
*
|
|
91
|
+
* Both conditions are load-bearing. The route has to be recognized, because
|
|
92
|
+
* this is a known-routes list rather than a general "did the run do anything?"
|
|
93
|
+
* heuristic. And the call has to have succeeded: a `tool_use` block records
|
|
94
|
+
* only that the model called the tool, so a send the channel refused leaves
|
|
95
|
+
* the same block as one that landed, and suppressing on it costs the user both
|
|
96
|
+
* the digest and the run's explanation of the failure. The executor's verdict
|
|
97
|
+
* is on the `tool_result` the call produced, so success is read from there,
|
|
98
|
+
* and a call with no result counts as undelivered. Either condition unmet gets
|
|
99
|
+
* the fallback, which is the safe failure.
|
|
100
|
+
*
|
|
101
|
+
* The result read starts at the run's first row, so it covers the results
|
|
102
|
+
* interleaved through the turn. Rows from a later turn in a reused
|
|
103
|
+
* conversation can come back too and are harmless, because only this run's
|
|
104
|
+
* own call ids are matched.
|
|
105
|
+
*
|
|
106
|
+
* A Slack Web API post through bash is deliberately not a route, because its
|
|
107
|
+
* success proves nothing about the post. Slack refuses a call with HTTP 200
|
|
108
|
+
* and `ok: false` in the body, and the authenticated-request command fails
|
|
109
|
+
* only on a non-2xx status, so a post Slack refused still leaves a successful
|
|
110
|
+
* tool result. A run that posts that way gets the fallback too: at worst a
|
|
111
|
+
* duplicate, never a silence.
|
|
93
112
|
*/
|
|
94
|
-
function
|
|
95
|
-
|
|
96
|
-
|
|
113
|
+
function deliveredThroughMessagingTool(
|
|
114
|
+
conversationId: string,
|
|
115
|
+
runRows: readonly MessageRow[],
|
|
116
|
+
): boolean {
|
|
117
|
+
const callIds = new Set<string>();
|
|
118
|
+
for (const row of runRows) {
|
|
119
|
+
for (const block of row.content) {
|
|
120
|
+
if (block.type === "tool_use" && block.name === "messaging_send") {
|
|
121
|
+
callIds.add(block.id);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
97
124
|
}
|
|
98
|
-
if (
|
|
99
|
-
return
|
|
125
|
+
if (callIds.size === 0) {
|
|
126
|
+
return false;
|
|
100
127
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
128
|
+
const [firstRunRow] = runRows;
|
|
129
|
+
for (const row of getMessagesAfter(conversationId, {
|
|
130
|
+
id: firstRunRow.id,
|
|
131
|
+
createdAt: firstRunRow.createdAt,
|
|
132
|
+
})) {
|
|
133
|
+
if (row.role !== "user") {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
for (const block of row.content) {
|
|
137
|
+
// guard:allow-tool-result-only: the local executor's verdict on a
|
|
138
|
+
// delivery it ran. A server-side `web_search_tool_result` carries no
|
|
139
|
+
// `is_error` and never delivers anything.
|
|
140
|
+
if (
|
|
141
|
+
block.type === "tool_result" &&
|
|
142
|
+
block.is_error !== true &&
|
|
143
|
+
callIds.has(block.tool_use_id)
|
|
144
|
+
) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
104
148
|
}
|
|
105
149
|
return false;
|
|
106
150
|
}
|
|
@@ -237,11 +281,11 @@ export async function emitScheduleResultNotification(
|
|
|
237
281
|
return;
|
|
238
282
|
}
|
|
239
283
|
|
|
240
|
-
// The run delivered around the pipeline
|
|
241
|
-
//
|
|
242
|
-
//
|
|
284
|
+
// The run delivered around the pipeline through the messaging tool, and
|
|
285
|
+
// the call succeeded. The user has the result; a notification reading
|
|
286
|
+
// "posted it" on top would be the duplicate.
|
|
243
287
|
const runRows = collectRunRows(latestRow, conversationId, runStartedAt);
|
|
244
|
-
if (
|
|
288
|
+
if (deliveredThroughMessagingTool(conversationId, runRows)) {
|
|
245
289
|
return;
|
|
246
290
|
}
|
|
247
291
|
|
package/src/oauth/AGENTS.md
CHANGED
|
@@ -8,6 +8,8 @@ When introducing a new built-in OAuth integration (one that appears in `seed-pro
|
|
|
8
8
|
|
|
9
9
|
Add an entry to `PROVIDER_SEED_DATA`. Required fields: `provider`, `authorizeUrl`, `tokenExchangeUrl`, `defaultScopes`, `displayLabel`, `description`, `dashboardUrl`, `clientIdPlaceholder`, `logoUrl`, and `injectionTemplates`. Optional: `availableScopes` — either a structured array of `{scope, description?}` objects or a URL string pointing to the provider's scope documentation. See existing entries for the full shape. The `provider` key must be snake_case and is used as the canonical identifier everywhere else.
|
|
10
10
|
|
|
11
|
+
`injectionTemplates` is also a security boundary, not only an injection rule. `assistant oauth request` and `assistant channels request` accept an absolute URL only when its host matches one of the entry's `hostPattern` values (`assertOAuthRequestUrlAllowed` in `../runtime/routes/oauth-commands-routes.ts`), so that list is the full set of hosts the credential may be sent to. A pattern matches exactly unless it starts with `*.`; reserve the wildcard for per-tenant hosts that cannot be enumerated (`*.salesforce.com`), and name a fixed host the provider documents (`files.slack.com`) by name. The guard sees only the URL the caller passes: the connection follows a redirect itself, and a cross-origin hop carries no `Authorization`, so a redirect target never receives the credential and never needs listing.
|
|
12
|
+
|
|
11
13
|
If the provider will support managed mode, set `managedServiceConfigKey` to a slug matching the key you will add to `ServicesSchema` (e.g. `"acme-oauth"`).
|
|
12
14
|
|
|
13
15
|
### 2. _(managed only)_ Add a service schema — `../config/schemas/services.ts`
|
|
@@ -60,6 +60,26 @@ describe("PROVIDER_SEED_DATA managed mode wiring", () => {
|
|
|
60
60
|
);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
// `folders:read` is documented too, but the app's scope picker only
|
|
64
|
+
// offers `folder_metadata:read` under Folders, so Figma answers
|
|
65
|
+
// "Invalid scopes for app" whenever it is requested.
|
|
66
|
+
expect(figma.defaultScopes).not.toContain("folders:read");
|
|
67
|
+
if (Array.isArray(availableScopes)) {
|
|
68
|
+
expect(availableScopes.map(({ scope }) => scope)).toContain(
|
|
69
|
+
"folders:read",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// `file_code_connect:write` is only offered to apps owned by an
|
|
74
|
+
// Organization-plan team. Managed apps in other environments are not,
|
|
75
|
+
// so requesting it fails the whole authorization there.
|
|
76
|
+
expect(figma.defaultScopes).not.toContain("file_code_connect:write");
|
|
77
|
+
if (Array.isArray(availableScopes)) {
|
|
78
|
+
expect(availableScopes.map(({ scope }) => scope)).toContain(
|
|
79
|
+
"file_code_connect:write",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
63
83
|
// GET /v1/me backs both the ping and the identity label.
|
|
64
84
|
expect(figma.defaultScopes).toContain("current_user:read");
|
|
65
85
|
});
|
|
@@ -127,6 +147,135 @@ describe("PROVIDER_SEED_DATA managed mode wiring", () => {
|
|
|
127
147
|
expect(figma.featureFlag).toBe("figma-oauth");
|
|
128
148
|
});
|
|
129
149
|
|
|
150
|
+
test("shopify keeps its per-tenant URL templates", () => {
|
|
151
|
+
// Shopify has no global OAuth host: every endpoint lives on the
|
|
152
|
+
// merchant's own domain. Hardcoding any host here would send every
|
|
153
|
+
// merchant's authorization to the wrong store, so the {tenant_host}
|
|
154
|
+
// placeholder the platform substitutes must survive.
|
|
155
|
+
const shopify = PROVIDER_SEED_DATA.shopify;
|
|
156
|
+
expect(shopify).toBeDefined();
|
|
157
|
+
for (const url of [
|
|
158
|
+
shopify.authorizeUrl,
|
|
159
|
+
shopify.tokenExchangeUrl,
|
|
160
|
+
shopify.refreshUrl,
|
|
161
|
+
shopify.baseUrl,
|
|
162
|
+
shopify.identityUrl,
|
|
163
|
+
]) {
|
|
164
|
+
expect(url).toContain("{tenant_host}");
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("shopify requests comma-separated scopes", () => {
|
|
169
|
+
// Shopify parses `scope` as a comma-separated list; the OAuth2 default
|
|
170
|
+
// space separator is read as one malformed scope and the whole
|
|
171
|
+
// authorization request is rejected.
|
|
172
|
+
expect(PROVIDER_SEED_DATA.shopify.scopeSeparator).toBe(",");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("shopify does not request approval-gated scopes", () => {
|
|
176
|
+
// Shopify rejects the entire authorization request when the app cannot
|
|
177
|
+
// grant a requested scope -- the same failure that broke monday and
|
|
178
|
+
// Figma. These scopes all require Shopify's prior approval.
|
|
179
|
+
const gated = [
|
|
180
|
+
"read_all_orders",
|
|
181
|
+
"read_customer_payment_methods",
|
|
182
|
+
"read_own_subscription_contracts",
|
|
183
|
+
"write_own_subscription_contracts",
|
|
184
|
+
"read_shopify_payments_dispute_evidences",
|
|
185
|
+
"write_shopify_payments_dispute_evidences",
|
|
186
|
+
];
|
|
187
|
+
for (const scope of gated) {
|
|
188
|
+
expect(PROVIDER_SEED_DATA.shopify.defaultScopes).not.toContain(scope);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("shopify injects the Admin API's own auth header", () => {
|
|
193
|
+
// The Admin API reads X-Shopify-Access-Token and ignores an
|
|
194
|
+
// Authorization: Bearer header entirely, so a Bearer template here would
|
|
195
|
+
// make every proxied request unauthenticated.
|
|
196
|
+
const templates = PROVIDER_SEED_DATA.shopify.injectionTemplates;
|
|
197
|
+
expect(templates).toBeDefined();
|
|
198
|
+
const [template] = templates ?? [];
|
|
199
|
+
expect(template?.headerName).toBe("X-Shopify-Access-Token");
|
|
200
|
+
expect(template?.valuePrefix).toBe("");
|
|
201
|
+
expect(template?.hostPattern).toBe("*.myshopify.com");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("quickbooks is wired up for managed mode behind its flag", () => {
|
|
205
|
+
const quickbooks = PROVIDER_SEED_DATA.quickbooks;
|
|
206
|
+
expect(quickbooks).toBeDefined();
|
|
207
|
+
expect(quickbooks.managedServiceConfigKey).toBe("quickbooks-oauth");
|
|
208
|
+
expect("quickbooks-oauth" in ServicesSchema.shape).toBe(true);
|
|
209
|
+
// Hidden until the platform side and client ids are live, like Figma
|
|
210
|
+
// and Shopify.
|
|
211
|
+
expect(quickbooks.featureFlag).toBe("quickbooks-oauth");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("quickbooks uses Intuit's OAuth endpoints with HTTP Basic", () => {
|
|
215
|
+
const quickbooks = PROVIDER_SEED_DATA.quickbooks;
|
|
216
|
+
expect(quickbooks.authorizeUrl).toBe(
|
|
217
|
+
"https://appcenter.intuit.com/connect/oauth2",
|
|
218
|
+
);
|
|
219
|
+
expect(quickbooks.tokenExchangeUrl).toBe(
|
|
220
|
+
"https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer",
|
|
221
|
+
);
|
|
222
|
+
expect(quickbooks.refreshUrl).toBe(quickbooks.tokenExchangeUrl);
|
|
223
|
+
expect(quickbooks.tokenEndpointAuthMethod).toBe("client_secret_basic");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("quickbooks keeps its per-company URL templates", () => {
|
|
227
|
+
// Every Accounting API path is scoped to the realm the user picked on
|
|
228
|
+
// Intuit's consent screen. The platform fills {realm_id} from the
|
|
229
|
+
// connection; a hardcoded realm here would send every company's calls
|
|
230
|
+
// to the wrong books.
|
|
231
|
+
const quickbooks = PROVIDER_SEED_DATA.quickbooks;
|
|
232
|
+
for (const url of [quickbooks.baseUrl, quickbooks.identityUrl]) {
|
|
233
|
+
expect(url).toContain("/v3/company/{realm_id}");
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("quickbooks seeds no ping or revoke URL", () => {
|
|
238
|
+
// The ping route sends the URL's origin as a base override and cannot
|
|
239
|
+
// fill {realm_id}; the daemon's revoke helper posts an unauthenticated
|
|
240
|
+
// form body that Intuit's JSON + Basic-auth endpoint rejects. Either
|
|
241
|
+
// would fail silently on every your-own connection.
|
|
242
|
+
const quickbooks = PROVIDER_SEED_DATA.quickbooks;
|
|
243
|
+
expect(quickbooks.pingUrl).toBeUndefined();
|
|
244
|
+
expect(quickbooks.revokeUrl).toBeUndefined();
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("quickbooks requests only the accounting scope by default", () => {
|
|
248
|
+
// Payments needs a Payments-enabled app and the OpenID scopes only add
|
|
249
|
+
// the user's profile; requesting either widens the consent screen for
|
|
250
|
+
// nothing the Accounting API needs.
|
|
251
|
+
const quickbooks = PROVIDER_SEED_DATA.quickbooks;
|
|
252
|
+
expect(quickbooks.defaultScopes).toEqual([
|
|
253
|
+
"com.intuit.quickbooks.accounting",
|
|
254
|
+
]);
|
|
255
|
+
const available = quickbooks.availableScopes;
|
|
256
|
+
expect(Array.isArray(available)).toBe(true);
|
|
257
|
+
if (Array.isArray(available)) {
|
|
258
|
+
expect(available.map(({ scope }) => scope)).toContain(
|
|
259
|
+
"com.intuit.quickbooks.payment",
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("quickbooks allows the credential on both Intuit API hosts", () => {
|
|
265
|
+
// Development keys only work against sandbox companies, which live on
|
|
266
|
+
// the sandbox host. Both hosts belong to Intuit and take the same
|
|
267
|
+
// bearer token.
|
|
268
|
+
const templates = PROVIDER_SEED_DATA.quickbooks.injectionTemplates ?? [];
|
|
269
|
+
expect(templates.map((t) => t.hostPattern).sort()).toEqual([
|
|
270
|
+
"quickbooks.api.intuit.com",
|
|
271
|
+
"sandbox-quickbooks.api.intuit.com",
|
|
272
|
+
]);
|
|
273
|
+
for (const template of templates) {
|
|
274
|
+
expect(template.headerName).toBe("Authorization");
|
|
275
|
+
expect(template.valuePrefix).toBe("Bearer ");
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
130
279
|
test("every managedServiceConfigKey resolves to a ServicesSchema key", () => {
|
|
131
280
|
// Cross-repo invariant: a provider with managedServiceConfigKey but no
|
|
132
281
|
// matching ServicesSchema entry silently falls back to BYO mode in
|
|
@@ -308,6 +308,66 @@ describe("BYOOAuthConnection", () => {
|
|
|
308
308
|
expect((init as RequestInit).method).toBe("GET");
|
|
309
309
|
});
|
|
310
310
|
|
|
311
|
+
test("follows a cross-origin redirect without the credential and returns the target's bytes", async () => {
|
|
312
|
+
// Real fetch against two loopback origins: the credential goes to the
|
|
313
|
+
// host the caller named, the redirect target sees no Authorization,
|
|
314
|
+
// and the target's bytes come back as the response body. This is the
|
|
315
|
+
// path a Slack file download takes when files.slack.com answers with a
|
|
316
|
+
// 302 to its CDN.
|
|
317
|
+
globalThis.fetch = originalFetch;
|
|
318
|
+
await setupCredential("google");
|
|
319
|
+
const bytes = Buffer.from([
|
|
320
|
+
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
|
|
321
|
+
]);
|
|
322
|
+
const seen: { origin?: string | null; target?: string | null } = {};
|
|
323
|
+
|
|
324
|
+
const target = Bun.serve({
|
|
325
|
+
port: 0,
|
|
326
|
+
hostname: "127.0.0.1",
|
|
327
|
+
fetch: (req) => {
|
|
328
|
+
seen.target = req.headers.get("authorization");
|
|
329
|
+
return new Response(bytes, {
|
|
330
|
+
status: 200,
|
|
331
|
+
headers: { "content-type": "image/png" },
|
|
332
|
+
});
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
const origin = Bun.serve({
|
|
336
|
+
port: 0,
|
|
337
|
+
hostname: "127.0.0.1",
|
|
338
|
+
fetch: (req) => {
|
|
339
|
+
seen.origin = req.headers.get("authorization");
|
|
340
|
+
return new Response(null, {
|
|
341
|
+
status: 302,
|
|
342
|
+
headers: { Location: `http://127.0.0.1:${target.port}/signed` },
|
|
343
|
+
});
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
const conn = new BYOOAuthConnection({
|
|
349
|
+
id: "conn-google",
|
|
350
|
+
provider: "google",
|
|
351
|
+
baseUrl: `http://127.0.0.1:${origin.port}`,
|
|
352
|
+
accountInfo: null,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
const result = await conn.request({
|
|
356
|
+
method: "GET",
|
|
357
|
+
path: "/files-pri/T0123-F0456/download/shot.png",
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
expect(seen.origin).toBe("Bearer test-access-token");
|
|
361
|
+
expect(seen.target).toBeNull();
|
|
362
|
+
expect(result.status).toBe(200);
|
|
363
|
+
expect(Buffer.isBuffer(result.body)).toBe(true);
|
|
364
|
+
expect(Buffer.from(result.body as Buffer).equals(bytes)).toBe(true);
|
|
365
|
+
} finally {
|
|
366
|
+
origin.stop(true);
|
|
367
|
+
target.stop(true);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
311
371
|
test("appends query parameters", async () => {
|
|
312
372
|
await setupCredential("google");
|
|
313
373
|
const conn = createConnection();
|
|
@@ -708,6 +768,51 @@ describe("BYOOAuthConnection", () => {
|
|
|
708
768
|
expect(headers.get("Authorization")).toBe("Bearer test-access-token");
|
|
709
769
|
});
|
|
710
770
|
|
|
771
|
+
test("sends the token in the provider's own header when one is configured", async () => {
|
|
772
|
+
// Shopify's Admin API reads X-Shopify-Access-Token and ignores
|
|
773
|
+
// Authorization, so a Bearer header reaches the shop unauthenticated.
|
|
774
|
+
await setupCredential("google");
|
|
775
|
+
const conn = new BYOOAuthConnection({
|
|
776
|
+
id: "conn-google",
|
|
777
|
+
provider: "google",
|
|
778
|
+
baseUrl: "https://example-store.myshopify.com",
|
|
779
|
+
accountInfo: null,
|
|
780
|
+
tokenHeader: { name: "X-Shopify-Access-Token", valuePrefix: "" },
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
await conn.request({
|
|
784
|
+
method: "GET",
|
|
785
|
+
path: "/admin/api/2026-07/shop.json",
|
|
786
|
+
// A caller-supplied Authorization must not ride along.
|
|
787
|
+
headers: { Authorization: "Bearer caller-supplied" },
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
791
|
+
expect(url).toBe(
|
|
792
|
+
"https://example-store.myshopify.com/admin/api/2026-07/shop.json",
|
|
793
|
+
);
|
|
794
|
+
const headers = (init as RequestInit).headers as Headers;
|
|
795
|
+
expect(headers.get("X-Shopify-Access-Token")).toBe("test-access-token");
|
|
796
|
+
expect(headers.has("Authorization")).toBe(false);
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
test("keeps the value prefix from the token header template", async () => {
|
|
800
|
+
await setupCredential("google");
|
|
801
|
+
const conn = new BYOOAuthConnection({
|
|
802
|
+
id: "conn-google",
|
|
803
|
+
provider: "google",
|
|
804
|
+
baseUrl: "https://discord.com/api",
|
|
805
|
+
accountInfo: null,
|
|
806
|
+
tokenHeader: { name: "Authorization", valuePrefix: "Bot " },
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
await conn.request({ method: "GET", path: "/users/@me" });
|
|
810
|
+
|
|
811
|
+
const [, init] = mockFetch.mock.calls[0];
|
|
812
|
+
const headers = (init as RequestInit).headers as Headers;
|
|
813
|
+
expect(headers.get("Authorization")).toBe("Bot test-access-token");
|
|
814
|
+
});
|
|
815
|
+
|
|
711
816
|
test("uses Telegram Bot API token URL format without Bearer auth", async () => {
|
|
712
817
|
await setupTelegramCredential();
|
|
713
818
|
const conn = createConnection("telegram");
|
|
@@ -21,11 +21,29 @@ const log = getLogger("byo-oauth-connection");
|
|
|
21
21
|
/** Default per-request timeout to prevent hung requests from blocking indefinitely. */
|
|
22
22
|
const REQUEST_TIMEOUT_MS = 30_000;
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* How the access token is attached to an outbound request. Almost every
|
|
26
|
+
* provider reads `Authorization: Bearer <token>`; a few use their own header
|
|
27
|
+
* (Shopify's Admin API reads `X-Shopify-Access-Token` and ignores
|
|
28
|
+
* Authorization entirely). Derived from the provider's header-type injection
|
|
29
|
+
* template so the seed is the single source of truth.
|
|
30
|
+
*/
|
|
31
|
+
export interface BYOTokenHeader {
|
|
32
|
+
name: string;
|
|
33
|
+
/** Text placed before the token, e.g. `"Bearer "`, `"Bot "`, or `""`. */
|
|
34
|
+
valuePrefix: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
24
37
|
export interface BYOOAuthConnectionOptions {
|
|
25
38
|
id: string;
|
|
26
39
|
provider: string;
|
|
27
40
|
baseUrl: string;
|
|
28
41
|
accountInfo: string | null;
|
|
42
|
+
/**
|
|
43
|
+
* Token header override. When omitted the connection falls back to
|
|
44
|
+
* `Authorization: Bearer` (or `Bot` for discord_channel).
|
|
45
|
+
*/
|
|
46
|
+
tokenHeader?: BYOTokenHeader | null;
|
|
29
47
|
}
|
|
30
48
|
|
|
31
49
|
export class BYOOAuthConnection implements OAuthConnection {
|
|
@@ -34,12 +52,14 @@ export class BYOOAuthConnection implements OAuthConnection {
|
|
|
34
52
|
readonly accountInfo: string | null;
|
|
35
53
|
|
|
36
54
|
private readonly baseUrl: string;
|
|
55
|
+
private readonly tokenHeader: BYOTokenHeader | null;
|
|
37
56
|
|
|
38
57
|
constructor(opts: BYOOAuthConnectionOptions) {
|
|
39
58
|
this.id = opts.id;
|
|
40
59
|
this.provider = opts.provider;
|
|
41
60
|
this.baseUrl = opts.baseUrl;
|
|
42
61
|
this.accountInfo = opts.accountInfo;
|
|
62
|
+
this.tokenHeader = opts.tokenHeader ?? null;
|
|
43
63
|
}
|
|
44
64
|
|
|
45
65
|
async request(req: OAuthConnectionRequest): Promise<OAuthConnectionResponse> {
|
|
@@ -51,8 +71,12 @@ export class BYOOAuthConnection implements OAuthConnection {
|
|
|
51
71
|
// Discord bot tokens authenticate with the `Bot ` scheme, not
|
|
52
72
|
// `Bearer`. Sending Bearer here reaches Discord as an unusable
|
|
53
73
|
// credential and comes back 401, which reads as a revoked token.
|
|
54
|
-
|
|
55
|
-
|
|
74
|
+
// Providers with their own token header (Shopify) override the
|
|
75
|
+
// whole thing via `tokenHeader`.
|
|
76
|
+
const tokenHeader: BYOTokenHeader = this.tokenHeader ?? {
|
|
77
|
+
name: "Authorization",
|
|
78
|
+
valuePrefix: this.provider === "discord_channel" ? "Bot " : "Bearer ",
|
|
79
|
+
};
|
|
56
80
|
const requestPath = isTelegram
|
|
57
81
|
? buildTelegramBotApiPath(req.path, token)
|
|
58
82
|
: req.path;
|
|
@@ -93,16 +117,22 @@ export class BYOOAuthConnection implements OAuthConnection {
|
|
|
93
117
|
}
|
|
94
118
|
}
|
|
95
119
|
if (!isTelegram) {
|
|
96
|
-
|
|
120
|
+
// The credential always wins over a caller-supplied value, and a
|
|
121
|
+
// provider that reads its own header must not also receive a
|
|
122
|
+
// stray Authorization the caller happened to send.
|
|
123
|
+
if (tokenHeader.name.toLowerCase() !== "authorization") {
|
|
124
|
+
headers.delete("Authorization");
|
|
125
|
+
}
|
|
126
|
+
headers.set(tokenHeader.name, `${tokenHeader.valuePrefix}${token}`);
|
|
97
127
|
}
|
|
98
128
|
|
|
99
129
|
const resp = await fetch(fullUrl, {
|
|
100
130
|
method: req.method,
|
|
101
131
|
headers,
|
|
102
132
|
body: hasBody
|
|
103
|
-
?
|
|
104
|
-
|
|
105
|
-
|
|
133
|
+
? binaryBody !== undefined
|
|
134
|
+
? Buffer.from(binaryBody)
|
|
135
|
+
: (rawBody ?? JSON.stringify(req.body))
|
|
106
136
|
: undefined,
|
|
107
137
|
// Following a redirect would replay a POST as a GET against a URL
|
|
108
138
|
// the caller never asked for, and hide the 3xx from them.
|
|
@@ -72,9 +72,11 @@ import { setConfig } from "../__tests__/helpers/set-config.js";
|
|
|
72
72
|
import { BYOOAuthConnection } from "./byo-connection.js";
|
|
73
73
|
import {
|
|
74
74
|
formatNoConnectionError,
|
|
75
|
+
platformProxyBaseUrl,
|
|
75
76
|
resolveEffectiveBaseUrl,
|
|
76
77
|
resolveOAuthConnection,
|
|
77
78
|
resolveOAuthConnectionWithMeta,
|
|
79
|
+
resolveTokenHeader,
|
|
78
80
|
} from "./connection-resolver.js";
|
|
79
81
|
import { PlatformOAuthConnection } from "./platform-connection.js";
|
|
80
82
|
|
|
@@ -753,3 +755,101 @@ describe("resolveEffectiveBaseUrl", () => {
|
|
|
753
755
|
).toBe("https://gmail.googleapis.com/gmail/v1/users/me");
|
|
754
756
|
});
|
|
755
757
|
});
|
|
758
|
+
|
|
759
|
+
describe("platformProxyBaseUrl", () => {
|
|
760
|
+
test("forwards a concrete seed base URL", () => {
|
|
761
|
+
expect(platformProxyBaseUrl("https://api.figma.com")).toBe(
|
|
762
|
+
"https://api.figma.com",
|
|
763
|
+
);
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
test("withholds a templated base URL so the platform fills it", () => {
|
|
767
|
+
// Shopify's host and QuickBooks' realm are pinned to the connection on
|
|
768
|
+
// the platform. Forwarding the unfilled template would fail the proxy's
|
|
769
|
+
// allowlist check and shadow the platform's own default.
|
|
770
|
+
expect(platformProxyBaseUrl("https://{tenant_host}")).toBeUndefined();
|
|
771
|
+
expect(
|
|
772
|
+
platformProxyBaseUrl(
|
|
773
|
+
"https://quickbooks.api.intuit.com/v3/company/{realm_id}",
|
|
774
|
+
),
|
|
775
|
+
).toBeUndefined();
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
test("treats a missing base URL as no override", () => {
|
|
779
|
+
expect(platformProxyBaseUrl(undefined)).toBeUndefined();
|
|
780
|
+
expect(platformProxyBaseUrl(null)).toBeUndefined();
|
|
781
|
+
expect(platformProxyBaseUrl("")).toBeUndefined();
|
|
782
|
+
});
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
describe("resolveTokenHeader", () => {
|
|
786
|
+
const shopify = JSON.stringify([
|
|
787
|
+
{
|
|
788
|
+
hostPattern: "*.myshopify.com",
|
|
789
|
+
injectionType: "header",
|
|
790
|
+
headerName: "X-Shopify-Access-Token",
|
|
791
|
+
valuePrefix: "",
|
|
792
|
+
},
|
|
793
|
+
]);
|
|
794
|
+
|
|
795
|
+
test("returns null when the provider declares no templates", () => {
|
|
796
|
+
expect(resolveTokenHeader(undefined, "https://api.example.com")).toBeNull();
|
|
797
|
+
expect(resolveTokenHeader(null, "https://api.example.com")).toBeNull();
|
|
798
|
+
expect(resolveTokenHeader("[]", "https://api.example.com")).toBeNull();
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
test("returns null for malformed JSON", () => {
|
|
802
|
+
expect(
|
|
803
|
+
resolveTokenHeader("{not json", "https://api.example.com"),
|
|
804
|
+
).toBeNull();
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
test("ignores non-header injection templates", () => {
|
|
808
|
+
const query = JSON.stringify([
|
|
809
|
+
{
|
|
810
|
+
hostPattern: "api.example.com",
|
|
811
|
+
injectionType: "query",
|
|
812
|
+
headerName: "",
|
|
813
|
+
},
|
|
814
|
+
]);
|
|
815
|
+
expect(resolveTokenHeader(query, "https://api.example.com")).toBeNull();
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
test("picks the template whose wildcard host pattern matches the base URL", () => {
|
|
819
|
+
const templates = JSON.stringify([
|
|
820
|
+
{
|
|
821
|
+
hostPattern: "api.other.com",
|
|
822
|
+
injectionType: "header",
|
|
823
|
+
headerName: "Authorization",
|
|
824
|
+
valuePrefix: "Bearer ",
|
|
825
|
+
},
|
|
826
|
+
...JSON.parse(shopify),
|
|
827
|
+
]);
|
|
828
|
+
expect(
|
|
829
|
+
resolveTokenHeader(templates, "https://example-store.myshopify.com"),
|
|
830
|
+
).toEqual({ name: "X-Shopify-Access-Token", valuePrefix: "" });
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
test("falls back to the first header template when the host cannot be matched", () => {
|
|
834
|
+
// A still-templated base URL is not a parseable URL.
|
|
835
|
+
expect(resolveTokenHeader(shopify, "https://{tenant_host}")).toEqual({
|
|
836
|
+
name: "X-Shopify-Access-Token",
|
|
837
|
+
valuePrefix: "",
|
|
838
|
+
});
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
test("preserves the Bot prefix for discord_channel", () => {
|
|
842
|
+
const discord = JSON.stringify([
|
|
843
|
+
{
|
|
844
|
+
hostPattern: "discord.com",
|
|
845
|
+
injectionType: "header",
|
|
846
|
+
headerName: "Authorization",
|
|
847
|
+
valuePrefix: "Bot ",
|
|
848
|
+
},
|
|
849
|
+
]);
|
|
850
|
+
expect(resolveTokenHeader(discord, "https://discord.com/api")).toEqual({
|
|
851
|
+
name: "Authorization",
|
|
852
|
+
valuePrefix: "Bot ",
|
|
853
|
+
});
|
|
854
|
+
});
|
|
855
|
+
});
|