@vellumai/assistant 0.11.6 → 0.11.7-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 +31 -19
- package/Dockerfile +1 -0
- package/docs/architecture/security.md +3 -3
- package/docs/flux-turn-detection-spike.md +8 -7
- package/docs/runbook-trusted-contacts.md +14 -0
- package/docs/stt-provider-onboarding.md +8 -4
- package/knip.json +1 -0
- package/node_modules/@vellumai/avatar-manifest/package.json +18 -0
- package/node_modules/@vellumai/avatar-manifest/src/__tests__/manifest.test.ts +173 -0
- package/node_modules/@vellumai/avatar-manifest/src/__tests__/read.test.ts +114 -0
- package/node_modules/@vellumai/avatar-manifest/src/index.ts +29 -0
- package/node_modules/@vellumai/avatar-manifest/src/layout.ts +18 -0
- package/node_modules/@vellumai/avatar-manifest/src/manifest.ts +156 -0
- package/node_modules/@vellumai/avatar-manifest/src/read.ts +109 -0
- package/node_modules/@vellumai/avatar-manifest/tsconfig.json +20 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
- package/node_modules/@vellumai/ces-client/src/http-credentials.ts +161 -0
- package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
- package/node_modules/@vellumai/environments/package.json +2 -1
- package/node_modules/@vellumai/environments/src/shell.test.ts +56 -0
- package/node_modules/@vellumai/environments/src/shell.ts +65 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +32 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +82 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/plugin-admission-denied-contract.test.ts +56 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +18 -0
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +5 -11
- package/node_modules/@vellumai/gateway-client/src/channel-permission-contract.ts +3 -1
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +9 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +4 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +75 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +17 -0
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +8 -0
- package/node_modules/@vellumai/gateway-client/src/plugin-admission-denied-contract.ts +47 -0
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +11 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
- package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
- package/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
- package/openapi.yaml +419 -55
- package/package.json +4 -1
- package/scripts/sync-llm-catalog.ts +6 -0
- package/src/__tests__/approval-card-edit-inbound-handoff.test.ts +199 -0
- package/src/__tests__/approval-card-edit.test.ts +153 -0
- package/src/__tests__/attachments-store-heic-normalize.test.ts +1 -1
- package/src/__tests__/available-skills.test.ts +54 -0
- package/src/__tests__/avatar-identity-sync.test.ts +5 -1
- package/src/__tests__/catalog-install-normalize.test.ts +9 -1
- package/src/__tests__/channel-policy.test.ts +1 -3
- package/src/__tests__/channel-readiness-discord.test.ts +9 -70
- package/src/__tests__/compactor-call-site-logging.test.ts +61 -0
- package/src/__tests__/config-loader-backfill.test.ts +69 -0
- package/src/__tests__/config-schema.test.ts +2 -3
- package/src/__tests__/conversation-agent-loop.test.ts +238 -0
- package/src/__tests__/conversation-attachments.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +44 -0
- package/src/__tests__/conversation-evictor.test.ts +19 -0
- package/src/__tests__/conversation-fork-referential.test.ts +73 -63
- package/src/__tests__/conversation-fork-retrospective.test.ts +99 -74
- package/src/__tests__/conversation-runtime-assembly.test.ts +23 -0
- package/src/__tests__/conversation-slash.test.ts +9 -0
- package/src/__tests__/conversation-title-service.test.ts +97 -0
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +16 -0
- package/src/__tests__/credential-broker-server-use.test.ts +0 -218
- package/src/__tests__/credential-broker.test.ts +43 -420
- package/src/__tests__/credential-record-write-through.test.ts +78 -0
- package/src/__tests__/credential-routes.test.ts +17 -1
- package/src/__tests__/credential-security-invariants.test.ts +8 -9
- package/src/__tests__/default-profile-catalog-fallback.test.ts +289 -0
- package/src/__tests__/delete-propagation.test.ts +92 -2
- package/src/__tests__/discord-callback-round-trip.test.ts +93 -0
- package/src/__tests__/discord-channel-config.test.ts +237 -0
- package/src/__tests__/edit-propagation.test.ts +42 -2
- package/src/__tests__/evict-conversations-for-reload.test.ts +51 -1
- package/src/__tests__/fallback-breaker.test.ts +1335 -0
- package/src/__tests__/gateway-threshold-reader-mock.ts +6 -0
- package/src/__tests__/guardian-card-withdrawal.test.ts +30 -0
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -2
- package/src/__tests__/image-conversion.test.ts +38 -4
- package/src/__tests__/install-skill-routing.test.ts +25 -0
- package/src/__tests__/llm-catalog-parity.test.ts +4 -0
- package/src/__tests__/llm-resolver.test.ts +74 -0
- package/src/__tests__/llm-schema-fallback-profile.test.ts +359 -0
- package/src/__tests__/managed-fallback-dispatch.test.ts +430 -0
- package/src/__tests__/managed-profile-guard.test.ts +28 -0
- package/src/__tests__/managed-speech-defaults.test.ts +196 -1
- package/src/__tests__/mcp-cli.test.ts +23 -1
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +43 -34
- package/src/__tests__/media-stream-stt-session.test.ts +2 -0
- package/src/__tests__/notification-decision-fallback.test.ts +106 -0
- package/src/__tests__/notification-discord-adapter.test.ts +162 -0
- package/src/__tests__/oauth-connect-orchestrator.test.ts +95 -0
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +6 -8
- package/src/__tests__/persist-unsendable-image.test.ts +3 -3
- package/src/__tests__/platform.test.ts +19 -1
- package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
- package/src/__tests__/provider-catalog-visibility.test.ts +17 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +8 -1
- package/src/__tests__/provider-usage-tracking.test.ts +56 -0
- package/src/__tests__/require-fresh-approval.test.ts +5 -2
- package/src/__tests__/retry-fallback-escalation.test.ts +1093 -0
- package/src/__tests__/schedule-retry.test.ts +183 -4
- package/src/__tests__/scheduler-reuse-conversation.test.ts +6 -6
- package/src/__tests__/secret-routes-platform-proxy.test.ts +55 -0
- package/src/__tests__/skill-load-tool.test.ts +34 -0
- package/src/__tests__/skill-tool-factory.test.ts +6 -1
- package/src/__tests__/skill-tool-manifest.test.ts +20 -0
- package/src/__tests__/skills.test.ts +25 -0
- package/src/__tests__/subagent-manager-notify.test.ts +38 -0
- package/src/__tests__/terminal-tools.test.ts +2 -0
- package/src/__tests__/tool-approval-handler.test.ts +146 -2
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +108 -2
- package/src/__tests__/tool-audit.test.ts +111 -40
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +228 -18
- package/src/__tests__/ui-shape-teaching.test.ts +1 -1
- package/src/__tests__/verification-control-plane-policy.test.ts +6 -2
- package/src/__tests__/voice-config-update.test.ts +43 -0
- package/src/__tests__/workspace-migration-147-rename-colliding-backup-profile-names.test.ts +480 -0
- package/src/__tests__/workspace-migration-148-strip-unsupported-fallback-profiles.test.ts +167 -0
- package/src/__tests__/workspace-migration-149-repoint-backup-profile-selections.test.ts +144 -0
- package/src/agent/loop.ts +31 -2
- package/src/approvals/guardian-card-withdrawal.ts +22 -0
- package/src/avatar/__tests__/ensure-raster.test.ts +291 -0
- package/src/avatar/avatar-manifest.ts +48 -100
- package/src/avatar/avatar-store.ts +7 -7
- package/src/avatar/ensure-raster.ts +146 -0
- package/src/avatar/resvg-lazy.test.ts +0 -1
- package/src/avatar/traits-png-sync.ts +9 -10
- package/src/background-wake/wake-intent-hooks.test.ts +0 -1
- package/src/backup/__tests__/paths.test.ts +6 -2
- package/src/backup/paths.ts +6 -4
- package/src/bundler/app-compiler.ts +2 -2
- package/src/bundler/compiler-tools.test.ts +36 -0
- package/src/bundler/compiler-tools.ts +54 -18
- package/src/calls/__tests__/telephony-synthesis-language.test.ts +81 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +57 -0
- package/src/calls/media-stream-stt-session.ts +2 -1
- package/src/calls/telephony-synthesis-language.ts +13 -5
- package/src/calls/voice-session-bridge.ts +19 -4
- package/src/channels/config.ts +23 -12
- package/src/cli/AGENTS.md +3 -1
- package/src/cli/commands/backup.help.ts +10 -13
- package/src/cli/commands/contacts.help.ts +16 -4
- package/src/cli/commands/contacts.ts +5 -1
- package/src/cli/commands/credentials.help.ts +1 -1
- package/src/cli/commands/inference-providers.ts +18 -53
- package/src/cli/commands/mcp.help.ts +6 -5
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/memory/memory-retrospective.ts +1 -0
- package/src/cli/commands/stt.help.ts +2 -2
- package/src/config/__tests__/backup-schema.test.ts +1 -1
- package/src/config/__tests__/default-profile-catalog.test.ts +83 -2
- package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +1 -1
- package/src/config/__tests__/memory-retrospective-schema.test.ts +12 -15
- package/src/config/bundled-skills/computer-use/SKILL.md +6 -6
- package/src/config/bundled-skills/computer-use/TOOLS.json +9 -6
- package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +3 -1
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +1 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +16 -15
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +2 -1
- package/src/config/bundled-skills/settings/TOOLS.json +11 -5
- package/src/config/bundled-skills/settings/tools/open-system-settings.test.ts +85 -0
- package/src/config/bundled-skills/settings/tools/open-system-settings.ts +33 -8
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +54 -3
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +1 -1
- package/src/config/default-profile-catalog.ts +195 -10
- package/src/config/default-profile-names.ts +41 -0
- package/src/config/feature-flag-registry.json +34 -2
- package/src/config/inference-profile-validation.ts +11 -2
- package/src/config/llm-resolver.ts +25 -0
- package/src/config/loader.ts +3 -0
- package/src/config/managed-speech-defaults.ts +160 -10
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/live-voice.test.ts +7 -6
- package/src/config/schemas/__tests__/stt.test.ts +94 -0
- package/src/config/schemas/backup.ts +1 -1
- package/src/config/schemas/channels.ts +32 -0
- package/src/config/schemas/live-voice.ts +13 -4
- package/src/config/schemas/llm.ts +263 -15
- package/src/config/schemas/mcp.ts +10 -2
- package/src/config/schemas/memory-retrospective.ts +1 -19
- package/src/config/schemas/services.ts +24 -0
- package/src/config/schemas/stt.ts +128 -5
- package/src/config/seed-inference-profiles.ts +32 -3
- package/src/config/skill-state.ts +5 -0
- package/src/config/skills.ts +21 -0
- package/src/contacts/notify-contacts-changed.ts +5 -2
- package/src/context/compactor.ts +74 -1
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +138 -1
- package/src/daemon/config-watcher.ts +2 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +27 -0
- package/src/daemon/conversation-agent-loop.ts +86 -10
- package/src/daemon/conversation-error.ts +25 -0
- package/src/daemon/conversation-evictor.ts +2 -4
- package/src/daemon/conversation-runtime-assembly.ts +19 -7
- package/src/daemon/conversation-skill-tools.ts +5 -1
- package/src/daemon/conversation-slash.ts +2 -2
- package/src/daemon/conversation-store.ts +8 -1
- package/src/daemon/conversation-tool-setup.ts +37 -0
- package/src/daemon/conversation.ts +0 -4
- package/src/daemon/handlers/channel-config-result.ts +30 -0
- package/src/daemon/handlers/config-discord-channel.ts +301 -0
- package/src/daemon/handlers/config-ingress.ts +9 -0
- package/src/daemon/handlers/config-slack-channel.ts +13 -15
- package/src/daemon/handlers/config-telegram.ts +2 -5
- package/src/daemon/handlers/conversations.ts +0 -2
- package/src/daemon/handlers/skills.ts +25 -7
- package/src/daemon/lifecycle.ts +3 -1
- package/src/daemon/message-types/messages.ts +0 -2
- package/src/daemon/tool-setup-types.ts +5 -3
- package/src/daemon/trust-context-types.test.ts +33 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +58 -4
- package/src/live-voice/__tests__/live-voice-managed-speech-fallback.test.ts +8 -3
- package/src/live-voice/__tests__/live-voice-session-manager.test.ts +73 -11
- package/src/live-voice/__tests__/live-voice-session-preflight.test.ts +148 -2
- package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +64 -0
- package/src/live-voice/__tests__/live-voice-stt.test.ts +3 -0
- package/src/live-voice/__tests__/live-voice-text-turn.test.ts +391 -0
- package/src/live-voice/__tests__/protocol.test.ts +224 -0
- package/src/live-voice/live-voice-credential-preflight.ts +5 -1
- package/src/live-voice/live-voice-session-manager.ts +39 -2
- package/src/live-voice/live-voice-session.ts +224 -9
- package/src/live-voice/protocol.ts +187 -1
- package/src/messaging/providers/discord/api.ts +2 -1
- package/src/messaging/providers/discord/withdraw.ts +62 -0
- package/src/messaging/read-provider-metadata.ts +32 -0
- package/src/monitoring/__tests__/file-descriptors.test.ts +32 -0
- package/src/monitoring/__tests__/process-memory.test.ts +34 -1
- package/src/monitoring/db-integrity-sample.ts +1 -0
- package/src/monitoring/file-descriptors.ts +39 -6
- package/src/monitoring/process-memory.ts +37 -5
- package/src/monitoring/resource-sampler.ts +25 -8
- package/src/notifications/__tests__/activity-failed-dedupe.test.ts +161 -0
- package/src/notifications/__tests__/background-failure-signal.test.ts +107 -0
- package/src/notifications/__tests__/connected-channels.test.ts +12 -0
- package/src/notifications/__tests__/copy-composer.test.ts +83 -0
- package/src/notifications/__tests__/destination-resolver.test.ts +38 -0
- package/src/notifications/activity-failed-dedupe.ts +95 -0
- package/src/notifications/adapters/discord.ts +132 -0
- package/src/notifications/adapters/shared.ts +16 -0
- package/src/notifications/adapters/slack.ts +6 -1
- package/src/notifications/adapters/telegram.ts +5 -8
- package/src/notifications/approval-card-data.ts +94 -1
- package/src/notifications/background-failure-signal.ts +116 -0
- package/src/notifications/broadcaster.ts +3 -9
- package/src/notifications/copy-composer.ts +41 -8
- package/src/notifications/decision-engine.ts +55 -0
- package/src/notifications/destination-resolver.ts +45 -5
- package/src/notifications/emit-signal.ts +19 -5
- package/src/notifications/guardian-question-mode.ts +57 -0
- package/src/notifications/types.ts +2 -8
- package/src/oauth/__tests__/seed-providers-managed.test.ts +18 -0
- package/src/oauth/connect-orchestrator.ts +16 -0
- package/src/oauth/seed-providers.ts +262 -0
- package/src/permissions/checker.test.ts +21 -0
- package/src/permissions/checker.ts +6 -0
- package/src/permissions/gateway-threshold-reader.test.ts +184 -0
- package/src/permissions/gateway-threshold-reader.ts +144 -10
- package/src/permissions/prompter.ts +0 -6
- package/src/permissions/types.ts +5 -4
- package/src/persistence/__tests__/bulk-write-gate.test.ts +0 -38
- package/src/persistence/bulk-write-gate.ts +9 -10
- package/src/persistence/conversation-crud.ts +88 -134
- package/src/persistence/conversation-title-service.ts +43 -29
- package/src/persistence/embeddings/embedding-local.ts +14 -85
- package/src/persistence/embeddings/embedding-runtime-manager.ts +1 -0
- package/src/persistence/lifecycle-events-store.test.ts +50 -0
- package/src/persistence/lifecycle-events-store.ts +75 -5
- package/src/platform/platform-patch-queue.test.ts +368 -0
- package/src/platform/platform-patch-queue.ts +190 -0
- package/src/platform/sync-avatar.test.ts +527 -0
- package/src/platform/sync-avatar.ts +209 -0
- package/src/platform/sync-identity.test.ts +75 -0
- package/src/platform/sync-identity.ts +17 -110
- package/src/plugin-api/model-profiles.test.ts +2 -2
- package/src/plugin-api/model-profiles.ts +7 -7
- package/src/plugin-api/transcription-session.ts +1 -1
- package/src/plugins/defaults/compaction/window-manager.ts +4 -0
- package/src/plugins/defaults/image-recovery/recover.ts +2 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +133 -34
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +21 -31
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +13 -9
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +31 -4
- package/src/plugins/defaults/memory/memory-retrospective-constants.ts +0 -11
- package/src/plugins/defaults/memory/memory-retrospective-fork-boundary.ts +27 -13
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +183 -113
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +13 -13
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +2 -2
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +1 -1
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +4 -2
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +9 -42
- package/src/plugins/defaults/memory/v2/rerank-local.ts +1 -0
- package/src/plugins/defaults/memory/worker.ts +7 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
- package/src/providers/__tests__/retry-callsite.test.ts +55 -0
- package/src/providers/__tests__/vellum-connection-routing.test.ts +21 -3
- package/src/providers/connection-resolution.ts +2 -17
- package/src/providers/fallback-breaker.ts +389 -0
- package/src/providers/inference/__tests__/adapter-factory-opencode.test.ts +92 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +16 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +12 -0
- package/src/providers/inference/adapter-factory.ts +246 -3
- package/src/providers/inference/auth.ts +15 -14
- package/src/providers/inference/chatgpt-subscription-auth.ts +70 -0
- package/src/providers/inference/chatgpt-subscription-credentials.ts +84 -0
- package/src/providers/inference/resolve-auth.ts +1 -1
- package/src/providers/media-resolve.ts +2 -3
- package/src/providers/model-catalog.ts +100 -0
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +22 -0
- package/src/providers/openai/__tests__/chat-template-flatten.test.ts +198 -0
- package/src/providers/openai/api-error-normalization.ts +13 -0
- package/src/providers/openai/chat-completions-provider.ts +84 -0
- package/src/providers/opencode/client.test.ts +150 -0
- package/src/providers/opencode/client.ts +61 -0
- package/src/providers/platform-proxy/constants.ts +5 -0
- package/src/providers/provider-resolvability.ts +20 -0
- package/src/providers/provider-secret-catalog.ts +3 -2
- package/src/providers/retry.ts +956 -68
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +42 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +136 -0
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +120 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +316 -12
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +83 -3
- package/src/providers/speech-to-text/deepgram-flux-realtime.ts +132 -4
- package/src/providers/speech-to-text/provider-catalog.ts +191 -3
- package/src/providers/speech-to-text/resolve.ts +132 -24
- package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +296 -0
- package/src/providers/types.ts +39 -0
- package/src/providers/usage-tracking.ts +12 -1
- package/src/providers/vellum/client.ts +29 -0
- package/src/providers/vellum-model-routing.test.ts +2 -0
- package/src/providers/vellum-model-routing.ts +3 -3
- package/src/runtime/AGENTS.md +1 -2
- package/src/runtime/__tests__/agent-wake.test.ts +105 -3
- package/src/runtime/__tests__/background-job-runner.test.ts +89 -3
- package/src/runtime/agent-wake.ts +44 -5
- package/src/runtime/background-job-runner.ts +68 -50
- package/src/runtime/channel-readiness-service.ts +0 -39
- package/src/runtime/http-server.ts +12 -4
- package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +18 -18
- package/src/runtime/routes/__tests__/backup-routes.test.ts +9 -5
- package/src/runtime/routes/__tests__/chatgpt-subscription-auth-routes.test.ts +372 -0
- package/src/runtime/routes/__tests__/contact-routes.test.ts +7 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +96 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +37 -0
- package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +2 -1
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +87 -0
- package/src/runtime/routes/acp-claude-auth-routes.ts +25 -54
- package/src/runtime/routes/approval-routes.ts +0 -12
- package/src/runtime/routes/avatar-routes.ts +17 -30
- package/src/runtime/routes/backup-routes.ts +2 -2
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +296 -113
- package/src/runtime/routes/contact-prompt-routes.ts +65 -6
- package/src/runtime/routes/contact-routes.ts +20 -15
- package/src/runtime/routes/conversation-query-routes.ts +98 -10
- package/src/runtime/routes/conversation-routes.ts +2 -2
- package/src/runtime/routes/credential-routes.ts +2 -1
- package/src/runtime/routes/guardian-approval-interception.ts +12 -9
- package/src/runtime/routes/inbound-message-handler.ts +47 -48
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +18 -13
- package/src/runtime/routes/inbound-stages/admission-policy.ts +2 -2
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +20 -2
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +9 -8
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -1
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +2 -2
- package/src/runtime/routes/inference-profiles-routes.ts +12 -7
- package/src/runtime/routes/inference-provider-connection-routes.ts +2 -2
- package/src/runtime/routes/inference-send-routes.ts +2 -2
- package/src/runtime/routes/ingress-status-routes.ts +2 -2
- package/src/runtime/routes/integrations/discord.ts +119 -0
- package/src/runtime/routes/llm-call-sites-routes.ts +2 -2
- package/src/runtime/routes/mcp-auth-routes.ts +12 -6
- package/src/runtime/routes/migration-routes.ts +3 -1
- package/src/runtime/routes/oauth-pending-flows.ts +136 -0
- package/src/runtime/routes/secret-routes.ts +7 -0
- package/src/runtime/routes/stt-routes.ts +38 -6
- package/src/runtime/routes/wake-conversation-routes.ts +8 -1
- package/src/runtime/routes/watch-routes.ts +9 -5
- package/src/runtime/sync/resource-sync-events.ts +2 -0
- package/src/runtime/trust-verdict-consumer.ts +3 -3
- package/src/schedule/__tests__/worker-feature-flags.test.ts +58 -0
- package/src/schedule/retry-policy.ts +8 -8
- package/src/schedule/run-script.ts +14 -23
- package/src/schedule/schedule-recovery.ts +0 -1
- package/src/schedule/scheduler.ts +89 -51
- package/src/schedule/worker.ts +57 -0
- package/src/security/__tests__/openai-device-auth.test.ts +355 -0
- package/src/security/ces-rpc-record-backend.ts +123 -0
- package/src/security/oauth2.ts +7 -0
- package/src/security/openai-device-auth.ts +364 -0
- package/src/security/secure-keys.ts +15 -0
- package/src/skills/available-skills.ts +10 -1
- package/src/skills/catalog-install.ts +19 -1
- package/src/skills/inline-command-runner.ts +6 -2
- package/src/skills/platform-compatibility.test.ts +49 -0
- package/src/skills/platform-compatibility.ts +65 -0
- package/src/skills/tool-manifest.ts +20 -0
- package/src/stt/__tests__/roles.test.ts +152 -0
- package/src/stt/daemon-batch-transcriber.ts +4 -1
- package/src/stt/roles.ts +180 -0
- package/src/stt/stt-stream-session.ts +3 -2
- package/src/stt/types.ts +33 -3
- package/src/subagent/manager.ts +12 -0
- package/src/telemetry/__tests__/live-voice-funnel.test.ts +34 -0
- package/src/telemetry/live-voice-funnel.ts +12 -2
- package/src/telemetry/telemetry-wire.generated.ts +6 -0
- package/src/telemetry/tool-audit.ts +83 -30
- package/src/telemetry/tool-executed-events-store.test.ts +0 -1
- package/src/telemetry/turn-outcome.ts +75 -4
- package/src/tools/client-os.ts +115 -0
- package/src/tools/computer-use/definitions.ts +11 -7
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -2
- package/src/tools/credentials/broker-types.ts +0 -71
- package/src/tools/credentials/broker.ts +6 -201
- package/src/tools/credentials/metadata-store.ts +65 -16
- package/src/tools/credentials/store.ts +2 -0
- package/src/tools/executor.ts +2 -8
- package/src/tools/host-shell.test.ts +75 -0
- package/src/tools/host-shell.ts +24 -0
- package/src/tools/host-terminal/host-shell.ts +15 -44
- package/src/tools/mcp/mcp-tool-factory.ts +44 -9
- package/src/tools/permission-checker.ts +73 -21
- package/src/tools/policy-context.ts +1 -0
- package/src/tools/shared/filesystem/image-read.ts +1 -1
- package/src/tools/skills/find-similar-skills.ts +10 -2
- package/src/tools/skills/load.ts +41 -6
- package/src/tools/skills/sandbox-runner.ts +8 -16
- package/src/tools/skills/skill-tool-factory.ts +1 -0
- package/src/tools/terminal/__tests__/safe-env.test.ts +64 -1
- package/src/tools/terminal/safe-env.ts +39 -9
- package/src/tools/terminal/shell.ts +12 -31
- package/src/tools/tool-approval-handler.ts +64 -10
- package/src/tools/tool-types.ts +10 -0
- package/src/tools/types.ts +19 -3
- package/src/tools/ui-surface/surface-shape-docs.ts +4 -3
- package/src/tools/workflows/manage-workflows.ts +2 -2
- package/src/tools/workflows/run-workflow.test.ts +2 -1
- package/src/util/__tests__/file-use.test.ts +61 -0
- package/src/util/__tests__/process-table.test.ts +66 -0
- package/src/util/__tests__/process-tree.test.ts +46 -0
- package/src/util/errors.ts +1 -0
- package/src/util/file-use.ts +91 -0
- package/src/util/host-process.test.ts +17 -0
- package/src/util/host-process.ts +75 -0
- package/src/util/image-conversion.ts +39 -7
- package/src/util/platform.ts +32 -13
- package/src/util/process-table.ts +249 -0
- package/src/util/process-tree.ts +85 -129
- package/src/util/provider-error-patterns.ts +20 -0
- package/src/util/spawn.ts +9 -2
- package/src/watch/__tests__/watch-retro.test.ts +48 -1
- package/src/watch/watch-retro.ts +46 -4
- package/src/workspace/git-service.ts +4 -14
- package/src/workspace/migrations/147-rename-colliding-backup-profile-names.ts +360 -0
- package/src/workspace/migrations/148-strip-unsupported-fallback-profiles.ts +117 -0
- package/src/workspace/migrations/149-repoint-backup-profile-selections.ts +204 -0
- package/src/workspace/migrations/150-stt-flux-provider-to-model-family.ts +139 -0
- package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +160 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/permission-types.test.ts +0 -17
- package/src/channels/gateway-discord-admission.ts +0 -32
- package/src/persistence/fork-message-copy.ts +0 -239
- package/src/plugins/defaults/memory/__tests__/fork-message-copy.test.ts +0 -221
- package/src/security/__tests__/oauth2-device-code.test.ts +0 -431
- package/src/security/oauth2-device-code.ts +0 -312
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the ChatGPT subscription auth routes.
|
|
3
|
+
*
|
|
4
|
+
* The device-code path is driven by mocking `openai-device-auth` so the route's
|
|
5
|
+
* own contract is under test: `device-auth` returns the user code immediately
|
|
6
|
+
* and the status route reports `pending` until the background poll settles,
|
|
7
|
+
* then `connected` or `error` with the provider's error code. The credential +
|
|
8
|
+
* connection store is mocked so no CES or database write is attempted.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
OAuth2FlowResult,
|
|
15
|
+
OAuth2TokenResult,
|
|
16
|
+
} from "../../../security/oauth2.js";
|
|
17
|
+
import type { DeviceAuthRequest } from "../../../security/openai-device-auth.js";
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Mocks, wired BEFORE importing the module under test.
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
const actualDeviceAuth =
|
|
24
|
+
await import("../../../security/openai-device-auth.js");
|
|
25
|
+
const { DeviceAuthError } = actualDeviceAuth;
|
|
26
|
+
|
|
27
|
+
const DEVICE_REQUEST: DeviceAuthRequest = {
|
|
28
|
+
deviceAuthId: "deviceauth_abc123",
|
|
29
|
+
userCode: "AMOO-SIISY",
|
|
30
|
+
verificationUrl: "https://auth.openai.com/codex/device",
|
|
31
|
+
expiresAt: "2099-01-01T00:00:00Z",
|
|
32
|
+
intervalSeconds: 5,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Every mint gets its own id, as OpenAI's do, so one flow's background poll can
|
|
36
|
+
// never settle a key another flow owns.
|
|
37
|
+
let mintCount = 0;
|
|
38
|
+
let lastMinted: DeviceAuthRequest = DEVICE_REQUEST;
|
|
39
|
+
|
|
40
|
+
const requestDeviceCodeMock = mock(async () => {
|
|
41
|
+
mintCount++;
|
|
42
|
+
lastMinted = {
|
|
43
|
+
...DEVICE_REQUEST,
|
|
44
|
+
deviceAuthId: `${DEVICE_REQUEST.deviceAuthId}_${mintCount}`,
|
|
45
|
+
};
|
|
46
|
+
return lastMinted;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/** Resolver handles for the in-flight poll each test drives by hand. */
|
|
50
|
+
let resolveDeviceAuth: (result: OAuth2FlowResult) => void = () => {};
|
|
51
|
+
let rejectDeviceAuth: (err: unknown) => void = () => {};
|
|
52
|
+
/** Every signal the route handed the poll, in mint order. */
|
|
53
|
+
const pollSignals: AbortSignal[] = [];
|
|
54
|
+
/**
|
|
55
|
+
* Whether an abort settles the mocked poll. Off for the races where the poll
|
|
56
|
+
* has already returned an authorization code and the exchange is mid-flight,
|
|
57
|
+
* which is exactly the window the route has to guard on its own.
|
|
58
|
+
*/
|
|
59
|
+
let rejectPollOnAbort = true;
|
|
60
|
+
|
|
61
|
+
const completeDeviceAuthMock = mock(
|
|
62
|
+
(
|
|
63
|
+
_config: unknown,
|
|
64
|
+
_request: DeviceAuthRequest,
|
|
65
|
+
options: { signal?: AbortSignal } = {},
|
|
66
|
+
) =>
|
|
67
|
+
new Promise<OAuth2FlowResult>((resolve, reject) => {
|
|
68
|
+
resolveDeviceAuth = resolve;
|
|
69
|
+
rejectDeviceAuth = reject;
|
|
70
|
+
if (options.signal) {
|
|
71
|
+
pollSignals.push(options.signal);
|
|
72
|
+
// The real poll throws once the signal fires; mirroring that keeps the
|
|
73
|
+
// route's own settle path under test.
|
|
74
|
+
options.signal.addEventListener("abort", () => {
|
|
75
|
+
if (!rejectPollOnAbort) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
reject(
|
|
79
|
+
new DeviceAuthError(
|
|
80
|
+
"Device authorization was cancelled.",
|
|
81
|
+
"aborted",
|
|
82
|
+
),
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
mock.module("../../../security/openai-device-auth.js", () => ({
|
|
90
|
+
...actualDeviceAuth,
|
|
91
|
+
requestDeviceCode: requestDeviceCodeMock,
|
|
92
|
+
completeDeviceAuth: completeDeviceAuthMock,
|
|
93
|
+
}));
|
|
94
|
+
|
|
95
|
+
const actualChatgptAuth =
|
|
96
|
+
await import("../../../providers/inference/chatgpt-subscription-auth.js");
|
|
97
|
+
/** When set, the store blocks on it, so a test can interleave a cancel. */
|
|
98
|
+
let storeGate: Promise<void> | null = null;
|
|
99
|
+
const storeChatgptSubscriptionTokensMock = mock(
|
|
100
|
+
async (_tokens: OAuth2TokenResult) => {
|
|
101
|
+
if (storeGate) {
|
|
102
|
+
await storeGate;
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
mock.module(
|
|
107
|
+
"../../../providers/inference/chatgpt-subscription-auth.js",
|
|
108
|
+
() => ({
|
|
109
|
+
...actualChatgptAuth,
|
|
110
|
+
storeChatgptSubscriptionTokens: storeChatgptSubscriptionTokensMock,
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const { ROUTES } = await import("../chatgpt-subscription-auth-routes.js");
|
|
115
|
+
|
|
116
|
+
const startDeviceAuthHandler = ROUTES.find(
|
|
117
|
+
(r) => r.operationId === "inference_chatgpt_subscription_device_auth",
|
|
118
|
+
)!.handler;
|
|
119
|
+
const deviceAuthStatusHandler = ROUTES.find(
|
|
120
|
+
(r) => r.operationId === "inference_chatgpt_subscription_device_auth_status",
|
|
121
|
+
)!.handler;
|
|
122
|
+
const cancelDeviceAuthHandler = ROUTES.find(
|
|
123
|
+
(r) => r.operationId === "inference_chatgpt_subscription_device_auth_cancel",
|
|
124
|
+
)!.handler;
|
|
125
|
+
const startAuthHandler = ROUTES.find(
|
|
126
|
+
(r) => r.operationId === "inference_chatgpt_subscription_auth",
|
|
127
|
+
)!.handler;
|
|
128
|
+
|
|
129
|
+
interface DeviceStartResult {
|
|
130
|
+
state: string;
|
|
131
|
+
user_code: string;
|
|
132
|
+
verification_url: string;
|
|
133
|
+
expires_at: string;
|
|
134
|
+
interval_seconds: number;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
interface StatusResult {
|
|
138
|
+
status: "pending" | "connected" | "error";
|
|
139
|
+
error?: string;
|
|
140
|
+
error_code?: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const TOKENS: OAuth2FlowResult = {
|
|
144
|
+
tokens: {
|
|
145
|
+
accessToken: "access-1",
|
|
146
|
+
refreshToken: "refresh-1",
|
|
147
|
+
expiresIn: 3600,
|
|
148
|
+
},
|
|
149
|
+
grantedScopes: ["openid"],
|
|
150
|
+
rawTokenResponse: {},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
async function startDeviceAuth(): Promise<DeviceStartResult> {
|
|
154
|
+
return (await startDeviceAuthHandler({})) as DeviceStartResult;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function readStatus(state: string): StatusResult {
|
|
158
|
+
return deviceAuthStatusHandler({ pathParams: { state } }) as StatusResult;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function cancelDeviceAuth(state: string): { cancelled: boolean } {
|
|
162
|
+
return cancelDeviceAuthHandler({ pathParams: { state } }) as {
|
|
163
|
+
cancelled: boolean;
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Let the background `.then`/`.catch` chain run before asserting. */
|
|
168
|
+
async function flush(): Promise<void> {
|
|
169
|
+
await Promise.resolve();
|
|
170
|
+
await Promise.resolve();
|
|
171
|
+
await Promise.resolve();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
describe("chatgpt subscription device auth routes", () => {
|
|
175
|
+
beforeEach(() => {
|
|
176
|
+
requestDeviceCodeMock.mockClear();
|
|
177
|
+
completeDeviceAuthMock.mockClear();
|
|
178
|
+
storeChatgptSubscriptionTokensMock.mockClear();
|
|
179
|
+
pollSignals.length = 0;
|
|
180
|
+
rejectPollOnAbort = true;
|
|
181
|
+
storeGate = null;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("returns the user code immediately and starts pending", async () => {
|
|
185
|
+
const result = await startDeviceAuth();
|
|
186
|
+
|
|
187
|
+
expect(result).toEqual({
|
|
188
|
+
state: lastMinted.deviceAuthId,
|
|
189
|
+
user_code: "AMOO-SIISY",
|
|
190
|
+
verification_url: "https://auth.openai.com/codex/device",
|
|
191
|
+
expires_at: "2099-01-01T00:00:00Z",
|
|
192
|
+
interval_seconds: 5,
|
|
193
|
+
});
|
|
194
|
+
expect(completeDeviceAuthMock).toHaveBeenCalledTimes(1);
|
|
195
|
+
expect(readStatus(result.state)).toEqual({ status: "pending" });
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("stores the tokens and flips to connected when the poll succeeds", async () => {
|
|
199
|
+
const result = await startDeviceAuth();
|
|
200
|
+
|
|
201
|
+
resolveDeviceAuth(TOKENS);
|
|
202
|
+
await flush();
|
|
203
|
+
|
|
204
|
+
expect(storeChatgptSubscriptionTokensMock).toHaveBeenCalledTimes(1);
|
|
205
|
+
expect(storeChatgptSubscriptionTokensMock.mock.calls[0]?.[0]).toEqual(
|
|
206
|
+
TOKENS.tokens,
|
|
207
|
+
);
|
|
208
|
+
expect(readStatus(result.state)).toEqual({ status: "connected" });
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("reports the provider error code when the poll fails", async () => {
|
|
212
|
+
const result = await startDeviceAuth();
|
|
213
|
+
|
|
214
|
+
rejectDeviceAuth(
|
|
215
|
+
new DeviceAuthError("Device auth is off.", "deviceauth_not_enabled"),
|
|
216
|
+
);
|
|
217
|
+
await flush();
|
|
218
|
+
|
|
219
|
+
expect(storeChatgptSubscriptionTokensMock).not.toHaveBeenCalled();
|
|
220
|
+
expect(readStatus(result.state)).toEqual({
|
|
221
|
+
status: "error",
|
|
222
|
+
error: "Device auth is off.",
|
|
223
|
+
error_code: "deviceauth_not_enabled",
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("falls back to a generic code for a non-device-auth failure", async () => {
|
|
228
|
+
const result = await startDeviceAuth();
|
|
229
|
+
|
|
230
|
+
rejectDeviceAuth(new Error("token exchange exploded"));
|
|
231
|
+
await flush();
|
|
232
|
+
|
|
233
|
+
expect(readStatus(result.state)).toEqual({
|
|
234
|
+
status: "error",
|
|
235
|
+
error: "token exchange exploded",
|
|
236
|
+
error_code: "device_auth_failed",
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("404s an unknown state", async () => {
|
|
241
|
+
expect(() => readStatus("deviceauth_missing")).toThrow(
|
|
242
|
+
/No active ChatGPT device sign-in/,
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe("chatgpt subscription device auth cancellation", () => {
|
|
248
|
+
beforeEach(() => {
|
|
249
|
+
requestDeviceCodeMock.mockClear();
|
|
250
|
+
completeDeviceAuthMock.mockClear();
|
|
251
|
+
storeChatgptSubscriptionTokensMock.mockClear();
|
|
252
|
+
pollSignals.length = 0;
|
|
253
|
+
rejectPollOnAbort = true;
|
|
254
|
+
storeGate = null;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("stops the poll and reports the flow aborted", async () => {
|
|
258
|
+
const result = await startDeviceAuth();
|
|
259
|
+
|
|
260
|
+
expect(cancelDeviceAuth(result.state)).toEqual({ cancelled: true });
|
|
261
|
+
|
|
262
|
+
expect(pollSignals[0]?.aborted).toBe(true);
|
|
263
|
+
const status = readStatus(result.state);
|
|
264
|
+
expect(status.status).toBe("error");
|
|
265
|
+
expect(status.error_code).toBe("aborted");
|
|
266
|
+
await flush();
|
|
267
|
+
expect(readStatus(result.state).error_code).toBe("aborted");
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("leaves a flow that already settled alone", async () => {
|
|
271
|
+
const result = await startDeviceAuth();
|
|
272
|
+
|
|
273
|
+
resolveDeviceAuth(TOKENS);
|
|
274
|
+
await flush();
|
|
275
|
+
|
|
276
|
+
expect(cancelDeviceAuth(result.state)).toEqual({ cancelled: false });
|
|
277
|
+
expect(readStatus(result.state)).toEqual({ status: "connected" });
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test("starting a flow aborts the one before it", async () => {
|
|
281
|
+
const first = await startDeviceAuth();
|
|
282
|
+
const second = await startDeviceAuth();
|
|
283
|
+
await flush();
|
|
284
|
+
|
|
285
|
+
expect(second.state).not.toBe(first.state);
|
|
286
|
+
expect(pollSignals[0]?.aborted).toBe(true);
|
|
287
|
+
expect(pollSignals[1]?.aborted).toBe(false);
|
|
288
|
+
|
|
289
|
+
const firstStatus = readStatus(first.state);
|
|
290
|
+
expect(firstStatus.status).toBe("error");
|
|
291
|
+
expect(firstStatus.error_code).toBe("aborted");
|
|
292
|
+
expect(readStatus(second.state)).toEqual({ status: "pending" });
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test("404s an unknown state", async () => {
|
|
296
|
+
expect(() => cancelDeviceAuth("deviceauth_missing")).toThrow(
|
|
297
|
+
/No active ChatGPT device sign-in/,
|
|
298
|
+
);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test("discards tokens from a flow cancelled during the exchange", async () => {
|
|
302
|
+
rejectPollOnAbort = false;
|
|
303
|
+
const result = await startDeviceAuth();
|
|
304
|
+
|
|
305
|
+
expect(cancelDeviceAuth(result.state)).toEqual({ cancelled: true });
|
|
306
|
+
resolveDeviceAuth(TOKENS);
|
|
307
|
+
await flush();
|
|
308
|
+
|
|
309
|
+
expect(storeChatgptSubscriptionTokensMock).not.toHaveBeenCalled();
|
|
310
|
+
const status = readStatus(result.state);
|
|
311
|
+
expect(status.status).toBe("error");
|
|
312
|
+
expect(status.error_code).toBe("aborted");
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test("leaves a flow cancelled mid-store marked aborted", async () => {
|
|
316
|
+
rejectPollOnAbort = false;
|
|
317
|
+
let releaseStore = () => {};
|
|
318
|
+
storeGate = new Promise<void>((resolve) => {
|
|
319
|
+
releaseStore = resolve;
|
|
320
|
+
});
|
|
321
|
+
const result = await startDeviceAuth();
|
|
322
|
+
|
|
323
|
+
resolveDeviceAuth(TOKENS);
|
|
324
|
+
await flush();
|
|
325
|
+
expect(storeChatgptSubscriptionTokensMock).toHaveBeenCalledTimes(1);
|
|
326
|
+
|
|
327
|
+
expect(cancelDeviceAuth(result.state)).toEqual({ cancelled: true });
|
|
328
|
+
releaseStore();
|
|
329
|
+
await flush();
|
|
330
|
+
|
|
331
|
+
const status = readStatus(result.state);
|
|
332
|
+
expect(status.status).toBe("error");
|
|
333
|
+
expect(status.error_code).toBe("aborted");
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("a superseded flow cannot store over the one that replaced it", async () => {
|
|
337
|
+
rejectPollOnAbort = false;
|
|
338
|
+
const first = await startDeviceAuth();
|
|
339
|
+
const resolveFirst = resolveDeviceAuth;
|
|
340
|
+
const second = await startDeviceAuth();
|
|
341
|
+
|
|
342
|
+
resolveFirst(TOKENS);
|
|
343
|
+
await flush();
|
|
344
|
+
|
|
345
|
+
expect(storeChatgptSubscriptionTokensMock).not.toHaveBeenCalled();
|
|
346
|
+
expect(readStatus(first.state).error_code).toBe("aborted");
|
|
347
|
+
expect(readStatus(second.state)).toEqual({ status: "pending" });
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
describe("chatgpt subscription paste flow", () => {
|
|
352
|
+
test("builds a PKCE authorize URL against OpenAI's client", async () => {
|
|
353
|
+
const result = (await startAuthHandler({})) as {
|
|
354
|
+
authorize_url: string;
|
|
355
|
+
state: string;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const url = new URL(result.authorize_url);
|
|
359
|
+
expect(url.origin + url.pathname).toBe(
|
|
360
|
+
"https://auth.openai.com/oauth/authorize",
|
|
361
|
+
);
|
|
362
|
+
expect(url.searchParams.get("client_id")).toBe(
|
|
363
|
+
"app_EMoamEEZ73f0CkXaXp7hrann",
|
|
364
|
+
);
|
|
365
|
+
expect(url.searchParams.get("code_challenge_method")).toBe("S256");
|
|
366
|
+
expect(url.searchParams.get("state")).toBe(result.state);
|
|
367
|
+
expect(url.searchParams.get("redirect_uri")).toBe(
|
|
368
|
+
"http://localhost:1455/auth/callback",
|
|
369
|
+
);
|
|
370
|
+
expect(url.searchParams.get("id_token_add_organizations")).toBe("true");
|
|
371
|
+
});
|
|
372
|
+
});
|
|
@@ -316,6 +316,7 @@ const nativeGatewayRead = {
|
|
|
316
316
|
contactType: "human",
|
|
317
317
|
lastInteraction: 9200,
|
|
318
318
|
interactionCount: 11,
|
|
319
|
+
autoApproveThreshold: "high",
|
|
319
320
|
createdAt: 1000,
|
|
320
321
|
updatedAt: 1500,
|
|
321
322
|
channels: [
|
|
@@ -376,6 +377,9 @@ describe("filtered/native contact reads: daemon filters, gateway hydrates teleme
|
|
|
376
377
|
// (4/4200/4100).
|
|
377
378
|
expect(contact.interactionCount).toBe(11);
|
|
378
379
|
expect(contact.lastInteraction).toBe(9200);
|
|
380
|
+
expect(
|
|
381
|
+
(contact as { autoApproveThreshold?: string | null }).autoApproveThreshold,
|
|
382
|
+
).toBe("high");
|
|
379
383
|
const channel = contact.channels[0] as Record<string, unknown>;
|
|
380
384
|
expect(channel.interactionCount).toBe(11);
|
|
381
385
|
expect(channel.lastSeenAt).toBe(9100);
|
|
@@ -404,6 +408,9 @@ describe("filtered/native contact reads: daemon filters, gateway hydrates teleme
|
|
|
404
408
|
const [contact] = result.contacts;
|
|
405
409
|
expect(contact.interactionCount).toBe(0);
|
|
406
410
|
expect(contact.lastInteraction).toBeNull();
|
|
411
|
+
expect(
|
|
412
|
+
(contact as { autoApproveThreshold?: string | null }).autoApproveThreshold,
|
|
413
|
+
).toBeNull();
|
|
407
414
|
const channel = contact.channels[0] as Record<string, unknown>;
|
|
408
415
|
expect(channel.interactionCount).toBe(0);
|
|
409
416
|
expect(channel.lastSeenAt).toBeNull();
|
|
@@ -50,6 +50,7 @@ mock.module("../../../persistence/embeddings/embedding-backend.js", () => ({
|
|
|
50
50
|
},
|
|
51
51
|
}));
|
|
52
52
|
|
|
53
|
+
import { BACKUP_PROFILE_KEYS } from "../../../config/default-profile-names.js";
|
|
53
54
|
import { getConfig, loadRawConfig } from "../../../config/loader.js";
|
|
54
55
|
import { LLMConfigBase } from "../../../config/schemas/llm.js";
|
|
55
56
|
import type { ConversationCreateType } from "../../../persistence/conversation-types.js";
|
|
@@ -836,6 +837,49 @@ describe("PUT /v1/config/llm/profiles/:name", () => {
|
|
|
836
837
|
seedRawConfig();
|
|
837
838
|
});
|
|
838
839
|
|
|
840
|
+
describe("fallbackProfile write protection", () => {
|
|
841
|
+
beforeEach(() => {
|
|
842
|
+
const llm = rawConfigFixture.llm as {
|
|
843
|
+
profiles: Record<string, Record<string, unknown>>;
|
|
844
|
+
};
|
|
845
|
+
llm.profiles.backup = {
|
|
846
|
+
provider: "anthropic",
|
|
847
|
+
model: "claude-sonnet-4-6",
|
|
848
|
+
};
|
|
849
|
+
seedRawConfig();
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
test("rejects a custom fallbackProfile without writing", async () => {
|
|
853
|
+
await expect(
|
|
854
|
+
replaceProfileRoute.handler({
|
|
855
|
+
pathParams: { name: "custom" },
|
|
856
|
+
body: {
|
|
857
|
+
provider: "anthropic",
|
|
858
|
+
model: "claude-sonnet-4-6",
|
|
859
|
+
fallbackProfile: "backup",
|
|
860
|
+
},
|
|
861
|
+
}),
|
|
862
|
+
).rejects.toThrow(/Automatic fallbacks are code-owned/);
|
|
863
|
+
expect(persistedProfile("custom").fallbackProfile).toBeUndefined();
|
|
864
|
+
expect(initializeProvidersCalls).toBe(0);
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
test("rejects a custom pointer at a managed backup name", async () => {
|
|
868
|
+
await expect(
|
|
869
|
+
replaceProfileRoute.handler({
|
|
870
|
+
pathParams: { name: "custom" },
|
|
871
|
+
body: {
|
|
872
|
+
provider: "anthropic",
|
|
873
|
+
model: "claude-sonnet-4-6",
|
|
874
|
+
fallbackProfile: "balanced-backup",
|
|
875
|
+
},
|
|
876
|
+
}),
|
|
877
|
+
).rejects.toThrow(/Automatic fallbacks are code-owned/);
|
|
878
|
+
expect(persistedProfile("custom").fallbackProfile).toBeUndefined();
|
|
879
|
+
expect(initializeProvidersCalls).toBe(0);
|
|
880
|
+
});
|
|
881
|
+
});
|
|
882
|
+
|
|
839
883
|
test("owns contextWindow maxInputTokens while preserving non-UI profile leaves", async () => {
|
|
840
884
|
const result = await replaceProfileRoute.handler({
|
|
841
885
|
pathParams: { name: "custom" },
|
|
@@ -1242,6 +1286,48 @@ describe("PUT /v1/config/llm/profiles/:name", () => {
|
|
|
1242
1286
|
});
|
|
1243
1287
|
});
|
|
1244
1288
|
|
|
1289
|
+
describe("PATCH /v1/config fallbackProfile write protection", () => {
|
|
1290
|
+
const patchRoute = ROUTES.find((r) => r.operationId === "config_patch")!;
|
|
1291
|
+
|
|
1292
|
+
beforeEach(() => {
|
|
1293
|
+
initializeProvidersCalls = 0;
|
|
1294
|
+
clearEmbeddingBackendCacheCalls = 0;
|
|
1295
|
+
rawConfigFixture = {
|
|
1296
|
+
llm: {
|
|
1297
|
+
profiles: {
|
|
1298
|
+
custom: { provider: "anthropic", model: "claude-sonnet-4-6" },
|
|
1299
|
+
backup: { provider: "anthropic", model: "claude-sonnet-4-6" },
|
|
1300
|
+
},
|
|
1301
|
+
},
|
|
1302
|
+
};
|
|
1303
|
+
seedRawConfig();
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
test("rejects a custom fallbackProfile without writing", async () => {
|
|
1307
|
+
await expect(
|
|
1308
|
+
patchRoute.handler({
|
|
1309
|
+
body: { llm: { profiles: { custom: { fallbackProfile: "backup" } } } },
|
|
1310
|
+
}),
|
|
1311
|
+
).rejects.toThrow(/Automatic fallbacks are code-owned/);
|
|
1312
|
+
expect(persistedProfile("custom").fallbackProfile).toBeUndefined();
|
|
1313
|
+
expect(initializeProvidersCalls).toBe(0);
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1316
|
+
test("rejects a custom pointer at a managed backup name", async () => {
|
|
1317
|
+
await expect(
|
|
1318
|
+
patchRoute.handler({
|
|
1319
|
+
body: {
|
|
1320
|
+
llm: {
|
|
1321
|
+
profiles: { custom: { fallbackProfile: "balanced-backup" } },
|
|
1322
|
+
},
|
|
1323
|
+
},
|
|
1324
|
+
}),
|
|
1325
|
+
).rejects.toThrow(/Automatic fallbacks are code-owned/);
|
|
1326
|
+
expect(persistedProfile("custom").fallbackProfile).toBeUndefined();
|
|
1327
|
+
expect(initializeProvidersCalls).toBe(0);
|
|
1328
|
+
});
|
|
1329
|
+
});
|
|
1330
|
+
|
|
1245
1331
|
describe("custom profile write normalization (complete overrides)", () => {
|
|
1246
1332
|
const configPatchRoute = ROUTES.find(
|
|
1247
1333
|
(r) => r.operationId === "config_patch",
|
|
@@ -1696,6 +1782,16 @@ describe("config invariant flag enrichment", () => {
|
|
|
1696
1782
|
}
|
|
1697
1783
|
});
|
|
1698
1784
|
|
|
1785
|
+
test("GET /v1/config hides managed backups from the picker catalog", async () => {
|
|
1786
|
+
const body = await configGetRoute.handler({});
|
|
1787
|
+
const profiles = wireProfiles(body);
|
|
1788
|
+
|
|
1789
|
+
for (const name of BACKUP_PROFILE_KEYS) {
|
|
1790
|
+
expect(profiles).not.toHaveProperty(name);
|
|
1791
|
+
}
|
|
1792
|
+
expect(profiles.balanced).toBeDefined();
|
|
1793
|
+
});
|
|
1794
|
+
|
|
1699
1795
|
test("PATCH /v1/config stamps the flag on the response but never persists it", async () => {
|
|
1700
1796
|
const body = await configPatchRoute.handler({
|
|
1701
1797
|
body: { memory: { enabled: true } },
|
|
@@ -64,6 +64,7 @@ mock.module("../../sync/resource-sync-events.js", () => ({
|
|
|
64
64
|
// ── Real imports (after mocks) ────────────────────────────────────────────────
|
|
65
65
|
|
|
66
66
|
import { setConfig } from "../../../__tests__/helpers/set-config.js";
|
|
67
|
+
import { BACKUP_PROFILE_KEYS } from "../../../config/default-profile-names.js";
|
|
67
68
|
import { loadRawConfig } from "../../../config/loader.js";
|
|
68
69
|
import { getDb } from "../../../persistence/db-connection.js";
|
|
69
70
|
import { initializeDb } from "../../../persistence/db-init.js";
|
|
@@ -625,6 +626,11 @@ describe("collectProfileReferences", () => {
|
|
|
625
626
|
source: "user",
|
|
626
627
|
mix: [{ profile: "my-fast", weight: 1 }],
|
|
627
628
|
},
|
|
629
|
+
"my-primary": {
|
|
630
|
+
source: "user",
|
|
631
|
+
provider: "anthropic",
|
|
632
|
+
fallbackProfile: "my-fast",
|
|
633
|
+
},
|
|
628
634
|
},
|
|
629
635
|
};
|
|
630
636
|
expect(collectProfileReferences(llm, "my-fast").sort()).toEqual(
|
|
@@ -633,6 +639,7 @@ describe("collectProfileReferences", () => {
|
|
|
633
639
|
"llm.advisorProfile",
|
|
634
640
|
"llm.callSites.memoryExtraction",
|
|
635
641
|
"llm.profiles.my-mix.mix",
|
|
642
|
+
"llm.profiles.my-primary.fallbackProfile",
|
|
636
643
|
].sort(),
|
|
637
644
|
);
|
|
638
645
|
});
|
|
@@ -676,6 +683,22 @@ describe("DELETE inference/profiles/:name reference guard", () => {
|
|
|
676
683
|
).rejects.toThrow(/llm\.profiles\.my-mix\.mix/);
|
|
677
684
|
});
|
|
678
685
|
|
|
686
|
+
test("rejects deletion referenced by another profile's fallbackProfile", async () => {
|
|
687
|
+
setConfig("llm", {
|
|
688
|
+
profiles: {
|
|
689
|
+
"my-fast": { source: "user", provider: "anthropic" },
|
|
690
|
+
"my-primary": {
|
|
691
|
+
source: "user",
|
|
692
|
+
provider: "anthropic",
|
|
693
|
+
fallbackProfile: "my-fast",
|
|
694
|
+
},
|
|
695
|
+
},
|
|
696
|
+
});
|
|
697
|
+
await expect(
|
|
698
|
+
call("inference_profiles_delete", { pathParams: { name: "my-fast" } }),
|
|
699
|
+
).rejects.toThrow(/llm\.profiles\.my-primary\.fallbackProfile/);
|
|
700
|
+
});
|
|
701
|
+
|
|
679
702
|
test("rejects deletion referenced by a call site", async () => {
|
|
680
703
|
setConfig("llm", {
|
|
681
704
|
callSites: { memoryExtraction: { profile: "my-fast" } },
|
|
@@ -706,6 +729,20 @@ describe("DELETE inference/profiles/:name reference guard", () => {
|
|
|
706
729
|
// ── provider-aware list/get (Finding 2) ───────────────────────────────────────
|
|
707
730
|
|
|
708
731
|
describe("GET inference/profiles honors llm.defaultProvider", () => {
|
|
732
|
+
test("omits managed backup routes from the user-facing catalog", async () => {
|
|
733
|
+
setConfig("llm", { profiles: {} });
|
|
734
|
+
|
|
735
|
+
const listed = (await call("inference_profiles_list", {})) as {
|
|
736
|
+
profiles: Array<{ name: string }>;
|
|
737
|
+
};
|
|
738
|
+
const names = new Set(listed.profiles.map((profile) => profile.name));
|
|
739
|
+
|
|
740
|
+
for (const key of BACKUP_PROFILE_KEYS) {
|
|
741
|
+
expect(names.has(key)).toBe(false);
|
|
742
|
+
}
|
|
743
|
+
expect(names.has("balanced")).toBe(true);
|
|
744
|
+
});
|
|
745
|
+
|
|
709
746
|
test("expands balanced through a BYOK default provider, not the vellum column", async () => {
|
|
710
747
|
setConfig("llm", {
|
|
711
748
|
defaultProvider: { provider: "anthropic" },
|
|
@@ -36,8 +36,9 @@ const probeTunnelMock = mock(
|
|
|
36
36
|
mock.module("../../../daemon/handlers/config-ingress.js", () => ({
|
|
37
37
|
getIngressConfigResult: () => ingressConfig,
|
|
38
38
|
isVelayManagedIngress: () => velayManaged,
|
|
39
|
-
loadLastTunnelRecord: () => lastTunnel,
|
|
40
39
|
loadPairingTunnelRecord: () => pairingTunnel,
|
|
40
|
+
// Mirrors the real selector: a pairing-only record wins over the last run.
|
|
41
|
+
loadRestartTunnelRecord: () => pairingTunnel ?? lastTunnel,
|
|
41
42
|
loadRecordedAssistantId: () => recordedAssistantId,
|
|
42
43
|
}));
|
|
43
44
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `internal_mcp_add` must not freeze a risk level into the entry it writes.
|
|
3
|
+
*
|
|
4
|
+
* The shipped default lives in `McpServerConfigSchema`, which parses
|
|
5
|
+
* `config.json` on every load. An entry that carries no `defaultRiskLevel`
|
|
6
|
+
* therefore tracks that default; one that carries an explicit level keeps it.
|
|
7
|
+
* Writing a level the caller never asked for would pin every added server to
|
|
8
|
+
* whatever the default happened to be on the day it was added.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
import { McpServerConfigSchema } from "../../../config/schemas/mcp.js";
|
|
14
|
+
import { BadRequestError } from "../errors.js";
|
|
15
|
+
|
|
16
|
+
let raw: Record<string, unknown> = {};
|
|
17
|
+
let saved: Record<string, unknown> | undefined;
|
|
18
|
+
|
|
19
|
+
const actualLoader = await import("../../../config/loader.js");
|
|
20
|
+
mock.module("../../../config/loader.js", () => ({
|
|
21
|
+
...actualLoader,
|
|
22
|
+
loadRawConfig: () => raw,
|
|
23
|
+
saveRawConfig: (next: Record<string, unknown>) => {
|
|
24
|
+
saved = next;
|
|
25
|
+
},
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
mock.module("../../../daemon/mcp-reload-service.js", () => ({
|
|
29
|
+
reloadMcpServers: async () => {},
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
const { ROUTES } = await import("../mcp-auth-routes.js");
|
|
33
|
+
|
|
34
|
+
const addRoute = ROUTES.find((r) => r.operationId === "internal_mcp_add")!;
|
|
35
|
+
|
|
36
|
+
function addedEntry(): Record<string, unknown> {
|
|
37
|
+
const mcp = saved?.mcp as { servers: Record<string, unknown> };
|
|
38
|
+
return mcp.servers["srv"] as Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe("internal_mcp_add risk level", () => {
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
raw = {};
|
|
44
|
+
saved = undefined;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("omits defaultRiskLevel when the caller names no risk", async () => {
|
|
48
|
+
await addRoute.handler({
|
|
49
|
+
body: {
|
|
50
|
+
name: "srv",
|
|
51
|
+
transportType: "streamable-http",
|
|
52
|
+
url: "https://example.com/mcp",
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const entry = addedEntry();
|
|
57
|
+
expect(entry).not.toHaveProperty("defaultRiskLevel");
|
|
58
|
+
expect(McpServerConfigSchema.parse(entry).defaultRiskLevel).toBe("medium");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("persists an explicit risk level", async () => {
|
|
62
|
+
await addRoute.handler({
|
|
63
|
+
body: {
|
|
64
|
+
name: "srv",
|
|
65
|
+
transportType: "streamable-http",
|
|
66
|
+
url: "https://example.com/mcp",
|
|
67
|
+
risk: "high",
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(addedEntry().defaultRiskLevel).toBe("high");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("rejects an unknown risk level", async () => {
|
|
75
|
+
await expect(
|
|
76
|
+
addRoute.handler({
|
|
77
|
+
body: {
|
|
78
|
+
name: "srv",
|
|
79
|
+
transportType: "streamable-http",
|
|
80
|
+
url: "https://example.com/mcp",
|
|
81
|
+
risk: "extreme",
|
|
82
|
+
},
|
|
83
|
+
}),
|
|
84
|
+
).rejects.toThrow(BadRequestError);
|
|
85
|
+
expect(saved).toBeUndefined();
|
|
86
|
+
});
|
|
87
|
+
});
|