@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
|
@@ -438,7 +438,7 @@ describe("assistant mcp add", () => {
|
|
|
438
438
|
expect(stderr).toContain("--url is required");
|
|
439
439
|
});
|
|
440
440
|
|
|
441
|
-
test("
|
|
441
|
+
test("sends no risk when --risk is omitted, leaving the config default", async () => {
|
|
442
442
|
const { exitCode } = await runMcpAdd("default-risk", [
|
|
443
443
|
"-t",
|
|
444
444
|
"sse",
|
|
@@ -447,6 +447,28 @@ describe("assistant mcp add", () => {
|
|
|
447
447
|
]);
|
|
448
448
|
expect(exitCode).toBe(0);
|
|
449
449
|
|
|
450
|
+
const addCall = mockCliIpcCallFn.mock.calls.find(
|
|
451
|
+
(c) => c[0] === "internal_mcp_add",
|
|
452
|
+
);
|
|
453
|
+
expect(addCall).toBeDefined();
|
|
454
|
+
const body = (addCall![1] as Record<string, unknown>).body as Record<
|
|
455
|
+
string,
|
|
456
|
+
unknown
|
|
457
|
+
>;
|
|
458
|
+
expect(body.risk).toBeUndefined();
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
test("forwards an explicit --risk", async () => {
|
|
462
|
+
const { exitCode } = await runMcpAdd("explicit-risk", [
|
|
463
|
+
"-t",
|
|
464
|
+
"sse",
|
|
465
|
+
"-u",
|
|
466
|
+
"https://example.com/sse",
|
|
467
|
+
"-r",
|
|
468
|
+
"high",
|
|
469
|
+
]);
|
|
470
|
+
expect(exitCode).toBe(0);
|
|
471
|
+
|
|
450
472
|
const addCall = mockCliIpcCallFn.mock.calls.find(
|
|
451
473
|
(c) => c[0] === "internal_mcp_add",
|
|
452
474
|
);
|
|
@@ -40,60 +40,69 @@ function serverConfig(defaultRiskLevel: ServerRisk) {
|
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
interface RiskAnnotations {
|
|
44
|
+
readOnlyHint?: boolean;
|
|
45
|
+
destructiveHint?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function toolWithAnnotations(annotations?: RiskAnnotations) {
|
|
44
49
|
return {
|
|
45
50
|
name: "get-summary",
|
|
46
51
|
description: "Read a summary",
|
|
47
52
|
inputSchema: { type: "object", properties: {} },
|
|
48
|
-
...(
|
|
53
|
+
...(annotations === undefined ? {} : { annotations }),
|
|
49
54
|
};
|
|
50
55
|
}
|
|
51
56
|
|
|
52
|
-
describe("MCP tool risk from
|
|
57
|
+
describe("MCP tool risk from annotations", () => {
|
|
53
58
|
const fakeManager = { callTool: jest.fn() } as never;
|
|
54
59
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
toolWithAnnotations(
|
|
58
|
-
"
|
|
59
|
-
serverConfig(
|
|
60
|
+
function riskFor(server: ServerRisk, annotations?: RiskAnnotations) {
|
|
61
|
+
return createMcpTool(
|
|
62
|
+
toolWithAnnotations(annotations),
|
|
63
|
+
"server",
|
|
64
|
+
serverConfig(server),
|
|
60
65
|
fakeManager,
|
|
61
|
-
);
|
|
66
|
+
).defaultRiskLevel;
|
|
67
|
+
}
|
|
62
68
|
|
|
63
|
-
|
|
69
|
+
test("no annotations keeps the server level", () => {
|
|
70
|
+
expect(riskFor("low")).toBe(RiskLevel.Low);
|
|
71
|
+
expect(riskFor("medium")).toBe(RiskLevel.Medium);
|
|
72
|
+
expect(riskFor("high")).toBe(RiskLevel.High);
|
|
64
73
|
});
|
|
65
74
|
|
|
66
|
-
test("readOnlyHint
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"untrusted",
|
|
70
|
-
serverConfig("high"),
|
|
71
|
-
fakeManager,
|
|
72
|
-
);
|
|
75
|
+
test("readOnlyHint steps down from the medium default", () => {
|
|
76
|
+
expect(riskFor("medium", { readOnlyHint: true })).toBe(RiskLevel.Low);
|
|
77
|
+
});
|
|
73
78
|
|
|
74
|
-
|
|
79
|
+
test("readOnlyHint cannot lower a server the user pinned to high", () => {
|
|
80
|
+
expect(riskFor("high", { readOnlyHint: true })).toBe(RiskLevel.High);
|
|
75
81
|
});
|
|
76
82
|
|
|
77
|
-
test("
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
"fathom",
|
|
81
|
-
serverConfig("medium"),
|
|
82
|
-
fakeManager,
|
|
83
|
-
);
|
|
83
|
+
test("readOnlyHint cannot step below low", () => {
|
|
84
|
+
expect(riskFor("low", { readOnlyHint: true })).toBe(RiskLevel.Low);
|
|
85
|
+
});
|
|
84
86
|
|
|
85
|
-
|
|
87
|
+
test("destructiveHint steps up one level", () => {
|
|
88
|
+
expect(riskFor("low", { destructiveHint: true })).toBe(RiskLevel.Medium);
|
|
89
|
+
expect(riskFor("medium", { destructiveHint: true })).toBe(RiskLevel.High);
|
|
86
90
|
});
|
|
87
91
|
|
|
88
|
-
test("
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
test("destructiveHint cannot step above high", () => {
|
|
93
|
+
expect(riskFor("high", { destructiveHint: true })).toBe(RiskLevel.High);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("destructiveHint wins when a server sends both", () => {
|
|
97
|
+
expect(
|
|
98
|
+
riskFor("medium", { readOnlyHint: true, destructiveHint: true }),
|
|
99
|
+
).toBe(RiskLevel.High);
|
|
100
|
+
});
|
|
95
101
|
|
|
96
|
-
|
|
102
|
+
test("hints set to false leave the server level alone", () => {
|
|
103
|
+
expect(
|
|
104
|
+
riskFor("medium", { readOnlyHint: false, destructiveHint: false }),
|
|
105
|
+
).toBe(RiskLevel.Medium);
|
|
97
106
|
});
|
|
98
107
|
});
|
|
99
108
|
|
|
@@ -774,6 +774,7 @@ describe("MediaStreamSttSession", () => {
|
|
|
774
774
|
|
|
775
775
|
expect(resolveStreamingTranscriber).toHaveBeenCalledTimes(1);
|
|
776
776
|
expect(resolveStreamingTranscriber).toHaveBeenCalledWith({
|
|
777
|
+
role: "telephony",
|
|
777
778
|
sampleRate: 16_000,
|
|
778
779
|
utteranceBoundaryFinals: true,
|
|
779
780
|
utteranceEndMs: 1000,
|
|
@@ -787,6 +788,7 @@ describe("MediaStreamSttSession", () => {
|
|
|
787
788
|
const { session } = await startStreamingSession();
|
|
788
789
|
|
|
789
790
|
expect(resolveStreamingTranscriber).toHaveBeenCalledWith({
|
|
791
|
+
role: "telephony",
|
|
790
792
|
sampleRate: 16_000,
|
|
791
793
|
utteranceBoundaryFinals: true,
|
|
792
794
|
utteranceEndMs: 2500,
|
|
@@ -629,3 +629,109 @@ describe("access-request instruction enforcement", () => {
|
|
|
629
629
|
expect(decision.renderedCopy.vellum?.body).not.toContain("reject");
|
|
630
630
|
});
|
|
631
631
|
});
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* A question is the one payload whose copy cannot be composed: the guardian is
|
|
635
|
+
* choosing between the words it carries. The engine replaces title / body /
|
|
636
|
+
* deliveryText wholesale with the model's version, which is how a question
|
|
637
|
+
* reached a channel as a summary of itself with its options missing.
|
|
638
|
+
*/
|
|
639
|
+
describe("ask_question copy is pinned to the question", () => {
|
|
640
|
+
const askQuestionSignal = (): NotificationSignal => ({
|
|
641
|
+
signalId: "sig-ask-question-1",
|
|
642
|
+
createdAt: Date.now(),
|
|
643
|
+
sourceChannel: "slack",
|
|
644
|
+
sourceContextId: "conv-1",
|
|
645
|
+
sourceEventName: "guardian.question",
|
|
646
|
+
contextPayload: {
|
|
647
|
+
requestId: "req-ask-1",
|
|
648
|
+
requestCode: "08B619",
|
|
649
|
+
requestKind: "pending_question",
|
|
650
|
+
questionText: "What should I dig into?",
|
|
651
|
+
options: [
|
|
652
|
+
{ id: "opt-thread", label: "This Slack thread" },
|
|
653
|
+
{ id: "opt-pr", label: "The pull request" },
|
|
654
|
+
],
|
|
655
|
+
},
|
|
656
|
+
attentionHints: {
|
|
657
|
+
requiresAction: true,
|
|
658
|
+
urgency: "high",
|
|
659
|
+
isAsyncBackground: false,
|
|
660
|
+
visibleInSourceNow: false,
|
|
661
|
+
},
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
beforeEach(() => {
|
|
665
|
+
configuredProvider = { sendMessage: async () => ({ content: [] }) };
|
|
666
|
+
// The tool-use envelope `classifyWithLLM` reads, not the decision fields
|
|
667
|
+
// alone: it takes `toolBlock.input`, and the field names inside must be
|
|
668
|
+
// the ones `validateDecisionOutput` requires. Either mismatch drops the
|
|
669
|
+
// decision to the fallback path, where this suite's own copy is correct
|
|
670
|
+
// and the pin under test never runs.
|
|
671
|
+
extractedToolUse = {
|
|
672
|
+
name: "record_notification_decision",
|
|
673
|
+
input: {
|
|
674
|
+
shouldNotify: true,
|
|
675
|
+
selectedChannels: ["slack"],
|
|
676
|
+
reasoningSummary: "guardian needs to answer",
|
|
677
|
+
dedupeKey: "ask-question-req-ask-1",
|
|
678
|
+
confidence: 0.9,
|
|
679
|
+
renderedCopy: {
|
|
680
|
+
slack: {
|
|
681
|
+
title: "Guardian question",
|
|
682
|
+
body: "Guardian wants to know where to focus",
|
|
683
|
+
deliveryText: "Guardian wants to know where to focus",
|
|
684
|
+
},
|
|
685
|
+
},
|
|
686
|
+
},
|
|
687
|
+
};
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
test("the composed paraphrase does not replace the question", async () => {
|
|
691
|
+
const decision = await evaluateSignal(askQuestionSignal(), [
|
|
692
|
+
"slack",
|
|
693
|
+
] as NotificationChannel[]);
|
|
694
|
+
|
|
695
|
+
// Guards the fixture itself: a shape the validator rejects would fall
|
|
696
|
+
// back, and the assertions below would then be testing the fallback copy
|
|
697
|
+
// rather than the pin that overrides a composed paraphrase.
|
|
698
|
+
expect(decision.fallbackUsed).toBe(false);
|
|
699
|
+
|
|
700
|
+
const copy = decision.renderedCopy.slack;
|
|
701
|
+
// `deliveryText` is what the Slack adapter sends, ahead of body and title.
|
|
702
|
+
expect(copy?.deliveryText).toContain("What should I dig into?");
|
|
703
|
+
expect(copy?.deliveryText).not.toContain("wants to know where to focus");
|
|
704
|
+
expect(copy?.body).toContain("What should I dig into?");
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
test("the options ride along, numbered as the resolver orders them", async () => {
|
|
708
|
+
const decision = await evaluateSignal(askQuestionSignal(), [
|
|
709
|
+
"slack",
|
|
710
|
+
] as NotificationChannel[]);
|
|
711
|
+
|
|
712
|
+
expect(decision.fallbackUsed).toBe(false);
|
|
713
|
+
const delivered = decision.renderedCopy.slack?.deliveryText ?? "";
|
|
714
|
+
expect(delivered).toContain("1. This Slack thread");
|
|
715
|
+
expect(delivered).toContain("2. The pull request");
|
|
716
|
+
expect(delivered).toContain("08B619");
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
test("a voice tool approval keeps its composed copy", async () => {
|
|
720
|
+
const signal = askQuestionSignal();
|
|
721
|
+
// A `pending_question` carrying a tool name is an approval, not a
|
|
722
|
+
// question, so composing its copy is correct.
|
|
723
|
+
signal.contextPayload = {
|
|
724
|
+
...signal.contextPayload,
|
|
725
|
+
toolName: "bash",
|
|
726
|
+
options: undefined,
|
|
727
|
+
};
|
|
728
|
+
const decision = await evaluateSignal(signal, [
|
|
729
|
+
"slack",
|
|
730
|
+
] as NotificationChannel[]);
|
|
731
|
+
|
|
732
|
+
expect(decision.fallbackUsed).toBe(false);
|
|
733
|
+
expect(decision.renderedCopy.slack?.deliveryText).toBe(
|
|
734
|
+
"Guardian wants to know where to focus",
|
|
735
|
+
);
|
|
736
|
+
});
|
|
737
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DiscordAdapter delivery behavior.
|
|
3
|
+
*
|
|
4
|
+
* The invariant under test: the adapter addresses the *person*. Every send
|
|
5
|
+
* and update resolves the DM channel from the guardian's user snowflake at
|
|
6
|
+
* call time, so a room id stored anywhere can never become a delivery
|
|
7
|
+
* target, and approval notifications carry the typed-command instructions
|
|
8
|
+
* because nothing ingests component presses yet.
|
|
9
|
+
*/
|
|
10
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
const dmOpens: string[] = [];
|
|
13
|
+
const sendCalls: Array<{ channelId: string; text: string }> = [];
|
|
14
|
+
const editCalls: Array<{ channelId: string; messageId: string; text: string }> =
|
|
15
|
+
[];
|
|
16
|
+
|
|
17
|
+
mock.module("../messaging/providers/discord/api.js", () => ({
|
|
18
|
+
openDiscordDmChannel: async (userId: string) => {
|
|
19
|
+
dmOpens.push(userId);
|
|
20
|
+
return `dm-for-${userId}`;
|
|
21
|
+
},
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
mock.module("../messaging/providers/discord/send.js", () => ({
|
|
25
|
+
sendDiscordReply: async (target: { channelId: string }, text: string) => {
|
|
26
|
+
sendCalls.push({ channelId: target.channelId, text });
|
|
27
|
+
return { lastMessageId: String(2000 + sendCalls.length) };
|
|
28
|
+
},
|
|
29
|
+
editDiscordMessage: async (
|
|
30
|
+
target: { channelId: string },
|
|
31
|
+
messageId: string,
|
|
32
|
+
text: string,
|
|
33
|
+
) => {
|
|
34
|
+
editCalls.push({ channelId: target.channelId, messageId, text });
|
|
35
|
+
},
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
import type {
|
|
39
|
+
ChannelDeliveryPayload,
|
|
40
|
+
ChannelDestination,
|
|
41
|
+
} from "../notifications/types.js";
|
|
42
|
+
|
|
43
|
+
const { DiscordAdapter } = await import("../notifications/adapters/discord.js");
|
|
44
|
+
|
|
45
|
+
const GUARDIAN_USER_ID = "111222333444555666";
|
|
46
|
+
|
|
47
|
+
function makePayload(
|
|
48
|
+
overrides?: Partial<ChannelDeliveryPayload>,
|
|
49
|
+
): ChannelDeliveryPayload {
|
|
50
|
+
return {
|
|
51
|
+
sourceEventName: "schedule.notify",
|
|
52
|
+
copy: {
|
|
53
|
+
title: "Heads up",
|
|
54
|
+
body: "Your task finished.",
|
|
55
|
+
},
|
|
56
|
+
urgency: "medium",
|
|
57
|
+
...overrides,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function makeDestination(
|
|
62
|
+
overrides?: Partial<ChannelDestination>,
|
|
63
|
+
): ChannelDestination {
|
|
64
|
+
return {
|
|
65
|
+
channel: "discord",
|
|
66
|
+
endpoint: GUARDIAN_USER_ID,
|
|
67
|
+
...overrides,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
beforeEach(() => {
|
|
72
|
+
dmOpens.length = 0;
|
|
73
|
+
sendCalls.length = 0;
|
|
74
|
+
editCalls.length = 0;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("DiscordAdapter.send", () => {
|
|
78
|
+
test("resolves the DM channel from the guardian's user snowflake", async () => {
|
|
79
|
+
const adapter = new DiscordAdapter();
|
|
80
|
+
const result = await adapter.send(makePayload(), makeDestination());
|
|
81
|
+
|
|
82
|
+
expect(result.success).toBe(true);
|
|
83
|
+
expect(result.messageId).toBe("2001");
|
|
84
|
+
expect(dmOpens).toEqual([GUARDIAN_USER_ID]);
|
|
85
|
+
expect(sendCalls).toHaveLength(1);
|
|
86
|
+
expect(sendCalls[0].channelId).toBe(`dm-for-${GUARDIAN_USER_ID}`);
|
|
87
|
+
expect(sendCalls[0].text).toContain("Your task finished.");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("approval notifications carry the typed-command instructions", async () => {
|
|
91
|
+
const adapter = new DiscordAdapter();
|
|
92
|
+
const result = await adapter.send(
|
|
93
|
+
makePayload({
|
|
94
|
+
approvalContext: {
|
|
95
|
+
requestId: "req-1",
|
|
96
|
+
actions: [{ id: "approve_once", label: "Approve once" }],
|
|
97
|
+
plainTextFallback: 'Reply "approve" or "reject" to decide.',
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
makeDestination(),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
expect(result.success).toBe(true);
|
|
104
|
+
expect(sendCalls).toHaveLength(1);
|
|
105
|
+
expect(sendCalls[0].text).toContain(
|
|
106
|
+
'Reply "approve" or "reject" to decide.',
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("a destination without a guardian user id fails without calling the API", async () => {
|
|
111
|
+
const adapter = new DiscordAdapter();
|
|
112
|
+
const result = await adapter.send(
|
|
113
|
+
makePayload(),
|
|
114
|
+
makeDestination({ endpoint: undefined }),
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
expect(result.success).toBe(false);
|
|
118
|
+
expect(dmOpens).toHaveLength(0);
|
|
119
|
+
expect(sendCalls).toHaveLength(0);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("DiscordAdapter.update", () => {
|
|
124
|
+
test("edits the card through the same person-resolved DM channel", async () => {
|
|
125
|
+
const adapter = new DiscordAdapter();
|
|
126
|
+
const result = await adapter.update(
|
|
127
|
+
{
|
|
128
|
+
deliveryId: "delivery-1",
|
|
129
|
+
destination: GUARDIAN_USER_ID,
|
|
130
|
+
messageId: "2001",
|
|
131
|
+
},
|
|
132
|
+
{ body: "This approval request has been resolved." },
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
expect(result.success).toBe(true);
|
|
136
|
+
expect(result.messageId).toBe("2001");
|
|
137
|
+
expect(dmOpens).toEqual([GUARDIAN_USER_ID]);
|
|
138
|
+
expect(editCalls).toEqual([
|
|
139
|
+
{
|
|
140
|
+
channelId: `dm-for-${GUARDIAN_USER_ID}`,
|
|
141
|
+
messageId: "2001",
|
|
142
|
+
text: "This approval request has been resolved.",
|
|
143
|
+
},
|
|
144
|
+
]);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("a delivery row without a captured message id cannot be updated", async () => {
|
|
148
|
+
const adapter = new DiscordAdapter();
|
|
149
|
+
const result = await adapter.update(
|
|
150
|
+
{
|
|
151
|
+
deliveryId: "delivery-2",
|
|
152
|
+
destination: GUARDIAN_USER_ID,
|
|
153
|
+
messageId: null,
|
|
154
|
+
},
|
|
155
|
+
{ body: "resolved" },
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
expect(result.success).toBe(false);
|
|
159
|
+
expect(result.error).toContain("missing_message_id");
|
|
160
|
+
expect(editCalls).toHaveLength(0);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -133,6 +133,7 @@ mock.module("../inbound/public-ingress-urls.js", () => ({
|
|
|
133
133
|
// ---------------------------------------------------------------------------
|
|
134
134
|
|
|
135
135
|
import { orchestrateOAuthConnect } from "../oauth/connect-orchestrator.js";
|
|
136
|
+
import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
|
|
136
137
|
import { setConfig } from "./helpers/set-config.js";
|
|
137
138
|
|
|
138
139
|
/** Seed `ingress.publicBaseUrl` in the real workspace config. */
|
|
@@ -219,6 +220,7 @@ beforeEach(() => {
|
|
|
219
220
|
setPublicBaseUrl("");
|
|
220
221
|
mockIdentityResult = "user@example.com";
|
|
221
222
|
mockProviderStore = {};
|
|
223
|
+
setOverridesForTesting({});
|
|
222
224
|
|
|
223
225
|
mockPrepareResult = {
|
|
224
226
|
authorizeUrl: "https://provider.example.com/authorize?prepared",
|
|
@@ -805,3 +807,96 @@ describe("orchestrateOAuthConnect — transport selection", () => {
|
|
|
805
807
|
});
|
|
806
808
|
});
|
|
807
809
|
});
|
|
810
|
+
|
|
811
|
+
// ---------------------------------------------------------------------------
|
|
812
|
+
// Feature-flag gating
|
|
813
|
+
// ---------------------------------------------------------------------------
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* A provider whose seed entry declares a `featureFlag` is only connectable
|
|
817
|
+
* while that flag is enabled. The orchestrator is the shared choke point for
|
|
818
|
+
* every connect path (runtime routes, gateway, CLI, credential vault tool),
|
|
819
|
+
* so the gate lives here rather than in each entry point.
|
|
820
|
+
*/
|
|
821
|
+
describe("orchestrateOAuthConnect — feature-flag gating", () => {
|
|
822
|
+
const GATED_PROVIDER = makeProviderRow({
|
|
823
|
+
provider: "gated",
|
|
824
|
+
displayLabel: "Gated",
|
|
825
|
+
loopbackPort: 17399,
|
|
826
|
+
featureFlag: "gated-provider-flag",
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
test("refuses to connect a gated provider when its flag is disabled", async () => {
|
|
830
|
+
mockProviderStore["gated"] = GATED_PROVIDER;
|
|
831
|
+
setOverridesForTesting({ "gated-provider-flag": false });
|
|
832
|
+
|
|
833
|
+
const result = await orchestrateOAuthConnect({
|
|
834
|
+
service: "gated",
|
|
835
|
+
clientId: "client-id",
|
|
836
|
+
isInteractive: false,
|
|
837
|
+
callbackTransport: "loopback",
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
expect(result.success).toBe(false);
|
|
841
|
+
// No authorization flow may be started for a gated provider.
|
|
842
|
+
expect(lastPrepareArgs).toBeNull();
|
|
843
|
+
expect(lastStartArgs).toBeNull();
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
test("gated provider is indistinguishable from an unregistered one", async () => {
|
|
847
|
+
mockProviderStore["gated"] = GATED_PROVIDER;
|
|
848
|
+
setOverridesForTesting({ "gated-provider-flag": false });
|
|
849
|
+
|
|
850
|
+
const gated = await orchestrateOAuthConnect({
|
|
851
|
+
service: "gated",
|
|
852
|
+
clientId: "client-id",
|
|
853
|
+
isInteractive: false,
|
|
854
|
+
callbackTransport: "loopback",
|
|
855
|
+
});
|
|
856
|
+
const absent = await orchestrateOAuthConnect({
|
|
857
|
+
service: "not-a-provider",
|
|
858
|
+
clientId: "client-id",
|
|
859
|
+
isInteractive: false,
|
|
860
|
+
callbackTransport: "loopback",
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
expect(gated.success).toBe(false);
|
|
864
|
+
expect(absent.success).toBe(false);
|
|
865
|
+
if (gated.success || absent.success) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
// Identical wording so the gate does not leak that the provider exists.
|
|
869
|
+
expect(gated.error).toBe(absent.error.replace("not-a-provider", "gated"));
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
test("connects a gated provider once its flag is enabled", async () => {
|
|
873
|
+
mockProviderStore["gated"] = GATED_PROVIDER;
|
|
874
|
+
setOverridesForTesting({ "gated-provider-flag": true });
|
|
875
|
+
|
|
876
|
+
const result = await orchestrateOAuthConnect({
|
|
877
|
+
service: "gated",
|
|
878
|
+
clientId: "client-id",
|
|
879
|
+
isInteractive: false,
|
|
880
|
+
callbackTransport: "loopback",
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
expect(result.success).toBe(true);
|
|
884
|
+
expect(lastPrepareArgs).not.toBeNull();
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
test("leaves ungated providers connectable", async () => {
|
|
888
|
+
mockProviderStore["google"] = GOOGLE_PROVIDER;
|
|
889
|
+
setOverridesForTesting({});
|
|
890
|
+
|
|
891
|
+
const result = await orchestrateOAuthConnect({
|
|
892
|
+
service: "google",
|
|
893
|
+
clientId: "client-id",
|
|
894
|
+
isInteractive: false,
|
|
895
|
+
callbackTransport: "loopback",
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
expect(GOOGLE_PROVIDER.featureFlag).toBeNull();
|
|
899
|
+
expect(result.success).toBe(true);
|
|
900
|
+
expect(lastPrepareArgs).not.toBeNull();
|
|
901
|
+
});
|
|
902
|
+
});
|
|
@@ -2,18 +2,16 @@
|
|
|
2
2
|
* Regression test for the durable-downscale path of the image-too-large
|
|
3
3
|
* recovery (JARVIS-1041 review follow-up).
|
|
4
4
|
*
|
|
5
|
-
* When an oversized stored image
|
|
6
|
-
*
|
|
7
|
-
* the
|
|
5
|
+
* When an oversized stored image can be shrunk,
|
|
6
|
+
* `persistUnsendableImageDowngrades` must write the downscaled bytes back to
|
|
7
|
+
* the DB, not leave the original in place. The
|
|
8
8
|
* latest tool-result media is intentionally kept in context, so leaving the
|
|
9
9
|
* full-size block would rehydrate and re-reject on every later turn instead of
|
|
10
10
|
* durably self-healing the conversation.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* file (the test runner isolates each file in its own process) to avoid
|
|
16
|
-
* disturbing the no-op-resize cases in persist-unsendable-image.test.ts.
|
|
12
|
+
* The encoder is mocked here to isolate the durable persistence behavior from
|
|
13
|
+
* image decoding. The mock is process-global, so this case lives in its own
|
|
14
|
+
* file to avoid disturbing the no-op resize cases.
|
|
17
15
|
*/
|
|
18
16
|
|
|
19
17
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
@@ -43,9 +43,9 @@ function resetTables(): void {
|
|
|
43
43
|
/**
|
|
44
44
|
* Build a minimal PNG whose IHDR declares the given dimensions. Only the
|
|
45
45
|
* 8-byte signature and the width/height fields (read by `parseImageDimensions`)
|
|
46
|
-
* need to be correct; the rest is padding.
|
|
47
|
-
*
|
|
48
|
-
*
|
|
46
|
+
* need to be correct; the rest is padding. The image is not decodable, so
|
|
47
|
+
* `optimizeImageForTransport` leaves it unchanged and produces the unsendable
|
|
48
|
+
* stored-image condition.
|
|
49
49
|
*/
|
|
50
50
|
function makePngBase64(width: number, height: number, padBytes = 0): string {
|
|
51
51
|
const header = Buffer.from(
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { existsSync, rmSync } from "node:fs";
|
|
2
2
|
import { homedir, tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { join, win32 } from "node:path";
|
|
4
4
|
import { afterAll, afterEach, describe, expect, test } from "bun:test";
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
ensureDataDir,
|
|
8
|
+
formatHomeRelativePath,
|
|
8
9
|
getDataDir,
|
|
9
10
|
getDbPath,
|
|
10
11
|
getHistoryPath,
|
|
@@ -64,6 +65,23 @@ afterEach(() => {
|
|
|
64
65
|
// Workspace helpers resolve under VELLUM_WORKSPACE_DIR when set,
|
|
65
66
|
// otherwise under ~/.vellum/workspace.
|
|
66
67
|
describe("path characterization", () => {
|
|
68
|
+
test("formats Windows workspace paths relative to the home directory", () => {
|
|
69
|
+
expect(
|
|
70
|
+
formatHomeRelativePath(
|
|
71
|
+
"C:\\Users\\Alice\\.vellum\\workspace",
|
|
72
|
+
"C:\\Users\\Alice",
|
|
73
|
+
win32,
|
|
74
|
+
),
|
|
75
|
+
).toBe("~\\.vellum\\workspace");
|
|
76
|
+
expect(
|
|
77
|
+
formatHomeRelativePath(
|
|
78
|
+
"D:\\Vellum\\workspace",
|
|
79
|
+
"C:\\Users\\Alice",
|
|
80
|
+
win32,
|
|
81
|
+
),
|
|
82
|
+
).toBe("D:\\Vellum\\workspace");
|
|
83
|
+
});
|
|
84
|
+
|
|
67
85
|
test("all path helpers resolve to expected locations", () => {
|
|
68
86
|
// Without VELLUM_WORKSPACE_DIR override, workspace is under ~/.vellum
|
|
69
87
|
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
@@ -128,7 +128,7 @@ const BASELINE: Record<string, readonly string[]> = {
|
|
|
128
128
|
"../../../../daemon/embedding-reconcile.js",
|
|
129
129
|
"../../../../daemon/trust-context.js",
|
|
130
130
|
"../../../../daemon/turn-latency-sub-spans.js",
|
|
131
|
-
"../../../../notifications/
|
|
131
|
+
"../../../../notifications/background-failure-signal.js",
|
|
132
132
|
"../../../../persistence/checkpoints.js",
|
|
133
133
|
"../../../../persistence/conversation-types.js",
|
|
134
134
|
"../../../../persistence/db-connection.js",
|
|
@@ -173,6 +173,10 @@ const BASELINE: Record<string, readonly string[]> = {
|
|
|
173
173
|
"../../../context/strip-injections.js",
|
|
174
174
|
"../../../context/token-estimator.js",
|
|
175
175
|
"../../../conversations/job-handlers/summarization.js",
|
|
176
|
+
// The standalone memory worker hosts real agent conversations, so its
|
|
177
|
+
// entry point starts the same eviction sweep the daemon starts at
|
|
178
|
+
// startup. No plugin-api equivalent.
|
|
179
|
+
"../../../daemon/conversation-evictor.js",
|
|
176
180
|
"../../../daemon/date-context.js",
|
|
177
181
|
"../../../daemon/disk-pressure-background-gate.js",
|
|
178
182
|
"../../../daemon/embedding-reconcile.js",
|
|
@@ -19,6 +19,23 @@ function makeConfig(): AssistantConfig {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
describe("getVisibleProviderCatalog", () => {
|
|
22
|
+
test("hides Vellum-hosted GPU models unless developer mode is on", () => {
|
|
23
|
+
setOverridesForTesting({});
|
|
24
|
+
const hidden = getVisibleProviderCatalog(makeConfig());
|
|
25
|
+
expect(hidden.find((p) => p.id === "vellum")).toBeUndefined();
|
|
26
|
+
expect(
|
|
27
|
+
hidden
|
|
28
|
+
.flatMap((p) => p.models)
|
|
29
|
+
.some((m) => m.id === "qwen/qwen3-8b"),
|
|
30
|
+
).toBe(false);
|
|
31
|
+
|
|
32
|
+
setOverridesForTesting({ "settings-developer-nav": true });
|
|
33
|
+
const visible = getVisibleProviderCatalog(makeConfig());
|
|
34
|
+
const vellum = visible.find((p) => p.id === "vellum");
|
|
35
|
+
expect(vellum).toBeDefined();
|
|
36
|
+
expect(vellum!.models.map((m) => m.id)).toEqual(["qwen/qwen3-8b"]);
|
|
37
|
+
});
|
|
38
|
+
|
|
22
39
|
test("shows openai-compatible endpoints unconditionally (GA'ed)", () => {
|
|
23
40
|
setOverridesForTesting({});
|
|
24
41
|
|