@iislee/opencodex 2.11.0 → 2.35.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 +117 -22
- package/bin/ocx.mjs +245 -88
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -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 +21 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +475 -62
- package/src/adapters/base.ts +82 -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 +637 -0
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +308 -0
- package/src/adapters/cursor/cursor-errors.ts +144 -6
- package/src/adapters/cursor/discovery.ts +122 -14
- package/src/adapters/cursor/effort-map.ts +106 -4
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- 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 +180 -59
- package/src/adapters/cursor/live-transport.ts +623 -170
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- 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 +5 -3
- package/src/adapters/cursor/native-exec.ts +121 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +447 -75
- package/src/adapters/cursor/request-builder.ts +271 -35
- package/src/adapters/cursor/tool-definitions.ts +252 -12
- package/src/adapters/cursor/tool-result-normalize.ts +115 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +48 -1
- package/src/adapters/cursor.ts +336 -39
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +713 -41
- package/src/adapters/google-antigravity-wire.ts +48 -10
- package/src/adapters/google-errors.ts +44 -12
- 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 +742 -86
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- 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 +65 -17
- package/src/adapters/kiro.ts +270 -73
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1377 -344
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +1164 -64
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +105 -21
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +186 -0
- package/src/bridge.ts +512 -92
- package/src/chat/inbound.ts +57 -20
- package/src/chat/outbound.ts +97 -33
- package/src/claude/agents-inject.ts +40 -10
- package/src/claude/context-windows.ts +37 -14
- 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 +88 -21
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +150 -19
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +648 -28
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +97 -19
- package/src/cli/agent.ts +53 -2
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +60 -15
- package/src/cli/claude.ts +129 -35
- 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 +821 -0
- package/src/cli/doctor.ts +440 -56
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +34 -274
- package/src/cli/index.ts +417 -528
- package/src/cli/init.ts +5 -17
- package/src/cli/inspect.ts +230 -0
- 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 +130 -2
- package/src/cli/models.ts +118 -16
- package/src/cli/observe.ts +123 -11
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +34 -3
- package/src/cli/provider.ts +25 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +476 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +57 -10
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +51 -4
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +137 -18
- package/src/cli/version-skew.ts +46 -0
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1442 -23
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +35 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +84 -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 +570 -107
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +893 -246
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +348 -32
- package/src/codex/autostart-health.ts +24 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +86 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +138 -32
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +555 -45
- package/src/codex/catalog/native-models.ts +75 -0
- package/src/codex/catalog/parsing.ts +269 -30
- package/src/codex/catalog/provider-fetch.ts +1626 -144
- package/src/codex/catalog/sync.ts +1576 -187
- 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 +386 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +676 -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/desktop-app-restart.ts +355 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +436 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +30 -24
- package/src/codex/history-provider.ts +1016 -235
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +223 -0
- package/src/codex/inject-coordination.ts +373 -0
- package/src/codex/inject.ts +1114 -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 +81 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +524 -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 +360 -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 +622 -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 +675 -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 +1313 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +264 -21
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +514 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1065 -31
- package/src/codex/subagent-model-fallback.ts +437 -43
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +720 -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 +47 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +91 -9
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-name.ts +24 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +1713 -709
- package/src/generated/compatibility-version.json +3324 -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 +157 -33
- package/src/images/plan.ts +24 -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-policy.ts +160 -0
- package/src/integrations/ownership.ts +155 -0
- package/src/integrations/registry.ts +166 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +433 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +715 -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/admin-secrets.ts +24 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +167 -11
- 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 +26 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +80 -5
- 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/package-tree-integrity.ts +101 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +6 -2
- 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/service-secrets.ts +15 -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 +253 -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 +30 -4
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +462 -71
- package/src/oauth/key-providers.ts +25 -0
- package/src/oauth/kimi.ts +25 -3
- package/src/oauth/kiro.ts +95 -6
- package/src/oauth/local-token-detect.ts +13 -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/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +133 -26
- package/src/oauth/token-guardian.ts +9 -3
- package/src/oauth/types.ts +15 -0
- 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/auto-compact-budget.ts +65 -0
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +176 -0
- package/src/providers/context-cap.ts +26 -8
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +281 -6
- package/src/providers/fastwire.ts +507 -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 +35 -2
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/new-model-policy.ts +146 -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 +30 -0
- package/src/providers/quota.ts +1379 -40
- package/src/providers/registry.ts +1545 -112
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +94 -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 +32 -4
- package/src/reasoning-effort.ts +68 -3
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +384 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +435 -0
- package/src/responses/parser.ts +225 -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 +504 -0
- package/src/responses/schema.ts +12 -2
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +659 -31
- package/src/responses/thought-signature-replay.ts +364 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +405 -31
- package/src/routing/analytics.ts +379 -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 +155 -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 +289 -48
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +154 -72
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +445 -0
- package/src/server/claude-messages.ts +160 -44
- 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 +1021 -190
- package/src/server/lifecycle.ts +336 -14
- 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 +433 -116
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +64 -25
- package/src/server/management/config-routes.ts +511 -51
- package/src/server/management/context.ts +89 -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 +127 -52
- package/src/server/management/model-routes.ts +461 -133
- package/src/server/management/model-rows.ts +167 -0
- package/src/server/management/native-integration-routes.ts +770 -0
- package/src/server/management/oauth-account-routes.ts +90 -8
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +836 -159
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/route-registry.ts +332 -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 +289 -33
- 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 +126 -23
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +161 -10
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +221 -54
- package/src/server/relay.ts +479 -87
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +298 -31
- 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/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +239 -36
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +444 -72
- package/src/server/responses/core.ts +3579 -436
- package/src/server/responses/empty-completion-guard.ts +333 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +89 -99
- package/src/server/responses/input-admission.ts +187 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +343 -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 +381 -0
- package/src/server/responses-custom-tool-repair.ts +363 -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 +363 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +376 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +319 -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 +53 -40
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1153 -94
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- 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 +911 -0
- package/src/types/provider.ts +614 -0
- package/src/types/request.ts +397 -0
- package/src/types/tools.ts +236 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +107 -1237
- package/src/update/index.ts +37 -23
- package/src/update/job.ts +445 -68
- 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 +266 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +287 -16
- package/src/usage/log.ts +666 -50
- package/src/usage/summary.ts +388 -17
- 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 +27 -16
- 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 +119 -0
- package/src/web-search/executor.ts +21 -6
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +284 -54
- 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,14 +1,16 @@
|
|
|
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,
|
|
8
|
+
deleteConfigTopLevelKey,
|
|
8
9
|
hasOwnProvider,
|
|
9
10
|
isValidProviderName,
|
|
10
11
|
loadConfig,
|
|
11
12
|
multiAgentGuidanceEnabled,
|
|
13
|
+
mutatePersistedConfig,
|
|
12
14
|
providerBaseUrlConfigError,
|
|
13
15
|
providerHeadersConfigError,
|
|
14
16
|
saveConfigPreservingClaudeCode,
|
|
@@ -30,7 +32,7 @@ import { deriveProviderPresets } from "../../providers/derive";
|
|
|
30
32
|
import { providerCodexAccountMode } from "../../providers/registry";
|
|
31
33
|
import { routedSlug, slugEquals } from "../../providers/slug-codec";
|
|
32
34
|
import { clearProviderQuotaCache, fetchProviderQuotaReports } from "../../providers/quota";
|
|
33
|
-
import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
35
|
+
import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
|
|
34
36
|
import { clearThreadAccountMap } from "../../codex/routing";
|
|
35
37
|
import { primeCodexPoolQuotas } from "../../codex/auth-api";
|
|
36
38
|
import { DEFAULT_PROVIDER_CONTEXT_CAP, globalContextCapValue, providerContextCap, providerContextCaps, setAllProviderContextCaps, setGlobalContextCapValue, setProviderContextCap } from "../../providers/context-cap";
|
|
@@ -50,6 +52,17 @@ import {
|
|
|
50
52
|
type DebugFlag,
|
|
51
53
|
} from "../../lib/debug-settings";
|
|
52
54
|
import type { OcxClaudeCodeConfig, OcxConfig, OcxCustomModel, OcxProviderConfig } from "../../types";
|
|
55
|
+
import {
|
|
56
|
+
visionCandidateRows,
|
|
57
|
+
visionDescriberIsProvablyBlind,
|
|
58
|
+
visionDescriberRejection,
|
|
59
|
+
} from "./vision-sidecar-options";
|
|
60
|
+
import {
|
|
61
|
+
webSearchCandidateRows,
|
|
62
|
+
webSearchModelIsRejected,
|
|
63
|
+
webSearchModelRejection,
|
|
64
|
+
type WebSearchBackend,
|
|
65
|
+
} from "./web-search-sidecar-options";
|
|
53
66
|
import { drainAndShutdown } from "../lifecycle";
|
|
54
67
|
import { filterRequestLogs, getRequestLogEntries, type RequestLogEntry } from "../request-log";
|
|
55
68
|
import { estimateComboCost, estimateRequestCost, normalizeCostTokens, tokensPerSecond } from "../../usage/cost";
|
|
@@ -59,7 +72,7 @@ import { applySystemEnvToggle } from "../system-env";
|
|
|
59
72
|
|
|
60
73
|
import { isPlainRecord, parseDebugLogQuery, tokPerSecondResult, unavailableCostReason, costResult, requestLogDto, stripRegistryOnlyStaticHeaders, fetchAllModels, fetchGrokCandidateModels, buildClaudeDesktopState } from "./shared";
|
|
61
74
|
import type { MetricUnavailableReason, TokPerSecondResult, CostEstimateReason, CostResult, MetricSource } from "./shared";
|
|
62
|
-
import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
|
|
75
|
+
import { readManagementJsonBody, readOptionalManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
|
|
63
76
|
|
|
64
77
|
const GROK_APPLY_JOIN_MS = 120_000;
|
|
65
78
|
export const GROK_APPLY_TERMINAL_MS = 10 * 60_000;
|
|
@@ -70,6 +83,49 @@ let grokApplyTestHooks: { now?: () => number; run?: () => Promise<unknown> } | n
|
|
|
70
83
|
|
|
71
84
|
class GrokApplyBusyError extends Error {}
|
|
72
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Mirror a durable desired-state transition onto the long-lived server snapshot.
|
|
88
|
+
*
|
|
89
|
+
* `setIntegrationEnabled` writes DISK only. The server reuses one `config` object
|
|
90
|
+
* for every request, so leaving it stale makes the native GET report the opposite
|
|
91
|
+
* of what was just persisted, and makes any later whole-snapshot save (the Desktop
|
|
92
|
+
* profile PUT does exactly that) write the stale value back over the transition.
|
|
93
|
+
* ON is the ABSENCE of the key, matching `setIntegrationEnabled`'s on-disk shape.
|
|
94
|
+
*/
|
|
95
|
+
function mirrorDesiredEnabledOntoSnapshot(config: OcxConfig, client: "claude-desktop", enabled: boolean): void {
|
|
96
|
+
const integrations = { ...(config.clientIntegrations ?? {}) };
|
|
97
|
+
if (enabled) delete integrations[client];
|
|
98
|
+
else integrations[client] = false;
|
|
99
|
+
if (Object.keys(integrations).length === 0) deleteConfigTopLevelKey(config, "clientIntegrations");
|
|
100
|
+
else config.clientIntegrations = integrations;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Persist ONLY `claudeCode.desktopProfile`, field-scoped, against the CURRENT
|
|
105
|
+
* on-disk config.
|
|
106
|
+
*
|
|
107
|
+
* `saveConfigPreservingClaudeCode(ctx.config)` writes the whole long-lived server
|
|
108
|
+
* snapshot. On the apply path that snapshot still carries the `clientIntegrations`
|
|
109
|
+
* it was loaded with, so a save right after `setIntegrationEnabled("claude-desktop",
|
|
110
|
+
* true)` carried the stale OFF back over the enable and made the route cancel its
|
|
111
|
+
* own apply. Mutating one field under the config-mutation lock cannot regress an
|
|
112
|
+
* unrelated key another writer just committed.
|
|
113
|
+
*/
|
|
114
|
+
function persistDesktopProfileField(
|
|
115
|
+
config: OcxConfig,
|
|
116
|
+
desktopProfile: NonNullable<OcxConfig["claudeCode"]>["desktopProfile"],
|
|
117
|
+
): { ok: true } | { ok: false; reason: "missing" | "invalid" | "conflict" } {
|
|
118
|
+
const outcome = mutatePersistedConfig(persisted => {
|
|
119
|
+
persisted.claudeCode = { ...(persisted.claudeCode ?? {}), desktopProfile };
|
|
120
|
+
return { changed: true, value: true };
|
|
121
|
+
});
|
|
122
|
+
// Only mirror into memory once the durable write actually landed; an
|
|
123
|
+
// `unavailable` outcome must not leave the snapshot claiming a saved profile.
|
|
124
|
+
if (outcome.status === "unavailable") return { ok: false, reason: outcome.reason };
|
|
125
|
+
config.claudeCode = { ...(config.claudeCode ?? {}), desktopProfile };
|
|
126
|
+
return { ok: true };
|
|
127
|
+
}
|
|
128
|
+
|
|
73
129
|
export function grokApplyFlightSnapshot(): { currentBytes: number; highWaterBytes: number; active: number } {
|
|
74
130
|
return {
|
|
75
131
|
currentBytes: grokApplyFlight?.bytes ?? 0,
|
|
@@ -94,7 +150,7 @@ function runGrokApplyFlight(): Promise<unknown> {
|
|
|
94
150
|
flight.promise = (grokApplyTestHooks?.run ?? (async () => {
|
|
95
151
|
const [{ syncGrokConfig }, { readRuntimePort }] = await Promise.all([
|
|
96
152
|
import("../../grok/sync"),
|
|
97
|
-
import("../../config"),
|
|
153
|
+
import("../../config/process-state"),
|
|
98
154
|
]);
|
|
99
155
|
const currentConfig = loadConfig();
|
|
100
156
|
const runtime = readRuntimePort(process.pid);
|
|
@@ -125,28 +181,44 @@ export function setGrokApplyFlightTestHooks(
|
|
|
125
181
|
import type { ManagementContext } from "./context";
|
|
126
182
|
|
|
127
183
|
export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise<Response | null> {
|
|
128
|
-
const { req, url, config, deps,
|
|
184
|
+
const { req, url, config, deps, convergeCodexCatalog, syncClaudeAgentDefsBestEffort } = ctx;
|
|
129
185
|
|
|
130
186
|
/** Best-effort Desktop 3P config auto-reconcile when providers change. */
|
|
131
187
|
async function autoApplyDesktopBestEffort(): Promise<void> {
|
|
132
188
|
try {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
189
|
+
const { claudeDesktopIntegrationEnabled } = await import("../../codex/desired-state");
|
|
190
|
+
const admitted = loadConfig();
|
|
191
|
+
if (!claudeDesktopIntegrationEnabled(admitted)) return;
|
|
192
|
+
if (admitted.claudeCode?.desktopAutoApply === false) return;
|
|
193
|
+
if (!admitted.claudeCode?.desktopProfile) return;
|
|
194
|
+
const { inspectDesktop3pConfigLibrary, writeDesktop3pConfig } = await import("../../claude/desktop-3p");
|
|
195
|
+
const beforeKind = inspectDesktop3pConfigLibrary({
|
|
196
|
+
appliedFingerprint: admitted.claudeCode.desktopProfile.appliedFingerprint ?? null,
|
|
197
|
+
}).kind;
|
|
198
|
+
if (["not_installed", "no_owned_state", "foreign", "unsafe", "broken"].includes(beforeKind)) return;
|
|
136
199
|
const { filterCatalogVisibleModels, desktopVisibleNativeSlugs } = await import("../../codex/catalog");
|
|
137
|
-
const allModels = await fetchAllModels(
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
200
|
+
const allModels = await (deps.fetchAllModels ?? fetchAllModels)(admitted);
|
|
201
|
+
const current = loadConfig();
|
|
202
|
+
// This is the real guard: the catalog await admits a concurrent explicit OFF.
|
|
203
|
+
if (!claudeDesktopIntegrationEnabled(current)) return;
|
|
204
|
+
if (current.claudeCode?.desktopAutoApply === false || !current.claudeCode?.desktopProfile) return;
|
|
205
|
+
const afterKind = inspectDesktop3pConfigLibrary({
|
|
206
|
+
appliedFingerprint: current.claudeCode.desktopProfile.appliedFingerprint ?? null,
|
|
207
|
+
}).kind;
|
|
208
|
+
if (["not_installed", "no_owned_state", "foreign", "unsafe", "broken"].includes(afterKind)) return;
|
|
209
|
+
const routed = filterCatalogVisibleModels(allModels, current).map(m => ({ provider: m.provider, id: m.id, contextWindow: m.contextWindow }));
|
|
210
|
+
const result = (deps.writeDesktop3pConfig ?? writeDesktop3pConfig)(
|
|
211
|
+
current.port ?? 10100,
|
|
212
|
+
[...desktopVisibleNativeSlugs(current)],
|
|
142
213
|
routed,
|
|
143
|
-
|
|
214
|
+
current.apiKeys?.[0]?.key,
|
|
144
215
|
"static",
|
|
145
|
-
|
|
216
|
+
current.claudeCode.desktopProfile,
|
|
217
|
+
nativeContextLimits(current),
|
|
146
218
|
);
|
|
147
219
|
if (result.written && result.fingerprint) {
|
|
148
|
-
|
|
149
|
-
saveConfigPreservingClaudeCode(
|
|
220
|
+
current.claudeCode = { ...current.claudeCode, desktopProfile: { ...current.claudeCode.desktopProfile, appliedFingerprint: result.fingerprint, appliedAt: new Date().toISOString() } };
|
|
221
|
+
saveConfigPreservingClaudeCode(current);
|
|
150
222
|
}
|
|
151
223
|
} catch { /* best-effort */ }
|
|
152
224
|
}
|
|
@@ -160,6 +232,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
160
232
|
const {
|
|
161
233
|
isMultiAgentV2Enabled, hasAgentsMaxThreads, getLogicalMaxThreads,
|
|
162
234
|
getAgentsEnabled, getAgentsMaxDepth, getSubagentDeveloperInstructions,
|
|
235
|
+
getMultiAgentModeHintText,
|
|
163
236
|
} = await import("../../codex/features");
|
|
164
237
|
const enabled = isMultiAgentV2Enabled();
|
|
165
238
|
return jsonResponse({
|
|
@@ -167,9 +240,11 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
167
240
|
agentsMaxThreadsConflict: enabled && hasAgentsMaxThreads(),
|
|
168
241
|
maxConcurrentThreadsPerSession: getLogicalMaxThreads(),
|
|
169
242
|
multiAgentMode: config.multiAgentMode ?? "default",
|
|
243
|
+
keepNativeChatGptOnV1: config.keepNativeChatGptOnV1 === true,
|
|
170
244
|
agentsEnabled: getAgentsEnabled(),
|
|
171
245
|
agentsMaxDepth: getAgentsMaxDepth(),
|
|
172
246
|
subagentDeveloperInstructions: getSubagentDeveloperInstructions(),
|
|
247
|
+
multiAgentModeHintText: getMultiAgentModeHintText(),
|
|
173
248
|
// max_depth is V1-only upstream; this is the global-flag statement, derived
|
|
174
249
|
// server-side so no client can present it as an effective V2 limit.
|
|
175
250
|
agentsMaxDepthAppliesWhenV2Disabled: !enabled,
|
|
@@ -180,24 +255,31 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
180
255
|
enabled?: unknown;
|
|
181
256
|
maxConcurrentThreadsPerSession?: unknown;
|
|
182
257
|
multiAgentMode?: unknown;
|
|
258
|
+
keepNativeChatGptOnV1?: unknown;
|
|
183
259
|
agentsEnabled?: unknown;
|
|
184
260
|
agentsMaxDepth?: unknown;
|
|
185
261
|
subagentDeveloperInstructions?: unknown;
|
|
262
|
+
multiAgentModeHintText?: unknown;
|
|
186
263
|
};
|
|
187
264
|
try { body = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
|
|
188
265
|
const wantsFlag = body.enabled !== undefined;
|
|
189
266
|
const wantsThreads = body.maxConcurrentThreadsPerSession !== undefined;
|
|
190
267
|
const wantsMode = body.multiAgentMode !== undefined;
|
|
268
|
+
const wantsKeepNative = body.keepNativeChatGptOnV1 !== undefined;
|
|
191
269
|
const wantsAgentsEnabled = body.agentsEnabled !== undefined;
|
|
192
270
|
const wantsMaxDepth = body.agentsMaxDepth !== undefined;
|
|
193
271
|
const wantsSubagentInstructions = body.subagentDeveloperInstructions !== undefined;
|
|
194
|
-
|
|
195
|
-
|
|
272
|
+
const wantsModeHintText = body.multiAgentModeHintText !== undefined;
|
|
273
|
+
if (!wantsFlag && !wantsThreads && !wantsMode && !wantsKeepNative && !wantsAgentsEnabled && !wantsMaxDepth && !wantsSubagentInstructions && !wantsModeHintText) {
|
|
274
|
+
return jsonResponse({ error: "body must set enabled, multiAgentMode, keepNativeChatGptOnV1, maxConcurrentThreadsPerSession, agentsEnabled, agentsMaxDepth, subagentDeveloperInstructions, and/or multiAgentModeHintText" }, 400);
|
|
196
275
|
}
|
|
197
276
|
if (wantsFlag && typeof body.enabled !== "boolean") return jsonResponse({ error: "body.enabled must be a boolean" }, 400);
|
|
198
277
|
if (wantsMode && body.multiAgentMode !== "v1" && body.multiAgentMode !== "default" && body.multiAgentMode !== "v2") {
|
|
199
278
|
return jsonResponse({ error: "body.multiAgentMode must be 'v1', 'default', or 'v2'" }, 400);
|
|
200
279
|
}
|
|
280
|
+
if (wantsKeepNative && typeof body.keepNativeChatGptOnV1 !== "boolean") {
|
|
281
|
+
return jsonResponse({ error: "body.keepNativeChatGptOnV1 must be a boolean" }, 400);
|
|
282
|
+
}
|
|
201
283
|
if (wantsThreads && (typeof body.maxConcurrentThreadsPerSession !== "number" || !Number.isInteger(body.maxConcurrentThreadsPerSession) || body.maxConcurrentThreadsPerSession < 1)) {
|
|
202
284
|
return jsonResponse({ error: "body.maxConcurrentThreadsPerSession must be an integer >= 1" }, 400);
|
|
203
285
|
}
|
|
@@ -216,29 +298,58 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
216
298
|
if (wantsSubagentInstructions && body.subagentDeveloperInstructions !== null && typeof body.subagentDeveloperInstructions !== "string") {
|
|
217
299
|
return jsonResponse({ error: "body.subagentDeveloperInstructions must be a string or null" }, 400);
|
|
218
300
|
}
|
|
301
|
+
// null unsets the upstream key (effort-derived policy resumes); an empty/whitespace
|
|
302
|
+
// string is rejected because codex-rs treats any present hint as an override that
|
|
303
|
+
// suppresses even the Ultra-derived Proactive message (Option<String>, no blank
|
|
304
|
+
// special-case in effective_multi_agent_mode).
|
|
305
|
+
if (wantsModeHintText && body.multiAgentModeHintText !== null
|
|
306
|
+
&& (typeof body.multiAgentModeHintText !== "string" || body.multiAgentModeHintText.trim().length === 0)) {
|
|
307
|
+
return jsonResponse({ error: "body.multiAgentModeHintText must be a non-empty string or null" }, 400);
|
|
308
|
+
}
|
|
219
309
|
const mode = wantsMode ? body.multiAgentMode as "v1" | "default" | "v2" : undefined;
|
|
220
|
-
const
|
|
310
|
+
const effectiveMode = mode ?? config.multiAgentMode ?? "default";
|
|
311
|
+
const effectiveKeepNative = wantsKeepNative
|
|
312
|
+
? body.keepNativeChatGptOnV1 === true
|
|
313
|
+
: config.keepNativeChatGptOnV1 === true;
|
|
314
|
+
const hybridPinActive = effectiveMode === "v2" && effectiveKeepNative;
|
|
315
|
+
const modeFlag = mode === "v2" ? !hybridPinActive : mode === "v1" ? false : undefined;
|
|
221
316
|
if (wantsFlag && modeFlag !== undefined && body.enabled !== modeFlag) {
|
|
222
|
-
return jsonResponse({
|
|
317
|
+
return jsonResponse({
|
|
318
|
+
error: hybridPinActive
|
|
319
|
+
? "body.enabled=true conflicts with keepNativeChatGptOnV1: Codex's global multi_agent_v2 override outranks catalog pins"
|
|
320
|
+
: `body.enabled conflicts with multiAgentMode '${mode}'`,
|
|
321
|
+
}, 400);
|
|
223
322
|
}
|
|
224
323
|
const {
|
|
225
324
|
isMultiAgentV2Enabled, hasAgentsMaxThreads, getLogicalMaxThreads, transitionMultiAgentV2,
|
|
226
325
|
getAgentsEnabled, getAgentsMaxDepth, getSubagentDeveloperInstructions,
|
|
227
|
-
setAgentsEnabled, setAgentsMaxDepth,
|
|
326
|
+
getMultiAgentModeHintText, probeCodexSupportsModeHint, setAgentsEnabled, setAgentsMaxDepth,
|
|
327
|
+
setSubagentDeveloperInstructions, setMultiAgentModeHintText, MODE_HINT_UNSUPPORTED_ERROR,
|
|
228
328
|
} = await import("../../codex/features");
|
|
329
|
+
// Probe the capability before any combined-request mutation. The scalar writer
|
|
330
|
+
// repeats this check, but doing it here prevents an earlier flag/mode/agents
|
|
331
|
+
// write from landing before an unsupported runtime returns 502.
|
|
332
|
+
if (wantsModeHintText && body.multiAgentModeHintText !== null
|
|
333
|
+
&& probeCodexSupportsModeHint() === false) {
|
|
334
|
+
return jsonResponse({
|
|
335
|
+
error: `writing multiAgentModeHintText failed: ${MODE_HINT_UNSUPPORTED_ERROR}`,
|
|
336
|
+
}, 502);
|
|
337
|
+
}
|
|
229
338
|
const warnings: string[] = [];
|
|
230
|
-
|
|
339
|
+
if (wantsFlag && body.enabled === true && hybridPinActive) {
|
|
340
|
+
return jsonResponse({
|
|
341
|
+
error: "body.enabled=true conflicts with keepNativeChatGptOnV1: Codex's global multi_agent_v2 override outranks catalog pins",
|
|
342
|
+
}, 400);
|
|
343
|
+
}
|
|
344
|
+
const requestedFlag = wantsFlag
|
|
345
|
+
? body.enabled as boolean
|
|
346
|
+
: modeFlag ?? (wantsKeepNative && hybridPinActive ? false : undefined);
|
|
231
347
|
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
|
-
};
|
|
348
|
+
const targetFlag = requestedFlag ?? isMultiAgentV2Enabled();
|
|
349
|
+
let toggle = deps.toggleCodexMultiAgentV2;
|
|
350
|
+
if (!toggle) {
|
|
351
|
+
const { runCodexFeaturesCommand } = await import("../../cli/v2");
|
|
352
|
+
toggle = (enabled: boolean) => runCodexFeaturesCommand(enabled ? "enable" : "disable");
|
|
242
353
|
}
|
|
243
354
|
const result = transitionMultiAgentV2(targetFlag, toggle, {
|
|
244
355
|
...(wantsThreads ? { threadLimit: body.maxConcurrentThreadsPerSession as number } : {}),
|
|
@@ -247,11 +358,21 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
247
358
|
if (result.changed && result.threadLimit !== null) warnings.push(`Thread limit ${result.threadLimit} preserved for ${targetFlag ? "v2" : "v1"}.`);
|
|
248
359
|
}
|
|
249
360
|
if (wantsMode) {
|
|
250
|
-
if (mode === "default")
|
|
361
|
+
if (mode === "default") deleteConfigTopLevelKey(config, "multiAgentMode");
|
|
251
362
|
else config.multiAgentMode = mode;
|
|
252
363
|
saveConfigPreservingClaudeCode(config);
|
|
253
364
|
warnings.push(`Multi-agent mode set to '${mode}'. Applies to new sessions.`);
|
|
254
365
|
}
|
|
366
|
+
if (wantsKeepNative) {
|
|
367
|
+
if (body.keepNativeChatGptOnV1 === true) config.keepNativeChatGptOnV1 = true;
|
|
368
|
+
else deleteConfigTopLevelKey(config, "keepNativeChatGptOnV1");
|
|
369
|
+
saveConfigPreservingClaudeCode(config);
|
|
370
|
+
warnings.push(body.keepNativeChatGptOnV1 === true
|
|
371
|
+
? (effectiveMode === "v2"
|
|
372
|
+
? "ChatGPT-native models stay on v1 while other models use v2. Applies to new sessions."
|
|
373
|
+
: "keepNativeChatGptOnV1 is stored but inactive until multi-agent mode is v2. Applies to new sessions.")
|
|
374
|
+
: "ChatGPT-native models follow the selected v1/v2/base surface. Applies to new sessions.");
|
|
375
|
+
}
|
|
255
376
|
// New-key scalar writes: each writer is individually atomic, so apply them in
|
|
256
377
|
// sequence after the transition. A failure here is a persistence failure (the
|
|
257
378
|
// writers' ok:false result or a throw from the underlying atomic write helper),
|
|
@@ -263,6 +384,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
263
384
|
if (wantsAgentsEnabled) scalarWrites.push({ field: "agentsEnabled", run: () => setAgentsEnabled(body.agentsEnabled as boolean | null) });
|
|
264
385
|
if (wantsMaxDepth) scalarWrites.push({ field: "agentsMaxDepth", run: () => setAgentsMaxDepth(body.agentsMaxDepth as number | null) });
|
|
265
386
|
if (wantsSubagentInstructions) scalarWrites.push({ field: "subagentDeveloperInstructions", run: () => setSubagentDeveloperInstructions(body.subagentDeveloperInstructions as string | null) });
|
|
387
|
+
if (wantsModeHintText) scalarWrites.push({ field: "multiAgentModeHintText", run: () => setMultiAgentModeHintText(body.multiAgentModeHintText as string | null) });
|
|
266
388
|
const landed: string[] = [];
|
|
267
389
|
for (const write of scalarWrites) {
|
|
268
390
|
try {
|
|
@@ -282,7 +404,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
282
404
|
if (getAgentsEnabled() === false && isMultiAgentV2Enabled()) {
|
|
283
405
|
warnings.push("agents.enabled = false has no effect while features.multi_agent_v2 is enabled; upstream keeps V2 active.");
|
|
284
406
|
}
|
|
285
|
-
await
|
|
407
|
+
const catalogRefresh = await convergeCodexCatalog();
|
|
286
408
|
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
409
|
const enabled = isMultiAgentV2Enabled();
|
|
288
410
|
return jsonResponse({
|
|
@@ -291,14 +413,71 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
291
413
|
agentsMaxThreadsConflict: enabled && hasAgentsMaxThreads(),
|
|
292
414
|
maxConcurrentThreadsPerSession: getLogicalMaxThreads(),
|
|
293
415
|
multiAgentMode: config.multiAgentMode ?? "default",
|
|
416
|
+
keepNativeChatGptOnV1: config.keepNativeChatGptOnV1 === true,
|
|
294
417
|
agentsEnabled: getAgentsEnabled(),
|
|
295
418
|
agentsMaxDepth: getAgentsMaxDepth(),
|
|
296
419
|
subagentDeveloperInstructions: getSubagentDeveloperInstructions(),
|
|
420
|
+
multiAgentModeHintText: getMultiAgentModeHintText(),
|
|
297
421
|
agentsMaxDepthAppliesWhenV2Disabled: !enabled,
|
|
298
422
|
warnings,
|
|
423
|
+
catalogRefresh,
|
|
299
424
|
});
|
|
300
425
|
}
|
|
301
426
|
|
|
427
|
+
// default_mode_request_user_input feature toggle (Codex Auth page). GET reads the
|
|
428
|
+
// flag from $CODEX_HOME/config.toml; PUT flips it via the official `codex features`
|
|
429
|
+
// CLI so the TOML edit stays upstream-owned and format-preserving.
|
|
430
|
+
if (url.pathname === "/api/codex-auth/features/default-mode-request-user-input" && req.method === "GET") {
|
|
431
|
+
const { isDefaultModeRequestUserInputEnabled, DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY } = await import("../../codex/features");
|
|
432
|
+
return jsonResponse({
|
|
433
|
+
enabled: isDefaultModeRequestUserInputEnabled(),
|
|
434
|
+
key: DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY,
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
if (url.pathname === "/api/codex-auth/features/default-mode-request-user-input" && req.method === "PUT") {
|
|
438
|
+
let parsedBody: unknown;
|
|
439
|
+
try {
|
|
440
|
+
parsedBody = await readManagementJsonBody(req);
|
|
441
|
+
} catch (error) {
|
|
442
|
+
rethrowManagementBodyTooLarge(error);
|
|
443
|
+
return jsonResponse({ error: "invalid JSON body" }, 400);
|
|
444
|
+
}
|
|
445
|
+
if (!parsedBody || typeof parsedBody !== "object" || Array.isArray(parsedBody)) {
|
|
446
|
+
return jsonResponse({ error: "body must be a JSON object" }, 400);
|
|
447
|
+
}
|
|
448
|
+
const body = parsedBody as { enabled?: unknown };
|
|
449
|
+
if (typeof body.enabled !== "boolean") return jsonResponse({ error: "body.enabled must be a boolean" }, 400);
|
|
450
|
+
const { isDefaultModeRequestUserInputEnabled, DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY } = await import("../../codex/features");
|
|
451
|
+
const before = isDefaultModeRequestUserInputEnabled();
|
|
452
|
+
let toggle = deps.toggleDefaultModeRequestUserInput;
|
|
453
|
+
if (!toggle) {
|
|
454
|
+
const { runCodexFeaturesCommand } = await import("../../cli/v2");
|
|
455
|
+
toggle = (enabled: boolean) => runCodexFeaturesCommand(enabled ? "enable" : "disable", DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY);
|
|
456
|
+
}
|
|
457
|
+
let toggleError: string | null = null;
|
|
458
|
+
try {
|
|
459
|
+
toggle(body.enabled);
|
|
460
|
+
} catch (error) {
|
|
461
|
+
const err = error as { stderr?: unknown; message?: string };
|
|
462
|
+
const raw = err.stderr;
|
|
463
|
+
const stderrText = typeof raw === "string"
|
|
464
|
+
? raw.trim()
|
|
465
|
+
: raw instanceof Uint8Array ? new TextDecoder().decode(raw).trim() : "";
|
|
466
|
+
toggleError = stderrText || (err.message ?? String(error));
|
|
467
|
+
}
|
|
468
|
+
const enabled = isDefaultModeRequestUserInputEnabled();
|
|
469
|
+
if (toggleError !== null || enabled !== body.enabled) {
|
|
470
|
+
const reason = toggleError
|
|
471
|
+
?? `postcondition failed - the installed Codex build may not know the ${DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY} flag yet`;
|
|
472
|
+
return jsonResponse({ error: `default_mode_request_user_input toggle failed: ${reason}` }, 502);
|
|
473
|
+
}
|
|
474
|
+
const warnings: string[] = [];
|
|
475
|
+
if (enabled !== before) {
|
|
476
|
+
warnings.push("Applies to new sessions; restart the Codex app or wait out its picker cache to see the change.");
|
|
477
|
+
}
|
|
478
|
+
return jsonResponse({ ok: true, enabled, changed: enabled !== before, warnings });
|
|
479
|
+
}
|
|
480
|
+
|
|
302
481
|
// Subagent prompt injection model: single native or routed model whose info is
|
|
303
482
|
// dynamically injected into the v1 proactive prompt, plus an optional reasoning
|
|
304
483
|
// effort the prompt tells the agent to pass to spawn_agent. GET returns the current
|
|
@@ -396,13 +575,13 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
396
575
|
|
|
397
576
|
config.multiAgentGuidanceEnabled = nextEnabled;
|
|
398
577
|
if (nextSyncCodexSubagentDefaults) config.syncCodexSubagentDefaults = true;
|
|
399
|
-
else
|
|
578
|
+
else deleteConfigTopLevelKey(config, "syncCodexSubagentDefaults");
|
|
400
579
|
if (nextModel) config.injectionModel = nextModel;
|
|
401
|
-
else
|
|
580
|
+
else deleteConfigTopLevelKey(config, "injectionModel");
|
|
402
581
|
if (nextEffort) config.injectionEffort = nextEffort;
|
|
403
|
-
else
|
|
582
|
+
else deleteConfigTopLevelKey(config, "injectionEffort");
|
|
404
583
|
if (nextPrompt) config.injectionPrompt = nextPrompt;
|
|
405
|
-
else
|
|
584
|
+
else deleteConfigTopLevelKey(config, "injectionPrompt");
|
|
406
585
|
|
|
407
586
|
saveConfigPreservingClaudeCode(config);
|
|
408
587
|
return jsonResponse({
|
|
@@ -433,7 +612,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
433
612
|
for (const key of ["effortCap", "subagentEffortCap"] as const) {
|
|
434
613
|
if (!(key in body)) continue;
|
|
435
614
|
const value = body[key];
|
|
436
|
-
if (value === null || value === "") {
|
|
615
|
+
if (value === null || value === "") { deleteConfigTopLevelKey(config, key); continue; }
|
|
437
616
|
if (typeof value !== "string" || !isCodexReasoningEffort(value)) {
|
|
438
617
|
return jsonResponse({ error: `unknown reasoning effort "${String(value)}"` }, 400);
|
|
439
618
|
}
|
|
@@ -456,15 +635,29 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
456
635
|
stored === catalogModelSlug(m) || slugEquals(stored, m.provider, m.id)
|
|
457
636
|
))
|
|
458
637
|
.map(catalogModelSlug))];
|
|
459
|
-
const
|
|
638
|
+
const chosen = config.subagentModels ?? [];
|
|
639
|
+
const selectable = [
|
|
460
640
|
...listCatalogNativeSlugs().filter(ns => !disabled.has(ns)),
|
|
461
641
|
...visibleRouted,
|
|
462
642
|
];
|
|
643
|
+
// A saved roster slot must stay representable even after its model is disabled
|
|
644
|
+
// elsewhere (Models page, provider allowlist, a provider row going away). The
|
|
645
|
+
// dashboard treats `available` as the set of rows it can render, so a chosen id
|
|
646
|
+
// missing from it disappears from the roster UI and the next Save — which PUTs
|
|
647
|
+
// exactly what the UI holds — silently truncates the persisted list. Losing a
|
|
648
|
+
// deliberate 5-model roster to an unrelated visibility toggle is data loss, not a
|
|
649
|
+
// filter. Same reasoning as `fetchGrokCandidateModels`, which deliberately lists a
|
|
650
|
+
// model the user already excluded so its switch remains reachable.
|
|
651
|
+
const selectableSet = new Set(selectable);
|
|
652
|
+
const available = [
|
|
653
|
+
...selectable,
|
|
654
|
+
...[...new Set(chosen)].filter(model => !selectableSet.has(model)),
|
|
655
|
+
];
|
|
463
656
|
// #857: let CLI/GUI show when a running Codex app-server keeps an older
|
|
464
657
|
// in-memory catalog than the one on disk.
|
|
465
658
|
const { collectCodexAppServerCatalogState } = await import("../../codex/app-server-processes");
|
|
466
659
|
const catalogState = collectCodexAppServerCatalogState();
|
|
467
|
-
return jsonResponse({ chosen
|
|
660
|
+
return jsonResponse({ chosen, available, catalogState });
|
|
468
661
|
}
|
|
469
662
|
if (url.pathname === "/api/subagent-models" && req.method === "PUT") {
|
|
470
663
|
let body: { models?: unknown };
|
|
@@ -473,10 +666,10 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
473
666
|
config.subagentModels = chosen;
|
|
474
667
|
const { saveConfigPreservingClaudeCode: save } = await import("../../config");
|
|
475
668
|
save(config);
|
|
476
|
-
await
|
|
669
|
+
const catalogRefresh = await convergeCodexCatalog();
|
|
477
670
|
await syncClaudeAgentDefsBestEffort();
|
|
478
671
|
await autoApplyDesktopBestEffort();
|
|
479
|
-
return jsonResponse({ ok: true, applied: chosen });
|
|
672
|
+
return jsonResponse({ ok: true, applied: chosen, catalogRefresh });
|
|
480
673
|
}
|
|
481
674
|
|
|
482
675
|
// Priority-ordered subagent model fallback chain for quota-aware spawn routing.
|
|
@@ -538,9 +731,9 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
538
731
|
}
|
|
539
732
|
}
|
|
540
733
|
if (nextModels !== undefined) config.subagentModelFallback = nextModels;
|
|
541
|
-
else
|
|
734
|
+
else deleteConfigTopLevelKey(config, "subagentModelFallback");
|
|
542
735
|
if (nextPollMs !== undefined) config.subagentModelFallbackPollMs = nextPollMs;
|
|
543
|
-
else
|
|
736
|
+
else deleteConfigTopLevelKey(config, "subagentModelFallbackPollMs");
|
|
544
737
|
saveConfigPreservingClaudeCode(config);
|
|
545
738
|
return jsonResponse({
|
|
546
739
|
ok: true,
|
|
@@ -581,7 +774,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
581
774
|
// cannot grow config.json without bound.
|
|
582
775
|
const excluded = [...new Set(raw as string[])].sort();
|
|
583
776
|
if (excluded.length > 2000) return jsonResponse({ error: "excluded list is too large" }, 400);
|
|
584
|
-
if (excluded.length === 0)
|
|
777
|
+
if (excluded.length === 0) deleteConfigTopLevelKey(config, "grokExcludedModels");
|
|
585
778
|
else config.grokExcludedModels = excluded;
|
|
586
779
|
saveConfigPreservingClaudeCode(config);
|
|
587
780
|
return jsonResponse({ ok: true, excluded });
|
|
@@ -783,21 +976,19 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
783
976
|
// #859: the CLI delegates here so the registry is built in the serving
|
|
784
977
|
// process. Accept an optional mode; default stays static for back-compat.
|
|
785
978
|
let mode: "static" | "hybrid" | "discovery" = "static";
|
|
786
|
-
const rawBody = await req.text();
|
|
787
979
|
let parsed: unknown;
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
}
|
|
980
|
+
try {
|
|
981
|
+
parsed = await readOptionalManagementJsonBody(req);
|
|
982
|
+
} catch (error) {
|
|
983
|
+
rethrowManagementBodyTooLarge(error);
|
|
984
|
+
return jsonResponse({ error: "invalid JSON body" }, 400);
|
|
985
|
+
}
|
|
986
|
+
const requested = (parsed as { mode?: unknown } | null)?.mode;
|
|
987
|
+
if (requested !== undefined) {
|
|
988
|
+
if (requested === "static" || requested === "hybrid" || requested === "discovery") {
|
|
989
|
+
mode = requested;
|
|
990
|
+
} else {
|
|
991
|
+
return jsonResponse({ error: "mode must be static, hybrid, or discovery" }, 400);
|
|
801
992
|
}
|
|
802
993
|
}
|
|
803
994
|
// #859: a delegated CLI apply carries the profile it just saved — the
|
|
@@ -813,9 +1004,26 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
813
1004
|
return jsonResponse({ error: error instanceof Error ? error.message : String(error) }, 400);
|
|
814
1005
|
}
|
|
815
1006
|
}
|
|
1007
|
+
const { setIntegrationEnabled, claudeDesktopIntegrationEnabled } = await import("../../codex/desired-state");
|
|
1008
|
+
const desired = setIntegrationEnabled("claude-desktop", true);
|
|
1009
|
+
if (!desired.ok) return jsonResponse({ error: desired.message }, desired.retryable ? 409 : 500);
|
|
1010
|
+
// Disk now says ON; the reused server snapshot must agree, or the native
|
|
1011
|
+
// GET reports OFF and a later whole-snapshot save undoes this transition.
|
|
1012
|
+
mirrorDesiredEnabledOntoSnapshot(config, "claude-desktop", true);
|
|
816
1013
|
const state = await buildClaudeDesktopState(config, profileOverride);
|
|
817
|
-
|
|
818
|
-
|
|
1014
|
+
// `setIntegrationEnabled` above wrote desired ON to DISK; it does not touch
|
|
1015
|
+
// this long-lived server snapshot. Saving the snapshot wholesale would carry
|
|
1016
|
+
// its stale `clientIntegrations` back over that write and turn the enable
|
|
1017
|
+
// action into an immediate self-cancelling OFF — the guard below would then
|
|
1018
|
+
// refuse the apply it was asked to perform. Persist ONLY the profile field.
|
|
1019
|
+
const profileSaved = persistDesktopProfileField(config, state.profile);
|
|
1020
|
+
if (!profileSaved.ok) {
|
|
1021
|
+
return jsonResponse({
|
|
1022
|
+
error: `Claude Desktop profile could not be saved (${profileSaved.reason}); nothing was applied.`,
|
|
1023
|
+
saved: false,
|
|
1024
|
+
applied: false,
|
|
1025
|
+
}, profileSaved.reason === "conflict" ? 409 : 500);
|
|
1026
|
+
}
|
|
819
1027
|
const { writeDesktop3pConfig } = await import("../../claude/desktop-3p");
|
|
820
1028
|
const { desktopVisibleNativeSlugs } = await import("../../codex/catalog");
|
|
821
1029
|
const routed = state.models
|
|
@@ -824,22 +1032,52 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
824
1032
|
const slash = model.route.indexOf("/");
|
|
825
1033
|
return { provider: model.route.slice(0, slash), id: model.route.slice(slash + 1), contextWindow: model.contextWindow };
|
|
826
1034
|
});
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
1035
|
+
// State construction can await catalog work; never write from the stale
|
|
1036
|
+
// config captured before that await if another request turned Desktop off.
|
|
1037
|
+
const latest = loadConfig();
|
|
1038
|
+
if (!claudeDesktopIntegrationEnabled(latest)) {
|
|
1039
|
+
return jsonResponse({
|
|
1040
|
+
error: "Claude Desktop apply was cancelled because the desired state changed to off.",
|
|
1041
|
+
code: "claude_desktop_apply_skipped",
|
|
1042
|
+
reason: "desired_state_changed",
|
|
1043
|
+
desiredEnabled: false,
|
|
1044
|
+
saved: true,
|
|
1045
|
+
applied: false,
|
|
1046
|
+
}, 409);
|
|
1047
|
+
}
|
|
1048
|
+
const result = (deps.writeDesktop3pConfig ?? writeDesktop3pConfig)(
|
|
1049
|
+
Number(url.port) || latest.port,
|
|
1050
|
+
[...desktopVisibleNativeSlugs(latest)],
|
|
830
1051
|
routed,
|
|
831
|
-
|
|
1052
|
+
latest.apiKeys?.[0]?.key,
|
|
832
1053
|
mode,
|
|
833
1054
|
state.profile,
|
|
1055
|
+
nativeContextLimits(latest),
|
|
834
1056
|
);
|
|
835
1057
|
if (!result.written) return jsonResponse({ error: result.reason ?? "Claude Desktop apply failed", saved: true, path: result.path }, 500);
|
|
836
1058
|
// Persist applied fingerprint + timestamp so GUI can show saved-vs-applied state.
|
|
837
1059
|
if (result.fingerprint) {
|
|
838
|
-
|
|
839
|
-
|
|
1060
|
+
// The Desktop write already landed, so a failed bookkeeping save is not
|
|
1061
|
+
// an apply failure: report the miss instead of claiming a clean apply.
|
|
1062
|
+
const marked = persistDesktopProfileField(config, {
|
|
1063
|
+
...state.profile,
|
|
1064
|
+
appliedFingerprint: result.fingerprint,
|
|
1065
|
+
appliedAt: new Date().toISOString(),
|
|
1066
|
+
});
|
|
1067
|
+
if (!marked.ok) {
|
|
1068
|
+
return jsonResponse({
|
|
1069
|
+
ok: true,
|
|
1070
|
+
applied: true,
|
|
1071
|
+
saved: false,
|
|
1072
|
+
path: result.path,
|
|
1073
|
+
fingerprint: result.fingerprint,
|
|
1074
|
+
warning: `Claude Desktop was applied, but the applied marker was not saved (${marked.reason}).`,
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
840
1077
|
}
|
|
841
1078
|
return jsonResponse({ ok: true, saved: true, applied: true, path: result.path, fingerprint: result.fingerprint });
|
|
842
1079
|
} catch (error) {
|
|
1080
|
+
rethrowManagementBodyTooLarge(error);
|
|
843
1081
|
return jsonResponse({ error: error instanceof Error ? error.message : String(error) }, 400);
|
|
844
1082
|
}
|
|
845
1083
|
}
|
|
@@ -847,47 +1085,35 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
847
1085
|
// Desktop applied-state + health status.
|
|
848
1086
|
if (url.pathname === "/api/claude-desktop/status" && req.method === "GET") {
|
|
849
1087
|
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;
|
|
1088
|
+
const { claudeDesktopIntegrationEnabled } = await import("../../codex/desired-state");
|
|
1089
|
+
const { inspectDesktop3pConfigLibrary } = await import("../../claude/desktop-3p");
|
|
1090
|
+
const persisted = loadConfig();
|
|
1091
|
+
const savedFingerprint = persisted.claudeCode?.desktopProfile?.appliedFingerprint ?? null;
|
|
1092
|
+
const observed = inspectDesktop3pConfigLibrary({ appliedFingerprint: savedFingerprint });
|
|
1093
|
+
const desiredEnabled = claudeDesktopIntegrationEnabled(persisted);
|
|
1094
|
+
const applied = observed.kind === "gateway_ours" || observed.kind === "gateway_drifted";
|
|
1095
|
+
// "Needs update" is only meaningful while the integration is wanted. When the
|
|
1096
|
+
// durable switch is OFF, a leftover drifted profile is residue to clear — not
|
|
1097
|
+
// a stale apply the operator should refresh.
|
|
1098
|
+
const stale = desiredEnabled && observed.kind === "gateway_drifted";
|
|
881
1099
|
const { getDesktopHealth } = await import("../../claude/desktop-health");
|
|
882
1100
|
const health = getDesktopHealth();
|
|
883
1101
|
return jsonResponse({
|
|
884
|
-
|
|
885
|
-
|
|
1102
|
+
desiredEnabled,
|
|
1103
|
+
installed: observed.kind !== "not_installed",
|
|
1104
|
+
observedKind: observed.kind,
|
|
1105
|
+
applied,
|
|
1106
|
+
appliedAt: persisted.claudeCode?.desktopProfile?.appliedAt ?? null,
|
|
886
1107
|
savedFingerprint,
|
|
887
|
-
onDiskFingerprint,
|
|
888
|
-
configPath,
|
|
1108
|
+
onDiskFingerprint: observed.fingerprint ?? null,
|
|
1109
|
+
configPath: observed.selectedProfilePath,
|
|
889
1110
|
stale,
|
|
890
|
-
|
|
1111
|
+
// Tri-state by ID match, independent of profile health: null =
|
|
1112
|
+
// undeterminable (no/unreadable metadata or no appliedId); a readable
|
|
1113
|
+
// appliedId with no owned entry is a KNOWN false. Predates the inspector.
|
|
1114
|
+
activeProfile: observed.ownedProfileActive,
|
|
1115
|
+
drift: desiredEnabled ? !applied || stale : applied || observed.kind === "unsafe",
|
|
1116
|
+
driftReason: desiredEnabled ? (!applied ? "desired_on_not_current" : stale ? "profile_drift" : null) : (applied ? "desired_off_gateway_selected" : null),
|
|
891
1117
|
health,
|
|
892
1118
|
});
|
|
893
1119
|
} catch (error) {
|
|
@@ -921,7 +1147,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
921
1147
|
if (isDisabled(m.provider, m.id)) continue;
|
|
922
1148
|
aliases.push({ id: claudeCodeAlias(m.provider, m.id), display_name: `${m.id} (${m.provider})` });
|
|
923
1149
|
}
|
|
924
|
-
const contextWindows = buildClaudeContextWindows([...visibleNativeSlugs(config)], models);
|
|
1150
|
+
const contextWindows = buildClaudeContextWindows([...visibleNativeSlugs(config)], models, nativeContextLimits(config));
|
|
925
1151
|
const webSearchOverride = config.claudeCode?.webSearchSidecar;
|
|
926
1152
|
const visionOverride = config.claudeCode?.visionSidecar;
|
|
927
1153
|
// Auto is a RESOLUTION, recomputed per request — never stored state. Detection is
|
|
@@ -950,8 +1176,10 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
950
1176
|
smallFastModel: config.claudeCode?.smallFastModel ?? "",
|
|
951
1177
|
tierModels: config.claudeCode?.tierModels ?? {},
|
|
952
1178
|
modelMap: config.claudeCode?.modelMap ?? {},
|
|
1179
|
+
classifierModel: config.claudeCode?.classifierModel ?? "",
|
|
1180
|
+
classifierFallbacks: config.claudeCode?.classifierFallbacks ?? [],
|
|
953
1181
|
systemEnv: config.claudeCode?.systemEnv === true,
|
|
954
|
-
autoConnectSupported: process.platform === "darwin",
|
|
1182
|
+
autoConnectSupported: (ctx.deps.platform ?? process.platform) === "darwin",
|
|
955
1183
|
maxContextTokens: config.claudeCode?.maxContextTokens ?? null,
|
|
956
1184
|
alwaysEnableEffort: config.claudeCode?.alwaysEnableEffort === true,
|
|
957
1185
|
autoContext: config.claudeCode?.autoContext !== false,
|
|
@@ -987,18 +1215,83 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
987
1215
|
return prototype === Object.prototype || prototype === null;
|
|
988
1216
|
};
|
|
989
1217
|
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 };
|
|
1218
|
+
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
1219
|
for (const field of ["webSearchSidecar", "visionSidecar"] as const) {
|
|
992
1220
|
const section = body[field];
|
|
993
1221
|
if (section === undefined || section === null) continue;
|
|
994
1222
|
if (!isPlainObject(section)) return jsonResponse({ error: `${field} must be an object or null` }, 400);
|
|
1223
|
+
// Both overrides now speak their full unions (roadmap 060 web, 170
|
|
1224
|
+
// vision revised). Vision's third arm is "routed" (loopback through the
|
|
1225
|
+
// proxy's own router), never exa: exa is not an LLM, and accepting an
|
|
1226
|
+
// unknown literal would persist a backend the vision resolver reads as
|
|
1227
|
+
// unset (review F1's failure mode).
|
|
1228
|
+
const allowedBackends = field === "webSearchSidecar"
|
|
1229
|
+
? ["openai", "anthropic", "xai", "gemini", "exa"]
|
|
1230
|
+
: ["openai", "anthropic", "routed"];
|
|
995
1231
|
if (section.backend !== undefined && section.backend !== null
|
|
996
|
-
&&
|
|
997
|
-
return jsonResponse({ error: `${field}.backend must be
|
|
1232
|
+
&& !allowedBackends.includes(section.backend as string)) {
|
|
1233
|
+
return jsonResponse({ error: `${field}.backend must be ${allowedBackends.join(", ")}, or null` }, 400);
|
|
998
1234
|
}
|
|
999
1235
|
if (section.model !== undefined && typeof section.model !== "string") {
|
|
1000
1236
|
return jsonResponse({ error: `${field}.model must be a string` }, 400);
|
|
1001
1237
|
}
|
|
1238
|
+
// Vision override only: reject a model we can prove is blind. Unknown ids stay
|
|
1239
|
+
// allowed. Shares one policy module with /api/sidecar-settings so the two
|
|
1240
|
+
// gates cannot drift.
|
|
1241
|
+
if (field === "visionSidecar" && typeof section.model === "string" && section.model !== "") {
|
|
1242
|
+
const requested = section.model;
|
|
1243
|
+
const candidates = await visionCandidateRows(config);
|
|
1244
|
+
const hint = section.backend === "anthropic" || section.backend === "openai"
|
|
1245
|
+
|| section.backend === "routed"
|
|
1246
|
+
? section.backend
|
|
1247
|
+
: config.claudeCode?.visionSidecar?.backend;
|
|
1248
|
+
// Same coherence rule as /api/sidecar-settings (roadmap 170 r2).
|
|
1249
|
+
const effectiveBackend = hint ?? "openai";
|
|
1250
|
+
const namespaced = requested.includes("/");
|
|
1251
|
+
if (namespaced && effectiveBackend !== "routed") {
|
|
1252
|
+
return jsonResponse({ error: `visionSidecar.model "${requested}" is provider-namespaced; it requires backend "routed"` }, 400);
|
|
1253
|
+
}
|
|
1254
|
+
if (!namespaced && effectiveBackend === "routed") {
|
|
1255
|
+
return jsonResponse({ error: `visionSidecar.backend "routed" requires a provider-namespaced model ("provider/model"); got "${requested}"` }, 400);
|
|
1256
|
+
}
|
|
1257
|
+
if (visionDescriberIsProvablyBlind(config, requested, candidates, hint)) {
|
|
1258
|
+
return jsonResponse(visionDescriberRejection("visionSidecar.model", requested, config, candidates), 400);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
// Web-search override: membership gate (#2188). The executor set is closed,
|
|
1262
|
+
// so an id outside (runnable candidates ∪ auth slots) can never run. Same
|
|
1263
|
+
// module as /api/sidecar-settings — a gate on one route and a stale copy on
|
|
1264
|
+
// the other is no gate at all.
|
|
1265
|
+
if (field === "webSearchSidecar"
|
|
1266
|
+
&& (section.model !== undefined || section.backend !== undefined)) {
|
|
1267
|
+
const stored = config.claudeCode?.webSearchSidecar;
|
|
1268
|
+
// Validate against the SUBMITTED backend across the whole union, not
|
|
1269
|
+
// just openai/anthropic (#2457). allowedBackends above already refused
|
|
1270
|
+
// unknown literals; Array.includes does not narrow, hence the cast.
|
|
1271
|
+
// null keeps its own meaning here — drop the override and inherit the
|
|
1272
|
+
// global backend — which is deliberately NOT the sidecar-settings rule.
|
|
1273
|
+
const submittedBackend = section.backend;
|
|
1274
|
+
const effectiveBackend = typeof submittedBackend === "string"
|
|
1275
|
+
&& allowedBackends.includes(submittedBackend)
|
|
1276
|
+
? submittedBackend as WebSearchBackend
|
|
1277
|
+
: submittedBackend === null
|
|
1278
|
+
? config.webSearchSidecar?.backend ?? "openai"
|
|
1279
|
+
: stored?.backend ?? config.webSearchSidecar?.backend ?? "openai";
|
|
1280
|
+
const effectiveModel = section.model === ""
|
|
1281
|
+
? config.webSearchSidecar?.model
|
|
1282
|
+
: typeof section.model === "string"
|
|
1283
|
+
? section.model
|
|
1284
|
+
: stored?.model ?? config.webSearchSidecar?.model;
|
|
1285
|
+
const candidates = await webSearchCandidateRows(config);
|
|
1286
|
+
if (effectiveModel && webSearchModelIsRejected(effectiveBackend, effectiveModel, candidates)) {
|
|
1287
|
+
return jsonResponse(webSearchModelRejection(
|
|
1288
|
+
"webSearchSidecar.model",
|
|
1289
|
+
effectiveBackend,
|
|
1290
|
+
effectiveModel,
|
|
1291
|
+
candidates,
|
|
1292
|
+
), 400);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1002
1295
|
}
|
|
1003
1296
|
const next = { ...(config.claudeCode ?? {}) };
|
|
1004
1297
|
for (const field of ["webSearchSidecar", "visionSidecar"] as const) {
|
|
@@ -1008,13 +1301,17 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1008
1301
|
delete next[field];
|
|
1009
1302
|
continue;
|
|
1010
1303
|
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1304
|
+
// The per-field validation above guarantees vision only ever carries the two-member
|
|
1305
|
+
// union; the cast is the loop's shared-shape compromise, not a wider write path.
|
|
1306
|
+
const requested = section as { backend?: "openai" | "anthropic" | "xai" | "gemini" | "exa" | null; model?: string };
|
|
1307
|
+
const override = { ...next[field] } as NonNullable<OcxClaudeCodeConfig[typeof field]>;
|
|
1013
1308
|
if (requested.backend === null) delete override.backend;
|
|
1014
|
-
else if (requested.backend !== undefined) override.backend = requested.backend;
|
|
1309
|
+
else if (requested.backend !== undefined) override.backend = requested.backend as never;
|
|
1015
1310
|
if (requested.model === "") delete override.model;
|
|
1016
1311
|
else if (requested.model !== undefined) override.model = requested.model;
|
|
1017
|
-
|
|
1312
|
+
// Indexed write across the field union collapses to an intersection; runtime
|
|
1313
|
+
// validation above already guarantees the per-field shape.
|
|
1314
|
+
if (Object.keys(override).length > 0) next[field] = override as never;
|
|
1018
1315
|
else delete next[field];
|
|
1019
1316
|
}
|
|
1020
1317
|
if (body.enabled !== undefined) {
|
|
@@ -1107,19 +1404,38 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1107
1404
|
else delete next.tierModels;
|
|
1108
1405
|
}
|
|
1109
1406
|
}
|
|
1407
|
+
let nextFastMode = config.fastMode;
|
|
1110
1408
|
if (body.fastMode !== undefined) {
|
|
1111
1409
|
if (body.fastMode !== true && body.fastMode !== false && body.fastMode !== null) {
|
|
1112
1410
|
return jsonResponse({ error: "fastMode must be true, false, or null" }, 400);
|
|
1113
1411
|
}
|
|
1114
|
-
|
|
1412
|
+
nextFastMode = body.fastMode === null ? undefined : body.fastMode;
|
|
1115
1413
|
}
|
|
1116
|
-
for (const field of ["model", "smallFastModel"] as const) {
|
|
1414
|
+
for (const field of ["model", "smallFastModel", "classifierModel"] as const) {
|
|
1117
1415
|
const value = body[field];
|
|
1118
1416
|
if (value === undefined) continue;
|
|
1119
1417
|
if (typeof value !== "string") return jsonResponse({ error: `${field} must be a string` }, 400);
|
|
1120
1418
|
if (value.trim() === "") delete next[field];
|
|
1121
1419
|
else next[field] = value.trim();
|
|
1122
1420
|
}
|
|
1421
|
+
if (body.classifierFallbacks !== undefined) {
|
|
1422
|
+
if (body.classifierFallbacks === null) {
|
|
1423
|
+
delete next.classifierFallbacks;
|
|
1424
|
+
} else {
|
|
1425
|
+
if (!Array.isArray(body.classifierFallbacks)) {
|
|
1426
|
+
return jsonResponse({ error: "classifierFallbacks must be an array of strings, or null" }, 400);
|
|
1427
|
+
}
|
|
1428
|
+
const list: string[] = [];
|
|
1429
|
+
for (const entry of body.classifierFallbacks) {
|
|
1430
|
+
if (typeof entry !== "string" || entry.trim() === "") {
|
|
1431
|
+
return jsonResponse({ error: "classifierFallbacks entries must be non-empty strings" }, 400);
|
|
1432
|
+
}
|
|
1433
|
+
list.push(entry.trim());
|
|
1434
|
+
}
|
|
1435
|
+
if (list.length > 0) next.classifierFallbacks = list;
|
|
1436
|
+
else delete next.classifierFallbacks;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1123
1439
|
if (body.modelMap !== undefined) {
|
|
1124
1440
|
if (body.modelMap === null) {
|
|
1125
1441
|
delete next.modelMap;
|
|
@@ -1138,6 +1454,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1138
1454
|
else delete next.modelMap;
|
|
1139
1455
|
}
|
|
1140
1456
|
}
|
|
1457
|
+
if (body.fastMode !== undefined) config.fastMode = nextFastMode;
|
|
1141
1458
|
config.claudeCode = next;
|
|
1142
1459
|
// Stamp the migration sentinel on EVERY persist of this block. The migration reads
|
|
1143
1460
|
// "a claudeCode block with no authMode" as a pre-upgrade subscriber and pins it to
|