@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
package/src/cli/commands/mcp.ts
CHANGED
|
@@ -24,6 +24,10 @@ interface McpServerEntry {
|
|
|
24
24
|
defaultRiskLevel: string;
|
|
25
25
|
allowedTools?: string[];
|
|
26
26
|
blockedTools?: string[];
|
|
27
|
+
/** Workspace `config.json` or a plugin's `mcp.json`. */
|
|
28
|
+
source?: "workspace" | "plugin";
|
|
29
|
+
/** Plugin that declared the server, when `source` is `plugin`. */
|
|
30
|
+
pluginName?: string;
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
// ---------------------------------------------------------------------------
|
|
@@ -81,6 +85,11 @@ async function pollMcpAuthStatus(
|
|
|
81
85
|
function printServerEntry(entry: McpServerEntry): void {
|
|
82
86
|
log.info(` ${entry.id}`);
|
|
83
87
|
log.info(` Status: ${entry.status}`);
|
|
88
|
+
// Only plugin-declared servers name a source. A workspace server is the
|
|
89
|
+
// default case and printing "workspace" on every one of them is noise.
|
|
90
|
+
if (entry.source === "plugin") {
|
|
91
|
+
log.info(` Source: plugin (${entry.pluginName ?? "unknown"})`);
|
|
92
|
+
}
|
|
84
93
|
log.info(` Transport: ${entry.transport?.type ?? "unknown"}`);
|
|
85
94
|
if (entry.transport?.type === "stdio") {
|
|
86
95
|
log.info(
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
* Tests for the `assistant memory v3` CLI subgroup (section-lane model).
|
|
3
3
|
*
|
|
4
4
|
* Validates:
|
|
5
|
-
* - Subcommand registration (rebuild-index, backfill-sections)
|
|
6
|
-
* `memory v3`.
|
|
7
|
-
* -
|
|
5
|
+
* - Subcommand registration (rebuild-index, backfill-sections, gate-stats)
|
|
6
|
+
* under `memory v3`.
|
|
7
|
+
* - `rebuild-index` and `backfill-sections` route through IPC to the daemon.
|
|
8
8
|
* - `backfill-sections` passes a long IPC timeout (the one-time full-corpus
|
|
9
|
-
* embed easily outlasts the default 60s)
|
|
10
|
-
* -
|
|
9
|
+
* embed easily outlasts the default 60s).
|
|
10
|
+
* - `gate-stats` calls handleMemoryV3GateStats directly — no daemon required.
|
|
11
|
+
* - Error paths return a non-zero exit code without throwing.
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
@@ -39,6 +40,47 @@ let mockIpcResult: {
|
|
|
39
40
|
/** Captured log output for assertion. */
|
|
40
41
|
let logOutput: string[] = [];
|
|
41
42
|
|
|
43
|
+
/** Shared fake gate-stats payload used across gate-stats tests. */
|
|
44
|
+
const fakeGateStats = {
|
|
45
|
+
lookbackDays: 30,
|
|
46
|
+
totalRuns: 10,
|
|
47
|
+
buckets: [
|
|
48
|
+
{
|
|
49
|
+
pageCountRange: "0–9",
|
|
50
|
+
total: 2,
|
|
51
|
+
scored: 1,
|
|
52
|
+
passed: 2,
|
|
53
|
+
scoredPassRate: 1,
|
|
54
|
+
reasons: { dense_pass: 1, dense_disabled: 1 },
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
pageCountRange: "10–49",
|
|
58
|
+
total: 5,
|
|
59
|
+
scored: 4,
|
|
60
|
+
passed: 3,
|
|
61
|
+
scoredPassRate: 0.75,
|
|
62
|
+
reasons: { dense_pass: 3, fail_no_signal: 1 },
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
pageCountRange: "50–199",
|
|
66
|
+
total: 2,
|
|
67
|
+
scored: 2,
|
|
68
|
+
passed: 1,
|
|
69
|
+
scoredPassRate: 0.5,
|
|
70
|
+
reasons: { dense_pass: 1, fail_no_signal: 1 },
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
pageCountRange: "200+",
|
|
74
|
+
total: 1,
|
|
75
|
+
scored: 1,
|
|
76
|
+
passed: 0,
|
|
77
|
+
scoredPassRate: 0,
|
|
78
|
+
reasons: { fail_no_signal: 1 },
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
unknownPageCount: { total: 0, passed: 0, reasons: {} },
|
|
82
|
+
};
|
|
83
|
+
|
|
42
84
|
// ---------------------------------------------------------------------------
|
|
43
85
|
// Mocks
|
|
44
86
|
// ---------------------------------------------------------------------------
|
|
@@ -68,6 +110,33 @@ mock.module("../../../../util/logger.js", () => ({
|
|
|
68
110
|
getCliLogger: () => fakeLogger,
|
|
69
111
|
}));
|
|
70
112
|
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// gate-stats direct-invocation mocks
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
/** Args captured from the last handleMemoryV3GateStats call. */
|
|
118
|
+
let lastGateStatsArgs: { lookbackDays: number; db: unknown } | null = null;
|
|
119
|
+
|
|
120
|
+
/** What handleMemoryV3GateStats returns, or an error to throw. */
|
|
121
|
+
let gateStatsImpl: (() => unknown) | null = null;
|
|
122
|
+
|
|
123
|
+
mock.module(
|
|
124
|
+
"../../../../plugins/defaults/memory/src/memory-v3-routes.js",
|
|
125
|
+
() => ({
|
|
126
|
+
handleMemoryV3GateStats: (lookbackDays: number, db: unknown) => {
|
|
127
|
+
lastGateStatsArgs = { lookbackDays, db };
|
|
128
|
+
if (gateStatsImpl) {
|
|
129
|
+
return gateStatsImpl();
|
|
130
|
+
}
|
|
131
|
+
return fakeGateStats;
|
|
132
|
+
},
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
mock.module("../../../../persistence/db-connection.js", () => ({
|
|
137
|
+
getTelemetrySqlite: () => ({}),
|
|
138
|
+
}));
|
|
139
|
+
|
|
71
140
|
// ---------------------------------------------------------------------------
|
|
72
141
|
// Import modules under test (after mocks)
|
|
73
142
|
// ---------------------------------------------------------------------------
|
|
@@ -135,6 +204,8 @@ beforeEach(() => {
|
|
|
135
204
|
ok: true,
|
|
136
205
|
result: { articles: 3, sections: 12, failures: 0 },
|
|
137
206
|
};
|
|
207
|
+
lastGateStatsArgs = null;
|
|
208
|
+
gateStatsImpl = null;
|
|
138
209
|
logOutput = [];
|
|
139
210
|
process.exitCode = 0;
|
|
140
211
|
});
|
|
@@ -155,6 +226,7 @@ describe("subcommand registration", () => {
|
|
|
155
226
|
"backfill-sections",
|
|
156
227
|
"eval",
|
|
157
228
|
"eval-tally",
|
|
229
|
+
"gate-stats",
|
|
158
230
|
"rebuild-index",
|
|
159
231
|
]);
|
|
160
232
|
});
|
|
@@ -236,3 +308,54 @@ describe("memory v3 backfill-sections", () => {
|
|
|
236
308
|
expect(exitCode).toBe(1);
|
|
237
309
|
});
|
|
238
310
|
});
|
|
311
|
+
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
// gate-stats (direct invocation — no daemon required)
|
|
314
|
+
// ---------------------------------------------------------------------------
|
|
315
|
+
|
|
316
|
+
describe("memory v3 gate-stats", () => {
|
|
317
|
+
test("calls handleMemoryV3GateStats directly with default lookback (no IPC)", async () => {
|
|
318
|
+
const { exitCode } = await runCommand(["memory", "v3", "gate-stats"]);
|
|
319
|
+
|
|
320
|
+
expect(exitCode).toBe(0);
|
|
321
|
+
// Must NOT have gone through IPC.
|
|
322
|
+
expect(lastIpcCall).toBeNull();
|
|
323
|
+
// Must have called the handler directly with the default lookback.
|
|
324
|
+
expect(lastGateStatsArgs!.lookbackDays).toBe(30);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test("forwards --lookback-days directly to handleMemoryV3GateStats", async () => {
|
|
328
|
+
await runCommand(["memory", "v3", "gate-stats", "--lookback-days", "7"]);
|
|
329
|
+
|
|
330
|
+
expect(lastIpcCall).toBeNull();
|
|
331
|
+
expect(lastGateStatsArgs!.lookbackDays).toBe(7);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test("prints formatted table on success (no --json)", async () => {
|
|
335
|
+
await runCommand(["memory", "v3", "gate-stats"]);
|
|
336
|
+
|
|
337
|
+
expect(logOutput.some((l) => l.includes("Gate-stats"))).toBe(true);
|
|
338
|
+
expect(logOutput.some((l) => l.includes("10–49"))).toBe(true);
|
|
339
|
+
expect(logOutput.some((l) => l.includes("75.0%"))).toBe(true);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test("emits raw JSON with --json", async () => {
|
|
343
|
+
await runCommand(["memory", "v3", "gate-stats", "--json"]);
|
|
344
|
+
|
|
345
|
+
const raw = logOutput.join("");
|
|
346
|
+
const parsed = JSON.parse(raw) as typeof fakeGateStats;
|
|
347
|
+
expect(parsed.totalRuns).toBe(10);
|
|
348
|
+
expect(parsed.buckets).toHaveLength(4);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test("exits with code 1 when handleMemoryV3GateStats throws", async () => {
|
|
352
|
+
gateStatsImpl = () => {
|
|
353
|
+
throw new Error("telemetry DB locked");
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const { exitCode } = await runCommand(["memory", "v3", "gate-stats"]);
|
|
357
|
+
|
|
358
|
+
expect(exitCode).toBe(1);
|
|
359
|
+
expect(logOutput.some((l) => l.includes("telemetry DB locked"))).toBe(true);
|
|
360
|
+
});
|
|
361
|
+
});
|
|
@@ -606,7 +606,9 @@ that live state safely.
|
|
|
606
606
|
|
|
607
607
|
Examples:
|
|
608
608
|
$ assistant memory v3 rebuild-index
|
|
609
|
-
$ assistant memory v3 backfill-sections
|
|
609
|
+
$ assistant memory v3 backfill-sections
|
|
610
|
+
$ assistant memory v3 gate-stats
|
|
611
|
+
$ assistant memory v3 gate-stats --lookback-days 7 --json`,
|
|
610
612
|
subcommands: [
|
|
611
613
|
{
|
|
612
614
|
name: "rebuild-index",
|
|
@@ -716,6 +718,46 @@ Examples:
|
|
|
716
718
|
$ assistant memory v3 eval --snapshot .mv3/snapshot/concepts --staging .mv3/staging --out .mv3/eval
|
|
717
719
|
$ assistant memory v3 eval --snapshot .mv3/snapshot/concepts --staging .mv3/staging --out .mv3/eval --turns-file .mv3/eval/key.json
|
|
718
720
|
$ assistant memory v3 eval --snapshot .mv3/snapshot/concepts --staging .mv3/staging --out .mv3/eval --exclude-conversation <migration-conv-id>`,
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
name: "gate-stats",
|
|
724
|
+
description:
|
|
725
|
+
"Show injection gate pass rates bucketed by corpus size (read-only)",
|
|
726
|
+
options: [
|
|
727
|
+
{
|
|
728
|
+
flags: "--lookback-days <n>",
|
|
729
|
+
description: "Days of telemetry to aggregate (1–90, default 30)",
|
|
730
|
+
defaultValue: "30",
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
flags: "--json",
|
|
734
|
+
description: "Emit raw JSON instead of a formatted table",
|
|
735
|
+
},
|
|
736
|
+
],
|
|
737
|
+
helpText: `
|
|
738
|
+
Reads the memory v3 injection gate telemetry outbox and prints pass rates
|
|
739
|
+
and reason distributions grouped by concept page count bucket
|
|
740
|
+
(0–9 / 10–49 / 50–199 / 200+). Scored runs (dense lane was available and
|
|
741
|
+
actually weighed scores) are reported separately from pass-open shortcuts
|
|
742
|
+
(dense disabled / unavailable / gate threw) so scoredPassRate reflects
|
|
743
|
+
only contested gate decisions — the signal relevant for threshold calibration.
|
|
744
|
+
|
|
745
|
+
Coverage is limited to runs still pending platform flush. In a healthy system
|
|
746
|
+
the outbox holds only the last few minutes to hours of events, so
|
|
747
|
+
--lookback-days is an upper bound, not a guarantee; long-window aggregation
|
|
748
|
+
lives on the platform side on top of flushed watchdog events.
|
|
749
|
+
|
|
750
|
+
Reads the telemetry database directly — the assistant does not need to be
|
|
751
|
+
running.
|
|
752
|
+
|
|
753
|
+
Intended use: verify that gate firing rates shift as expected after a
|
|
754
|
+
config or threshold change, and check whether the bm25-auto-calibration
|
|
755
|
+
flag is worth enabling for large corpora.
|
|
756
|
+
|
|
757
|
+
Examples:
|
|
758
|
+
$ assistant memory v3 gate-stats
|
|
759
|
+
$ assistant memory v3 gate-stats --lookback-days 7
|
|
760
|
+
$ assistant memory v3 gate-stats --json | jq '.buckets[] | select(.pageCountRange == "200+")'`,
|
|
719
761
|
},
|
|
720
762
|
{
|
|
721
763
|
name: "eval-tally",
|
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
MemoryEvalTallyResult,
|
|
26
26
|
} from "../../../plugins/defaults/memory/src/memory-eval-routes.js";
|
|
27
27
|
import type {
|
|
28
|
+
GateStatsResponse,
|
|
28
29
|
MemoryV3BackfillSectionsResult,
|
|
29
30
|
MemoryV3RebuildIndexResult,
|
|
30
31
|
} from "../../../plugins/defaults/memory/src/memory-v3-routes.js";
|
|
@@ -52,6 +53,36 @@ function collectRepeatable(value: string, acc: string[]): string[] {
|
|
|
52
53
|
return [...acc, value];
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
/** Render gate-stats as a human-readable table. */
|
|
57
|
+
function formatGateStats(r: GateStatsResponse): string {
|
|
58
|
+
const lines: string[] = [
|
|
59
|
+
`Gate-stats (last ${r.lookbackDays} day${r.lookbackDays === 1 ? "" : "s"}, ${r.totalRuns} run${r.totalRuns === 1 ? "" : "s"})`,
|
|
60
|
+
"",
|
|
61
|
+
`Bucket Total Scored Passed ScoredPassRate Top reasons`,
|
|
62
|
+
];
|
|
63
|
+
for (const b of r.buckets) {
|
|
64
|
+
const rate =
|
|
65
|
+
b.scoredPassRate !== null
|
|
66
|
+
? `${(b.scoredPassRate * 100).toFixed(1)}%`
|
|
67
|
+
: "n/a";
|
|
68
|
+
const topReasons = Object.entries(b.reasons)
|
|
69
|
+
.sort(([, a], [, bv]) => (bv as number) - (a as number))
|
|
70
|
+
.slice(0, 3)
|
|
71
|
+
.map(([k, v]) => `${k}: ${v}`)
|
|
72
|
+
.join(", ");
|
|
73
|
+
lines.push(
|
|
74
|
+
`${b.pageCountRange.padEnd(10)} ${String(b.total).padStart(5)} ${String(b.scored).padStart(6)} ${String(b.passed).padStart(6)} ${rate.padStart(14)} ${topReasons}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
if (r.unknownPageCount.total > 0) {
|
|
78
|
+
lines.push("");
|
|
79
|
+
lines.push(
|
|
80
|
+
`Unknown page count: ${r.unknownPageCount.total} total, ${r.unknownPageCount.passed} passed`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return lines.join("\n");
|
|
84
|
+
}
|
|
85
|
+
|
|
55
86
|
/**
|
|
56
87
|
* Read the `turn` ids from a prior run's `key.json` or `packets.json` (both are
|
|
57
88
|
* arrays of objects carrying a `turn` field). Used to pin `--turns-file` so a
|
|
@@ -271,4 +302,37 @@ export function registerMemoryV3Command(memory: Command): void {
|
|
|
271
302
|
log.info(formatTally(payload));
|
|
272
303
|
},
|
|
273
304
|
);
|
|
305
|
+
|
|
306
|
+
// ── gate-stats ────────────────────────────────────────────────────────
|
|
307
|
+
// Reads the telemetry DB directly — no daemon required.
|
|
308
|
+
|
|
309
|
+
subcommand(v3, "gate-stats").action(
|
|
310
|
+
async (opts: { lookbackDays: string; json?: boolean }) => {
|
|
311
|
+
const lookbackDays = Number(opts.lookbackDays);
|
|
312
|
+
const [{ handleMemoryV3GateStats }, { getTelemetrySqlite }] =
|
|
313
|
+
await Promise.all([
|
|
314
|
+
import("../../../plugins/defaults/memory/src/memory-v3-routes.js"),
|
|
315
|
+
import("../../../persistence/db-connection.js"),
|
|
316
|
+
]);
|
|
317
|
+
let payload;
|
|
318
|
+
try {
|
|
319
|
+
payload = handleMemoryV3GateStats(
|
|
320
|
+
Number.isFinite(lookbackDays) ? lookbackDays : 30,
|
|
321
|
+
getTelemetrySqlite(),
|
|
322
|
+
);
|
|
323
|
+
} catch (err) {
|
|
324
|
+
log.error(
|
|
325
|
+
"Failed to read gate stats: %s",
|
|
326
|
+
err instanceof Error ? err.message : String(err),
|
|
327
|
+
);
|
|
328
|
+
process.exitCode = 1;
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (opts.json === true) {
|
|
332
|
+
log.info(JSON.stringify(payload, null, 2));
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
log.info(formatGateStats(payload));
|
|
336
|
+
},
|
|
337
|
+
);
|
|
274
338
|
}
|
|
@@ -1,7 +1,31 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Declarative help for the `assistant stt` command.
|
|
3
|
+
*
|
|
4
|
+
* The advertised provider list is derived from the STT provider catalog, the
|
|
5
|
+
* single source of truth for which providers exist and which boundaries they
|
|
6
|
+
* serve, so the help cannot drift from what the daemon actually accepts.
|
|
7
|
+
*/
|
|
2
8
|
|
|
9
|
+
// provider-catalog is an execution-free data leaf (a literal map behind
|
|
10
|
+
// type-only imports, no adapter or credential graph), consumed synchronously
|
|
11
|
+
// to derive this module's constant. Pure data from pure data, so a lazy
|
|
12
|
+
// `import()` has nothing to defer.
|
|
13
|
+
// eslint-disable-next-line cli/no-daemon-internals
|
|
14
|
+
import {
|
|
15
|
+
listProviderIds,
|
|
16
|
+
supportsBoundary,
|
|
17
|
+
} from "../../providers/speech-to-text/provider-catalog.js";
|
|
3
18
|
import type { CliCommandHelp } from "../lib/cli-command-help.js";
|
|
4
19
|
|
|
20
|
+
/**
|
|
21
|
+
* `stt transcribe` runs the `daemon-batch` boundary, so streaming-only
|
|
22
|
+
* providers are omitted: naming one would advertise a configuration that
|
|
23
|
+
* fails on every file.
|
|
24
|
+
*/
|
|
25
|
+
const batchProviders = listProviderIds()
|
|
26
|
+
.filter((id) => supportsBoundary(id, "daemon-batch"))
|
|
27
|
+
.join(", ");
|
|
28
|
+
|
|
5
29
|
export const sttHelp: CliCommandHelp = {
|
|
6
30
|
name: "stt",
|
|
7
31
|
description: "Speech-to-text operations",
|
|
@@ -11,7 +35,8 @@ audio and video files. The provider is set via:
|
|
|
11
35
|
|
|
12
36
|
$ assistant config set services.stt.provider <provider>
|
|
13
37
|
|
|
14
|
-
Supported providers:
|
|
38
|
+
Supported providers: ${batchProviders}.
|
|
39
|
+
Streaming-only providers serve live speech and cannot transcribe files.
|
|
15
40
|
|
|
16
41
|
Examples:
|
|
17
42
|
$ assistant stt transcribe --file /path/to/meeting.wav
|
|
@@ -32,6 +32,7 @@ import { computeFingerprint } from "../plugin-fingerprint.js";
|
|
|
32
32
|
import { PluginNotInstalledError } from "../uninstall-plugin.js";
|
|
33
33
|
import {
|
|
34
34
|
PluginMergeBaselineError,
|
|
35
|
+
PluginNotCuratedError,
|
|
35
36
|
PluginNotUpgradableError,
|
|
36
37
|
upgradePlugin,
|
|
37
38
|
} from "../upgrade-plugin.js";
|
|
@@ -574,6 +575,44 @@ describe("upgradePlugin — direct GitHub-URL installs", () => {
|
|
|
574
575
|
expect(calls[0]?.[0]).toBe("ls-remote");
|
|
575
576
|
});
|
|
576
577
|
|
|
578
|
+
test("marketplaceOnly refuses the direct path instead of following the ref", async () => {
|
|
579
|
+
// GIVEN a direct install tracking `main` at SHA_A, absent from the
|
|
580
|
+
// marketplace, whose branch has advanced to SHA_B
|
|
581
|
+
installCopy(pluginsDir, "level-up", { commit: SHA_A, ref: "main" });
|
|
582
|
+
const fetch = makeFetch({ manifest: undefined });
|
|
583
|
+
const calls: string[][] = [];
|
|
584
|
+
const runGit = directGitRunner({ main: SHA_B }, SHA_B, { calls });
|
|
585
|
+
|
|
586
|
+
// WHEN a caller that only accepts a curated pin asks for the upgrade
|
|
587
|
+
const upgrade = upgradePlugin(
|
|
588
|
+
{ name: "level-up", marketplaceOnly: true },
|
|
589
|
+
{ fetch, runGit, workspacePluginsDir: pluginsDir },
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
// THEN it is refused, and the mutable ref is never even resolved
|
|
593
|
+
await expect(upgrade).rejects.toThrow(PluginNotCuratedError);
|
|
594
|
+
expect(calls).toEqual([]);
|
|
595
|
+
// AND the install stays exactly where it was
|
|
596
|
+
expect(sidecarCommit(pluginsDir, "level-up")).toBe(SHA_A);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
test("marketplaceOnly still upgrades a plugin the catalog claims", async () => {
|
|
600
|
+
// GIVEN an install the marketplace pins at SHA_B
|
|
601
|
+
installCopy(pluginsDir, "level-up", { commit: SHA_A });
|
|
602
|
+
const fetch = makeFetch({ manifest: manifestWith("level-up", SHA_B) });
|
|
603
|
+
const runGit = fakeGitRunner(SHA_B);
|
|
604
|
+
|
|
605
|
+
// WHEN the same curated-only caller asks for the upgrade
|
|
606
|
+
const result = await upgradePlugin(
|
|
607
|
+
{ name: "level-up", marketplaceOnly: true },
|
|
608
|
+
{ fetch, runGit, workspacePluginsDir: pluginsDir },
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
// THEN the flag is inert: the curated pin is taken as usual
|
|
612
|
+
expect(result.outcome).toBe("upgraded");
|
|
613
|
+
expect(result.toCommit).toBe(SHA_B);
|
|
614
|
+
});
|
|
615
|
+
|
|
577
616
|
test("is a no-op when the recorded branch still points at the installed commit", async () => {
|
|
578
617
|
// GIVEN a direct install tracking `main` at SHA_A
|
|
579
618
|
installCopy(pluginsDir, "level-up", { commit: SHA_A, ref: "main" });
|
|
@@ -422,6 +422,19 @@
|
|
|
422
422
|
"license": "MIT",
|
|
423
423
|
"icon": "✈️"
|
|
424
424
|
},
|
|
425
|
+
{
|
|
426
|
+
"name": "unabyss",
|
|
427
|
+
"source": {
|
|
428
|
+
"source": "github",
|
|
429
|
+
"repo": "Unabyss/unabyss-vellum",
|
|
430
|
+
"ref": "9f0bb0753dad09dace8578e242961bc2da912af3"
|
|
431
|
+
},
|
|
432
|
+
"description": "Personal and company context from Unabyss, available to your agent through the Unabyss MCP server.",
|
|
433
|
+
"category": "memory",
|
|
434
|
+
"homepage": "https://unabyss.com",
|
|
435
|
+
"license": "MIT",
|
|
436
|
+
"icon": "🌊"
|
|
437
|
+
},
|
|
425
438
|
{
|
|
426
439
|
"name": "vellum-client-qa",
|
|
427
440
|
"source": {
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
* verbatim, with no curated adapter overlay, exactly as the original untrusted
|
|
18
18
|
* install was (see {@link directUpgrade}).
|
|
19
19
|
*
|
|
20
|
+
* Because that target is a mutable ref, taking it means running whatever
|
|
21
|
+
* upstream pushed since, which is a choice only a human should make. A caller
|
|
22
|
+
* with nobody in the loop passes `marketplaceOnly` to rule it out: the direct
|
|
23
|
+
* path then throws {@link PluginNotCuratedError} instead of advancing.
|
|
24
|
+
*
|
|
20
25
|
* This is deliberately a distinct operation from install: `install` is
|
|
21
26
|
* first-time materialization (and errors on an existing install unless
|
|
22
27
|
* `--force` is passed), whereas `upgrade` moves an existing install forward.
|
|
@@ -114,6 +119,20 @@ export interface UpgradePluginOptions {
|
|
|
114
119
|
* {@link DEFAULT_PLUGIN_UPGRADE_STRATEGY}.
|
|
115
120
|
*/
|
|
116
121
|
readonly strategy?: PluginUpgradeStrategy;
|
|
122
|
+
/**
|
|
123
|
+
* Refuse to advance an install the marketplace does not claim, instead of
|
|
124
|
+
* falling back to {@link directUpgrade}.
|
|
125
|
+
*
|
|
126
|
+
* A direct install's upgrade target is a mutable upstream ref, so taking it
|
|
127
|
+
* means fetching and executing whatever was pushed since. Callers with
|
|
128
|
+
* nobody in the loop (the unattended auto-update sweep) set this so that
|
|
129
|
+
* outcome is impossible for them, and it is enforced here rather than by the
|
|
130
|
+
* caller's own pre-check: the catalog is fetched again inside this call, so
|
|
131
|
+
* an entry that disappears in between would otherwise silently reroute a
|
|
132
|
+
* curated upgrade into a direct one. Defaults to false, which is what an
|
|
133
|
+
* interactive `assistant plugins upgrade` wants.
|
|
134
|
+
*/
|
|
135
|
+
readonly marketplaceOnly?: boolean;
|
|
117
136
|
}
|
|
118
137
|
|
|
119
138
|
/** Dependencies injected by the caller. */
|
|
@@ -208,6 +227,23 @@ export class PluginNotUpgradableError extends Error {
|
|
|
208
227
|
}
|
|
209
228
|
}
|
|
210
229
|
|
|
230
|
+
/**
|
|
231
|
+
* A `marketplaceOnly` upgrade was requested for an install the marketplace does
|
|
232
|
+
* not claim, so the only revision available is a mutable upstream ref the
|
|
233
|
+
* caller refuses to take.
|
|
234
|
+
*
|
|
235
|
+
* Distinct from {@link PluginNotUpgradableError}: the install *can* be
|
|
236
|
+
* upgraded, just not without a human choosing to accept unreviewed code.
|
|
237
|
+
*/
|
|
238
|
+
export class PluginNotCuratedError extends Error {
|
|
239
|
+
constructor(readonly pluginName: string) {
|
|
240
|
+
super(
|
|
241
|
+
`Plugin "${pluginName}" cannot be upgraded automatically: it has no marketplace entry, so its only upgrade target is a mutable upstream ref. Run 'assistant plugins upgrade ${pluginName}' to move it deliberately.`,
|
|
242
|
+
);
|
|
243
|
+
this.name = "PluginNotCuratedError";
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
211
247
|
/**
|
|
212
248
|
* A merge strategy (`ours`/`theirs`) was requested but the install-time
|
|
213
249
|
* baseline needed for a three-way merge cannot be reconstructed.
|
|
@@ -244,6 +280,8 @@ function pluginTarget(name: string, deps: UpgradePluginDeps): string {
|
|
|
244
280
|
* Throws {@link PluginNotInstalledError} when no copy is installed,
|
|
245
281
|
* {@link PluginNotUpgradableError} when the install is neither in the
|
|
246
282
|
* marketplace nor carries a recorded GitHub source to advance,
|
|
283
|
+
* {@link PluginNotCuratedError} when `marketplaceOnly` was requested for an
|
|
284
|
+
* install the marketplace does not claim,
|
|
247
285
|
* {@link PluginMergeBaselineError} when a merge strategy is requested but the
|
|
248
286
|
* install-time baseline cannot be reconstructed,
|
|
249
287
|
* {@link PluginSourceUnavailableError} when the marketplace catalog or the
|
|
@@ -302,6 +340,10 @@ export async function upgradePlugin(
|
|
|
302
340
|
"it has no marketplace entry and no installed copy to upgrade",
|
|
303
341
|
);
|
|
304
342
|
}
|
|
343
|
+
if (opts.marketplaceOnly) {
|
|
344
|
+
// The caller only accepts a curated pin, and this install has none.
|
|
345
|
+
throw new PluginNotCuratedError(name);
|
|
346
|
+
}
|
|
305
347
|
return directUpgrade({ name, local, dryRun, strategy }, deps);
|
|
306
348
|
}
|
|
307
349
|
case "remote-unavailable":
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
CODE_DEFAULT_PROFILE_ENTRIES,
|
|
8
8
|
getEffectiveProfile,
|
|
9
9
|
getEffectiveProfiles,
|
|
10
|
+
getEffectiveProfilesForProvider,
|
|
10
11
|
PROFILE_IMPLS,
|
|
11
12
|
resolveDefaultProfileForProvider,
|
|
12
13
|
} from "../default-profile-catalog.js";
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
import {
|
|
23
24
|
type DefaultProviderConfig,
|
|
24
25
|
type LLMCallSite,
|
|
26
|
+
LLMCallSiteEnum,
|
|
25
27
|
LLMSchema,
|
|
26
28
|
type ProfileEntry,
|
|
27
29
|
} from "../schemas/llm.js";
|
|
@@ -245,7 +247,10 @@ describe("resolver integration", () => {
|
|
|
245
247
|
},
|
|
246
248
|
});
|
|
247
249
|
const body = CODE_DEFAULT_PROFILE_ENTRIES["latency-optimized"]!;
|
|
248
|
-
for (const callSite of [
|
|
250
|
+
for (const callSite of [
|
|
251
|
+
"voiceFrontDoor",
|
|
252
|
+
"voiceProgressNarration",
|
|
253
|
+
] as const) {
|
|
249
254
|
const resolved = resolveCallSiteConfig(callSite, llm);
|
|
250
255
|
expect(resolved.model).toBe(body.model as string);
|
|
251
256
|
expect(resolved.model).not.toBe("claude-opus-4-6");
|
|
@@ -265,6 +270,12 @@ describe("resolver integration", () => {
|
|
|
265
270
|
});
|
|
266
271
|
|
|
267
272
|
describe("schema validation", () => {
|
|
273
|
+
test("voiceFrontDoor is the only front callsite", () => {
|
|
274
|
+
expect(
|
|
275
|
+
LLMCallSiteEnum.options.filter((callSite) => callSite.includes("Front")),
|
|
276
|
+
).toEqual(["voiceFrontDoor"]);
|
|
277
|
+
});
|
|
278
|
+
|
|
268
279
|
test("always-available default names are valid references; os-beta only when materialized", () => {
|
|
269
280
|
expect(() => LLMSchema.parse({ activeProfile: "balanced" })).not.toThrow();
|
|
270
281
|
expect(() =>
|
|
@@ -323,7 +334,7 @@ describe("resolveDefaultProfileForProvider", () => {
|
|
|
323
334
|
expect(typeof entry?.model).toBe("string");
|
|
324
335
|
expect(entry?.provider).toBeDefined();
|
|
325
336
|
// Identity columns stamp no connection; BYOK columns always do.
|
|
326
|
-
if (entry?.provider === "vellum") {
|
|
337
|
+
if (entry?.provider === "vellum" || entry?.provider === "chatgpt") {
|
|
327
338
|
expect(entry?.provider_connection).toBeUndefined();
|
|
328
339
|
} else {
|
|
329
340
|
expect(entry?.provider_connection).toBeDefined();
|
|
@@ -333,6 +344,27 @@ describe("resolveDefaultProfileForProvider", () => {
|
|
|
333
344
|
}
|
|
334
345
|
});
|
|
335
346
|
|
|
347
|
+
test("the chatgpt column resolves Codex-pinned models with no connection stamp", () => {
|
|
348
|
+
const byKey: Record<string, string> = {
|
|
349
|
+
balanced: "gpt-5.6-luna",
|
|
350
|
+
"quality-optimized": "gpt-5.6-sol",
|
|
351
|
+
"cost-optimized": "gpt-5.6-luna",
|
|
352
|
+
"latency-optimized": "gpt-5.6-luna",
|
|
353
|
+
};
|
|
354
|
+
const effective = getEffectiveProfilesForProvider(undefined, dp("chatgpt"));
|
|
355
|
+
for (const [key, model] of Object.entries(byKey)) {
|
|
356
|
+
const entry = effective[key];
|
|
357
|
+
expect(entry?.provider).toBe("chatgpt");
|
|
358
|
+
expect(entry?.model).toBe(model);
|
|
359
|
+
expect(entry?.provider_connection).toBeUndefined();
|
|
360
|
+
expect(entry?.source).toBe("managed");
|
|
361
|
+
}
|
|
362
|
+
// Cost and Speed both opt fully out of reasoning.
|
|
363
|
+
expect(effective["cost-optimized"]?.effort).toBe("none");
|
|
364
|
+
expect(effective["latency-optimized"]?.effort).toBe("none");
|
|
365
|
+
expect(effective.balanced?.thinking?.enabled).toBe(true);
|
|
366
|
+
});
|
|
367
|
+
|
|
336
368
|
test("a provider without a named matrix column materializes from the shared BYOK templates", () => {
|
|
337
369
|
const entry = resolveDefaultProfileForProvider(
|
|
338
370
|
undefined,
|
|
@@ -157,6 +157,12 @@ describe("resolveDefaultConnectionName", () => {
|
|
|
157
157
|
);
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
+
test("chatgpt resolves to the canonical subscription connection name", () => {
|
|
161
|
+
expect(resolveDefaultConnectionName({ provider: "chatgpt" })).toBe(
|
|
162
|
+
"chatgpt-subscription",
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
160
166
|
test("every other provider resolves to its personal connection", () => {
|
|
161
167
|
expect(resolveDefaultConnectionName({ provider: "anthropic" })).toBe(
|
|
162
168
|
"anthropic-personal",
|
|
@@ -221,7 +227,6 @@ describe("getDefaultProvider / setDefaultProvider", () => {
|
|
|
221
227
|
test("setDefaultProvider validates the provider before writing", () => {
|
|
222
228
|
expect(() =>
|
|
223
229
|
setDefaultProvider({
|
|
224
|
-
// @ts-expect-error deliberately invalid for the test
|
|
225
230
|
provider: "not-a-provider",
|
|
226
231
|
}),
|
|
227
232
|
).toThrow();
|