@iislee/opencodex 2.11.0 → 2.31.0
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_INSTALL.md +109 -0
- package/README.md +114 -19
- package/bin/ocx.mjs +164 -36
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-BF38heuV.js +104 -0
- package/gui/dist/assets/index-DMiI18Kv.css +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +19 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +376 -52
- package/src/adapters/base.ts +54 -7
- package/src/adapters/client-fingerprint.ts +18 -12
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +601 -0
- package/src/adapters/cursor/checkpoint-store.ts +303 -0
- package/src/adapters/cursor/cursor-errors.ts +129 -6
- package/src/adapters/cursor/discovery.ts +61 -4
- package/src/adapters/cursor/effort-map.ts +27 -3
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/live-models.ts +174 -58
- package/src/adapters/cursor/live-transport.ts +609 -170
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-fs.ts +10 -7
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +0 -1
- package/src/adapters/cursor/native-exec.ts +101 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +383 -65
- package/src/adapters/cursor/request-builder.ts +237 -31
- package/src/adapters/cursor/tool-definitions.ts +242 -12
- package/src/adapters/cursor/tool-result-normalize.ts +92 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +28 -1
- package/src/adapters/cursor.ts +133 -7
- package/src/adapters/google-antigravity-replay.ts +558 -34
- package/src/adapters/google-antigravity-wire.ts +43 -10
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +618 -74
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +10 -1
- package/src/adapters/kiro.ts +92 -54
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1311 -336
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +830 -56
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +104 -21
- package/src/adapters/xai-web-search.ts +185 -0
- package/src/bridge.ts +428 -61
- package/src/chat/inbound.ts +43 -19
- package/src/chat/outbound.ts +82 -26
- package/src/claude/agents-inject.ts +32 -9
- package/src/claude/context-windows.ts +21 -5
- package/src/claude/desktop-3p.ts +243 -9
- package/src/claude/gateway-cache.ts +41 -4
- package/src/claude/inbound.ts +72 -3
- package/src/claude/model-info.ts +38 -15
- package/src/claude/outbound.ts +70 -16
- package/src/cli/account-api.ts +35 -1
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +389 -2
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +30 -6
- package/src/cli/agent.ts +45 -1
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +29 -4
- package/src/cli/claude.ts +103 -34
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +8 -0
- package/src/cli/config-command.ts +74 -10
- package/src/cli/dispatch.ts +593 -0
- package/src/cli/doctor.ts +315 -43
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +20 -271
- package/src/cli/index.ts +378 -519
- package/src/cli/init.ts +4 -17
- package/src/cli/integrations.ts +120 -2
- package/src/cli/lab.ts +607 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/minimax.ts +497 -0
- package/src/cli/models-runtime.ts +35 -2
- package/src/cli/models.ts +100 -14
- package/src/cli/observe.ts +92 -3
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +18 -1
- package/src/cli/provider.ts +24 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +437 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +6 -3
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +10 -3
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/v2.ts +105 -10
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1358 -21
- package/src/codex/account-label.ts +14 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/account-store.ts +29 -2
- package/src/codex/account-usability.ts +25 -2
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/app-server-processes.ts +493 -106
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +849 -242
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +345 -32
- package/src/codex/autostart-health.ts +8 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +68 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +121 -30
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +529 -45
- package/src/codex/catalog/native-models.ts +72 -0
- package/src/codex/catalog/parsing.ts +224 -30
- package/src/codex/catalog/provider-fetch.ts +1460 -134
- package/src/codex/catalog/sync.ts +1449 -186
- package/src/codex/catalog-admission.ts +199 -0
- package/src/codex/catalog-refresh-status.ts +105 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +6 -3
- package/src/codex/codex-write-lock.ts +384 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +651 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +407 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-migration-guardian.ts +26 -20
- package/src/codex/history-provider.ts +231 -28
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +220 -0
- package/src/codex/inject-coordination.ts +290 -0
- package/src/codex/inject.ts +1073 -152
- package/src/codex/injected-marker.ts +37 -3
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +80 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +506 -0
- package/src/codex/log-guard/lock.ts +150 -0
- package/src/codex/log-guard/maintenance.ts +403 -0
- package/src/codex/log-guard/path-safety.ts +88 -0
- package/src/codex/log-guard/policy.ts +44 -0
- package/src/codex/log-guard/processes.ts +205 -0
- package/src/codex/log-guard/protection.ts +489 -0
- package/src/codex/log-guard/sqlite-errors.ts +9 -0
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/main-account.ts +29 -1
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +56 -10
- package/src/codex/model-entitlements.ts +353 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +178 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +492 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +682 -0
- package/src/codex/paths.ts +80 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +1 -1
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/project-config-warnings.ts +20 -6
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +175 -13
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +505 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1009 -28
- package/src/codex/subagent-model-fallback.ts +350 -35
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +612 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +187 -81
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +20 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +81 -9
- package/src/config/provider-name.ts +24 -0
- package/src/config.ts +1762 -140
- package/src/generated/compatibility-version.json +3164 -0
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
- package/src/grok/inject.ts +16 -5
- package/src/grok/inspect.ts +45 -0
- package/src/grok/sync.ts +2 -2
- package/src/images/loop.ts +152 -29
- package/src/images/plan.ts +23 -13
- package/src/integrations/config-io.ts +269 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/mutation-flight.ts +71 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/omp-yaml-source.ts +358 -0
- package/src/integrations/owned-refresh.ts +74 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +159 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +361 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +691 -0
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +499 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +64 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +164 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +68 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +214 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +229 -0
- package/src/lab/events/validate.ts +781 -0
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +489 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +54 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +310 -0
- package/src/lab/ledger/store.ts +532 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +336 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +118 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +301 -0
- package/src/lab/observe/from-live.ts +117 -0
- package/src/lab/paths.ts +153 -0
- package/src/lab/projection/rebuild.ts +495 -0
- package/src/lab/projection/schema.ts +135 -0
- package/src/lab/projection/verdicts.ts +474 -0
- package/src/lab/projection/verification.ts +412 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +243 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lab/query/catalog.ts +101 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +45 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +112 -0
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +74 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +153 -9
- package/src/lib/bun-runtime.ts +125 -12
- package/src/lib/bun-stream-caps.ts +13 -9
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +6 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +44 -2
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-activation.ts +223 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +56 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/local-provider-reload-contract.ts +100 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +4 -1
- package/src/lib/provider-outbound.ts +49 -9
- package/src/lib/redact.ts +419 -3
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/shadow-call.ts +35 -4
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/state-store-registrations.ts +10 -2
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/token-estimate.ts +19 -2
- package/src/lib/tool-argument-integers.ts +202 -0
- package/src/lib/translator-budget.ts +44 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +156 -3
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-elevation.ts +70 -2
- package/src/lib/windows-secret-acl.ts +409 -69
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/lib/windows-text.ts +106 -0
- package/src/lib/windows-user-principal.ts +341 -0
- package/src/lib/winsw.ts +33 -5
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +8 -2
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +398 -66
- package/src/oauth/key-providers.ts +16 -0
- package/src/oauth/kimi.ts +16 -2
- package/src/oauth/kiro.ts +50 -6
- package/src/oauth/local-token-detect.ts +11 -2
- package/src/oauth/log.ts +3 -1
- package/src/oauth/login-cli.ts +88 -28
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/store.ts +119 -21
- package/src/oauth/token-guardian.ts +9 -3
- package/src/pi/models.ts +2 -2
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/antigravity-models.ts +521 -31
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +144 -0
- package/src/providers/context-cap.ts +22 -5
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/derive.ts +253 -6
- package/src/providers/fastwire.ts +501 -0
- package/src/providers/free-directory.ts +10 -7
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/label.ts +1 -1
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/openai-sidecar.ts +72 -4
- package/src/providers/openai-tier-startup.ts +31 -2
- package/src/providers/openai-tiers.ts +119 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +29 -0
- package/src/providers/quota.ts +1319 -38
- package/src/providers/registry.ts +1429 -111
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +42 -6
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/providers/xai-responses-opt-in.ts +15 -0
- package/src/providers/xai-transport.ts +11 -4
- package/src/reasoning-effort.ts +49 -1
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +266 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +355 -0
- package/src/responses/parser.ts +220 -38
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +426 -0
- package/src/responses/schema.ts +7 -1
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +565 -27
- package/src/responses/thought-signature-replay.ts +347 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/router.ts +366 -30
- package/src/routing/analytics.ts +378 -0
- package/src/routing/capability.ts +244 -0
- package/src/routing/compatibility/assemble.ts +73 -0
- package/src/routing/compatibility/behavior.ts +278 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +495 -0
- package/src/routing/health.ts +412 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +547 -0
- package/src/routing/quota.ts +145 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +2 -29
- package/src/server/auth-cors.ts +267 -46
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +130 -56
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +426 -0
- package/src/server/claude-messages.ts +159 -43
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +18 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +94 -12
- package/src/server/index.ts +865 -181
- package/src/server/lifecycle.ts +284 -13
- package/src/server/live.ts +136 -17
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/local-provider-reload-client.ts +137 -0
- package/src/server/management/agent-settings-routes.ts +398 -103
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/combo-routes.ts +62 -24
- package/src/server/management/config-routes.ts +464 -51
- package/src/server/management/context.ts +80 -2
- package/src/server/management/integration-routes.ts +498 -0
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +563 -0
- package/src/server/management/logs-usage-routes.ts +101 -32
- package/src/server/management/model-routes.ts +189 -131
- package/src/server/management/model-rows.ts +163 -0
- package/src/server/management/native-integration-routes.ts +769 -0
- package/src/server/management/oauth-account-routes.ts +80 -4
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +764 -157
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +380 -0
- package/src/server/management/shared.ts +27 -11
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/storage-log-guard-routes.ts +186 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +288 -32
- package/src/server/management/system-routes.ts +77 -0
- package/src/server/management/usage-summary-cache.ts +9 -1
- package/src/server/management/vision-sidecar-options.ts +167 -0
- package/src/server/management/web-search-sidecar-options.ts +120 -0
- package/src/server/management-api.ts +115 -14
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +132 -5
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +82 -42
- package/src/server/relay.ts +236 -76
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log.ts +235 -22
- package/src/server/responses/agent-task-recovery-cache.ts +143 -0
- package/src/server/responses/agent-task-recovery.ts +465 -0
- package/src/server/responses/collaboration.ts +204 -35
- package/src/server/responses/compact.ts +442 -55
- package/src/server/responses/core.ts +2872 -331
- package/src/server/responses/empty-completion-guard.ts +276 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +79 -4
- package/src/server/responses/input-admission.ts +185 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +251 -0
- package/src/server/responses/terminal-guard.ts +26 -5
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses/ws-upstream.ts +308 -0
- package/src/server/responses-custom-tool-repair.ts +282 -0
- package/src/server/responses-item-id-repair.ts +54 -6
- package/src/server/responses-json-events.ts +90 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses-terminal-repair.ts +342 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +153 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +110 -22
- package/src/server/startup-action-control.ts +8 -1
- package/src/server/startup-health-cache.ts +19 -1
- package/src/server/system-env.ts +80 -9
- package/src/server/ws-bridge.ts +39 -38
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1111 -90
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/scanner.ts +1 -1
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +83 -9
- package/src/tray/windows.ts +43 -16
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +845 -0
- package/src/types/provider.ts +545 -0
- package/src/types/request.ts +384 -0
- package/src/types/tools.ts +131 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +104 -1236
- package/src/update/index.ts +32 -19
- package/src/update/job.ts +442 -67
- package/src/update/notify.ts +12 -6
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +259 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +268 -16
- package/src/usage/log.ts +606 -41
- package/src/usage/summary.ts +177 -9
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +9 -3
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +238 -24
- package/src/vision/reasoning.ts +55 -0
- package/src/vision/routed-describe.ts +175 -0
- package/src/vision/timeout-bounds.ts +9 -0
- package/src/web-search/anthropic-executor.ts +13 -7
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +88 -0
- package/src/web-search/executor.ts +11 -3
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +279 -50
- package/src/web-search/parse.ts +125 -30
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/xai-executor.ts +219 -0
- package/gui/dist/assets/index-DTpMHS4F.js +0 -67
- package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
- package/src/cli/internal-dispatch.ts +0 -20
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import type { CatalogModel } from "../../codex/catalog";
|
|
4
|
-
import { catalogModelSlug, invalidateCodexModelsCache, nativeModelRows, uniqueCatalogModelsForPublicList } from "../../codex/catalog";
|
|
4
|
+
import { catalogModelSlug, invalidateCodexModelsCache, nativeContextLimits, nativeModelRows, uniqueCatalogModelsForPublicList } from "../../codex/catalog";
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_SUBAGENT_MODELS,
|
|
7
7
|
codexAutoStartEnabled,
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
isValidProviderName,
|
|
10
10
|
loadConfig,
|
|
11
11
|
multiAgentGuidanceEnabled,
|
|
12
|
+
mutatePersistedConfig,
|
|
12
13
|
providerBaseUrlConfigError,
|
|
13
14
|
providerHeadersConfigError,
|
|
14
15
|
saveConfigPreservingClaudeCode,
|
|
@@ -30,7 +31,7 @@ import { deriveProviderPresets } from "../../providers/derive";
|
|
|
30
31
|
import { providerCodexAccountMode } from "../../providers/registry";
|
|
31
32
|
import { routedSlug, slugEquals } from "../../providers/slug-codec";
|
|
32
33
|
import { clearProviderQuotaCache, fetchProviderQuotaReports } from "../../providers/quota";
|
|
33
|
-
import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
34
|
+
import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
|
|
34
35
|
import { clearThreadAccountMap } from "../../codex/routing";
|
|
35
36
|
import { primeCodexPoolQuotas } from "../../codex/auth-api";
|
|
36
37
|
import { DEFAULT_PROVIDER_CONTEXT_CAP, globalContextCapValue, providerContextCap, providerContextCaps, setAllProviderContextCaps, setGlobalContextCapValue, setProviderContextCap } from "../../providers/context-cap";
|
|
@@ -50,6 +51,16 @@ import {
|
|
|
50
51
|
type DebugFlag,
|
|
51
52
|
} from "../../lib/debug-settings";
|
|
52
53
|
import type { OcxClaudeCodeConfig, OcxConfig, OcxCustomModel, OcxProviderConfig } from "../../types";
|
|
54
|
+
import {
|
|
55
|
+
visionCandidateRows,
|
|
56
|
+
visionDescriberIsProvablyBlind,
|
|
57
|
+
visionDescriberRejection,
|
|
58
|
+
} from "./vision-sidecar-options";
|
|
59
|
+
import {
|
|
60
|
+
webSearchCandidateRows,
|
|
61
|
+
webSearchModelIsRejected,
|
|
62
|
+
webSearchModelRejection,
|
|
63
|
+
} from "./web-search-sidecar-options";
|
|
53
64
|
import { drainAndShutdown } from "../lifecycle";
|
|
54
65
|
import { filterRequestLogs, getRequestLogEntries, type RequestLogEntry } from "../request-log";
|
|
55
66
|
import { estimateComboCost, estimateRequestCost, normalizeCostTokens, tokensPerSecond } from "../../usage/cost";
|
|
@@ -59,7 +70,7 @@ import { applySystemEnvToggle } from "../system-env";
|
|
|
59
70
|
|
|
60
71
|
import { isPlainRecord, parseDebugLogQuery, tokPerSecondResult, unavailableCostReason, costResult, requestLogDto, stripRegistryOnlyStaticHeaders, fetchAllModels, fetchGrokCandidateModels, buildClaudeDesktopState } from "./shared";
|
|
61
72
|
import type { MetricUnavailableReason, TokPerSecondResult, CostEstimateReason, CostResult, MetricSource } from "./shared";
|
|
62
|
-
import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
|
|
73
|
+
import { readManagementJsonBody, readOptionalManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
|
|
63
74
|
|
|
64
75
|
const GROK_APPLY_JOIN_MS = 120_000;
|
|
65
76
|
export const GROK_APPLY_TERMINAL_MS = 10 * 60_000;
|
|
@@ -70,6 +81,49 @@ let grokApplyTestHooks: { now?: () => number; run?: () => Promise<unknown> } | n
|
|
|
70
81
|
|
|
71
82
|
class GrokApplyBusyError extends Error {}
|
|
72
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Mirror a durable desired-state transition onto the long-lived server snapshot.
|
|
86
|
+
*
|
|
87
|
+
* `setIntegrationEnabled` writes DISK only. The server reuses one `config` object
|
|
88
|
+
* for every request, so leaving it stale makes the native GET report the opposite
|
|
89
|
+
* of what was just persisted, and makes any later whole-snapshot save (the Desktop
|
|
90
|
+
* profile PUT does exactly that) write the stale value back over the transition.
|
|
91
|
+
* ON is the ABSENCE of the key, matching `setIntegrationEnabled`'s on-disk shape.
|
|
92
|
+
*/
|
|
93
|
+
function mirrorDesiredEnabledOntoSnapshot(config: OcxConfig, client: "claude-desktop", enabled: boolean): void {
|
|
94
|
+
const integrations = { ...(config.clientIntegrations ?? {}) };
|
|
95
|
+
if (enabled) delete integrations[client];
|
|
96
|
+
else integrations[client] = false;
|
|
97
|
+
if (Object.keys(integrations).length === 0) delete config.clientIntegrations;
|
|
98
|
+
else config.clientIntegrations = integrations;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Persist ONLY `claudeCode.desktopProfile`, field-scoped, against the CURRENT
|
|
103
|
+
* on-disk config.
|
|
104
|
+
*
|
|
105
|
+
* `saveConfigPreservingClaudeCode(ctx.config)` writes the whole long-lived server
|
|
106
|
+
* snapshot. On the apply path that snapshot still carries the `clientIntegrations`
|
|
107
|
+
* it was loaded with, so a save right after `setIntegrationEnabled("claude-desktop",
|
|
108
|
+
* true)` carried the stale OFF back over the enable and made the route cancel its
|
|
109
|
+
* own apply. Mutating one field under the config-mutation lock cannot regress an
|
|
110
|
+
* unrelated key another writer just committed.
|
|
111
|
+
*/
|
|
112
|
+
function persistDesktopProfileField(
|
|
113
|
+
config: OcxConfig,
|
|
114
|
+
desktopProfile: NonNullable<OcxConfig["claudeCode"]>["desktopProfile"],
|
|
115
|
+
): { ok: true } | { ok: false; reason: "missing" | "invalid" | "conflict" } {
|
|
116
|
+
const outcome = mutatePersistedConfig(persisted => {
|
|
117
|
+
persisted.claudeCode = { ...(persisted.claudeCode ?? {}), desktopProfile };
|
|
118
|
+
return { changed: true, value: true };
|
|
119
|
+
});
|
|
120
|
+
// Only mirror into memory once the durable write actually landed; an
|
|
121
|
+
// `unavailable` outcome must not leave the snapshot claiming a saved profile.
|
|
122
|
+
if (outcome.status === "unavailable") return { ok: false, reason: outcome.reason };
|
|
123
|
+
config.claudeCode = { ...(config.claudeCode ?? {}), desktopProfile };
|
|
124
|
+
return { ok: true };
|
|
125
|
+
}
|
|
126
|
+
|
|
73
127
|
export function grokApplyFlightSnapshot(): { currentBytes: number; highWaterBytes: number; active: number } {
|
|
74
128
|
return {
|
|
75
129
|
currentBytes: grokApplyFlight?.bytes ?? 0,
|
|
@@ -125,28 +179,44 @@ export function setGrokApplyFlightTestHooks(
|
|
|
125
179
|
import type { ManagementContext } from "./context";
|
|
126
180
|
|
|
127
181
|
export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise<Response | null> {
|
|
128
|
-
const { req, url, config, deps,
|
|
182
|
+
const { req, url, config, deps, convergeCodexCatalog, syncClaudeAgentDefsBestEffort } = ctx;
|
|
129
183
|
|
|
130
184
|
/** Best-effort Desktop 3P config auto-reconcile when providers change. */
|
|
131
185
|
async function autoApplyDesktopBestEffort(): Promise<void> {
|
|
132
186
|
try {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
187
|
+
const { claudeDesktopIntegrationEnabled } = await import("../../codex/desired-state");
|
|
188
|
+
const admitted = loadConfig();
|
|
189
|
+
if (!claudeDesktopIntegrationEnabled(admitted)) return;
|
|
190
|
+
if (admitted.claudeCode?.desktopAutoApply === false) return;
|
|
191
|
+
if (!admitted.claudeCode?.desktopProfile) return;
|
|
192
|
+
const { inspectDesktop3pConfigLibrary, writeDesktop3pConfig } = await import("../../claude/desktop-3p");
|
|
193
|
+
const beforeKind = inspectDesktop3pConfigLibrary({
|
|
194
|
+
appliedFingerprint: admitted.claudeCode.desktopProfile.appliedFingerprint ?? null,
|
|
195
|
+
}).kind;
|
|
196
|
+
if (["not_installed", "no_owned_state", "foreign", "unsafe", "broken"].includes(beforeKind)) return;
|
|
136
197
|
const { filterCatalogVisibleModels, desktopVisibleNativeSlugs } = await import("../../codex/catalog");
|
|
137
|
-
const allModels = await fetchAllModels(
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
198
|
+
const allModels = await (deps.fetchAllModels ?? fetchAllModels)(admitted);
|
|
199
|
+
const current = loadConfig();
|
|
200
|
+
// This is the real guard: the catalog await admits a concurrent explicit OFF.
|
|
201
|
+
if (!claudeDesktopIntegrationEnabled(current)) return;
|
|
202
|
+
if (current.claudeCode?.desktopAutoApply === false || !current.claudeCode?.desktopProfile) return;
|
|
203
|
+
const afterKind = inspectDesktop3pConfigLibrary({
|
|
204
|
+
appliedFingerprint: current.claudeCode.desktopProfile.appliedFingerprint ?? null,
|
|
205
|
+
}).kind;
|
|
206
|
+
if (["not_installed", "no_owned_state", "foreign", "unsafe", "broken"].includes(afterKind)) return;
|
|
207
|
+
const routed = filterCatalogVisibleModels(allModels, current).map(m => ({ provider: m.provider, id: m.id, contextWindow: m.contextWindow }));
|
|
208
|
+
const result = (deps.writeDesktop3pConfig ?? writeDesktop3pConfig)(
|
|
209
|
+
current.port ?? 10100,
|
|
210
|
+
[...desktopVisibleNativeSlugs(current)],
|
|
142
211
|
routed,
|
|
143
|
-
|
|
212
|
+
current.apiKeys?.[0]?.key,
|
|
144
213
|
"static",
|
|
145
|
-
|
|
214
|
+
current.claudeCode.desktopProfile,
|
|
215
|
+
nativeContextLimits(current),
|
|
146
216
|
);
|
|
147
217
|
if (result.written && result.fingerprint) {
|
|
148
|
-
|
|
149
|
-
saveConfigPreservingClaudeCode(
|
|
218
|
+
current.claudeCode = { ...current.claudeCode, desktopProfile: { ...current.claudeCode.desktopProfile, appliedFingerprint: result.fingerprint, appliedAt: new Date().toISOString() } };
|
|
219
|
+
saveConfigPreservingClaudeCode(current);
|
|
150
220
|
}
|
|
151
221
|
} catch { /* best-effort */ }
|
|
152
222
|
}
|
|
@@ -160,6 +230,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
160
230
|
const {
|
|
161
231
|
isMultiAgentV2Enabled, hasAgentsMaxThreads, getLogicalMaxThreads,
|
|
162
232
|
getAgentsEnabled, getAgentsMaxDepth, getSubagentDeveloperInstructions,
|
|
233
|
+
getMultiAgentModeHintText,
|
|
163
234
|
} = await import("../../codex/features");
|
|
164
235
|
const enabled = isMultiAgentV2Enabled();
|
|
165
236
|
return jsonResponse({
|
|
@@ -167,9 +238,11 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
167
238
|
agentsMaxThreadsConflict: enabled && hasAgentsMaxThreads(),
|
|
168
239
|
maxConcurrentThreadsPerSession: getLogicalMaxThreads(),
|
|
169
240
|
multiAgentMode: config.multiAgentMode ?? "default",
|
|
241
|
+
keepNativeChatGptOnV1: config.keepNativeChatGptOnV1 === true,
|
|
170
242
|
agentsEnabled: getAgentsEnabled(),
|
|
171
243
|
agentsMaxDepth: getAgentsMaxDepth(),
|
|
172
244
|
subagentDeveloperInstructions: getSubagentDeveloperInstructions(),
|
|
245
|
+
multiAgentModeHintText: getMultiAgentModeHintText(),
|
|
173
246
|
// max_depth is V1-only upstream; this is the global-flag statement, derived
|
|
174
247
|
// server-side so no client can present it as an effective V2 limit.
|
|
175
248
|
agentsMaxDepthAppliesWhenV2Disabled: !enabled,
|
|
@@ -180,24 +253,31 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
180
253
|
enabled?: unknown;
|
|
181
254
|
maxConcurrentThreadsPerSession?: unknown;
|
|
182
255
|
multiAgentMode?: unknown;
|
|
256
|
+
keepNativeChatGptOnV1?: unknown;
|
|
183
257
|
agentsEnabled?: unknown;
|
|
184
258
|
agentsMaxDepth?: unknown;
|
|
185
259
|
subagentDeveloperInstructions?: unknown;
|
|
260
|
+
multiAgentModeHintText?: unknown;
|
|
186
261
|
};
|
|
187
262
|
try { body = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
|
|
188
263
|
const wantsFlag = body.enabled !== undefined;
|
|
189
264
|
const wantsThreads = body.maxConcurrentThreadsPerSession !== undefined;
|
|
190
265
|
const wantsMode = body.multiAgentMode !== undefined;
|
|
266
|
+
const wantsKeepNative = body.keepNativeChatGptOnV1 !== undefined;
|
|
191
267
|
const wantsAgentsEnabled = body.agentsEnabled !== undefined;
|
|
192
268
|
const wantsMaxDepth = body.agentsMaxDepth !== undefined;
|
|
193
269
|
const wantsSubagentInstructions = body.subagentDeveloperInstructions !== undefined;
|
|
194
|
-
|
|
195
|
-
|
|
270
|
+
const wantsModeHintText = body.multiAgentModeHintText !== undefined;
|
|
271
|
+
if (!wantsFlag && !wantsThreads && !wantsMode && !wantsKeepNative && !wantsAgentsEnabled && !wantsMaxDepth && !wantsSubagentInstructions && !wantsModeHintText) {
|
|
272
|
+
return jsonResponse({ error: "body must set enabled, multiAgentMode, keepNativeChatGptOnV1, maxConcurrentThreadsPerSession, agentsEnabled, agentsMaxDepth, subagentDeveloperInstructions, and/or multiAgentModeHintText" }, 400);
|
|
196
273
|
}
|
|
197
274
|
if (wantsFlag && typeof body.enabled !== "boolean") return jsonResponse({ error: "body.enabled must be a boolean" }, 400);
|
|
198
275
|
if (wantsMode && body.multiAgentMode !== "v1" && body.multiAgentMode !== "default" && body.multiAgentMode !== "v2") {
|
|
199
276
|
return jsonResponse({ error: "body.multiAgentMode must be 'v1', 'default', or 'v2'" }, 400);
|
|
200
277
|
}
|
|
278
|
+
if (wantsKeepNative && typeof body.keepNativeChatGptOnV1 !== "boolean") {
|
|
279
|
+
return jsonResponse({ error: "body.keepNativeChatGptOnV1 must be a boolean" }, 400);
|
|
280
|
+
}
|
|
201
281
|
if (wantsThreads && (typeof body.maxConcurrentThreadsPerSession !== "number" || !Number.isInteger(body.maxConcurrentThreadsPerSession) || body.maxConcurrentThreadsPerSession < 1)) {
|
|
202
282
|
return jsonResponse({ error: "body.maxConcurrentThreadsPerSession must be an integer >= 1" }, 400);
|
|
203
283
|
}
|
|
@@ -216,6 +296,14 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
216
296
|
if (wantsSubagentInstructions && body.subagentDeveloperInstructions !== null && typeof body.subagentDeveloperInstructions !== "string") {
|
|
217
297
|
return jsonResponse({ error: "body.subagentDeveloperInstructions must be a string or null" }, 400);
|
|
218
298
|
}
|
|
299
|
+
// null unsets the upstream key (effort-derived policy resumes); an empty/whitespace
|
|
300
|
+
// string is rejected because codex-rs treats any present hint as an override that
|
|
301
|
+
// suppresses even the Ultra-derived Proactive message (Option<String>, no blank
|
|
302
|
+
// special-case in effective_multi_agent_mode).
|
|
303
|
+
if (wantsModeHintText && body.multiAgentModeHintText !== null
|
|
304
|
+
&& (typeof body.multiAgentModeHintText !== "string" || body.multiAgentModeHintText.trim().length === 0)) {
|
|
305
|
+
return jsonResponse({ error: "body.multiAgentModeHintText must be a non-empty string or null" }, 400);
|
|
306
|
+
}
|
|
219
307
|
const mode = wantsMode ? body.multiAgentMode as "v1" | "default" | "v2" : undefined;
|
|
220
308
|
const modeFlag = mode === "v2" ? true : mode === "v1" ? false : undefined;
|
|
221
309
|
if (wantsFlag && modeFlag !== undefined && body.enabled !== modeFlag) {
|
|
@@ -224,21 +312,26 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
224
312
|
const {
|
|
225
313
|
isMultiAgentV2Enabled, hasAgentsMaxThreads, getLogicalMaxThreads, transitionMultiAgentV2,
|
|
226
314
|
getAgentsEnabled, getAgentsMaxDepth, getSubagentDeveloperInstructions,
|
|
227
|
-
setAgentsEnabled, setAgentsMaxDepth,
|
|
315
|
+
getMultiAgentModeHintText, probeCodexSupportsModeHint, setAgentsEnabled, setAgentsMaxDepth,
|
|
316
|
+
setSubagentDeveloperInstructions, setMultiAgentModeHintText, MODE_HINT_UNSUPPORTED_ERROR,
|
|
228
317
|
} = await import("../../codex/features");
|
|
318
|
+
// Probe the capability before any combined-request mutation. The scalar writer
|
|
319
|
+
// repeats this check, but doing it here prevents an earlier flag/mode/agents
|
|
320
|
+
// write from landing before an unsupported runtime returns 502.
|
|
321
|
+
if (wantsModeHintText && body.multiAgentModeHintText !== null
|
|
322
|
+
&& probeCodexSupportsModeHint() === false) {
|
|
323
|
+
return jsonResponse({
|
|
324
|
+
error: `writing multiAgentModeHintText failed: ${MODE_HINT_UNSUPPORTED_ERROR}`,
|
|
325
|
+
}, 502);
|
|
326
|
+
}
|
|
229
327
|
const warnings: string[] = [];
|
|
230
328
|
const requestedFlag = wantsFlag ? body.enabled as boolean : modeFlag;
|
|
231
329
|
if (requestedFlag !== undefined || wantsThreads) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
toggle = (enabled: boolean) => {
|
|
238
|
-
const inv = codexFeaturesInvocation(enabled ? "enable" : "disable");
|
|
239
|
-
execFileSync(inv.file, inv.args,
|
|
240
|
-
{ stdio: ["ignore", "pipe", "pipe"], timeout: 15_000, windowsHide: true, ...inv.options });
|
|
241
|
-
};
|
|
330
|
+
const targetFlag = requestedFlag ?? isMultiAgentV2Enabled();
|
|
331
|
+
let toggle = deps.toggleCodexMultiAgentV2;
|
|
332
|
+
if (!toggle) {
|
|
333
|
+
const { runCodexFeaturesCommand } = await import("../../cli/v2");
|
|
334
|
+
toggle = (enabled: boolean) => runCodexFeaturesCommand(enabled ? "enable" : "disable");
|
|
242
335
|
}
|
|
243
336
|
const result = transitionMultiAgentV2(targetFlag, toggle, {
|
|
244
337
|
...(wantsThreads ? { threadLimit: body.maxConcurrentThreadsPerSession as number } : {}),
|
|
@@ -252,6 +345,17 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
252
345
|
saveConfigPreservingClaudeCode(config);
|
|
253
346
|
warnings.push(`Multi-agent mode set to '${mode}'. Applies to new sessions.`);
|
|
254
347
|
}
|
|
348
|
+
if (wantsKeepNative) {
|
|
349
|
+
if (body.keepNativeChatGptOnV1 === true) config.keepNativeChatGptOnV1 = true;
|
|
350
|
+
else delete config.keepNativeChatGptOnV1;
|
|
351
|
+
saveConfigPreservingClaudeCode(config);
|
|
352
|
+
const effectiveMode = mode ?? config.multiAgentMode ?? "default";
|
|
353
|
+
warnings.push(body.keepNativeChatGptOnV1 === true
|
|
354
|
+
? (effectiveMode === "v2"
|
|
355
|
+
? "ChatGPT-native models stay on v1 while other models use v2. Applies to new sessions."
|
|
356
|
+
: "keepNativeChatGptOnV1 is stored but inactive until multi-agent mode is v2. Applies to new sessions.")
|
|
357
|
+
: "ChatGPT-native models follow the selected v1/v2/base surface. Applies to new sessions.");
|
|
358
|
+
}
|
|
255
359
|
// New-key scalar writes: each writer is individually atomic, so apply them in
|
|
256
360
|
// sequence after the transition. A failure here is a persistence failure (the
|
|
257
361
|
// writers' ok:false result or a throw from the underlying atomic write helper),
|
|
@@ -263,6 +367,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
263
367
|
if (wantsAgentsEnabled) scalarWrites.push({ field: "agentsEnabled", run: () => setAgentsEnabled(body.agentsEnabled as boolean | null) });
|
|
264
368
|
if (wantsMaxDepth) scalarWrites.push({ field: "agentsMaxDepth", run: () => setAgentsMaxDepth(body.agentsMaxDepth as number | null) });
|
|
265
369
|
if (wantsSubagentInstructions) scalarWrites.push({ field: "subagentDeveloperInstructions", run: () => setSubagentDeveloperInstructions(body.subagentDeveloperInstructions as string | null) });
|
|
370
|
+
if (wantsModeHintText) scalarWrites.push({ field: "multiAgentModeHintText", run: () => setMultiAgentModeHintText(body.multiAgentModeHintText as string | null) });
|
|
266
371
|
const landed: string[] = [];
|
|
267
372
|
for (const write of scalarWrites) {
|
|
268
373
|
try {
|
|
@@ -282,7 +387,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
282
387
|
if (getAgentsEnabled() === false && isMultiAgentV2Enabled()) {
|
|
283
388
|
warnings.push("agents.enabled = false has no effect while features.multi_agent_v2 is enabled; upstream keeps V2 active.");
|
|
284
389
|
}
|
|
285
|
-
await
|
|
390
|
+
const catalogRefresh = await convergeCodexCatalog();
|
|
286
391
|
if (requestedFlag !== undefined) warnings.push("Applies to new sessions; restart the Codex app or wait out its picker cache to see the ladder change.");
|
|
287
392
|
const enabled = isMultiAgentV2Enabled();
|
|
288
393
|
return jsonResponse({
|
|
@@ -291,14 +396,71 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
291
396
|
agentsMaxThreadsConflict: enabled && hasAgentsMaxThreads(),
|
|
292
397
|
maxConcurrentThreadsPerSession: getLogicalMaxThreads(),
|
|
293
398
|
multiAgentMode: config.multiAgentMode ?? "default",
|
|
399
|
+
keepNativeChatGptOnV1: config.keepNativeChatGptOnV1 === true,
|
|
294
400
|
agentsEnabled: getAgentsEnabled(),
|
|
295
401
|
agentsMaxDepth: getAgentsMaxDepth(),
|
|
296
402
|
subagentDeveloperInstructions: getSubagentDeveloperInstructions(),
|
|
403
|
+
multiAgentModeHintText: getMultiAgentModeHintText(),
|
|
297
404
|
agentsMaxDepthAppliesWhenV2Disabled: !enabled,
|
|
298
405
|
warnings,
|
|
406
|
+
catalogRefresh,
|
|
299
407
|
});
|
|
300
408
|
}
|
|
301
409
|
|
|
410
|
+
// default_mode_request_user_input feature toggle (Codex Auth page). GET reads the
|
|
411
|
+
// flag from $CODEX_HOME/config.toml; PUT flips it via the official `codex features`
|
|
412
|
+
// CLI so the TOML edit stays upstream-owned and format-preserving.
|
|
413
|
+
if (url.pathname === "/api/codex-auth/features/default-mode-request-user-input" && req.method === "GET") {
|
|
414
|
+
const { isDefaultModeRequestUserInputEnabled, DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY } = await import("../../codex/features");
|
|
415
|
+
return jsonResponse({
|
|
416
|
+
enabled: isDefaultModeRequestUserInputEnabled(),
|
|
417
|
+
key: DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
if (url.pathname === "/api/codex-auth/features/default-mode-request-user-input" && req.method === "PUT") {
|
|
421
|
+
let parsedBody: unknown;
|
|
422
|
+
try {
|
|
423
|
+
parsedBody = await readManagementJsonBody(req);
|
|
424
|
+
} catch (error) {
|
|
425
|
+
rethrowManagementBodyTooLarge(error);
|
|
426
|
+
return jsonResponse({ error: "invalid JSON body" }, 400);
|
|
427
|
+
}
|
|
428
|
+
if (!parsedBody || typeof parsedBody !== "object" || Array.isArray(parsedBody)) {
|
|
429
|
+
return jsonResponse({ error: "body must be a JSON object" }, 400);
|
|
430
|
+
}
|
|
431
|
+
const body = parsedBody as { enabled?: unknown };
|
|
432
|
+
if (typeof body.enabled !== "boolean") return jsonResponse({ error: "body.enabled must be a boolean" }, 400);
|
|
433
|
+
const { isDefaultModeRequestUserInputEnabled, DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY } = await import("../../codex/features");
|
|
434
|
+
const before = isDefaultModeRequestUserInputEnabled();
|
|
435
|
+
let toggle = deps.toggleDefaultModeRequestUserInput;
|
|
436
|
+
if (!toggle) {
|
|
437
|
+
const { runCodexFeaturesCommand } = await import("../../cli/v2");
|
|
438
|
+
toggle = (enabled: boolean) => runCodexFeaturesCommand(enabled ? "enable" : "disable", DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY);
|
|
439
|
+
}
|
|
440
|
+
let toggleError: string | null = null;
|
|
441
|
+
try {
|
|
442
|
+
toggle(body.enabled);
|
|
443
|
+
} catch (error) {
|
|
444
|
+
const err = error as { stderr?: unknown; message?: string };
|
|
445
|
+
const raw = err.stderr;
|
|
446
|
+
const stderrText = typeof raw === "string"
|
|
447
|
+
? raw.trim()
|
|
448
|
+
: raw instanceof Uint8Array ? new TextDecoder().decode(raw).trim() : "";
|
|
449
|
+
toggleError = stderrText || (err.message ?? String(error));
|
|
450
|
+
}
|
|
451
|
+
const enabled = isDefaultModeRequestUserInputEnabled();
|
|
452
|
+
if (toggleError !== null || enabled !== body.enabled) {
|
|
453
|
+
const reason = toggleError
|
|
454
|
+
?? `postcondition failed - the installed Codex build may not know the ${DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY} flag yet`;
|
|
455
|
+
return jsonResponse({ error: `default_mode_request_user_input toggle failed: ${reason}` }, 502);
|
|
456
|
+
}
|
|
457
|
+
const warnings: string[] = [];
|
|
458
|
+
if (enabled !== before) {
|
|
459
|
+
warnings.push("Applies to new sessions; restart the Codex app or wait out its picker cache to see the change.");
|
|
460
|
+
}
|
|
461
|
+
return jsonResponse({ ok: true, enabled, changed: enabled !== before, warnings });
|
|
462
|
+
}
|
|
463
|
+
|
|
302
464
|
// Subagent prompt injection model: single native or routed model whose info is
|
|
303
465
|
// dynamically injected into the v1 proactive prompt, plus an optional reasoning
|
|
304
466
|
// effort the prompt tells the agent to pass to spawn_agent. GET returns the current
|
|
@@ -456,15 +618,29 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
456
618
|
stored === catalogModelSlug(m) || slugEquals(stored, m.provider, m.id)
|
|
457
619
|
))
|
|
458
620
|
.map(catalogModelSlug))];
|
|
459
|
-
const
|
|
621
|
+
const chosen = config.subagentModels ?? [];
|
|
622
|
+
const selectable = [
|
|
460
623
|
...listCatalogNativeSlugs().filter(ns => !disabled.has(ns)),
|
|
461
624
|
...visibleRouted,
|
|
462
625
|
];
|
|
626
|
+
// A saved roster slot must stay representable even after its model is disabled
|
|
627
|
+
// elsewhere (Models page, provider allowlist, a provider row going away). The
|
|
628
|
+
// dashboard treats `available` as the set of rows it can render, so a chosen id
|
|
629
|
+
// missing from it disappears from the roster UI and the next Save — which PUTs
|
|
630
|
+
// exactly what the UI holds — silently truncates the persisted list. Losing a
|
|
631
|
+
// deliberate 5-model roster to an unrelated visibility toggle is data loss, not a
|
|
632
|
+
// filter. Same reasoning as `fetchGrokCandidateModels`, which deliberately lists a
|
|
633
|
+
// model the user already excluded so its switch remains reachable.
|
|
634
|
+
const selectableSet = new Set(selectable);
|
|
635
|
+
const available = [
|
|
636
|
+
...selectable,
|
|
637
|
+
...[...new Set(chosen)].filter(model => !selectableSet.has(model)),
|
|
638
|
+
];
|
|
463
639
|
// #857: let CLI/GUI show when a running Codex app-server keeps an older
|
|
464
640
|
// in-memory catalog than the one on disk.
|
|
465
641
|
const { collectCodexAppServerCatalogState } = await import("../../codex/app-server-processes");
|
|
466
642
|
const catalogState = collectCodexAppServerCatalogState();
|
|
467
|
-
return jsonResponse({ chosen
|
|
643
|
+
return jsonResponse({ chosen, available, catalogState });
|
|
468
644
|
}
|
|
469
645
|
if (url.pathname === "/api/subagent-models" && req.method === "PUT") {
|
|
470
646
|
let body: { models?: unknown };
|
|
@@ -473,10 +649,10 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
473
649
|
config.subagentModels = chosen;
|
|
474
650
|
const { saveConfigPreservingClaudeCode: save } = await import("../../config");
|
|
475
651
|
save(config);
|
|
476
|
-
await
|
|
652
|
+
const catalogRefresh = await convergeCodexCatalog();
|
|
477
653
|
await syncClaudeAgentDefsBestEffort();
|
|
478
654
|
await autoApplyDesktopBestEffort();
|
|
479
|
-
return jsonResponse({ ok: true, applied: chosen });
|
|
655
|
+
return jsonResponse({ ok: true, applied: chosen, catalogRefresh });
|
|
480
656
|
}
|
|
481
657
|
|
|
482
658
|
// Priority-ordered subagent model fallback chain for quota-aware spawn routing.
|
|
@@ -783,21 +959,19 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
783
959
|
// #859: the CLI delegates here so the registry is built in the serving
|
|
784
960
|
// process. Accept an optional mode; default stays static for back-compat.
|
|
785
961
|
let mode: "static" | "hybrid" | "discovery" = "static";
|
|
786
|
-
const rawBody = await req.text();
|
|
787
962
|
let parsed: unknown;
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
}
|
|
963
|
+
try {
|
|
964
|
+
parsed = await readOptionalManagementJsonBody(req);
|
|
965
|
+
} catch (error) {
|
|
966
|
+
rethrowManagementBodyTooLarge(error);
|
|
967
|
+
return jsonResponse({ error: "invalid JSON body" }, 400);
|
|
968
|
+
}
|
|
969
|
+
const requested = (parsed as { mode?: unknown } | null)?.mode;
|
|
970
|
+
if (requested !== undefined) {
|
|
971
|
+
if (requested === "static" || requested === "hybrid" || requested === "discovery") {
|
|
972
|
+
mode = requested;
|
|
973
|
+
} else {
|
|
974
|
+
return jsonResponse({ error: "mode must be static, hybrid, or discovery" }, 400);
|
|
801
975
|
}
|
|
802
976
|
}
|
|
803
977
|
// #859: a delegated CLI apply carries the profile it just saved — the
|
|
@@ -813,9 +987,26 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
813
987
|
return jsonResponse({ error: error instanceof Error ? error.message : String(error) }, 400);
|
|
814
988
|
}
|
|
815
989
|
}
|
|
990
|
+
const { setIntegrationEnabled, claudeDesktopIntegrationEnabled } = await import("../../codex/desired-state");
|
|
991
|
+
const desired = setIntegrationEnabled("claude-desktop", true);
|
|
992
|
+
if (!desired.ok) return jsonResponse({ error: desired.message }, desired.retryable ? 409 : 500);
|
|
993
|
+
// Disk now says ON; the reused server snapshot must agree, or the native
|
|
994
|
+
// GET reports OFF and a later whole-snapshot save undoes this transition.
|
|
995
|
+
mirrorDesiredEnabledOntoSnapshot(config, "claude-desktop", true);
|
|
816
996
|
const state = await buildClaudeDesktopState(config, profileOverride);
|
|
817
|
-
|
|
818
|
-
|
|
997
|
+
// `setIntegrationEnabled` above wrote desired ON to DISK; it does not touch
|
|
998
|
+
// this long-lived server snapshot. Saving the snapshot wholesale would carry
|
|
999
|
+
// its stale `clientIntegrations` back over that write and turn the enable
|
|
1000
|
+
// action into an immediate self-cancelling OFF — the guard below would then
|
|
1001
|
+
// refuse the apply it was asked to perform. Persist ONLY the profile field.
|
|
1002
|
+
const profileSaved = persistDesktopProfileField(config, state.profile);
|
|
1003
|
+
if (!profileSaved.ok) {
|
|
1004
|
+
return jsonResponse({
|
|
1005
|
+
error: `Claude Desktop profile could not be saved (${profileSaved.reason}); nothing was applied.`,
|
|
1006
|
+
saved: false,
|
|
1007
|
+
applied: false,
|
|
1008
|
+
}, profileSaved.reason === "conflict" ? 409 : 500);
|
|
1009
|
+
}
|
|
819
1010
|
const { writeDesktop3pConfig } = await import("../../claude/desktop-3p");
|
|
820
1011
|
const { desktopVisibleNativeSlugs } = await import("../../codex/catalog");
|
|
821
1012
|
const routed = state.models
|
|
@@ -824,22 +1015,52 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
824
1015
|
const slash = model.route.indexOf("/");
|
|
825
1016
|
return { provider: model.route.slice(0, slash), id: model.route.slice(slash + 1), contextWindow: model.contextWindow };
|
|
826
1017
|
});
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
1018
|
+
// State construction can await catalog work; never write from the stale
|
|
1019
|
+
// config captured before that await if another request turned Desktop off.
|
|
1020
|
+
const latest = loadConfig();
|
|
1021
|
+
if (!claudeDesktopIntegrationEnabled(latest)) {
|
|
1022
|
+
return jsonResponse({
|
|
1023
|
+
error: "Claude Desktop apply was cancelled because the desired state changed to off.",
|
|
1024
|
+
code: "claude_desktop_apply_skipped",
|
|
1025
|
+
reason: "desired_state_changed",
|
|
1026
|
+
desiredEnabled: false,
|
|
1027
|
+
saved: true,
|
|
1028
|
+
applied: false,
|
|
1029
|
+
}, 409);
|
|
1030
|
+
}
|
|
1031
|
+
const result = (deps.writeDesktop3pConfig ?? writeDesktop3pConfig)(
|
|
1032
|
+
Number(url.port) || latest.port,
|
|
1033
|
+
[...desktopVisibleNativeSlugs(latest)],
|
|
830
1034
|
routed,
|
|
831
|
-
|
|
1035
|
+
latest.apiKeys?.[0]?.key,
|
|
832
1036
|
mode,
|
|
833
1037
|
state.profile,
|
|
1038
|
+
nativeContextLimits(latest),
|
|
834
1039
|
);
|
|
835
1040
|
if (!result.written) return jsonResponse({ error: result.reason ?? "Claude Desktop apply failed", saved: true, path: result.path }, 500);
|
|
836
1041
|
// Persist applied fingerprint + timestamp so GUI can show saved-vs-applied state.
|
|
837
1042
|
if (result.fingerprint) {
|
|
838
|
-
|
|
839
|
-
|
|
1043
|
+
// The Desktop write already landed, so a failed bookkeeping save is not
|
|
1044
|
+
// an apply failure: report the miss instead of claiming a clean apply.
|
|
1045
|
+
const marked = persistDesktopProfileField(config, {
|
|
1046
|
+
...state.profile,
|
|
1047
|
+
appliedFingerprint: result.fingerprint,
|
|
1048
|
+
appliedAt: new Date().toISOString(),
|
|
1049
|
+
});
|
|
1050
|
+
if (!marked.ok) {
|
|
1051
|
+
return jsonResponse({
|
|
1052
|
+
ok: true,
|
|
1053
|
+
applied: true,
|
|
1054
|
+
saved: false,
|
|
1055
|
+
path: result.path,
|
|
1056
|
+
fingerprint: result.fingerprint,
|
|
1057
|
+
warning: `Claude Desktop was applied, but the applied marker was not saved (${marked.reason}).`,
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
840
1060
|
}
|
|
841
1061
|
return jsonResponse({ ok: true, saved: true, applied: true, path: result.path, fingerprint: result.fingerprint });
|
|
842
1062
|
} catch (error) {
|
|
1063
|
+
rethrowManagementBodyTooLarge(error);
|
|
843
1064
|
return jsonResponse({ error: error instanceof Error ? error.message : String(error) }, 400);
|
|
844
1065
|
}
|
|
845
1066
|
}
|
|
@@ -847,47 +1068,35 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
847
1068
|
// Desktop applied-state + health status.
|
|
848
1069
|
if (url.pathname === "/api/claude-desktop/status" && req.method === "GET") {
|
|
849
1070
|
try {
|
|
850
|
-
const {
|
|
851
|
-
const {
|
|
852
|
-
const
|
|
853
|
-
const
|
|
854
|
-
const
|
|
855
|
-
const
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
//
|
|
859
|
-
//
|
|
860
|
-
|
|
861
|
-
let activeProfile: boolean | null = null;
|
|
862
|
-
if (existsSync(metaPath)) {
|
|
863
|
-
try {
|
|
864
|
-
const meta = JSON.parse(readFile(metaPath, "utf8"));
|
|
865
|
-
const entry = Array.isArray(meta.entries) ? meta.entries.find((e: { name?: string }) => e?.name === "opencodex") : undefined;
|
|
866
|
-
const appliedId = typeof meta.appliedId === "string" ? meta.appliedId : null;
|
|
867
|
-
// A readable appliedId with no opencodex entry is a KNOWN false, not unknown.
|
|
868
|
-
activeProfile = appliedId === null ? null : (entry?.id ? appliedId === entry.id : false);
|
|
869
|
-
if (entry?.id) {
|
|
870
|
-
configPath = join(libraryPath, `${entry.id}.json`);
|
|
871
|
-
if (existsSync(configPath)) {
|
|
872
|
-
const onDisk = readFile(configPath, "utf8");
|
|
873
|
-
onDiskFingerprint = createHash("sha256").update(onDisk).digest("hex").slice(0, 16);
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
} catch { /* unreadable metadata */ }
|
|
877
|
-
}
|
|
878
|
-
const savedFingerprint = config.claudeCode?.desktopProfile?.appliedFingerprint ?? null;
|
|
879
|
-
const appliedAt = config.claudeCode?.desktopProfile?.appliedAt ?? null;
|
|
880
|
-
const stale = savedFingerprint !== null && onDiskFingerprint !== null && savedFingerprint !== onDiskFingerprint;
|
|
1071
|
+
const { claudeDesktopIntegrationEnabled } = await import("../../codex/desired-state");
|
|
1072
|
+
const { inspectDesktop3pConfigLibrary } = await import("../../claude/desktop-3p");
|
|
1073
|
+
const persisted = loadConfig();
|
|
1074
|
+
const savedFingerprint = persisted.claudeCode?.desktopProfile?.appliedFingerprint ?? null;
|
|
1075
|
+
const observed = inspectDesktop3pConfigLibrary({ appliedFingerprint: savedFingerprint });
|
|
1076
|
+
const desiredEnabled = claudeDesktopIntegrationEnabled(persisted);
|
|
1077
|
+
const applied = observed.kind === "gateway_ours" || observed.kind === "gateway_drifted";
|
|
1078
|
+
// "Needs update" is only meaningful while the integration is wanted. When the
|
|
1079
|
+
// durable switch is OFF, a leftover drifted profile is residue to clear — not
|
|
1080
|
+
// a stale apply the operator should refresh.
|
|
1081
|
+
const stale = desiredEnabled && observed.kind === "gateway_drifted";
|
|
881
1082
|
const { getDesktopHealth } = await import("../../claude/desktop-health");
|
|
882
1083
|
const health = getDesktopHealth();
|
|
883
1084
|
return jsonResponse({
|
|
884
|
-
|
|
885
|
-
|
|
1085
|
+
desiredEnabled,
|
|
1086
|
+
installed: observed.kind !== "not_installed",
|
|
1087
|
+
observedKind: observed.kind,
|
|
1088
|
+
applied,
|
|
1089
|
+
appliedAt: persisted.claudeCode?.desktopProfile?.appliedAt ?? null,
|
|
886
1090
|
savedFingerprint,
|
|
887
|
-
onDiskFingerprint,
|
|
888
|
-
configPath,
|
|
1091
|
+
onDiskFingerprint: observed.fingerprint ?? null,
|
|
1092
|
+
configPath: observed.selectedProfilePath,
|
|
889
1093
|
stale,
|
|
890
|
-
|
|
1094
|
+
// Tri-state by ID match, independent of profile health: null =
|
|
1095
|
+
// undeterminable (no/unreadable metadata or no appliedId); a readable
|
|
1096
|
+
// appliedId with no owned entry is a KNOWN false. Predates the inspector.
|
|
1097
|
+
activeProfile: observed.ownedProfileActive,
|
|
1098
|
+
drift: desiredEnabled ? !applied || stale : applied || observed.kind === "unsafe",
|
|
1099
|
+
driftReason: desiredEnabled ? (!applied ? "desired_on_not_current" : stale ? "profile_drift" : null) : (applied ? "desired_off_gateway_selected" : null),
|
|
891
1100
|
health,
|
|
892
1101
|
});
|
|
893
1102
|
} catch (error) {
|
|
@@ -921,7 +1130,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
921
1130
|
if (isDisabled(m.provider, m.id)) continue;
|
|
922
1131
|
aliases.push({ id: claudeCodeAlias(m.provider, m.id), display_name: `${m.id} (${m.provider})` });
|
|
923
1132
|
}
|
|
924
|
-
const contextWindows = buildClaudeContextWindows([...visibleNativeSlugs(config)], models);
|
|
1133
|
+
const contextWindows = buildClaudeContextWindows([...visibleNativeSlugs(config)], models, nativeContextLimits(config));
|
|
925
1134
|
const webSearchOverride = config.claudeCode?.webSearchSidecar;
|
|
926
1135
|
const visionOverride = config.claudeCode?.visionSidecar;
|
|
927
1136
|
// Auto is a RESOLUTION, recomputed per request — never stored state. Detection is
|
|
@@ -950,8 +1159,10 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
950
1159
|
smallFastModel: config.claudeCode?.smallFastModel ?? "",
|
|
951
1160
|
tierModels: config.claudeCode?.tierModels ?? {},
|
|
952
1161
|
modelMap: config.claudeCode?.modelMap ?? {},
|
|
1162
|
+
classifierModel: config.claudeCode?.classifierModel ?? "",
|
|
1163
|
+
classifierFallbacks: config.claudeCode?.classifierFallbacks ?? [],
|
|
953
1164
|
systemEnv: config.claudeCode?.systemEnv === true,
|
|
954
|
-
autoConnectSupported: process.platform === "darwin",
|
|
1165
|
+
autoConnectSupported: (ctx.deps.platform ?? process.platform) === "darwin",
|
|
955
1166
|
maxContextTokens: config.claudeCode?.maxContextTokens ?? null,
|
|
956
1167
|
alwaysEnableEffort: config.claudeCode?.alwaysEnableEffort === true,
|
|
957
1168
|
autoContext: config.claudeCode?.autoContext !== false,
|
|
@@ -987,18 +1198,78 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
987
1198
|
return prototype === Object.prototype || prototype === null;
|
|
988
1199
|
};
|
|
989
1200
|
if (!isPlainObject(parsedBody)) return jsonResponse({ error: "body must be an object" }, 400);
|
|
990
|
-
const body = parsedBody as { enabled?: unknown; authMode?: unknown; model?: unknown; smallFastModel?: unknown; modelMap?: unknown; systemEnv?: unknown; fastMode?: unknown; maxContextTokens?: unknown; alwaysEnableEffort?: unknown; tierModels?: unknown; autoContext?: unknown; autoCompactWindow?: unknown; blockedSkills?: unknown; injectAgents?: unknown; webSearchSidecar?: unknown; visionSidecar?: unknown };
|
|
1201
|
+
const body = parsedBody as { enabled?: unknown; authMode?: unknown; model?: unknown; smallFastModel?: unknown; modelMap?: unknown; classifierModel?: unknown; classifierFallbacks?: unknown; systemEnv?: unknown; fastMode?: unknown; maxContextTokens?: unknown; alwaysEnableEffort?: unknown; tierModels?: unknown; autoContext?: unknown; autoCompactWindow?: unknown; blockedSkills?: unknown; injectAgents?: unknown; webSearchSidecar?: unknown; visionSidecar?: unknown };
|
|
991
1202
|
for (const field of ["webSearchSidecar", "visionSidecar"] as const) {
|
|
992
1203
|
const section = body[field];
|
|
993
1204
|
if (section === undefined || section === null) continue;
|
|
994
1205
|
if (!isPlainObject(section)) return jsonResponse({ error: `${field} must be an object or null` }, 400);
|
|
1206
|
+
// Both overrides now speak their full unions (roadmap 060 web, 170
|
|
1207
|
+
// vision revised). Vision's third arm is "routed" (loopback through the
|
|
1208
|
+
// proxy's own router), never exa: exa is not an LLM, and accepting an
|
|
1209
|
+
// unknown literal would persist a backend the vision resolver reads as
|
|
1210
|
+
// unset (review F1's failure mode).
|
|
1211
|
+
const allowedBackends = field === "webSearchSidecar"
|
|
1212
|
+
? ["openai", "anthropic", "xai", "gemini", "exa"]
|
|
1213
|
+
: ["openai", "anthropic", "routed"];
|
|
995
1214
|
if (section.backend !== undefined && section.backend !== null
|
|
996
|
-
&&
|
|
997
|
-
return jsonResponse({ error: `${field}.backend must be
|
|
1215
|
+
&& !allowedBackends.includes(section.backend as string)) {
|
|
1216
|
+
return jsonResponse({ error: `${field}.backend must be ${allowedBackends.join(", ")}, or null` }, 400);
|
|
998
1217
|
}
|
|
999
1218
|
if (section.model !== undefined && typeof section.model !== "string") {
|
|
1000
1219
|
return jsonResponse({ error: `${field}.model must be a string` }, 400);
|
|
1001
1220
|
}
|
|
1221
|
+
// Vision override only: reject a model we can prove is blind. Unknown ids stay
|
|
1222
|
+
// allowed. Shares one policy module with /api/sidecar-settings so the two
|
|
1223
|
+
// gates cannot drift.
|
|
1224
|
+
if (field === "visionSidecar" && typeof section.model === "string" && section.model !== "") {
|
|
1225
|
+
const requested = section.model;
|
|
1226
|
+
const candidates = await visionCandidateRows(config);
|
|
1227
|
+
const hint = section.backend === "anthropic" || section.backend === "openai"
|
|
1228
|
+
|| section.backend === "routed"
|
|
1229
|
+
? section.backend
|
|
1230
|
+
: config.claudeCode?.visionSidecar?.backend;
|
|
1231
|
+
// Same coherence rule as /api/sidecar-settings (roadmap 170 r2).
|
|
1232
|
+
const effectiveBackend = hint ?? "openai";
|
|
1233
|
+
const namespaced = requested.includes("/");
|
|
1234
|
+
if (namespaced && effectiveBackend !== "routed") {
|
|
1235
|
+
return jsonResponse({ error: `visionSidecar.model "${requested}" is provider-namespaced; it requires backend "routed"` }, 400);
|
|
1236
|
+
}
|
|
1237
|
+
if (!namespaced && effectiveBackend === "routed") {
|
|
1238
|
+
return jsonResponse({ error: `visionSidecar.backend "routed" requires a provider-namespaced model ("provider/model"); got "${requested}"` }, 400);
|
|
1239
|
+
}
|
|
1240
|
+
if (visionDescriberIsProvablyBlind(config, requested, candidates, hint)) {
|
|
1241
|
+
return jsonResponse(visionDescriberRejection("visionSidecar.model", requested, config, candidates), 400);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
// Web-search override: membership gate (#2188). The executor set is closed,
|
|
1245
|
+
// so an id outside (runnable candidates ∪ auth slots) can never run. Same
|
|
1246
|
+
// module as /api/sidecar-settings — a gate on one route and a stale copy on
|
|
1247
|
+
// the other is no gate at all.
|
|
1248
|
+
if (field === "webSearchSidecar"
|
|
1249
|
+
&& (section.model !== undefined || section.backend !== undefined)) {
|
|
1250
|
+
const stored = config.claudeCode?.webSearchSidecar;
|
|
1251
|
+
const effectiveBackend = section.backend === "anthropic"
|
|
1252
|
+
? "anthropic"
|
|
1253
|
+
: section.backend === "openai"
|
|
1254
|
+
? "openai"
|
|
1255
|
+
: section.backend === null
|
|
1256
|
+
? config.webSearchSidecar?.backend ?? "openai"
|
|
1257
|
+
: stored?.backend ?? config.webSearchSidecar?.backend ?? "openai";
|
|
1258
|
+
const effectiveModel = section.model === ""
|
|
1259
|
+
? config.webSearchSidecar?.model
|
|
1260
|
+
: typeof section.model === "string"
|
|
1261
|
+
? section.model
|
|
1262
|
+
: stored?.model ?? config.webSearchSidecar?.model;
|
|
1263
|
+
const candidates = await webSearchCandidateRows(config);
|
|
1264
|
+
if (effectiveModel && webSearchModelIsRejected(effectiveBackend, effectiveModel, candidates)) {
|
|
1265
|
+
return jsonResponse(webSearchModelRejection(
|
|
1266
|
+
"webSearchSidecar.model",
|
|
1267
|
+
effectiveBackend,
|
|
1268
|
+
effectiveModel,
|
|
1269
|
+
candidates,
|
|
1270
|
+
), 400);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1002
1273
|
}
|
|
1003
1274
|
const next = { ...(config.claudeCode ?? {}) };
|
|
1004
1275
|
for (const field of ["webSearchSidecar", "visionSidecar"] as const) {
|
|
@@ -1008,13 +1279,17 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1008
1279
|
delete next[field];
|
|
1009
1280
|
continue;
|
|
1010
1281
|
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1282
|
+
// The per-field validation above guarantees vision only ever carries the two-member
|
|
1283
|
+
// union; the cast is the loop's shared-shape compromise, not a wider write path.
|
|
1284
|
+
const requested = section as { backend?: "openai" | "anthropic" | "xai" | "gemini" | "exa" | null; model?: string };
|
|
1285
|
+
const override = { ...next[field] } as NonNullable<OcxClaudeCodeConfig[typeof field]>;
|
|
1013
1286
|
if (requested.backend === null) delete override.backend;
|
|
1014
|
-
else if (requested.backend !== undefined) override.backend = requested.backend;
|
|
1287
|
+
else if (requested.backend !== undefined) override.backend = requested.backend as never;
|
|
1015
1288
|
if (requested.model === "") delete override.model;
|
|
1016
1289
|
else if (requested.model !== undefined) override.model = requested.model;
|
|
1017
|
-
|
|
1290
|
+
// Indexed write across the field union collapses to an intersection; runtime
|
|
1291
|
+
// validation above already guarantees the per-field shape.
|
|
1292
|
+
if (Object.keys(override).length > 0) next[field] = override as never;
|
|
1018
1293
|
else delete next[field];
|
|
1019
1294
|
}
|
|
1020
1295
|
if (body.enabled !== undefined) {
|
|
@@ -1107,19 +1382,38 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1107
1382
|
else delete next.tierModels;
|
|
1108
1383
|
}
|
|
1109
1384
|
}
|
|
1385
|
+
let nextFastMode = config.fastMode;
|
|
1110
1386
|
if (body.fastMode !== undefined) {
|
|
1111
1387
|
if (body.fastMode !== true && body.fastMode !== false && body.fastMode !== null) {
|
|
1112
1388
|
return jsonResponse({ error: "fastMode must be true, false, or null" }, 400);
|
|
1113
1389
|
}
|
|
1114
|
-
|
|
1390
|
+
nextFastMode = body.fastMode === null ? undefined : body.fastMode;
|
|
1115
1391
|
}
|
|
1116
|
-
for (const field of ["model", "smallFastModel"] as const) {
|
|
1392
|
+
for (const field of ["model", "smallFastModel", "classifierModel"] as const) {
|
|
1117
1393
|
const value = body[field];
|
|
1118
1394
|
if (value === undefined) continue;
|
|
1119
1395
|
if (typeof value !== "string") return jsonResponse({ error: `${field} must be a string` }, 400);
|
|
1120
1396
|
if (value.trim() === "") delete next[field];
|
|
1121
1397
|
else next[field] = value.trim();
|
|
1122
1398
|
}
|
|
1399
|
+
if (body.classifierFallbacks !== undefined) {
|
|
1400
|
+
if (body.classifierFallbacks === null) {
|
|
1401
|
+
delete next.classifierFallbacks;
|
|
1402
|
+
} else {
|
|
1403
|
+
if (!Array.isArray(body.classifierFallbacks)) {
|
|
1404
|
+
return jsonResponse({ error: "classifierFallbacks must be an array of strings, or null" }, 400);
|
|
1405
|
+
}
|
|
1406
|
+
const list: string[] = [];
|
|
1407
|
+
for (const entry of body.classifierFallbacks) {
|
|
1408
|
+
if (typeof entry !== "string" || entry.trim() === "") {
|
|
1409
|
+
return jsonResponse({ error: "classifierFallbacks entries must be non-empty strings" }, 400);
|
|
1410
|
+
}
|
|
1411
|
+
list.push(entry.trim());
|
|
1412
|
+
}
|
|
1413
|
+
if (list.length > 0) next.classifierFallbacks = list;
|
|
1414
|
+
else delete next.classifierFallbacks;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1123
1417
|
if (body.modelMap !== undefined) {
|
|
1124
1418
|
if (body.modelMap === null) {
|
|
1125
1419
|
delete next.modelMap;
|
|
@@ -1138,6 +1432,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1138
1432
|
else delete next.modelMap;
|
|
1139
1433
|
}
|
|
1140
1434
|
}
|
|
1435
|
+
if (body.fastMode !== undefined) config.fastMode = nextFastMode;
|
|
1141
1436
|
config.claudeCode = next;
|
|
1142
1437
|
// Stamp the migration sentinel on EVERY persist of this block. The migration reads
|
|
1143
1438
|
// "a claudeCode block with no authMode" as a pre-upgrade subscriber and pins it to
|