@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,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace migration `147-rename-colliding-backup-profile-names`.
|
|
3
|
+
*
|
|
4
|
+
* The four managed backup profile keys (`balanced-backup`,
|
|
5
|
+
* `quality-optimized-backup`, `cost-optimized-backup`,
|
|
6
|
+
* `latency-optimized-backup`) became reserved code-owned names. From that
|
|
7
|
+
* release on, a name in the code-owned set resolves to the code body no
|
|
8
|
+
* matter what `llm.profiles` holds, so a user-created profile that happens
|
|
9
|
+
* to carry one of those names is silently replaced in the effective catalog:
|
|
10
|
+
* the user's own model, provider, and tuning stop being what their
|
|
11
|
+
* `activeProfile`, call-site pin, mix arm, or `fallbackProfile` reference
|
|
12
|
+
* routes through.
|
|
13
|
+
*
|
|
14
|
+
* This migration runs before the reservation can take effect (workspace
|
|
15
|
+
* migrations run ahead of the first config load and the profile seeder) and
|
|
16
|
+
* gives any such profile a fresh key, rewriting every reference to it so the
|
|
17
|
+
* user keeps both the profile and its wiring:
|
|
18
|
+
*
|
|
19
|
+
* - `llm.profiles` key (insertion order preserved)
|
|
20
|
+
* - `llm.activeProfile`, `llm.advisorProfile`
|
|
21
|
+
* - `llm.profileOrder` entries
|
|
22
|
+
* - `llm.callSites.<site>.profile` pins
|
|
23
|
+
* - `llm.profiles.<key>.mix[].profile` arms
|
|
24
|
+
* - `llm.profiles.<key>.fallbackProfile` targets
|
|
25
|
+
* - `conversations.inference_profile` and `cron_jobs.inference_profile`
|
|
26
|
+
* rows (see below)
|
|
27
|
+
*
|
|
28
|
+
* The new key is `<name>-custom`, suffixed `-custom-2`, `-custom-3`, ... when
|
|
29
|
+
* that is taken. Candidates are checked against every existing profile key
|
|
30
|
+
* AND every reserved code-defined name, so a rename never lands on another
|
|
31
|
+
* name the catalog owns.
|
|
32
|
+
*
|
|
33
|
+
* Only workspace-owned profiles are renamed. An entry whose `source` is
|
|
34
|
+
* `managed` is a thin stub for a code-owned profile, not a user profile, and
|
|
35
|
+
* a non-object value under one of these keys is not a profile at all (the
|
|
36
|
+
* loader strips it); both are left alone.
|
|
37
|
+
*
|
|
38
|
+
* The DB rewrite is NOT best effort. A pin the rewrite abandons does not
|
|
39
|
+
* degrade to the default selection: its old name is now a reserved code-owned
|
|
40
|
+
* key, so the conversation or schedule silently starts routing through the
|
|
41
|
+
* managed backup instead of the user's profile. So a database that exists but
|
|
42
|
+
* cannot be opened or written is retried a bounded number of times with a
|
|
43
|
+
* short backoff, and a failure that outlives the retries throws. The runner
|
|
44
|
+
* then records a `failed` checkpoint, and `retryFailedCheckpoint` makes the
|
|
45
|
+
* next boot re-run the whole migration. An absent database file is a real
|
|
46
|
+
* state (no pins to strand) and a table the schema does not carry yet is
|
|
47
|
+
* nothing to rewrite; both are skipped without failing.
|
|
48
|
+
*
|
|
49
|
+
* The DB rewrite deliberately runs BEFORE the config write, so a throw leaves
|
|
50
|
+
* config.json holding the old keys and the next run rebuilds the same
|
|
51
|
+
* mapping from it.
|
|
52
|
+
*
|
|
53
|
+
* Idempotent: after a successful run no workspace-owned profile carries a
|
|
54
|
+
* reserved backup name, so a re-run finds nothing to rename and writes
|
|
55
|
+
* nothing. The pin rewrite matches on the old name, so re-running it after a
|
|
56
|
+
* partial pass is a no-op on the rows already repointed.
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
60
|
+
import { join } from "node:path";
|
|
61
|
+
import { Database } from "bun:sqlite";
|
|
62
|
+
|
|
63
|
+
import { getLogger } from "../../util/logger.js";
|
|
64
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
65
|
+
|
|
66
|
+
const log = getLogger(
|
|
67
|
+
"workspace-migration-147-rename-colliding-backup-profile-names",
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The newly reserved keys this migration clears. Frozen snapshot of
|
|
72
|
+
* `BACKUP_PROFILE_KEYS` as of 2026-08-21 (migrations are self-contained).
|
|
73
|
+
*/
|
|
74
|
+
const BACKUP_PROFILE_KEYS = [
|
|
75
|
+
"balanced-backup",
|
|
76
|
+
"quality-optimized-backup",
|
|
77
|
+
"cost-optimized-backup",
|
|
78
|
+
"latency-optimized-backup",
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Every code-defined profile name as of 2026-08-21: the default keys, the
|
|
83
|
+
* backups, and the flag-gated `os-beta`. A renamed profile must not land on
|
|
84
|
+
* any of them, or the rename would just move the collision.
|
|
85
|
+
*/
|
|
86
|
+
const RESERVED_PROFILE_NAMES = new Set<string>([
|
|
87
|
+
"balanced",
|
|
88
|
+
"quality-optimized",
|
|
89
|
+
"cost-optimized",
|
|
90
|
+
"latency-optimized",
|
|
91
|
+
...BACKUP_PROFILE_KEYS,
|
|
92
|
+
"os-beta",
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
const RENAME_SUFFIX = "-custom";
|
|
96
|
+
|
|
97
|
+
/** Total attempts at the pin rewrite, the first one included. */
|
|
98
|
+
const DB_ATTEMPTS = 4;
|
|
99
|
+
|
|
100
|
+
/** First backoff step; each further wait doubles it (50, 100, 200 ms). */
|
|
101
|
+
const DB_RETRY_BASE_DELAY_MS = 50;
|
|
102
|
+
|
|
103
|
+
export const renameCollidingBackupProfileNamesMigration: WorkspaceMigration = {
|
|
104
|
+
id: "147-rename-colliding-backup-profile-names",
|
|
105
|
+
description:
|
|
106
|
+
"Rename user profiles colliding with the reserved managed backup keys and rewrite their references",
|
|
107
|
+
// The failure this migration can hit is a database it cannot write, and
|
|
108
|
+
// both halves of the run are idempotent, so a failed checkpoint is worth
|
|
109
|
+
// re-running: the config still holds the old keys, the mapping rebuilds
|
|
110
|
+
// from them, and the pin rewrite matches on the old name.
|
|
111
|
+
retryFailedCheckpoint: true,
|
|
112
|
+
|
|
113
|
+
async run(workspaceDir: string): Promise<void> {
|
|
114
|
+
const configPath = join(workspaceDir, "config.json");
|
|
115
|
+
if (!existsSync(configPath)) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let config: Record<string, unknown>;
|
|
120
|
+
try {
|
|
121
|
+
const parsed = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
122
|
+
if (!isPlainObject(parsed)) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
config = parsed;
|
|
126
|
+
} catch {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const llm = asObject(config.llm);
|
|
131
|
+
const profiles = llm === null ? null : asObject(llm.profiles);
|
|
132
|
+
if (llm === null || profiles === null) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const renames = planRenames(profiles);
|
|
137
|
+
if (renames.size === 0) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// The DB rewrite runs first so an interrupted or failed migration re-runs
|
|
142
|
+
// with the config still holding the old keys, i.e. with the mapping
|
|
143
|
+
// intact. A rewrite that cannot be completed throws out of here, which
|
|
144
|
+
// stops the config rename below rather than stranding the pins.
|
|
145
|
+
await rewriteDbReferences(workspaceDir, renames);
|
|
146
|
+
|
|
147
|
+
llm.profiles = renameProfileKeys(profiles, renames);
|
|
148
|
+
rewriteConfigReferences(llm, renames);
|
|
149
|
+
|
|
150
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
151
|
+
log.info(
|
|
152
|
+
{ renames: Object.fromEntries(renames) },
|
|
153
|
+
"Renamed user profiles colliding with reserved managed backup keys",
|
|
154
|
+
);
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
down(_workspaceDir: string): void {
|
|
158
|
+
// Forward-only: the reserved names cannot be handed back.
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Old key -> new key for every workspace-owned profile sitting on a reserved
|
|
164
|
+
* backup name. Deterministic: keys are considered in `BACKUP_PROFILE_KEYS`
|
|
165
|
+
* order and each assigned name is reserved against later candidates, so the
|
|
166
|
+
* same config always produces the same mapping.
|
|
167
|
+
*/
|
|
168
|
+
function planRenames(profiles: Record<string, unknown>): Map<string, string> {
|
|
169
|
+
const renames = new Map<string, string>();
|
|
170
|
+
const taken = new Set<string>([
|
|
171
|
+
...Object.keys(profiles),
|
|
172
|
+
...RESERVED_PROFILE_NAMES,
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
for (const key of BACKUP_PROFILE_KEYS) {
|
|
176
|
+
if (!(key in profiles)) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const entry = asObject(profiles[key]);
|
|
180
|
+
if (entry === null) {
|
|
181
|
+
log.warn(
|
|
182
|
+
{ profile: key },
|
|
183
|
+
"Left a non-object value under a reserved backup key; it is not a profile to preserve",
|
|
184
|
+
);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (entry.source === "managed") {
|
|
188
|
+
// A managed stub is the workspace's overlay slot for a code-owned
|
|
189
|
+
// profile, not a user profile. Backups take no overlay, so the stub is
|
|
190
|
+
// inert; renaming it would manufacture a phantom user profile.
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
let candidate = `${key}${RENAME_SUFFIX}`;
|
|
194
|
+
let counter = 2;
|
|
195
|
+
while (taken.has(candidate)) {
|
|
196
|
+
candidate = `${key}${RENAME_SUFFIX}-${counter}`;
|
|
197
|
+
counter += 1;
|
|
198
|
+
}
|
|
199
|
+
taken.add(candidate);
|
|
200
|
+
renames.set(key, candidate);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return renames;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Rebuild the profiles record with the renamed keys, preserving order. */
|
|
207
|
+
function renameProfileKeys(
|
|
208
|
+
profiles: Record<string, unknown>,
|
|
209
|
+
renames: Map<string, string>,
|
|
210
|
+
): Record<string, unknown> {
|
|
211
|
+
const renamed: Record<string, unknown> = {};
|
|
212
|
+
for (const [name, value] of Object.entries(profiles)) {
|
|
213
|
+
renamed[renames.get(name) ?? name] = value;
|
|
214
|
+
}
|
|
215
|
+
return renamed;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Point every config reference at the renamed key. */
|
|
219
|
+
function rewriteConfigReferences(
|
|
220
|
+
llm: Record<string, unknown>,
|
|
221
|
+
renames: Map<string, string>,
|
|
222
|
+
): void {
|
|
223
|
+
for (const field of ["activeProfile", "advisorProfile"]) {
|
|
224
|
+
const current = llm[field];
|
|
225
|
+
if (typeof current === "string" && renames.has(current)) {
|
|
226
|
+
llm[field] = renames.get(current);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (Array.isArray(llm.profileOrder)) {
|
|
231
|
+
llm.profileOrder = (llm.profileOrder as unknown[]).map((name) =>
|
|
232
|
+
typeof name === "string" ? (renames.get(name) ?? name) : name,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const callSites = asObject(llm.callSites);
|
|
237
|
+
if (callSites !== null) {
|
|
238
|
+
for (const value of Object.values(callSites)) {
|
|
239
|
+
const site = asObject(value);
|
|
240
|
+
if (
|
|
241
|
+
site !== null &&
|
|
242
|
+
typeof site.profile === "string" &&
|
|
243
|
+
renames.has(site.profile)
|
|
244
|
+
) {
|
|
245
|
+
site.profile = renames.get(site.profile);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const profiles = asObject(llm.profiles);
|
|
251
|
+
if (profiles === null) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
for (const value of Object.values(profiles)) {
|
|
255
|
+
const entry = asObject(value);
|
|
256
|
+
if (entry === null) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (
|
|
260
|
+
typeof entry.fallbackProfile === "string" &&
|
|
261
|
+
renames.has(entry.fallbackProfile)
|
|
262
|
+
) {
|
|
263
|
+
entry.fallbackProfile = renames.get(entry.fallbackProfile);
|
|
264
|
+
}
|
|
265
|
+
if (!Array.isArray(entry.mix)) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
for (const armValue of entry.mix as unknown[]) {
|
|
269
|
+
const arm = asObject(armValue);
|
|
270
|
+
if (
|
|
271
|
+
arm !== null &&
|
|
272
|
+
typeof arm.profile === "string" &&
|
|
273
|
+
renames.has(arm.profile)
|
|
274
|
+
) {
|
|
275
|
+
arm.profile = renames.get(arm.profile);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Repoint the profile pins stored outside config.json: the sticky
|
|
283
|
+
* per-conversation profile and the per-schedule profile.
|
|
284
|
+
*
|
|
285
|
+
* Retried as a whole rather than per statement: opening the database, reading
|
|
286
|
+
* `sqlite_master` and running the updates all fail the same way under write
|
|
287
|
+
* contention (`SQLITE_BUSY` from a second connection), and every step is
|
|
288
|
+
* idempotent, so re-running the block is the simplest recovery that cannot
|
|
289
|
+
* double-apply. A failure that outlives the attempts throws, which fails the
|
|
290
|
+
* migration for `retryFailedCheckpoint` to pick up on the next boot.
|
|
291
|
+
*/
|
|
292
|
+
async function rewriteDbReferences(
|
|
293
|
+
workspaceDir: string,
|
|
294
|
+
renames: Map<string, string>,
|
|
295
|
+
): Promise<void> {
|
|
296
|
+
const dbPath = join(workspaceDir, "data", "db", "assistant.db");
|
|
297
|
+
if (!existsSync(dbPath)) {
|
|
298
|
+
// No database yet, so there are no pins that could be stranded.
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
await withDbRetry(() => {
|
|
302
|
+
const db = new Database(dbPath);
|
|
303
|
+
try {
|
|
304
|
+
for (const table of ["conversations", "cron_jobs"]) {
|
|
305
|
+
const present = db
|
|
306
|
+
.query(
|
|
307
|
+
`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?`,
|
|
308
|
+
)
|
|
309
|
+
.get(table);
|
|
310
|
+
if (!present) {
|
|
311
|
+
// A workspace whose schema does not carry this table holds no pins
|
|
312
|
+
// in it. Nothing to rewrite, and nothing to fail over.
|
|
313
|
+
log.info({ table }, "No profile pin table to repoint");
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
for (const [from, to] of renames) {
|
|
317
|
+
db.query(
|
|
318
|
+
`UPDATE ${table} SET inference_profile = ? WHERE inference_profile = ?`,
|
|
319
|
+
).run(to, from);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
} finally {
|
|
323
|
+
db.close();
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Run the pin rewrite, retrying a transient database failure with a short
|
|
330
|
+
* doubling backoff. Rethrows once the attempts are spent so the caller stops
|
|
331
|
+
* before the config rename.
|
|
332
|
+
*/
|
|
333
|
+
async function withDbRetry(attempt: () => void): Promise<void> {
|
|
334
|
+
for (let index = 0; ; index += 1) {
|
|
335
|
+
try {
|
|
336
|
+
attempt();
|
|
337
|
+
return;
|
|
338
|
+
} catch (err) {
|
|
339
|
+
if (index >= DB_ATTEMPTS - 1) {
|
|
340
|
+
throw new Error(
|
|
341
|
+
`Could not repoint conversation and schedule profile pins after ${DB_ATTEMPTS} attempts; leaving the config unrenamed so the next boot retries`,
|
|
342
|
+
{ cause: err },
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
log.warn(
|
|
346
|
+
{ err, attempt: index },
|
|
347
|
+
"Could not repoint profile pins; retrying",
|
|
348
|
+
);
|
|
349
|
+
await Bun.sleep(DB_RETRY_BASE_DELAY_MS * 2 ** index);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
355
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function asObject(value: unknown): Record<string, unknown> | null {
|
|
359
|
+
return isPlainObject(value) ? value : null;
|
|
360
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace migration `148-strip-unsupported-fallback-profiles`.
|
|
3
|
+
*
|
|
4
|
+
* Automatic model fallbacks are code-owned metadata on the four managed
|
|
5
|
+
* default profiles. Earlier builds accepted custom `fallbackProfile` values
|
|
6
|
+
* in raw workspace config, including pointers rewritten by migration 147.
|
|
7
|
+
* Those values cannot be executed under the managed-only fallback contract
|
|
8
|
+
* and would make later config writes fail validation.
|
|
9
|
+
*
|
|
10
|
+
* This migration removes every persisted pointer except the exact managed
|
|
11
|
+
* default mapping while managed backups resolve under the selected provider.
|
|
12
|
+
* It runs before config parsing, so legacy values are cleaned up without
|
|
13
|
+
* depending on schema salvage. The operation is idempotent: a second run
|
|
14
|
+
* finds no unsupported fields and writes nothing.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
|
|
20
|
+
import { getLogger } from "../../util/logger.js";
|
|
21
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
22
|
+
|
|
23
|
+
const log = getLogger(
|
|
24
|
+
"workspace-migration-148-strip-unsupported-fallback-profiles",
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
/** Frozen snapshot of the code-owned fallback mapping as of 2026-08-24. */
|
|
28
|
+
const CODE_OWNED_FALLBACKS: Record<string, string> = {
|
|
29
|
+
balanced: "balanced-backup",
|
|
30
|
+
"quality-optimized": "quality-optimized-backup",
|
|
31
|
+
"cost-optimized": "cost-optimized-backup",
|
|
32
|
+
"latency-optimized": "latency-optimized-backup",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const stripUnsupportedFallbackProfilesMigration: WorkspaceMigration = {
|
|
36
|
+
id: "148-strip-unsupported-fallback-profiles",
|
|
37
|
+
description:
|
|
38
|
+
"Remove custom fallback profile pointers outside the managed default contract",
|
|
39
|
+
// A transient config write failure must not leave an unsupported pointer
|
|
40
|
+
// behind permanently; the next boot can safely repeat this idempotent pass.
|
|
41
|
+
retryFailedCheckpoint: true,
|
|
42
|
+
|
|
43
|
+
run(workspaceDir: string): void {
|
|
44
|
+
const configPath = join(workspaceDir, "config.json");
|
|
45
|
+
let config: Record<string, unknown>;
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
48
|
+
if (!isPlainObject(parsed)) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
config = parsed;
|
|
52
|
+
} catch {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const llm = asObject(config.llm);
|
|
57
|
+
const profiles = llm === null ? null : asObject(llm.profiles);
|
|
58
|
+
if (llm === null || profiles === null) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const backupsResolve = backupProfilesResolveUnderDefaultProvider(
|
|
62
|
+
llm.defaultProvider,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const strippedProfiles: string[] = [];
|
|
66
|
+
for (const [name, value] of Object.entries(profiles)) {
|
|
67
|
+
const entry = asObject(value);
|
|
68
|
+
if (
|
|
69
|
+
entry === null ||
|
|
70
|
+
!Object.prototype.hasOwnProperty.call(entry, "fallbackProfile")
|
|
71
|
+
) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const isCodeOwnedMapping =
|
|
75
|
+
backupsResolve &&
|
|
76
|
+
entry.source === "managed" &&
|
|
77
|
+
entry.fallbackProfile === CODE_OWNED_FALLBACKS[name];
|
|
78
|
+
if (isCodeOwnedMapping) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
delete entry.fallbackProfile;
|
|
82
|
+
strippedProfiles.push(name);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (strippedProfiles.length === 0) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const tempPath = `${configPath}.tmp`;
|
|
89
|
+
writeFileSync(tempPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
90
|
+
renameSync(tempPath, configPath);
|
|
91
|
+
log.info(
|
|
92
|
+
{ profiles: strippedProfiles },
|
|
93
|
+
"Removed unsupported custom fallback profile pointers",
|
|
94
|
+
);
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
down(_workspaceDir: string): void {
|
|
98
|
+
// Forward-only: unsupported fallback pointers cannot be restored safely.
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
103
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function asObject(value: unknown): Record<string, unknown> | null {
|
|
107
|
+
return isPlainObject(value) ? value : null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function backupProfilesResolveUnderDefaultProvider(
|
|
111
|
+
defaultProvider: unknown,
|
|
112
|
+
): boolean {
|
|
113
|
+
const provider = isPlainObject(defaultProvider)
|
|
114
|
+
? defaultProvider.provider
|
|
115
|
+
: undefined;
|
|
116
|
+
return typeof provider !== "string" || provider === "vellum";
|
|
117
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace migration `149-repoint-backup-profile-selections`.
|
|
3
|
+
*
|
|
4
|
+
* Managed backup profiles are internal failover routes. Earlier builds let
|
|
5
|
+
* users select those names directly, so existing workspaces can retain them
|
|
6
|
+
* in active, advisor, call-site, mix, conversation, or schedule references.
|
|
7
|
+
* Repoint those direct selections to their corresponding primary profile
|
|
8
|
+
* before the user-facing catalog hides the backups. Automatic fallback
|
|
9
|
+
* metadata is preserved: a primary's `fallbackProfile` still names its
|
|
10
|
+
* internal backup route.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { Database } from "bun:sqlite";
|
|
16
|
+
|
|
17
|
+
import { FALLBACK_PROFILE_BY_KEY } from "../../config/default-profile-names.js";
|
|
18
|
+
import { getLogger } from "../../util/logger.js";
|
|
19
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
20
|
+
|
|
21
|
+
const log = getLogger(
|
|
22
|
+
"workspace-migration-149-repoint-backup-profile-selections",
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
/** Mapping of every managed backup to its direct-selectable primary. */
|
|
26
|
+
const BACKUP_TO_PRIMARY: Record<string, string> = Object.fromEntries(
|
|
27
|
+
Object.entries(FALLBACK_PROFILE_BY_KEY).map(([primary, backup]) => [
|
|
28
|
+
backup,
|
|
29
|
+
primary,
|
|
30
|
+
]),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const PROFILE_PIN_TABLES = ["conversations", "cron_jobs"] as const;
|
|
34
|
+
const DB_ATTEMPTS = 4;
|
|
35
|
+
const DB_RETRY_BASE_DELAY_MS = 50;
|
|
36
|
+
|
|
37
|
+
export const repointBackupProfileSelectionsMigration: WorkspaceMigration = {
|
|
38
|
+
id: "149-repoint-backup-profile-selections",
|
|
39
|
+
description:
|
|
40
|
+
"Repoint direct selections of managed backup profiles to their primaries",
|
|
41
|
+
retryFailedCheckpoint: true,
|
|
42
|
+
|
|
43
|
+
async run(workspaceDir: string): Promise<void> {
|
|
44
|
+
const configPath = join(workspaceDir, "config.json");
|
|
45
|
+
const config = readConfig(configPath);
|
|
46
|
+
const llm = config === null ? null : asObject(config.llm);
|
|
47
|
+
const configChanged = llm !== null && rewriteConfigReferences(llm);
|
|
48
|
+
|
|
49
|
+
// Rewrite database pins before config.json. If the database cannot be
|
|
50
|
+
// updated, leaving config untouched lets the next boot retry the same
|
|
51
|
+
// migration without stranding a direct selection.
|
|
52
|
+
await rewriteDbReferences(workspaceDir);
|
|
53
|
+
|
|
54
|
+
if (config !== null && configChanged) {
|
|
55
|
+
const tempPath = `${configPath}.tmp`;
|
|
56
|
+
writeFileSync(tempPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
57
|
+
renameSync(tempPath, configPath);
|
|
58
|
+
log.info("Repointed direct backup-profile selections to primaries");
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
down(_workspaceDir: string): void {
|
|
63
|
+
// Forward-only: restoring a backup selection would make it unreachable
|
|
64
|
+
// from the user-facing profile catalog.
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
function readConfig(path: string): Record<string, unknown> | null {
|
|
69
|
+
if (!existsSync(path)) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const parsed: unknown = JSON.parse(readFileSync(path, "utf-8"));
|
|
74
|
+
return isPlainObject(parsed) ? parsed : null;
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Rewrite config references that represent direct user profile choices. */
|
|
81
|
+
function rewriteConfigReferences(llm: Record<string, unknown>): boolean {
|
|
82
|
+
let changed = false;
|
|
83
|
+
|
|
84
|
+
for (const field of ["activeProfile", "advisorProfile"] as const) {
|
|
85
|
+
const current = llm[field];
|
|
86
|
+
const replacement = remapProfileName(current);
|
|
87
|
+
if (replacement !== current) {
|
|
88
|
+
llm[field] = replacement;
|
|
89
|
+
changed = true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const callSites = asObject(llm.callSites);
|
|
94
|
+
if (callSites !== null) {
|
|
95
|
+
for (const value of Object.values(callSites)) {
|
|
96
|
+
const site = asObject(value);
|
|
97
|
+
if (site === null) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const replacement = remapProfileName(site.profile);
|
|
101
|
+
if (replacement !== site.profile) {
|
|
102
|
+
site.profile = replacement;
|
|
103
|
+
changed = true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const profiles = asObject(llm.profiles);
|
|
109
|
+
if (profiles === null) {
|
|
110
|
+
return changed;
|
|
111
|
+
}
|
|
112
|
+
for (const value of Object.values(profiles)) {
|
|
113
|
+
const entry = asObject(value);
|
|
114
|
+
if (entry === null || !Array.isArray(entry.mix)) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
for (const armValue of entry.mix as unknown[]) {
|
|
118
|
+
const arm = asObject(armValue);
|
|
119
|
+
if (arm === null) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const replacement = remapProfileName(arm.profile);
|
|
123
|
+
if (replacement !== arm.profile) {
|
|
124
|
+
arm.profile = replacement;
|
|
125
|
+
changed = true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return changed;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function remapProfileName(value: unknown): unknown {
|
|
134
|
+
if (typeof value !== "string") {
|
|
135
|
+
return value;
|
|
136
|
+
}
|
|
137
|
+
return BACKUP_TO_PRIMARY[value] ?? value;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Rewrite conversation and schedule pins, retrying transient DB contention. */
|
|
141
|
+
async function rewriteDbReferences(workspaceDir: string): Promise<void> {
|
|
142
|
+
const path = join(workspaceDir, "data", "db", "assistant.db");
|
|
143
|
+
if (!existsSync(path)) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
await withDbRetry(() => {
|
|
148
|
+
const db = new Database(path);
|
|
149
|
+
try {
|
|
150
|
+
for (const table of PROFILE_PIN_TABLES) {
|
|
151
|
+
if (!tableHasColumn(db, table, "inference_profile")) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
for (const [backup, primary] of Object.entries(BACKUP_TO_PRIMARY)) {
|
|
155
|
+
db.query(
|
|
156
|
+
`UPDATE ${table} SET inference_profile = ? WHERE inference_profile = ?`,
|
|
157
|
+
).run(primary, backup);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} finally {
|
|
161
|
+
db.close();
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function tableHasColumn(
|
|
167
|
+
db: Database,
|
|
168
|
+
table: (typeof PROFILE_PIN_TABLES)[number],
|
|
169
|
+
column: string,
|
|
170
|
+
): boolean {
|
|
171
|
+
const rows = db.query(`PRAGMA table_info(${table})`).all() as Array<{
|
|
172
|
+
name?: unknown;
|
|
173
|
+
}>;
|
|
174
|
+
return rows.some((row) => row.name === column);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function withDbRetry(attempt: () => void): Promise<void> {
|
|
178
|
+
for (let index = 0; ; index += 1) {
|
|
179
|
+
try {
|
|
180
|
+
attempt();
|
|
181
|
+
return;
|
|
182
|
+
} catch (err) {
|
|
183
|
+
if (index >= DB_ATTEMPTS - 1) {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`Could not repoint backup-profile selections after ${DB_ATTEMPTS} attempts`,
|
|
186
|
+
{ cause: err },
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
log.warn(
|
|
190
|
+
{ err, attempt: index },
|
|
191
|
+
"Could not repoint backup-profile selections; retrying",
|
|
192
|
+
);
|
|
193
|
+
await Bun.sleep(DB_RETRY_BASE_DELAY_MS * 2 ** index);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
199
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function asObject(value: unknown): Record<string, unknown> | null {
|
|
203
|
+
return isPlainObject(value) ? value : null;
|
|
204
|
+
}
|