@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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { restoreApprovedCodexProviders } from "../provider-routing";
|
|
1
2
|
import { beginCodexWriteSection, cleanExternalProviderJournal, publishCodexArtifact } from "./config-write-section";
|
|
2
3
|
import { assertCodexHomeOwner, codexHomeIsAbsent, codexHomeOwnerBlocksCompensation, CodexHomeOwnerRefusal, type CodexHomeOwnerRefusalReason } from "../codex-home-owner";
|
|
3
4
|
import { loadConfig } from "../../config";
|
|
@@ -115,6 +116,7 @@ export interface CodexRestoreConfigResult {
|
|
|
115
116
|
| "owned-fields-stripped"
|
|
116
117
|
| "routing-restored-provider-retained"
|
|
117
118
|
| "external-provider-preserved"
|
|
119
|
+
| "approved-provider-restored"
|
|
118
120
|
| "failed";
|
|
119
121
|
message: string;
|
|
120
122
|
retained?: RetainedCodexProviderTable;
|
|
@@ -236,6 +238,26 @@ export function failedHistoryRestoreFromOutcome(
|
|
|
236
238
|
return failedHistoryRestore(undefined, describeHistoryJobFailure(outcome, "restore"));
|
|
237
239
|
}
|
|
238
240
|
|
|
241
|
+
export function finishApprovedProviderRestore(result: CodexNativeRestoreResult, revalidateDesiredState = false): CodexNativeRestoreResult {
|
|
242
|
+
if (!result.success || revalidateDesiredState && shouldSyncCodexOnStart(loadConfig())) return result;
|
|
243
|
+
try {
|
|
244
|
+
if (!restoreApprovedCodexProviders()) return result;
|
|
245
|
+
} catch (error) {
|
|
246
|
+
if (error instanceof CodexHomeOwnerRefusal) throw error;
|
|
247
|
+
const message = `Approved Codex provider URL restoration failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
248
|
+
return { ...result, success: false, message: `${result.message} ${message}`, artifacts: {
|
|
249
|
+
...result.artifacts, config: { ...result.artifacts.config, state: "failed", action: "failed", message },
|
|
250
|
+
} };
|
|
251
|
+
}
|
|
252
|
+
const message = "Approved Codex provider URLs restored; identity, other settings and history preserved.";
|
|
253
|
+
return { ...result, message: result.externalProvider ? message : `${result.message} ${message}`, artifacts: {
|
|
254
|
+
...result.artifacts,
|
|
255
|
+
config: { ...result.artifacts.config, state: result.artifacts.config.state === "partial" ? "partial" : "ok", changed: true,
|
|
256
|
+
action: result.artifacts.config.changed ? result.artifacts.config.action : "approved-provider-restored",
|
|
257
|
+
message: result.artifacts.config.state === "partial" ? `${result.artifacts.config.message} ${message}` : message },
|
|
258
|
+
} };
|
|
259
|
+
}
|
|
260
|
+
|
|
239
261
|
function externalProviderRestoreResult(activeProvider: string): CodexNativeRestoreResult {
|
|
240
262
|
const message = `External Codex provider ${tomlString(activeProvider)} preserved; no native restore was needed.`;
|
|
241
263
|
return {
|
|
@@ -553,7 +575,7 @@ export async function restoreNativeCodexAsync(
|
|
|
553
575
|
if (sibling) return sibling;
|
|
554
576
|
try {
|
|
555
577
|
assertCodexHomeOwner(getCodexHome());
|
|
556
|
-
return await restoreNativeCodexAsyncImpl(options);
|
|
578
|
+
return finishApprovedProviderRestore(await restoreNativeCodexAsyncImpl(options), options.revalidateDesiredState);
|
|
557
579
|
} catch (error) {
|
|
558
580
|
if (error instanceof CodexHomeOwnerRefusal) return homeOwnerRestoreRefusal(error);
|
|
559
581
|
if (!(error instanceof CodexRestoreRefusal)) throw error;
|
|
@@ -782,7 +804,7 @@ export function restoreNativeCodex(
|
|
|
782
804
|
if (sibling) return sibling;
|
|
783
805
|
try {
|
|
784
806
|
assertCodexHomeOwner(getCodexHome());
|
|
785
|
-
return restoreNativeCodexImpl(options);
|
|
807
|
+
return finishApprovedProviderRestore(restoreNativeCodexImpl(options), options.revalidateDesiredState);
|
|
786
808
|
} catch (error) {
|
|
787
809
|
if (error instanceof CodexHomeOwnerRefusal) return homeOwnerRestoreRefusal(error);
|
|
788
810
|
throw error;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isApprovedCodexProviderUrl } from "../provider-routing";
|
|
1
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
3
|
import {
|
|
3
4
|
hasInjectedCodexRouting,
|
|
@@ -54,7 +55,7 @@ export function classifyRoutingEndpoint(value: string): RoutingEndpointKind {
|
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
/** Classify actual routing dependency separately from opencodex ownership. */
|
|
57
|
-
export function classifyCodexRouting(content: string): CodexRoutingKind {
|
|
58
|
+
export function classifyCodexRouting(content: string, approvedProviderUrl?: (id: string, url: string) => boolean): CodexRoutingKind {
|
|
58
59
|
const rootBaseUrl = rootTomlString(content, "openai_base_url");
|
|
59
60
|
if (rootBaseUrl) {
|
|
60
61
|
const endpoint = classifyRoutingEndpoint(rootBaseUrl);
|
|
@@ -74,7 +75,7 @@ export function classifyCodexRouting(content: string): CodexRoutingKind {
|
|
|
74
75
|
if (providerBaseUrl) {
|
|
75
76
|
const endpoint = classifyRoutingEndpoint(providerBaseUrl);
|
|
76
77
|
if (endpoint === "unknown") return "unknown";
|
|
77
|
-
if (rootProvider === "opencodex") return "opencodex-local";
|
|
78
|
+
if (rootProvider === "opencodex" || endpoint === "local" && approvedProviderUrl?.(rootProvider, providerBaseUrl)) return "opencodex-local";
|
|
78
79
|
return endpoint === "local" ? "custom-local" : "custom-remote";
|
|
79
80
|
}
|
|
80
81
|
if (
|
|
@@ -102,7 +103,7 @@ export function getCodexRoutingKind(): CodexRoutingKind {
|
|
|
102
103
|
const path = CODEX_CONFIG_PATH;
|
|
103
104
|
if (!existsSync(path)) return "native";
|
|
104
105
|
try {
|
|
105
|
-
return classifyCodexRouting(readFileSync(path, "utf8"));
|
|
106
|
+
return classifyCodexRouting(readFileSync(path, "utf8"), isApprovedCodexProviderUrl);
|
|
106
107
|
} catch {
|
|
107
108
|
return "unknown";
|
|
108
109
|
}
|
package/src/codex/inject.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { injectApprovedCodexProvider } from "./provider-routing";
|
|
1
2
|
import { beginCodexWriteSection, cleanExternalProviderJournal, publishCodexArtifact } from "./inject/config-write-section";
|
|
2
3
|
import { closeSync, existsSync, openSync, readFileSync, statSync } from "node:fs";
|
|
3
4
|
import { dirname } from "node:path";
|
|
@@ -46,8 +47,8 @@ import {
|
|
|
46
47
|
writeJournal,
|
|
47
48
|
} from "./journal";
|
|
48
49
|
import { HISTORY_RELABEL_STANDS_DOWN } from "./history-provider";
|
|
50
|
+
import { formatApplyHistoryFailure } from "./inject/history-failure";
|
|
49
51
|
import {
|
|
50
|
-
describeHistoryJobFailure,
|
|
51
52
|
deriveCodexHistoryOperation,
|
|
52
53
|
resolveCodexHistoryJobTarget,
|
|
53
54
|
runCodexHistoryJob,
|
|
@@ -142,6 +143,8 @@ export interface CodexInjectResult {
|
|
|
142
143
|
message: string;
|
|
143
144
|
/** False when injection intentionally preserves configuration owned by another provider. */
|
|
144
145
|
configApplied?: false;
|
|
146
|
+
/** True only after a successful configuration transaction changes config.toml bytes. */
|
|
147
|
+
configChanged?: boolean;
|
|
145
148
|
/**
|
|
146
149
|
* Structured read-only history preflight refusal; never parsed from display text.
|
|
147
150
|
*
|
|
@@ -240,6 +243,10 @@ async function injectCodexConfigImpl(
|
|
|
240
243
|
const rawContent = missingConfig ? "" : readFileSync(CODEX_CONFIG_PATH, "utf-8");
|
|
241
244
|
const activeProvider = externalCodexModelProvider(rawContent);
|
|
242
245
|
if (activeProvider) {
|
|
246
|
+
const approved = injectApprovedCodexProvider(activeProvider, routingTarget, options.beforeClientWrite ? () => runClientWriteGuard(options.beforeClientWrite) : undefined, options.validateOnly);
|
|
247
|
+
if (approved) {
|
|
248
|
+
return { success: true, configChanged: approved.configChanged, ...(options.validateOnly ? { configApplied: false as const } : {}), message: `Approved Codex provider ${tomlString(activeProvider)} routes through OpenCodex; provider identity and transport settings preserved.` };
|
|
249
|
+
}
|
|
243
250
|
// A launcher may have journaled before the provider manager took ownership. Never let shutdown
|
|
244
251
|
// replay that stale snapshot over externally managed config.
|
|
245
252
|
if (!options.validateOnly) {
|
|
@@ -258,7 +265,7 @@ async function injectCodexConfigImpl(
|
|
|
258
265
|
: {}),
|
|
259
266
|
message:
|
|
260
267
|
`⚠️ Codex routing NOT injected: config.toml selects the external model_provider ${tomlString(activeProvider)}.\n` +
|
|
261
|
-
` OpenCodex preserves external provider configuration so existing ${tomlString(activeProvider)} session history stays visible.\n` +
|
|
268
|
+
` OpenCodex preserves external provider configuration so existing ${tomlString(activeProvider)} session history stays visible.\n To authorize URL-only management, run ocx codex-provider allow ${tomlString(activeProvider)} and confirm.\n` +
|
|
262
269
|
` Configure that provider for Responses passthrough at ${routingTarget.baseUrl}` +
|
|
263
270
|
`${routingTarget.requiresAdmissionToken ? ` with x-opencodex-api-key from ${routingTarget.tokenEnv}` : ""}.\n` +
|
|
264
271
|
` For direct injection, switch to the built-in openai provider, remove any user-owned root openai_base_url, and rerun 'ocx start'.`,
|
|
@@ -717,6 +724,11 @@ async function injectCodexConfigImpl(
|
|
|
717
724
|
releaseConfigWriteLock(configLock.handle);
|
|
718
725
|
heldConfigWriteLock = undefined;
|
|
719
726
|
}
|
|
727
|
+
// Both write paths have committed, including the coordinator's post-callback checks.
|
|
728
|
+
// Compare against the admitted original, not post-v1-reconcile input: the native
|
|
729
|
+
// feature transition is part of this transaction too. Refusals/rollbacks return
|
|
730
|
+
// above and must never publish this receipt. No filesystem reread after unlock.
|
|
731
|
+
const configChanged = Boolean(missingConfig) || effectivePlan.content !== rawContent;
|
|
720
732
|
// Legacy mode still forward-tags history so re-tagged threads stay listable. Design B needs
|
|
721
733
|
// the opposite: a one-time migration of previously re-tagged threads BACK to openai (restore
|
|
722
734
|
// machinery; cheap no-op when there is nothing to migrate). The client-compaction opt-in keeps
|
|
@@ -799,6 +811,7 @@ async function injectCodexConfigImpl(
|
|
|
799
811
|
if (effectivePlan.keptUserBaseUrl && effectivePlan.keepRootOverrideAlongsideTable) {
|
|
800
812
|
return {
|
|
801
813
|
success: true,
|
|
814
|
+
configChanged,
|
|
802
815
|
...(effectivePlan.nativeSubagentDefaultsWarning ? { nativeSubagentDefaultsWarning: effectivePlan.nativeSubagentDefaultsWarning } : {}),
|
|
803
816
|
...(effectivePlan.historyRelabelRefusal ? { historyPreflightFailureReason: effectivePlan.historyRelabelRefusal } : {}),
|
|
804
817
|
message:
|
|
@@ -817,6 +830,7 @@ async function injectCodexConfigImpl(
|
|
|
817
830
|
if (effectivePlan.keptUserBaseUrl) {
|
|
818
831
|
return {
|
|
819
832
|
success: true,
|
|
833
|
+
configChanged,
|
|
820
834
|
...(effectivePlan.nativeSubagentDefaultsWarning
|
|
821
835
|
? { nativeSubagentDefaultsWarning: effectivePlan.nativeSubagentDefaultsWarning }
|
|
822
836
|
: {}),
|
|
@@ -839,6 +853,7 @@ async function injectCodexConfigImpl(
|
|
|
839
853
|
: `Pointed Codex's built-in openai provider at the opencodex proxy (openai_base_url + realtime sideband override).\n`;
|
|
840
854
|
return {
|
|
841
855
|
success: true,
|
|
856
|
+
configChanged,
|
|
842
857
|
...(effectivePlan.nativeSubagentDefaultsWarning ? { nativeSubagentDefaultsWarning: effectivePlan.nativeSubagentDefaultsWarning } : {}),
|
|
843
858
|
...(effectivePlan.historyRelabelRefusal ? { historyPreflightFailureReason: effectivePlan.historyRelabelRefusal } : {}),
|
|
844
859
|
message:
|
|
@@ -860,31 +875,7 @@ export function getCodexConfigPath(): string {
|
|
|
860
875
|
return CODEX_CONFIG_PATH;
|
|
861
876
|
}
|
|
862
877
|
|
|
863
|
-
|
|
864
|
-
* Frame one failed apply history job honestly.
|
|
865
|
-
*
|
|
866
|
-
* A genuine lock keeps the established deferred/SKIPPED wording; any other
|
|
867
|
-
* reason names itself instead of blaming the Codex app/IDE.
|
|
868
|
-
*/
|
|
869
|
-
export function formatApplyHistoryFailure(outcome: CodexHistoryJobOutcome, legacyMode: boolean): string {
|
|
870
|
-
// A busy database is a deferral no matter which half observed it: the lock
|
|
871
|
-
// contended (blocked/busy), or the worker acquired the lock and then found
|
|
872
|
-
// SQLite busy (failed with a busy history reason). Only those keep the
|
|
873
|
-
// deferred headline; every other failure is a real "NOT changed".
|
|
874
|
-
const busy =
|
|
875
|
-
(outcome.kind === "blocked" && outcome.reason === "busy") ||
|
|
876
|
-
(outcome.kind === "failed" && outcome.historyFailureReason === "busy");
|
|
877
|
-
const partiallyChanged = outcome.kind === "failed"
|
|
878
|
-
&& ((outcome.rows ?? 0) > 0 || (outcome.files ?? 0) > 0);
|
|
879
|
-
const headline = partiallyChanged
|
|
880
|
-
? "Codex resume history changed but did not converge"
|
|
881
|
-
: legacyMode
|
|
882
|
-
? "Codex resume history sync SKIPPED"
|
|
883
|
-
: busy
|
|
884
|
-
? "Codex resume history metadata restore deferred"
|
|
885
|
-
: "Codex resume history NOT changed";
|
|
886
|
-
return ` ⚠️ ${headline}: ${describeHistoryJobFailure(outcome, "apply", legacyMode)}\n`;
|
|
887
|
-
}
|
|
878
|
+
export { formatApplyHistoryFailure };
|
|
888
879
|
|
|
889
880
|
export {
|
|
890
881
|
providerBaseHost,
|
package/src/codex/journal.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { beginCodexWriteSection, publishCodexArtifact } from "./inject/config-write-section";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import type { AtomicWriteHooks } from "../config/atomic-write";
|
|
4
|
-
import { existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
4
|
+
import { existsSync, lstatSync, readFileSync, unlinkSync } from "node:fs";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { atomicWriteFile } from "../config";
|
|
7
7
|
import { assertCodexHomeOwner, codexHomeIsAbsent, CODEX_HOME_JOURNAL_FILE, CodexHomeOwnerRefusal, opencodexHomeForInjection, readCodexHomeJournal, type CodexHomeOwnerRefusalReason } from "./codex-home-owner";
|
|
@@ -347,6 +347,21 @@ export function restoreJournalState(
|
|
|
347
347
|
};
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
+
// A matching byte hash does not authorize following a newly substituted profile link.
|
|
351
|
+
// Refuse the complete pair before restoring config or deleting its recovery journal.
|
|
352
|
+
let profileIsSymlink = false;
|
|
353
|
+
if (profileUnchanged && (!comparison.profileAlreadyOriginal || comparison.originalProfile === null)) {
|
|
354
|
+
try { profileIsSymlink = lstatSync(CODEX_PROFILE_PATH).isSymbolicLink(); }
|
|
355
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; }
|
|
356
|
+
}
|
|
357
|
+
if (profileIsSymlink) {
|
|
358
|
+
return {
|
|
359
|
+
configRestored: comparison.configAlreadyOriginal, profileRestored: false,
|
|
360
|
+
configRewritten: false, profileRewritten: false,
|
|
361
|
+
configChanged: !configUnchanged, profileChanged: false,
|
|
362
|
+
complete: false, unverified: false, profileRestoreFailed: true as const,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
350
365
|
assertCodexHomeOwner(CODEX_HOME);
|
|
351
366
|
let configRestored = comparison.configAlreadyOriginal;
|
|
352
367
|
let profileRestored = comparison.profileAlreadyOriginal;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { OcxConfig } from "../types";
|
|
2
|
+
import { MAIN_CODEX_ACCOUNT_ID } from "./account-id";
|
|
3
|
+
import { codexAccountUsesRemainingQuota } from "./account-use-remaining";
|
|
4
|
+
import { isDeclaredReasoningEffort } from "../reasoning-effort";
|
|
2
5
|
import { getMainPolicyQuota } from "./quota";
|
|
3
6
|
import { MAIN_ACCOUNT_HARD_LOCK_PERCENT, MAIN_ACCOUNT_HARD_LOCK_SHORT_PERCENT, MAIN_ACCOUNT_HARD_LOCK_MIN_PERCENT } from "./quota-types";
|
|
4
7
|
|
|
@@ -13,9 +16,35 @@ export interface MainAccountHardLockStatus {
|
|
|
13
16
|
window?: "short" | "long";
|
|
14
17
|
}
|
|
15
18
|
|
|
16
|
-
type PolicyConfig = Pick<OcxConfig, "codexMainAccountHardLock" | "codexMainAccountHardLockThresholds">;
|
|
19
|
+
type PolicyConfig = Pick<OcxConfig, "codexMainAccountHardLock" | "codexMainAccountHardLockThresholds" | "codexMainAccountHardLockFallback" | "codexUseRemainingQuotaAccountIds">;
|
|
20
|
+
|
|
21
|
+
/** The reroute destination for requests the lock blocks, when one is configured and readable. */
|
|
22
|
+
export interface MainAccountHardLockFallback {
|
|
23
|
+
model: string;
|
|
24
|
+
reasoningEffort?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The configured fallback, or `undefined` when nothing qualifies. The dashboard row keeps
|
|
29
|
+
* the configured model while its switch is off, so `enabled: false` pauses the reroute
|
|
30
|
+
* without forgetting the destination. A malformed hand edit — blank or padded model,
|
|
31
|
+
* undeclared effort — disables the reroute entirely rather than degrading into a
|
|
32
|
+
* half-applied one: the request path must never guess which part of a broken setting to
|
|
33
|
+
* keep. This is the same disable-don't-widen reading the compaction override takes, and
|
|
34
|
+
* the load/write boundaries report the malformation separately.
|
|
35
|
+
*/
|
|
36
|
+
export function resolveMainAccountHardLockFallback(config: PolicyConfig | undefined): MainAccountHardLockFallback | undefined {
|
|
37
|
+
const raw = config?.codexMainAccountHardLockFallback;
|
|
38
|
+
if (!raw || raw.enabled === false) return undefined;
|
|
39
|
+
const model = typeof raw.model === "string" ? raw.model : "";
|
|
40
|
+
if (model.length === 0 || model !== model.trim() || /\s/.test(model)) return undefined;
|
|
41
|
+
if (raw.reasoningEffort !== undefined
|
|
42
|
+
&& (typeof raw.reasoningEffort !== "string" || !isDeclaredReasoningEffort(raw.reasoningEffort))) return undefined;
|
|
43
|
+
return raw.reasoningEffort !== undefined ? { model, reasoningEffort: raw.reasoningEffort } : { model };
|
|
44
|
+
}
|
|
17
45
|
|
|
18
46
|
export function resolveMainAccountHardLockThresholds(config: PolicyConfig | undefined): { short: number; long: number } {
|
|
47
|
+
if (codexAccountUsesRemainingQuota(config, MAIN_CODEX_ACCOUNT_ID)) return { short: 100, long: 100 };
|
|
19
48
|
const valid = (value: unknown, fallback: number): number => typeof value === "number"
|
|
20
49
|
&& Number.isInteger(value) && value >= MAIN_ACCOUNT_HARD_LOCK_MIN_PERCENT && value <= 100 ? value : fallback;
|
|
21
50
|
const long = valid(config?.codexMainAccountHardLockThresholds?.long, MAIN_ACCOUNT_HARD_LOCK_PERCENT);
|
|
@@ -31,6 +31,12 @@ import { advanceCodexCredentialMutationEpoch } from "./credential-mutation-epoch
|
|
|
31
31
|
import { withNativeMainExclusiveClaim } from "./native-main-claim";
|
|
32
32
|
import { resolveNativeProfileContext } from "./native-profile-store";
|
|
33
33
|
import { isNativeMainTrafficBlocked } from "./native-profile-startup";
|
|
34
|
+
import { loadConfig } from "../config";
|
|
35
|
+
import {
|
|
36
|
+
requestCodexAuthStatus,
|
|
37
|
+
resolveNativeMainRefreshMode,
|
|
38
|
+
type CodexAuthStatus,
|
|
39
|
+
} from "./native-main-refresh-mode";
|
|
34
40
|
|
|
35
41
|
export { MAIN_CODEX_ACCOUNT_ID } from "./account-id";
|
|
36
42
|
|
|
@@ -48,13 +54,11 @@ type MainAuthJsonCredential = {
|
|
|
48
54
|
path: string;
|
|
49
55
|
rawSha256: string;
|
|
50
56
|
/**
|
|
51
|
-
* Filesystem identity of the file the hash was taken from
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* hash
|
|
55
|
-
*
|
|
56
|
-
* still the same file, not merely one with the same bytes. `null` when the target could
|
|
57
|
-
* not be stat'ed, which is treated as "cannot prove identity" rather than "matches".
|
|
57
|
+
* Filesystem identity of the file the hash was taken from. It catches a writer that
|
|
58
|
+
* replaces auth.json by rename with identical bytes. It does not see Codex's own writes:
|
|
59
|
+
* Codex rewrites auth.json in place (truncate + write), which keeps dev and ino, so only
|
|
60
|
+
* the content hash notices those, and only once the new bytes are visible (#2999).
|
|
61
|
+
* `null` when the target could not be stat'ed, which fails closed.
|
|
58
62
|
*/
|
|
59
63
|
identity: { dev: number; ino: number } | null;
|
|
60
64
|
root: Record<string, unknown>;
|
|
@@ -66,6 +70,8 @@ type MainAuthJsonCredential = {
|
|
|
66
70
|
|
|
67
71
|
export interface NativeMainRefreshDependencies {
|
|
68
72
|
refreshToken?: (refreshToken: string, options: { signal: AbortSignal }) => Promise<OAuthCredentials>;
|
|
73
|
+
/** Codex-mode seam: one app-server `getAuthStatus` call. Used only when Codex owns the refresh. */
|
|
74
|
+
codexAuthStatus?: (options: { refresh: boolean; signal: AbortSignal }) => Promise<CodexAuthStatus>;
|
|
69
75
|
signal?: AbortSignal;
|
|
70
76
|
/** Metadata-only refresh must not retract a concurrent traffic reauth quarantine. */
|
|
71
77
|
preserveReauth?: boolean;
|
|
@@ -264,10 +270,9 @@ function assertMainAuthJsonSnapshotUnchanged(expected: MainAuthJsonCredential):
|
|
|
264
270
|
if (!current || current.path !== expected.path || current.rawSha256 !== expected.rawSha256) {
|
|
265
271
|
throw new MainAuthJsonChangedDuringRefreshError();
|
|
266
272
|
}
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
// unreadable identity on either side fails closed: unprovable is not the same as equal.
|
|
273
|
+
// Also refuse a rename-based replacement with identical bytes. An unreadable identity on
|
|
274
|
+
// either side fails closed. This narrows the window; rename(2) still replaces
|
|
275
|
+
// unconditionally after this check returns.
|
|
271
276
|
assertMainAuthJsonIdentityUnchanged(expected);
|
|
272
277
|
}
|
|
273
278
|
|
|
@@ -288,9 +293,7 @@ function persistRefreshedMainAuthJson(
|
|
|
288
293
|
assertNotRealCodexHomeUnderTest(resolveCodexHomeDir());
|
|
289
294
|
const accessToken = refreshed.access;
|
|
290
295
|
const refreshToken = refreshed.refresh || expected.refreshToken!;
|
|
291
|
-
const chatgptAccountId = refreshed
|
|
292
|
-
?? extractAccountId(undefined, accessToken)
|
|
293
|
-
?? expected.chatgptAccountId;
|
|
296
|
+
const { chatgptAccountId } = inFlightMainAccountToken(expected, refreshed);
|
|
294
297
|
const tokens = {
|
|
295
298
|
...expected.tokens,
|
|
296
299
|
access_token: accessToken,
|
|
@@ -475,6 +478,12 @@ async function resolveMainAccountToken(
|
|
|
475
478
|
const signal = dependencies.signal
|
|
476
479
|
? AbortSignal.any([dependencies.signal, refreshTimeout])
|
|
477
480
|
: refreshTimeout;
|
|
481
|
+
// An injected exchange is the guarded writer by definition. Otherwise Codex refreshes its own
|
|
482
|
+
// login whenever a supported binary exists, unless the operator chose "opencodex" (#2999).
|
|
483
|
+
const refreshMode = dependencies.refreshToken
|
|
484
|
+
? null
|
|
485
|
+
: await resolveNativeMainRefreshMode(loadConfig());
|
|
486
|
+
if (signal.aborted) throw signal.reason;
|
|
478
487
|
const lockKey = refreshGrantFingerprintForToken(initial.refreshToken);
|
|
479
488
|
// Two locks, because they guard two different things that live in two different
|
|
480
489
|
// homes. `withCodexRefreshFileLock` is keyed on the grant fingerprint and lives
|
|
@@ -508,6 +517,11 @@ async function resolveMainAccountToken(
|
|
|
508
517
|
&& mainAccessTokenFresh(locked.accessToken, Date.now(), MAIN_TOKEN_REFRESH_SKEW_MS)) {
|
|
509
518
|
return { accessToken: locked.accessToken!, chatgptAccountId: locked.chatgptAccountId };
|
|
510
519
|
}
|
|
520
|
+
if (refreshMode?.mode === "codex") {
|
|
521
|
+
const command = refreshMode.command!;
|
|
522
|
+
return refreshThroughCodex(locked, rejectedAccessToken, dependencies, refreshTimeout,
|
|
523
|
+
dependencies.codexAuthStatus ?? (options => requestCodexAuthStatus(command, options)));
|
|
524
|
+
}
|
|
511
525
|
const refresh = dependencies.refreshToken ?? refreshNativeMainGrant;
|
|
512
526
|
let refreshed: OAuthCredentials;
|
|
513
527
|
if (signal.aborted) throw signal.reason;
|
|
@@ -539,7 +553,19 @@ async function resolveMainAccountToken(
|
|
|
539
553
|
// Once the exchange starts, only its own timeout may abort it: a successful
|
|
540
554
|
// rotating grant retires the old refresh token. Save the only live grant before
|
|
541
555
|
// honoring caller cancellation, while retaining the external-writer guards.
|
|
542
|
-
|
|
556
|
+
let result: { accessToken: string; chatgptAccountId: string };
|
|
557
|
+
try {
|
|
558
|
+
result = persistRefreshedMainAuthJson(locked, refreshed);
|
|
559
|
+
} catch (cause) {
|
|
560
|
+
if (!(cause instanceof MainAuthJsonChangedDuringRefreshError)) throw cause;
|
|
561
|
+
// Another writer owns auth.json now (#2999). The exchange already succeeded, so
|
|
562
|
+
// failing the request would waste it. Serve the new access token to this request
|
|
563
|
+
// only: leave the file as the other writer left it, keep nothing as the main
|
|
564
|
+
// credential, and do not retire the grant.
|
|
565
|
+
noteNativeMainRefresh("publish-refused-served-in-flight");
|
|
566
|
+
if (dependencies.signal?.aborted) throw dependencies.signal.reason;
|
|
567
|
+
return inFlightMainAccountToken(locked, refreshed);
|
|
568
|
+
}
|
|
543
569
|
// A permitted in-flight success speaks only for its own profile and grant.
|
|
544
570
|
clearMainRefreshGrantRejection(mainRefreshGrantKey(locked)!);
|
|
545
571
|
if (dependencies.preserveReauth !== true) clearAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID);
|
|
@@ -556,6 +582,64 @@ async function resolveMainAccountToken(
|
|
|
556
582
|
}
|
|
557
583
|
}
|
|
558
584
|
|
|
585
|
+
/** The refreshed bearer for one request whose save was refused; never stored anywhere. */
|
|
586
|
+
function inFlightMainAccountToken(
|
|
587
|
+
expected: MainAuthJsonCredential,
|
|
588
|
+
refreshed: OAuthCredentials,
|
|
589
|
+
): { accessToken: string; chatgptAccountId: string } {
|
|
590
|
+
return {
|
|
591
|
+
accessToken: refreshed.access,
|
|
592
|
+
chatgptAccountId: refreshed.accountId
|
|
593
|
+
?? extractAccountId(undefined, refreshed.access)
|
|
594
|
+
?? expected.chatgptAccountId,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Codex mode (#2999): Codex refreshes and stores its own login; OpenCodex only uses the bearer.
|
|
600
|
+
*
|
|
601
|
+
* auth.json is not read again after the call. In keyring mode Codex deletes the file once the
|
|
602
|
+
* keyring save succeeds, so the RPC answer is the only place the new token exists for us.
|
|
603
|
+
* The call runs on the refresh timeout alone: killing app-server mid-exchange could retire
|
|
604
|
+
* the grant before Codex saves its successor.
|
|
605
|
+
*/
|
|
606
|
+
async function refreshThroughCodex(
|
|
607
|
+
locked: MainAuthJsonCredential,
|
|
608
|
+
rejectedAccessToken: string | undefined,
|
|
609
|
+
dependencies: NativeMainRefreshDependencies,
|
|
610
|
+
refreshTimeout: AbortSignal,
|
|
611
|
+
authStatus: NonNullable<NativeMainRefreshDependencies["codexAuthStatus"]>,
|
|
612
|
+
): Promise<{ accessToken: string; chatgptAccountId: string }> {
|
|
613
|
+
let status: CodexAuthStatus;
|
|
614
|
+
try {
|
|
615
|
+
status = await authStatus({ refresh: true, signal: refreshTimeout });
|
|
616
|
+
} catch (cause) {
|
|
617
|
+
noteNativeMainRefresh("codex-unavailable");
|
|
618
|
+
throw new MainAccountTokenRefreshError("transient", { cause });
|
|
619
|
+
}
|
|
620
|
+
if (status.authMethod === "chatgpt" && !status.authToken) {
|
|
621
|
+
// The only permanent-failure signal the response carries: Codex withholds the token once
|
|
622
|
+
// its refresh was refused for good. The specific OAuth code is not on the wire.
|
|
623
|
+
noteNativeMainRefresh("codex-reauth");
|
|
624
|
+
markMainRefreshGrantRejected(mainRefreshGrantKey(locked)!);
|
|
625
|
+
throw new MainAccountTokenRefreshError("reauth");
|
|
626
|
+
}
|
|
627
|
+
const token = status.authToken;
|
|
628
|
+
if (status.authMethod !== "chatgpt" || !token) {
|
|
629
|
+
noteNativeMainRefresh("codex-no-chatgpt-login");
|
|
630
|
+
throw new MainAccountTokenRefreshError("transient");
|
|
631
|
+
}
|
|
632
|
+
// A transient Codex refresh failure still answers with its cached bearer.
|
|
633
|
+
if (token === rejectedAccessToken || !mainAccessTokenFresh(token, Date.now(), 0)) {
|
|
634
|
+
noteNativeMainRefresh("codex-transient");
|
|
635
|
+
throw new MainAccountTokenRefreshError("transient");
|
|
636
|
+
}
|
|
637
|
+
noteNativeMainRefresh("codex-ok");
|
|
638
|
+
if (dependencies.preserveReauth !== true) clearAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID);
|
|
639
|
+
if (dependencies.signal?.aborted) throw dependencies.signal.reason;
|
|
640
|
+
return { accessToken: token, chatgptAccountId: extractAccountId(undefined, token) ?? locked.chatgptAccountId };
|
|
641
|
+
}
|
|
642
|
+
|
|
559
643
|
/** Refresh the CLI-owned native credential before upstream I/O and publish it atomically. */
|
|
560
644
|
export function getValidMainAccountToken(
|
|
561
645
|
dependencies: NativeMainRefreshDependencies = {},
|