@vellumai/assistant 0.11.6 → 0.11.7-staging.1
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/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/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/openapi.yaml +419 -55
- package/package.json +4 -1
- 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-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__/discord-callback-round-trip.test.ts +93 -0
- package/src/__tests__/discord-channel-config.test.ts +237 -0
- 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-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__/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-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 +20 -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 +6 -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 +18 -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 +259 -11
- 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-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 +213 -8
- 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/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/seed-providers.ts +261 -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 +85 -133
- 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__/retry-callsite.test.ts +55 -0
- 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 +231 -3
- package/src/providers/inference/auth.ts +1 -0
- 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 +75 -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/provider-resolvability.ts +20 -0
- 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/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 +21 -9
- 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/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/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/oauth2.ts +7 -0
- package/src/security/openai-device-auth.ts +364 -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/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/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
|
@@ -25,6 +25,26 @@ export function isVisionNotSupportedError(message: string): boolean {
|
|
|
25
25
|
return VISION_NOT_SUPPORTED_PATTERNS.some((re) => re.test(message));
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// Provider prose from server-side chat-template renderers that failed to
|
|
29
|
+
// serialize the request into the model's prompt format. Emitted by
|
|
30
|
+
// vLLM/Jinja-style template engines behind OpenAI-compatible endpoints, e.g.
|
|
31
|
+
// Together serving MiniMax M3: "Failed to apply chat template: invalid
|
|
32
|
+
// operation: object is not callable (in chat:22)".
|
|
33
|
+
export const CHAT_TEMPLATE_FAILURE_PATTERNS = [
|
|
34
|
+
/appl\w* (?:the )?chat[ _-]?template/i,
|
|
35
|
+
/chat[ _-]?template (?:error|render)/i,
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Whether a provider error message indicates the endpoint's server-side chat
|
|
40
|
+
* template failed to render the request. These endpoints typically only
|
|
41
|
+
* handle plain-string message content, so structured shapes (content-parts
|
|
42
|
+
* arrays, tool payloads) are what trip the renderer.
|
|
43
|
+
*/
|
|
44
|
+
export function isChatTemplateFailureError(message: string): boolean {
|
|
45
|
+
return CHAT_TEMPLATE_FAILURE_PATTERNS.some((re) => re.test(message));
|
|
46
|
+
}
|
|
47
|
+
|
|
28
48
|
// Vendor-neutral (OpenRouter/Anthropic-style) credit-exhaustion prose. Also
|
|
29
49
|
// covers per-key spend caps: OpenRouter returns 403 "Key limit exceeded" when a
|
|
30
50
|
// key's configured credit limit is reached — a billing/budget condition that
|
package/src/util/spawn.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Shared spawn-with-timeout helper used by media-processing and transcribe tools.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { delimiter } from "node:path";
|
|
6
|
+
|
|
5
7
|
/** Full video preprocessing: mpdecimate analysis, frame extraction, palette analysis. Longest operation. */
|
|
6
8
|
export const FFMPEG_PREPROCESS_TIMEOUT_MS = 600_000;
|
|
7
9
|
|
|
@@ -29,9 +31,14 @@ export function spawnWithTimeout(
|
|
|
29
31
|
stderr: "pipe",
|
|
30
32
|
env: {
|
|
31
33
|
...process.env,
|
|
32
|
-
PATH: [
|
|
34
|
+
PATH: [
|
|
35
|
+
process.env.PATH,
|
|
36
|
+
...(process.platform === "darwin"
|
|
37
|
+
? ["/opt/homebrew/bin", "/usr/local/bin"]
|
|
38
|
+
: []),
|
|
39
|
+
]
|
|
33
40
|
.filter(Boolean)
|
|
34
|
-
.join(
|
|
41
|
+
.join(delimiter),
|
|
35
42
|
},
|
|
36
43
|
});
|
|
37
44
|
const timer = setTimeout(() => {
|
|
@@ -12,6 +12,7 @@ import { join } from "node:path";
|
|
|
12
12
|
import { promisify } from "node:util";
|
|
13
13
|
|
|
14
14
|
import { getConfig } from "../config/loader.js";
|
|
15
|
+
import { isFileUnheld } from "../util/file-use.js";
|
|
15
16
|
import { getLogger } from "../util/logger.js";
|
|
16
17
|
import { Mutex } from "../util/mutex.js";
|
|
17
18
|
import { PromiseGuard } from "../util/promise-guard.js";
|
|
@@ -419,20 +420,9 @@ export class WorkspaceGitService {
|
|
|
419
420
|
return;
|
|
420
421
|
}
|
|
421
422
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
const { stdout } = await execFileAsync("lsof", ["-t", lockPath], {
|
|
426
|
-
timeout: 3000,
|
|
427
|
-
});
|
|
428
|
-
if (stdout.length > 0) {
|
|
429
|
-
log.debug("index.lock held by an active process, skipping removal");
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
} catch {
|
|
433
|
-
// lsof unavailable or errored — fall through to remove.
|
|
434
|
-
// On platforms without lsof this degrades to unconditional removal,
|
|
435
|
-
// which is the same as the previous behavior.
|
|
423
|
+
if (!(await isFileUnheld(lockPath))) {
|
|
424
|
+
log.debug("Could not prove index.lock is stale, skipping removal");
|
|
425
|
+
return;
|
|
436
426
|
}
|
|
437
427
|
|
|
438
428
|
try {
|
|
@@ -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
|
+
}
|