@bitkyc08/opencodex 2.84.0 → 2.87.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/README.md +0 -4
- package/bin/ocx.mjs +61 -3
- package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-C9TGtwad.js → Tray-DCd-A1az.js} +1 -1
- package/gui/dist/assets/index-CfYDMj34.js +94 -0
- package/gui/dist/assets/index-DPeB9HpV.css +1 -0
- package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -2
- package/src/adapters/anthropic/beta-allowlist.ts +91 -11
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/client-identity.ts +1 -1
- package/src/adapters/anthropic/passthrough.ts +16 -3
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic.ts +108 -21
- package/src/adapters/azure.ts +19 -1
- package/src/adapters/base.ts +2 -0
- package/src/adapters/claude-cli/adapter.ts +111 -23
- package/src/adapters/claude-cli/profiles.ts +2 -1
- package/src/adapters/claude-cli/stable-replay.ts +108 -0
- package/src/adapters/claude-cli/usage-admission.ts +230 -0
- package/src/adapters/client-fingerprint.ts +24 -0
- package/src/adapters/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +40 -11
- package/src/adapters/cursor/arg-codec.ts +17 -5
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/cursor/freeform-args.ts +75 -0
- package/src/adapters/cursor/protobuf-events.ts +36 -23
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google-antigravity-wire.ts +22 -1
- package/src/adapters/google.ts +37 -10
- package/src/adapters/openai-chat/messages.ts +4 -3
- package/src/adapters/openai-chat/passthrough.ts +8 -2
- package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
- package/src/adapters/openai-chat/response-events.ts +59 -1
- package/src/adapters/openai-chat.ts +8 -5
- package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
- package/src/adapters/openai-responses/passthrough.ts +57 -125
- package/src/adapters/openai-responses/request-strips.ts +25 -3
- package/src/adapters/openai-responses/transport.ts +81 -0
- package/src/adapters/qoder/adapter.ts +44 -3
- package/src/adapters/registry.ts +5 -0
- package/src/adapters/zed.ts +26 -1
- package/src/advisor/activity.ts +55 -0
- package/src/advisor/budget.ts +62 -0
- package/src/advisor/consult.ts +223 -0
- package/src/advisor/context.ts +238 -0
- package/src/advisor/disclosure.ts +20 -0
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +353 -0
- package/src/advisor/settings.ts +144 -0
- package/src/advisor/state.ts +369 -0
- package/src/advisor/synthetic-tool.ts +33 -0
- package/src/bridge/errors.ts +24 -1
- package/src/bridge/internal.ts +3 -1
- package/src/bridge/response-json.ts +18 -3
- package/src/bridge/sse.ts +58 -8
- package/src/bridge/terminal-refusal.ts +59 -0
- package/src/chat/inbound.ts +30 -6
- package/src/chat/outbound.ts +5 -0
- package/src/claude/desktop-3p-library.ts +72 -7
- package/src/claude/desktop-3p.ts +75 -19
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker-profile.ts +11 -4
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-profile.ts +10 -1
- package/src/claude/desktop-remote-store-io.ts +5 -3
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +11 -0
- package/src/claude/inbound.ts +154 -50
- package/src/claude/intercept/local-ca.ts +8 -1
- package/src/claude/intercept/picker-ca-startup.ts +2 -1
- package/src/claude/intercept/picker-runtime.ts +6 -5
- package/src/claude/intercept/picker-trust-windows.ts +338 -0
- package/src/claude/intercept/picker-trust.ts +19 -0
- package/src/claude/intercept/port-policy.ts +30 -0
- package/src/claude/intercept/runtime.ts +97 -94
- package/src/claude/model-default-settings.ts +154 -0
- package/src/claude/model-info.ts +7 -7
- package/src/claude/outbound.ts +51 -19
- package/src/cli/account-api.ts +1 -0
- package/src/cli/account-policy-dto.ts +6 -0
- package/src/cli/account-policy.ts +34 -5
- package/src/cli/account-preservation.ts +44 -0
- package/src/cli/account.ts +8 -2
- package/src/cli/advisor.ts +165 -0
- package/src/cli/auxiliary-listener-preflight.ts +37 -0
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +23 -2
- package/src/cli/capabilities-base.ts +39 -11
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- package/src/cli/chatgpt-command.ts +35 -14
- package/src/cli/claude.ts +46 -6
- package/src/cli/codex-cli-update.ts +151 -41
- package/src/cli/codex-provider.ts +44 -0
- package/src/cli/combo-input.ts +10 -1
- package/src/cli/combo-result.ts +1 -1
- package/src/cli/combo.ts +2 -1
- package/src/cli/cross-home-owner.ts +17 -14
- package/src/cli/dispatch.ts +30 -5
- package/src/cli/doctor.ts +15 -1
- package/src/cli/ensure-desktop-cold-start.ts +382 -0
- package/src/cli/export-command.ts +1 -1
- package/src/cli/help.ts +4 -2
- package/src/cli/index.ts +14 -5
- package/src/cli/integration-plan-dto.ts +3 -0
- package/src/cli/log-history.ts +172 -0
- package/src/cli/models-runtime-subcommands.ts +1 -0
- package/src/cli/models-runtime.ts +72 -8
- package/src/cli/observe-stream.ts +2 -0
- package/src/cli/observe.ts +16 -4
- package/src/cli/provider.ts +27 -5
- package/src/cli/registry.ts +41 -9
- package/src/cli/restart-scope.ts +3 -1
- package/src/cli/runtime-api.ts +5 -3
- package/src/cli/system-command.ts +40 -8
- package/src/cli/uninstall-client-state.ts +32 -0
- package/src/cli/usage-compare.ts +309 -0
- package/src/cli/usage-model-search.ts +4 -4
- package/src/cli/usage-report.ts +2 -2
- package/src/cli/v2.ts +1 -1
- package/src/client/machine-listener.ts +1 -3
- package/src/client/runtime.ts +1 -0
- package/src/clients/client-path.ts +51 -0
- package/src/clients/config-export/contracts.ts +3 -1
- package/src/clients/config-export/model-metadata.ts +3 -2
- package/src/clients/config-export/qoder.ts +93 -0
- package/src/clients/config-export.ts +35 -48
- package/src/clients/gentle-shell-path.ts +87 -0
- package/src/codex/account-auto-switch.ts +2 -0
- package/src/codex/account-identity.ts +29 -0
- package/src/codex/account-label.ts +1 -1
- package/src/codex/account-lifecycle.ts +21 -5
- package/src/codex/account-preservation.ts +28 -0
- package/src/codex/account-use-remaining.ts +23 -0
- package/src/codex/app-server-processes.ts +97 -7
- package/src/codex/app-server-restart-service.ts +55 -4
- package/src/codex/auth-api/account-list.ts +4 -0
- package/src/codex/auth-api/account-pause-group.ts +6 -50
- package/src/codex/auth-api/account-preservation.ts +57 -0
- package/src/codex/auth-api/pool-mode-gate.ts +5 -2
- package/src/codex/auth-api/routes.ts +8 -0
- package/src/codex/auth-api/use-remaining-route.ts +36 -0
- package/src/codex/auth-api.ts +0 -1
- package/src/codex/auth-context.ts +81 -7
- package/src/codex/autostart-health.ts +4 -0
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +19 -2
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +50 -0
- package/src/codex/catalog/effort.ts +4 -1
- package/src/codex/catalog/gather-capture.ts +2 -0
- package/src/codex/catalog/metadata.ts +11 -9
- package/src/codex/catalog/model-hints.ts +42 -19
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/native-ultrafast.ts +106 -0
- package/src/codex/catalog/parsing.ts +35 -86
- package/src/codex/catalog/provider-models.ts +40 -34
- package/src/codex/catalog/retained-sync.ts +2 -0
- package/src/codex/catalog/routed-gather.ts +33 -16
- package/src/codex/catalog/vendor-context.ts +82 -0
- package/src/codex/cli-install-provenance.ts +105 -17
- package/src/codex/cli-update-plan.ts +545 -0
- package/src/codex/config-write-lock.ts +63 -4
- package/src/codex/convergence.ts +2 -0
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app/types.ts +2 -0
- package/src/codex/desktop-app/windows.ts +29 -16
- package/src/codex/desktop-app-restart.ts +25 -2
- package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
- package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
- package/src/codex/desktop-compatibility/connection-store.ts +87 -0
- package/src/codex/desktop-compatibility/installed-build.ts +73 -0
- package/src/codex/desktop-compatibility/json-body.ts +29 -0
- package/src/codex/desktop-compatibility/native-identity.ts +62 -0
- package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
- package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
- package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
- package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
- package/src/codex/desktop-compatibility/runtime.ts +182 -0
- package/src/codex/desktop-compatibility/service.ts +30 -0
- package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
- package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
- package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
- package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
- package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
- package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
- package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
- package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
- package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
- package/src/codex/desktop-compatibility/windows-key-protection.ts +165 -0
- package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
- package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
- package/src/codex/features.ts +85 -45
- package/src/codex/history-provider.ts +1 -1
- package/src/codex/inject/config-write-section.ts +4 -1
- package/src/codex/inject/history-failure.ts +27 -0
- package/src/codex/inject/remove.ts +18 -6
- package/src/codex/inject/restore.ts +24 -2
- package/src/codex/inject/routing-classify.ts +4 -3
- package/src/codex/inject.ts +18 -27
- package/src/codex/journal.ts +16 -1
- package/src/codex/main-account-hard-lock.ts +30 -1
- package/src/codex/main-account.ts +99 -15
- package/src/codex/native-main-refresh-mode.ts +217 -0
- package/src/codex/native-profile-startup.ts +1 -1
- package/src/codex/ocx-compaction-history.ts +12 -10
- package/src/codex/paths.ts +86 -1
- package/src/codex/provider-routing.ts +174 -0
- package/src/codex/refresh.ts +2 -2
- package/src/codex/routing/health-store.ts +2 -2
- package/src/codex/routing/pool-recovery.ts +79 -0
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- package/src/codex/shim-diagnostics.ts +6 -0
- package/src/codex/shim-overlay.ts +7 -2
- package/src/codex/signed-in-identity.ts +43 -0
- package/src/codex/subagent-model-fallback.ts +1 -1
- package/src/codex/sync.ts +6 -1
- package/src/codex/user-identity.ts +16 -5
- package/src/codex/windows-local-appdata.ts +84 -0
- package/src/combos/failover.ts +6 -1
- package/src/combos/index.ts +1 -0
- package/src/combos/resolve.ts +46 -10
- package/src/combos/types.ts +15 -0
- package/src/config/diagnostics.ts +35 -7
- package/src/config/live-reconcile.ts +51 -2
- package/src/config/load-degrade.ts +23 -1
- package/src/config/opencode-wire-upgrade.ts +54 -0
- package/src/config/opencode-zen-upgrade.ts +52 -0
- package/src/config/owner-registry.ts +22 -17
- package/src/config/paths.ts +10 -3
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +39 -0
- package/src/config/schema/desktop-compatibility.ts +9 -0
- package/src/config/schema/leaf-validators.ts +39 -9
- package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
- package/src/config/schema/native-reasoning-retention.ts +26 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +962 -350
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/sync.ts +1 -1
- package/src/images/loop.ts +46 -8
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/config-io.ts +20 -2
- package/src/integrations/journal.ts +15 -1
- package/src/integrations/mutation-plan.ts +2 -0
- package/src/integrations/registry.ts +15 -0
- package/src/integrations/store.ts +3 -0
- package/src/integrations/writer.ts +10 -1
- package/src/lib/advisor-activation.ts +16 -0
- package/src/lib/background-work.ts +65 -0
- package/src/lib/bounded-file-read.ts +58 -0
- package/src/lib/bun-path-runtime.d.mts +10 -1
- package/src/lib/bun-path-runtime.mjs +62 -3
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-proxy-route.ts +30 -0
- package/src/lib/desktop-upstream-tunnel.ts +72 -0
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +26 -3
- package/src/lib/gcp-adc.ts +96 -9
- package/src/lib/gcp-credential-input.ts +12 -0
- package/src/lib/lab-live-route-production.ts +5 -0
- package/src/lib/local-internal-call-capability.ts +55 -0
- package/src/lib/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/package-tree-integrity.ts +2 -2
- package/src/lib/redact.ts +2 -2
- package/src/lib/socks5-fetch.ts +48 -71
- package/src/lib/socks5-handshake.ts +112 -0
- package/src/lib/translator-budget.ts +7 -1
- package/src/oauth/account-quota-rank.ts +69 -2
- package/src/oauth/anthropic-account-refusal.ts +20 -2
- package/src/oauth/anthropic-oauth-definitions.ts +3 -1
- package/src/oauth/anthropic-routing.ts +1 -21
- package/src/oauth/anthropic.ts +38 -7
- package/src/oauth/callback-server.ts +27 -1
- package/src/oauth/chatgpt-device.ts +1 -1
- package/src/oauth/chatgpt-shared.ts +83 -0
- package/src/oauth/chatgpt.ts +8 -72
- package/src/oauth/dsh.ts +203 -0
- package/src/oauth/generic-account-failover.ts +62 -12
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +188 -111
- package/src/oauth/kiro-device-login.ts +2 -1
- package/src/oauth/kiro.ts +7 -1
- package/src/oauth/local-token-detect.ts +21 -1
- package/src/oauth/login-cli-errors.ts +44 -0
- package/src/oauth/login-cli.ts +69 -7
- package/src/oauth/login-flow-state.ts +371 -23
- package/src/oauth/meta-muse-errors.ts +41 -0
- package/src/oauth/meta-muse-keychain.ts +52 -0
- package/src/oauth/meta-muse.ts +51 -84
- package/src/oauth/model-discovery-auth.ts +3 -0
- package/src/oauth/pool-settings-capability.ts +4 -2
- package/src/oauth/refresh-flight-retirement.ts +48 -0
- package/src/oauth/session-affinity-key.ts +23 -0
- package/src/oauth/store-anthropic-instance.ts +9 -2
- package/src/oauth/store.ts +12 -5
- package/src/protocols/codecs/chat-ir.ts +467 -0
- package/src/protocols/codecs/chat.ts +10 -1
- package/src/protocols/codecs/messages-ir.ts +500 -0
- package/src/protocols/encoders/adapter-events.ts +5 -4
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/features.ts +10 -2
- package/src/protocols/path.ts +11 -1
- package/src/protocols/plan-snapshot.ts +39 -16
- package/src/protocols/plan.ts +9 -1
- package/src/protocols/provider-summary.ts +2 -1
- package/src/protocols/settings.ts +8 -0
- package/src/providers/antigravity-account-roster.ts +102 -0
- package/src/providers/antigravity-models.ts +85 -7
- package/src/providers/api-key-cooldowns.ts +49 -0
- package/src/providers/api-key-resolve.ts +11 -6
- package/src/providers/api-key-response-ownership.ts +57 -0
- package/src/providers/api-key-selection.ts +31 -6
- package/src/providers/api-keys.ts +214 -3
- package/src/providers/claude-cli-usage.ts +96 -0
- package/src/providers/dsh-account-destination.ts +31 -0
- package/src/providers/dsh-account-models.ts +41 -0
- package/src/providers/gcp-credential-cleanup.ts +59 -0
- package/src/providers/key-failover.ts +54 -50
- package/src/providers/key-store.ts +7 -2
- package/src/providers/model-rename-fields.ts +5 -0
- package/src/providers/model-rename-migration.ts +7 -5
- package/src/providers/openai-tiers-destination.ts +11 -2
- package/src/providers/openai-tiers.ts +5 -12
- package/src/providers/opencode-go-transport.ts +3 -1
- package/src/providers/quota/account-cache.ts +6 -5
- package/src/providers/quota/account-row.ts +34 -0
- package/src/providers/quota/antigravity.ts +28 -7
- package/src/providers/quota/dsh-account.ts +70 -0
- package/src/providers/quota/vendor-probes-oauth.ts +1 -93
- package/src/providers/quota-types.ts +2 -0
- package/src/providers/quota.ts +14 -20
- package/src/providers/registry/entries-core.ts +44 -14
- package/src/providers/registry/entries-extended.ts +19 -0
- package/src/providers/registry/types.ts +1 -1
- package/src/providers/request-pacing.ts +77 -19
- package/src/providers/routed-config.ts +339 -0
- package/src/providers/service-tier.ts +2 -1
- package/src/quota/window-mapping.ts +5 -2
- package/src/responses/anthropic-progress.ts +34 -0
- package/src/responses/assistant-replay.ts +35 -0
- package/src/responses/compaction-history-framing.ts +65 -0
- package/src/responses/compaction.ts +6 -1
- package/src/responses/incomplete-reason.ts +31 -0
- package/src/responses/parser.ts +78 -22
- package/src/responses/reasoning-envelope.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +24 -2
- package/src/responses/reasoning-retention.ts +173 -0
- package/src/responses/retained-compaction.ts +46 -0
- package/src/responses/schema.ts +23 -0
- package/src/responses/state.ts +32 -29
- package/src/router.ts +8 -321
- package/src/routing/health.ts +37 -22
- package/src/routing/history/build-file.ts +92 -0
- package/src/routing/history/indexer.ts +254 -221
- package/src/routing/history/ingest.ts +242 -0
- package/src/routing/history/rebuild-job.ts +111 -0
- package/src/routing/history/rebuild-protocol.ts +15 -0
- package/src/routing/history/rebuild-worker.ts +34 -0
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +21 -1
- package/src/server/antigravity-direct-eligibility.ts +208 -0
- package/src/server/auth-cors.ts +15 -0
- package/src/server/chat-completions.ts +82 -10
- package/src/server/chat-native-reasoning.ts +123 -0
- package/src/server/chat-native-sse.ts +110 -7
- package/src/server/chat-native.ts +76 -17
- package/src/server/claude-messages.ts +164 -433
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/desktop-compatibility-startup.ts +56 -0
- package/src/server/index/model-alias-id.ts +36 -0
- package/src/server/index/optional-listeners.ts +2 -0
- package/src/server/index/serve-options.ts +11 -3
- package/src/server/index/startup-warnings.ts +11 -0
- package/src/server/index/websocket-handler.ts +9 -0
- package/src/server/index.ts +14 -11
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/inference/client-encoder-delivery.ts +6 -5
- package/src/server/login-shell-path.ts +146 -0
- package/src/server/management/advisor-routes.ts +202 -0
- package/src/server/management/agent-settings-routes.ts +22 -4
- package/src/server/management/claude-desktop-picker-routes.ts +1 -1
- package/src/server/management/combo-routes.ts +176 -50
- package/src/server/management/companion-routes.ts +4 -0
- package/src/server/management/config-routes.ts +77 -6
- package/src/server/management/context.ts +4 -0
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/desktop-compatibility-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
- package/src/server/management/model-routes.ts +13 -8
- package/src/server/management/model-rows.ts +14 -0
- package/src/server/management/native-integration-routes.ts +2 -7
- package/src/server/management/native-reasoning-retention-routes.ts +41 -0
- package/src/server/management/oauth-account-routes.ts +87 -14
- package/src/server/management/protocol-settings-patch.ts +1 -0
- package/src/server/management/provider-gcp-credential-routes.ts +29 -0
- package/src/server/management/provider-key-pacing-routes.ts +31 -0
- package/src/server/management/provider-overwrite-carry.ts +7 -0
- package/src/server/management/provider-routes.ts +25 -30
- package/src/server/management/route-registry.ts +21 -1
- package/src/server/management/sibling-guard.ts +1 -0
- package/src/server/management-api.ts +9 -1
- package/src/server/messages-antigravity-direct.ts +195 -0
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +106 -22
- package/src/server/messages-shared.ts +431 -0
- package/src/server/relay.ts +1 -1
- package/src/server/request-log-terminal-status.ts +1 -1
- package/src/server/request-log.ts +13 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +48 -19
- package/src/server/responses/adapter-delivery.ts +43 -8
- package/src/server/responses/adapter-dispatch.ts +48 -4
- package/src/server/responses/advisor-plan-slot.ts +27 -0
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +368 -0
- package/src/server/responses/code-mode-collab-surface.ts +116 -8
- package/src/server/responses/codex-ws-wire.ts +15 -8
- package/src/server/responses/compact.ts +303 -125
- package/src/server/responses/compaction-recovery.ts +18 -10
- package/src/server/responses/compaction-retention.ts +61 -0
- package/src/server/responses/context-overflow.ts +11 -6
- package/src/server/responses/core-auth.ts +6 -0
- package/src/server/responses/core-codex-account.ts +11 -1
- package/src/server/responses/core-combo-failure.ts +12 -1
- package/src/server/responses/core-combo.ts +141 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +7 -2
- package/src/server/responses/core-normalize.ts +11 -0
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +51 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +25 -6
- package/src/server/responses/core.ts +3 -116
- package/src/server/responses/fetch-helpers.ts +73 -19
- package/src/server/responses/main-hard-lock-routing.ts +109 -0
- package/src/server/responses/native-response-control.ts +4 -2
- package/src/server/responses/passthrough-delivery.ts +19 -11
- package/src/server/responses/passthrough-dispatch.ts +29 -16
- package/src/server/responses/request-prepare.ts +144 -17
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +120 -27
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +166 -38
- package/src/server/responses/sidecar-execution.ts +98 -5
- package/src/server/responses/terminal-guard.ts +4 -17
- package/src/server/responses-undeclared-tool-guard.ts +30 -9
- package/src/server/startup-action-control.ts +16 -2
- package/src/server/startup-health-cache.ts +1 -0
- package/src/service/auth-preflight.ts +71 -0
- package/src/service/cli.ts +4 -2
- package/src/service/definition.ts +97 -0
- package/src/service/desktop-startup.ts +14 -0
- package/src/service/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +99 -61
- package/src/service/health-sentinel-probe.ts +24 -7
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +135 -0
- package/src/service/repair.ts +2 -1
- package/src/service/state.ts +7 -8
- package/src/service/systemd.ts +1 -1
- package/src/service/windows-guarded-manager.ts +2 -1
- package/src/service/windows-ops.ts +1 -1
- package/src/service/windows-scheduler.ts +2 -7
- package/src/service/windows-taskxml.ts +2 -2
- package/src/service/windows-wscript.ts +14 -0
- package/src/service.ts +4 -2
- package/src/sidecar/auth.ts +2 -0
- package/src/tray/windows-tray.ps1 +43 -24
- package/src/types/config.ts +124 -6
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +29 -0
- package/src/types/provider.ts +39 -6
- package/src/types/request.ts +38 -2
- package/src/types/tools.ts +2 -0
- package/src/types/wire.ts +29 -37
- package/src/types.ts +1 -0
- package/src/update/async-check.ts +1 -1
- package/src/update/badge.ts +2 -2
- package/src/update/index.ts +88 -15
- package/src/update/job-supervised-restart.ts +107 -0
- package/src/update/job.ts +32 -32
- package/src/update/notify.ts +1 -1
- package/src/update/npm-invocation.mjs +12 -2
- package/src/update/refresh-scheduler.ts +2 -2
- package/src/update/supervised-recovery.d.mts +27 -0
- package/src/update/supervised-recovery.mjs +157 -0
- package/src/usage/cache-diagnostic.ts +2 -0
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +15 -0
- package/src/usage/ledger-retention.ts +2 -1
- package/src/usage/ledger-scanner.ts +6 -5
- package/src/usage/log.ts +17 -8
- package/src/usage/summary.ts +22 -9
- package/src/usage/user-cost-overlays.ts +14 -19
- package/src/usage/user-cost-validation.ts +38 -0
- package/src/vision/eligibility.ts +13 -0
- package/src/web-search/gemini-executor.ts +2 -2
- package/src/web-search/loop.ts +55 -5
- package/src/web-search/progress-stream.ts +4 -1
- package/src/web-search/run-turn-loop.ts +9 -1
- package/assets/sponsors/packycode.png +0 -0
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/App-CZtqiGcw.js +0 -56
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
- package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
|
@@ -34,6 +34,7 @@ import type {
|
|
|
34
34
|
CodexRestartResponse,
|
|
35
35
|
} from "../lib/codex-restart-contract";
|
|
36
36
|
import { getServerListenPort } from "../server/lifecycle";
|
|
37
|
+
import type { DesktopPickerState } from "./desktop-app/picker-freshness";
|
|
37
38
|
|
|
38
39
|
async function defaultRestartDesktopApp(): Promise<CodexDesktopRestartSummary> {
|
|
39
40
|
const { restartCodexDesktopApp } = await import("./desktop-app-restart");
|
|
@@ -47,9 +48,40 @@ async function defaultRestartDesktopApp(): Promise<CodexDesktopRestartSummary> {
|
|
|
47
48
|
};
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
async function defaultDesktopPickerState(): Promise<DesktopPickerState> {
|
|
52
|
+
const { readCodexDesktopPickerState } = await import("./desktop-app/picker-freshness");
|
|
53
|
+
return readCodexDesktopPickerState();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function defaultCatalogBinding(): Promise<string | null> {
|
|
57
|
+
const { readCodexCatalogBinding } = await import("./desktop-app/picker-freshness");
|
|
58
|
+
return readCodexCatalogBinding();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function readBinding(io: CodexRestartServiceIo): Promise<string | null | undefined> {
|
|
62
|
+
try {
|
|
63
|
+
return await (io.catalogBinding ?? defaultCatalogBinding)();
|
|
64
|
+
} catch {
|
|
65
|
+
// Distinct from null ("binds no catalog"): an unreadable binding cannot prove sameness.
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function readPickerState(io: CodexRestartServiceIo): Promise<DesktopPickerState> {
|
|
71
|
+
try {
|
|
72
|
+
return await (io.desktopPickerState ?? defaultDesktopPickerState)();
|
|
73
|
+
} catch {
|
|
74
|
+
return "unknown";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
50
78
|
export interface CodexRestartServiceIo {
|
|
51
79
|
/** Desktop-restart seam, so a route test cannot terminate the developer's own Codex. */
|
|
52
80
|
restartDesktopApp?: () => Promise<CodexDesktopRestartSummary>;
|
|
81
|
+
/** Whether the desktop picker predates the catalog; decides if the shell must restart. */
|
|
82
|
+
desktopPickerState?: () => Promise<DesktopPickerState>;
|
|
83
|
+
/** The catalog config.toml binds, read before and after the sync to catch a rebinding. */
|
|
84
|
+
catalogBinding?: () => Promise<string | null>;
|
|
53
85
|
/** Process-layer seam, forwarded to every app-server-processes call. */
|
|
54
86
|
processIo?: CodexAppServerProcessIo;
|
|
55
87
|
/** Catalog refresh seam. Resolves to whether a catalog or cache write happened. */
|
|
@@ -133,6 +165,7 @@ async function runCodexRestart(io: CodexRestartServiceIo): Promise<CodexRestartR
|
|
|
133
165
|
// roster; stopping app-servers before the write would hand the replacement the
|
|
134
166
|
// same stale file it just lost.
|
|
135
167
|
let synced = false;
|
|
168
|
+
const bindingBefore = await readBinding(io);
|
|
136
169
|
try {
|
|
137
170
|
const port = (io.listenPort ?? getServerListenPort)();
|
|
138
171
|
synced = await (io.syncCatalog ?? defaultSyncCatalog)(port);
|
|
@@ -146,15 +179,33 @@ async function runCodexRestart(io: CodexRestartServiceIo): Promise<CodexRestartR
|
|
|
146
179
|
(io.resetStateCache ?? resetCodexAppServerCatalogStateCache)();
|
|
147
180
|
const before = (io.collectState ?? collectCodexAppServerCatalogState)(io.processIo ?? {});
|
|
148
181
|
|
|
149
|
-
//
|
|
150
|
-
// app: "no app-server is running" is not a reason to leave a stale roster on screen
|
|
151
|
-
//
|
|
182
|
+
// Decided here, before the early returns, because the model picker lives in the DESKTOP
|
|
183
|
+
// app: "no app-server is running" is not a reason to leave a stale roster on screen.
|
|
184
|
+
//
|
|
185
|
+
// The shell restarts only when its picker predates the catalog (#4761). Quitting it
|
|
186
|
+
// discards composer drafts, unsent picker selections and pending approvals, and when
|
|
187
|
+
// the picker is already current the app-server restart below is all the operator
|
|
188
|
+
// needs: the desktop respawns its app-server child, which rereads config and catalog.
|
|
189
|
+
// An unreadable picker state takes the full restart, the behaviour before #4761.
|
|
152
190
|
//
|
|
153
191
|
// allowHandoff is false and that is deliberate. The handoff waits for the CALLING
|
|
154
192
|
// process to exit, and this runs inside a long-lived proxy that does not, so every
|
|
155
193
|
// handoff started here would sit out its window and fail after the operator had
|
|
156
194
|
// already been told it was handed off. An honest refusal beats that.
|
|
157
|
-
|
|
195
|
+
//
|
|
196
|
+
// A sync that rebinds model_catalog_json without rewriting the catalog bytes leaves the
|
|
197
|
+
// catalog mtime older than the shell, yet the shell loaded the previous binding. Seen
|
|
198
|
+
// across this request's own sync, that is proof of staleness, so it skips the probe.
|
|
199
|
+
const bindingAfter = await readBinding(io);
|
|
200
|
+
const rebound = bindingBefore === undefined || bindingAfter === undefined || bindingBefore !== bindingAfter;
|
|
201
|
+
const picker: DesktopPickerState = rebound ? "stale" : await readPickerState(io);
|
|
202
|
+
const desktop: CodexDesktopRestartSummary = picker === "current" || picker === "not_running"
|
|
203
|
+
? {
|
|
204
|
+
attempted: false, stopped: [], surviving: [], relaunch: "skipped",
|
|
205
|
+
reason: picker === "current" ? "picker_current" : "no_targets",
|
|
206
|
+
pickerState: picker,
|
|
207
|
+
}
|
|
208
|
+
: { ...await (io.restartDesktopApp ?? defaultRestartDesktopApp)(), pickerState: picker };
|
|
158
209
|
|
|
159
210
|
const nothingToDo = (): CodexRestartResponse => ({
|
|
160
211
|
desktopApp: desktop,
|
|
@@ -10,6 +10,7 @@ import { ConfigMutationLockError, mutatePersistedConfig } from "../../config";
|
|
|
10
10
|
import { reconcileMainCodexAccountRuntimeState } from "../account-lifecycle";
|
|
11
11
|
import { isCodexAccountPaused, setCodexAccountPaused } from "../account-pause";
|
|
12
12
|
import { codexAccountUsesCreditsAfterLimit } from "../account-credit-use";
|
|
13
|
+
import { codexAccountUsesRemainingQuota } from "../account-use-remaining";
|
|
13
14
|
import { getCodexAccountPriority } from "../account-priority";
|
|
14
15
|
import { getCodexAccountAutoSwitchThresholdOverride } from "../account-auto-switch";
|
|
15
16
|
import { clearThreadAccountMapForAccount, isCodexAccountPlanExcluded, reconcileCodexActiveAfterExclusion } from "../routing";
|
|
@@ -143,6 +144,7 @@ export function poolAccountDto(
|
|
|
143
144
|
priority,
|
|
144
145
|
autoSwitchThresholdOverride: getCodexAccountAutoSwitchThresholdOverride(config, account.id),
|
|
145
146
|
creditsAfterLimit: codexAccountUsesCreditsAfterLimit(config, account.id),
|
|
147
|
+
useRemainingQuota: codexAccountUsesRemainingQuota(config, account.id),
|
|
146
148
|
quota: quota ? { ...quota } : null,
|
|
147
149
|
...codexCreditsDtoField(config, account.id, poolQuotaHistoryIdentity(account.id) ?? null),
|
|
148
150
|
needsReauth: needsReauth || health.status === "reauth_required",
|
|
@@ -171,6 +173,7 @@ export interface CodexAuthAccountDto {
|
|
|
171
173
|
autoSwitchThresholdOverride: number | null;
|
|
172
174
|
/** False keeps the account out of selection while one of its usage windows is full. */
|
|
173
175
|
creditsAfterLimit?: boolean;
|
|
176
|
+
useRemainingQuota?: boolean;
|
|
174
177
|
quota: (StoredAccountQuota | (Omit<StoredAccountQuota, "updatedAt"> & { updatedAt: number })) | null;
|
|
175
178
|
credits?: CodexCredits;
|
|
176
179
|
needsReauth?: boolean;
|
|
@@ -405,6 +408,7 @@ export async function listCodexAuthAccountsSnapshot(
|
|
|
405
408
|
priority: getCodexAccountPriority(runtimeConfig, MAIN_CODEX_ACCOUNT_ID),
|
|
406
409
|
autoSwitchThresholdOverride: getCodexAccountAutoSwitchThresholdOverride(runtimeConfig, MAIN_CODEX_ACCOUNT_ID),
|
|
407
410
|
creditsAfterLimit: codexAccountUsesCreditsAfterLimit(runtimeConfig, MAIN_CODEX_ACCOUNT_ID),
|
|
411
|
+
useRemainingQuota: codexAccountUsesRemainingQuota(runtimeConfig, MAIN_CODEX_ACCOUNT_ID),
|
|
408
412
|
hasCredential: hasMainCredential,
|
|
409
413
|
needsReauth: mainNeedsReauth,
|
|
410
414
|
...(mainReauthReason !== undefined ? { reauthReason: mainReauthReason } : {}),
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
+
import { observeSignedInCodexIdentity } from "../signed-in-identity";
|
|
1
2
|
import { lstatSync } from "node:fs";
|
|
2
3
|
import type { OcxConfig } from "../../types";
|
|
3
|
-
import { inspectChatGptDomainClaim, extractEmail } from "../../oauth/chatgpt";
|
|
4
|
-
import { MAIN_CODEX_ACCOUNT_ID, isSelectableCodexPoolAccount } from "../account-id";
|
|
5
4
|
import { readCodexTokensResult } from "../auth-collision";
|
|
6
5
|
import type { CodexTokenReadResult } from "../auth-collision";
|
|
7
|
-
import {
|
|
6
|
+
import { linkedCodexAccountIds } from "../account-identity";
|
|
8
7
|
import { resolveCodexHomeDir } from "../home";
|
|
9
8
|
import { tryAcquireNativeMainProfileClaim } from "../native-main-admission";
|
|
10
9
|
import { withNativeMainSharedClaim } from "../native-main-claim";
|
|
@@ -12,7 +11,6 @@ import { MAX_AUTH_BYTES, readBounded, resolveNativeProfileContext } from "../nat
|
|
|
12
11
|
import { NativeProfileError } from "../native-profile-types";
|
|
13
12
|
import { isNativeMainClaimUnavailable, nativeMainProfileBusyResponse } from "./http";
|
|
14
13
|
|
|
15
|
-
type AccountIdentity = { accountId: string; email: string };
|
|
16
14
|
type MainAuth = CodexTokenReadResult | { status: "api-key-only" };
|
|
17
15
|
|
|
18
16
|
/** A valid API-key login has no ChatGPT identity; anything else unreadable stays unknown. */
|
|
@@ -32,50 +30,6 @@ function readMainAuth(authPath: string): MainAuth {
|
|
|
32
30
|
}
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
function identity(accountId: unknown, email: unknown): AccountIdentity | undefined {
|
|
36
|
-
const normalizedEmail = typeof email === "string" ? email.trim().toLowerCase() : undefined;
|
|
37
|
-
return typeof accountId === "string" && accountId.trim() && normalizedEmail
|
|
38
|
-
? { accountId: accountId.trim(), email: normalizedEmail }
|
|
39
|
-
: undefined;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Resolve only existing entries, under native-main ownership, never on the request path. */
|
|
43
|
-
function linkedAccountIds(config: OcxConfig, selectedId: string, main: MainAuth): string[] | undefined {
|
|
44
|
-
// Unknown main identity must not produce a successful but incomplete pause.
|
|
45
|
-
if (main.status === "unreadable" || main.status === "invalid") return undefined;
|
|
46
|
-
const identities = new Map<string, AccountIdentity | undefined>();
|
|
47
|
-
if (main.status === "ok") {
|
|
48
|
-
const tokens = main.tokens;
|
|
49
|
-
if (typeof tokens.access_token !== "string" || typeof tokens.account_id !== "string"
|
|
50
|
-
|| (tokens.id_token !== undefined && typeof tokens.id_token !== "string")) return undefined;
|
|
51
|
-
const claims = [tokens.id_token, tokens.access_token]
|
|
52
|
-
.filter((token): token is string => typeof token === "string")
|
|
53
|
-
.map(inspectChatGptDomainClaim);
|
|
54
|
-
if (claims.some(claim => claim.kind === "invalid")) return undefined;
|
|
55
|
-
const claimIds = claims.flatMap(claim => claim.kind === "valid" ? [claim.accountId] : []);
|
|
56
|
-
// organizations[] describes memberships, not the selected workspace. Never use its first row.
|
|
57
|
-
const accountId = tokens.account_id.trim() || claimIds[0];
|
|
58
|
-
if (claimIds.some(claim => claim !== accountId)) return undefined;
|
|
59
|
-
// Both tokens must name the same member; otherwise the bearer's identity is unknown.
|
|
60
|
-
const emails = new Set([extractEmail(tokens.id_token), extractEmail(undefined, tokens.access_token)]
|
|
61
|
-
.flatMap(email => email?.trim() ? [email.trim().toLowerCase()] : []));
|
|
62
|
-
if (emails.size > 1) return undefined;
|
|
63
|
-
identities.set(MAIN_CODEX_ACCOUNT_ID, identity(accountId, [...emails][0]));
|
|
64
|
-
}
|
|
65
|
-
for (const account of config.codexAccounts ?? []) {
|
|
66
|
-
if (!isSelectableCodexPoolAccount(account)) continue;
|
|
67
|
-
const credential = getCodexAccountCredential(account.id);
|
|
68
|
-
identities.set(account.id, identity(credential?.chatgptAccountId, account.email));
|
|
69
|
-
}
|
|
70
|
-
const selected = identities.get(selectedId);
|
|
71
|
-
// Missing identity evidence never links unrelated logins, including members of one workspace.
|
|
72
|
-
if (!selected) return [selectedId];
|
|
73
|
-
return [selectedId, ...[...identities].flatMap(([id, candidate]) => (
|
|
74
|
-
id !== selectedId && candidate?.accountId === selected.accountId && candidate.email === selected.email
|
|
75
|
-
? [id] : []
|
|
76
|
-
))];
|
|
77
|
-
}
|
|
78
|
-
|
|
79
33
|
/** Hold admission throughout; claim the physical main whenever its home exists. */
|
|
80
34
|
export async function withCodexAccountPauseGroup(
|
|
81
35
|
config: OcxConfig,
|
|
@@ -92,13 +46,15 @@ export async function withCodexAccountPauseGroup(
|
|
|
92
46
|
// Only positive absence permits Pool-only publication. A dangling symlink or access
|
|
93
47
|
// failure is not absence; do not create a home just to coordinate an unused main login.
|
|
94
48
|
if ((error as NodeJS.ErrnoException)?.code !== "ENOENT") return nativeMainProfileBusyResponse();
|
|
95
|
-
const ids =
|
|
49
|
+
const ids = linkedCodexAccountIds(config, selectedId, { status: "missing" });
|
|
50
|
+
if (ids) observeSignedInCodexIdentity();
|
|
96
51
|
return ids ? publish(ids) : nativeMainProfileBusyResponse();
|
|
97
52
|
}
|
|
98
53
|
const context = resolveNativeProfileContext({ codexHome });
|
|
99
54
|
return await withNativeMainSharedClaim(context, async () => {
|
|
100
55
|
const main = readMainAuth(context.authPath);
|
|
101
|
-
const ids =
|
|
56
|
+
const ids = linkedCodexAccountIds(config, selectedId, main);
|
|
57
|
+
if (ids) observeSignedInCodexIdentity(main.status === "ok" ? main.tokens : undefined);
|
|
102
58
|
return ids ? publish(ids) : nativeMainProfileBusyResponse();
|
|
103
59
|
});
|
|
104
60
|
} catch (error) {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { getSignedInCodexIdentity } from "../signed-in-identity";
|
|
2
|
+
import type { OcxConfig } from "../../types";
|
|
3
|
+
import { captureConfigTopLevelRollback } from "../../config/rebase-provenance";
|
|
4
|
+
import { clearCodexAccountPin, pinnedCodexAccountId } from "../account-priority";
|
|
5
|
+
import { clearThreadAccountMapForAccount, previewCodexAccountForRequest } from "../routing";
|
|
6
|
+
import { forgetManualPreference } from "../routing/active-account";
|
|
7
|
+
import { withCodexAccountPauseGroup } from "./account-pause-group";
|
|
8
|
+
import { MAIN_CODEX_ACCOUNT_ID } from "../account-id";
|
|
9
|
+
import { getRuntimeConfig, saveRuntimeConfig } from "./runtime-config";
|
|
10
|
+
import { jsonResponse } from "./http";
|
|
11
|
+
|
|
12
|
+
/** Management owns the native-main claim; publish before releasing any runtime bindings. */
|
|
13
|
+
export async function handleCodexAccountPreservation(req: Request, config: OcxConfig): Promise<Response> {
|
|
14
|
+
let update: { enabled: boolean; applyToBound: boolean } | undefined;
|
|
15
|
+
if (req.method === "PUT") {
|
|
16
|
+
const body: unknown = await req.json().catch(() => null);
|
|
17
|
+
if (!body || typeof body !== "object" || Array.isArray(body)
|
|
18
|
+
|| !("enabled" in body) || typeof body.enabled !== "boolean"
|
|
19
|
+
|| Object.keys(body).some(key => key !== "enabled" && key !== "applyToBound")
|
|
20
|
+
|| ("applyToBound" in body && typeof body.applyToBound !== "boolean")) {
|
|
21
|
+
return jsonResponse({ error: "enabled and optional applyToBound must be booleans" }, 400);
|
|
22
|
+
}
|
|
23
|
+
const applyToBound = "applyToBound" in body && body.applyToBound === true;
|
|
24
|
+
if (applyToBound && !body.enabled) return jsonResponse({ error: "applyToBound requires enabled: true" }, 400);
|
|
25
|
+
update = { enabled: body.enabled, applyToBound };
|
|
26
|
+
}
|
|
27
|
+
const runtime = getRuntimeConfig(config);
|
|
28
|
+
return withCodexAccountPauseGroup(runtime, MAIN_CODEX_ACCOUNT_ID, affectedAccountIds => {
|
|
29
|
+
const releasedPin = update?.applyToBound === true && affectedAccountIds.includes(pinnedCodexAccountId(runtime) ?? "");
|
|
30
|
+
if (update) {
|
|
31
|
+
const rollback = captureConfigTopLevelRollback(runtime, ["codexPreserveSignedInAccount", "activeCodexAccountPinned"]);
|
|
32
|
+
try {
|
|
33
|
+
runtime.codexPreserveSignedInAccount = update.enabled;
|
|
34
|
+
if (releasedPin) clearCodexAccountPin(runtime);
|
|
35
|
+
saveRuntimeConfig(config, runtime);
|
|
36
|
+
} catch (error) { rollback(); throw error; }
|
|
37
|
+
if (update.applyToBound) {
|
|
38
|
+
for (const id of affectedAccountIds) {
|
|
39
|
+
clearThreadAccountMapForAccount(id);
|
|
40
|
+
forgetManualPreference(id);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const enabled = runtime.codexPreserveSignedInAccount === true;
|
|
45
|
+
const nextAccountId = previewCodexAccountForRequest(null, runtime);
|
|
46
|
+
return jsonResponse({
|
|
47
|
+
ok: true, enabled, affectedAccountIds, identityKnown: getSignedInCodexIdentity() !== undefined,
|
|
48
|
+
nextAccountId,
|
|
49
|
+
nextUsesSignedInQuota: nextAccountId !== null && affectedAccountIds.includes(nextAccountId),
|
|
50
|
+
pinnedAccountId: pinnedCodexAccountId(runtime) ?? null,
|
|
51
|
+
bindings: update?.applyToBound ? "released-for-next-request" : "retained",
|
|
52
|
+
releasedPin,
|
|
53
|
+
priorityFailbackEnabled: runtime.codexAccountPriorityFailback === true,
|
|
54
|
+
fallbackConsumesSignedInQuota: true,
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -25,6 +25,7 @@ import type { PoolQuotaResult } from "./pool-quota-probe";
|
|
|
25
25
|
import { fetchMainAccountInfoAttempt, fetchMainAccountInfo, MAIN_CACHE_TTL } from "./main-account-probe";
|
|
26
26
|
import { fetchPoolAccountQuota, PoolQuotaProbeBusyError, POOL_CACHE_TTL, POOL_QUOTA_REFRESH_CONCURRENCY } from "./pool-quota-probe";
|
|
27
27
|
import { getRuntimeConfig, configuredPoolAccount, mapWithConcurrency } from "./runtime-config";
|
|
28
|
+
import { trackBackgroundWork } from "../../lib/background-work";
|
|
28
29
|
|
|
29
30
|
let primeInFlight: Promise<void> | null = null;
|
|
30
31
|
let lastPriorityFailbackPrimeAt: number | undefined;
|
|
@@ -259,7 +260,9 @@ export async function primeCodexPoolQuotas(
|
|
|
259
260
|
&& now - lastPriorityFailbackPrimeAt < CODEX_PRIORITY_FAILBACK_REFRESH_MS) return;
|
|
260
261
|
lastPriorityFailbackPrimeAt = now;
|
|
261
262
|
}
|
|
262
|
-
|
|
263
|
+
// Tracked because most callers do not await it, yet it holds the native-main claim
|
|
264
|
+
// database open; whoever removes that directory settles it first (see background-work).
|
|
265
|
+
primeInFlight = trackBackgroundWork(`codex-pool-quota-prime:${reason}`, (async () => {
|
|
263
266
|
const pool = (runtimeConfig.codexAccounts ?? []).filter(isSelectableCodexPoolAccount);
|
|
264
267
|
const stale = pool.filter(a => {
|
|
265
268
|
const q = getAccountQuota(a.id);
|
|
@@ -344,7 +347,7 @@ export async function primeCodexPoolQuotas(
|
|
|
344
347
|
if (process.env.OPENCODEX_DEBUG_QUOTA === "1") {
|
|
345
348
|
console.warn(`[codex-quota] prime done (reason=${reason}, pool=${pool.length}, refreshed=${stale.length})`);
|
|
346
349
|
}
|
|
347
|
-
})().finally(() => { primeInFlight = null; });
|
|
350
|
+
})().finally(() => { primeInFlight = null; }));
|
|
348
351
|
return primeInFlight;
|
|
349
352
|
}
|
|
350
353
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { handleCodexAccountPreservation } from "./account-preservation";
|
|
1
2
|
import { persistCodexAccountSelection } from "./account-selection";
|
|
2
3
|
import { CODEX_ACCOUNT_LOG_LABEL_RE, codexAccountLogLabel } from "../account-label";
|
|
4
|
+
import { handleUseRemainingQuotaRoute } from "./use-remaining-route";
|
|
3
5
|
import { poolQuotaHistoryIdentity, readCodexAccountRecord } from "../account-store";
|
|
4
6
|
import { estimateCodexQuotaCapacity, insufficientCodexCapacity } from "../quota-capacity";
|
|
5
7
|
import type { CodexCapacityResult } from "../quota-capacity";
|
|
@@ -34,6 +36,12 @@ export async function handleCodexAuthAPI(
|
|
|
34
36
|
convergeCodexCatalog?: CodexAuthCatalogConvergence,
|
|
35
37
|
principal?: import("../../server/management-auth").ManagementPrincipal,
|
|
36
38
|
): Promise<Response | null> {
|
|
39
|
+
if (url.pathname === "/api/codex-auth/preserve-signed-in" && (req.method === "GET" || req.method === "PUT")) {
|
|
40
|
+
return handleCodexAccountPreservation(req, config);
|
|
41
|
+
}
|
|
42
|
+
if (url.pathname === "/api/codex-auth/accounts/use-remaining" && (req.method === "GET" || req.method === "PUT")) {
|
|
43
|
+
return handleUseRemainingQuotaRoute(req, url, config);
|
|
44
|
+
}
|
|
37
45
|
if (url.pathname === "/api/codex-auth/accounts" && req.method === "GET") {
|
|
38
46
|
const forceRefresh = url.searchParams.get("refresh") === "1" || url.searchParams.get("refresh") === "true";
|
|
39
47
|
return jsonResponse({ accounts: await listCodexAuthAccounts(config, forceRefresh) });
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { OcxConfig } from "../../types";
|
|
2
|
+
import { captureConfigTopLevelRollback } from "../../config/rebase-provenance";
|
|
3
|
+
import { MAIN_CODEX_ACCOUNT_ID, isValidCodexAccountId } from "../account-id";
|
|
4
|
+
import { getEffectiveCodexAutoSwitchThreshold } from "../account-auto-switch";
|
|
5
|
+
import { codexAccountUsesRemainingQuota, setCodexAccountUseRemainingQuota } from "../account-use-remaining";
|
|
6
|
+
import { getMainAccountHardLockStatus } from "../main-account-hard-lock";
|
|
7
|
+
import { configuredPoolAccount, getRuntimeConfig, saveRuntimeConfig } from "./runtime-config";
|
|
8
|
+
import { jsonResponse } from "./http";
|
|
9
|
+
|
|
10
|
+
/** Called only behind the existing Codex management authentication boundary. */
|
|
11
|
+
export async function handleUseRemainingQuotaRoute(req: Request, url: URL, config: OcxConfig): Promise<Response> {
|
|
12
|
+
const parsed: unknown = req.method === "PUT" ? await req.json().catch(() => null) : { id: url.searchParams.get("id") };
|
|
13
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return jsonResponse({ error: "body must be an object" }, 400);
|
|
14
|
+
const body = parsed as { id?: unknown; useRemainingQuota?: unknown };
|
|
15
|
+
if (typeof body.id !== "string" || (body.id !== MAIN_CODEX_ACCOUNT_ID && !isValidCodexAccountId(body.id))) {
|
|
16
|
+
return jsonResponse({ error: "Invalid account id format" }, 400);
|
|
17
|
+
}
|
|
18
|
+
const runtime = getRuntimeConfig(config);
|
|
19
|
+
if (body.id !== MAIN_CODEX_ACCOUNT_ID && !configuredPoolAccount(runtime, body.id)) return jsonResponse({ error: "Account not found" }, 404);
|
|
20
|
+
if (req.method === "PUT") {
|
|
21
|
+
if (typeof body.useRemainingQuota !== "boolean") return jsonResponse({ error: "useRemainingQuota must be a boolean" }, 400);
|
|
22
|
+
const rollback = captureConfigTopLevelRollback(runtime, ["codexUseRemainingQuotaAccountIds"]);
|
|
23
|
+
try {
|
|
24
|
+
setCodexAccountUseRemainingQuota(runtime, body.id, body.useRemainingQuota);
|
|
25
|
+
saveRuntimeConfig(config, runtime);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
rollback();
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return jsonResponse({ ok: true, id: body.id,
|
|
32
|
+
useRemainingQuota: codexAccountUsesRemainingQuota(runtime, body.id),
|
|
33
|
+
autoSwitchThreshold: getEffectiveCodexAutoSwitchThreshold(runtime, body.id),
|
|
34
|
+
...(body.id === MAIN_CODEX_ACCOUNT_ID ? { mainAccountHardLock: getMainAccountHardLockStatus(runtime) } : {}),
|
|
35
|
+
});
|
|
36
|
+
}
|
package/src/codex/auth-api.ts
CHANGED
|
@@ -33,7 +33,6 @@ export {
|
|
|
33
33
|
} from "./auth-api/pool-mode-gate";
|
|
34
34
|
export { createResetCreditWhamClient } from "./auth-api/reset-credit-service";
|
|
35
35
|
export type { CodexAuthCatalogConvergence } from "./auth-api/login-flow";
|
|
36
|
-
export { handleCodexAuthAPI } from "./auth-api/routes";
|
|
37
36
|
import { getEffectiveActiveCodexAccountId } from "./routing";
|
|
38
37
|
import { MAIN_CODEX_ACCOUNT_ID } from "./main-account";
|
|
39
38
|
import type { OcxConfig } from "../types";
|
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
// The half-open TRANSIENT-HOLD lease (#4701). Not the quota-cooldown probe lease imported from
|
|
52
52
|
// ./routing above -- different module, different domain, and a request never holds both.
|
|
53
53
|
import { releaseTransientProbe } from "../routing/probe-lease";
|
|
54
|
+
import { trackBackgroundWork } from "../lib/background-work";
|
|
54
55
|
import {
|
|
55
56
|
codexConversationIdentity,
|
|
56
57
|
recordCodexThreadLineage,
|
|
@@ -66,7 +67,8 @@ import {
|
|
|
66
67
|
import { ACCOUNT_GATED_NATIVE_OPENAI_MODELS, NATIVE_RESERVE_MODEL } from "./catalog/native-models";
|
|
67
68
|
import type { CodexCooldownSource, CodexQuotaScope } from "./routing";
|
|
68
69
|
import { maskAccountId } from "../lib/privacy";
|
|
69
|
-
import { formatErrorResponse } from "../bridge";
|
|
70
|
+
import { formatErrorResponse, formatUsageLimitReachedResponse } from "../bridge";
|
|
71
|
+
import { nextCodexPoolRecoveryAt } from "./routing/pool-recovery";
|
|
70
72
|
import { CODEX_UNKNOWN_USAGE_SCORE, getAccountQuota, getMainPolicyQuota, parseUsageQuota, parseMainPolicyUsageQuota, setAccountQuotaFromParsed } from "./quota";
|
|
71
73
|
import { codexAccountUsesCreditsAfterLimit, codexUsageLimitResetAt } from "./account-credit-use";
|
|
72
74
|
import type { CodexAccountMode, OcxConfig, OcxProviderConfig } from "../types";
|
|
@@ -523,6 +525,11 @@ export class CodexAccountCooldownError extends Error {
|
|
|
523
525
|
|
|
524
526
|
export class CodexMainAccountHardLockError extends CodexAccountCooldownError {
|
|
525
527
|
readonly resetAt?: number;
|
|
528
|
+
/**
|
|
529
|
+
* Earliest known recovery of another stored pool account, set only when an unpinned pool
|
|
530
|
+
* selection reached main (#7046). A hint for the client's retry time; never routing input.
|
|
531
|
+
*/
|
|
532
|
+
poolRecoveryAt?: number;
|
|
526
533
|
|
|
527
534
|
constructor(resetAt?: number, thresholds = resolveMainAccountHardLockThresholds(undefined)) {
|
|
528
535
|
super(MAIN_CODEX_ACCOUNT_ID, resetAt ?? 0);
|
|
@@ -625,7 +632,7 @@ export class CodexRecoveryWithheldError extends CodexAccountCooldownError {
|
|
|
625
632
|
|
|
626
633
|
export type CodexAuthPolicyConfig = Readonly<Pick<OcxConfig,
|
|
627
634
|
"codexMainAccountHardLock" | "codexMainAccountHardLockThresholds" | "codexDesktopAuthless" | "runtimeRole" | "pausedCodexAccountIds"
|
|
628
|
-
| "creditCodexAccountIds" | "codexAccounts"
|
|
635
|
+
| "creditCodexAccountIds" | "codexAccounts" | "codexUseRemainingQuotaAccountIds"
|
|
629
636
|
>>;
|
|
630
637
|
|
|
631
638
|
interface CodexAuthMaterializationOptions {
|
|
@@ -776,7 +783,7 @@ function mainCreditsHoldResetAt(config: Pick<OcxConfig, "creditCodexAccountIds">
|
|
|
776
783
|
}
|
|
777
784
|
|
|
778
785
|
function assertMainAccountPolicy(
|
|
779
|
-
config: Pick<OcxConfig, "codexMainAccountHardLock" | "codexMainAccountHardLockThresholds" | "creditCodexAccountIds"> | undefined,
|
|
786
|
+
config: Pick<OcxConfig, "codexMainAccountHardLock" | "codexMainAccountHardLockThresholds" | "creditCodexAccountIds" | "codexUseRemainingQuotaAccountIds"> | undefined,
|
|
780
787
|
): void {
|
|
781
788
|
if (config) {
|
|
782
789
|
const status = getMainAccountHardLockStatus(config);
|
|
@@ -935,16 +942,40 @@ export function cooldownErrorMessage(err: CodexAccountCooldownError, accountSele
|
|
|
935
942
|
+ ` (source: ${err.cooldownSource ?? "default"}).${recovery}`;
|
|
936
943
|
}
|
|
937
944
|
|
|
945
|
+
/**
|
|
946
|
+
* The soonest a main hard-lock refusal is worth retrying: a sibling pool account's known
|
|
947
|
+
* recovery when it comes first, otherwise main's own reset (#7046). A pool hint is held to at
|
|
948
|
+
* least {@link MAIN_HARD_LOCK_MIN_POOL_RETRY_MS} so a hold about to lapse cannot turn the refusal
|
|
949
|
+
* into a tight retry loop. Null when neither time is known and still in the future.
|
|
950
|
+
*/
|
|
951
|
+
export const MAIN_HARD_LOCK_MIN_POOL_RETRY_MS = 60_000;
|
|
952
|
+
export function mainHardLockRetryAt(err: CodexMainAccountHardLockError, now: number): number | null {
|
|
953
|
+
const future = (value: number | undefined): number | null =>
|
|
954
|
+
typeof value === "number" && Number.isFinite(value) && value > now ? value : null;
|
|
955
|
+
const main = future(err.resetAt);
|
|
956
|
+
const pool = future(err.poolRecoveryAt);
|
|
957
|
+
const poolRetryAt = pool === null ? null : Math.max(pool, now + MAIN_HARD_LOCK_MIN_POOL_RETRY_MS);
|
|
958
|
+
if (poolRetryAt !== null && (main === null || poolRetryAt < main)) return poolRetryAt;
|
|
959
|
+
return main;
|
|
960
|
+
}
|
|
961
|
+
|
|
938
962
|
/** HTTP form of {@link cooldownErrorMessage}, carrying Retry-After for well-behaved clients. */
|
|
939
963
|
export function cooldownErrorResponse(
|
|
940
964
|
err: CodexAccountCooldownError,
|
|
941
965
|
now = Date.now(),
|
|
942
966
|
accountSelector?: string,
|
|
943
967
|
): Response {
|
|
968
|
+
if (err instanceof CodexMainAccountHardLockError) {
|
|
969
|
+
// A local policy refusal with a known horizon, not a transient throttle (#7046): Codex keeps
|
|
970
|
+
// a Goal active and sleeps through `rate_limit_exceeded` for the whole Retry-After, but
|
|
971
|
+
// stops on `usage_limit_reached` and shows the reset. The guard itself is unchanged.
|
|
972
|
+
const retryAt = mainHardLockRetryAt(err, now);
|
|
973
|
+
return formatUsageLimitReachedResponse(cooldownErrorMessage(err, accountSelector), retryAt,
|
|
974
|
+
retryAt === null ? null : Math.max(1, Math.ceil((retryAt - now) / 1000)));
|
|
975
|
+
}
|
|
944
976
|
const res = formatErrorResponse(429, "rate_limit_error", cooldownErrorMessage(err, accountSelector));
|
|
945
977
|
const headers = new Headers(res.headers);
|
|
946
|
-
if (!(err instanceof CodexReserveUnavailableError)
|
|
947
|
-
&& (!(err instanceof CodexMainAccountHardLockError) || err.resetAt !== undefined)) {
|
|
978
|
+
if (!(err instanceof CodexReserveUnavailableError)) {
|
|
948
979
|
headers.set("Retry-After", String(Math.max(1, Math.ceil((err.cooldownUntil - now) / 1000))));
|
|
949
980
|
}
|
|
950
981
|
return new Response(res.body, { status: res.status, headers });
|
|
@@ -1044,11 +1075,51 @@ async function awaitMainAccountPolicyBindingSettled(
|
|
|
1044
1075
|
if (!settled) throw new CodexMainProfileDrainingError();
|
|
1045
1076
|
}
|
|
1046
1077
|
|
|
1078
|
+
/**
|
|
1079
|
+
* Resolve the credential for one Codex turn. A main hard-lock reached through unpinned pool
|
|
1080
|
+
* selection is annotated with the earliest known pool recovery, so the refusal can name a retry
|
|
1081
|
+
* time that a sibling account may meet (#7046).
|
|
1082
|
+
*/
|
|
1047
1083
|
export async function resolveCodexAuthContext(
|
|
1048
1084
|
headers: Headers,
|
|
1049
1085
|
config: OcxConfig,
|
|
1050
1086
|
mode: CodexAccountMode,
|
|
1051
1087
|
options: ResolveCodexAuthContextOptions = {},
|
|
1088
|
+
): Promise<CodexAuthContext> {
|
|
1089
|
+
// Filled by the inner resolver with the request's own fenced model evidence, so the hint obeys it.
|
|
1090
|
+
const eligibility: RecoveryHintEligibility = {};
|
|
1091
|
+
try {
|
|
1092
|
+
return await resolveCodexAuthContextUnannotated(headers, config, mode, options, eligibility);
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
if (error instanceof CodexMainAccountHardLockError && mode === "pool" && options.accountId === undefined
|
|
1095
|
+
&& options.excludeAccountId !== MAIN_CODEX_ACCOUNT_ID
|
|
1096
|
+
&& !requiresReserveAuthorization(options.codexAuthPolicy ?? config, options.modelId, options.admission)
|
|
1097
|
+
// An account-gated model with no resolved roster has no known eligible sibling.
|
|
1098
|
+
&& (!options.modelId || !ACCOUNT_GATED_NATIVE_OPENAI_MODELS.has(options.modelId)
|
|
1099
|
+
|| eligibility.modelEligibleAccountIds !== undefined)) {
|
|
1100
|
+
try {
|
|
1101
|
+
const now = Date.now();
|
|
1102
|
+
error.poolRecoveryAt = nextCodexPoolRecoveryAt(config, now, codexQuotaScopeForModel(options.modelId), {
|
|
1103
|
+
deniedAccountIds: eligibility.deniedModelAccountIds,
|
|
1104
|
+
eligibleAccountIds: eligibility.modelEligibleAccountIds,
|
|
1105
|
+
});
|
|
1106
|
+
} catch { /* the hint is optional; the refusal stands without it */ }
|
|
1107
|
+
}
|
|
1108
|
+
throw error;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
interface RecoveryHintEligibility {
|
|
1113
|
+
modelEligibleAccountIds?: ReadonlySet<string>;
|
|
1114
|
+
deniedModelAccountIds?: ReadonlySet<string>;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
async function resolveCodexAuthContextUnannotated(
|
|
1118
|
+
headers: Headers,
|
|
1119
|
+
config: OcxConfig,
|
|
1120
|
+
mode: CodexAccountMode,
|
|
1121
|
+
options: ResolveCodexAuthContextOptions = {},
|
|
1122
|
+
eligibility: RecoveryHintEligibility = {},
|
|
1052
1123
|
): Promise<CodexAuthContext> {
|
|
1053
1124
|
const writerGeneration = captureConfigGeneration();
|
|
1054
1125
|
const policy = options.codexAuthPolicy ?? config;
|
|
@@ -1256,6 +1327,7 @@ export async function resolveCodexAuthContext(
|
|
|
1256
1327
|
const modelEligibleAccountIds = entitledAccountIds
|
|
1257
1328
|
? new Set([...entitledAccountIds].filter(candidate => !excludeAccountIds?.has(candidate)))
|
|
1258
1329
|
: undefined;
|
|
1330
|
+
eligibility.modelEligibleAccountIds = modelEligibleAccountIds;
|
|
1259
1331
|
// #4768: the flagships stay visible and never fail closed, so this is evidence routing may
|
|
1260
1332
|
// ORDER by, not evidence it may refuse on. Read synchronously from rosters discovery has
|
|
1261
1333
|
// already gathered -- no upstream fetch joins the request path for the most commonly
|
|
@@ -1273,6 +1345,7 @@ export async function resolveCodexAuthContext(
|
|
|
1273
1345
|
undefined,
|
|
1274
1346
|
{ excludeAccountIds },
|
|
1275
1347
|
);
|
|
1348
|
+
eligibility.deniedModelAccountIds = deniedModelAccountIds;
|
|
1276
1349
|
const selectionOptions = {
|
|
1277
1350
|
// Temporary switch drain keeps the candidate until the atomic claim rejects
|
|
1278
1351
|
// it. Retained recovery makes main wholly ineligible so pool routing continues.
|
|
@@ -1475,9 +1548,10 @@ export async function resolveCodexAuthContext(
|
|
|
1475
1548
|
if (options.primeCodexPoolQuotas) {
|
|
1476
1549
|
void options.primeCodexPoolQuotas(config, reason).catch(() => {});
|
|
1477
1550
|
} else {
|
|
1478
|
-
import
|
|
1551
|
+
// Tracked from the import on: the prime registers itself only once the module resolves.
|
|
1552
|
+
void trackBackgroundWork(`codex-pool-quota-prime-import:${reason}`, import("./auth-api")
|
|
1479
1553
|
.then(({ primeCodexPoolQuotas }) => primeCodexPoolQuotas(config, reason))
|
|
1480
|
-
.catch(() => {});
|
|
1554
|
+
.catch(() => {}));
|
|
1481
1555
|
}
|
|
1482
1556
|
}
|
|
1483
1557
|
// Snapshot (not just the deadline) so a refused request can report WHY it is cooled:
|
|
@@ -35,6 +35,7 @@ export interface StartupHealthInputs {
|
|
|
35
35
|
shimHealthy: boolean;
|
|
36
36
|
platform: NodeJS.Platform;
|
|
37
37
|
diagnosticStale?: boolean;
|
|
38
|
+
shimActivationPending?: boolean;
|
|
38
39
|
routingAdoption?: RoutingAdoptionEvidence;
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -55,6 +56,8 @@ export interface StartupHealth {
|
|
|
55
56
|
serviceConflict: boolean;
|
|
56
57
|
shimInstalled: boolean;
|
|
57
58
|
shimHealthy: boolean;
|
|
59
|
+
/** Intact PATH-overlay shim this process could not see active; activate it rather than repair it. */
|
|
60
|
+
shimActivationPending?: boolean;
|
|
58
61
|
shimCoverage: ShimCoverage;
|
|
59
62
|
serviceSupported: boolean;
|
|
60
63
|
platform: NodeJS.Platform;
|
|
@@ -171,6 +174,7 @@ export function collectStartupHealth(
|
|
|
171
174
|
serviceSupported: service.supported,
|
|
172
175
|
shimInstalled: shim.installed,
|
|
173
176
|
shimHealthy: shim.healthy,
|
|
177
|
+
...(shim.activationPending ? { shimActivationPending: true } : {}),
|
|
174
178
|
platform: process.platform,
|
|
175
179
|
...(routingAdoption ? { routingAdoption } : {}),
|
|
176
180
|
});
|
|
@@ -480,20 +480,37 @@ function configHasProviderAutoReview(config: Pick<OcxConfig, "providers">): bool
|
|
|
480
480
|
provider.autoReviewModel !== undefined || provider.autoReviewModelOverrides !== undefined);
|
|
481
481
|
}
|
|
482
482
|
|
|
483
|
+
/**
|
|
484
|
+
* The global override selector, when the operator enabled it and named a model.
|
|
485
|
+
*
|
|
486
|
+
* Occupies the same slot as Codex's root `auto_review_model` fallback and wins over it: an
|
|
487
|
+
* enabled Model-settings choice is the operator's explicit pick, while a disabled or blank block
|
|
488
|
+
* leaves the root selector in charge. Provider-scoped stamps keep their precedence separately.
|
|
489
|
+
*/
|
|
490
|
+
export function resolveAutoReviewOverrideSelector(
|
|
491
|
+
config?: Pick<OcxConfig, "autoReviewOverride">,
|
|
492
|
+
): string | undefined {
|
|
493
|
+
const block = config?.autoReviewOverride;
|
|
494
|
+
if (block?.enabled !== true) return undefined;
|
|
495
|
+
const model = typeof block.model === "string" ? block.model.trim() : "";
|
|
496
|
+
return model.length > 0 ? model : undefined;
|
|
497
|
+
}
|
|
498
|
+
|
|
483
499
|
/** Apply the root Codex auto-review selector after the final catalog merge. */
|
|
484
500
|
export function finalizeAutoReviewModelOverride(
|
|
485
501
|
models: RawEntry[] | undefined,
|
|
486
502
|
sourceModels: readonly RawEntry[] = [],
|
|
487
|
-
config?: Pick<OcxConfig, "providers">,
|
|
503
|
+
config?: Pick<OcxConfig, "providers" | "autoReviewOverride">,
|
|
488
504
|
): AutoReviewModelOverrideResult {
|
|
489
505
|
if (models && sourceModels.length > 0) preserveNativeAutoReviewModelOverrides(
|
|
490
506
|
models.filter(entry => !isCodexControlPlaneModel(entry.slug)),
|
|
491
507
|
sourceModels.filter(entry => !isCodexControlPlaneModel(entry.slug)),
|
|
492
508
|
);
|
|
509
|
+
const rootValue = resolveAutoReviewOverrideSelector(config) ?? readConfiguredAutoReviewModel();
|
|
493
510
|
if (config && configHasProviderAutoReview(config)) {
|
|
494
|
-
return applyConfiguredAutoReviewModelOverride(models,
|
|
511
|
+
return applyConfiguredAutoReviewModelOverride(models, rootValue, config, sourceModels);
|
|
495
512
|
}
|
|
496
|
-
return applyAutoReviewModelOverride(models,
|
|
513
|
+
return applyAutoReviewModelOverride(models, rootValue, sourceModels);
|
|
497
514
|
}
|
|
498
515
|
/**
|
|
499
516
|
* Why an account-gated native model stopped being offered, but only when the answer is one the
|