@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,78 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { credentialKey } from "@vellumai/credential-storage";
|
|
4
|
+
import type { CredentialRecord } from "@vellumai/service-contracts/credential-rpc";
|
|
5
|
+
|
|
6
|
+
import type { CredentialRecordBackend } from "../security/ces-rpc-record-backend.js";
|
|
7
|
+
import {
|
|
8
|
+
_setMetadataPath,
|
|
9
|
+
persistCredentialMetadata,
|
|
10
|
+
setCredentialRecordBackend,
|
|
11
|
+
upsertCredentialMetadata,
|
|
12
|
+
} from "../tools/credentials/metadata-store.js";
|
|
13
|
+
|
|
14
|
+
function makeBackend(): CredentialRecordBackend & {
|
|
15
|
+
store: Map<string, CredentialRecord>;
|
|
16
|
+
} {
|
|
17
|
+
const store = new Map<string, CredentialRecord>();
|
|
18
|
+
return {
|
|
19
|
+
store,
|
|
20
|
+
isAvailable: () => true,
|
|
21
|
+
get: async (account) => store.get(account),
|
|
22
|
+
set: async (account, record) => {
|
|
23
|
+
store.set(account, record);
|
|
24
|
+
return true;
|
|
25
|
+
},
|
|
26
|
+
delete: async (account) => {
|
|
27
|
+
if (!store.has(account)) {
|
|
28
|
+
return "not-found";
|
|
29
|
+
}
|
|
30
|
+
store.delete(account);
|
|
31
|
+
return "deleted";
|
|
32
|
+
},
|
|
33
|
+
list: async () =>
|
|
34
|
+
[...store.entries()].map(([account, record]) => ({ account, record })),
|
|
35
|
+
bulkSet: async (records) => {
|
|
36
|
+
for (const { account, record } of records) {
|
|
37
|
+
store.set(account, record);
|
|
38
|
+
}
|
|
39
|
+
return records.map((entry) => ({ account: entry.account, ok: true }));
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe("CES credential record write-through", () => {
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
setCredentialRecordBackend(undefined);
|
|
47
|
+
_setMetadataPath(null);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("upsert write-through updates the CES backend", async () => {
|
|
51
|
+
const backend = makeBackend();
|
|
52
|
+
setCredentialRecordBackend(backend);
|
|
53
|
+
|
|
54
|
+
const created = upsertCredentialMetadata("slack_channel", "bot_token", {
|
|
55
|
+
allowedTools: ["bash"],
|
|
56
|
+
});
|
|
57
|
+
await persistCredentialMetadata(created);
|
|
58
|
+
|
|
59
|
+
const stored = backend.store.get(
|
|
60
|
+
credentialKey("slack_channel", "bot_token"),
|
|
61
|
+
);
|
|
62
|
+
expect(stored?.allowedTools).toEqual(["bash"]);
|
|
63
|
+
expect(stored?.credentialId).toBe(created.credentialId);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("test metadata path override skips CES write-through", async () => {
|
|
67
|
+
const backend = makeBackend();
|
|
68
|
+
setCredentialRecordBackend(backend);
|
|
69
|
+
_setMetadataPath("/tmp/does-not-write-through-metadata.json");
|
|
70
|
+
|
|
71
|
+
const created = upsertCredentialMetadata("github", "token", {
|
|
72
|
+
allowedTools: ["bash"],
|
|
73
|
+
});
|
|
74
|
+
await persistCredentialMetadata(created);
|
|
75
|
+
|
|
76
|
+
expect(backend.store.size).toBe(0);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -749,12 +749,28 @@ describe("credentials routes", () => {
|
|
|
749
749
|
expect(entry.service).toBe("vercel");
|
|
750
750
|
expect(entry.field).toBe("api_token");
|
|
751
751
|
expect(entry.hasSecret).toBe(true);
|
|
752
|
-
expect(entry.scrubbedValue).toBe("****
|
|
752
|
+
expect(entry.scrubbedValue).toBe("supe****");
|
|
753
753
|
|
|
754
754
|
// AND the raw secret never appears anywhere in the response
|
|
755
755
|
expect(JSON.stringify(result)).not.toContain(SECRET_VALUE);
|
|
756
756
|
});
|
|
757
757
|
|
|
758
|
+
test("masked preview keeps a vendor token prefix", async () => {
|
|
759
|
+
const token = "cfat_000000000000000000000000000000000000abcd";
|
|
760
|
+
await setRoute!.handler({
|
|
761
|
+
body: {
|
|
762
|
+
service: "cloudflare",
|
|
763
|
+
field: "api_token",
|
|
764
|
+
value: token,
|
|
765
|
+
},
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
const result = (await listRoute!.handler({ body: {} })) as ListResponse;
|
|
769
|
+
expect(result.credentials).toHaveLength(1);
|
|
770
|
+
expect(result.credentials[0].scrubbedValue).toBe("cfat****");
|
|
771
|
+
expect(JSON.stringify(result)).not.toContain(token);
|
|
772
|
+
});
|
|
773
|
+
|
|
758
774
|
test("filters credentials by search substring", async () => {
|
|
759
775
|
/**
|
|
760
776
|
* The optional `search` filter restricts the listing to credentials
|
|
@@ -179,6 +179,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
|
|
|
179
179
|
"providers/inference/credential-slot-repair.ts", // boot repair: copies the shared openai-compatible slot value into per-connection slots (get/set of provider API keys only; values never logged or returned)
|
|
180
180
|
"runtime/routes/inference-provider-connection-routes.ts", // connection delete removes its dedicated per-connection key slot (deleteSecureKeyAsync only; no reads)
|
|
181
181
|
"daemon/handlers/config-slack-channel.ts", // Slack channel config credential management
|
|
182
|
+
"daemon/handlers/config-discord-channel.ts", // Discord channel bot token management
|
|
182
183
|
"providers/platform-proxy/context.ts", // managed proxy API key lookup for provider initialization
|
|
183
184
|
"platform/client.ts", // platform client credential store fallback for standalone CLI auth
|
|
184
185
|
"mcp/mcp-header-store.ts", // MCP static auth header persistence (credential store CRUD + legacy migration)
|
|
@@ -203,8 +204,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
|
|
|
203
204
|
"providers/registry.ts", // provider registry API key lookup for initialization
|
|
204
205
|
"providers/inference/resolve-auth.ts", // provider_connection auth resolver (api_key path reads vault, mirrors registry.ts)
|
|
205
206
|
"providers/inference/codex-token-refresh.ts", // Codex OAuth token refresh (reads/writes access_token, refresh_token, expires_at)
|
|
206
|
-
"
|
|
207
|
-
"runtime/routes/chatgpt-subscription-auth-routes.ts", // ChatGPT subscription daemon OAuth flow (stores tokens in CES)
|
|
207
|
+
"providers/inference/chatgpt-subscription-credentials.ts", // ChatGPT subscription sign-in token storage, shared by the daemon routes and the CLI (setSecureKeyAsync only; no reads)
|
|
208
208
|
"providers/provider-availability.ts", // provider availability API key check
|
|
209
209
|
"media/image-credentials.ts", // shared image-gen credential resolver (provider API key lookup)
|
|
210
210
|
"persistence/embeddings/embedding-backend.ts", // embedding backend API key lookup
|
|
@@ -231,7 +231,6 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
|
|
|
231
231
|
"runtime/routes/avatar-routes.ts", // avatar generate route reads platform_base_url from credential store
|
|
232
232
|
"cli/commands/keys.ts", // CLI provider key management
|
|
233
233
|
"cli/commands/oauth/connect.ts", // CLI OAuth connect stored-secret verification
|
|
234
|
-
"runtime/routes/chatgpt-subscription-auth-routes.ts", // ChatGPT subscription OAuth token storage
|
|
235
234
|
"runtime/routes/identity-routes.ts", // health/readyz endpoint checks CES connectivity via getCesClient
|
|
236
235
|
"tools/executor.ts", // CES approval bridge resolves the CES RPC client via getCesClient
|
|
237
236
|
"tools/network/web-fetch.ts", // Firecrawl /scrape BYOK fetch provider API key lookup (firecrawl provider key)
|
|
@@ -470,24 +469,24 @@ describe("Invariant 4: credentials only used for allowed purpose", () => {
|
|
|
470
469
|
});
|
|
471
470
|
|
|
472
471
|
// PR 18 — vault policy fields with strict defaults
|
|
473
|
-
test("credential without explicit policy gets strict defaults (deny all)", () => {
|
|
472
|
+
test("credential without explicit policy gets strict defaults (deny all)", async () => {
|
|
474
473
|
// A credential stored without allowed_tools defaults to empty array,
|
|
475
474
|
// which the broker's isToolAllowed check fails closed on.
|
|
476
475
|
upsertCredentialMetadata("test-svc", "pass", {});
|
|
477
476
|
|
|
478
|
-
const result = broker.
|
|
477
|
+
const result = await broker.browserFill({
|
|
479
478
|
service: "test-svc",
|
|
480
479
|
field: "pass",
|
|
481
480
|
toolName: "browser_fill_credential",
|
|
481
|
+
fill: async () => {},
|
|
482
482
|
});
|
|
483
483
|
|
|
484
|
-
expect(result.
|
|
485
|
-
expect(
|
|
486
|
-
"No tools are currently allowed",
|
|
487
|
-
);
|
|
484
|
+
expect(result.success).toBe(false);
|
|
485
|
+
expect(result.reason).toContain("No tools are currently allowed");
|
|
488
486
|
});
|
|
489
487
|
});
|
|
490
488
|
|
|
489
|
+
|
|
491
490
|
// ---------------------------------------------------------------------------
|
|
492
491
|
// Invariant 6 — oauth2ClientSecret never in plaintext metadata
|
|
493
492
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Managed backup profiles and the `fallbackProfile` pointers of the vellum
|
|
3
|
+
* column (`default-profile-catalog.ts`).
|
|
4
|
+
*
|
|
5
|
+
* The invariants under test:
|
|
6
|
+
* - Every default profile's managed (vellum) implementation points at a
|
|
7
|
+
* backup profile that exists in the code catalog.
|
|
8
|
+
* - Cross-provider rule: a backup pins its model at a DIFFERENT managed
|
|
9
|
+
* upstream than its primary, so an outage at the primary's provider can
|
|
10
|
+
* be served by the backup. Holds for the experiment arms too.
|
|
11
|
+
* - Probe coverage: every backup model is a catalog member of its upstream
|
|
12
|
+
* provider, which is what feeds the platform's model liveness probe.
|
|
13
|
+
* - Scoping: backups exist for the managed column only. A BYOK or chatgpt
|
|
14
|
+
* default provider materializes no backup profiles, and its primaries
|
|
15
|
+
* carry no `fallbackProfile` pointers.
|
|
16
|
+
* - The effective catalog parses under `LLMSchema` (exercising the
|
|
17
|
+
* `fallbackProfile` superRefine against the real entries), and backups
|
|
18
|
+
* are delete-protected like the other managed defaults.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
22
|
+
|
|
23
|
+
import { BALANCED_MODEL_EXPERIMENT_FLAG_KEY } from "../config/balanced-model-experiment.js";
|
|
24
|
+
import {
|
|
25
|
+
CODE_DEFAULT_PROFILE_ENTRIES,
|
|
26
|
+
getEffectiveProfile,
|
|
27
|
+
getEffectiveProfiles,
|
|
28
|
+
getEffectiveProfilesForProvider,
|
|
29
|
+
INVARIANT_PROFILE_NAMES,
|
|
30
|
+
MANAGED_PROFILE_NAMES,
|
|
31
|
+
MANAGED_PROFILE_TEMPLATES,
|
|
32
|
+
PROFILE_IMPLS,
|
|
33
|
+
resolveDefaultProfileForProvider,
|
|
34
|
+
} from "../config/default-profile-catalog.js";
|
|
35
|
+
import {
|
|
36
|
+
BACKUP_PROFILE_KEYS,
|
|
37
|
+
DEFAULT_PROFILE_KEYS,
|
|
38
|
+
DEFAULT_PROFILE_PROVIDERS,
|
|
39
|
+
FALLBACK_PROFILE_BY_KEY,
|
|
40
|
+
} from "../config/default-profile-names.js";
|
|
41
|
+
import { clearCachedOverrides } from "../config/feature-flag-cache.js";
|
|
42
|
+
import {
|
|
43
|
+
type DefaultProviderConfig,
|
|
44
|
+
LLMSchema,
|
|
45
|
+
type ProfileEntry,
|
|
46
|
+
} from "../config/schemas/llm.js";
|
|
47
|
+
import { isModelInCatalog } from "../providers/model-catalog.js";
|
|
48
|
+
import { getManagedUpstream } from "../providers/vellum-model-routing.js";
|
|
49
|
+
import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
|
|
50
|
+
|
|
51
|
+
const vellum: DefaultProviderConfig = { provider: "vellum" };
|
|
52
|
+
|
|
53
|
+
afterEach(() => {
|
|
54
|
+
clearCachedOverrides();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("fallbackProfile pointers on the vellum column", () => {
|
|
58
|
+
test("every default profile's vellum impl points at an existing backup", () => {
|
|
59
|
+
for (const key of DEFAULT_PROFILE_KEYS) {
|
|
60
|
+
const impl = PROFILE_IMPLS[key].vellum;
|
|
61
|
+
expect(impl.fallbackProfile).toBe(FALLBACK_PROFILE_BY_KEY[key]);
|
|
62
|
+
const backup = CODE_DEFAULT_PROFILE_ENTRIES[FALLBACK_PROFILE_BY_KEY[key]];
|
|
63
|
+
expect(backup).toBeDefined();
|
|
64
|
+
expect(backup.provider).toBe("vellum");
|
|
65
|
+
expect(backup.source).toBe("managed");
|
|
66
|
+
// Single hop: a backup never declares a fallback of its own.
|
|
67
|
+
expect(backup.fallbackProfile).toBeUndefined();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("no BYOK or chatgpt column impl carries fallbackProfile", () => {
|
|
72
|
+
for (const key of DEFAULT_PROFILE_KEYS) {
|
|
73
|
+
for (const provider of DEFAULT_PROFILE_PROVIDERS) {
|
|
74
|
+
if (provider === "vellum") {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
expect(PROFILE_IMPLS[key][provider].fallbackProfile).toBeUndefined();
|
|
78
|
+
}
|
|
79
|
+
// A default-capable provider without a named matrix column
|
|
80
|
+
// materializes from the shared BYOK templates: no pointer either.
|
|
81
|
+
const entry = resolveDefaultProfileForProvider(undefined, key, {
|
|
82
|
+
provider: "together",
|
|
83
|
+
});
|
|
84
|
+
expect(entry?.fallbackProfile).toBeUndefined();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("cross-provider rule", () => {
|
|
90
|
+
test("every backup dispatches through a different managed upstream than its primary", () => {
|
|
91
|
+
for (const key of DEFAULT_PROFILE_KEYS) {
|
|
92
|
+
const primary = CODE_DEFAULT_PROFILE_ENTRIES[key];
|
|
93
|
+
const backup = CODE_DEFAULT_PROFILE_ENTRIES[FALLBACK_PROFILE_BY_KEY[key]];
|
|
94
|
+
const primaryUpstream = getManagedUpstream(primary.model as string);
|
|
95
|
+
const backupUpstream = getManagedUpstream(backup.model as string);
|
|
96
|
+
expect(primaryUpstream).not.toBeNull();
|
|
97
|
+
expect(backupUpstream).not.toBeNull();
|
|
98
|
+
expect(backupUpstream).not.toBe(primaryUpstream);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("the balanced experiment arms keep the cross-provider split", () => {
|
|
103
|
+
const backup =
|
|
104
|
+
CODE_DEFAULT_PROFILE_ENTRIES[FALLBACK_PROFILE_BY_KEY.balanced];
|
|
105
|
+
const backupUpstream = getManagedUpstream(backup.model as string);
|
|
106
|
+
for (const arm of ["terra", "glm-5p2"]) {
|
|
107
|
+
setOverridesForTesting({ [BALANCED_MODEL_EXPERIMENT_FLAG_KEY]: arm });
|
|
108
|
+
const armed = resolveDefaultProfileForProvider(
|
|
109
|
+
undefined,
|
|
110
|
+
"balanced",
|
|
111
|
+
vellum,
|
|
112
|
+
);
|
|
113
|
+
expect(armed?.fallbackProfile).toBe(FALLBACK_PROFILE_BY_KEY.balanced);
|
|
114
|
+
const armedUpstream = getManagedUpstream(armed?.model as string);
|
|
115
|
+
expect(armedUpstream).not.toBeNull();
|
|
116
|
+
expect(armedUpstream).not.toBe(backupUpstream);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("every backup model is a catalog member of its upstream (liveness-probe coverage)", () => {
|
|
121
|
+
for (const key of BACKUP_PROFILE_KEYS) {
|
|
122
|
+
const model = CODE_DEFAULT_PROFILE_ENTRIES[key].model as string;
|
|
123
|
+
const upstream = getManagedUpstream(model);
|
|
124
|
+
expect(upstream).not.toBeNull();
|
|
125
|
+
expect(isModelInCatalog(upstream as string, model)).toBe(true);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("managed-column-only scoping", () => {
|
|
131
|
+
test("the managed column materializes the backups after the primaries", () => {
|
|
132
|
+
const effective = getEffectiveProfilesForProvider(undefined, vellum);
|
|
133
|
+
for (const key of BACKUP_PROFILE_KEYS) {
|
|
134
|
+
expect(effective[key]).toBeDefined();
|
|
135
|
+
expect(effective[key]?.provider).toBe("vellum");
|
|
136
|
+
expect(effective[key]?.source).toBe("managed");
|
|
137
|
+
}
|
|
138
|
+
// The seeder inserts missing managed keys in MANAGED_PROFILE_TEMPLATES
|
|
139
|
+
// order, which is what places backups after the primaries in
|
|
140
|
+
// `profileOrder` presentation.
|
|
141
|
+
expect(Object.keys(MANAGED_PROFILE_TEMPLATES)).toEqual([
|
|
142
|
+
...DEFAULT_PROFILE_KEYS,
|
|
143
|
+
...BACKUP_PROFILE_KEYS,
|
|
144
|
+
]);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("a null defaultProvider (pre-defaultProvider install) keeps its backups", () => {
|
|
148
|
+
const effective = getEffectiveProfiles(undefined);
|
|
149
|
+
for (const key of BACKUP_PROFILE_KEYS) {
|
|
150
|
+
expect(effective[key]).toBeDefined();
|
|
151
|
+
expect(
|
|
152
|
+
resolveDefaultProfileForProvider(undefined, key, null),
|
|
153
|
+
).toBeDefined();
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("a BYOK default provider materializes no backups and no pointers", () => {
|
|
158
|
+
for (const provider of ["anthropic", "openai", "gemini"] as const) {
|
|
159
|
+
const effective = getEffectiveProfilesForProvider(undefined, {
|
|
160
|
+
provider,
|
|
161
|
+
});
|
|
162
|
+
for (const key of BACKUP_PROFILE_KEYS) {
|
|
163
|
+
expect(effective[key]).toBeUndefined();
|
|
164
|
+
}
|
|
165
|
+
for (const key of DEFAULT_PROFILE_KEYS) {
|
|
166
|
+
expect(effective[key]?.fallbackProfile).toBeUndefined();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("the chatgpt column materializes no backups and no pointers", () => {
|
|
172
|
+
const effective = getEffectiveProfilesForProvider(undefined, {
|
|
173
|
+
provider: "chatgpt",
|
|
174
|
+
});
|
|
175
|
+
for (const key of BACKUP_PROFILE_KEYS) {
|
|
176
|
+
expect(effective[key]).toBeUndefined();
|
|
177
|
+
}
|
|
178
|
+
for (const key of DEFAULT_PROFILE_KEYS) {
|
|
179
|
+
expect(effective[key]?.fallbackProfile).toBeUndefined();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("a persisted managed stub does not resurrect a backup off the managed column", () => {
|
|
184
|
+
// A `config get` -> `config set` round-trip on a managed install can
|
|
185
|
+
// leave a thin managed stub for a backup in `llm.profiles`
|
|
186
|
+
// (`normalizeManagedProfileWrites`). The stub is the workspace's slot for
|
|
187
|
+
// a code-owned body, not a profile, so once the column has no body behind
|
|
188
|
+
// it the name must stop being listed rather than resolve to a
|
|
189
|
+
// providerless entry the pickers would offer and `LLMSchema` would
|
|
190
|
+
// reject.
|
|
191
|
+
const workspace: Record<string, ProfileEntry> = {
|
|
192
|
+
"balanced-backup": { source: "managed" },
|
|
193
|
+
};
|
|
194
|
+
for (const provider of ["anthropic", "chatgpt"] as const) {
|
|
195
|
+
const effective = getEffectiveProfilesForProvider(workspace, {
|
|
196
|
+
provider,
|
|
197
|
+
});
|
|
198
|
+
expect(effective["balanced-backup"]).toBeUndefined();
|
|
199
|
+
expect(
|
|
200
|
+
resolveDefaultProfileForProvider(workspace, "balanced-backup", {
|
|
201
|
+
provider,
|
|
202
|
+
}),
|
|
203
|
+
).toBeUndefined();
|
|
204
|
+
}
|
|
205
|
+
// The managed column still has a body for the stub to stand for.
|
|
206
|
+
expect(
|
|
207
|
+
getEffectiveProfilesForProvider(workspace, vellum)["balanced-backup"]
|
|
208
|
+
?.model,
|
|
209
|
+
).toBe(CODE_DEFAULT_PROFILE_ENTRIES["balanced-backup"]?.model);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("a user-owned entry under a backup name stays listed off the managed column", () => {
|
|
213
|
+
// Nothing code-owned resolves there, so the entry is simply a profile of
|
|
214
|
+
// that name and keeps its own body.
|
|
215
|
+
const workspace: Record<string, ProfileEntry> = {
|
|
216
|
+
"balanced-backup": {
|
|
217
|
+
source: "user",
|
|
218
|
+
provider: "anthropic",
|
|
219
|
+
model: "claude-opus-4-7",
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
const effective = getEffectiveProfilesForProvider(workspace, {
|
|
223
|
+
provider: "anthropic",
|
|
224
|
+
});
|
|
225
|
+
expect(effective["balanced-backup"]?.model).toBe("claude-opus-4-7");
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe("effective-profile machinery", () => {
|
|
230
|
+
test("the full effective catalog parses under LLMSchema", () => {
|
|
231
|
+
// Exercises the fallbackProfile superRefine (target exists, no self
|
|
232
|
+
// reference, no mix target, single hop) against the real entries.
|
|
233
|
+
for (const profiles of [
|
|
234
|
+
getEffectiveProfiles(undefined),
|
|
235
|
+
getEffectiveProfilesForProvider(undefined, vellum),
|
|
236
|
+
]) {
|
|
237
|
+
const parsed = LLMSchema.parse({
|
|
238
|
+
profiles,
|
|
239
|
+
activeProfile: "balanced",
|
|
240
|
+
});
|
|
241
|
+
for (const key of DEFAULT_PROFILE_KEYS) {
|
|
242
|
+
expect(parsed.profiles[key]?.fallbackProfile).toBe(
|
|
243
|
+
FALLBACK_PROFILE_BY_KEY[key],
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("a workspace entry never overlays a backup, whatever its source", () => {
|
|
250
|
+
// Backups are code-owned (`CODE_OWNED_PROFILE_NAMES`), so unlike the
|
|
251
|
+
// primaries they take no workspace overlay at all: not the usual
|
|
252
|
+
// label/status/topP whitelist from a managed stub, and not a user-owned
|
|
253
|
+
// shadow. Disabling a backup through a hand-edited config would strand
|
|
254
|
+
// the primary's fallback exactly when it is needed.
|
|
255
|
+
for (const source of ["managed", "user"] as const) {
|
|
256
|
+
const workspace: Record<string, ProfileEntry> = {
|
|
257
|
+
"balanced-backup": {
|
|
258
|
+
source,
|
|
259
|
+
label: "My Backup",
|
|
260
|
+
status: "disabled",
|
|
261
|
+
topP: 0.7,
|
|
262
|
+
// Stale content drift on disk must lose to the code default body.
|
|
263
|
+
provider: "anthropic",
|
|
264
|
+
model: "claude-sonnet-4-6",
|
|
265
|
+
maxTokens: 1,
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
const entry = getEffectiveProfile(workspace, "balanced-backup");
|
|
269
|
+
const body = CODE_DEFAULT_PROFILE_ENTRIES["balanced-backup"];
|
|
270
|
+
expect(entry?.label).toBe(body.label);
|
|
271
|
+
expect(entry?.status).toBeUndefined();
|
|
272
|
+
expect(entry?.topP).toBe(body.topP);
|
|
273
|
+
expect(entry?.model).toBe(body.model);
|
|
274
|
+
expect(entry?.maxTokens).toBe(body.maxTokens);
|
|
275
|
+
expect(String(entry?.provider)).toBe("vellum");
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("backup profiles are delete-protected like the other managed defaults", () => {
|
|
280
|
+
// Membership in these sets is what wires the route-level deletion
|
|
281
|
+
// rejection (`rejectManagedProfileDeletion`, `handleCreateProfile`) and
|
|
282
|
+
// the commitConfigWrite invariant guard
|
|
283
|
+
// (`assertInvariantProfilesPreserved`).
|
|
284
|
+
for (const key of BACKUP_PROFILE_KEYS) {
|
|
285
|
+
expect(MANAGED_PROFILE_NAMES.has(key)).toBe(true);
|
|
286
|
+
expect(INVARIANT_PROFILE_NAMES.has(key)).toBe(true);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
});
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
11
11
|
|
|
12
|
+
import { readProviderMetadata } from "../messaging/read-provider-metadata.js";
|
|
13
|
+
|
|
12
14
|
mock.module("../config/env.js", () => ({
|
|
13
15
|
isHttpAuthDisabled: () => true,
|
|
14
16
|
getGatewayInternalBaseUrl: () => "http://127.0.0.1:7830",
|
|
@@ -249,7 +251,10 @@ describe("Slack delete propagation", () => {
|
|
|
249
251
|
expect(slackMeta!.deletedAt).toBeUndefined();
|
|
250
252
|
});
|
|
251
253
|
|
|
252
|
-
test("delete for row without slackMeta
|
|
254
|
+
test("delete for row without slackMeta stamps the neutral metadata", async () => {
|
|
255
|
+
// A legacy pre-enrichment row still gets its delete marked: the neutral
|
|
256
|
+
// envelope is synthesized so readProviderMetadata serves the stamp to
|
|
257
|
+
// every channel-agnostic reader, and content stays for audit.
|
|
253
258
|
const seeded = seedSlackMessage({
|
|
254
259
|
externalChatId: "C0123CHANNEL",
|
|
255
260
|
originalTs: "2222.2222",
|
|
@@ -257,6 +262,7 @@ describe("Slack delete propagation", () => {
|
|
|
257
262
|
withSlackMeta: false,
|
|
258
263
|
});
|
|
259
264
|
|
|
265
|
+
const before = Date.now();
|
|
260
266
|
const req = buildSlackDeleteRequest({
|
|
261
267
|
externalChatId: seeded.externalChatId,
|
|
262
268
|
deletedTs: seeded.originalTs,
|
|
@@ -265,7 +271,7 @@ describe("Slack delete propagation", () => {
|
|
|
265
271
|
const json = (await resp.json()) as Record<string, unknown>;
|
|
266
272
|
|
|
267
273
|
expect(json.accepted).toBe(true);
|
|
268
|
-
expect(json.deleted).toBe(
|
|
274
|
+
expect(json.deleted).toBe(true);
|
|
269
275
|
|
|
270
276
|
const db = getDb();
|
|
271
277
|
const row = db
|
|
@@ -277,6 +283,90 @@ describe("Slack delete propagation", () => {
|
|
|
277
283
|
expect(row!.content).toBe("Legacy pre-upgrade text");
|
|
278
284
|
const parsed = JSON.parse(row!.metadata!) as Record<string, unknown>;
|
|
279
285
|
expect(parsed.slackMeta).toBeUndefined();
|
|
286
|
+
const neutral = readProviderMetadata(row!.metadata);
|
|
287
|
+
expect(neutral).not.toBeNull();
|
|
288
|
+
expect(neutral!.source).toBe("slack");
|
|
289
|
+
expect(neutral!.messageId).toBe(seeded.originalTs);
|
|
290
|
+
expect(neutral!.deletedAt).toBeDefined();
|
|
291
|
+
expect(neutral!.deletedAt!).toBeGreaterThanOrEqual(before);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("a flat-legacy row's fields survive the delete stamp", async () => {
|
|
295
|
+
// Rows written before slackMeta nesting carry the Slack envelope flat in
|
|
296
|
+
// messages.metadata. The stamp bases on the mapped envelope, so thread
|
|
297
|
+
// and display identity remain readable beside deletedAt instead of
|
|
298
|
+
// being shadowed by a minimal synthesis.
|
|
299
|
+
const seeded = seedSlackMessage({
|
|
300
|
+
externalChatId: "C0123CHANNEL",
|
|
301
|
+
originalTs: "3333.3333",
|
|
302
|
+
content: "Flat legacy text",
|
|
303
|
+
withSlackMeta: false,
|
|
304
|
+
});
|
|
305
|
+
const db = getDb();
|
|
306
|
+
db.update(messages)
|
|
307
|
+
.set({
|
|
308
|
+
metadata: JSON.stringify({
|
|
309
|
+
source: "slack",
|
|
310
|
+
channelId: seeded.externalChatId,
|
|
311
|
+
channelTs: seeded.originalTs,
|
|
312
|
+
threadTs: "3000.0001",
|
|
313
|
+
eventKind: "message",
|
|
314
|
+
displayName: "Flat User",
|
|
315
|
+
}),
|
|
316
|
+
})
|
|
317
|
+
.where(eq(messages.id, seeded.messageId))
|
|
318
|
+
.run();
|
|
319
|
+
|
|
320
|
+
const req = buildSlackDeleteRequest({
|
|
321
|
+
externalChatId: seeded.externalChatId,
|
|
322
|
+
deletedTs: seeded.originalTs,
|
|
323
|
+
});
|
|
324
|
+
const resp = await handleChannelInbound(req, undefined, TEST_BEARER_TOKEN);
|
|
325
|
+
const json = (await resp.json()) as Record<string, unknown>;
|
|
326
|
+
expect(json.deleted).toBe(true);
|
|
327
|
+
|
|
328
|
+
const row = db
|
|
329
|
+
.select()
|
|
330
|
+
.from(messages)
|
|
331
|
+
.where(eq(messages.id, seeded.messageId))
|
|
332
|
+
.get();
|
|
333
|
+
const neutral = readProviderMetadata(row!.metadata);
|
|
334
|
+
expect(neutral).not.toBeNull();
|
|
335
|
+
expect(neutral!.deletedAt).toBeDefined();
|
|
336
|
+
expect(neutral!.threadId).toBe("3000.0001");
|
|
337
|
+
expect(neutral!.displayName).toBe("Flat User");
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test("a row with malformed metadata still records its delete", async () => {
|
|
341
|
+
const seeded = seedSlackMessage({
|
|
342
|
+
externalChatId: "C0123CHANNEL",
|
|
343
|
+
originalTs: "4444.4444",
|
|
344
|
+
content: "Row with broken envelope",
|
|
345
|
+
withSlackMeta: false,
|
|
346
|
+
});
|
|
347
|
+
const db = getDb();
|
|
348
|
+
db.update(messages)
|
|
349
|
+
.set({ metadata: "{not json" })
|
|
350
|
+
.where(eq(messages.id, seeded.messageId))
|
|
351
|
+
.run();
|
|
352
|
+
|
|
353
|
+
const req = buildSlackDeleteRequest({
|
|
354
|
+
externalChatId: seeded.externalChatId,
|
|
355
|
+
deletedTs: seeded.originalTs,
|
|
356
|
+
});
|
|
357
|
+
const resp = await handleChannelInbound(req, undefined, TEST_BEARER_TOKEN);
|
|
358
|
+
const json = (await resp.json()) as Record<string, unknown>;
|
|
359
|
+
expect(json.accepted).toBe(true);
|
|
360
|
+
expect(json.deleted).toBe(true);
|
|
361
|
+
|
|
362
|
+
const row = db
|
|
363
|
+
.select()
|
|
364
|
+
.from(messages)
|
|
365
|
+
.where(eq(messages.id, seeded.messageId))
|
|
366
|
+
.get();
|
|
367
|
+
const neutral = readProviderMetadata(row!.metadata);
|
|
368
|
+
expect(neutral).not.toBeNull();
|
|
369
|
+
expect(neutral!.deletedAt).toBeDefined();
|
|
280
370
|
});
|
|
281
371
|
|
|
282
372
|
test("delete missing sourceMetadata.messageId is a no-op", async () => {
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Discord callback seam, end to end through the join rather than either
|
|
3
|
+
* half of it.
|
|
4
|
+
*
|
|
5
|
+
* Discord's send, render and DM delivery each have their own unit coverage,
|
|
6
|
+
* and the gateway's inbound has schema, admission and normalizer coverage.
|
|
7
|
+
* Nothing exercised the point where they meet: a reply addressed to a Discord
|
|
8
|
+
* callback resolving to the Discord transport and being delivered with the
|
|
9
|
+
* chat id it was given. That join is where a channel goes silently deaf while
|
|
10
|
+
* every component around it still passes, which is the shape the Slack
|
|
11
|
+
* plan-mode bug had.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
15
|
+
|
|
16
|
+
const sent: Array<{ channelId: string; text: string }> = [];
|
|
17
|
+
|
|
18
|
+
// Spread the real module rather than listing the exports this test happens to
|
|
19
|
+
// need. `mock.module` replaces the module wholesale, so a partial stub breaks
|
|
20
|
+
// the transport's import of any export it omits, and would break again the
|
|
21
|
+
// next time `send.ts` grows one.
|
|
22
|
+
const actualSend = await import("../messaging/providers/discord/send.js");
|
|
23
|
+
|
|
24
|
+
mock.module("../messaging/providers/discord/send.js", () => ({
|
|
25
|
+
...actualSend,
|
|
26
|
+
sendDiscordReply: async (target: { channelId: string }, text: string) => {
|
|
27
|
+
sent.push({ channelId: target.channelId, text });
|
|
28
|
+
return { messageId: "1401234567890123456" };
|
|
29
|
+
},
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
const { channelForCallback } =
|
|
33
|
+
await import("../messaging/providers/callback-routing.js");
|
|
34
|
+
const { deliverDirect, getTransportForCallback, isDirectDelivery } =
|
|
35
|
+
await import("../messaging/providers/index.js");
|
|
36
|
+
|
|
37
|
+
type ChannelReplyPayload = Parameters<typeof deliverDirect>[1];
|
|
38
|
+
|
|
39
|
+
const DISCORD_CALLBACK = "https://gateway.example/deliver/discord";
|
|
40
|
+
|
|
41
|
+
describe("a Discord reply reaches the Discord transport", () => {
|
|
42
|
+
test("the callback resolves to discord, absolute and relative", () => {
|
|
43
|
+
expect(channelForCallback(DISCORD_CALLBACK)).toBe("discord");
|
|
44
|
+
// Off-channel guardian flows emit a base-less callback.
|
|
45
|
+
expect(channelForCallback("/deliver/discord")).toBe("discord");
|
|
46
|
+
expect(channelForCallback("/deliver/discord?x=1")).toBe("discord");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("the resolved transport is the one that can send", () => {
|
|
50
|
+
expect(isDirectDelivery(DISCORD_CALLBACK)).toBe(true);
|
|
51
|
+
const transport = getTransportForCallback(DISCORD_CALLBACK);
|
|
52
|
+
expect(transport).toBeDefined();
|
|
53
|
+
expect(typeof transport?.deliver).toBe("function");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("a reply is delivered to the chat id it was addressed to", async () => {
|
|
57
|
+
sent.length = 0;
|
|
58
|
+
const payload: ChannelReplyPayload = {
|
|
59
|
+
chatId: "1409876543210987654",
|
|
60
|
+
text: "hello from the assistant",
|
|
61
|
+
};
|
|
62
|
+
await deliverDirect(DISCORD_CALLBACK, payload);
|
|
63
|
+
|
|
64
|
+
expect(sent).toHaveLength(1);
|
|
65
|
+
// `POST /channels/{id}/messages` addresses a channel and nothing more, so
|
|
66
|
+
// the chat id is the channel the reply lands in.
|
|
67
|
+
expect(sent[0]?.channelId).toBe("1409876543210987654");
|
|
68
|
+
expect(sent[0]?.text).toContain("hello from the assistant");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("a threaded reply lands in the thread, which is its own channel", async () => {
|
|
72
|
+
sent.length = 0;
|
|
73
|
+
const payload: ChannelReplyPayload = {
|
|
74
|
+
chatId: "1409876543210987654",
|
|
75
|
+
text: "in the thread",
|
|
76
|
+
};
|
|
77
|
+
await deliverDirect(
|
|
78
|
+
`${DISCORD_CALLBACK}?threadId=1405555555555555555`,
|
|
79
|
+
payload,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
expect(sent[0]?.channelId).toBe("1405555555555555555");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("Discord declines streaming rather than faking it", () => {
|
|
86
|
+
// No primitive exists, so the transport omits the method and the caller
|
|
87
|
+
// sends the finished reply. An implementation here would be a repeated
|
|
88
|
+
// edit, which is the approach the platforms discourage.
|
|
89
|
+
expect(getTransportForCallback(DISCORD_CALLBACK)?.streamReply).toBe(
|
|
90
|
+
undefined,
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
});
|