@vellumai/assistant 0.12.1 → 0.12.2-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/AGENTS.md +4 -0
- package/ARCHITECTURE.md +20 -21
- package/docs/architecture/turn-actor.md +15 -8
- package/docs/guardian-request-flow.md +16 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +13 -8
- package/node_modules/@vellumai/slack-text/src/label-resolution-entities.test.ts +95 -0
- package/openapi.yaml +360 -51
- package/package.json +3 -1
- package/scripts/smoke-desktop-window-theme.ts +186 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
- package/src/__tests__/anthropic-provider.test.ts +0 -63
- package/src/__tests__/app-compiler.test.ts +6 -0
- package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
- package/src/__tests__/app-dir-path-guard.test.ts +1 -3
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
- package/src/__tests__/assistant-event-hub.test.ts +44 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
- package/src/__tests__/computer-use-tools.test.ts +19 -7
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +28 -29
- package/src/__tests__/config-watcher.test.ts +23 -8
- package/src/__tests__/connection-model-compat.test.ts +2 -2
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +26 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
- package/src/__tests__/conversation-queue.test.ts +48 -207
- package/src/__tests__/conversation-retry-discard.test.ts +54 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +28 -0
- package/src/__tests__/conversation-skill-tools.test.ts +27 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
- package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-point-at-capability.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-title-service.test.ts +14 -10
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
- package/src/__tests__/credential-record-retire.test.ts +171 -1
- package/src/__tests__/credential-routes.test.ts +250 -3
- package/src/__tests__/credential-security-invariants.test.ts +5 -3
- package/src/__tests__/cu-unified-flow.test.ts +14 -4
- package/src/__tests__/document-sync-tags.test.ts +25 -0
- package/src/__tests__/drain-kick-guard.test.ts +0 -2
- package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
- package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
- package/src/__tests__/git-porcelain.test.ts +47 -0
- package/src/__tests__/heartbeat-service.test.ts +56 -40
- package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
- package/src/__tests__/host-bash-proxy.test.ts +26 -45
- package/src/__tests__/host-browser-proxy.test.ts +6 -17
- package/src/__tests__/host-cu-proxy.test.ts +596 -0
- package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
- package/src/__tests__/host-file-proxy.test.ts +11 -23
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
- package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
- package/src/__tests__/llm-catalog-parity.test.ts +25 -0
- package/src/__tests__/mcp-auth-routes.test.ts +6 -9
- package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
- package/src/__tests__/mcp-health-check.test.ts +8 -11
- package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
- package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
- package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
- package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +355 -548
- package/src/__tests__/model-intents.test.ts +3 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
- package/src/__tests__/oauth-commands-routes.test.ts +168 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +22 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/openai-provider.test.ts +1 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-callback-registration.test.ts +8 -16
- package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
- package/src/__tests__/qdrant-manager.test.ts +132 -0
- package/src/__tests__/resolve-app.test.ts +135 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
- package/src/__tests__/script-proxy-certs.test.ts +35 -0
- package/src/__tests__/secret-routes-acp-guard.test.ts +59 -1
- package/src/__tests__/service-account-token.test.ts +247 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/skills.test.ts +9 -4
- package/src/__tests__/source-fingerprint.test.ts +184 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +70 -3
- package/src/__tests__/subagent-tools.test.ts +3 -2
- package/src/__tests__/telephony-tts-guard.test.ts +38 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/ui-channel-variants.test.ts +1 -56
- package/src/__tests__/unicode.test.ts +13 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
- package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
- package/src/__tests__/workspace-git-service.test.ts +26 -0
- package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +257 -6
- package/src/acp/__tests__/acp-credentials.test.ts +13 -0
- package/src/acp/__tests__/agent-process.test.ts +258 -4
- package/src/acp/__tests__/auth-required.test.ts +96 -0
- package/src/acp/__tests__/claude-token-refresh.test.ts +257 -0
- package/src/acp/__tests__/client-handler.test.ts +90 -2
- package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +60 -1
- package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
- package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
- package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
- package/src/acp/__tests__/session-manager.test.ts +895 -4
- package/src/acp/acp-claude-oauth.ts +328 -14
- package/src/acp/acp-credentials.ts +19 -0
- package/src/acp/agent-process.test.ts +4 -1
- package/src/acp/agent-process.ts +96 -10
- package/src/acp/auth-required.ts +50 -1
- package/src/acp/auto-install.test.ts +9 -7
- package/src/acp/claude-token-refresh.ts +150 -0
- package/src/acp/client-handler.ts +16 -3
- package/src/acp/model-config.test.ts +166 -0
- package/src/acp/model-config.ts +138 -0
- package/src/acp/prepare-agent-env.ts +21 -6
- package/src/acp/resolve-agent.test.ts +220 -6
- package/src/acp/resolve-agent.ts +88 -23
- package/src/acp/session-manager.test.ts +196 -3
- package/src/acp/session-manager.ts +441 -18
- package/src/acp/types.ts +50 -5
- package/src/agent/loop.ts +1 -0
- package/src/api/events/acp-session-model-update.test.ts +88 -0
- package/src/api/events/acp-session-model-update.ts +46 -0
- package/src/api/events/app-files-changed.ts +4 -3
- package/src/api/events/notification-intent.ts +2 -0
- package/src/api/events/user-message-echo.ts +7 -1
- package/src/api/index.ts +6 -0
- package/src/api/responses/conversation-message.ts +5 -0
- package/src/apps/app-store.ts +1 -1
- package/src/apps/resolve-app.ts +81 -0
- package/src/apps/source-fingerprint.ts +266 -0
- package/src/avatar/identity-avatar.ts +7 -2
- package/src/bundler/app-compiler.ts +15 -0
- package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
- package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
- package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
- package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
- package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
- package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
- package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
- package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
- package/src/calls/__tests__/voice-control-protocol.test.ts +120 -25
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
- package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
- package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
- package/src/calls/__tests__/voice-session-bridge.test.ts +56 -1
- package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
- package/src/calls/barge-in-guard.ts +118 -0
- package/src/calls/call-constants.ts +24 -4
- package/src/calls/call-controller.ts +499 -199
- package/src/calls/call-transport.ts +16 -0
- package/src/calls/media-stream-output.ts +32 -0
- package/src/calls/media-stream-server.ts +82 -30
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/{live-voice → calls}/progress-narration.ts +2 -2
- package/src/calls/resolve-call-tts-provider.ts +33 -2
- package/src/calls/tts-call-strategy.ts +9 -5
- package/src/calls/types.ts +2 -0
- package/src/calls/voice-control-protocol.ts +155 -21
- package/src/calls/voice-leg-coordinator.ts +189 -0
- package/src/calls/voice-progress-cadence.ts +423 -0
- package/src/calls/voice-session-bridge.ts +92 -72
- package/src/calls/voice-triage-escalate.ts +178 -59
- package/src/cli/commands/__tests__/apps.test.ts +245 -0
- package/src/cli/commands/__tests__/bash.test.ts +53 -0
- package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
- package/src/cli/commands/apps.help.ts +67 -13
- package/src/cli/commands/apps.ts +183 -0
- package/src/cli/commands/bash.help.ts +8 -13
- package/src/cli/commands/bash.ts +7 -25
- package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
- package/src/cli/commands/channels/index.help.ts +70 -2
- package/src/cli/commands/channels/index.ts +16 -3
- package/src/cli/commands/channels/send.ts +128 -0
- package/src/cli/commands/credentials.help.ts +6 -2
- package/src/cli/commands/credentials.ts +2 -2
- package/src/cli/commands/mcp.help.ts +8 -8
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/oauth/status.ts +5 -0
- package/src/cli/commands/roadmap.help.ts +5 -1
- package/src/cli/commands/roadmap.ts +1 -1
- package/src/cli/lib/bundled-marketplace.json +103 -90
- package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
- package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
- package/src/config/__tests__/platform-identity.test.ts +255 -0
- package/src/config/__tests__/platform-rehydration.test.ts +99 -9
- package/src/config/acp-agent-write.ts +43 -0
- package/src/config/acp-defaults.test.ts +13 -3
- package/src/config/acp-defaults.ts +23 -6
- package/src/config/acp-schema.test.ts +81 -0
- package/src/config/acp-schema.ts +30 -2
- package/src/config/bundled-skills/acp/SKILL.md +22 -14
- package/src/config/bundled-skills/acp/TOOLS.json +5 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +29 -2
- package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
- package/src/config/bundled-skills/schedule/SKILL.md +3 -4
- package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +33 -13
- package/src/config/loader.ts +2 -0
- package/src/config/memory-v3-gate.test.ts +31 -0
- package/src/config/memory-v3-gate.ts +20 -16
- package/src/config/platform-identity.ts +201 -0
- package/src/config/platform-rehydration.ts +11 -27
- package/src/config/schema.ts +2 -2
- package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
- package/src/config/schemas/__tests__/voice.test.ts +148 -0
- package/src/config/schemas/calls.ts +1 -28
- package/src/config/schemas/live-voice.ts +1 -173
- package/src/config/schemas/mcp.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +10 -0
- package/src/config/schemas/services.ts +18 -0
- package/src/config/schemas/skills.ts +0 -14
- package/src/config/schemas/voice.ts +150 -0
- package/src/credential-execution/process-manager.test.ts +18 -19
- package/src/credential-health/credential-health-service.ts +11 -17
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
- package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
- package/src/daemon/app-change-notify.ts +45 -0
- package/src/daemon/config-watcher.ts +13 -194
- package/src/daemon/conversation-error.ts +3 -2
- package/src/daemon/conversation-media-retry.ts +2 -1
- package/src/daemon/conversation-process.ts +35 -64
- package/src/daemon/conversation-runtime-assembly.ts +20 -7
- package/src/daemon/conversation-skill-tools.ts +13 -31
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +21 -10
- package/src/daemon/conversation-tool-setup.ts +45 -59
- package/src/daemon/conversation.ts +0 -3
- package/src/daemon/dictation-profile-store.ts +2 -1
- package/src/daemon/handlers/shared.ts +2 -1
- package/src/daemon/handlers/skills.ts +6 -3
- package/src/daemon/host-bash-proxy.ts +8 -12
- package/src/daemon/host-browser-proxy.ts +15 -1
- package/src/daemon/host-cu-proxy.ts +224 -19
- package/src/daemon/host-file-proxy.ts +8 -10
- package/src/daemon/host-proxy-preactivation.ts +2 -1
- package/src/daemon/host-transfer-proxy.ts +14 -18
- package/src/daemon/lifecycle.ts +0 -5
- package/src/daemon/mcp-reload-service.ts +5 -5
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/orphan-reaper.ts +1 -1
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/reaction-history-render.ts +2 -1
- package/src/daemon/shutdown-handlers.ts +1 -9
- package/src/daemon/skill-memory-refresh.ts +64 -0
- package/src/daemon/tool-side-effects.ts +7 -18
- package/src/desktop/__tests__/fake-desktop.ts +122 -0
- package/src/desktop/desktop-chrome-policy.test.ts +79 -0
- package/src/desktop/desktop-chrome-policy.ts +47 -0
- package/src/desktop/desktop-chrome-session.ts +37 -0
- package/src/desktop/desktop-dependencies.test.ts +102 -0
- package/src/desktop/desktop-dependencies.ts +308 -0
- package/src/desktop/desktop-feature.test.ts +18 -0
- package/src/desktop/desktop-feature.ts +19 -0
- package/src/desktop/desktop-panel-config.test.ts +135 -0
- package/src/desktop/desktop-panel-config.ts +149 -0
- package/src/desktop/desktop-session-manager.test.ts +730 -0
- package/src/desktop/desktop-session-manager.ts +850 -0
- package/src/desktop/desktop-stream-bridge.test.ts +395 -0
- package/src/desktop/desktop-stream-bridge.ts +214 -0
- package/src/desktop/desktop-wallpaper.test.ts +191 -0
- package/src/desktop/desktop-wallpaper.ts +113 -0
- package/src/desktop/desktop-window-drag.ts +177 -0
- package/src/desktop/desktop-window-manager-config.test.ts +230 -0
- package/src/desktop/desktop-window-manager-config.ts +238 -0
- package/src/desktop/desktop-window-theme.ts +170 -0
- package/src/heartbeat/heartbeat-service.ts +21 -3
- package/src/home/__tests__/feed-writer.test.ts +22 -0
- package/src/home/feed-writer.ts +11 -2
- package/src/hooks/types.ts +33 -0
- package/src/i18n/AGENTS.md +22 -0
- package/src/i18n/__tests__/i18n.test.ts +108 -0
- package/src/i18n/index.ts +16 -0
- package/src/i18n/locales.ts +93 -0
- package/src/i18n/messages.ts +61 -0
- package/src/i18n/title.ts +25 -0
- package/src/inbound/platform-callback-registration.ts +4 -5
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +121 -0
- package/src/live-voice/__tests__/live-voice-events.test.ts +8 -7
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
- package/src/live-voice/__tests__/live-voice-progress.test.ts +94 -15
- package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
- package/src/live-voice/__tests__/protocol.test.ts +39 -0
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
- package/src/live-voice/__tests__/session-controls.test.ts +79 -0
- package/src/live-voice/live-voice-photo.ts +25 -6
- package/src/live-voice/live-voice-session.ts +297 -699
- package/src/live-voice/protocol.ts +60 -2
- package/src/live-voice/session-controls.ts +111 -0
- package/src/mcp/__tests__/effective-config.test.ts +2 -2
- package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
- package/src/mcp/effective-config.ts +3 -3
- package/src/mcp/mcp-header-store.ts +29 -45
- package/src/mcp/spec-schema.ts +111 -0
- package/src/mcp/startup.ts +8 -4
- package/src/mcp/workspace-mcp-config.ts +188 -0
- package/src/messaging/provider.ts +10 -3
- package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
- package/src/messaging/providers/callback-routing.ts +18 -0
- package/src/messaging/providers/channel-transport.ts +56 -0
- package/src/messaging/providers/discord/transport.ts +17 -0
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
- package/src/messaging/providers/slack/adapter.ts +7 -44
- package/src/messaging/providers/slack/transport.ts +16 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
- package/src/messaging/providers/telegram-bot/client.ts +0 -26
- package/src/messaging/providers/telegram-bot/transport.ts +20 -0
- package/src/messaging/providers/whatsapp/adapter.ts +1 -48
- package/src/messaging/providers/whatsapp/transport.ts +18 -0
- package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
- package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
- package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
- package/src/notifications/adapters/macos.ts +3 -0
- package/src/notifications/assistant-reply-producer.ts +8 -3
- package/src/notifications/conversation-pairing.ts +36 -16
- package/src/notifications/delivered-post-record.ts +11 -1
- package/src/notifications/guardian-feed-projection.ts +125 -5
- package/src/notifications/home-feed-side-effect.ts +55 -7
- package/src/notifications/schedule-result-producer.ts +66 -22
- package/src/oauth/AGENTS.md +2 -0
- package/src/oauth/__tests__/seed-providers-managed.test.ts +149 -0
- package/src/oauth/byo-connection.test.ts +105 -0
- package/src/oauth/byo-connection.ts +36 -6
- package/src/oauth/connection-resolver.test.ts +100 -0
- package/src/oauth/connection-resolver.ts +120 -3
- package/src/oauth/provider-serializer.ts +9 -0
- package/src/oauth/scope-utils.ts +24 -0
- package/src/oauth/seed-providers.ts +314 -27
- package/src/outbound-proxy/certs.ts +3 -5
- package/src/permissions/checker.test.ts +11 -6
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/conversation-crud.ts +71 -63
- package/src/persistence/conversation-lineage.ts +12 -4
- package/src/persistence/conversation-queries.ts +2 -2
- package/src/persistence/conversation-title-placeholders.ts +23 -0
- package/src/persistence/conversation-title-service.ts +9 -26
- package/src/persistence/embeddings/qdrant-manager.ts +163 -18
- package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
- package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
- package/src/persistence/message-cursor.ts +78 -0
- package/src/persistence/message-reads.ts +30 -0
- package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
- package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
- package/src/persistence/schema/memory-core.ts +5 -0
- package/src/platform/client.test.ts +4 -2
- package/src/platform/client.ts +2 -10
- package/src/plugin-api/constants.ts +2 -0
- package/src/plugin-api/index.ts +1 -0
- package/src/plugin-api/types.ts +2 -0
- package/src/plugins/defaults/index.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
- package/src/plugins/defaults/memory/context-search/format.ts +3 -2
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
- package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
- package/src/plugins/defaults/memory/hooks/init.ts +10 -0
- package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
- package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
- package/src/plugins/defaults/memory/host-utils.ts +10 -0
- package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
- package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
- package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
- package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
- package/src/plugins/defaults/memory/startup.ts +28 -2
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
- package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
- package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
- package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
- package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
- package/src/plugins/defaults/memory/v3/card.ts +3 -2
- package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
- package/src/plugins/defaults/memory/v3/sections.ts +9 -3
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
- package/src/plugins/defaults/memory/worker.ts +6 -0
- package/src/plugins/mcp-servers.ts +15 -75
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
- package/src/providers/__tests__/retry-callsite.test.ts +41 -0
- package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
- package/src/providers/anthropic/client.ts +0 -47
- package/src/providers/gemini/client.ts +9 -23
- package/src/providers/gemini-thought-signature.test.ts +63 -0
- package/src/providers/gemini-thought-signature.ts +44 -0
- package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
- package/src/providers/inference/auth.ts +2 -3
- package/src/providers/inference/connection-availability.ts +27 -8
- package/src/providers/inference/endpoint-probe.ts +10 -1
- package/src/providers/inference/resolve-auth.ts +22 -4
- package/src/providers/inference/service-account-token.ts +248 -0
- package/src/providers/model-catalog.ts +34 -0
- package/src/providers/model-intents.ts +10 -5
- package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
- package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
- package/src/providers/openai/chat-completions-provider.ts +212 -79
- package/src/providers/openai/codex-models.ts +0 -4
- package/src/providers/openai/google-thought-signature.ts +320 -0
- package/src/providers/opencode/client.test.ts +73 -4
- package/src/providers/opencode/client.ts +60 -3
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/outbound-request-sanitize.ts +96 -0
- package/src/providers/retry.ts +30 -19
- package/src/providers/unparseable-tool-args.ts +5 -1
- package/src/runtime/AGENTS.md +3 -1
- package/src/runtime/__tests__/channel-send.test.ts +443 -0
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
- package/src/runtime/__tests__/host-observe.test.ts +40 -1
- package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
- package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
- package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
- package/src/runtime/agent-wake.ts +2 -1
- package/src/runtime/assistant-event-hub.ts +21 -3
- package/src/runtime/auth/same-actor.ts +38 -3
- package/src/runtime/channel-send.ts +316 -0
- package/src/runtime/host-observe.ts +11 -1
- package/src/runtime/http-server.ts +160 -151
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +16 -5
- package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +275 -0
- package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
- package/src/runtime/routes/acp-claude-auth-routes.ts +11 -3
- package/src/runtime/routes/acp-routes.test.ts +15 -0
- package/src/runtime/routes/acp-routes.ts +72 -9
- package/src/runtime/routes/app-management-routes.ts +90 -2
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/channel-send-routes.ts +85 -0
- package/src/runtime/routes/conversation-list-routes.ts +14 -3
- package/src/runtime/routes/conversation-query-routes.ts +40 -0
- package/src/runtime/routes/conversation-routes.ts +16 -3
- package/src/runtime/routes/credential-routes.ts +20 -14
- package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
- package/src/runtime/routes/desktop-setup-routes.ts +35 -0
- package/src/runtime/routes/diagnostics-routes.ts +61 -4
- package/src/runtime/routes/events-routes.ts +4 -0
- package/src/runtime/routes/host-cu-routes.ts +6 -0
- package/src/runtime/routes/index.ts +4 -2
- package/src/runtime/routes/log-export-routes.ts +2 -0
- package/src/runtime/routes/mcp-auth-routes.ts +39 -47
- package/src/runtime/routes/migration-routes.ts +6 -20
- package/src/runtime/routes/oauth-commands-routes.ts +50 -0
- package/src/runtime/routes/oauth-providers.ts +7 -0
- package/src/runtime/routes/platform-routes.ts +18 -15
- package/src/runtime/routes/workspace-routes.test.ts +55 -26
- package/src/runtime/routes/workspace-routes.ts +46 -63
- package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
- package/src/runtime/routes/workspace-tree-walk.ts +240 -0
- package/src/runtime/routes/workspace-utils.ts +17 -0
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +16 -3
- package/src/runtime/sync/resource-sync-events.ts +2 -1
- package/src/schedule/run-script.ts +2 -1
- package/src/schedule/schedule-store.ts +9 -2
- package/src/schedule/worker.ts +1 -1
- package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
- package/src/security/ces-http-record-backend.ts +120 -0
- package/src/security/ces-rpc-record-backend.ts +7 -0
- package/src/security/untrusted-content.ts +3 -1
- package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
- package/src/skills/workspace-skill-md-signature.ts +50 -0
- package/src/subagent/consult-context.ts +12 -25
- package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
- package/src/tools/acp/list-agents.test.ts +3 -1
- package/src/tools/acp/spawn.test.ts +146 -11
- package/src/tools/acp/spawn.ts +21 -3
- package/src/tools/acp/status.test.ts +94 -0
- package/src/tools/acp/status.ts +40 -3
- package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
- package/src/tools/browser/network-recorder.ts +2 -1
- package/src/tools/capability-offer.test.ts +22 -2
- package/src/tools/capability-offer.ts +16 -1
- package/src/tools/computer-use/definitions.ts +121 -2
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
- package/src/tools/credentials/metadata-store.ts +100 -0
- package/src/tools/filesystem/search.ts +2 -1
- package/src/tools/host-filesystem/edit.test.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.test.ts +2 -1
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.test.ts +2 -1
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.test.ts +2 -1
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +16 -0
- package/src/tools/host-terminal/host-shell.ts +4 -20
- package/src/tools/network/web-search.ts +6 -1
- package/src/tools/policy-context.ts +6 -7
- package/src/tools/skills/scaffold-managed.ts +61 -3
- package/src/tools/subagent/spawn.ts +5 -8
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
- package/src/tools/terminal/sanitized-bash.ts +105 -0
- package/src/tools/ui-surface/channel-variants.ts +10 -59
- package/src/tools/watch/watch-retro-report.ts +0 -8
- package/src/types/host-capabilities.ts +5 -3
- package/src/util/__tests__/language-subtag.test.ts +23 -1
- package/src/util/ca-bundle.ts +13 -0
- package/src/util/language-subtag.ts +16 -0
- package/src/util/platform.ts +8 -0
- package/src/util/unicode.ts +15 -1
- package/src/watch/watch-retro.ts +6 -8
- package/src/watch/watch-timeline.ts +2 -2
- package/src/workflows/run-manager.ts +2 -1
- package/src/workspace/git-porcelain.ts +66 -0
- package/src/workspace/git-service.ts +6 -55
- package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
- package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
- package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
- package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/app-source-watcher.test.ts +0 -218
- package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
- package/src/__tests__/speaker-identification.test.ts +0 -56
- package/src/calls/speaker-identification.ts +0 -233
- package/src/daemon/app-source-watcher.ts +0 -218
- package/src/messaging/providers/whatsapp/client.ts +0 -29
- package/src/runtime/routes/debug-bash-routes.ts +0 -173
- package/src/util/fs-watcher-error.ts +0 -36
- /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NUL-terminated `git status --porcelain -z` parsing.
|
|
3
|
+
*
|
|
4
|
+
* Status reporting and batched staging share this parser so rename/copy
|
|
5
|
+
* origin paths cannot drift between the two readers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface PorcelainEntry {
|
|
9
|
+
/** Two-character XY status from porcelain v1. */
|
|
10
|
+
status: string;
|
|
11
|
+
/** Path git reports for this record (the destination for rename/copy). */
|
|
12
|
+
path: string;
|
|
13
|
+
/** Pre-rename/copy path. Present when the record is a rename or copy. */
|
|
14
|
+
origin?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parse NUL-terminated `git status --porcelain -z` output into status/path
|
|
19
|
+
* records. NUL termination is required so paths with special characters
|
|
20
|
+
* (non-ASCII, quotes, newlines) arrive verbatim instead of C-style quoted.
|
|
21
|
+
*
|
|
22
|
+
* A rename/copy record is followed by a bare origin-path entry, captured on
|
|
23
|
+
* {@link PorcelainEntry.origin}.
|
|
24
|
+
*/
|
|
25
|
+
export function parsePorcelainZ(stdout: string): PorcelainEntry[] {
|
|
26
|
+
const entries = stdout.split("\0");
|
|
27
|
+
const parsed: PorcelainEntry[] = [];
|
|
28
|
+
for (let i = 0; i < entries.length; i++) {
|
|
29
|
+
const entry = entries[i] ?? "";
|
|
30
|
+
if (entry.length < 4) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const status = entry.substring(0, 2);
|
|
34
|
+
const path = entry.substring(3);
|
|
35
|
+
if (status[0] === "R" || status[0] === "C") {
|
|
36
|
+
i++;
|
|
37
|
+
const origin = entries[i];
|
|
38
|
+
if (origin && origin.length > 0) {
|
|
39
|
+
parsed.push({ status, path, origin });
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
parsed.push({ status, path });
|
|
44
|
+
}
|
|
45
|
+
return parsed;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Every path git status knows about, including rename/copy origins.
|
|
50
|
+
* Origins still need to be staged so a deletion is not left behind when a
|
|
51
|
+
* rename is split across add batches.
|
|
52
|
+
*/
|
|
53
|
+
export function collectDirtyPathsFromPorcelain(stdout: string): string[] {
|
|
54
|
+
const paths: string[] = [];
|
|
55
|
+
const seen = new Set<string>();
|
|
56
|
+
for (const entry of parsePorcelainZ(stdout)) {
|
|
57
|
+
for (const path of [entry.path, entry.origin]) {
|
|
58
|
+
if (!path || seen.has(path)) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
seen.add(path);
|
|
62
|
+
paths.push(path);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return paths;
|
|
66
|
+
}
|
|
@@ -17,6 +17,10 @@ import { getLogger } from "../util/logger.js";
|
|
|
17
17
|
import { Mutex } from "../util/mutex.js";
|
|
18
18
|
import { addToPathEnv, getExtraToolPathDirs } from "../util/platform.js";
|
|
19
19
|
import { PromiseGuard } from "../util/promise-guard.js";
|
|
20
|
+
import {
|
|
21
|
+
collectDirtyPathsFromPorcelain,
|
|
22
|
+
parsePorcelainZ,
|
|
23
|
+
} from "./git-porcelain.js";
|
|
20
24
|
|
|
21
25
|
const execFileAsync = promisify(execFile);
|
|
22
26
|
const log = getLogger("workspace-git");
|
|
@@ -51,7 +55,7 @@ function cleanGitEnv(workspaceDir: string): Record<string, string> {
|
|
|
51
55
|
* Patterns excluded from workspace git tracking.
|
|
52
56
|
* These are written to .gitignore on init and appended to existing .gitignore files.
|
|
53
57
|
*/
|
|
54
|
-
const WORKSPACE_GITIGNORE_RULES = [
|
|
58
|
+
export const WORKSPACE_GITIGNORE_RULES = [
|
|
55
59
|
// Runtime state directories
|
|
56
60
|
"data/db/",
|
|
57
61
|
"data/qdrant/",
|
|
@@ -226,59 +230,6 @@ done
|
|
|
226
230
|
exit 0
|
|
227
231
|
`;
|
|
228
232
|
|
|
229
|
-
/**
|
|
230
|
-
* Parse NUL-terminated `git status --porcelain -z` output into status/path
|
|
231
|
-
* pairs. NUL termination is required so paths with special characters
|
|
232
|
-
* (non-ASCII, quotes, newlines) arrive verbatim instead of C-style quoted.
|
|
233
|
-
* A rename/copy record is followed by a bare origin-path entry, which is
|
|
234
|
-
* skipped.
|
|
235
|
-
*/
|
|
236
|
-
function parsePorcelainZ(
|
|
237
|
-
stdout: string,
|
|
238
|
-
): Array<{ status: string; path: string }> {
|
|
239
|
-
const entries = stdout.split("\0");
|
|
240
|
-
const parsed: Array<{ status: string; path: string }> = [];
|
|
241
|
-
for (let i = 0; i < entries.length; i++) {
|
|
242
|
-
const entry = entries[i] ?? "";
|
|
243
|
-
if (entry.length < 4) {
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
const status = entry.substring(0, 2);
|
|
247
|
-
parsed.push({ status, path: entry.substring(3) });
|
|
248
|
-
if (status[0] === "R" || status[0] === "C") {
|
|
249
|
-
i++;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
return parsed;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Every path git status knows about, including rename/copy origins.
|
|
257
|
-
* Origins are skipped by {@link parsePorcelainZ} (they have no status
|
|
258
|
-
* prefix) but still need to be staged so a deletion is not left behind
|
|
259
|
-
* when a rename is split across add batches.
|
|
260
|
-
*/
|
|
261
|
-
function collectDirtyPathsFromPorcelain(stdout: string): string[] {
|
|
262
|
-
const entries = stdout.split("\0");
|
|
263
|
-
const paths: string[] = [];
|
|
264
|
-
for (let i = 0; i < entries.length; i++) {
|
|
265
|
-
const entry = entries[i] ?? "";
|
|
266
|
-
if (entry.length < 4) {
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
const status = entry.substring(0, 2);
|
|
270
|
-
paths.push(entry.substring(3));
|
|
271
|
-
if (status[0] === "R" || status[0] === "C") {
|
|
272
|
-
i++;
|
|
273
|
-
const origin = entries[i];
|
|
274
|
-
if (origin && origin.length > 0) {
|
|
275
|
-
paths.push(origin);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
return paths;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
233
|
/** Properties added by Node's child_process errors. */
|
|
283
234
|
interface ExecError extends Error {
|
|
284
235
|
killed?: boolean;
|
|
@@ -433,7 +384,7 @@ export class WorkspaceGitService {
|
|
|
433
384
|
*
|
|
434
385
|
* This method is always called inside the mutex, so no git operation from
|
|
435
386
|
* our code can be concurrently holding the lock. However, an external git
|
|
436
|
-
* process (user running `git add`, IDE tooling, etc.) could legitimately
|
|
387
|
+
* git process (user running `git add`, IDE tooling, etc.) could legitimately
|
|
437
388
|
* hold the lock. We use `lsof` to check — if any process has the file
|
|
438
389
|
* open, we leave it alone. If no process holds it, it's stale (crashed
|
|
439
390
|
* process) and safe to remove.
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { Database } from "bun:sqlite";
|
|
4
|
+
|
|
5
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Repair `gpt-5.4` and `gpt-5.4-mini` pins on fragments that dispatch
|
|
9
|
+
* through the ChatGPT subscription.
|
|
10
|
+
*
|
|
11
|
+
* OpenAI serves neither model under ChatGPT sign-in: the Codex endpoint
|
|
12
|
+
* rejects them with HTTP 400 ("not supported when using Codex with a
|
|
13
|
+
* ChatGPT account"), so both are out of `CODEX_SUBSCRIPTION_MODEL_IDS`.
|
|
14
|
+
* API-key access is unaffected, so a fragment on any other route keeps
|
|
15
|
+
* its model.
|
|
16
|
+
*
|
|
17
|
+
* Two stored shapes dispatch through the subscription. The `chatgpt`
|
|
18
|
+
* routing identity fails `LLMSchema.superRefine` once the allowlist drops
|
|
19
|
+
* the model, and the loader's per-section salvage then resets the whole
|
|
20
|
+
* `llm` section, losing every profile and call-site tuning: this is the
|
|
21
|
+
* repair that prevents data loss. A profile bound to the subscription row
|
|
22
|
+
* (row kind `chatgpt`, or an `oauth_subscription` auth on the
|
|
23
|
+
* pre-DB-migration-366 row shape) bypasses the auto-resolution compat
|
|
24
|
+
* gate and 400s on every request; the row is named by the legacy
|
|
25
|
+
* `provider_connection` binding when present (dispatch honors it first,
|
|
26
|
+
* and the name is only ever a row name), otherwise by an entry-name
|
|
27
|
+
* `provider`. Both shapes are swept in `llm.default`, `llm.callSites.*`,
|
|
28
|
+
* and `llm.profiles.*`; call-site fragments carry no binding (the schema
|
|
29
|
+
* strips a raw `provider_connection`), so only their declared provider
|
|
30
|
+
* counts.
|
|
31
|
+
*
|
|
32
|
+
* A providerless call-site pin is deliberately left alone. It overlays
|
|
33
|
+
* whichever profile wins the site for a turn: on an API-key or managed
|
|
34
|
+
* winner it still serves the model, and on a subscription winner the
|
|
35
|
+
* request already fails upstream and keeps failing, with no config loss
|
|
36
|
+
* either way. Rewriting such pins would change API-key users' model and
|
|
37
|
+
* price to cover a winner that may never be selected.
|
|
38
|
+
*
|
|
39
|
+
* Replacements: `gpt-5.4` becomes `gpt-5.5` (its successor) and
|
|
40
|
+
* `gpt-5.4-mini` becomes `gpt-5.6-luna` (the subscription's Balanced model;
|
|
41
|
+
* on the subscription every model bills the same).
|
|
42
|
+
*/
|
|
43
|
+
export const repairRetiredCodexGpt54ModelIdsMigration: WorkspaceMigration = {
|
|
44
|
+
id: "154-repair-retired-codex-gpt-5-4-model-ids",
|
|
45
|
+
description:
|
|
46
|
+
"Repair gpt-5.4 and gpt-5.4-mini pins on ChatGPT-subscription LLM fragments in workspace config",
|
|
47
|
+
run(workspaceDir: string): void {
|
|
48
|
+
const configPath = join(workspaceDir, "config.json");
|
|
49
|
+
if (!existsSync(configPath)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Read outside the parse catch: a transient filesystem error (EIO,
|
|
54
|
+
// EACCES) must reach the runner so the migration retries, while
|
|
55
|
+
// malformed JSON is a permanent state this migration cannot repair.
|
|
56
|
+
const rawText = readFileSync(configPath, "utf-8");
|
|
57
|
+
|
|
58
|
+
let config: Record<string, unknown>;
|
|
59
|
+
try {
|
|
60
|
+
const raw = JSON.parse(rawText);
|
|
61
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
config = raw as Record<string, unknown>;
|
|
65
|
+
} catch {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const llm = readObject(config.llm);
|
|
70
|
+
if (llm === null) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Entry rows load lazily: only a stale fragment naming a row needs
|
|
75
|
+
// them. An unreadable DB then fails the run (retried next boot) rather
|
|
76
|
+
// than checkpointing a pass that skips entry-bound profiles.
|
|
77
|
+
let rows: Map<string, boolean> | null | undefined;
|
|
78
|
+
const isSubscriptionRow = (name: unknown): boolean => {
|
|
79
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
if (rows === undefined) {
|
|
83
|
+
rows = readConnectionRows(workspaceDir);
|
|
84
|
+
}
|
|
85
|
+
if (rows === null) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
"provider_connections is not readable; retrying the model-ID repair on the next run",
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return rows.get(name) === true;
|
|
91
|
+
};
|
|
92
|
+
// A `provider` value: the identity dispatches to the subscription, a
|
|
93
|
+
// known vendor never does, and any other value is an entry name.
|
|
94
|
+
const isSubscriptionProvider = (provider: unknown): boolean =>
|
|
95
|
+
provider === CHATGPT_IDENTITY ||
|
|
96
|
+
(typeof provider === "string" &&
|
|
97
|
+
!KNOWN_PROVIDERS.has(provider) &&
|
|
98
|
+
isSubscriptionRow(provider));
|
|
99
|
+
// The binding is authoritative when present, since dispatch honors it
|
|
100
|
+
// ahead of the declared provider; the identity is repaired regardless
|
|
101
|
+
// (the schema rejects the pair whatever the binding).
|
|
102
|
+
const isBound = (fragment: Record<string, unknown>): boolean => {
|
|
103
|
+
if (fragment.provider === CHATGPT_IDENTITY) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
const binding = fragment.provider_connection;
|
|
107
|
+
return typeof binding === "string" && binding.length > 0
|
|
108
|
+
? isSubscriptionRow(binding)
|
|
109
|
+
: isSubscriptionProvider(fragment.provider);
|
|
110
|
+
};
|
|
111
|
+
const isProviderBound = (fragment: Record<string, unknown>): boolean =>
|
|
112
|
+
isSubscriptionProvider(fragment.provider);
|
|
113
|
+
|
|
114
|
+
let changed = false;
|
|
115
|
+
|
|
116
|
+
changed = repairFragment(readObject(llm.default), isBound) || changed;
|
|
117
|
+
|
|
118
|
+
const callSites = readObject(llm.callSites);
|
|
119
|
+
if (callSites !== null) {
|
|
120
|
+
for (const rawConfig of Object.values(callSites)) {
|
|
121
|
+
changed =
|
|
122
|
+
repairFragment(readObject(rawConfig), isProviderBound) || changed;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const profiles = readObject(llm.profiles);
|
|
127
|
+
if (profiles !== null) {
|
|
128
|
+
for (const rawProfile of Object.values(profiles)) {
|
|
129
|
+
changed = repairFragment(readObject(rawProfile), isBound) || changed;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!changed) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Write-then-rename so an interrupted write cannot leave config.json
|
|
138
|
+
// truncated: a torn in-place write would parse as invalid JSON on the
|
|
139
|
+
// retry, which the catch above treats as "nothing to do", letting the
|
|
140
|
+
// runner checkpoint the migration as completed against a corrupt file.
|
|
141
|
+
const tmpPath = `${configPath}.migration-154.tmp`;
|
|
142
|
+
writeFileSync(tmpPath, JSON.stringify(config, null, 2) + "\n");
|
|
143
|
+
renameSync(tmpPath, configPath);
|
|
144
|
+
},
|
|
145
|
+
// The exact-match rewrite is idempotent, so a transient failure (full
|
|
146
|
+
// disk, I/O error) is safe to retry on later startups.
|
|
147
|
+
retryFailedCheckpoint: true,
|
|
148
|
+
down(_workspaceDir: string): void {
|
|
149
|
+
// Forward-only: reintroducing the retired model IDs would fail schema
|
|
150
|
+
// validation once the allowlist no longer carries them.
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// Helpers: self-contained per workspace migrations AGENTS.md
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
const CHATGPT_IDENTITY = "chatgpt";
|
|
159
|
+
|
|
160
|
+
// Frozen snapshot of `KNOWN_LLM_PROVIDERS`: the vendor and identity values
|
|
161
|
+
// that dispatch without a connection row, so none of them is an entry name.
|
|
162
|
+
const KNOWN_PROVIDERS = new Set([
|
|
163
|
+
"anthropic",
|
|
164
|
+
"openai",
|
|
165
|
+
"gemini",
|
|
166
|
+
"ollama",
|
|
167
|
+
"fireworks",
|
|
168
|
+
"openrouter",
|
|
169
|
+
"vercel-ai-gateway",
|
|
170
|
+
"openai-compatible",
|
|
171
|
+
"minimax",
|
|
172
|
+
"atlascloud",
|
|
173
|
+
"together",
|
|
174
|
+
"litellm",
|
|
175
|
+
"opencode",
|
|
176
|
+
"baseten",
|
|
177
|
+
"poolside",
|
|
178
|
+
"vellum",
|
|
179
|
+
"chatgpt",
|
|
180
|
+
]);
|
|
181
|
+
|
|
182
|
+
const REPLACEMENTS: ReadonlyMap<string, string> = new Map([
|
|
183
|
+
["gpt-5.4", "gpt-5.5"],
|
|
184
|
+
["gpt-5.4-mini", "gpt-5.6-luna"],
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
function repairFragment(
|
|
188
|
+
fragment: Record<string, unknown> | null,
|
|
189
|
+
isSubscriptionRouted: (fragment: Record<string, unknown>) => boolean,
|
|
190
|
+
): boolean {
|
|
191
|
+
if (fragment === null || typeof fragment.model !== "string") {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
const replacement = REPLACEMENTS.get(fragment.model);
|
|
195
|
+
if (replacement === undefined) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
if (!isSubscriptionRouted(fragment)) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
fragment.model = replacement;
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* `provider_connections` row name to whether the row dispatches to the
|
|
207
|
+
* ChatGPT subscription, or null when the DB or table is not readable. The
|
|
208
|
+
* caller fails the run on null: entry-name providers must be judged against
|
|
209
|
+
* real rows, never guessed. An absent DB file is a real state (no rows, so
|
|
210
|
+
* every entry name is dangling).
|
|
211
|
+
*/
|
|
212
|
+
function readConnectionRows(workspaceDir: string): Map<string, boolean> | null {
|
|
213
|
+
const dbPath = join(workspaceDir, "data", "db", "assistant.db");
|
|
214
|
+
if (!existsSync(dbPath)) {
|
|
215
|
+
return new Map();
|
|
216
|
+
}
|
|
217
|
+
let db: Database;
|
|
218
|
+
try {
|
|
219
|
+
db = new Database(dbPath, { readonly: true });
|
|
220
|
+
} catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const rows = db
|
|
225
|
+
.query(`SELECT name, provider, auth FROM provider_connections`)
|
|
226
|
+
.all() as Array<{ name: string; provider: string; auth: string }>;
|
|
227
|
+
return new Map(
|
|
228
|
+
rows.map((row) => [
|
|
229
|
+
row.name,
|
|
230
|
+
row.provider === CHATGPT_IDENTITY || isSubscriptionAuth(row.auth),
|
|
231
|
+
]),
|
|
232
|
+
);
|
|
233
|
+
} catch {
|
|
234
|
+
return null;
|
|
235
|
+
} finally {
|
|
236
|
+
db.close();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function isSubscriptionAuth(auth: string): boolean {
|
|
241
|
+
try {
|
|
242
|
+
const parsed: unknown = JSON.parse(auth);
|
|
243
|
+
return (
|
|
244
|
+
parsed !== null &&
|
|
245
|
+
typeof parsed === "object" &&
|
|
246
|
+
(parsed as { type?: unknown }).type === "oauth_subscription"
|
|
247
|
+
);
|
|
248
|
+
} catch {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function readObject(value: unknown): Record<string, unknown> | null {
|
|
254
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
return value as Record<string, unknown>;
|
|
258
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { getLogger } from "../../util/logger.js";
|
|
5
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
6
|
+
|
|
7
|
+
const log = getLogger("workspace-migration-155");
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Move `liveVoice.frontModel` to `voice.frontModel` in `config.json`.
|
|
11
|
+
*
|
|
12
|
+
* The front-door tuning (endpointing budgets and progress narration cadence)
|
|
13
|
+
* is read by phone calls as well as live voice, so it lives under a
|
|
14
|
+
* transport-agnostic key. A `voice.frontModel` already present wins field by
|
|
15
|
+
* field; the `liveVoice` block is removed either way so raw-config saves do
|
|
16
|
+
* not write it back.
|
|
17
|
+
*/
|
|
18
|
+
export const moveFrontModelConfigToVoiceMigration: WorkspaceMigration = {
|
|
19
|
+
id: "155-move-front-model-config-to-voice",
|
|
20
|
+
description: "Move liveVoice.frontModel to voice.frontModel in config.json",
|
|
21
|
+
run(workspaceDir: string): void {
|
|
22
|
+
const configPath = join(workspaceDir, "config.json");
|
|
23
|
+
if (!existsSync(configPath)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let config: Record<string, unknown>;
|
|
28
|
+
try {
|
|
29
|
+
const raw = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
30
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
config = raw as Record<string, unknown>;
|
|
34
|
+
} catch {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const liveVoice = readObject(config.liveVoice);
|
|
39
|
+
if (liveVoice === null || !Object.hasOwn(liveVoice, "frontModel")) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const legacy = readObject(liveVoice.frontModel);
|
|
43
|
+
delete liveVoice.frontModel;
|
|
44
|
+
|
|
45
|
+
if (legacy !== null) {
|
|
46
|
+
const voice = readObject(config.voice) ?? {};
|
|
47
|
+
const current = readObject(voice.frontModel) ?? {};
|
|
48
|
+
const legacyProgress = readObject(legacy.progress) ?? {};
|
|
49
|
+
const currentProgress = readObject(current.progress) ?? {};
|
|
50
|
+
const merged: Record<string, unknown> = { ...legacy, ...current };
|
|
51
|
+
if (
|
|
52
|
+
Object.keys(legacyProgress).length > 0 ||
|
|
53
|
+
Object.keys(currentProgress).length > 0
|
|
54
|
+
) {
|
|
55
|
+
merged.progress = { ...legacyProgress, ...currentProgress };
|
|
56
|
+
}
|
|
57
|
+
voice.frontModel = merged;
|
|
58
|
+
config.voice = voice;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const tmpPath = `${configPath}.migration-155.tmp`;
|
|
62
|
+
writeFileSync(tmpPath, JSON.stringify(config, null, 2) + "\n");
|
|
63
|
+
renameSync(tmpPath, configPath);
|
|
64
|
+
log.info("Moved liveVoice.frontModel to voice.frontModel in config.json");
|
|
65
|
+
},
|
|
66
|
+
retryFailedCheckpoint: true,
|
|
67
|
+
down(_workspaceDir: string): void {
|
|
68
|
+
// Forward-only: the schema reads voice.frontModel and strips unknown
|
|
69
|
+
// liveVoice keys, so a block moved back is dropped on the next load.
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
function readObject(value: unknown): Record<string, unknown> | null {
|
|
74
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return value as Record<string, unknown>;
|
|
78
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { getLogger } from "../../util/logger.js";
|
|
5
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
6
|
+
|
|
7
|
+
const WORKSPACE_MCP_FILENAME = "mcp.json";
|
|
8
|
+
|
|
9
|
+
// Inlined Agent Plugins 1.0.0 schema URL so this migration stays
|
|
10
|
+
// self-contained if the production constant later changes or moves.
|
|
11
|
+
const AGENT_PLUGINS_MCP_SCHEMA_URL =
|
|
12
|
+
"https://agent-plugins.org/schemas/1.0.0/mcp.schema.json";
|
|
13
|
+
|
|
14
|
+
const log = getLogger("workspace-migration-156");
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Move user-owned MCP servers from `config.json` into a spec-pure
|
|
18
|
+
* `/workspace/mcp.json`. Presence of a server is what starts it. Headers
|
|
19
|
+
* that were still on a transport are copied onto the spec entry so the
|
|
20
|
+
* credential-store migration can lift them on the next MCP reload.
|
|
21
|
+
*/
|
|
22
|
+
export const extractWorkspaceMcpJsonMigration: WorkspaceMigration = {
|
|
23
|
+
id: "156-extract-workspace-mcp-json",
|
|
24
|
+
description:
|
|
25
|
+
"Extract workspace MCP servers from config.json into a spec-pure mcp.json",
|
|
26
|
+
run(workspaceDir: string): void {
|
|
27
|
+
const configPath = join(workspaceDir, "config.json");
|
|
28
|
+
if (!existsSync(configPath)) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const config = readJsonObject(configPath);
|
|
33
|
+
if (config === null) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!Object.hasOwn(config, "mcp")) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const extracted = extractSpecServers(config.mcp);
|
|
42
|
+
const mcpPath = join(workspaceDir, WORKSPACE_MCP_FILENAME);
|
|
43
|
+
const existing = readExistingMcpServers(mcpPath);
|
|
44
|
+
const merged: Record<string, unknown> = {
|
|
45
|
+
...extracted,
|
|
46
|
+
...existing.servers,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (Object.keys(merged).length > 0 || existing.present) {
|
|
50
|
+
const document = {
|
|
51
|
+
$schema: existing.schema ?? AGENT_PLUGINS_MCP_SCHEMA_URL,
|
|
52
|
+
mcpServers: merged,
|
|
53
|
+
};
|
|
54
|
+
atomicWrite(mcpPath, document);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
delete config.mcp;
|
|
58
|
+
atomicWrite(configPath, config);
|
|
59
|
+
log.info("Extracted workspace MCP servers into mcp.json");
|
|
60
|
+
},
|
|
61
|
+
retryFailedCheckpoint: true,
|
|
62
|
+
down(_workspaceDir: string): void {
|
|
63
|
+
// Forward-only: mcp.json is the store. Putting servers back under
|
|
64
|
+
// config.json would just be extracted again on the next load.
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
function extractSpecServers(mcp: unknown): Record<string, unknown> {
|
|
69
|
+
const block = readObject(mcp);
|
|
70
|
+
if (block === null) {
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const servers: Record<string, unknown> = {};
|
|
75
|
+
const rawServers = block.servers;
|
|
76
|
+
if (Array.isArray(rawServers)) {
|
|
77
|
+
for (const entry of rawServers) {
|
|
78
|
+
const object = readObject(entry);
|
|
79
|
+
if (object === null) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const name = object.name;
|
|
83
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const spec = toSpecServer(object);
|
|
87
|
+
if (spec !== null) {
|
|
88
|
+
servers[name] = spec;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return servers;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const serverMap = readObject(rawServers);
|
|
95
|
+
if (serverMap === null) {
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
for (const [id, entry] of Object.entries(serverMap)) {
|
|
99
|
+
if (id.trim().length === 0) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const object = readObject(entry);
|
|
103
|
+
if (object === null) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const spec = toSpecServer(object);
|
|
107
|
+
if (spec !== null) {
|
|
108
|
+
servers[id] = spec;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return servers;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function toSpecServer(entry: Record<string, unknown>): Record<string, unknown> | null {
|
|
115
|
+
const transport = readObject(entry.transport);
|
|
116
|
+
if (transport === null) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const type = transport.type;
|
|
120
|
+
if (type === "stdio") {
|
|
121
|
+
if (typeof transport.command !== "string" || transport.command.length === 0) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const spec: Record<string, unknown> = {
|
|
125
|
+
type: "stdio",
|
|
126
|
+
command: transport.command,
|
|
127
|
+
};
|
|
128
|
+
if (
|
|
129
|
+
Array.isArray(transport.args) &&
|
|
130
|
+
transport.args.length > 0 &&
|
|
131
|
+
transport.args.every((arg) => typeof arg === "string")
|
|
132
|
+
) {
|
|
133
|
+
spec.args = transport.args;
|
|
134
|
+
}
|
|
135
|
+
const env = readStringRecord(transport.env);
|
|
136
|
+
if (env !== null) {
|
|
137
|
+
spec.env = env;
|
|
138
|
+
}
|
|
139
|
+
return spec;
|
|
140
|
+
}
|
|
141
|
+
if (type === "sse" || type === "streamable-http" || type === "http") {
|
|
142
|
+
if (typeof transport.url !== "string" || transport.url.length === 0) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
const spec: Record<string, unknown> = {
|
|
146
|
+
type: type === "http" ? "streamable-http" : type,
|
|
147
|
+
url: transport.url,
|
|
148
|
+
};
|
|
149
|
+
const headers = readStringRecord(transport.headers);
|
|
150
|
+
if (headers !== null) {
|
|
151
|
+
spec.headers = headers;
|
|
152
|
+
}
|
|
153
|
+
return spec;
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function readExistingMcpServers(path: string): {
|
|
159
|
+
present: boolean;
|
|
160
|
+
schema?: string;
|
|
161
|
+
servers: Record<string, unknown>;
|
|
162
|
+
} {
|
|
163
|
+
if (!existsSync(path)) {
|
|
164
|
+
return { present: false, servers: {} };
|
|
165
|
+
}
|
|
166
|
+
const document = readJsonObject(path);
|
|
167
|
+
if (document === null) {
|
|
168
|
+
return { present: true, servers: {} };
|
|
169
|
+
}
|
|
170
|
+
const servers = readObject(document.mcpServers);
|
|
171
|
+
return {
|
|
172
|
+
present: true,
|
|
173
|
+
schema:
|
|
174
|
+
typeof document.$schema === "string" ? document.$schema : undefined,
|
|
175
|
+
servers: servers ?? {},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function readJsonObject(path: string): Record<string, unknown> | null {
|
|
180
|
+
try {
|
|
181
|
+
const raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
182
|
+
return readObject(raw);
|
|
183
|
+
} catch {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function atomicWrite(path: string, value: unknown): void {
|
|
189
|
+
const tmpPath = `${path}.migration-156.tmp`;
|
|
190
|
+
writeFileSync(tmpPath, JSON.stringify(value, null, 2) + "\n");
|
|
191
|
+
renameSync(tmpPath, path);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function readObject(value: unknown): Record<string, unknown> | null {
|
|
195
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
return value as Record<string, unknown>;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function readStringRecord(value: unknown): Record<string, string> | null {
|
|
202
|
+
const object = readObject(value);
|
|
203
|
+
if (object === null) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
const result: Record<string, string> = {};
|
|
207
|
+
for (const [key, entry] of Object.entries(object)) {
|
|
208
|
+
if (typeof entry !== "string") {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
result[key] = entry;
|
|
212
|
+
}
|
|
213
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
214
|
+
}
|