@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,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connect and disconnect the Discord bot: the assistant's own identity in a
|
|
3
|
+
* server, not a grant to act as the person who authorized it. The user-scoped
|
|
4
|
+
* `discord` OAuth provider is a different surface and is untouched here.
|
|
5
|
+
*
|
|
6
|
+
* Storing the credential IS the connection. The gateway's Discord client is
|
|
7
|
+
* credential-gated (`gateway/src/index.ts`, "Discord Gateway lifecycle"): the
|
|
8
|
+
* watcher's next tick starts it when the token appears and tears it down when
|
|
9
|
+
* it goes, so there is no webhook to register and no restart to arrange.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { CHANNEL_BOT_PROVIDER } from "@vellumai/service-contracts/channels";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
getConfig,
|
|
17
|
+
invalidateConfigCache,
|
|
18
|
+
loadRawConfig,
|
|
19
|
+
saveRawConfig,
|
|
20
|
+
setNestedValue,
|
|
21
|
+
} from "../../config/loader.js";
|
|
22
|
+
import { DISCORD_API_BASE_URL } from "../../messaging/providers/discord/api.js";
|
|
23
|
+
import {
|
|
24
|
+
ensureManualTokenConnection,
|
|
25
|
+
removeManualTokenConnection,
|
|
26
|
+
} from "../../oauth/manual-token-connection.js";
|
|
27
|
+
import { credentialKey } from "../../security/credential-key.js";
|
|
28
|
+
import {
|
|
29
|
+
deleteSecureKeyAsync,
|
|
30
|
+
getSecureKeyAsync,
|
|
31
|
+
setSecureKeyAsync,
|
|
32
|
+
} from "../../security/secure-keys.js";
|
|
33
|
+
import {
|
|
34
|
+
deleteCredentialMetadata,
|
|
35
|
+
upsertCredentialMetadata,
|
|
36
|
+
} from "../../tools/credentials/metadata-store.js";
|
|
37
|
+
import { ChannelConfigResultBaseSchema } from "./channel-config-result.js";
|
|
38
|
+
import { log } from "./shared.js";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* From the shared vocabulary rather than a literal: `manual-token-providers`
|
|
42
|
+
* declares this bot's credential fields under the same constant, and a second
|
|
43
|
+
* spelling would be free to disagree with it.
|
|
44
|
+
*/
|
|
45
|
+
const DISCORD_PROVIDER = CHANNEL_BOT_PROVIDER.discord;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The fields read back from Discord when a token validates. Parsed rather than
|
|
49
|
+
* cast: this runs at a network boundary and what it keeps is written into
|
|
50
|
+
* config, so a shape Discord did not send should fail here rather than persist
|
|
51
|
+
* as an undefined the reader silently tolerates.
|
|
52
|
+
*/
|
|
53
|
+
const DiscordBotIdentitySchema = z.object({
|
|
54
|
+
id: z.string().optional(),
|
|
55
|
+
username: z.string().optional(),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The application read, including its own install settings. The portal's
|
|
60
|
+
* Installation page writes Default Install Settings to
|
|
61
|
+
* `integration_types_config["0"].oauth2_install_params` (`0` is
|
|
62
|
+
* GUILD_INSTALL); top-level `install_params` is the older in-app
|
|
63
|
+
* authorization link, still served for apps that configured it.
|
|
64
|
+
*/
|
|
65
|
+
const DiscordInstallParamsSchema = z.object({
|
|
66
|
+
scopes: z.array(z.string()).optional(),
|
|
67
|
+
});
|
|
68
|
+
const DiscordApplicationSchema = z.object({
|
|
69
|
+
id: z.string().optional(),
|
|
70
|
+
install_params: DiscordInstallParamsSchema.optional(),
|
|
71
|
+
integration_types_config: z
|
|
72
|
+
.record(
|
|
73
|
+
z.string(),
|
|
74
|
+
z.object({
|
|
75
|
+
oauth2_install_params: DiscordInstallParamsSchema.optional(),
|
|
76
|
+
}),
|
|
77
|
+
)
|
|
78
|
+
.optional(),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The permission integer a parameterized invite link requests: View Channels,
|
|
83
|
+
* Send Messages, Send Messages in Threads, Embed Links, Attach Files, Read
|
|
84
|
+
* Message History, Add Reactions, Use External Emojis, Use Slash Commands.
|
|
85
|
+
* The setup skill's invite script derives the same integer from named bits;
|
|
86
|
+
* skills are import-isolated from this package, so the value is stated here
|
|
87
|
+
* and pinned equal by the connect test.
|
|
88
|
+
*/
|
|
89
|
+
const DISCORD_INVITE_PERMISSIONS = "277025770560";
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The install link for the application, total over every app state.
|
|
93
|
+
*
|
|
94
|
+
* An app whose own install settings grant the `bot` scope gets a link
|
|
95
|
+
* carrying only the client id, so the grant is whatever the owner configured
|
|
96
|
+
* and an edit on the portal's Installation page takes effect. Any other app,
|
|
97
|
+
* including the fresh one the wizard's create step just made, gets the
|
|
98
|
+
* parameterized link: a client-id-only link installs nothing without
|
|
99
|
+
* settings, and explicit scope and permission parameters are honored
|
|
100
|
+
* regardless of them.
|
|
101
|
+
*/
|
|
102
|
+
function buildDiscordInviteUrl(
|
|
103
|
+
app: z.infer<typeof DiscordApplicationSchema>,
|
|
104
|
+
): string | undefined {
|
|
105
|
+
if (!app.id) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
const url = new URL("https://discord.com/oauth2/authorize");
|
|
109
|
+
url.searchParams.set("client_id", app.id);
|
|
110
|
+
const settingsScopes =
|
|
111
|
+
app.integration_types_config?.["0"]?.oauth2_install_params?.scopes ??
|
|
112
|
+
app.install_params?.scopes ??
|
|
113
|
+
[];
|
|
114
|
+
if (!settingsScopes.includes("bot")) {
|
|
115
|
+
url.searchParams.set("permissions", DISCORD_INVITE_PERMISSIONS);
|
|
116
|
+
url.searchParams.set("scope", "bot");
|
|
117
|
+
}
|
|
118
|
+
return url.toString();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const DiscordChannelConfigResultSchema =
|
|
122
|
+
ChannelConfigResultBaseSchema.extend({
|
|
123
|
+
/** The bot's own account, read back from Discord when the token validates. */
|
|
124
|
+
botUserId: z.string().optional(),
|
|
125
|
+
botUsername: z.string().optional(),
|
|
126
|
+
/**
|
|
127
|
+
* The application the bot belongs to. Read from Discord rather than
|
|
128
|
+
* assumed equal to the bot user id: they coincide today, and anything
|
|
129
|
+
* built on that assumption breaks silently if it ever stops being true.
|
|
130
|
+
*/
|
|
131
|
+
applicationId: z.string().optional(),
|
|
132
|
+
/**
|
|
133
|
+
* The install link for the bot's application, computed daemon-side from
|
|
134
|
+
* the application's own install settings so no client re-derives the
|
|
135
|
+
* grant rules. Absent until a token has validated.
|
|
136
|
+
*/
|
|
137
|
+
inviteUrl: z.string().optional(),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
export type DiscordChannelConfigResult = z.infer<
|
|
141
|
+
typeof DiscordChannelConfigResultSchema
|
|
142
|
+
>;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* A failed operation still reports the current state. For Discord "connected"
|
|
146
|
+
* and "has a token" are one question, since the gateway's client exists while
|
|
147
|
+
* the credential does.
|
|
148
|
+
*/
|
|
149
|
+
async function failure(error: string): Promise<DiscordChannelConfigResult> {
|
|
150
|
+
const hasToken = await hasStoredBotToken();
|
|
151
|
+
return { success: false, hasBotToken: hasToken, connected: hasToken, error };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function hasStoredBotToken(): Promise<boolean> {
|
|
155
|
+
return Boolean(
|
|
156
|
+
await getSecureKeyAsync(credentialKey(DISCORD_PROVIDER, "bot_token")),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Current state, without touching anything. */
|
|
161
|
+
export async function getDiscordChannelConfig(): Promise<DiscordChannelConfigResult> {
|
|
162
|
+
const { discord } = getConfig();
|
|
163
|
+
const hasToken = await hasStoredBotToken();
|
|
164
|
+
return {
|
|
165
|
+
success: true,
|
|
166
|
+
hasBotToken: hasToken,
|
|
167
|
+
connected: hasToken,
|
|
168
|
+
...(discord.botUserId ? { botUserId: discord.botUserId } : {}),
|
|
169
|
+
...(discord.botUsername ? { botUsername: discord.botUsername } : {}),
|
|
170
|
+
...(discord.applicationId ? { applicationId: discord.applicationId } : {}),
|
|
171
|
+
...(discord.inviteUrl ? { inviteUrl: discord.inviteUrl } : {}),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Validate a bot token with Discord and store it.
|
|
177
|
+
*
|
|
178
|
+
* Recording the bot's own account is what lets a connected panel name it
|
|
179
|
+
* rather than say "connected": the gateway learns that identity only at
|
|
180
|
+
* READY, which no caller here can wait for.
|
|
181
|
+
*/
|
|
182
|
+
export async function setDiscordChannelConfig(
|
|
183
|
+
botToken?: string,
|
|
184
|
+
): Promise<DiscordChannelConfigResult> {
|
|
185
|
+
if (!botToken?.trim()) {
|
|
186
|
+
return failure("botToken is required");
|
|
187
|
+
}
|
|
188
|
+
const token = botToken.trim();
|
|
189
|
+
|
|
190
|
+
let botUserId: string | undefined;
|
|
191
|
+
let botUsername: string | undefined;
|
|
192
|
+
let applicationId: string | undefined;
|
|
193
|
+
let inviteUrl: string | undefined;
|
|
194
|
+
try {
|
|
195
|
+
// Not `callDiscordApi`: that resolves the stored credential, and this
|
|
196
|
+
// token is not stored yet. Validating first is what keeps an invalid one
|
|
197
|
+
// out of secure storage, where it would start a connection that cannot
|
|
198
|
+
// authenticate.
|
|
199
|
+
const headers = { Authorization: `Bot ${token}` };
|
|
200
|
+
const signal = () => AbortSignal.timeout(10_000);
|
|
201
|
+
// Both answer from the token alone, so they go together. The application
|
|
202
|
+
// read is optional and swallows its own failure: it enriches the result
|
|
203
|
+
// and must never be the reason a valid token is rejected.
|
|
204
|
+
const [res, appRes] = await Promise.all([
|
|
205
|
+
fetch(`${DISCORD_API_BASE_URL}/users/@me`, {
|
|
206
|
+
headers,
|
|
207
|
+
signal: signal(),
|
|
208
|
+
}),
|
|
209
|
+
fetch(`${DISCORD_API_BASE_URL}/oauth2/applications/@me`, {
|
|
210
|
+
headers,
|
|
211
|
+
signal: signal(),
|
|
212
|
+
}).catch(() => null),
|
|
213
|
+
]);
|
|
214
|
+
if (!res.ok) {
|
|
215
|
+
throw new Error(`Discord returned ${res.status}`);
|
|
216
|
+
}
|
|
217
|
+
const me = DiscordBotIdentitySchema.parse(await res.json());
|
|
218
|
+
botUserId = me.id;
|
|
219
|
+
botUsername = me.username;
|
|
220
|
+
|
|
221
|
+
if (appRes?.ok) {
|
|
222
|
+
const app = DiscordApplicationSchema.safeParse(await appRes.json());
|
|
223
|
+
if (app.success) {
|
|
224
|
+
applicationId = app.data.id;
|
|
225
|
+
inviteUrl = buildDiscordInviteUrl(app.data);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} catch (err) {
|
|
229
|
+
// The body is not echoed: this path runs with a secret in hand, and the
|
|
230
|
+
// caller only needs to know the token was rejected.
|
|
231
|
+
log.warn({ err }, "Discord bot token validation failed");
|
|
232
|
+
return failure(
|
|
233
|
+
"Discord rejected this bot token. Check it was copied from the Bot tab of your application, and reset it there if it was shown before.",
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const stored = await setSecureKeyAsync(
|
|
238
|
+
credentialKey(DISCORD_PROVIDER, "bot_token"),
|
|
239
|
+
token,
|
|
240
|
+
);
|
|
241
|
+
if (!stored) {
|
|
242
|
+
return failure("Failed to store the bot token in secure storage");
|
|
243
|
+
}
|
|
244
|
+
upsertCredentialMetadata(DISCORD_PROVIDER, "bot_token", {});
|
|
245
|
+
|
|
246
|
+
const raw = loadRawConfig();
|
|
247
|
+
setNestedValue(raw, "discord.botUserId", botUserId ?? "");
|
|
248
|
+
setNestedValue(raw, "discord.botUsername", botUsername ?? "");
|
|
249
|
+
setNestedValue(raw, "discord.applicationId", applicationId ?? "");
|
|
250
|
+
setNestedValue(raw, "discord.inviteUrl", inviteUrl ?? "");
|
|
251
|
+
saveRawConfig(raw);
|
|
252
|
+
invalidateConfigCache();
|
|
253
|
+
|
|
254
|
+
await ensureManualTokenConnection(
|
|
255
|
+
DISCORD_PROVIDER,
|
|
256
|
+
botUsername ? `@${botUsername}` : undefined,
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
log.info({ botUserId, botUsername }, "Discord bot token stored");
|
|
260
|
+
return {
|
|
261
|
+
success: true,
|
|
262
|
+
hasBotToken: true,
|
|
263
|
+
connected: true,
|
|
264
|
+
...(botUserId ? { botUserId } : {}),
|
|
265
|
+
...(botUsername ? { botUsername } : {}),
|
|
266
|
+
...(applicationId ? { applicationId } : {}),
|
|
267
|
+
...(inviteUrl ? { inviteUrl } : {}),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Clear the stored credential, which disconnects the bot.
|
|
273
|
+
*
|
|
274
|
+
* A legacy `discord.allowedChannelIds` entry, where one exists, is left
|
|
275
|
+
* alone on purpose: the gateway still enforces it as persisted operator
|
|
276
|
+
* intent, so reconnecting the same bot must not silently widen its room
|
|
277
|
+
* scope by clearing it here.
|
|
278
|
+
*/
|
|
279
|
+
export async function clearDiscordChannelConfig(): Promise<DiscordChannelConfigResult> {
|
|
280
|
+
const deleted = await deleteSecureKeyAsync(
|
|
281
|
+
credentialKey(DISCORD_PROVIDER, "bot_token"),
|
|
282
|
+
);
|
|
283
|
+
if (deleted === "error") {
|
|
284
|
+
// The token is still stored, so the gateway's client stays connected: a
|
|
285
|
+
// success here would report a disconnect that did not happen.
|
|
286
|
+
return failure("Failed to delete the bot token from secure storage");
|
|
287
|
+
}
|
|
288
|
+
deleteCredentialMetadata(DISCORD_PROVIDER, "bot_token");
|
|
289
|
+
await removeManualTokenConnection(DISCORD_PROVIDER);
|
|
290
|
+
|
|
291
|
+
const raw = loadRawConfig();
|
|
292
|
+
setNestedValue(raw, "discord.botUserId", "");
|
|
293
|
+
setNestedValue(raw, "discord.botUsername", "");
|
|
294
|
+
setNestedValue(raw, "discord.applicationId", "");
|
|
295
|
+
setNestedValue(raw, "discord.inviteUrl", "");
|
|
296
|
+
saveRawConfig(raw);
|
|
297
|
+
invalidateConfigCache();
|
|
298
|
+
|
|
299
|
+
log.info("Discord bot token cleared");
|
|
300
|
+
return { success: true, hasBotToken: false, connected: false };
|
|
301
|
+
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
normalizePublicBaseUrl,
|
|
6
6
|
parseRecordedAssistantId,
|
|
7
7
|
parseTunnelRecord,
|
|
8
|
+
selectRestartTunnelRecord,
|
|
8
9
|
type TunnelRecord,
|
|
9
10
|
} from "@vellumai/service-contracts/ingress";
|
|
10
11
|
|
|
@@ -60,6 +61,14 @@ export function loadPairingTunnelRecord(): TunnelRecord | null {
|
|
|
60
61
|
return parseTunnelRecord(readIngressSection()[INGRESS_PAIRING_TUNNEL_KEY]);
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Read the tunnel a reader should offer to restart: the pairing-only record
|
|
66
|
+
* when one exists, else the last that ran.
|
|
67
|
+
*/
|
|
68
|
+
export function loadRestartTunnelRecord(): TunnelRecord | null {
|
|
69
|
+
return selectRestartTunnelRecord(readIngressSection());
|
|
70
|
+
}
|
|
71
|
+
|
|
63
72
|
/**
|
|
64
73
|
* Read the assistant id the last tunnel fronted, or null when absent. The
|
|
65
74
|
* daemon cannot derive it: it scopes itself as `self` internally.
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
getCredentialMetadata,
|
|
25
25
|
upsertCredentialMetadata,
|
|
26
26
|
} from "../../tools/credentials/metadata-store.js";
|
|
27
|
+
import { ChannelConfigResultBaseSchema } from "./channel-config-result.js";
|
|
27
28
|
import { log as _log } from "./shared.js";
|
|
28
29
|
|
|
29
30
|
// -- Result type --
|
|
@@ -31,21 +32,18 @@ import { log as _log } from "./shared.js";
|
|
|
31
32
|
export const SlackThreadMode = z.enum(["mention_only", "mention_then_thread"]);
|
|
32
33
|
export type SlackThreadMode = z.infer<typeof SlackThreadMode>;
|
|
33
34
|
|
|
34
|
-
export const SlackChannelConfigResultSchema =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
error: z.string().optional(),
|
|
47
|
-
warning: z.string().optional(),
|
|
48
|
-
});
|
|
35
|
+
export const SlackChannelConfigResultSchema =
|
|
36
|
+
ChannelConfigResultBaseSchema.extend({
|
|
37
|
+
hasAppToken: z.boolean(),
|
|
38
|
+
hasUserToken: z.boolean(),
|
|
39
|
+
teamId: z.string().optional(),
|
|
40
|
+
teamName: z.string().optional(),
|
|
41
|
+
teamUrl: z.string().optional(),
|
|
42
|
+
botUserId: z.string().optional(),
|
|
43
|
+
botUsername: z.string().optional(),
|
|
44
|
+
threadMode: SlackThreadMode,
|
|
45
|
+
warning: z.string().optional(),
|
|
46
|
+
});
|
|
49
47
|
|
|
50
48
|
export type SlackChannelConfigResult = z.infer<
|
|
51
49
|
typeof SlackChannelConfigResultSchema
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
deleteCredentialMetadata,
|
|
29
29
|
upsertCredentialMetadata,
|
|
30
30
|
} from "../../tools/credentials/metadata-store.js";
|
|
31
|
+
import { ChannelConfigResultBaseSchema } from "./channel-config-result.js";
|
|
31
32
|
import { log } from "./shared.js";
|
|
32
33
|
|
|
33
34
|
const TELEGRAM_BOT_TOKEN_IN_URL_PATTERN =
|
|
@@ -61,15 +62,11 @@ function summarizeTelegramError(err: unknown): string {
|
|
|
61
62
|
|
|
62
63
|
// -- Transport-agnostic result type (omits the `type` discriminant) --
|
|
63
64
|
|
|
64
|
-
export const TelegramConfigResultSchema =
|
|
65
|
-
success: z.boolean(),
|
|
66
|
-
hasBotToken: z.boolean(),
|
|
65
|
+
export const TelegramConfigResultSchema = ChannelConfigResultBaseSchema.extend({
|
|
67
66
|
botId: z.string().optional(),
|
|
68
67
|
botUsername: z.string().optional(),
|
|
69
|
-
connected: z.boolean(),
|
|
70
68
|
hasWebhookSecret: z.boolean(),
|
|
71
69
|
lastError: z.string().optional(),
|
|
72
|
-
error: z.string().optional(),
|
|
73
70
|
commandsRegistered: z.array(z.string()).optional(),
|
|
74
71
|
warning: z.string().optional(),
|
|
75
72
|
});
|
|
@@ -43,8 +43,6 @@ export function handleConfirmationResponse(msg: ConfirmationResponse): void {
|
|
|
43
43
|
if (conversation.hasPendingConfirmation(msg.requestId)) {
|
|
44
44
|
touchConversation(conversationId);
|
|
45
45
|
conversation.handleConfirmationResponse(msg.requestId, decision, {
|
|
46
|
-
selectedPattern: msg.selectedPattern,
|
|
47
|
-
selectedScope: msg.selectedScope,
|
|
48
46
|
emissionContext: { source: "button" },
|
|
49
47
|
});
|
|
50
48
|
return;
|
|
@@ -67,6 +67,11 @@ import {
|
|
|
67
67
|
deleteManagedSkill,
|
|
68
68
|
validateManagedSkillId,
|
|
69
69
|
} from "../../skills/managed-store.js";
|
|
70
|
+
import {
|
|
71
|
+
filterSkillsByPlatform,
|
|
72
|
+
isSkillCompatibleWithPlatform,
|
|
73
|
+
skillPlatformUnavailableMessage,
|
|
74
|
+
} from "../../skills/platform-compatibility.js";
|
|
70
75
|
import type { SkillFileProvider } from "../../skills/skill-file-provider.js";
|
|
71
76
|
import { createSkillsShProvider } from "../../skills/skillssh-files.js";
|
|
72
77
|
import type { SkillAuditData } from "../../skills/skillssh-registry.js";
|
|
@@ -475,7 +480,7 @@ async function listSkillsWithCatalog(): Promise<SlimSkillResponse[]> {
|
|
|
475
480
|
|
|
476
481
|
// All entries from the Vellum platform API are first-party.
|
|
477
482
|
// Create SlimSkillResponses for catalog skills not already installed.
|
|
478
|
-
const available: SlimSkillResponse[] = catalogSkills
|
|
483
|
+
const available: SlimSkillResponse[] = filterSkillsByPlatform(catalogSkills)
|
|
479
484
|
.filter((cs) => !installedIds.has(cs.id))
|
|
480
485
|
.map((cs) => catalogSkillToSlim(cs));
|
|
481
486
|
|
|
@@ -1227,11 +1232,17 @@ export async function installSkill(spec: {
|
|
|
1227
1232
|
// Bundled skills are already available — no install needed
|
|
1228
1233
|
const catalog = loadSkillCatalog();
|
|
1229
1234
|
|
|
1230
|
-
//
|
|
1235
|
+
// Reject install if the skill is unsupported on this host or its flag is disabled
|
|
1231
1236
|
const config = getConfig();
|
|
1232
|
-
const
|
|
1233
|
-
if (
|
|
1234
|
-
|
|
1237
|
+
const catalogSkill = catalog.find((s) => s.id === spec.slug);
|
|
1238
|
+
if (catalogSkill) {
|
|
1239
|
+
if (!isSkillCompatibleWithPlatform(catalogSkill)) {
|
|
1240
|
+
return {
|
|
1241
|
+
success: false,
|
|
1242
|
+
error: skillPlatformUnavailableMessage(spec.slug, catalogSkill),
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
const flagKey = skillFlagKey(catalogSkill);
|
|
1235
1246
|
if (flagKey && !isAssistantFeatureFlagEnabled(flagKey, config)) {
|
|
1236
1247
|
return {
|
|
1237
1248
|
success: false,
|
|
@@ -1277,6 +1288,12 @@ export async function installSkill(spec: {
|
|
|
1277
1288
|
const vellumCatalog = await getCatalog();
|
|
1278
1289
|
const catalogEntry = vellumCatalog.find((s) => s.id === spec.slug);
|
|
1279
1290
|
if (catalogEntry) {
|
|
1291
|
+
if (!isSkillCompatibleWithPlatform(catalogEntry)) {
|
|
1292
|
+
return {
|
|
1293
|
+
success: false,
|
|
1294
|
+
error: skillPlatformUnavailableMessage(spec.slug, catalogEntry),
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1280
1297
|
// Default `overwrite` to true at the handler boundary to preserve
|
|
1281
1298
|
// pre-existing HTTP API behaviour. CLI callers always pass an
|
|
1282
1299
|
// explicit boolean (`opts.overwrite ?? false`) so the CLI surface
|
|
@@ -1476,10 +1493,11 @@ export async function searchSkills(
|
|
|
1476
1493
|
// hard-coded as catalog/available.
|
|
1477
1494
|
const catalog = loadSkillCatalog();
|
|
1478
1495
|
const config = getConfig();
|
|
1479
|
-
const
|
|
1496
|
+
const compatibleCatalog = filterSkillsByPlatform(catalog);
|
|
1497
|
+
const resolved = resolveSkillStates(compatibleCatalog, config);
|
|
1480
1498
|
const resolvedById = new Map(resolved.map((r) => [r.summary.id, r]));
|
|
1481
1499
|
|
|
1482
|
-
const catalogMatches = filterByQuery(
|
|
1500
|
+
const catalogMatches = filterByQuery(compatibleCatalog, query, [
|
|
1483
1501
|
(s) => s.id,
|
|
1484
1502
|
(s) => s.displayName,
|
|
1485
1503
|
(s) => s.description,
|
package/src/daemon/lifecycle.ts
CHANGED
|
@@ -32,6 +32,7 @@ import { maybeEnqueueLexicalBackfillOnUpgrade } from "../persistence/job-handler
|
|
|
32
32
|
import { clearLifecycleQuiesce } from "../persistence/lifecycle-quiesce.js";
|
|
33
33
|
import { isPlatformClientConfigured } from "../platform/client.js";
|
|
34
34
|
import { startConsentRefresh } from "../platform/consent-cache.js";
|
|
35
|
+
import { syncAvatarToPlatform } from "../platform/sync-avatar.js";
|
|
35
36
|
import { syncWorkspaceIdentityToPlatform } from "../platform/sync-identity.js";
|
|
36
37
|
import { ensurePromptFiles } from "../prompts/system-prompt.js";
|
|
37
38
|
import { runProviderConnectionsBackfill } from "../providers/inference/backfill.js";
|
|
@@ -695,9 +696,10 @@ export async function runDaemon(): Promise<void> {
|
|
|
695
696
|
|
|
696
697
|
// Initialize providers before Qdrant so HTTP routes can begin accepting
|
|
697
698
|
// requests while Qdrant initializes, then best-effort sync the workspace
|
|
698
|
-
// identity name to the platform record.
|
|
699
|
+
// identity name and avatar to the platform record.
|
|
699
700
|
await initializeProviders(config);
|
|
700
701
|
syncWorkspaceIdentityToPlatform();
|
|
702
|
+
syncAvatarToPlatform();
|
|
701
703
|
|
|
702
704
|
// Start the idle/LRU/memory-pressure sweep over the in-memory conversation
|
|
703
705
|
// pool.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* tool-setup ↔ doordash-steps and tool-setup ↔ tool-side-effects cycles.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { InterfaceId } from "../channels/types.js";
|
|
6
|
+
import type { ClientOs, InterfaceId } from "../channels/types.js";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* How a subagent/wake tool allowlist is enforced.
|
|
@@ -51,8 +51,8 @@ export interface SubagentToolStats {
|
|
|
51
51
|
* transport interface, no channel capabilities), which drops client-gated
|
|
52
52
|
* tools (`host_*`, `ui_*`, `ask_question`, `request_system_permission`) from
|
|
53
53
|
* the wire definitions and breaks the cache prefix anyway. When this pin is
|
|
54
|
-
* set on the conversation, `isToolActiveForContext` reads `hasNoClient
|
|
55
|
-
* `transportInterface` exclusively from the pin and treats channel
|
|
54
|
+
* set on the conversation, `isToolActiveForContext` reads `hasNoClient`,
|
|
55
|
+
* `transportInterface`, and `clientOs` exclusively from the pin and treats channel
|
|
56
56
|
* capabilities as unset — an absent optional field pins the value to
|
|
57
57
|
* `undefined`; there is no fall-through to the live conversation state.
|
|
58
58
|
* (Interactive-interface turns never set channel capabilities, so unset IS
|
|
@@ -70,6 +70,8 @@ export interface WakeToolContextPin {
|
|
|
70
70
|
hasNoClient: boolean;
|
|
71
71
|
/** The interface the source's live turns ran on (e.g. `"macos"`). */
|
|
72
72
|
transportInterface?: InterfaceId;
|
|
73
|
+
/** The client OS the source's live turns reported. */
|
|
74
|
+
clientOs?: ClientOs;
|
|
73
75
|
/**
|
|
74
76
|
* Origin tag stamped onto `ToolContext.requestOrigin` for the duration of
|
|
75
77
|
* the wake (e.g. `"memory_retrospective"`). Wakes bypass `runAgentLoopImpl`,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
sameTrustIdentity,
|
|
5
|
+
type TrustContext,
|
|
6
|
+
} from "./trust-context-types.js";
|
|
7
|
+
|
|
8
|
+
function contactTrust(
|
|
9
|
+
overrides: Partial<TrustContext> = {},
|
|
10
|
+
): TrustContext {
|
|
11
|
+
return {
|
|
12
|
+
trustClass: "trusted_contact",
|
|
13
|
+
sourceChannel: "slack",
|
|
14
|
+
requesterExternalUserId: "U12345678",
|
|
15
|
+
requesterContactId: "contact-abc",
|
|
16
|
+
...overrides,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("sameTrustIdentity", () => {
|
|
21
|
+
test("two identical contact contexts compare equal", () => {
|
|
22
|
+
expect(sameTrustIdentity(contactTrust(), contactTrust())).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("a different contact id is a different actor", () => {
|
|
26
|
+
expect(
|
|
27
|
+
sameTrustIdentity(
|
|
28
|
+
contactTrust({ requesterContactId: "contact-abc" }),
|
|
29
|
+
contactTrust({ requesterContactId: "contact-xyz" }),
|
|
30
|
+
),
|
|
31
|
+
).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -75,7 +75,6 @@ mock.module("../../util/platform.js", () => ({
|
|
|
75
75
|
getSandboxWorkingDir: () => join(workspaceDir ?? fallbackDir, "sandbox/work"),
|
|
76
76
|
getSoundsDir: () => join(workspaceDir ?? fallbackDir, "sounds"),
|
|
77
77
|
getAvatarDir: () => join(workspaceDir ?? fallbackDir, "avatar"),
|
|
78
|
-
AVATAR_IMAGE_FILENAME: "avatar-image.png",
|
|
79
78
|
getAvatarImagePath: () =>
|
|
80
79
|
join(workspaceDir ?? fallbackDir, "avatar/avatar-image.png"),
|
|
81
80
|
|
|
@@ -277,6 +277,13 @@ const FLUX_ON = {
|
|
|
277
277
|
eotTimeoutMs: 500,
|
|
278
278
|
} as const satisfies Partial<LiveVoiceFluxConfig>;
|
|
279
279
|
|
|
280
|
+
// Turn-end is on by schema default, so a session that must run the local
|
|
281
|
+
// silence boundary has to say so. Naming it keeps those tests about the
|
|
282
|
+
// boundary they exercise rather than about a default they no longer own.
|
|
283
|
+
const FLUX_OFF = {
|
|
284
|
+
turnEnd: { enabled: false },
|
|
285
|
+
} as const satisfies Partial<LiveVoiceFluxConfig>;
|
|
286
|
+
|
|
280
287
|
describe("LiveVoiceSession Flux end-of-turn", () => {
|
|
281
288
|
test("commits the turn on turn-end without an endpoint-decision leg", async () => {
|
|
282
289
|
const { frames, session, transcribers, turnCalls } = createHarness({
|
|
@@ -331,6 +338,7 @@ describe("LiveVoiceSession Flux end-of-turn", () => {
|
|
|
331
338
|
|
|
332
339
|
test("ignores turn-end when the flag is off and keeps the hold path", async () => {
|
|
333
340
|
const { frames, session, transcribers, turnCalls } = createHarness({
|
|
341
|
+
fluxConfig: FLUX_OFF,
|
|
334
342
|
startVoiceTurn: autoCompletingTurn(),
|
|
335
343
|
});
|
|
336
344
|
|
|
@@ -420,8 +428,9 @@ describe("LiveVoiceSession Flux end-of-turn", () => {
|
|
|
420
428
|
|
|
421
429
|
test("records the commit latency on the front-door path too, with no hold", async () => {
|
|
422
430
|
const { frames, session, transcribers, turnCalls } = createHarness({
|
|
423
|
-
//
|
|
431
|
+
// Turn-end off: the latch is down and the local silence boundary owns
|
|
424
432
|
// the commit, which is arm A of the measurement run.
|
|
433
|
+
fluxConfig: FLUX_OFF,
|
|
425
434
|
silenceThresholdMs: 40,
|
|
426
435
|
emitMetrics: true,
|
|
427
436
|
startVoiceTurn: autoCompletingTurn(),
|
|
@@ -823,7 +832,10 @@ describe("LiveVoiceSession Flux end-of-turn", () => {
|
|
|
823
832
|
describe("LiveVoiceSession Flux end-of-turn during the STT dial", () => {
|
|
824
833
|
beforeEach(() => {
|
|
825
834
|
setConfig("services", {
|
|
826
|
-
stt: {
|
|
835
|
+
stt: {
|
|
836
|
+
provider: "deepgram",
|
|
837
|
+
providers: { deepgram: { model: "flux" } },
|
|
838
|
+
},
|
|
827
839
|
});
|
|
828
840
|
});
|
|
829
841
|
|
|
@@ -912,11 +924,53 @@ describe("LiveVoiceSession Flux end-of-turn during the STT dial", () => {
|
|
|
912
924
|
await session.close("client_end");
|
|
913
925
|
});
|
|
914
926
|
|
|
927
|
+
test("seeds the latch from the live-voice role, not the global provider", async () => {
|
|
928
|
+
// The configuration roles exist for: live voice on flux while the global
|
|
929
|
+
// stays on a family that owns no turn boundary. Seeding from the global
|
|
930
|
+
// leaves the latch false, and the opening utterance closes its silence
|
|
931
|
+
// boundary on the caller's side before the dial can correct it.
|
|
932
|
+
setConfig("services", {
|
|
933
|
+
stt: {
|
|
934
|
+
provider: "deepgram",
|
|
935
|
+
providers: { deepgram: {} },
|
|
936
|
+
roles: { liveVoice: { provider: "deepgram", model: "flux" } },
|
|
937
|
+
},
|
|
938
|
+
});
|
|
939
|
+
const gate = createDialGate();
|
|
940
|
+
const { frames, session, transcribers, turnCalls } = createHarness({
|
|
941
|
+
fluxConfig: FLUX_ON,
|
|
942
|
+
silenceThresholdMs: 40,
|
|
943
|
+
resolveGate: gate.promise,
|
|
944
|
+
startVoiceTurn: autoCompletingTurn(),
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
await session.start();
|
|
948
|
+
await session.handleBinaryAudio(LOUD_CHUNK);
|
|
949
|
+
await sleep(PAST_SILENCE_BOUNDARY_MS);
|
|
950
|
+
|
|
951
|
+
// Deferred under the seeded latch: Flux owns this boundary.
|
|
952
|
+
expect(countFrames(frames, "utterance_end")).toBe(0);
|
|
953
|
+
expect(turnCalls).toHaveLength(0);
|
|
954
|
+
|
|
955
|
+
gate.open();
|
|
956
|
+
await waitFor(() => transcribers.length > 0);
|
|
957
|
+
transcribers[0]?.endOfTurn("what is the weather");
|
|
958
|
+
|
|
959
|
+
await waitFor(
|
|
960
|
+
() => turnCalls.length === 1,
|
|
961
|
+
"The opening utterance never committed on the Flux end-of-turn",
|
|
962
|
+
);
|
|
963
|
+
expect(turnCalls[0]?.content).toBe("what is the weather");
|
|
964
|
+
expect(countFrames(frames, "utterance_end")).toBe(1);
|
|
965
|
+
|
|
966
|
+
await session.close("client_end");
|
|
967
|
+
});
|
|
968
|
+
|
|
915
969
|
test("never seeds the latch when the flag is off", async () => {
|
|
916
970
|
const gate = createDialGate();
|
|
917
971
|
const { frames, session, transcribers, turnCalls } = createHarness({
|
|
918
|
-
//
|
|
919
|
-
|
|
972
|
+
// Turn-end explicitly off while config still selects the flux family.
|
|
973
|
+
fluxConfig: FLUX_OFF,
|
|
920
974
|
silenceThresholdMs: 40,
|
|
921
975
|
resolveGate: gate.promise,
|
|
922
976
|
startVoiceTurn: autoCompletingTurn(),
|