@vellumai/assistant 0.11.3 → 0.11.4-staging.1
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 +140 -38
- 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 +2 -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 +55 -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__/user-plugin-loader.test.ts +1 -1
- package/src/__tests__/visible-app-context.test.ts +16 -9
- 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/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/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 +9 -2
- 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/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 +82 -11
- 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 +9 -6
- package/src/daemon/conversation-runtime-assembly.ts +3 -4
- package/src/daemon/conversation-tool-setup.ts +1 -2
- package/src/daemon/conversation.ts +48 -0
- package/src/daemon/mcp-reload-service.ts +36 -6
- package/src/daemon/process-message.ts +13 -3
- 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/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-intents.ts +26 -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/workflows/run-workflow.ts +1 -2
- package/src/tts/__tests__/reasoning-tag-filter.test.ts +63 -0
- package/src/tts/reasoning-tag-filter.ts +110 -0
- package/src/workspace/byok-default-profile-ensure.ts +61 -18
- 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/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
- package/src/workspace/migrations/registry.ts +8 -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
|
@@ -21,6 +21,7 @@ import { getDb } from "../../persistence/db-connection.js";
|
|
|
21
21
|
import {
|
|
22
22
|
type Auth,
|
|
23
23
|
AuthSchema,
|
|
24
|
+
CHATGPT_SUBSCRIPTION_CONNECTION_NAME,
|
|
24
25
|
type ConnectionModel,
|
|
25
26
|
ConnectionModelSchema,
|
|
26
27
|
ConnectionProviderSchema,
|
|
@@ -205,14 +206,26 @@ function deriveConnectionAuth(provider: string, credential: unknown): Auth {
|
|
|
205
206
|
function assertAuthMatchesProvider(provider: string, auth: Auth): void {
|
|
206
207
|
const managedAuth = auth.type === "platform";
|
|
207
208
|
const managedProvider = provider === VELLUM_MANAGED_PROVIDER;
|
|
208
|
-
if (managedAuth
|
|
209
|
-
|
|
209
|
+
if (managedAuth !== managedProvider) {
|
|
210
|
+
throw new BadRequestError(
|
|
211
|
+
managedAuth
|
|
212
|
+
? `Auth type "platform" is only valid for provider "${VELLUM_MANAGED_PROVIDER}", not "${provider}". Vellum-managed routing is selected by the provider, so omit "auth" to derive it.`
|
|
213
|
+
: `Provider "${VELLUM_MANAGED_PROVIDER}" is always platform-authenticated; "${auth.type}" auth is not valid for it. Omit "auth" to derive it, or name a real provider for key-based auth.`,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
// Same rule for the subscription identity: provider "chatgpt" and
|
|
217
|
+
// oauth_subscription auth record one fact and must pair, or a key-auth
|
|
218
|
+
// row under the identity would dispatch against the API while the user
|
|
219
|
+
// believes they are on subscription billing.
|
|
220
|
+
const subscriptionAuth = auth.type === "oauth_subscription";
|
|
221
|
+
const subscriptionProvider = provider === "chatgpt";
|
|
222
|
+
if (subscriptionAuth !== subscriptionProvider) {
|
|
223
|
+
throw new BadRequestError(
|
|
224
|
+
subscriptionAuth
|
|
225
|
+
? `Auth type "oauth_subscription" is only valid for provider "chatgpt", not "${provider}". Run the ChatGPT sign-in flow to connect a subscription.`
|
|
226
|
+
: `Provider "chatgpt" is always subscription-authenticated; "${auth.type}" auth is not valid for it. Run the ChatGPT sign-in flow, or name a real provider for key-based auth.`,
|
|
227
|
+
);
|
|
210
228
|
}
|
|
211
|
-
throw new BadRequestError(
|
|
212
|
-
managedAuth
|
|
213
|
-
? `Auth type "platform" is only valid for provider "${VELLUM_MANAGED_PROVIDER}", not "${provider}". Vellum-managed routing is selected by the provider, so omit "auth" to derive it.`
|
|
214
|
-
: `Provider "${VELLUM_MANAGED_PROVIDER}" is always platform-authenticated; "${auth.type}" auth is not valid for it. Omit "auth" to derive it, or name a real provider for key-based auth.`,
|
|
215
|
-
);
|
|
216
229
|
}
|
|
217
230
|
|
|
218
231
|
/**
|
|
@@ -323,6 +336,15 @@ async function handleCreateConnection({ body = {} }: RouteHandlerArgs) {
|
|
|
323
336
|
`Connection name "${name}" is reserved for the Vellum-managed connection. Pick another name.`,
|
|
324
337
|
);
|
|
325
338
|
}
|
|
339
|
+
// Provider ids and routing identities are labels in profile config, so an
|
|
340
|
+
// entry under one of those names could never be referenced by name (the
|
|
341
|
+
// label reads as the vendor), and a future catalog addition must never
|
|
342
|
+
// capture an existing user name. Reserve the whole vocabulary.
|
|
343
|
+
if (VALID_CONNECTION_PROVIDERS.includes(name)) {
|
|
344
|
+
throw new BadRequestError(
|
|
345
|
+
`Connection name "${name}" is reserved as a provider id. Pick another name.`,
|
|
346
|
+
);
|
|
347
|
+
}
|
|
326
348
|
|
|
327
349
|
const providerResult = ConnectionProviderSchema.safeParse(provider);
|
|
328
350
|
if (!providerResult.success) {
|
|
@@ -330,15 +352,27 @@ async function handleCreateConnection({ body = {} }: RouteHandlerArgs) {
|
|
|
330
352
|
`Invalid provider "${String(provider)}". Valid: ${VALID_CONNECTION_PROVIDERS.join(", ")}`,
|
|
331
353
|
);
|
|
332
354
|
}
|
|
355
|
+
// The chatgpt identity lives on its canonical row: routing resolves the
|
|
356
|
+
// subscription by that name, so an identity row under any other name can
|
|
357
|
+
// never dispatch.
|
|
358
|
+
if (
|
|
359
|
+
providerResult.data === "chatgpt" &&
|
|
360
|
+
name !== CHATGPT_SUBSCRIPTION_CONNECTION_NAME
|
|
361
|
+
) {
|
|
362
|
+
throw new BadRequestError(
|
|
363
|
+
`Provider "chatgpt" is reserved for the "${CHATGPT_SUBSCRIPTION_CONNECTION_NAME}" connection. Run the ChatGPT sign-in flow to connect a subscription.`,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
333
366
|
const authResult = AuthSchema.safeParse(
|
|
334
367
|
auth ?? deriveConnectionAuth(providerResult.data, body.credential),
|
|
335
368
|
);
|
|
336
369
|
if (!authResult.success) {
|
|
337
370
|
throw new BadRequestError(`Invalid auth: ${authResult.error.message}`);
|
|
338
371
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
372
|
+
// Asserted on derived auth as well: derivation pairs every provider
|
|
373
|
+
// correctly except the chatgpt identity, whose fallthrough would mint
|
|
374
|
+
// api_key auth from a bare credential.
|
|
375
|
+
assertAuthMatchesProvider(providerResult.data, authResult.data);
|
|
342
376
|
|
|
343
377
|
const labelRaw = body.label;
|
|
344
378
|
if (
|
|
@@ -434,15 +468,31 @@ async function handleUpdateConnection({
|
|
|
434
468
|
if (!authResult.success) {
|
|
435
469
|
throw new BadRequestError(`Invalid auth: ${authResult.error.message}`);
|
|
436
470
|
}
|
|
471
|
+
// The canonical subscription row owns the "chatgpt" identity: writing
|
|
472
|
+
// subscription auth to it stamps the provider with the auth, mirroring
|
|
473
|
+
// the daemon's own sign-in exchange route. The CLI's login-chatgpt PATCHes
|
|
474
|
+
// auth through this route, and without the stamp a row the identity
|
|
475
|
+
// migration deliberately skipped (a claiming row with key auth) would end
|
|
476
|
+
// up as provider "openai" with subscription auth. Provider stays
|
|
477
|
+
// immutable for every other row.
|
|
478
|
+
const chatgptIdentityStamp =
|
|
479
|
+
name === CHATGPT_SUBSCRIPTION_CONNECTION_NAME &&
|
|
480
|
+
authResult.data.type === "oauth_subscription" &&
|
|
481
|
+
existing.provider !== "chatgpt";
|
|
482
|
+
|
|
437
483
|
// The pairing is enforced on an actual auth change, not on the field being
|
|
438
484
|
// present: the web editor and the CLI both resend the stored auth on every
|
|
439
485
|
// edit, so a row whose columns already disagree stays relabelable and
|
|
440
|
-
// re-pointable.
|
|
486
|
+
// re-pointable. Judged against the stamped provider when the stamp
|
|
487
|
+
// applies, since that is the pair being written.
|
|
441
488
|
if (
|
|
442
489
|
body.auth !== undefined &&
|
|
443
490
|
authFingerprint(authResult.data) !== authFingerprint(existing.auth)
|
|
444
491
|
) {
|
|
445
|
-
assertAuthMatchesProvider(
|
|
492
|
+
assertAuthMatchesProvider(
|
|
493
|
+
chatgptIdentityStamp ? "chatgpt" : existing.provider,
|
|
494
|
+
authResult.data,
|
|
495
|
+
);
|
|
446
496
|
}
|
|
447
497
|
|
|
448
498
|
const labelRaw = body.label;
|
|
@@ -473,6 +523,7 @@ async function handleUpdateConnection({
|
|
|
473
523
|
|
|
474
524
|
const result = updateConnection(getDb(), name, {
|
|
475
525
|
auth: authResult.data,
|
|
526
|
+
...(chatgptIdentityStamp ? { provider: "chatgpt" } : {}),
|
|
476
527
|
...(labelRaw !== undefined ? { label: labelRaw as string | null } : {}),
|
|
477
528
|
...customFields,
|
|
478
529
|
});
|
|
@@ -574,10 +625,21 @@ async function handleDeleteConnection({ pathParams = {} }: RouteHandlerArgs) {
|
|
|
574
625
|
// above; this keeps the scan a faithful backstop rather than one that
|
|
575
626
|
// silently skips the defaults.
|
|
576
627
|
const profiles = getEffectiveProfilesForProvider(config.llm?.profiles, dp);
|
|
628
|
+
// A binding lives in `provider` (the entry name) under the entries model,
|
|
629
|
+
// or in the legacy `provider_connection` field; both count, or deleting a
|
|
630
|
+
// bound entry would dangle the profile behind selection's silent healing.
|
|
631
|
+
// Provider values count only for non-vendor names: a profile saying
|
|
632
|
+
// "vellum" references the routing identity, never a row that happens to
|
|
633
|
+
// claim that name, and the claiming-row delete is the recovery path.
|
|
634
|
+
const nameIsEntryName = !VALID_CONNECTION_PROVIDERS.includes(name);
|
|
577
635
|
const referencingProfiles = Object.entries(profiles)
|
|
578
|
-
.filter(
|
|
579
|
-
|
|
580
|
-
|
|
636
|
+
.filter(([, p]) => {
|
|
637
|
+
const entry = p as Record<string, unknown>;
|
|
638
|
+
return (
|
|
639
|
+
entry.provider_connection === name ||
|
|
640
|
+
(nameIsEntryName && entry.provider === name)
|
|
641
|
+
);
|
|
642
|
+
})
|
|
581
643
|
.map(([profileName]) => profileName);
|
|
582
644
|
|
|
583
645
|
const result = deleteConnection(getDb(), name, {
|
|
@@ -143,6 +143,9 @@ async function handleExport({
|
|
|
143
143
|
? [eq(messages.conversationId, conversationId)]
|
|
144
144
|
: [];
|
|
145
145
|
|
|
146
|
+
// Any-state export, deliberately: this is a diagnostic surface, and an
|
|
147
|
+
// in-flight row (finalized = 0, content a { ref } into the turn's delta
|
|
148
|
+
// file) is often exactly what a bug report needs to show.
|
|
146
149
|
const messageRows = capRows(
|
|
147
150
|
db
|
|
148
151
|
.select()
|
|
@@ -19,6 +19,7 @@ import type { McpConfig, McpServerConfig } from "../../config/schemas/mcp.js";
|
|
|
19
19
|
import { estimateToolDefinitionTokens } from "../../context/token-estimator.js";
|
|
20
20
|
import { reloadMcpServers } from "../../daemon/mcp-reload-service.js";
|
|
21
21
|
import { McpClient } from "../../mcp/client.js";
|
|
22
|
+
import { getMcpServerManager } from "../../mcp/manager.js";
|
|
22
23
|
import { orchestrateMcpOAuthConnect } from "../../mcp/mcp-auth-orchestrator.js";
|
|
23
24
|
import { getMcpAuthState } from "../../mcp/mcp-auth-state.js";
|
|
24
25
|
import {
|
|
@@ -30,6 +31,7 @@ import {
|
|
|
30
31
|
deleteMcpOAuthCredentials,
|
|
31
32
|
hasMcpOAuthTokens,
|
|
32
33
|
} from "../../mcp/mcp-oauth-provider.js";
|
|
34
|
+
import { readPluginMcpServers } from "../../plugins/mcp-servers.js";
|
|
33
35
|
import { getMcpToolsByServer } from "../../tools/registry.js";
|
|
34
36
|
import { getLogger } from "../../util/logger.js";
|
|
35
37
|
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
@@ -224,8 +226,33 @@ interface McpServerEntry {
|
|
|
224
226
|
authHeaderName?: string;
|
|
225
227
|
allowedTools?: string[];
|
|
226
228
|
blockedTools?: string[];
|
|
229
|
+
/**
|
|
230
|
+
* Where the definition came from: the workspace `config.json` or a
|
|
231
|
+
* plugin's `mcp.json`. A plugin server is read-only from the CLI's
|
|
232
|
+
* perspective, since `mcp remove` and `mcp update` act on the workspace
|
|
233
|
+
* config, so the caller needs to be able to tell the two apart.
|
|
234
|
+
*/
|
|
235
|
+
source: "workspace" | "plugin";
|
|
236
|
+
/** Plugin that declared this server. Present only when `source` is `plugin`. */
|
|
237
|
+
pluginName?: string;
|
|
227
238
|
}
|
|
228
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Status reported for a plugin-declared server the MCP manager holds no
|
|
242
|
+
* live client for — it has not been started yet, or its connection failed.
|
|
243
|
+
*
|
|
244
|
+
* A plugin server is never health-checked the way a workspace server is.
|
|
245
|
+
* `checkMachineReadableHealth` constructs its own `McpClient`, which
|
|
246
|
+
* resolves `mcp:<serverId>:headers` and `mcp:<serverId>:tokens` from the
|
|
247
|
+
* credential store, and a plugin controls both its server key and its URL,
|
|
248
|
+
* so probing one would send workspace-owned credentials to an endpoint the
|
|
249
|
+
* plugin chose whenever an id happens to match a stored key. Skipping the
|
|
250
|
+
* probe also avoids spawning a plugin's declared stdio command as a side
|
|
251
|
+
* effect of listing. The manager's own client — started with credentials
|
|
252
|
+
* isolated — is the only thing consulted instead.
|
|
253
|
+
*/
|
|
254
|
+
const PLUGIN_SERVER_STATUS = "declared";
|
|
255
|
+
|
|
229
256
|
function detectAuthType(headers: Record<string, string>): "bearer" | "api-key" {
|
|
230
257
|
const authValue = headers["Authorization"] ?? headers["authorization"];
|
|
231
258
|
if (authValue?.startsWith("Bearer ")) {
|
|
@@ -240,65 +267,129 @@ async function handleMcpList(_args: {
|
|
|
240
267
|
const raw = loadRawConfig();
|
|
241
268
|
const mcpConfig = raw.mcp as Partial<McpConfig> | undefined;
|
|
242
269
|
const servers = mcpConfig?.servers ?? {};
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}),
|
|
270
|
+
const configEntries = (
|
|
271
|
+
Object.entries(servers) as [string, McpServerConfig][]
|
|
272
|
+
).filter(([, config]) => config && typeof config === "object");
|
|
273
|
+
|
|
274
|
+
const workspaceEntries: McpServerEntry[] = await Promise.all(
|
|
275
|
+
configEntries.map(async ([id, config]) => {
|
|
276
|
+
const enabled = config.enabled !== false;
|
|
277
|
+
let status: string;
|
|
278
|
+
if (!enabled) {
|
|
279
|
+
status = "disabled";
|
|
280
|
+
} else {
|
|
281
|
+
status = await checkMachineReadableHealth(id, config);
|
|
282
|
+
}
|
|
283
|
+
const hasOAuth =
|
|
284
|
+
config.transport.type !== "stdio" ? await hasMcpOAuthTokens(id) : false;
|
|
285
|
+
|
|
286
|
+
// Check credential store for stored static auth headers
|
|
287
|
+
const storedHeaders = await getMcpHeaders(id);
|
|
288
|
+
// Also check legacy config-level headers
|
|
289
|
+
const configHeaders =
|
|
290
|
+
config.transport.type !== "stdio"
|
|
291
|
+
? config.transport.headers
|
|
292
|
+
: undefined;
|
|
293
|
+
const effectiveHeaders = storedHeaders ?? configHeaders;
|
|
294
|
+
const hasStaticAuth =
|
|
295
|
+
!!effectiveHeaders && Object.keys(effectiveHeaders).length > 0;
|
|
296
|
+
const authType: "none" | "bearer" | "api-key" = hasStaticAuth
|
|
297
|
+
? detectAuthType(effectiveHeaders!)
|
|
298
|
+
: "none";
|
|
299
|
+
const authHeaderName =
|
|
300
|
+
authType === "api-key" && effectiveHeaders
|
|
301
|
+
? Object.keys(effectiveHeaders).find(
|
|
302
|
+
(k) => k.toLowerCase() !== "authorization",
|
|
303
|
+
)
|
|
304
|
+
: undefined;
|
|
305
|
+
|
|
306
|
+
// Strip headers from transport, never return secrets
|
|
307
|
+
const { headers: _stripped, ...safeTransport } =
|
|
308
|
+
config.transport as Record<string, unknown>;
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
id,
|
|
312
|
+
status,
|
|
313
|
+
transport: safeTransport as McpServerEntry["transport"],
|
|
314
|
+
enabled,
|
|
315
|
+
defaultRiskLevel: config.defaultRiskLevel ?? "high",
|
|
316
|
+
hasOAuth,
|
|
317
|
+
hasStaticAuth,
|
|
318
|
+
authType,
|
|
319
|
+
...(authHeaderName && { authHeaderName }),
|
|
320
|
+
...(config.allowedTools && { allowedTools: config.allowedTools }),
|
|
321
|
+
...(config.blockedTools && { blockedTools: config.blockedTools }),
|
|
322
|
+
source: "workspace" as const,
|
|
323
|
+
};
|
|
324
|
+
}),
|
|
299
325
|
);
|
|
300
326
|
|
|
301
|
-
return {
|
|
327
|
+
return {
|
|
328
|
+
servers: [...workspaceEntries, ...listPluginServerEntries(configEntries)],
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Project plugin-declared servers onto listing entries.
|
|
334
|
+
*
|
|
335
|
+
* Nothing here touches the credential store or the network: a plugin
|
|
336
|
+
* server carries no auth state the assistant owns, and its status comes
|
|
337
|
+
* from the manager's in-memory client rather than a probe (see
|
|
338
|
+
* {@link PLUGIN_SERVER_STATUS}).
|
|
339
|
+
*/
|
|
340
|
+
function listPluginServerEntries(
|
|
341
|
+
configEntries: [string, McpServerConfig][],
|
|
342
|
+
): McpServerEntry[] {
|
|
343
|
+
const { servers, issues } = readPluginMcpServers();
|
|
344
|
+
for (const issue of issues) {
|
|
345
|
+
log.warn(
|
|
346
|
+
{
|
|
347
|
+
plugin: issue.pluginName,
|
|
348
|
+
...(issue.serverKey && { serverKey: issue.serverKey }),
|
|
349
|
+
},
|
|
350
|
+
`Plugin MCP declaration problem: ${issue.message}`,
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// A workspace entry outranks a plugin's declaration of the same id: the
|
|
355
|
+
// user's own configuration is the more specific statement, and it is the
|
|
356
|
+
// one they can edit. `buildEffectiveMcpConfig` applies the same
|
|
357
|
+
// precedence, so this listing describes the set the daemon actually runs.
|
|
358
|
+
const workspaceIds = new Set(configEntries.map(([id]) => id));
|
|
359
|
+
const manager = getMcpServerManager();
|
|
360
|
+
|
|
361
|
+
return servers
|
|
362
|
+
.filter((server) => {
|
|
363
|
+
if (!workspaceIds.has(server.id)) {
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
log.warn(
|
|
367
|
+
{ plugin: server.pluginName, serverId: server.id },
|
|
368
|
+
"Plugin MCP server shadowed by a workspace server of the same id; skipping",
|
|
369
|
+
);
|
|
370
|
+
return false;
|
|
371
|
+
})
|
|
372
|
+
.map((server) => {
|
|
373
|
+
const { headers: _stripped, ...safeTransport } = server.config
|
|
374
|
+
.transport as Record<string, unknown>;
|
|
375
|
+
return {
|
|
376
|
+
id: server.id,
|
|
377
|
+
status: manager.getClient(server.id)?.isConnected
|
|
378
|
+
? "connected"
|
|
379
|
+
: PLUGIN_SERVER_STATUS,
|
|
380
|
+
transport: safeTransport as McpServerEntry["transport"],
|
|
381
|
+
enabled: true,
|
|
382
|
+
defaultRiskLevel: server.config.defaultRiskLevel,
|
|
383
|
+
// A plugin server has no assistant-owned credentials. Resolving
|
|
384
|
+
// these against `mcp:<id>:*` would report, and could disclose, a
|
|
385
|
+
// workspace credential that happens to share the id.
|
|
386
|
+
hasOAuth: false,
|
|
387
|
+
hasStaticAuth: false,
|
|
388
|
+
authType: "none" as const,
|
|
389
|
+
source: "plugin" as const,
|
|
390
|
+
pluginName: server.pluginName,
|
|
391
|
+
};
|
|
392
|
+
});
|
|
302
393
|
}
|
|
303
394
|
|
|
304
395
|
// ---------------------------------------------------------------------------
|
|
@@ -85,6 +85,7 @@ import {
|
|
|
85
85
|
} from "../../cli/lib/uninstall-plugin.js";
|
|
86
86
|
import {
|
|
87
87
|
PluginMergeBaselineError,
|
|
88
|
+
PluginNotCuratedError,
|
|
88
89
|
PluginNotUpgradableError,
|
|
89
90
|
upgradePlugin,
|
|
90
91
|
} from "../../cli/lib/upgrade-plugin.js";
|
|
@@ -606,6 +607,12 @@ const pluginUpgradeRequestSchema = z.object({
|
|
|
606
607
|
.describe(
|
|
607
608
|
"How to reconcile local edits with the pin. `overwrite` (default) discards local edits and re-installs the pin; `ours`/`theirs` three-way merge, resolving conflicting hunks toward the local edit or the pin respectively; `assistant` is not yet supported.",
|
|
608
609
|
),
|
|
610
|
+
marketplaceOnly: z
|
|
611
|
+
.boolean()
|
|
612
|
+
.optional()
|
|
613
|
+
.describe(
|
|
614
|
+
"Refuse the upgrade (409) when the marketplace does not claim this plugin, instead of advancing it to whatever its recorded GitHub ref now resolves to. Set by unattended callers such as the automatic update sweep, for which running code no curator reviewed is never an acceptable outcome. Defaults to false.",
|
|
615
|
+
),
|
|
609
616
|
});
|
|
610
617
|
|
|
611
618
|
const pluginUpgradeResponseSchema = z.object({
|
|
@@ -1465,6 +1472,11 @@ async function handleUpgradePlugin({
|
|
|
1465
1472
|
typeof body.strategy === "string"
|
|
1466
1473
|
? (body.strategy as PluginUpgradeStrategy)
|
|
1467
1474
|
: undefined;
|
|
1475
|
+
// Enforced here, not just by whoever decided to call: `upgradePlugin` fetches
|
|
1476
|
+
// the catalog itself, so an entry that vanishes between a caller's own check
|
|
1477
|
+
// and this handler would otherwise reroute a curated upgrade onto the
|
|
1478
|
+
// install's mutable recorded ref.
|
|
1479
|
+
const marketplaceOnly = body.marketplaceOnly === true;
|
|
1468
1480
|
|
|
1469
1481
|
// Like install, the upgrade target ref is the curated marketplace pin
|
|
1470
1482
|
// (resolved inside `upgradePlugin` via `inspectPlugin`), never a
|
|
@@ -1498,7 +1510,7 @@ async function handleUpgradePlugin({
|
|
|
1498
1510
|
// upgraded revision adds is surfaced to the user by the schedule
|
|
1499
1511
|
// reconciler's `schedule.declared` notification when it arms.
|
|
1500
1512
|
const result = await upgradePlugin(
|
|
1501
|
-
{ name, dryRun, strategy },
|
|
1513
|
+
{ name, dryRun, strategy, marketplaceOnly },
|
|
1502
1514
|
{
|
|
1503
1515
|
fetch: globalThis.fetch.bind(globalThis),
|
|
1504
1516
|
// Tear the old version down BEFORE the staged tree replaces its
|
|
@@ -1568,6 +1580,12 @@ async function handleUpgradePlugin({
|
|
|
1568
1580
|
if (err instanceof PluginNotUpgradableError) {
|
|
1569
1581
|
throw new ConflictError(err.message);
|
|
1570
1582
|
}
|
|
1583
|
+
// A `marketplaceOnly` caller asked about an install the catalog does not
|
|
1584
|
+
// claim. Retrying changes nothing; only a human upgrading it deliberately
|
|
1585
|
+
// (without the flag) can move it, so 409 rather than a retryable 503.
|
|
1586
|
+
if (err instanceof PluginNotCuratedError) {
|
|
1587
|
+
throw new ConflictError(err.message);
|
|
1588
|
+
}
|
|
1571
1589
|
// A rate-limited or temporarily-down source (the plugin repo or the
|
|
1572
1590
|
// marketplace catalog) is a retryable outage, not a conflict — 503.
|
|
1573
1591
|
if (err instanceof PluginSourceUnavailableError) {
|
|
@@ -2039,7 +2057,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
2039
2057
|
},
|
|
2040
2058
|
summary: "Upgrade a plugin to its source's current revision",
|
|
2041
2059
|
description:
|
|
2042
|
-
'Move an installed plugin to its source\'s current revision, re-materializing it under `<workspaceDir>/plugins/<name>/`. A marketplace plugin advances to the curated pin; a plugin installed directly from a GitHub URL (untrusted, not in the marketplace) advances to whatever its recorded ref now resolves to — a pinned SHA is immutable (a no-op), a branch/tag/HEAD moves as upstream does — re-materialized verbatim with no curated adapter overlay, exactly as the original untrusted install was. The target ref is never taken from the request (no caller-supplied ref), mirroring `plugins install`\'s curation boundary. A no-op (`outcome: "already-up-to-date"`) when the installed commit already equals the target; pass `dryRun` to preview the move (`outcome: "would-upgrade"`) without touching the install. Installs lacking provenance are re-pinned to the current SHA. The upgraded code is picked up live on the next read (no restart required). `strategy` controls how local edits are reconciled: `overwrite` (default) discards them and re-installs the target wholesale; `ours`/`theirs`/`assistant` perform a three-way merge against the re-materialized install commit, carrying non-conflicting edits from both sides forward and resolving conflicting hunks toward the local edit (`ours`) or the target (`theirs`), or writing git conflict markers into the file and reporting them in `conflicts`/`binaryConflicts` for the assistant to resolve (`assistant`). A merge strategy whose install-time baseline cannot be reconstructed returns 409. Mirrors the CLI\'s `assistant plugins upgrade <name> [--strategy <s>]`.',
|
|
2060
|
+
'Move an installed plugin to its source\'s current revision, re-materializing it under `<workspaceDir>/plugins/<name>/`. A marketplace plugin advances to the curated pin; a plugin installed directly from a GitHub URL (untrusted, not in the marketplace) advances to whatever its recorded ref now resolves to — a pinned SHA is immutable (a no-op), a branch/tag/HEAD moves as upstream does — re-materialized verbatim with no curated adapter overlay, exactly as the original untrusted install was. The target ref is never taken from the request (no caller-supplied ref), mirroring `plugins install`\'s curation boundary. A no-op (`outcome: "already-up-to-date"`) when the installed commit already equals the target; pass `dryRun` to preview the move (`outcome: "would-upgrade"`) without touching the install. Installs lacking provenance are re-pinned to the current SHA. The upgraded code is picked up live on the next read (no restart required). `strategy` controls how local edits are reconciled: `overwrite` (default) discards them and re-installs the target wholesale; `ours`/`theirs`/`assistant` perform a three-way merge against the re-materialized install commit, carrying non-conflicting edits from both sides forward and resolving conflicting hunks toward the local edit (`ours`) or the target (`theirs`), or writing git conflict markers into the file and reporting them in `conflicts`/`binaryConflicts` for the assistant to resolve (`assistant`). A merge strategy whose install-time baseline cannot be reconstructed returns 409. Pass `marketplaceOnly` to refuse the untrusted direct path outright (409), which is what the unattended auto-update sweep does. Mirrors the CLI\'s `assistant plugins upgrade <name> [--strategy <s>]`.',
|
|
2043
2061
|
tags: ["plugins"],
|
|
2044
2062
|
pathParams: [
|
|
2045
2063
|
{
|
|
@@ -2062,7 +2080,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
2062
2080
|
},
|
|
2063
2081
|
"409": {
|
|
2064
2082
|
description:
|
|
2065
|
-
"The install has neither a marketplace entry nor a recorded GitHub source to advance to, or a merge strategy was requested whose install-time baseline cannot be reconstructed.",
|
|
2083
|
+
"The install has neither a marketplace entry nor a recorded GitHub source to advance to, a `marketplaceOnly` upgrade was requested for an install the marketplace does not claim, or a merge strategy was requested whose install-time baseline cannot be reconstructed.",
|
|
2066
2084
|
},
|
|
2067
2085
|
"503": {
|
|
2068
2086
|
description:
|
|
@@ -223,6 +223,35 @@ const STT_ERROR_MAP: Record<SttErrorCategory, () => RouteError> = {
|
|
|
223
223
|
"provider-error": () => new BadGatewayError("STT provider error"),
|
|
224
224
|
};
|
|
225
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Resolve the configured batch transcriber, or fail the request with a 503
|
|
228
|
+
* naming which kind of unavailability this is.
|
|
229
|
+
*
|
|
230
|
+
* A typed {@link SttError} already names the situation and its fix (a
|
|
231
|
+
* configured provider that cannot batch transcribe), so its message is
|
|
232
|
+
* surfaced verbatim. `null` means nothing is configured at all, and any other
|
|
233
|
+
* throw is an unexpected resolver failure.
|
|
234
|
+
*/
|
|
235
|
+
async function resolveBatchTranscriberOrUnavailable(): Promise<BatchTranscriber> {
|
|
236
|
+
let transcriber: BatchTranscriber | null;
|
|
237
|
+
try {
|
|
238
|
+
transcriber = await resolveBatchTranscriber();
|
|
239
|
+
} catch (err) {
|
|
240
|
+
if (err instanceof SttError) {
|
|
241
|
+
throw new ServiceUnavailableError(err.message);
|
|
242
|
+
}
|
|
243
|
+
log.error({ err }, "Failed to resolve STT transcriber");
|
|
244
|
+
throw new ServiceUnavailableError("STT provider is not available");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (!transcriber) {
|
|
248
|
+
throw new ServiceUnavailableError(
|
|
249
|
+
"No speech-to-text provider is configured",
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
return transcriber;
|
|
253
|
+
}
|
|
254
|
+
|
|
226
255
|
// ---------------------------------------------------------------------------
|
|
227
256
|
// Handlers
|
|
228
257
|
// ---------------------------------------------------------------------------
|
|
@@ -288,19 +317,7 @@ async function handleTranscribe({ body }: RouteHandlerArgs) {
|
|
|
288
317
|
}
|
|
289
318
|
|
|
290
319
|
// -- Resolve transcriber --------------------------------------------------
|
|
291
|
-
|
|
292
|
-
try {
|
|
293
|
-
transcriber = await resolveBatchTranscriber();
|
|
294
|
-
} catch (err) {
|
|
295
|
-
log.error({ err }, "Failed to resolve STT transcriber");
|
|
296
|
-
throw new ServiceUnavailableError("STT provider is not available");
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
if (!transcriber) {
|
|
300
|
-
throw new ServiceUnavailableError(
|
|
301
|
-
"No speech-to-text provider is configured",
|
|
302
|
-
);
|
|
303
|
-
}
|
|
320
|
+
const transcriber = await resolveBatchTranscriberOrUnavailable();
|
|
304
321
|
|
|
305
322
|
// -- Transcribe with timeout ----------------------------------------------
|
|
306
323
|
const abortController = new AbortController();
|
|
@@ -358,18 +375,7 @@ async function handleTranscribeFile({ body }: RouteHandlerArgs) {
|
|
|
358
375
|
);
|
|
359
376
|
}
|
|
360
377
|
|
|
361
|
-
|
|
362
|
-
try {
|
|
363
|
-
transcriber = await resolveBatchTranscriber();
|
|
364
|
-
} catch (err) {
|
|
365
|
-
log.error({ err }, "Failed to resolve STT transcriber");
|
|
366
|
-
throw new ServiceUnavailableError("STT provider is not available");
|
|
367
|
-
}
|
|
368
|
-
if (!transcriber) {
|
|
369
|
-
throw new ServiceUnavailableError(
|
|
370
|
-
"No speech-to-text provider is configured",
|
|
371
|
-
);
|
|
372
|
-
}
|
|
378
|
+
const transcriber = await resolveBatchTranscriberOrUnavailable();
|
|
373
379
|
|
|
374
380
|
const startTime = Date.now();
|
|
375
381
|
let wavPath: string | null = null;
|
|
@@ -64,6 +64,9 @@ export async function resolveSurfaceConversation(
|
|
|
64
64
|
const escaped = surfaceId.replace(/[\\%_]/g, "\\$&");
|
|
65
65
|
const row = rawGet<{ conversation_id: string }>(
|
|
66
66
|
"surfaceResolver:resolveConversation",
|
|
67
|
+
// Unfinalized rows cannot match: their content is a { ref } pointer,
|
|
68
|
+
// never inline surface JSON, so the LIKE probes below see finalized
|
|
69
|
+
// content only by shape. No completeness predicate needed.
|
|
67
70
|
`SELECT conversation_id FROM messages
|
|
68
71
|
WHERE content LIKE ? ESCAPE '\\'
|
|
69
72
|
ORDER BY created_at DESC
|