@bitkyc08/opencodex 2.80.0 → 2.82.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 +3 -3
- package/bin/ocx.mjs +166 -73
- package/gui/dist/assets/App-Ds_qL7o8.js +54 -0
- package/gui/dist/assets/Tray-BqHF5Trd.js +1 -0
- package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
- package/gui/dist/assets/index-D3FBCNiP.js +94 -0
- package/gui/dist/assets/quota-summary-DTBC8n7-.js +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/claude-green.svg +1 -0
- package/package.json +3 -2
- package/src/adapters/anthropic-model-contract.ts +8 -2
- package/src/adapters/anthropic.ts +8 -3
- package/src/adapters/codebuddy/mcp-server.ts +2 -25
- package/src/adapters/codebuddy/tool-bridge.ts +1 -7
- package/src/adapters/coding-agent/mcp-server.ts +33 -0
- package/src/adapters/coding-agent/protocol.ts +122 -17
- package/src/adapters/coding-agent/tool-catalog.ts +75 -0
- package/src/adapters/coding-agent/turn.ts +101 -44
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +2 -0
- package/src/adapters/devin/live-models.ts +2 -0
- package/src/adapters/kiro-retry.ts +9 -4
- package/src/adapters/openai-chat/messages.ts +7 -6
- package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
- package/src/adapters/openai-responses/passthrough.ts +9 -4
- package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
- package/src/adapters/physical-send.ts +8 -5
- package/src/adapters/qoder/adapter.ts +41 -6
- package/src/adapters/qoder/scaffold-guard.ts +2 -3
- package/src/adapters/zed.ts +16 -1
- package/src/claude/agents-inject.ts +14 -3
- package/src/claude/context-windows.ts +19 -17
- package/src/claude/desktop-3p.ts +88 -12
- package/src/claude/desktop-profile.ts +2 -1
- package/src/claude/gateway-cache.ts +18 -1
- package/src/claude/inbound-cache-stabilize.ts +4 -9
- package/src/claude/inbound.ts +78 -3
- package/src/claude/intercept/cli-catalog.ts +3 -0
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/claude/intercept/picker-ca-persistence.ts +7 -6
- package/src/claude/intercept/picker-listener.ts +71 -4
- package/src/claude/intercept/picker-models.ts +14 -6
- package/src/claude/intercept/settings.ts +3 -0
- package/src/claude/long-context.ts +66 -0
- package/src/claude/model-info.ts +19 -16
- package/src/claude/outbound.ts +94 -9
- package/src/claude/subagent-model.ts +40 -11
- package/src/cli/account-anthropic-threshold.ts +14 -5
- package/src/cli/account-api.ts +3 -1
- package/src/cli/account-extended.ts +13 -9
- package/src/cli/account-policy-dto.ts +1 -1
- package/src/cli/account-policy.ts +13 -4
- package/src/cli/account.ts +3 -3
- package/src/cli/agent.ts +9 -5
- package/src/cli/aside-profile-recovery.ts +33 -0
- package/src/cli/capabilities-accounts.ts +2 -2
- package/src/cli/capabilities-agents-routing.ts +24 -0
- package/src/cli/capabilities-base.ts +5 -5
- package/src/cli/capabilities-integrations.ts +18 -2
- package/src/cli/cli-path-diagnostics.ts +132 -0
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/desktop-runtime-guidance.ts +65 -0
- package/src/cli/dispatch.ts +15 -1
- package/src/cli/doctor.ts +16 -3
- package/src/cli/help.ts +4 -1
- package/src/cli/index.ts +25 -14
- package/src/cli/integration-aside-sync.ts +2 -1
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/integration-preview.ts +8 -1
- package/src/cli/integrations.ts +48 -6
- package/src/cli/message-args.ts +34 -0
- package/src/cli/message-command.ts +43 -0
- package/src/cli/message-runtime.ts +6 -0
- package/src/cli/observe.ts +3 -1
- package/src/cli/provider.ts +29 -6
- package/src/cli/registry.ts +24 -2
- package/src/cli/resolve.ts +25 -2
- package/src/cli/root.ts +4 -2
- package/src/cli/status.ts +64 -12
- package/src/cli/update-restart-child.ts +4 -2
- package/src/cli/update-restart-home.ts +28 -10
- package/src/cli/update-restart-service-record.ts +116 -0
- package/src/cli/update-restart-supervision.ts +96 -0
- package/src/cli/update-restart.ts +59 -13
- package/src/cli/v2.ts +27 -20
- package/src/cli/version-skew-notice.ts +77 -0
- package/src/clients/config-export/commandcode.ts +131 -0
- package/src/clients/config-export/contracts.ts +12 -0
- package/src/clients/config-export.ts +64 -1
- package/src/clients/lazycodex.ts +1 -1
- package/src/clients/omo-role-models.ts +32 -7
- package/src/codex/auth-api/account-list.ts +11 -3
- package/src/codex/auth-api/account-selection.ts +107 -0
- package/src/codex/auth-api/main-account-probe.ts +25 -8
- package/src/codex/auth-api/pool-mode-gate.ts +33 -9
- package/src/codex/auth-api/pool-quota-probe.ts +15 -1
- package/src/codex/auth-api/routes.ts +11 -49
- package/src/codex/auth-context.ts +20 -1
- package/src/codex/autostart-health.ts +12 -3
- package/src/codex/catalog/auto-review.ts +13 -6
- package/src/codex/catalog/build-entries.ts +33 -7
- package/src/codex/catalog/bundled.ts +155 -26
- package/src/codex/catalog/combo-member.ts +3 -2
- package/src/codex/catalog/control-plane.ts +45 -0
- package/src/codex/catalog/effort.ts +37 -6
- package/src/codex/catalog/gather-capture.ts +10 -3
- package/src/codex/catalog/model-hints.ts +15 -8
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/operator-tiers.ts +38 -0
- package/src/codex/catalog/parsing.ts +2 -1
- package/src/codex/catalog/provider-models.ts +58 -44
- package/src/codex/catalog/retained-sync.ts +7 -5
- package/src/codex/catalog/routed-gather.ts +15 -1
- package/src/codex/catalog-auto-refresh-sources.ts +3 -4
- package/src/codex/catalog-auto-refresh.ts +19 -0
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-home-owner.ts +3 -0
- package/src/codex/config-write-lock.ts +333 -0
- package/src/codex/control-plane-models.ts +6 -0
- package/src/codex/convergence.ts +2 -1
- package/src/codex/features.ts +257 -200
- package/src/codex/inject/config-write-section.ts +34 -0
- package/src/codex/inject/multi-agent-v2.ts +14 -6
- package/src/codex/inject/remove.ts +36 -11
- package/src/codex/inject/restore.ts +85 -20
- package/src/codex/inject-coordination.ts +15 -6
- package/src/codex/inject.ts +62 -20
- package/src/codex/journal.ts +62 -17
- package/src/codex/main-account-cache.ts +43 -0
- package/src/codex/paths.ts +2 -0
- package/src/codex/prepared-home.ts +18 -0
- package/src/codex/prompt-journal.ts +25 -12
- package/src/codex/prompt-layers.ts +49 -26
- package/src/codex/prompt-lock-claim.ts +134 -0
- package/src/codex/prompt-lock-evidence.ts +26 -0
- package/src/codex/prompt-lock-io.ts +16 -0
- package/src/codex/prompt-lock-owner.ts +130 -0
- package/src/codex/prompt-lock.ts +84 -53
- package/src/codex/routing/failure-window.ts +146 -0
- package/src/codex/routing/selection.ts +4 -1
- package/src/codex/routing.ts +13 -17
- package/src/codex/runtime.ts +106 -18
- package/src/codex/shim.ts +45 -19
- package/src/combos/jev-decision-contract.ts +25 -2
- package/src/combos/jev-service-exchange.ts +281 -0
- package/src/combos/jev.ts +34 -219
- package/src/combos/types.ts +1 -1
- package/src/config/atomic-write.ts +1 -1
- package/src/config/codex-ws-reuse-setting.ts +10 -0
- package/src/config/diagnostics.ts +27 -16
- package/src/config/live-reconcile.ts +17 -0
- package/src/config/load-degrade.ts +42 -0
- package/src/config/rebase-provenance.ts +11 -0
- package/src/config/schema/anthropic-account-pool.ts +143 -0
- package/src/config/schema/config-schema.ts +11 -1
- package/src/config/schema/leaf-validators.ts +4 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +566 -298
- package/src/generated/model-metadata.ts +7 -6
- package/src/grok/inject.ts +50 -4
- package/src/grok/sync.ts +2 -1
- package/src/images/loop.ts +30 -11
- package/src/integrations/aside-profiles.ts +10 -3
- package/src/integrations/catalog-refresh.ts +19 -3
- package/src/integrations/mutation-plan.ts +7 -0
- package/src/integrations/registry.ts +8 -0
- package/src/integrations/state.ts +9 -2
- package/src/integrations/writer.ts +17 -5
- package/src/lib/bun-path-runtime.d.mts +32 -0
- package/src/lib/bun-path-runtime.mjs +95 -0
- package/src/lib/bun-runtime-preflight.ts +140 -0
- package/src/lib/desktop-cli-handoff.d.mts +20 -0
- package/src/lib/desktop-cli-handoff.mjs +92 -0
- package/src/lib/desktop-cli-record.d.mts +29 -0
- package/src/lib/desktop-cli-record.mjs +102 -0
- package/src/lib/lab-live-route-production.ts +9 -1
- package/src/lib/request-execution-budget.ts +244 -58
- package/src/lib/spend-pool-alias-validation.ts +79 -0
- package/src/lib/spend-pool-continuity.ts +34 -0
- package/src/lib/spend-reservation-ledger.ts +412 -50
- package/src/lib/state-store-registrations.ts +20 -3
- package/src/lib/synced-state-location.ts +46 -6
- package/src/lib/test-home-guard.ts +87 -1
- package/src/lib/translator-budget.ts +17 -4
- package/src/lib/upstream-retry.ts +17 -5
- package/src/lib/windows-atomic-replace.ts +38 -16
- package/src/lib/windows-elevation.ts +6 -0
- package/src/lib/windows-secret-acl.ts +92 -25
- package/src/lib/winsw.ts +2 -3
- package/src/lib/workflow-budget.ts +53 -11
- package/src/messaging/budget.ts +45 -0
- package/src/messaging/discovery.ts +57 -0
- package/src/messaging/envelope.ts +49 -0
- package/src/messaging/input.ts +37 -0
- package/src/messaging/rpc.ts +196 -0
- package/src/messaging/send.ts +58 -0
- package/src/messaging/socket.ts +72 -0
- package/src/messaging/types.ts +29 -0
- package/src/oauth/anthropic-account-refusal.ts +90 -18
- package/src/oauth/anthropic-account-threshold.ts +8 -2
- package/src/oauth/anthropic-continuity.ts +15 -1
- package/src/oauth/anthropic-model-quota.ts +120 -48
- package/src/oauth/anthropic-model-routes.ts +11 -3
- package/src/oauth/anthropic-oauth-definitions.ts +98 -0
- package/src/oauth/anthropic-pool-config.ts +30 -0
- package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
- package/src/oauth/anthropic-routing.ts +1202 -866
- package/src/oauth/anthropic-send-ownership.ts +41 -0
- package/src/oauth/anthropic.ts +4 -1
- package/src/oauth/chatgpt.ts +113 -41
- package/src/oauth/generic-account-failover.ts +3 -1
- package/src/oauth/health.ts +8 -6
- package/src/oauth/index.ts +96 -175
- package/src/oauth/kiro.ts +22 -9
- package/src/oauth/login-cli.ts +2 -0
- package/src/oauth/model-discovery-auth.ts +43 -0
- package/src/oauth/pool-kernel.ts +11 -5
- package/src/oauth/pool-settings-capability.ts +5 -3
- package/src/oauth/store-anthropic-instance.ts +87 -0
- package/src/oauth/store.ts +68 -10
- package/src/oauth/token-guardian.ts +2 -0
- package/src/oauth/types.ts +2 -0
- package/src/protocols/plan-snapshot.ts +5 -0
- package/src/protocols/settings.ts +20 -5
- package/src/providers/anthropic-instance-id.ts +47 -0
- package/src/providers/anthropic-instance.ts +48 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
- package/src/providers/azure-model-metadata.ts +115 -0
- package/src/providers/derive.ts +23 -0
- package/src/providers/kiro-models.ts +3 -0
- package/src/providers/label.ts +1 -1
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/quota/account-cache.ts +37 -11
- package/src/providers/quota/anthropic-account-quota.ts +93 -0
- package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
- package/src/providers/quota/report-cache.ts +24 -1
- package/src/providers/quota/vendor-probes-key.ts +127 -17
- package/src/providers/quota/vendor-probes-oauth.ts +46 -14
- package/src/providers/quota.ts +23 -33
- package/src/providers/registry/entries-core.ts +35 -30
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/model-seeds.ts +7 -4
- package/src/providers/registry/types.ts +3 -0
- package/src/providers/registry.ts +3 -3
- package/src/responses/reasoning-envelope.ts +29 -1
- package/src/responses/reasoning-replay-cache.ts +17 -1
- package/src/responses/spill-store.ts +20 -3
- package/src/responses/state/metrics.ts +95 -0
- package/src/responses/state/snapshot-codec.ts +6 -0
- package/src/responses/state/spill-failure.ts +12 -0
- package/src/responses/state/spill-queue.ts +13 -5
- package/src/responses/state/unforced-store-false.ts +55 -0
- package/src/responses/state.ts +68 -76
- package/src/responses/task-input.ts +17 -0
- package/src/router.ts +28 -4
- package/src/routing/compatibility/assemble.ts +1 -1
- package/src/routing/quota.ts +7 -3
- package/src/server/auth-cors.ts +5 -0
- package/src/server/background-lifecycle.ts +27 -0
- package/src/server/chat-completions.ts +7 -1
- package/src/server/chat-native.ts +64 -28
- package/src/server/claude-messages.ts +59 -8
- package/src/server/gui-static.ts +38 -4
- package/src/server/inbound-body-admission.ts +1 -0
- package/src/server/index/claude-intercept-lifecycle.ts +3 -1
- package/src/server/index/serve-options.ts +10 -2
- package/src/server/index/spend-ledger-lifecycle.ts +12 -5
- package/src/server/index/startup-warnings.ts +19 -0
- package/src/server/index.ts +5 -2
- package/src/server/inference/context.ts +1 -1
- package/src/server/lifecycle.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +123 -38
- package/src/server/management/anthropic-account-threshold.ts +12 -9
- package/src/server/management/anthropic-pool-settings.ts +72 -0
- package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
- package/src/server/management/codex-agent-role-routes.ts +4 -2
- package/src/server/management/codex-prompt-routes.ts +1 -0
- package/src/server/management/codex-role-auto-assign.ts +13 -2
- package/src/server/management/config-routes.ts +85 -14
- package/src/server/management/context.ts +2 -2
- package/src/server/management/decision-routes.ts +2 -1
- package/src/server/management/integration-routes.ts +25 -2
- package/src/server/management/oauth-account-routes.ts +79 -98
- package/src/server/management/provider-instance-ownership.ts +44 -0
- package/src/server/management/provider-routes.ts +21 -28
- package/src/server/management/route-registry.ts +3 -1
- package/src/server/management/routing-profile-routes.ts +4 -4
- package/src/server/management/shared.ts +3 -3
- package/src/server/management/system-restart.ts +44 -14
- package/src/server/management/system-routes.ts +24 -2
- package/src/server/management/vision-sidecar-options.ts +9 -6
- package/src/server/management/web-search-sidecar-options.ts +51 -2
- package/src/server/management-auth.ts +6 -0
- package/src/server/messages-native-eligibility.ts +3 -2
- package/src/server/messages-native-oauth.ts +91 -45
- package/src/server/messages-native-selector.ts +39 -0
- package/src/server/messages-native.ts +133 -38
- package/src/server/messages-response-headers.ts +14 -0
- package/src/server/request-log-conversation.ts +48 -5
- package/src/server/request-log.ts +36 -2
- package/src/server/responses/adapter-continuation.ts +31 -19
- package/src/server/responses/adapter-dispatch.ts +75 -24
- package/src/server/responses/antigravity-validation-refusal.ts +20 -6
- package/src/server/responses/codex-ws-exchange.ts +17 -2
- package/src/server/responses/codex-ws-pool.ts +100 -13
- package/src/server/responses/codex-ws-wire.ts +9 -0
- package/src/server/responses/combo-requested-effort.ts +3 -1
- package/src/server/responses/compact.ts +25 -8
- package/src/server/responses/compaction-recovery.ts +1 -1
- package/src/server/responses/core-codex-account.ts +22 -1
- package/src/server/responses/core-combo-native.ts +24 -4
- package/src/server/responses/core-combo.ts +38 -25
- package/src/server/responses/core-lifetime.ts +3 -36
- package/src/server/responses/core-normalize.ts +1 -0
- package/src/server/responses/core-options.ts +8 -1
- package/src/server/responses/core-replay.ts +78 -0
- package/src/server/responses/core.ts +3 -3
- package/src/server/responses/fetch-helpers.ts +26 -3
- package/src/server/responses/passthrough-delivery.ts +30 -4
- package/src/server/responses/passthrough-dispatch.ts +39 -9
- package/src/server/responses/request-prepare.ts +41 -5
- package/src/server/responses/request-send-budget.ts +150 -50
- package/src/server/responses/request-spend.ts +325 -33
- package/src/server/responses/request-transport.ts +132 -52
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +13 -3
- package/src/server/responses/sidecar-execution.ts +113 -22
- package/src/server/responses/sidecar-send-budget.ts +58 -0
- package/src/server/responses/ws-upstream.ts +19 -6
- package/src/server/responses-undeclared-tool-guard.ts +5 -1
- package/src/server/startup-health-cache.ts +2 -1
- package/src/server/workflow-refusal.ts +33 -0
- package/src/service/cli.ts +20 -4
- package/src/service/desktop-command-guard.ts +32 -0
- package/src/service/desktop-startup.ts +66 -40
- package/src/service/desktop-supervision.d.mts +26 -0
- package/src/service/desktop-supervision.mjs +140 -0
- package/src/service/managing-cli.ts +1 -0
- package/src/service/orchestration.ts +46 -20
- package/src/service/ownership-mutation-lease.d.mts +1 -1
- package/src/service/ownership-mutation-lease.mjs +5 -7
- package/src/service/repair.ts +25 -9
- package/src/service/state.ts +4 -3
- package/src/service/systemd.ts +17 -8
- package/src/service/windows-ops.ts +61 -22
- package/src/sidecar/anthropic-binding.ts +79 -0
- package/src/sidecar/auth.ts +76 -2
- package/src/storage/policy-job.ts +5 -1
- package/src/storage/policy-worker.ts +3 -1
- package/src/storage/policy.ts +9 -0
- package/src/types/anthropic-account-pool.ts +33 -0
- package/src/types/config.ts +33 -28
- package/src/types/provider.ts +5 -0
- package/src/types/request.ts +5 -0
- package/src/types/tools.ts +7 -0
- package/src/types/wire.ts +1 -0
- package/src/types.ts +4 -0
- package/src/update/index.ts +57 -9
- package/src/update/restart-ownership.ts +10 -3
- package/src/update/runtime-ownership.d.mts +6 -0
- package/src/update/runtime-ownership.mjs +23 -7
- package/src/usage/cost.ts +3 -2
- package/src/usage/expected-prices.ts +66 -14
- package/src/vision/anthropic-describe.ts +16 -3
- package/src/vision/index.ts +3 -0
- package/src/vision/plan.ts +19 -6
- package/src/web-search/alpha-search.ts +7 -1
- package/src/web-search/anthropic-executor.ts +16 -4
- package/src/web-search/index.ts +7 -3
- package/src/web-search/loop.ts +32 -7
- package/src/web-search/passthrough-bridge.ts +4 -1
- package/src/web-search/sidecar-providers.ts +4 -3
- package/gui/dist/assets/App-MbDVfUeC.js +0 -54
- package/gui/dist/assets/Tray-CzkxK82A.js +0 -1
- package/gui/dist/assets/index-UJt6npXk.js +0 -94
- package/gui/dist/assets/quota-summary-DU3sD6Z-.js +0 -1
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { codexAccountUsesCreditsAfterLimit } from "../account-credit-use";
|
|
1
|
+
import { codexAccountUsesCreditsAfterLimit, codexUsageLimitResetAt } from "../account-credit-use";
|
|
2
2
|
import { nextCodexUsageQueryAt, nextQuotaQueryDelay, pruneRemovedCodexPoolUsageAccounts } from "../quota-query-backoff";
|
|
3
3
|
import { CODEX_PRIORITY_FAILBACK_REFRESH_MS } from "../account-priority";
|
|
4
4
|
import { codexQuotaHasFreshUsage } from "../quota-observation-freshness";
|
|
5
5
|
import { getCodexAccountCredential, getValidCodexToken, readCodexAccountRecord } from "../account-store";
|
|
6
|
-
import { getAccountQuota, isCompleteCodexQuotaRecoverySnapshot } from "../quota";
|
|
6
|
+
import { getAccountQuota, getMainPolicyQuota, isCompleteCodexQuotaRecoverySnapshot } from "../quota";
|
|
7
|
+
import { isCodexAccountPaused } from "../account-pause";
|
|
8
|
+
import { hasSpendableCodexCredits } from "../quota-types";
|
|
7
9
|
import { reconcileMainCodexAccountRuntimeState } from "../account-lifecycle";
|
|
8
10
|
import { claimDueCodexQuotaRecoveryProbes, settleCodexQuotaRecoveryProbe } from "../routing";
|
|
9
11
|
import { readCodexTokens } from "../auth-collision";
|
|
@@ -73,17 +75,33 @@ export async function runCodexCooldownRecoveryProbes(config: OcxConfig, now = Da
|
|
|
73
75
|
return cooldownRecoveryInFlight;
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
const MAIN_CREDIT_RENEWAL_AGE_MS = 3 * 60_000;
|
|
79
|
+
|
|
76
80
|
let mainHardLockRecoveryInFlight: Promise<void> | null = null;
|
|
77
81
|
let mainHardLockRecoveryAttempt: { identity: number; credential: number; after: number; delay: number } | undefined;
|
|
78
82
|
|
|
79
83
|
/** Metadata-only recovery on the existing sweep; failures retain the observed policy block. */
|
|
80
84
|
export async function runMainAccountHardLockRecovery(config: OcxConfig): Promise<void> {
|
|
81
85
|
if (mainHardLockRecoveryInFlight) return mainHardLockRecoveryInFlight;
|
|
86
|
+
/** Schedule a real credit observation while leaving actual-time admission unchanged. */
|
|
87
|
+
const creditRecoveryNeeded = (): boolean => {
|
|
88
|
+
const now = Date.now();
|
|
89
|
+
const quota = getMainPolicyQuota();
|
|
90
|
+
const observedAt = quota?.credits?.observedAt;
|
|
91
|
+
// getMainPolicyQuota binds evidence to the observed physical identity. Only renew
|
|
92
|
+
// valid formerly spendable funds; admission retains the actual five-minute clock.
|
|
93
|
+
return codexAccountUsesCreditsAfterLimit(config, MAIN_CODEX_ACCOUNT_ID)
|
|
94
|
+
&& !isCodexAccountPaused(config, MAIN_CODEX_ACCOUNT_ID)
|
|
95
|
+
&& codexUsageLimitResetAt(quota, undefined, now) !== undefined
|
|
96
|
+
&& observedAt !== undefined && observedAt > 0
|
|
97
|
+
&& now - observedAt >= MAIN_CREDIT_RENEWAL_AGE_MS
|
|
98
|
+
&& hasSpendableCodexCredits(quota, observedAt);
|
|
99
|
+
};
|
|
82
100
|
const status = getMainAccountHardLockStatus(config);
|
|
83
|
-
if (status.state !== "blocked") { mainHardLockRecoveryAttempt = undefined; return; }
|
|
101
|
+
if (status.state !== "blocked" && !creditRecoveryNeeded()) { mainHardLockRecoveryAttempt = undefined; return; }
|
|
84
102
|
if (isAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID)) return;
|
|
85
103
|
// A predicted reset is a scheduling hint, never proof that the hard lock can be lifted.
|
|
86
|
-
if (status.resetAt !== undefined && status.resetAt > Date.now()) return;
|
|
104
|
+
if (status.state === "blocked" && status.resetAt !== undefined && status.resetAt > Date.now()) return;
|
|
87
105
|
// Reconcile the owned physical credential before skipping work for an older one.
|
|
88
106
|
reconcileMainCodexAccountRuntimeState();
|
|
89
107
|
const physical = readCodexTokens();
|
|
@@ -98,9 +116,10 @@ export async function runMainAccountHardLockRecovery(config: OcxConfig): Promise
|
|
|
98
116
|
if (!lease) return;
|
|
99
117
|
mainHardLockRecoveryInFlight = (async () => {
|
|
100
118
|
reconcileMainCodexAccountRuntimeState();
|
|
101
|
-
if (getMainAccountHardLockStatus(config).state !== "blocked"
|
|
119
|
+
if ((getMainAccountHardLockStatus(config).state !== "blocked" && !creditRecoveryNeeded())
|
|
102
120
|
|| isAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID)) return;
|
|
103
121
|
const identityGeneration = captureMainAccountIdentityGeneration();
|
|
122
|
+
const passive = getMainAccountHardLockStatus(config).state !== "blocked";
|
|
104
123
|
try {
|
|
105
124
|
// Refresh can require an exclusive credential claim: never hold WHAM's shared
|
|
106
125
|
// claim while obtaining a valid token. The runtime lease spans both operations.
|
|
@@ -111,7 +130,12 @@ export async function runMainAccountHardLockRecovery(config: OcxConfig): Promise
|
|
|
111
130
|
// Native refresh retains its own grant-scoped refusal; global quarantine would outlive it.
|
|
112
131
|
return;
|
|
113
132
|
}
|
|
114
|
-
|
|
133
|
+
const currentStatus = getMainAccountHardLockStatus(config);
|
|
134
|
+
if (isAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID)
|
|
135
|
+
|| !isMainAccountIdentityGenerationLive(identityGeneration)
|
|
136
|
+
|| (passive && !creditRecoveryNeeded())
|
|
137
|
+
|| (currentStatus.state === "blocked" && currentStatus.resetAt !== undefined
|
|
138
|
+
&& currentStatus.resetAt > Date.now())) return;
|
|
115
139
|
const credential = getMainQuotaCredentialGeneration();
|
|
116
140
|
// Reconcile the physical bearer before consulting the previous credential's delay.
|
|
117
141
|
if (previous?.identity === identityGeneration && previous.credential === credential
|
|
@@ -123,7 +147,7 @@ export async function runMainAccountHardLockRecovery(config: OcxConfig): Promise
|
|
|
123
147
|
after: currentQueryAfter };
|
|
124
148
|
return;
|
|
125
149
|
}
|
|
126
|
-
const result = await fetchMainAccountInfoAttempt(true, 1, lease, false, false, false, config);
|
|
150
|
+
const result = await fetchMainAccountInfoAttempt(true, 1, lease, false, false, false, config, { passive });
|
|
127
151
|
// Never charge a replacement credential for a late result from its predecessor.
|
|
128
152
|
if (isMainAccountIdentityGenerationLive(identityGeneration)
|
|
129
153
|
&& credential === getMainQuotaCredentialGeneration()) {
|
|
@@ -134,11 +158,11 @@ export async function runMainAccountHardLockRecovery(config: OcxConfig): Promise
|
|
|
134
158
|
const authStatus = result.quotaRefresh.status === "http_error"
|
|
135
159
|
? result.quotaRefresh.httpStatus : undefined;
|
|
136
160
|
const transientAuth = (authStatus === 401 || authStatus === 403)
|
|
137
|
-
&& !isAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID);
|
|
161
|
+
&& !result.terminalAuthFailure && !isAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID);
|
|
138
162
|
if (transientAuth) mainHardLockRecoveryAttempt = undefined;
|
|
139
163
|
else {
|
|
140
164
|
const delay = nextQuotaQueryDelay(previousDelay || undefined);
|
|
141
|
-
mainHardLockRecoveryAttempt = getMainAccountHardLockStatus(config).state === "blocked"
|
|
165
|
+
mainHardLockRecoveryAttempt = getMainAccountHardLockStatus(config).state === "blocked" || creditRecoveryNeeded()
|
|
142
166
|
? { identity: identityGeneration, credential, delay, after: Math.max(Date.now() + delay, queryAfter) }
|
|
143
167
|
: undefined;
|
|
144
168
|
}
|
|
@@ -495,6 +495,7 @@ export async function fetchPoolAccountQuota(
|
|
|
495
495
|
validatePending = false,
|
|
496
496
|
afterDispatchSequence?: number,
|
|
497
497
|
recoveryProbeNow?: number,
|
|
498
|
+
explicitRefresh = false,
|
|
498
499
|
): Promise<PoolQuotaResult> {
|
|
499
500
|
const existing = getAccountQuota(accountId);
|
|
500
501
|
if (afterDispatchSequence === undefined && !forceRefresh && existing && Date.now() - existing.updatedAt < POOL_CACHE_TTL) {
|
|
@@ -504,10 +505,23 @@ export async function fetchPoolAccountQuota(
|
|
|
504
505
|
credentialGeneration: readCodexAccountRecord(accountId)?.generation,
|
|
505
506
|
};
|
|
506
507
|
}
|
|
508
|
+
const record = readCodexAccountRecord(accountId);
|
|
509
|
+
// The token endpoint already called this grant revoked or expired. Only a re-login or a
|
|
510
|
+
// completed validation rewrites the record and drops the marker, so until then a passive read
|
|
511
|
+
// (dashboard poll, listing, priming, recovery probe) would only POST the dead refresh token again
|
|
512
|
+
// and log the same 401. Report the stored verdict the failed refresh would have produced. An
|
|
513
|
+
// operator's explicit refresh command from any principal, a dashboard validation, and a post-reset
|
|
514
|
+
// readback keep their probe, and a source-linked credential spends no grant of its own, so none
|
|
515
|
+
// of them is held here.
|
|
516
|
+
if (!explicitRefresh && !validatePending && afterDispatchSequence === undefined && record && record.deletedAt == null
|
|
517
|
+
&& record.credential && !record.credential.sourceAuthPath
|
|
518
|
+
&& record.lastCodexValidationTerminal === true && record.lastCodexValidationStatus === "failed") {
|
|
519
|
+
markAccountNeedsReauth(accountId, captureConfigGeneration(), record.generation);
|
|
520
|
+
return { quota: existing ?? null, needsReauth: true, reauthReason: "refresh_failed", credentialGeneration: record.generation };
|
|
521
|
+
}
|
|
507
522
|
// A token refresh may increment the generation (and rotate the refresh token) before WHAM
|
|
508
523
|
// completes. Join a flight whose starting or resolved generation is still current, but let a
|
|
509
524
|
// replacement credential with the same pool id start its own request.
|
|
510
|
-
const record = readCodexAccountRecord(accountId);
|
|
511
525
|
const flights = poolQuotaRefreshInFlight.get(accountId);
|
|
512
526
|
const current = flights && [...flights].find(flight => {
|
|
513
527
|
const generation = flight.state.resolvedCredentialGeneration
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { persistCodexAccountSelection } from "./account-selection";
|
|
1
2
|
import { CODEX_ACCOUNT_LOG_LABEL_RE, codexAccountLogLabel } from "../account-label";
|
|
2
3
|
import { poolQuotaHistoryIdentity, readCodexAccountRecord } from "../account-store";
|
|
3
4
|
import { estimateCodexQuotaCapacity, insufficientCodexCapacity } from "../quota-capacity";
|
|
@@ -7,13 +8,13 @@ import { getAccountQuotaHistory, listAccountQuotas } from "../quota";
|
|
|
7
8
|
import { deleteCodexAccount } from "../account-lifecycle";
|
|
8
9
|
import { isCodexAccountPaused, setCodexAccountPaused } from "../account-pause";
|
|
9
10
|
import { setAllCodexAccountsCreditsAfterLimit, setCodexAccountCreditsAfterLimit } from "../account-credit-use";
|
|
10
|
-
import { clearCodexAccountPin, isCodexAccountPriorityKey, pinnedCodexAccountId,
|
|
11
|
-
import {
|
|
11
|
+
import { clearCodexAccountPin, isCodexAccountPriorityKey, pinnedCodexAccountId, setCodexAccountPriority } from "../account-priority";
|
|
12
|
+
import { codexQuotaScopeForModel, clearCodexAccountCooldown, clearThreadAccountMapForAccount, getEffectiveActiveCodexAccountId, isEffectiveCodexAccountPinned } from "../routing";
|
|
12
13
|
import { DEFAULT_ACCOUNT_PRIORITY, MAX_ACCOUNT_PRIORITY, MIN_ACCOUNT_PRIORITY, normalizeAccountPoolStickyLimit, normalizeCodexAccountPoolStrategy, parseAccountPoolStickyLimit, parseCodexAccountPoolStrategy, parseAccountPriority } from "../pool-rotation";
|
|
13
14
|
import { MAIN_CODEX_ACCOUNT_ID } from "../main-account";
|
|
14
15
|
import { reconcileLiveStateStores } from "../../lib/state-store-registrations";
|
|
15
16
|
import type { OcxConfig } from "../../types";
|
|
16
|
-
import { CODEX_ACCOUNT_ID_RE,
|
|
17
|
+
import { CODEX_ACCOUNT_ID_RE, isSelectableCodexPoolAccount, isValidCodexAccountId } from "../account-id";
|
|
17
18
|
import { isCodexResetCreditOperationId } from "../reset-credit-recovery";
|
|
18
19
|
import { listCodexAuthAccounts, selectFallbackAfterPause, pauseExhaustedCodexAccounts } from "./account-list";
|
|
19
20
|
import { jsonResponse, manualImportDisabledResponse } from "./http";
|
|
@@ -43,6 +44,7 @@ export async function handleCodexAuthAPI(
|
|
|
43
44
|
// required by AGENTS_INSTALL.md. Raw-admin/CLI refreshes remain observational.
|
|
44
45
|
return jsonResponse({ accounts: await listCodexAuthAccounts(config, true, {
|
|
45
46
|
validatePending: principal === "gui-session",
|
|
47
|
+
explicitRefresh: true,
|
|
46
48
|
}) });
|
|
47
49
|
}
|
|
48
50
|
|
|
@@ -246,53 +248,13 @@ export async function handleCodexAuthAPI(
|
|
|
246
248
|
}
|
|
247
249
|
|
|
248
250
|
if (url.pathname === "/api/codex-auth/active" && req.method === "PUT") {
|
|
249
|
-
let body:
|
|
250
|
-
try { body =
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
return jsonResponse({ error: "Remove the legacy __main__ pool row before selecting the Desktop account" }, 409);
|
|
255
|
-
}
|
|
256
|
-
if (body.accountId != null && isCodexAccountPaused(runtimeConfig, targetAccountId)) {
|
|
257
|
-
return jsonResponse({ error: "Account is paused" }, 409);
|
|
258
|
-
}
|
|
259
|
-
if (body.accountId != null && body.accountId !== MAIN_CODEX_ACCOUNT_ID) {
|
|
260
|
-
if (!isValidCodexAccountId(body.accountId)) return jsonResponse({ error: "Invalid account id format" }, 400);
|
|
261
|
-
const exists = (runtimeConfig.codexAccounts ?? [])
|
|
262
|
-
.some(account => isSelectableCodexPoolAccount(account) && account.id === body.accountId);
|
|
263
|
-
if (!exists) return jsonResponse({ error: "Account not found" }, 400);
|
|
264
|
-
if (readCodexAccountRecord(body.accountId)?.codexValidationPending) {
|
|
265
|
-
return jsonResponse({ error: "Account validation is pending. Refresh quota after recovery to validate it." }, 409);
|
|
266
|
-
}
|
|
251
|
+
let body: unknown;
|
|
252
|
+
try { body = await req.json(); } catch { return jsonResponse({ error: "Invalid JSON" }, 400); }
|
|
253
|
+
if (!body || typeof body !== "object" || Array.isArray(body)
|
|
254
|
+
|| !("accountId" in body) || (body.accountId !== null && typeof body.accountId !== "string")) {
|
|
255
|
+
return jsonResponse({ error: "accountId must be an account id or null" }, 400);
|
|
267
256
|
}
|
|
268
|
-
|
|
269
|
-
// "Use this account now" outranks selection order until the account is spent:
|
|
270
|
-
// persisted here rather than in resetCodexRoutingForManualSelection, which is
|
|
271
|
-
// runtime state only. A null id clears the selection instead of making one, so it
|
|
272
|
-
// must release the pin rather than record one: pinning the `targetAccountId`
|
|
273
|
-
// fallback would leave a pin that no effective active account matches, which
|
|
274
|
-
// `isEffectiveCodexAccountPinned` reports as unpinned while the tier filter still
|
|
275
|
-
// honours it as a ceiling — invisibly capping the pool at the main account's tier.
|
|
276
|
-
if (body.accountId == null) clearCodexAccountPin(runtimeConfig);
|
|
277
|
-
else setCodexAccountPin(runtimeConfig, targetAccountId);
|
|
278
|
-
resetCodexRoutingForManualSelection(targetAccountId);
|
|
279
|
-
saveRuntimeConfig(config, runtimeConfig);
|
|
280
|
-
// A pin this route accepts can still be dropped by the very next resolve, and saying
|
|
281
|
-
// nothing about that is what made the setting look ignored (#4521). The checks above
|
|
282
|
-
// refuse an account that cannot be selected at all; this reports the one remaining
|
|
283
|
-
// outcome they do not cover, from the same predicate routing releases on, so the two
|
|
284
|
-
// cannot drift. Absent means the pin survives — additive for existing clients.
|
|
285
|
-
// `appliesImmediately` is unchanged: it answers whether thread affinity was cleared,
|
|
286
|
-
// not whether the pin is durable.
|
|
287
|
-
const pinDrainReason = body.accountId == null
|
|
288
|
-
? undefined
|
|
289
|
-
: codexAccountPinDrainReason(runtimeConfig, targetAccountId);
|
|
290
|
-
return jsonResponse({
|
|
291
|
-
ok: true,
|
|
292
|
-
activeCodexAccountId: body.accountId,
|
|
293
|
-
appliesImmediately: true,
|
|
294
|
-
...(pinDrainReason !== undefined ? { pinDrained: true, pinDrainReason } : {}),
|
|
295
|
-
});
|
|
257
|
+
return persistCodexAccountSelection(getRuntimeConfig(config), body.accountId);
|
|
296
258
|
}
|
|
297
259
|
|
|
298
260
|
if (url.pathname === "/api/codex-auth/active" && req.method === "GET") {
|
|
@@ -81,11 +81,13 @@ import {
|
|
|
81
81
|
import {
|
|
82
82
|
captureMainAccountIdentityGeneration,
|
|
83
83
|
captureMainQuotaWriter,
|
|
84
|
+
captureMainQuotaDispatch,
|
|
84
85
|
getObservedMainQuotaIdentityKey,
|
|
85
86
|
isMainQuotaWriterLive,
|
|
86
87
|
matchesMainQuotaCredential,
|
|
87
88
|
observeMainQuotaCredential,
|
|
88
89
|
type MainQuotaWriter,
|
|
90
|
+
type MainQuotaDispatch,
|
|
89
91
|
} from "./main-account-cache";
|
|
90
92
|
import { CODEX_RESERVE_HELPER_UNSUPPORTED_MESSAGE, isCodexReserveHelperUnsupported, isCodexReserveRequestEligible } from "./loopback-target";
|
|
91
93
|
import type { DataPlaneAdmission } from "../server/auth-cors";
|
|
@@ -275,7 +277,11 @@ export function previewCodexPoolLineage(
|
|
|
275
277
|
}
|
|
276
278
|
|
|
277
279
|
export type CodexAuthContext =
|
|
278
|
-
| {
|
|
280
|
+
| {
|
|
281
|
+
kind: "main"; accountId: null; reserveAuthorization?: MainReserveAuthorization;
|
|
282
|
+
/** Captured for the observed main credential actually selected for upstream. */
|
|
283
|
+
mainQuotaDispatch?: MainQuotaDispatch;
|
|
284
|
+
}
|
|
279
285
|
| {
|
|
280
286
|
kind: "pool";
|
|
281
287
|
accountId: string;
|
|
@@ -1635,6 +1641,14 @@ export class CodexMainSubstitutionUnavailableError extends Error {
|
|
|
1635
1641
|
}
|
|
1636
1642
|
}
|
|
1637
1643
|
|
|
1644
|
+
/** Dispatch identity comes only from an owned observation of the selected credential. */
|
|
1645
|
+
function selectedMainQuotaDispatch(selected: Headers): MainQuotaDispatch | undefined {
|
|
1646
|
+
const bearer = selected.get("authorization")?.replace(/^Bearer\s+/i, "").trim();
|
|
1647
|
+
if (!bearer) return undefined;
|
|
1648
|
+
const accountId = selected.get("chatgpt-account-id") ?? extractAccountId(undefined, bearer);
|
|
1649
|
+
return captureMainQuotaDispatch(bearer, accountId, captureConfigGeneration());
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1638
1652
|
/**
|
|
1639
1653
|
* Build the upstream auth headers for one Codex turn.
|
|
1640
1654
|
*
|
|
@@ -1654,6 +1668,7 @@ export function materializeCodexUpstreamAuth(
|
|
|
1654
1668
|
ctx: CodexAuthContext,
|
|
1655
1669
|
options: CodexAuthMaterializationOptions = {},
|
|
1656
1670
|
): Headers {
|
|
1671
|
+
if (ctx.kind === "main") ctx.mainQuotaDispatch = undefined;
|
|
1657
1672
|
const selected = new Headers();
|
|
1658
1673
|
for (const name of FORWARD_HEADERS) {
|
|
1659
1674
|
const value = headers.get(name);
|
|
@@ -1695,10 +1710,12 @@ export function materializeCodexUpstreamAuth(
|
|
|
1695
1710
|
observeSelectedMainCredential(stored, writer);
|
|
1696
1711
|
assertMainAccountPolicy(options.config);
|
|
1697
1712
|
assertMaterializedReserve(selected, ctx, options);
|
|
1713
|
+
ctx.mainQuotaDispatch = selectedMainQuotaDispatch(selected);
|
|
1698
1714
|
return selected;
|
|
1699
1715
|
}
|
|
1700
1716
|
if (callerMatchesObservedMain(selected)) assertMainAccountPolicy(options.config);
|
|
1701
1717
|
assertMaterializedReserve(selected, ctx, options);
|
|
1718
|
+
if (ctx.kind === "main") ctx.mainQuotaDispatch = selectedMainQuotaDispatch(selected);
|
|
1702
1719
|
return selected;
|
|
1703
1720
|
}
|
|
1704
1721
|
|
|
@@ -1749,6 +1766,7 @@ export async function materializeCodexUpstreamAuthAsync(
|
|
|
1749
1766
|
ctx: CodexAuthContext,
|
|
1750
1767
|
options: CodexAuthMaterializationOptions = {},
|
|
1751
1768
|
): Promise<Headers> {
|
|
1769
|
+
if (ctx.kind === "main") ctx.mainQuotaDispatch = undefined;
|
|
1752
1770
|
if (requiresReserveAuthorization(options.config, options.modelId, options.admission)) {
|
|
1753
1771
|
return materializeReserveUpstreamAuth(headers, ctx, options);
|
|
1754
1772
|
}
|
|
@@ -1776,6 +1794,7 @@ export async function materializeCodexUpstreamAuthAsync(
|
|
|
1776
1794
|
assertMainAccountPolicy(options.config);
|
|
1777
1795
|
// An opt-in enabled during token refresh must not turn a proof-less context into Reserve.
|
|
1778
1796
|
assertMaterializedReserve(selected, ctx, options);
|
|
1797
|
+
ctx.mainQuotaDispatch = selectedMainQuotaDispatch(selected);
|
|
1779
1798
|
return selected;
|
|
1780
1799
|
}
|
|
1781
1800
|
|
|
@@ -60,6 +60,7 @@ export interface StartupHealth {
|
|
|
60
60
|
platform: NodeJS.Platform;
|
|
61
61
|
diagnosticStale: boolean;
|
|
62
62
|
recommendedCommand: string | null;
|
|
63
|
+
recommendedAction?: string | null;
|
|
63
64
|
commands: {
|
|
64
65
|
installService: string;
|
|
65
66
|
repairService: string;
|
|
@@ -91,7 +92,8 @@ export function deriveStartupHealth(inputs: StartupHealthInputs): StartupHealth
|
|
|
91
92
|
// An arbitrary localhost gateway has an independent lifecycle that OCX cannot repair.
|
|
92
93
|
const ownsLocalRouting = inputs.routingKind === "opencodex-local";
|
|
93
94
|
const desktopEffective = (inputs.platform === "darwin" || inputs.platform === "linux") && !inputs.diagnosticStale
|
|
94
|
-
&& inputs.desktop
|
|
95
|
+
&& inputs.desktop !== undefined && (inputs.desktop.owned || inputs.desktop.supervisor !== undefined)
|
|
96
|
+
&& inputs.desktop.loginEnabled
|
|
95
97
|
&& inputs.desktop.running && inputs.desktop.viable;
|
|
96
98
|
const protection: StartupProtection = ownsLocalRouting && inputs.serviceViable
|
|
97
99
|
? "service"
|
|
@@ -105,7 +107,7 @@ export function deriveStartupHealth(inputs: StartupHealthInputs): StartupHealth
|
|
|
105
107
|
: rebootSafe
|
|
106
108
|
? "protected"
|
|
107
109
|
: "at-risk";
|
|
108
|
-
const recommendedCommand = status !== "at-risk" || (ownsLocalRouting && inputs.desktop
|
|
110
|
+
const recommendedCommand = status !== "at-risk" || (ownsLocalRouting && inputs.desktop !== undefined)
|
|
109
111
|
? null
|
|
110
112
|
: inputs.routingKind === "custom-local" || inputs.routingKind === "unknown"
|
|
111
113
|
? COMMANDS.restoreNative
|
|
@@ -117,6 +119,11 @@ export function deriveStartupHealth(inputs: StartupHealthInputs): StartupHealth
|
|
|
117
119
|
// gets the registering command.
|
|
118
120
|
? (inputs.serviceInstalled && !inputs.serviceConflict ? COMMANDS.repairService : COMMANDS.installService)
|
|
119
121
|
: COMMANDS.restoreNative;
|
|
122
|
+
const recommendedAction = status === "at-risk" && ownsLocalRouting && inputs.desktop !== undefined
|
|
123
|
+
? inputs.diagnosticStale || inputs.desktop.loginEnabled
|
|
124
|
+
? "Reopen OpenCodex and check Start at Login."
|
|
125
|
+
: "Turn on Start at Login in the OpenCodex menu so the desktop app starts this proxy after a restart."
|
|
126
|
+
: null;
|
|
120
127
|
return {
|
|
121
128
|
...inputs,
|
|
122
129
|
diagnosticStale: inputs.diagnosticStale ?? false,
|
|
@@ -127,6 +134,7 @@ export function deriveStartupHealth(inputs: StartupHealthInputs): StartupHealth
|
|
|
127
134
|
protection,
|
|
128
135
|
shimCoverage,
|
|
129
136
|
recommendedCommand,
|
|
137
|
+
recommendedAction,
|
|
130
138
|
commands: { ...COMMANDS },
|
|
131
139
|
};
|
|
132
140
|
}
|
|
@@ -211,7 +219,8 @@ function classifyStartupHealthSummary(health: StartupHealth): string {
|
|
|
211
219
|
const command = health.recommendedCommand ?? health.commands.restoreNative;
|
|
212
220
|
if (health.routingKind === "unknown") return `AT RISK after restart (Codex routing could not be verified; run '${command}')`;
|
|
213
221
|
if (health.routingKind === "custom-local") return `AT RISK after restart (custom local gateway lifecycle is not managed by opencodex; run '${command}')`;
|
|
214
|
-
if (health.desktop?.
|
|
222
|
+
if (health.desktop?.supervisor && !health.desktop.loginEnabled && !health.diagnosticStale) return "AT RISK after restart (OpenCodex Desktop runs this proxy, but its Start at Login could not be verified; turn it on in the OpenCodex menu)";
|
|
223
|
+
if (health.desktop) return "AT RISK after restart (desktop startup could not be verified; reopen OpenCodex and check Start at Login)";
|
|
215
224
|
if (health.shimCoverage === "cli-only") return `AT RISK for Codex Desktop after restart (launcher shim covers CLI scripts only; run '${command}')`;
|
|
216
225
|
if (health.serviceConflict) return `AT RISK after restart (background service managers conflict; run '${command}')`;
|
|
217
226
|
if (health.serviceStale) return `AT RISK after restart (background service files are stale; run '${command}')`;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isCodexControlPlaneModel } from "../control-plane-models";
|
|
1
2
|
import { redactSecretString } from "../../lib/redact";
|
|
2
3
|
import type { OcxConfig } from "../../types";
|
|
3
4
|
import { encodeRoutedModelId } from "../../providers/slug-codec";
|
|
@@ -34,6 +35,7 @@ export type AutoReviewModelOverrideResult = "absent" | "applied" | "invalid" | "
|
|
|
34
35
|
|
|
35
36
|
/** True when a catalog row was synthesized by opencodex instead of coming from upstream. */
|
|
36
37
|
function isRoutedCatalogEntry(entry: RawEntry): boolean {
|
|
38
|
+
if (isCodexControlPlaneModel(entry.slug)) return false;
|
|
37
39
|
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
38
40
|
return slug.includes("/")
|
|
39
41
|
|| (typeof entry.description === "string" && entry.description.startsWith("Routed via opencodex → "));
|
|
@@ -57,6 +59,7 @@ function clearAutoReviewOverrideValue(entry: RawEntry): void {
|
|
|
57
59
|
* Returns the stamped values when the observed rows match that shape.
|
|
58
60
|
*/
|
|
59
61
|
function legacyRootStampValues(observedModels: readonly RawEntry[]): ReadonlySet<string> | undefined {
|
|
62
|
+
observedModels = observedModels.filter(entry => !isCodexControlPlaneModel(entry?.slug));
|
|
60
63
|
if (observedModels.some(entry => entry?.[AUTO_REVIEW_ROOT_MARKER] !== undefined)) return undefined;
|
|
61
64
|
const configuredValues = new Set(observedModels.flatMap(entry => {
|
|
62
65
|
const value = entry?.auto_review_model_override;
|
|
@@ -90,7 +93,7 @@ function clearLegacyRootStamps(models: readonly RawEntry[], sourceModels: readon
|
|
|
90
93
|
const legacyStamp = legacyRootStampValues([...models, ...sourceModels]);
|
|
91
94
|
if (legacyStamp === undefined) return;
|
|
92
95
|
for (const entry of models) {
|
|
93
|
-
if (!entry || typeof entry !== "object") continue;
|
|
96
|
+
if (!entry || typeof entry !== "object" || isCodexControlPlaneModel(entry.slug)) continue;
|
|
94
97
|
const current = entry.auto_review_model_override;
|
|
95
98
|
if (entry[AUTO_REVIEW_ROOT_MARKER] === undefined
|
|
96
99
|
&& typeof current === "string" && legacyStamp.has(current)) clearAutoReviewOverrideValue(entry);
|
|
@@ -107,7 +110,7 @@ function clearAutoReviewModelOverride(
|
|
|
107
110
|
): void {
|
|
108
111
|
const legacyStamp = legacyRootStampValues([...models, ...sourceModels]);
|
|
109
112
|
for (const entry of models) {
|
|
110
|
-
if (!entry || typeof entry !== "object") continue;
|
|
113
|
+
if (!entry || typeof entry !== "object" || isCodexControlPlaneModel(entry.slug)) continue;
|
|
111
114
|
const current = entry.auto_review_model_override;
|
|
112
115
|
if (isRoutedCatalogEntry(entry)
|
|
113
116
|
|| (entry[AUTO_REVIEW_ROOT_MARKER] === true || rootAutoReviewStamp(entry) !== undefined)
|
|
@@ -188,6 +191,7 @@ function preserveNativeAutoReviewModelOverrides(
|
|
|
188
191
|
|
|
189
192
|
/** Stamp a root-derived override and mark native rows so later root removal is durable. */
|
|
190
193
|
function stampRootAutoReviewOverride(entry: RawEntry, target: string): void {
|
|
194
|
+
if (isCodexControlPlaneModel(entry.slug)) return;
|
|
191
195
|
if (!isRoutedCatalogEntry(entry)) {
|
|
192
196
|
const previous = rootAutoReviewStamp(entry);
|
|
193
197
|
const current = entry.auto_review_model_override;
|
|
@@ -399,7 +403,7 @@ function applyRootSelectorToRemaining(
|
|
|
399
403
|
): AutoReviewModelOverrideResult {
|
|
400
404
|
const clearRemaining = (): void => {
|
|
401
405
|
for (const entry of models) {
|
|
402
|
-
if (!entry || providerStamped.has(entry)) continue;
|
|
406
|
+
if (!entry || providerStamped.has(entry) || isCodexControlPlaneModel(entry.slug)) continue;
|
|
403
407
|
// Native rows written by releases before the root marker cannot be told apart from upstream
|
|
404
408
|
// values once provider stamps diverge. clearLegacyRootStamps sweeps the ones the legacy
|
|
405
409
|
// uniform signature still recognizes before provider plans land, because provider stamping
|
|
@@ -427,7 +431,7 @@ function applyRootSelectorToRemaining(
|
|
|
427
431
|
return "unresolved";
|
|
428
432
|
}
|
|
429
433
|
for (const entry of models) {
|
|
430
|
-
if (!entry || providerStamped.has(entry)) continue;
|
|
434
|
+
if (!entry || providerStamped.has(entry) || isCodexControlPlaneModel(entry.slug)) continue;
|
|
431
435
|
stampRootAutoReviewOverride(entry, trimmed);
|
|
432
436
|
}
|
|
433
437
|
return "applied";
|
|
@@ -449,7 +453,7 @@ export function applyConfiguredAutoReviewModelOverride(
|
|
|
449
453
|
const { plans, failure } = buildProviderReviewPlans(models, config);
|
|
450
454
|
const providerStamped = new Set<RawEntry>();
|
|
451
455
|
for (const entry of models) {
|
|
452
|
-
if (!entry || typeof entry !== "object") continue;
|
|
456
|
+
if (!entry || typeof entry !== "object" || isCodexControlPlaneModel(entry.slug)) continue;
|
|
453
457
|
const provider = catalogEntryProviderName(entry);
|
|
454
458
|
if (!provider) continue;
|
|
455
459
|
const plan = plans.get(provider);
|
|
@@ -482,7 +486,10 @@ export function finalizeAutoReviewModelOverride(
|
|
|
482
486
|
sourceModels: readonly RawEntry[] = [],
|
|
483
487
|
config?: Pick<OcxConfig, "providers">,
|
|
484
488
|
): AutoReviewModelOverrideResult {
|
|
485
|
-
if (models && sourceModels.length > 0) preserveNativeAutoReviewModelOverrides(
|
|
489
|
+
if (models && sourceModels.length > 0) preserveNativeAutoReviewModelOverrides(
|
|
490
|
+
models.filter(entry => !isCodexControlPlaneModel(entry.slug)),
|
|
491
|
+
sourceModels.filter(entry => !isCodexControlPlaneModel(entry.slug)),
|
|
492
|
+
);
|
|
486
493
|
if (config && configHasProviderAutoReview(config)) {
|
|
487
494
|
return applyConfiguredAutoReviewModelOverride(models, readConfiguredAutoReviewModel(), config, sourceModels);
|
|
488
495
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isCodexControlPlaneModel } from "../control-plane-models";
|
|
2
|
+
import { withCodexControlPlaneRows } from "./control-plane";
|
|
1
3
|
import { CODEX_REASONING_LEVELS, type CodexReasoningLevel } from "../../reasoning-effort";
|
|
2
4
|
import { clearModelCache } from "../model-cache";
|
|
3
5
|
import { routedSlug, slugEquivalenceKey } from "../../providers/slug-codec";
|
|
@@ -46,6 +48,7 @@ import { NATIVE_RESERVE_MODEL } from "./native-models";
|
|
|
46
48
|
import { isReserveCatalogProjection, type ReserveCatalogProjection } from "./reserve";
|
|
47
49
|
import { deriveEntry, finishUpstreamNativeEntry, isExactComboCatalogEntry } from "./derive-entry";
|
|
48
50
|
import { PICKER_ORDER_PRIORITY_BASE, SPAWN_PRIORITY_FIELD } from "./subagent-roster";
|
|
51
|
+
import { preserveOperatorUltraFastTiers } from "./operator-tiers";
|
|
49
52
|
|
|
50
53
|
export interface ObservedCatalogEntryBuildInput {
|
|
51
54
|
readonly template: RawEntry | null;
|
|
@@ -194,7 +197,7 @@ export function buildCatalogEntriesFromObservedState({
|
|
|
194
197
|
const comboPublicSlugs = new Set(goModels
|
|
195
198
|
.filter(model => model.provider === COMBO_NAMESPACE)
|
|
196
199
|
.map(catalogModelSlug));
|
|
197
|
-
for (const slug of gptSlugs) {
|
|
200
|
+
for (const slug of gptSlugs.filter(slug => !isCodexControlPlaneModel(slug))) {
|
|
198
201
|
const native = deriveEntry(template, slug, "OpenAI native model (Codex OAuth passthrough).", 9, undefined, new Set(), openaiContextCap);
|
|
199
202
|
if (rank.has(slug)) native.priority = rank.get(slug)!;
|
|
200
203
|
nativeEntries.push(native);
|
|
@@ -224,7 +227,7 @@ export function buildCatalogEntriesFromObservedState({
|
|
|
224
227
|
const selectorNativeSlugs = accountNativeSlugsBySelector?.get(selector)
|
|
225
228
|
?? accountNativeSlugs
|
|
226
229
|
?? gptSlugs;
|
|
227
|
-
const accountNativeEntries = selectorNativeSlugs.filter(slug => slug !== NATIVE_RESERVE_MODEL).map(slug => (
|
|
230
|
+
const accountNativeEntries = selectorNativeSlugs.filter(slug => slug !== NATIVE_RESERVE_MODEL && !isCodexControlPlaneModel(slug)).map(slug => (
|
|
228
231
|
nativeEntriesBySlug.get(slug)
|
|
229
232
|
?? deriveEntry(template, slug, "OpenAI native model (Codex OAuth passthrough).", 9, undefined, new Set(), openaiContextCap)
|
|
230
233
|
));
|
|
@@ -306,7 +309,7 @@ export function buildCatalogEntriesFromObservedState({
|
|
|
306
309
|
delete entry.prefer_websockets;
|
|
307
310
|
}
|
|
308
311
|
}
|
|
309
|
-
return applyMultiAgentMode(out, multiAgentMode, multiAgentV2Enabled, {
|
|
312
|
+
return applyMultiAgentMode(withCodexControlPlaneRows(out, template ? [template] : [], true, wsEnabled), multiAgentMode, multiAgentV2Enabled, {
|
|
310
313
|
keepNativeChatGptOnV1,
|
|
311
314
|
preserveDefaultMultiAgentVersion: isReserveCatalogProjection,
|
|
312
315
|
});
|
|
@@ -409,12 +412,12 @@ export function mergeCatalogModelsWithNativeRecovery(
|
|
|
409
412
|
): RawEntry[] {
|
|
410
413
|
const merged = [...primaryCatalogModels];
|
|
411
414
|
const recoveredNativeSlugs = new Set(primaryCatalogModels.flatMap(entry => {
|
|
412
|
-
const slug = recoverableNativeSlug(entry);
|
|
415
|
+
const slug = isCodexControlPlaneModel(entry.slug) ? String(entry.slug) : recoverableNativeSlug(entry);
|
|
413
416
|
return slug === null ? [] : [slug];
|
|
414
417
|
}));
|
|
415
418
|
for (const source of nativeRecoverySources) {
|
|
416
419
|
for (const entry of source) {
|
|
417
|
-
const slug = recoverableNativeSlug(entry);
|
|
420
|
+
const slug = isCodexControlPlaneModel(entry.slug) ? String(entry.slug) : recoverableNativeSlug(entry);
|
|
418
421
|
if (slug === null || recoveredNativeSlugs.has(slug)) continue;
|
|
419
422
|
merged.push(structuredClone(entry) as RawEntry);
|
|
420
423
|
recoveredNativeSlugs.add(slug);
|
|
@@ -460,6 +463,7 @@ export function applyFullModelPickerOrder(entries: RawEntry[], order: readonly s
|
|
|
460
463
|
if (!pickerOrder.some(slug => !slug.includes("/"))) return;
|
|
461
464
|
const rankOf = modelPickerRank(pickerOrder);
|
|
462
465
|
for (const entry of entries) {
|
|
466
|
+
if (isCodexControlPlaneModel(entry.slug)) continue;
|
|
463
467
|
const natural = entry[SPAWN_PRIORITY_FIELD] ?? entry.priority ?? 9;
|
|
464
468
|
entry[SPAWN_PRIORITY_FIELD] = natural;
|
|
465
469
|
entry.priority = rankOf(String(entry.slug)) ?? pickerOrder.length + Number(natural);
|
|
@@ -467,6 +471,8 @@ export function applyFullModelPickerOrder(entries: RawEntry[], order: readonly s
|
|
|
467
471
|
}
|
|
468
472
|
|
|
469
473
|
export interface ObservedCatalogMergeInput {
|
|
474
|
+
/** Preserve operator-supplied Ultra Fast only from the exact persisted routed row. */
|
|
475
|
+
readonly ultraFastTier?: boolean;
|
|
470
476
|
readonly catalogModels: readonly RawEntry[];
|
|
471
477
|
readonly baselineCatalogModels: readonly RawEntry[];
|
|
472
478
|
readonly routedEntries: readonly RawEntry[];
|
|
@@ -507,6 +513,7 @@ export interface ObservedCatalogMergeInput {
|
|
|
507
513
|
* accidentally fall back to process-ambient catalog discovery or merge-policy warnings.
|
|
508
514
|
*/
|
|
509
515
|
export function mergeCatalogEntriesFromObservedState({
|
|
516
|
+
ultraFastTier = false,
|
|
510
517
|
catalogModels,
|
|
511
518
|
baselineCatalogModels,
|
|
512
519
|
routedEntries,
|
|
@@ -539,10 +546,12 @@ export function mergeCatalogEntriesFromObservedState({
|
|
|
539
546
|
// Raw catalog rows contain nested arrays/objects that normalization mutates. Detach every row at
|
|
540
547
|
// the observed-core boundary so callers can safely retain evidence objects or repeat the merge.
|
|
541
548
|
const detachedCatalogModels = catalogModels
|
|
549
|
+
.filter(entry => !isCodexControlPlaneModel(entry.slug))
|
|
542
550
|
.map(entry => restoreNativeDisplayName(structuredClone(entry) as RawEntry));
|
|
543
551
|
const detachedBaselineCatalogModels = baselineCatalogModels
|
|
552
|
+
.filter(entry => !isCodexControlPlaneModel(entry.slug))
|
|
544
553
|
.map(entry => restoreNativeDisplayName(structuredClone(entry) as RawEntry));
|
|
545
|
-
const detachedRoutedEntries = routedEntries.map(entry => structuredClone(entry) as RawEntry);
|
|
554
|
+
const detachedRoutedEntries = routedEntries.filter(entry => !isCodexControlPlaneModel(entry.slug)).map(entry => structuredClone(entry) as RawEntry);
|
|
546
555
|
// Track this invocation's generated custom rows, not ownership markers read from disk.
|
|
547
556
|
// Their builder already finalized exact native ladders and ordinary routed mock tiers.
|
|
548
557
|
const freshCustomEntries = new Set(detachedRoutedEntries.filter(entry =>
|
|
@@ -844,6 +853,20 @@ export function mergeCatalogEntriesFromObservedState({
|
|
|
844
853
|
}
|
|
845
854
|
return false;
|
|
846
855
|
});
|
|
856
|
+
// First occurrence wins, matching enforceCatalogSlugUniqueness: a later duplicate row of the
|
|
857
|
+
// same slug is what the writer drops, so it must not decide which declaration survives.
|
|
858
|
+
const operatorRows = new Map<string, RawEntry | undefined>();
|
|
859
|
+
for (const entry of detachedCatalogModels) {
|
|
860
|
+
if (typeof entry.slug !== "string" || operatorRows.has(entry.slug)) continue;
|
|
861
|
+
operatorRows.set(entry.slug, trustedAccountBoundNativeCatalogSlug(entry) === undefined
|
|
862
|
+
&& !isNativeAliasCatalogEntry(entry) && entry.owned_by !== COMBO_NAMESPACE ? entry : undefined);
|
|
863
|
+
}
|
|
864
|
+
for (const entry of finalRoutedEntries) {
|
|
865
|
+
if (typeof entry.slug !== "string" || !entry.slug.includes("/")
|
|
866
|
+
|| isNativeAliasCatalogEntry(entry) || isExactComboCatalogEntry(entry, exactComboSlugs)
|
|
867
|
+
|| trustedAccountBoundNativeCatalogSlug(entry) !== undefined) continue;
|
|
868
|
+
preserveOperatorUltraFastTiers(entry, operatorRows.get(entry.slug), ultraFastTier);
|
|
869
|
+
}
|
|
847
870
|
const finalRoutedEntrySet = new Set(finalRoutedEntries);
|
|
848
871
|
const degradedPreservedCount = preservedRoutedEntries.filter(entry => {
|
|
849
872
|
if (!finalRoutedEntrySet.has(entry)) return false;
|
|
@@ -921,7 +944,10 @@ export function mergeCatalogEntriesFromObservedState({
|
|
|
921
944
|
// clobber a hide flag back to list. Bare ids disable every account clone; qualified ids disable
|
|
922
945
|
// only their generated account row.
|
|
923
946
|
const versionedEntries = applyMultiAgentMode(
|
|
924
|
-
|
|
947
|
+
withCodexControlPlaneRows(
|
|
948
|
+
applyNativeVisibility(mergedEntries, disabledModels, alignedAccountBoundEntries.length > 0, observedNativeSlugs),
|
|
949
|
+
[...catalogModels, ...baselineCatalogModels, ...routedEntries], includeNativeOpenAi, wsEnabled,
|
|
950
|
+
),
|
|
925
951
|
multiAgentMode,
|
|
926
952
|
multiAgentV2Enabled,
|
|
927
953
|
{ keepNativeChatGptOnV1, preserveDefaultMultiAgentVersion: isReserveCatalogProjection, nativeDefaults: nativeMultiAgentDefaults },
|