@vellumai/assistant 0.11.3 → 0.11.4-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 +11 -6
- package/docs/architecture/memory.md +11 -0
- package/docs/architecture/turn-actor.md +70 -0
- package/docs/flux-turn-detection-spike.md +243 -0
- package/docs/stt-provider-onboarding.md +3 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/openapi.yaml +139 -37
- package/package.json +1 -1
- package/scripts/voice-ttft-spike.ts +3 -3
- package/src/__tests__/app-compiler.test.ts +38 -3
- package/src/__tests__/attachments-store.test.ts +21 -12
- package/src/__tests__/byok-default-profile-ensure.test.ts +17 -0
- package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
- package/src/__tests__/call-site-routing-provider.test.ts +1 -1
- package/src/__tests__/channel-availability-routes.test.ts +14 -1
- package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
- package/src/__tests__/channel-delivery-store.test.ts +14 -14
- package/src/__tests__/config-loader-backfill.test.ts +3 -3
- package/src/__tests__/config-schema.test.ts +25 -10
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
- package/src/__tests__/conversation-agent-loop.test.ts +28 -20
- package/src/__tests__/conversation-attention-store.test.ts +63 -0
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
- package/src/__tests__/conversation-fork-crud.test.ts +69 -0
- package/src/__tests__/conversation-fork-referential.test.ts +67 -0
- package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
- package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
- package/src/__tests__/conversation-queue.test.ts +177 -4
- package/src/__tests__/conversation-runtime-assembly.test.ts +134 -102
- package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
- package/src/__tests__/credential-prompt-route.test.ts +7 -10
- package/src/__tests__/custom-profile-ensure.test.ts +5 -1
- package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
- package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
- package/src/__tests__/document-append-idempotency.test.ts +233 -0
- package/src/__tests__/edit-propagation.test.ts +0 -7
- package/src/__tests__/helpers/mock-actor-context.ts +49 -0
- package/src/__tests__/helpers/mock-conversation.ts +13 -1
- package/src/__tests__/injector-chain.test.ts +63 -41
- package/src/__tests__/injector-disk-pressure.test.ts +11 -23
- package/src/__tests__/llm-context-resolution.test.ts +73 -1
- package/src/__tests__/llm-schema.test.ts +5 -2
- package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +6 -5
- package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
- package/src/__tests__/mtime-cache.test.ts +1 -1
- package/src/__tests__/non-member-access-request.test.ts +0 -20
- package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
- package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -0
- package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
- package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +434 -105
- package/src/__tests__/scoped-approval-grants.test.ts +11 -6
- package/src/__tests__/secret-ingress-channel.test.ts +0 -1
- package/src/__tests__/skills.test.ts +32 -0
- package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
- package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
- package/src/__tests__/subagent-spawn-and-await.test.ts +14 -10
- package/src/__tests__/turn-events-store.test.ts +43 -0
- package/src/__tests__/ui-shape-teaching.test.ts +33 -0
- package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
- package/src/__tests__/user-plugin-loader.test.ts +1 -1
- package/src/__tests__/visible-app-context.test.ts +16 -9
- package/src/__tests__/voice-config-update.test.ts +40 -0
- package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
- package/src/__tests__/worker-plugin-surface.test.ts +77 -0
- package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
- package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
- package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
- package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
- package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
- package/src/acp/__tests__/auth-required.test.ts +161 -0
- package/src/acp/acp-claude-oauth.ts +19 -2
- package/src/acp/agent-process.test.ts +100 -0
- package/src/acp/agent-process.ts +29 -26
- package/src/acp/auth-required.ts +102 -0
- package/src/acp/session-manager.test.ts +119 -0
- package/src/acp/session-manager.ts +68 -2
- package/src/api/events/acp-auth-required.ts +55 -0
- package/src/api/index.ts +7 -0
- package/src/api/surfaces.ts +7 -3
- package/src/apps/app-store.ts +3 -0
- package/src/bundler/package-resolver.ts +2 -30
- package/src/calls/__tests__/voice-session-bridge.test.ts +173 -1
- package/src/calls/__tests__/voice-triage-escalate.test.ts +94 -0
- package/src/calls/call-controller.ts +19 -3
- package/src/calls/call-setup-flow.ts +0 -1
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/calls/voice-session-bridge.ts +71 -16
- package/src/calls/voice-triage-escalate.ts +104 -2
- package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
- package/src/channels/config.ts +13 -0
- package/src/channels/plugin-channel-declarations.ts +108 -0
- package/src/channels/types.ts +30 -0
- package/src/cli/AGENTS.md +5 -2
- package/src/cli/commands/credentials.help.ts +2 -2
- package/src/cli/commands/inference-providers.ts +1 -1
- package/src/cli/commands/mcp.help.ts +13 -4
- package/src/cli/commands/mcp.ts +9 -0
- package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
- package/src/cli/commands/memory/index.help.ts +43 -1
- package/src/cli/commands/memory/memory-v3.ts +64 -0
- package/src/cli/commands/stt.help.ts +27 -2
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
- package/src/cli/lib/bundled-marketplace.json +13 -0
- package/src/cli/lib/upgrade-plugin.ts +42 -0
- package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
- package/src/config/__tests__/default-provider.test.ts +6 -1
- package/src/config/__tests__/profile-materialization.test.ts +75 -19
- package/src/config/bundled-skills/acp/SKILL.md +6 -7
- package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
- package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
- package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
- package/src/config/call-site-defaults.ts +4 -5
- package/src/config/default-profile-catalog.ts +83 -12
- package/src/config/default-profile-names.ts +4 -1
- package/src/config/default-provider-resolution.ts +4 -0
- package/src/config/llm-context-resolution.ts +11 -3
- package/src/config/llm-resolver.ts +28 -1
- package/src/config/profile-materialization.ts +70 -22
- package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
- package/src/config/schemas/call-site-catalog.ts +4 -4
- package/src/config/schemas/live-voice.ts +57 -23
- package/src/config/schemas/llm.ts +59 -32
- package/src/config/schemas/mcp.ts +23 -0
- package/src/config/schemas/plugin-updates.ts +6 -2
- package/src/config/schemas/stt.ts +1 -0
- package/src/context/outbound-sanitize.ts +96 -1
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
- package/src/daemon/conversation-agent-loop.ts +17 -6
- package/src/daemon/conversation-messaging.ts +5 -1
- package/src/daemon/conversation-notifiers.ts +9 -1
- package/src/daemon/conversation-process.ts +36 -6
- package/src/daemon/conversation-runtime-assembly.ts +3 -4
- package/src/daemon/conversation-surfaces.ts +27 -5
- package/src/daemon/conversation-tool-setup.ts +1 -2
- package/src/daemon/conversation.ts +48 -0
- package/src/daemon/interactive-turn-sender.ts +59 -0
- package/src/daemon/mcp-reload-service.ts +36 -6
- package/src/daemon/process-message.ts +24 -24
- package/src/daemon/providers-setup.ts +6 -3
- package/src/daemon/trust-context-types.ts +29 -0
- package/src/daemon/wake-conversation-ops.ts +3 -2
- package/src/documents/document-store.ts +138 -5
- package/src/hooks/hook-loader.ts +3 -3
- package/src/hooks/registry.ts +50 -6
- package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
- package/src/inbound/oauth-callback-url.ts +61 -0
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
- package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +932 -0
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
- package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
- package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
- package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
- package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
- package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
- package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
- package/src/live-voice/live-voice-archive.ts +2 -0
- package/src/live-voice/live-voice-metrics.ts +57 -32
- package/src/live-voice/live-voice-photo.ts +1 -2
- package/src/live-voice/live-voice-session.ts +535 -314
- package/src/live-voice/progress-narration.ts +277 -0
- package/src/live-voice/protocol.ts +21 -1
- package/src/mcp/__tests__/effective-config.test.ts +238 -0
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
- package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
- package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
- package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
- package/src/mcp/client.ts +16 -11
- package/src/mcp/effective-config.ts +113 -0
- package/src/mcp/manager.ts +11 -6
- package/src/mcp/mcp-auth-orchestrator.ts +13 -22
- package/src/mcp/mcp-oauth-provider.ts +205 -240
- package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
- package/src/monitoring/plugin-auto-update.ts +128 -24
- package/src/notifications/signal.ts +1 -0
- package/src/permissions/confirmation-guardian-request.test.ts +15 -11
- package/src/permissions/confirmation-guardian-request.ts +2 -2
- package/src/permissions/question-guardian-request.test.ts +14 -6
- package/src/permissions/question-guardian-request.ts +1 -2
- package/src/persistence/attachments-store.ts +8 -1
- package/src/persistence/bookmark-crud.ts +3 -7
- package/src/persistence/conversation-attention-store.ts +16 -45
- package/src/persistence/conversation-crud.ts +33 -4
- package/src/persistence/conversation-lineage.ts +9 -0
- package/src/persistence/conversation-queries.ts +108 -41
- package/src/persistence/delivery-crud.ts +38 -29
- package/src/persistence/external-conversation-store.ts +32 -4
- package/src/persistence/llm-request-log-store.ts +4 -10
- package/src/persistence/llm-usage-store.ts +8 -3
- package/src/persistence/message-reads.test.ts +197 -0
- package/src/persistence/message-reads.ts +211 -0
- package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
- package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
- package/src/persistence/real-user-turn-filter.ts +27 -3
- package/src/persistence/steps.ts +9 -0
- package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
- package/src/plugin-api/conversation-turn.ts +168 -5
- package/src/plugin-api/index.ts +21 -5
- package/src/plugin-api/vision-support.test.ts +1 -1
- package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
- package/src/plugins/defaults/memory/AGENTS.md +4 -0
- package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
- package/src/plugins/defaults/memory/buffer-format.ts +165 -0
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
- package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +8 -12
- package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
- package/src/plugins/defaults/memory/indexer.ts +3 -1
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
- package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
- package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +33 -0
- package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +199 -2
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +25 -18
- package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
- package/src/plugins/defaults/memory/substrate/static-context.ts +160 -4
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
- package/src/plugins/defaults/memory/v3/prune.ts +2 -0
- package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
- package/src/plugins/defaults/memory/worker.ts +6 -3
- package/src/plugins/external-plugin-loader.ts +47 -0
- package/src/plugins/mcp-servers.ts +361 -0
- package/src/plugins/mtime-cache.ts +23 -49
- package/src/plugins/worker-plugin-surface.ts +33 -0
- package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
- package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
- package/src/providers/__tests__/retry-callsite.test.ts +5 -2
- package/src/providers/call-site-routing.ts +30 -3
- package/src/providers/connection-resolution.ts +194 -11
- package/src/providers/inference/auth.ts +6 -0
- package/src/providers/inference/connection-availability.ts +24 -2
- package/src/providers/inference/connections.ts +2 -0
- package/src/providers/model-catalog.ts +3 -3
- package/src/providers/model-intents.ts +28 -8
- package/src/providers/openai/chat-completions-provider.ts +5 -6
- package/src/providers/openai/codex-models.ts +2 -1
- package/src/providers/provider-send-message.ts +32 -3
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +620 -0
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
- package/src/providers/speech-to-text/deepgram-flux-realtime.ts +719 -0
- package/src/providers/speech-to-text/provider-catalog.ts +99 -8
- package/src/providers/speech-to-text/resolve.ts +25 -2
- package/src/routes/worker.ts +17 -5
- package/src/runtime/access-request-helper.ts +9 -12
- package/src/runtime/agent-wake.ts +3 -3
- package/src/runtime/pre-first-message-gate.ts +4 -0
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +219 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +44 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +62 -1
- package/src/runtime/routes/channel-availability-routes.ts +32 -14
- package/src/runtime/routes/channel-route-shared.ts +0 -6
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
- package/src/runtime/routes/conversation-list-routes.ts +112 -1
- package/src/runtime/routes/conversation-query-routes.ts +40 -27
- package/src/runtime/routes/credential-prompt-routes.ts +4 -7
- package/src/runtime/routes/default-provider-routes.ts +15 -0
- package/src/runtime/routes/inbound-message-handler.ts +17 -41
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
- package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +1 -3
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -4
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +11 -20
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
- package/src/runtime/routes/inference-profiles-routes.ts +20 -11
- package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/mcp-auth-routes.ts +148 -57
- package/src/runtime/routes/plugins-routes.ts +21 -3
- package/src/runtime/routes/stt-routes.ts +31 -25
- package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
- package/src/runtime/routes/user-route-dispatcher.ts +39 -14
- package/src/runtime/routes/user-route-import.ts +108 -0
- package/src/schedule/worker.ts +6 -0
- package/src/security/oauth2.ts +6 -22
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
- package/src/stt/__tests__/types.test.ts +94 -0
- package/src/stt/daemon-batch-transcriber.ts +10 -0
- package/src/stt/stt-stream-session.ts +8 -4
- package/src/stt/types.ts +103 -0
- package/src/subagent/manager.ts +1 -3
- package/src/subagent/types.ts +7 -6
- package/src/tools/acp/spawn.test.ts +97 -0
- package/src/tools/acp/spawn.ts +32 -0
- package/src/tools/document/document-tool.ts +12 -3
- package/src/tools/registry.ts +2 -1
- package/src/tools/ui-surface/surface-shape-docs.ts +11 -0
- package/src/tools/workflows/run-workflow.ts +1 -2
- package/src/tts/__tests__/reasoning-tag-filter.test.ts +78 -0
- package/src/tts/reasoning-tag-filter.ts +89 -0
- package/src/util/think-tag-stream.ts +95 -0
- package/src/workspace/byok-default-profile-ensure.ts +76 -24
- package/src/workspace/custom-profile-ensure.ts +4 -24
- package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
- package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
- package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
- package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
- package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
- package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
- package/src/workspace/migrations/registry.ts +10 -0
- package/src/workspace/provider-commit-message-generator.ts +7 -5
- package/src/live-voice/__tests__/front-decision.test.ts +0 -645
- package/src/live-voice/front-decision.ts +0 -476
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
ConversationStreamingMode,
|
|
15
15
|
SttBoundaryId,
|
|
16
16
|
SttProviderId,
|
|
17
|
+
SttTurnDetectionMode,
|
|
17
18
|
TelephonySttMode,
|
|
18
19
|
} from "../../stt/types.js";
|
|
19
20
|
import { baseLanguageSubtag } from "../../util/language-subtag.js";
|
|
@@ -87,6 +88,14 @@ interface SttProviderEntry {
|
|
|
87
88
|
*/
|
|
88
89
|
readonly conversationStreamingMode: ConversationStreamingMode;
|
|
89
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Whether the provider decides end-of-turn itself, in-band with the audio
|
|
93
|
+
* it transcribes, or leaves the boundary to the session's local silence
|
|
94
|
+
* timer. A live-voice session reads this to decide whether to arm its
|
|
95
|
+
* provider turn-end path; it never names a provider directly.
|
|
96
|
+
*/
|
|
97
|
+
readonly turnDetection: SttTurnDetectionMode;
|
|
98
|
+
|
|
90
99
|
/**
|
|
91
100
|
* Whether the provider can attribute transcribed speech to distinct
|
|
92
101
|
* speakers (speaker diarization). When `true`, callers may opt in to
|
|
@@ -105,8 +114,9 @@ interface SttProviderEntry {
|
|
|
105
114
|
*
|
|
106
115
|
* - `"manual"`: the provider accepts an explicit language parameter and
|
|
107
116
|
* defaults to English when omitted; a language picker is meaningful.
|
|
108
|
-
* - `"auto"`: the provider
|
|
109
|
-
*
|
|
117
|
+
* - `"auto"`: the provider takes no language parameter, either because it
|
|
118
|
+
* detects the language natively or because its model is monolingual; a
|
|
119
|
+
* picker would be a no-op, so clients must hide it.
|
|
110
120
|
*/
|
|
111
121
|
readonly languageSelection: "manual" | "auto";
|
|
112
122
|
|
|
@@ -118,6 +128,17 @@ interface SttProviderEntry {
|
|
|
118
128
|
// Catalog data
|
|
119
129
|
// ---------------------------------------------------------------------------
|
|
120
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Shared by every provider that authenticates against a Deepgram account.
|
|
133
|
+
* The key is the same one, so the instructions must not drift apart.
|
|
134
|
+
*/
|
|
135
|
+
const DEEPGRAM_CREDENTIALS_GUIDE: SttCredentialsGuide = {
|
|
136
|
+
description:
|
|
137
|
+
"Sign in to the Deepgram console, navigate to API Keys, and create a new key.",
|
|
138
|
+
url: "https://console.deepgram.com/",
|
|
139
|
+
linkLabel: "Open Deepgram Console",
|
|
140
|
+
};
|
|
141
|
+
|
|
121
142
|
/**
|
|
122
143
|
* Provider catalog entries, keyed by provider ID.
|
|
123
144
|
*
|
|
@@ -147,14 +168,41 @@ const CATALOG: ReadonlyMap<SttProviderId, SttProviderEntry> = new Map<
|
|
|
147
168
|
]),
|
|
148
169
|
telephonyMode: "realtime-ws",
|
|
149
170
|
conversationStreamingMode: "realtime-ws",
|
|
171
|
+
turnDetection: "none",
|
|
150
172
|
supportsDiarization: true,
|
|
151
173
|
languageSelection: "manual",
|
|
152
|
-
credentialsGuide:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
174
|
+
credentialsGuide: DEEPGRAM_CREDENTIALS_GUIDE,
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
[
|
|
178
|
+
"deepgram-flux",
|
|
179
|
+
{
|
|
180
|
+
id: "deepgram-flux",
|
|
181
|
+
displayName: "Deepgram Flux",
|
|
182
|
+
subtitle:
|
|
183
|
+
"Conversational speech-to-text with model-native turn detection. Uses your Deepgram API key.",
|
|
184
|
+
setupMode: "api-key",
|
|
185
|
+
setupHint:
|
|
186
|
+
"Enter your Deepgram API key. Flux shares the same key as Deepgram.",
|
|
187
|
+
// Shared with the `deepgram` provider: Flux is a model on the same
|
|
188
|
+
// account, not a separate credential.
|
|
189
|
+
credentialProvider: "deepgram",
|
|
190
|
+
// Streaming only: Flux has no batch endpoint.
|
|
191
|
+
supportedBoundaries: new Set<SttBoundaryId>(["daemon-streaming"]),
|
|
192
|
+
// Telephony is out of scope for the spike. Nothing reroutes a call to
|
|
193
|
+
// another provider, so a Flux-configured assistant does not transcribe
|
|
194
|
+
// calls at all.
|
|
195
|
+
telephonyMode: "none",
|
|
196
|
+
conversationStreamingMode: "realtime-ws",
|
|
197
|
+
// Flux emits turn lifecycle events on its transcript stream and numbers
|
|
198
|
+
// them, so a live-voice session may let them commit the turn.
|
|
199
|
+
turnDetection: "provider",
|
|
200
|
+
supportsDiarization: false,
|
|
201
|
+
// "no picker", not native detection: the Flux model is English-only and
|
|
202
|
+
// takes no language parameter, so audio in another language transcribes
|
|
203
|
+
// as English rather than being detected.
|
|
204
|
+
languageSelection: "auto",
|
|
205
|
+
credentialsGuide: DEEPGRAM_CREDENTIALS_GUIDE,
|
|
158
206
|
},
|
|
159
207
|
],
|
|
160
208
|
[
|
|
@@ -174,6 +222,7 @@ const CATALOG: ReadonlyMap<SttProviderId, SttProviderEntry> = new Map<
|
|
|
174
222
|
]),
|
|
175
223
|
telephonyMode: "batch-only",
|
|
176
224
|
conversationStreamingMode: "realtime-ws",
|
|
225
|
+
turnDetection: "none",
|
|
177
226
|
supportsDiarization: false,
|
|
178
227
|
languageSelection: "auto",
|
|
179
228
|
credentialsGuide: {
|
|
@@ -200,6 +249,7 @@ const CATALOG: ReadonlyMap<SttProviderId, SttProviderEntry> = new Map<
|
|
|
200
249
|
]),
|
|
201
250
|
telephonyMode: "batch-only",
|
|
202
251
|
conversationStreamingMode: "incremental-batch",
|
|
252
|
+
turnDetection: "none",
|
|
203
253
|
supportsDiarization: false,
|
|
204
254
|
languageSelection: "auto",
|
|
205
255
|
credentialsGuide: {
|
|
@@ -226,6 +276,7 @@ const CATALOG: ReadonlyMap<SttProviderId, SttProviderEntry> = new Map<
|
|
|
226
276
|
]),
|
|
227
277
|
telephonyMode: "realtime-ws",
|
|
228
278
|
conversationStreamingMode: "realtime-ws",
|
|
279
|
+
turnDetection: "none",
|
|
229
280
|
supportsDiarization: false,
|
|
230
281
|
// The relay dials Deepgram nova-3 server-side, which takes an explicit
|
|
231
282
|
// language parameter.
|
|
@@ -248,6 +299,7 @@ const CATALOG: ReadonlyMap<SttProviderId, SttProviderEntry> = new Map<
|
|
|
248
299
|
]),
|
|
249
300
|
telephonyMode: "batch-only",
|
|
250
301
|
conversationStreamingMode: "realtime-ws",
|
|
302
|
+
turnDetection: "none",
|
|
251
303
|
supportsDiarization: true,
|
|
252
304
|
languageSelection: "manual",
|
|
253
305
|
credentialsGuide: {
|
|
@@ -342,6 +394,32 @@ export function supportsBoundary(
|
|
|
342
394
|
return CATALOG.get(id)?.supportedBoundaries.has(boundary) ?? false;
|
|
343
395
|
}
|
|
344
396
|
|
|
397
|
+
/**
|
|
398
|
+
* Message explaining why a provider cannot serve the `daemon-batch` boundary.
|
|
399
|
+
*
|
|
400
|
+
* Streaming-only providers are selected through the same
|
|
401
|
+
* `services.stt.provider` key as batch-capable ones, so a batch caller that
|
|
402
|
+
* reports only "nothing is configured" points the operator at the wrong
|
|
403
|
+
* problem. This names the provider and, where one exists, the batch-capable
|
|
404
|
+
* provider on the same credential, so acting on it needs no catalog reading.
|
|
405
|
+
*/
|
|
406
|
+
export function batchBoundaryGapReason(id: SttProviderId): string {
|
|
407
|
+
const entry = CATALOG.get(id);
|
|
408
|
+
const label = entry?.displayName ?? id;
|
|
409
|
+
const alternative = entry
|
|
410
|
+
? [...CATALOG.values()].find(
|
|
411
|
+
(candidate) =>
|
|
412
|
+
candidate.id !== entry.id &&
|
|
413
|
+
candidate.credentialProvider === entry.credentialProvider &&
|
|
414
|
+
candidate.supportedBoundaries.has("daemon-batch"),
|
|
415
|
+
)
|
|
416
|
+
: undefined;
|
|
417
|
+
const remedy = alternative
|
|
418
|
+
? `Batch transcription requires the ${alternative.id} provider: set services.stt.provider to "${alternative.id}".`
|
|
419
|
+
: "Set services.stt.provider to a provider that supports batch transcription.";
|
|
420
|
+
return `${label} is streaming-only. ${remedy}`;
|
|
421
|
+
}
|
|
422
|
+
|
|
345
423
|
/**
|
|
346
424
|
* Check whether a provider supports speaker diarization.
|
|
347
425
|
*
|
|
@@ -353,6 +431,19 @@ export function supportsDiarization(id: SttProviderId): boolean {
|
|
|
353
431
|
return CATALOG.get(id)?.supportsDiarization ?? false;
|
|
354
432
|
}
|
|
355
433
|
|
|
434
|
+
/**
|
|
435
|
+
* Check whether a provider decides end-of-turn itself.
|
|
436
|
+
*
|
|
437
|
+
* Returns `false` for unknown provider IDs, which keeps an unrecognized
|
|
438
|
+
* provider on the local silence boundary rather than waiting for turn events
|
|
439
|
+
* that will never arrive. A live-voice session reads this instead of naming a
|
|
440
|
+
* provider, so a new turn-detecting provider is a catalog entry rather than a
|
|
441
|
+
* session change.
|
|
442
|
+
*/
|
|
443
|
+
export function supportsProviderTurnDetection(id: SttProviderId): boolean {
|
|
444
|
+
return CATALOG.get(id)?.turnDetection === "provider";
|
|
445
|
+
}
|
|
446
|
+
|
|
356
447
|
/**
|
|
357
448
|
* Return all canonical provider IDs in deterministic (insertion) order.
|
|
358
449
|
*/
|
|
@@ -6,8 +6,10 @@ import type {
|
|
|
6
6
|
StreamingTranscriber,
|
|
7
7
|
SttProviderId,
|
|
8
8
|
} from "../../stt/types.js";
|
|
9
|
+
import { SttError } from "../../stt/types.js";
|
|
9
10
|
import { getLogger } from "../../util/logger.js";
|
|
10
11
|
import {
|
|
12
|
+
batchBoundaryGapReason,
|
|
11
13
|
getCredentialProvider,
|
|
12
14
|
getProviderEntry,
|
|
13
15
|
supportsBoundary,
|
|
@@ -88,8 +90,13 @@ export function effectiveSttLanguage(
|
|
|
88
90
|
*
|
|
89
91
|
* Returns `null` when:
|
|
90
92
|
* - The configured provider is not in the catalog.
|
|
91
|
-
* - The configured provider doesn't support the `daemon-batch` boundary.
|
|
92
93
|
* - No credentials are configured for the resolved provider.
|
|
94
|
+
*
|
|
95
|
+
* Throws an {@link SttError} when the configured provider is in the catalog
|
|
96
|
+
* but has no `daemon-batch` boundary. That is a configuration mismatch the
|
|
97
|
+
* resolver can name, and returning `null` for it would reach the user as the
|
|
98
|
+
* "no speech-to-text provider is configured" copy every caller pairs with
|
|
99
|
+
* `null`, which is the opposite of what happened.
|
|
93
100
|
*/
|
|
94
101
|
export async function resolveBatchTranscriber(): Promise<BatchTranscriber | null> {
|
|
95
102
|
// Snapshot the stt config once, before any await, so a concurrent config
|
|
@@ -111,7 +118,9 @@ export async function resolveBatchTranscriber(): Promise<BatchTranscriber | null
|
|
|
111
118
|
|
|
112
119
|
// Verify the provider supports the daemon-batch boundary.
|
|
113
120
|
if (!supportsBoundary(provider as SttProviderId, "daemon-batch")) {
|
|
114
|
-
|
|
121
|
+
const reason = batchBoundaryGapReason(provider as SttProviderId);
|
|
122
|
+
log.warn({ providerId: provider }, reason);
|
|
123
|
+
throw new SttError("provider-error", reason, { userFacing: true });
|
|
115
124
|
}
|
|
116
125
|
|
|
117
126
|
if (provider === "vellum") {
|
|
@@ -632,6 +641,20 @@ async function createStreamingTranscriber(
|
|
|
632
641
|
: {}),
|
|
633
642
|
});
|
|
634
643
|
}
|
|
644
|
+
case "deepgram-flux": {
|
|
645
|
+
// Flux is streaming-only and dials Deepgram's /v2/listen conversational
|
|
646
|
+
// endpoint. Turn-detection tuning comes from `liveVoice.flux`, which
|
|
647
|
+
// the adapter reads itself, so only the transport-level sample rate is
|
|
648
|
+
// passed here. No language is forwarded: the spike pins the
|
|
649
|
+
// English-only `flux-general-en`, and `language_hint` means nothing to
|
|
650
|
+
// a monolingual model. Diarization is off in the catalog, so a
|
|
651
|
+
// `"required"` caller never reaches this case.
|
|
652
|
+
const { DeepgramFluxRealtimeTranscriber } =
|
|
653
|
+
await import("./deepgram-flux-realtime.js");
|
|
654
|
+
return new DeepgramFluxRealtimeTranscriber(apiKey, {
|
|
655
|
+
sampleRate: options.sampleRate,
|
|
656
|
+
});
|
|
657
|
+
}
|
|
635
658
|
default: {
|
|
636
659
|
const _exhaustive: never = providerId;
|
|
637
660
|
return null;
|
package/src/routes/worker.ts
CHANGED
|
@@ -25,6 +25,11 @@ import type { IpcEnvelope } from "@vellumai/ipc-server-utils";
|
|
|
25
25
|
import { IpcFrameReader, writeMessage } from "@vellumai/ipc-server-utils";
|
|
26
26
|
|
|
27
27
|
import { disableStreamSeqStamping } from "../runtime/assistant-stream-state.js";
|
|
28
|
+
import {
|
|
29
|
+
evictRouteSourceTree,
|
|
30
|
+
importRouteModule,
|
|
31
|
+
sourceRootForHandler,
|
|
32
|
+
} from "../runtime/routes/user-route-import.js";
|
|
28
33
|
import { getLogger } from "../util/logger.js";
|
|
29
34
|
import {
|
|
30
35
|
ensureProcDir,
|
|
@@ -43,6 +48,9 @@ import {
|
|
|
43
48
|
|
|
44
49
|
const log = getLogger("route-host");
|
|
45
50
|
|
|
51
|
+
/** Last entry-file mtime imported in this process, keyed by handler path. */
|
|
52
|
+
const lastHandlerMtime = new Map<string, number>();
|
|
53
|
+
|
|
46
54
|
const socketPath = getProcSocketPath(ROUTE_HOST_PROC_NAME);
|
|
47
55
|
const pidPath = getProcPidPath(ROUTE_HOST_PROC_NAME);
|
|
48
56
|
|
|
@@ -110,11 +118,15 @@ async function handleInvoke(
|
|
|
110
118
|
params: RouteInvokeParams,
|
|
111
119
|
body: Uint8Array | undefined,
|
|
112
120
|
): Promise<void> {
|
|
113
|
-
// Each worker has its own module cache
|
|
114
|
-
// handler
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
// Each worker has its own module cache. When the entry file's mtime
|
|
122
|
+
// moves, evict the whole source tree so a new handler cannot bind to a
|
|
123
|
+
// stale helper, then re-import.
|
|
124
|
+
const lastMtime = lastHandlerMtime.get(params.filePath);
|
|
125
|
+
if (lastMtime !== params.mtimeMs) {
|
|
126
|
+
evictRouteSourceTree(sourceRootForHandler(params.filePath));
|
|
127
|
+
lastHandlerMtime.set(params.filePath, params.mtimeMs);
|
|
128
|
+
}
|
|
129
|
+
const mod = await importRouteModule(params.filePath);
|
|
118
130
|
|
|
119
131
|
const handler = mod[params.method];
|
|
120
132
|
if (typeof handler !== "function") {
|
|
@@ -29,6 +29,7 @@ import { buildVellumCardAffinity } from "../notifications/vellum-card-affinity.j
|
|
|
29
29
|
import { IntegrityError } from "../util/errors.js";
|
|
30
30
|
import { getLogger } from "../util/logger.js";
|
|
31
31
|
import { resolveAnchoredGuardian } from "./anchored-guardian.js";
|
|
32
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from "./assistant-scope.js";
|
|
32
33
|
import { CHALLENGE_TTL_MS } from "./channel-verification-service.js";
|
|
33
34
|
import {
|
|
34
35
|
type AccessRequestTrigger,
|
|
@@ -46,7 +47,6 @@ const log = getLogger("access-request-helper");
|
|
|
46
47
|
export type { AccessRequestTrigger } from "./introduction-policy.js";
|
|
47
48
|
|
|
48
49
|
export interface AccessRequestParams {
|
|
49
|
-
canonicalAssistantId: string;
|
|
50
50
|
sourceChannel: ChannelId;
|
|
51
51
|
conversationExternalId: string;
|
|
52
52
|
actorExternalId?: string;
|
|
@@ -93,15 +93,18 @@ export type AccessRequestResult =
|
|
|
93
93
|
// ---------------------------------------------------------------------------
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
96
|
+
* Conversation id for an actor's access requests. Stable key that dedupes
|
|
97
|
+
* pending prompts for the same (channel, actor).
|
|
98
|
+
*
|
|
99
|
+
* The `self` segment is part of a persisted id format: guardian request rows
|
|
100
|
+
* carry these ids, so the shape is fixed even though the daemon scopes all its
|
|
101
|
+
* storage to the one assistant (`DAEMON_INTERNAL_ASSISTANT_ID`).
|
|
98
102
|
*/
|
|
99
103
|
export function accessRequestConversationId(
|
|
100
|
-
canonicalAssistantId: string,
|
|
101
104
|
sourceChannel: string,
|
|
102
105
|
actorExternalId: string,
|
|
103
106
|
): string {
|
|
104
|
-
return `access-req-${
|
|
107
|
+
return `access-req-${DAEMON_INTERNAL_ASSISTANT_ID}-${sourceChannel}-${actorExternalId}`;
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
/**
|
|
@@ -126,7 +129,6 @@ export function accessRequestConversationId(
|
|
|
126
129
|
* fail-closed.
|
|
127
130
|
*/
|
|
128
131
|
export async function isApprovalHandshakeInProgress(params: {
|
|
129
|
-
canonicalAssistantId: string;
|
|
130
132
|
sourceChannel: string;
|
|
131
133
|
actorExternalId: string;
|
|
132
134
|
}): Promise<boolean> {
|
|
@@ -134,7 +136,6 @@ export async function isApprovalHandshakeInProgress(params: {
|
|
|
134
136
|
return false;
|
|
135
137
|
}
|
|
136
138
|
const conversationId = accessRequestConversationId(
|
|
137
|
-
params.canonicalAssistantId,
|
|
138
139
|
params.sourceChannel,
|
|
139
140
|
params.actorExternalId,
|
|
140
141
|
);
|
|
@@ -171,7 +172,6 @@ export async function notifyGuardianOfAccessRequest(
|
|
|
171
172
|
params: AccessRequestParams,
|
|
172
173
|
): Promise<AccessRequestResult> {
|
|
173
174
|
const {
|
|
174
|
-
canonicalAssistantId,
|
|
175
175
|
sourceChannel,
|
|
176
176
|
conversationExternalId,
|
|
177
177
|
actorExternalId,
|
|
@@ -219,7 +219,6 @@ export async function notifyGuardianOfAccessRequest(
|
|
|
219
219
|
// from assistant A could be returned for assistant B, allowing the caller
|
|
220
220
|
// to piggyback on A's guardian approval.
|
|
221
221
|
const conversationId = accessRequestConversationId(
|
|
222
|
-
canonicalAssistantId,
|
|
223
222
|
sourceChannel,
|
|
224
223
|
actorExternalId,
|
|
225
224
|
);
|
|
@@ -270,7 +269,6 @@ export async function notifyGuardianOfAccessRequest(
|
|
|
270
269
|
// re-prompting is allowed again.
|
|
271
270
|
if (
|
|
272
271
|
await isApprovalHandshakeInProgress({
|
|
273
|
-
canonicalAssistantId,
|
|
274
272
|
sourceChannel,
|
|
275
273
|
actorExternalId,
|
|
276
274
|
})
|
|
@@ -283,7 +281,7 @@ export async function notifyGuardianOfAccessRequest(
|
|
|
283
281
|
}
|
|
284
282
|
|
|
285
283
|
const senderIdentifier = actorDisplayName || actorUsername || actorExternalId;
|
|
286
|
-
const requestId = `access-req-${
|
|
284
|
+
const requestId = `access-req-${DAEMON_INTERNAL_ASSISTANT_ID}-${sourceChannel}-${actorExternalId}-${Date.now()}`;
|
|
287
285
|
|
|
288
286
|
// Access requests are decisionable: without a bound principal nobody could
|
|
289
287
|
// ever decide them (mirrors the gateway create's integrity guard).
|
|
@@ -461,7 +459,6 @@ export async function maybeNotifyGuardianOfAdmittedContact(
|
|
|
461
459
|
}
|
|
462
460
|
|
|
463
461
|
const conversationId = accessRequestConversationId(
|
|
464
|
-
params.canonicalAssistantId,
|
|
465
462
|
params.sourceChannel,
|
|
466
463
|
params.actorExternalId,
|
|
467
464
|
);
|
|
@@ -718,9 +718,9 @@ export async function wakeAgentForOpportunity(
|
|
|
718
718
|
return runWakeSingleFlight(conversationId, async () => {
|
|
719
719
|
// Snapshot the conversation's resting trust before the resolver runs, so
|
|
720
720
|
// it can be restored after. The resolver leaves the wake's trust on the
|
|
721
|
-
// conversation, and a following no-trust wake would otherwise
|
|
722
|
-
//
|
|
723
|
-
//
|
|
721
|
+
// conversation, and a following no-trust wake would otherwise pick it up
|
|
722
|
+
// from the resting trust its consumers fall back to. Null when the
|
|
723
|
+
// conversation isn't resident yet (a fresh hydrate or a fork).
|
|
724
724
|
let priorPersistentTrust: TrustContext | null = null;
|
|
725
725
|
if (opts.trustContext) {
|
|
726
726
|
const { findConversation } =
|
|
@@ -55,9 +55,13 @@ export function hasReceivedUserMessage(): boolean {
|
|
|
55
55
|
try {
|
|
56
56
|
const row = rawGet<{ one: number }>(
|
|
57
57
|
"preFirstMsg:hasReceivedUserMessage",
|
|
58
|
+
// Finalized user-role rows only: the grouped tool-result row is a
|
|
59
|
+
// user-role row reserved unfinalized mid-turn, and this gate must not
|
|
60
|
+
// read tool plumbing as "the user has messaged this assistant".
|
|
58
61
|
`SELECT 1 AS one FROM messages m
|
|
59
62
|
JOIN conversations c ON m.conversation_id = c.id
|
|
60
63
|
WHERE m.role = 'user'
|
|
64
|
+
AND m.finalized = 1
|
|
61
65
|
AND c.conversation_type = 'standard'
|
|
62
66
|
LIMIT 1`,
|
|
63
67
|
);
|
|
@@ -47,7 +47,7 @@ mock.module("../../../security/oauth2.js", () => ({
|
|
|
47
47
|
}));
|
|
48
48
|
|
|
49
49
|
const actualClaudeOauth = await import("../../../acp/acp-claude-oauth.js");
|
|
50
|
-
const { CLAUDE_MANUAL_REDIRECT_URI } = actualClaudeOauth;
|
|
50
|
+
const { CLAUDE_MANUAL_REDIRECT_URI, CLAUDE_OAUTH_CONFIG } = actualClaudeOauth;
|
|
51
51
|
const storeAcpClaudeTokenMock = mock(async (_token: string) => {});
|
|
52
52
|
// The connect-status route reads token presence; mock it so the route test
|
|
53
53
|
// doesn't reach real secure storage.
|
|
@@ -125,7 +125,7 @@ function deferredFlow(state: string): {
|
|
|
125
125
|
reject = rej;
|
|
126
126
|
});
|
|
127
127
|
prepareOAuth2FlowMock.mockImplementationOnce(async () => ({
|
|
128
|
-
authorizeUrl:
|
|
128
|
+
authorizeUrl: `${CLAUDE_OAUTH_CONFIG.authorizeUrl}?state=${state}`,
|
|
129
129
|
state,
|
|
130
130
|
completion,
|
|
131
131
|
}));
|
|
@@ -153,7 +153,7 @@ describe("acp_claude_auth_start", () => {
|
|
|
153
153
|
|
|
154
154
|
const url = new URL(result.authorize_url);
|
|
155
155
|
expect(`${url.origin}${url.pathname}`).toBe(
|
|
156
|
-
|
|
156
|
+
CLAUDE_OAUTH_CONFIG.authorizeUrl,
|
|
157
157
|
);
|
|
158
158
|
expect(url.searchParams.get("client_id")).toBe(CLAUDE_CLIENT_ID);
|
|
159
159
|
expect(url.searchParams.get("code_challenge_method")).toBe("S256");
|
|
@@ -161,6 +161,11 @@ describe("acp_claude_auth_start", () => {
|
|
|
161
161
|
const redirectUri = url.searchParams.get("redirect_uri") ?? "";
|
|
162
162
|
expect(redirectUri).toMatch(/^http:\/\/localhost:\d+\/callback$/);
|
|
163
163
|
|
|
164
|
+
// The loopback flow performs a REAL redirect to the localhost callback;
|
|
165
|
+
// `code=true` (render the code on the page) belongs to the manual flow
|
|
166
|
+
// only and must not leak into this URL.
|
|
167
|
+
expect(url.searchParams.get("code")).toBeNull();
|
|
168
|
+
|
|
164
169
|
expect(result.state).toBeTruthy();
|
|
165
170
|
expect(url.searchParams.get("state")).toBe(result.state);
|
|
166
171
|
|
|
@@ -284,7 +289,7 @@ describe("acp_claude_auth_start (cloud/manual)", () => {
|
|
|
284
289
|
expect(result.mode).toBe("manual");
|
|
285
290
|
const url = new URL(result.authorize_url);
|
|
286
291
|
expect(`${url.origin}${url.pathname}`).toBe(
|
|
287
|
-
|
|
292
|
+
CLAUDE_OAUTH_CONFIG.authorizeUrl,
|
|
288
293
|
);
|
|
289
294
|
expect(url.searchParams.get("redirect_uri")).toBe(
|
|
290
295
|
CLAUDE_MANUAL_REDIRECT_URI,
|
|
@@ -292,6 +297,9 @@ describe("acp_claude_auth_start (cloud/manual)", () => {
|
|
|
292
297
|
expect(url.searchParams.get("client_id")).toBe(CLAUDE_CLIENT_ID);
|
|
293
298
|
expect(url.searchParams.get("code_challenge_method")).toBe("S256");
|
|
294
299
|
expect(url.searchParams.get("state")).toBe(result.state);
|
|
300
|
+
// Manual flow requires `code=true` so the callback page renders the
|
|
301
|
+
// `code#state` string to paste back.
|
|
302
|
+
expect(url.searchParams.get("code")).toBe("true");
|
|
295
303
|
|
|
296
304
|
// The manual path must not bind a loopback.
|
|
297
305
|
expect(prepareOAuth2FlowMock).not.toHaveBeenCalled();
|