@bitkyc08/opencodex 2.86.0 → 2.88.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/gui/dist/assets/App-CHC89EXQ.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-DyR9yBee.js → Tray-B5bC7AWL.js} +1 -1
- package/gui/dist/assets/index-B5bsvxor.js +94 -0
- package/gui/dist/assets/index-DPeB9HpV.css +1 -0
- package/gui/dist/assets/quota-summary-D8y2wTsB.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic/beta-allowlist.ts +9 -8
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/passthrough.ts +7 -23
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic-rejection-memo.ts +71 -0
- package/src/adapters/anthropic-replay.ts +135 -0
- package/src/adapters/anthropic-tool-catalog.ts +11 -0
- package/src/adapters/anthropic.ts +151 -33
- 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/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +18 -2
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google-progress-history.ts +53 -0
- package/src/adapters/google.ts +52 -9
- 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-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 +4 -2
- 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 +4 -1
- package/src/advisor/context.ts +16 -4
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +79 -31
- package/src/advisor/settings.ts +11 -2
- package/src/bridge/errors.ts +19 -1
- package/src/bridge/internal.ts +7 -1
- package/src/bridge/response-json.ts +18 -3
- package/src/bridge/sse.ts +59 -8
- package/src/bridge/terminal-refusal.ts +59 -0
- package/src/claude/desktop-3p-library.ts +32 -7
- package/src/claude/desktop-3p.ts +55 -13
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +77 -0
- package/src/claude/inbound.ts +33 -0
- 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/outbound.ts +46 -13
- 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 +17 -3
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +5 -2
- package/src/cli/capabilities-base.ts +38 -10
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- 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 +33 -16
- package/src/cli/dispatch.ts +25 -4
- package/src/cli/doctor.ts +6 -1
- package/src/cli/ensure-desktop-cold-start.ts +60 -7
- package/src/cli/help.ts +3 -2
- package/src/cli/index.ts +1 -1
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/log-history.ts +172 -0
- package/src/cli/loopback-family.ts +31 -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 +28 -8
- 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 +2 -1
- package/src/clients/config-export.ts +18 -44
- 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/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 +2 -2
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +9 -1
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +1 -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 +21 -16
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/parsing.ts +30 -86
- package/src/codex/catalog/provider-models.ts +36 -34
- package/src/codex/catalog/routed-gather.ts +14 -13
- 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 +23 -2
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app-restart.ts +21 -0
- package/src/codex/desktop-compatibility/installed-build.ts +24 -6
- package/src/codex/desktop-compatibility/routing-preflight.ts +1 -1
- package/src/codex/desktop-compatibility/runtime.ts +12 -3
- package/src/codex/desktop-compatibility/windows-key-protection.ts +115 -9
- package/src/codex/features.ts +85 -45
- package/src/codex/inject/history-failure.ts +27 -0
- 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/main-account-hard-lock.ts +30 -1
- 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 +1 -1
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- 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/websocket-activity.ts +161 -0
- package/src/codex/websocket-registry.ts +25 -1
- 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 +33 -6
- package/src/config/live-reconcile.ts +38 -2
- package/src/config/load-degrade.ts +17 -0
- package/src/config/opencode-wire-upgrade.ts +54 -0
- package/src/config/opencode-zen-upgrade.ts +53 -0
- package/src/config/owner-registry.ts +22 -17
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +22 -0
- package/src/config/schema/leaf-validators.ts +38 -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 +657 -297
- package/src/grok/sync.ts +1 -1
- package/src/images/loop.ts +116 -27
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/mutation-plan.ts +1 -0
- package/src/integrations/registry.ts +8 -0
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-upstream-tunnel.ts +18 -53
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +19 -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/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/redact.ts +2 -2
- package/src/lib/request-failure-model.ts +2 -0
- 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 +40 -11
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +184 -109
- 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/encoders/adapter-events.ts +6 -5
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/provider-summary.ts +2 -1
- 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 +3 -0
- package/src/providers/model-rename-migration.ts +2 -2
- 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/opencode-zen-rate-limit.ts +7 -2
- 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 +37 -8
- 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 +50 -5
- package/src/responses/reasoning-envelope.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +74 -6
- 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/router.ts +8 -321
- package/src/routing/analytics.ts +7 -15
- package/src/routing/health.ts +37 -22
- package/src/routing/history/indexer.ts +6 -5
- package/src/routing/history/ingest.ts +6 -2
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +17 -0
- package/src/server/auth-cors.ts +11 -0
- package/src/server/chat-completions.ts +7 -1
- 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 +56 -414
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/live-sideband.ts +3 -2
- 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 +20 -6
- package/src/server/index/startup-warnings.ts +2 -0
- package/src/server/index/websocket-handler.ts +18 -4
- package/src/server/index.ts +8 -8
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/lifecycle.ts +13 -1
- package/src/server/management/advisor-routes.ts +15 -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 +1 -1
- package/src/server/management/config-routes.ts +75 -4
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/model-routes.ts +11 -6
- 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/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 +10 -1
- package/src/server/management-api.ts +3 -1
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +117 -24
- 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 +6 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +47 -19
- package/src/server/responses/adapter-delivery.ts +72 -12
- package/src/server/responses/adapter-dispatch.ts +46 -3
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +73 -41
- package/src/server/responses/anthropic-thinking-recovery.ts +153 -0
- package/src/server/responses/codex-ws-wire.ts +15 -8
- package/src/server/responses/combo-stream-preflight.ts +3 -2
- package/src/server/responses/compact.ts +303 -125
- package/src/server/responses/compaction-recovery.ts +20 -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 +2 -0
- package/src/server/responses/core-combo.ts +121 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +8 -4
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +7 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +23 -3
- package/src/server/responses/core.ts +2 -115
- 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 +3 -1
- package/src/server/responses/passthrough-dispatch.ts +15 -13
- package/src/server/responses/passthrough-execution.ts +3 -2
- package/src/server/responses/request-prepare.ts +77 -12
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +104 -27
- package/src/server/responses/response-effects.ts +2 -0
- package/src/server/responses/response-tool-ownership.ts +21 -0
- package/src/server/responses/run-turn-execution.ts +96 -30
- package/src/server/responses/sidecar-execution.ts +63 -9
- package/src/server/responses/sidecar-send-budget.ts +2 -2
- package/src/server/responses/terminal-guard.ts +2 -16
- package/src/server/responses-undeclared-tool-guard.ts +1 -1
- package/src/server/ws-bridge.ts +2 -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/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +76 -57
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +72 -9
- 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/types/config.ts +64 -3
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +35 -0
- package/src/types/provider.ts +32 -6
- package/src/types/request.ts +31 -2
- package/src/types/wire.ts +31 -37
- package/src/types.ts +2 -0
- package/src/update/npm-invocation.mjs +12 -2
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +3 -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 +30 -11
- package/src/usage/telemetry-contract.ts +1 -0
- 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/anthropic-history.ts +59 -0
- package/src/web-search/loop.ts +236 -73
- package/src/web-search/progress-stream.ts +15 -2
- package/src/web-search/run-turn-loop.ts +9 -1
- package/gui/dist/assets/App-BjlttVLy.js +0 -58
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-BMssP4k3.js +0 -94
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +0 -1
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
resolveTrustedWindowsPowerShellExe,
|
|
15
15
|
resolveTrustedWindowsTaskkillExe,
|
|
16
16
|
} from "../lib/windows-elevation";
|
|
17
|
-
import { readCodexCatalogPath } from "./
|
|
17
|
+
import { readCodexCatalogPath } from "./paths";
|
|
18
18
|
|
|
19
19
|
export const STALE_CODEX_APP_SERVER_HINT =
|
|
20
20
|
"If Codex still shows an older model list, run `ocx sync --restart-codex`: it restarts the long-lived app-server "
|
|
@@ -332,6 +332,32 @@ export function isCodexAppServerCommandLine(commandLine: string, executable?: st
|
|
|
332
332
|
return false;
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
+
/**
|
|
336
|
+
* True when the command line is ANY live Codex session, not just an app-server.
|
|
337
|
+
*
|
|
338
|
+
* The update-blocking contract needs the broader reading: an interactive TUI, a
|
|
339
|
+
* one-shot `codex exec`, or an app-server all have the package's binaries in use,
|
|
340
|
+
* so every subcommand counts. The executable identity stays exactly as strict as
|
|
341
|
+
* {@link isCodexAppServerCommandLine} - only the argv discipline is dropped. A
|
|
342
|
+
* coincidental codex-shaped argument can never open this matcher.
|
|
343
|
+
*/
|
|
344
|
+
export function isCodexSessionCommandLine(commandLine: string, executable?: string): boolean {
|
|
345
|
+
const trimmed = commandLine.trim();
|
|
346
|
+
let tokens = tokenizeCommandLine(trimmed);
|
|
347
|
+
if (executable) {
|
|
348
|
+
if (isCodeModeHostToken(executable) || isCodexExecutableToken(executable)) return true;
|
|
349
|
+
}
|
|
350
|
+
if (tokens.length === 0) return false;
|
|
351
|
+
if (isCodeModeHostProcess(tokens)) return true;
|
|
352
|
+
// Same interpreter unwrap the app-server matcher uses: a codex-shaped token must
|
|
353
|
+
// immediately follow the interpreter so an interpreter flag value is never read
|
|
354
|
+
// as the entrypoint.
|
|
355
|
+
if (isInterpreterToken(tokens[0]!) && tokens.length > 1 && isCodexExecutableToken(tokens[1]!)) {
|
|
356
|
+
tokens = tokens.slice(1);
|
|
357
|
+
}
|
|
358
|
+
return isCodexExecutableToken(tokens[0]!);
|
|
359
|
+
}
|
|
360
|
+
|
|
335
361
|
function parseUnixProcStatusUid(status: string): number | undefined {
|
|
336
362
|
const match = /^Uid:\s+(\d+)/m.exec(status);
|
|
337
363
|
if (!match) return undefined;
|
|
@@ -339,7 +365,7 @@ function parseUnixProcStatusUid(status: string): number | undefined {
|
|
|
339
365
|
return Number.isSafeInteger(uid) ? uid : undefined;
|
|
340
366
|
}
|
|
341
367
|
|
|
342
|
-
function listUnixProcSnapshots(uid: number | undefined): ProcessSnapshot[] {
|
|
368
|
+
function listUnixProcSnapshots(uid: number | undefined, requireComplete = true): ProcessSnapshot[] {
|
|
343
369
|
// procfs missing on a Linux-shaped platform is an enumeration failure, not
|
|
344
370
|
// "no processes" — the staleness collector must not read it as not_running.
|
|
345
371
|
if (!existsSync("/proc")) throw new Error("procfs_unavailable");
|
|
@@ -359,8 +385,10 @@ function listUnixProcSnapshots(uid: number | undefined): ProcessSnapshot[] {
|
|
|
359
385
|
const commandLine = argv.join(" ").trim();
|
|
360
386
|
if (!commandLine) continue;
|
|
361
387
|
out.push({ pid, commandLine, executable: argv[0], uid: processUid });
|
|
362
|
-
} catch {
|
|
363
|
-
|
|
388
|
+
} catch (error) {
|
|
389
|
+
// Complete observations may ignore only disappearance. Restart target
|
|
390
|
+
// enumeration can skip an unreadable PID while retaining verified ones.
|
|
391
|
+
if (requireComplete && (error as NodeJS.ErrnoException)?.code !== "ENOENT") throw error;
|
|
364
392
|
}
|
|
365
393
|
}
|
|
366
394
|
return out;
|
|
@@ -518,10 +546,10 @@ async function listWindowsSnapshotsAsync(): Promise<ProcessSnapshot[]> {
|
|
|
518
546
|
return parseWindowsSnapshotOutput(output);
|
|
519
547
|
}
|
|
520
548
|
|
|
521
|
-
function defaultListSnapshots(platform: NodeJS.Platform, getuid: () => number | undefined): ProcessSnapshot[] {
|
|
549
|
+
function defaultListSnapshots(platform: NodeJS.Platform, getuid: () => number | undefined, requireComplete = true): ProcessSnapshot[] {
|
|
522
550
|
if (platform === "win32") return listWindowsSnapshots();
|
|
523
551
|
if (platform === "darwin") return listDarwinSnapshots(getuid());
|
|
524
|
-
return listUnixProcSnapshots(getuid());
|
|
552
|
+
return listUnixProcSnapshots(getuid(), requireComplete);
|
|
525
553
|
}
|
|
526
554
|
|
|
527
555
|
export interface ListProcessSnapshotsOptions {
|
|
@@ -565,7 +593,7 @@ export function listCodexAppServerProcesses(io: CodexAppServerProcessIo = {}): C
|
|
|
565
593
|
// Restart/kill contract (#476): enumeration failure means no targets —
|
|
566
594
|
// never signal a process we could not verify.
|
|
567
595
|
try {
|
|
568
|
-
snapshots = defaultListSnapshots(platform, getuid);
|
|
596
|
+
snapshots = defaultListSnapshots(platform, getuid, false);
|
|
569
597
|
} catch {
|
|
570
598
|
snapshots = [];
|
|
571
599
|
}
|
|
@@ -581,6 +609,68 @@ export function listCodexAppServerProcesses(io: CodexAppServerProcessIo = {}): C
|
|
|
581
609
|
return matched;
|
|
582
610
|
}
|
|
583
611
|
|
|
612
|
+
/**
|
|
613
|
+
* Enumeration outcome for callers that must DEFER on an unreadable process table.
|
|
614
|
+
*
|
|
615
|
+
* {@link listCodexAppServerProcesses} maps enumeration failure to an empty list because its
|
|
616
|
+
* kill contract must never signal a process it could not verify. The Codex CLI update
|
|
617
|
+
* workflow needs the opposite reading: "no matches" and "could not look" lead to different
|
|
618
|
+
* decisions, and only the first one may allow an install to proceed.
|
|
619
|
+
*/
|
|
620
|
+
export type CodexAppServerProcessScan =
|
|
621
|
+
| Readonly<{ kind: "observed"; processes: readonly CodexAppServerProcess[] }>
|
|
622
|
+
| Readonly<{ kind: "unavailable" }>;
|
|
623
|
+
|
|
624
|
+
function scanProcessSnapshots(
|
|
625
|
+
io: CodexAppServerProcessIo,
|
|
626
|
+
matches: (commandLine: string, executable?: string) => boolean,
|
|
627
|
+
): CodexAppServerProcessScan {
|
|
628
|
+
const platform = io.platform ?? process.platform;
|
|
629
|
+
const getuid = io.getuid ?? (() => {
|
|
630
|
+
try {
|
|
631
|
+
return typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
632
|
+
} catch {
|
|
633
|
+
return undefined;
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
let snapshots: ProcessSnapshot[];
|
|
637
|
+
try {
|
|
638
|
+
snapshots = io.listSnapshots ? io.listSnapshots() : defaultListSnapshots(platform, getuid);
|
|
639
|
+
} catch {
|
|
640
|
+
return Object.freeze({ kind: "unavailable" as const });
|
|
641
|
+
}
|
|
642
|
+
const seen = new Set<number>();
|
|
643
|
+
const matched: CodexAppServerProcess[] = [];
|
|
644
|
+
for (const snapshot of snapshots) {
|
|
645
|
+
if (seen.has(snapshot.pid)) continue;
|
|
646
|
+
if (!matches(snapshot.commandLine, snapshot.executable)) continue;
|
|
647
|
+
seen.add(snapshot.pid);
|
|
648
|
+
matched.push({ pid: snapshot.pid, commandLine: snapshot.commandLine });
|
|
649
|
+
}
|
|
650
|
+
return Object.freeze({ kind: "observed" as const, processes: Object.freeze(matched) });
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/** Same matcher and snapshot sources as {@link listCodexAppServerProcesses}, failing closed. */
|
|
654
|
+
export function scanCodexAppServerProcesses(io: CodexAppServerProcessIo = {}): CodexAppServerProcessScan {
|
|
655
|
+
return scanProcessSnapshots(io, isCodexAppServerCommandLine);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* {@link isCodexSessionCommandLine} over the same snapshot sources, failing closed.
|
|
660
|
+
*
|
|
661
|
+
* Any live Codex session counts - TUI, exec or app-server all hold the installed
|
|
662
|
+
* package open while npm would be replacing it, and a missed TUI was the reported
|
|
663
|
+
* source of a half-written install on Linux. The kill path keeps the narrower
|
|
664
|
+
* {@link isCodexAppServerCommandLine} because a signal must never leave its
|
|
665
|
+
* verified contract; this scan decides whether it is SAFE to install, so it may
|
|
666
|
+
* only err toward deferring.
|
|
667
|
+
*/
|
|
668
|
+
export function scanCodexSessionProcesses(io: CodexAppServerProcessIo = {}): CodexAppServerProcessScan {
|
|
669
|
+
// Shared installs may be held open by another readable user's session. This
|
|
670
|
+
// read-only scan is broader than the unchanged same-user restart/kill path.
|
|
671
|
+
return scanProcessSnapshots({ ...io, getuid: io.getuid ?? (() => undefined) }, isCodexSessionCommandLine);
|
|
672
|
+
}
|
|
673
|
+
|
|
584
674
|
export function formatStaleCodexAppServerWarning(
|
|
585
675
|
processes: readonly { pid: number }[],
|
|
586
676
|
): string {
|
|
@@ -34,6 +34,7 @@ import type {
|
|
|
34
34
|
CodexRestartResponse,
|
|
35
35
|
} from "../lib/codex-restart-contract";
|
|
36
36
|
import { getServerListenPort } from "../server/lifecycle";
|
|
37
|
+
import type { DesktopPickerState } from "./desktop-app/picker-freshness";
|
|
37
38
|
|
|
38
39
|
async function defaultRestartDesktopApp(): Promise<CodexDesktopRestartSummary> {
|
|
39
40
|
const { restartCodexDesktopApp } = await import("./desktop-app-restart");
|
|
@@ -47,9 +48,40 @@ async function defaultRestartDesktopApp(): Promise<CodexDesktopRestartSummary> {
|
|
|
47
48
|
};
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
async function defaultDesktopPickerState(): Promise<DesktopPickerState> {
|
|
52
|
+
const { readCodexDesktopPickerState } = await import("./desktop-app/picker-freshness");
|
|
53
|
+
return readCodexDesktopPickerState();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function defaultCatalogBinding(): Promise<string | null> {
|
|
57
|
+
const { readCodexCatalogBinding } = await import("./desktop-app/picker-freshness");
|
|
58
|
+
return readCodexCatalogBinding();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function readBinding(io: CodexRestartServiceIo): Promise<string | null | undefined> {
|
|
62
|
+
try {
|
|
63
|
+
return await (io.catalogBinding ?? defaultCatalogBinding)();
|
|
64
|
+
} catch {
|
|
65
|
+
// Distinct from null ("binds no catalog"): an unreadable binding cannot prove sameness.
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function readPickerState(io: CodexRestartServiceIo): Promise<DesktopPickerState> {
|
|
71
|
+
try {
|
|
72
|
+
return await (io.desktopPickerState ?? defaultDesktopPickerState)();
|
|
73
|
+
} catch {
|
|
74
|
+
return "unknown";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
50
78
|
export interface CodexRestartServiceIo {
|
|
51
79
|
/** Desktop-restart seam, so a route test cannot terminate the developer's own Codex. */
|
|
52
80
|
restartDesktopApp?: () => Promise<CodexDesktopRestartSummary>;
|
|
81
|
+
/** Whether the desktop picker predates the catalog; decides if the shell must restart. */
|
|
82
|
+
desktopPickerState?: () => Promise<DesktopPickerState>;
|
|
83
|
+
/** The catalog config.toml binds, read before and after the sync to catch a rebinding. */
|
|
84
|
+
catalogBinding?: () => Promise<string | null>;
|
|
53
85
|
/** Process-layer seam, forwarded to every app-server-processes call. */
|
|
54
86
|
processIo?: CodexAppServerProcessIo;
|
|
55
87
|
/** Catalog refresh seam. Resolves to whether a catalog or cache write happened. */
|
|
@@ -133,6 +165,7 @@ async function runCodexRestart(io: CodexRestartServiceIo): Promise<CodexRestartR
|
|
|
133
165
|
// roster; stopping app-servers before the write would hand the replacement the
|
|
134
166
|
// same stale file it just lost.
|
|
135
167
|
let synced = false;
|
|
168
|
+
const bindingBefore = await readBinding(io);
|
|
136
169
|
try {
|
|
137
170
|
const port = (io.listenPort ?? getServerListenPort)();
|
|
138
171
|
synced = await (io.syncCatalog ?? defaultSyncCatalog)(port);
|
|
@@ -146,15 +179,33 @@ async function runCodexRestart(io: CodexRestartServiceIo): Promise<CodexRestartR
|
|
|
146
179
|
(io.resetStateCache ?? resetCodexAppServerCatalogStateCache)();
|
|
147
180
|
const before = (io.collectState ?? collectCodexAppServerCatalogState)(io.processIo ?? {});
|
|
148
181
|
|
|
149
|
-
//
|
|
150
|
-
// app: "no app-server is running" is not a reason to leave a stale roster on screen
|
|
151
|
-
//
|
|
182
|
+
// Decided here, before the early returns, because the model picker lives in the DESKTOP
|
|
183
|
+
// app: "no app-server is running" is not a reason to leave a stale roster on screen.
|
|
184
|
+
//
|
|
185
|
+
// The shell restarts only when its picker predates the catalog (#4761). Quitting it
|
|
186
|
+
// discards composer drafts, unsent picker selections and pending approvals, and when
|
|
187
|
+
// the picker is already current the app-server restart below is all the operator
|
|
188
|
+
// needs: the desktop respawns its app-server child, which rereads config and catalog.
|
|
189
|
+
// An unreadable picker state takes the full restart, the behaviour before #4761.
|
|
152
190
|
//
|
|
153
191
|
// allowHandoff is false and that is deliberate. The handoff waits for the CALLING
|
|
154
192
|
// process to exit, and this runs inside a long-lived proxy that does not, so every
|
|
155
193
|
// handoff started here would sit out its window and fail after the operator had
|
|
156
194
|
// already been told it was handed off. An honest refusal beats that.
|
|
157
|
-
|
|
195
|
+
//
|
|
196
|
+
// A sync that rebinds model_catalog_json without rewriting the catalog bytes leaves the
|
|
197
|
+
// catalog mtime older than the shell, yet the shell loaded the previous binding. Seen
|
|
198
|
+
// across this request's own sync, that is proof of staleness, so it skips the probe.
|
|
199
|
+
const bindingAfter = await readBinding(io);
|
|
200
|
+
const rebound = bindingBefore === undefined || bindingAfter === undefined || bindingBefore !== bindingAfter;
|
|
201
|
+
const picker: DesktopPickerState = rebound ? "stale" : await readPickerState(io);
|
|
202
|
+
const desktop: CodexDesktopRestartSummary = picker === "current" || picker === "not_running"
|
|
203
|
+
? {
|
|
204
|
+
attempted: false, stopped: [], surviving: [], relaunch: "skipped",
|
|
205
|
+
reason: picker === "current" ? "picker_current" : "no_targets",
|
|
206
|
+
pickerState: picker,
|
|
207
|
+
}
|
|
208
|
+
: { ...await (io.restartDesktopApp ?? defaultRestartDesktopApp)(), pickerState: picker };
|
|
158
209
|
|
|
159
210
|
const nothingToDo = (): CodexRestartResponse => ({
|
|
160
211
|
desktopApp: desktop,
|
|
@@ -10,6 +10,7 @@ import { ConfigMutationLockError, mutatePersistedConfig } from "../../config";
|
|
|
10
10
|
import { reconcileMainCodexAccountRuntimeState } from "../account-lifecycle";
|
|
11
11
|
import { isCodexAccountPaused, setCodexAccountPaused } from "../account-pause";
|
|
12
12
|
import { codexAccountUsesCreditsAfterLimit } from "../account-credit-use";
|
|
13
|
+
import { codexAccountUsesRemainingQuota } from "../account-use-remaining";
|
|
13
14
|
import { getCodexAccountPriority } from "../account-priority";
|
|
14
15
|
import { getCodexAccountAutoSwitchThresholdOverride } from "../account-auto-switch";
|
|
15
16
|
import { clearThreadAccountMapForAccount, isCodexAccountPlanExcluded, reconcileCodexActiveAfterExclusion } from "../routing";
|
|
@@ -143,6 +144,7 @@ export function poolAccountDto(
|
|
|
143
144
|
priority,
|
|
144
145
|
autoSwitchThresholdOverride: getCodexAccountAutoSwitchThresholdOverride(config, account.id),
|
|
145
146
|
creditsAfterLimit: codexAccountUsesCreditsAfterLimit(config, account.id),
|
|
147
|
+
useRemainingQuota: codexAccountUsesRemainingQuota(config, account.id),
|
|
146
148
|
quota: quota ? { ...quota } : null,
|
|
147
149
|
...codexCreditsDtoField(config, account.id, poolQuotaHistoryIdentity(account.id) ?? null),
|
|
148
150
|
needsReauth: needsReauth || health.status === "reauth_required",
|
|
@@ -171,6 +173,7 @@ export interface CodexAuthAccountDto {
|
|
|
171
173
|
autoSwitchThresholdOverride: number | null;
|
|
172
174
|
/** False keeps the account out of selection while one of its usage windows is full. */
|
|
173
175
|
creditsAfterLimit?: boolean;
|
|
176
|
+
useRemainingQuota?: boolean;
|
|
174
177
|
quota: (StoredAccountQuota | (Omit<StoredAccountQuota, "updatedAt"> & { updatedAt: number })) | null;
|
|
175
178
|
credits?: CodexCredits;
|
|
176
179
|
needsReauth?: boolean;
|
|
@@ -405,6 +408,7 @@ export async function listCodexAuthAccountsSnapshot(
|
|
|
405
408
|
priority: getCodexAccountPriority(runtimeConfig, MAIN_CODEX_ACCOUNT_ID),
|
|
406
409
|
autoSwitchThresholdOverride: getCodexAccountAutoSwitchThresholdOverride(runtimeConfig, MAIN_CODEX_ACCOUNT_ID),
|
|
407
410
|
creditsAfterLimit: codexAccountUsesCreditsAfterLimit(runtimeConfig, MAIN_CODEX_ACCOUNT_ID),
|
|
411
|
+
useRemainingQuota: codexAccountUsesRemainingQuota(runtimeConfig, MAIN_CODEX_ACCOUNT_ID),
|
|
408
412
|
hasCredential: hasMainCredential,
|
|
409
413
|
needsReauth: mainNeedsReauth,
|
|
410
414
|
...(mainReauthReason !== undefined ? { reauthReason: mainReauthReason } : {}),
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
+
import { observeSignedInCodexIdentity } from "../signed-in-identity";
|
|
1
2
|
import { lstatSync } from "node:fs";
|
|
2
3
|
import type { OcxConfig } from "../../types";
|
|
3
|
-
import { inspectChatGptDomainClaim, extractEmail } from "../../oauth/chatgpt";
|
|
4
|
-
import { MAIN_CODEX_ACCOUNT_ID, isSelectableCodexPoolAccount } from "../account-id";
|
|
5
4
|
import { readCodexTokensResult } from "../auth-collision";
|
|
6
5
|
import type { CodexTokenReadResult } from "../auth-collision";
|
|
7
|
-
import {
|
|
6
|
+
import { linkedCodexAccountIds } from "../account-identity";
|
|
8
7
|
import { resolveCodexHomeDir } from "../home";
|
|
9
8
|
import { tryAcquireNativeMainProfileClaim } from "../native-main-admission";
|
|
10
9
|
import { withNativeMainSharedClaim } from "../native-main-claim";
|
|
@@ -12,7 +11,6 @@ import { MAX_AUTH_BYTES, readBounded, resolveNativeProfileContext } from "../nat
|
|
|
12
11
|
import { NativeProfileError } from "../native-profile-types";
|
|
13
12
|
import { isNativeMainClaimUnavailable, nativeMainProfileBusyResponse } from "./http";
|
|
14
13
|
|
|
15
|
-
type AccountIdentity = { accountId: string; email: string };
|
|
16
14
|
type MainAuth = CodexTokenReadResult | { status: "api-key-only" };
|
|
17
15
|
|
|
18
16
|
/** A valid API-key login has no ChatGPT identity; anything else unreadable stays unknown. */
|
|
@@ -32,50 +30,6 @@ function readMainAuth(authPath: string): MainAuth {
|
|
|
32
30
|
}
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
function identity(accountId: unknown, email: unknown): AccountIdentity | undefined {
|
|
36
|
-
const normalizedEmail = typeof email === "string" ? email.trim().toLowerCase() : undefined;
|
|
37
|
-
return typeof accountId === "string" && accountId.trim() && normalizedEmail
|
|
38
|
-
? { accountId: accountId.trim(), email: normalizedEmail }
|
|
39
|
-
: undefined;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Resolve only existing entries, under native-main ownership, never on the request path. */
|
|
43
|
-
function linkedAccountIds(config: OcxConfig, selectedId: string, main: MainAuth): string[] | undefined {
|
|
44
|
-
// Unknown main identity must not produce a successful but incomplete pause.
|
|
45
|
-
if (main.status === "unreadable" || main.status === "invalid") return undefined;
|
|
46
|
-
const identities = new Map<string, AccountIdentity | undefined>();
|
|
47
|
-
if (main.status === "ok") {
|
|
48
|
-
const tokens = main.tokens;
|
|
49
|
-
if (typeof tokens.access_token !== "string" || typeof tokens.account_id !== "string"
|
|
50
|
-
|| (tokens.id_token !== undefined && typeof tokens.id_token !== "string")) return undefined;
|
|
51
|
-
const claims = [tokens.id_token, tokens.access_token]
|
|
52
|
-
.filter((token): token is string => typeof token === "string")
|
|
53
|
-
.map(inspectChatGptDomainClaim);
|
|
54
|
-
if (claims.some(claim => claim.kind === "invalid")) return undefined;
|
|
55
|
-
const claimIds = claims.flatMap(claim => claim.kind === "valid" ? [claim.accountId] : []);
|
|
56
|
-
// organizations[] describes memberships, not the selected workspace. Never use its first row.
|
|
57
|
-
const accountId = tokens.account_id.trim() || claimIds[0];
|
|
58
|
-
if (claimIds.some(claim => claim !== accountId)) return undefined;
|
|
59
|
-
// Both tokens must name the same member; otherwise the bearer's identity is unknown.
|
|
60
|
-
const emails = new Set([extractEmail(tokens.id_token), extractEmail(undefined, tokens.access_token)]
|
|
61
|
-
.flatMap(email => email?.trim() ? [email.trim().toLowerCase()] : []));
|
|
62
|
-
if (emails.size > 1) return undefined;
|
|
63
|
-
identities.set(MAIN_CODEX_ACCOUNT_ID, identity(accountId, [...emails][0]));
|
|
64
|
-
}
|
|
65
|
-
for (const account of config.codexAccounts ?? []) {
|
|
66
|
-
if (!isSelectableCodexPoolAccount(account)) continue;
|
|
67
|
-
const credential = getCodexAccountCredential(account.id);
|
|
68
|
-
identities.set(account.id, identity(credential?.chatgptAccountId, account.email));
|
|
69
|
-
}
|
|
70
|
-
const selected = identities.get(selectedId);
|
|
71
|
-
// Missing identity evidence never links unrelated logins, including members of one workspace.
|
|
72
|
-
if (!selected) return [selectedId];
|
|
73
|
-
return [selectedId, ...[...identities].flatMap(([id, candidate]) => (
|
|
74
|
-
id !== selectedId && candidate?.accountId === selected.accountId && candidate.email === selected.email
|
|
75
|
-
? [id] : []
|
|
76
|
-
))];
|
|
77
|
-
}
|
|
78
|
-
|
|
79
33
|
/** Hold admission throughout; claim the physical main whenever its home exists. */
|
|
80
34
|
export async function withCodexAccountPauseGroup(
|
|
81
35
|
config: OcxConfig,
|
|
@@ -92,13 +46,15 @@ export async function withCodexAccountPauseGroup(
|
|
|
92
46
|
// Only positive absence permits Pool-only publication. A dangling symlink or access
|
|
93
47
|
// failure is not absence; do not create a home just to coordinate an unused main login.
|
|
94
48
|
if ((error as NodeJS.ErrnoException)?.code !== "ENOENT") return nativeMainProfileBusyResponse();
|
|
95
|
-
const ids =
|
|
49
|
+
const ids = linkedCodexAccountIds(config, selectedId, { status: "missing" });
|
|
50
|
+
if (ids) observeSignedInCodexIdentity();
|
|
96
51
|
return ids ? publish(ids) : nativeMainProfileBusyResponse();
|
|
97
52
|
}
|
|
98
53
|
const context = resolveNativeProfileContext({ codexHome });
|
|
99
54
|
return await withNativeMainSharedClaim(context, async () => {
|
|
100
55
|
const main = readMainAuth(context.authPath);
|
|
101
|
-
const ids =
|
|
56
|
+
const ids = linkedCodexAccountIds(config, selectedId, main);
|
|
57
|
+
if (ids) observeSignedInCodexIdentity(main.status === "ok" ? main.tokens : undefined);
|
|
102
58
|
return ids ? publish(ids) : nativeMainProfileBusyResponse();
|
|
103
59
|
});
|
|
104
60
|
} catch (error) {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { getSignedInCodexIdentity } from "../signed-in-identity";
|
|
2
|
+
import type { OcxConfig } from "../../types";
|
|
3
|
+
import { captureConfigTopLevelRollback } from "../../config/rebase-provenance";
|
|
4
|
+
import { clearCodexAccountPin, pinnedCodexAccountId } from "../account-priority";
|
|
5
|
+
import { clearThreadAccountMapForAccount, previewCodexAccountForRequest } from "../routing";
|
|
6
|
+
import { forgetManualPreference } from "../routing/active-account";
|
|
7
|
+
import { withCodexAccountPauseGroup } from "./account-pause-group";
|
|
8
|
+
import { MAIN_CODEX_ACCOUNT_ID } from "../account-id";
|
|
9
|
+
import { getRuntimeConfig, saveRuntimeConfig } from "./runtime-config";
|
|
10
|
+
import { jsonResponse } from "./http";
|
|
11
|
+
|
|
12
|
+
/** Management owns the native-main claim; publish before releasing any runtime bindings. */
|
|
13
|
+
export async function handleCodexAccountPreservation(req: Request, config: OcxConfig): Promise<Response> {
|
|
14
|
+
let update: { enabled: boolean; applyToBound: boolean } | undefined;
|
|
15
|
+
if (req.method === "PUT") {
|
|
16
|
+
const body: unknown = await req.json().catch(() => null);
|
|
17
|
+
if (!body || typeof body !== "object" || Array.isArray(body)
|
|
18
|
+
|| !("enabled" in body) || typeof body.enabled !== "boolean"
|
|
19
|
+
|| Object.keys(body).some(key => key !== "enabled" && key !== "applyToBound")
|
|
20
|
+
|| ("applyToBound" in body && typeof body.applyToBound !== "boolean")) {
|
|
21
|
+
return jsonResponse({ error: "enabled and optional applyToBound must be booleans" }, 400);
|
|
22
|
+
}
|
|
23
|
+
const applyToBound = "applyToBound" in body && body.applyToBound === true;
|
|
24
|
+
if (applyToBound && !body.enabled) return jsonResponse({ error: "applyToBound requires enabled: true" }, 400);
|
|
25
|
+
update = { enabled: body.enabled, applyToBound };
|
|
26
|
+
}
|
|
27
|
+
const runtime = getRuntimeConfig(config);
|
|
28
|
+
return withCodexAccountPauseGroup(runtime, MAIN_CODEX_ACCOUNT_ID, affectedAccountIds => {
|
|
29
|
+
const releasedPin = update?.applyToBound === true && affectedAccountIds.includes(pinnedCodexAccountId(runtime) ?? "");
|
|
30
|
+
if (update) {
|
|
31
|
+
const rollback = captureConfigTopLevelRollback(runtime, ["codexPreserveSignedInAccount", "activeCodexAccountPinned"]);
|
|
32
|
+
try {
|
|
33
|
+
runtime.codexPreserveSignedInAccount = update.enabled;
|
|
34
|
+
if (releasedPin) clearCodexAccountPin(runtime);
|
|
35
|
+
saveRuntimeConfig(config, runtime);
|
|
36
|
+
} catch (error) { rollback(); throw error; }
|
|
37
|
+
if (update.applyToBound) {
|
|
38
|
+
for (const id of affectedAccountIds) {
|
|
39
|
+
clearThreadAccountMapForAccount(id);
|
|
40
|
+
forgetManualPreference(id);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const enabled = runtime.codexPreserveSignedInAccount === true;
|
|
45
|
+
const nextAccountId = previewCodexAccountForRequest(null, runtime);
|
|
46
|
+
return jsonResponse({
|
|
47
|
+
ok: true, enabled, affectedAccountIds, identityKnown: getSignedInCodexIdentity() !== undefined,
|
|
48
|
+
nextAccountId,
|
|
49
|
+
nextUsesSignedInQuota: nextAccountId !== null && affectedAccountIds.includes(nextAccountId),
|
|
50
|
+
pinnedAccountId: pinnedCodexAccountId(runtime) ?? null,
|
|
51
|
+
bindings: update?.applyToBound ? "released-for-next-request" : "retained",
|
|
52
|
+
releasedPin,
|
|
53
|
+
priorityFailbackEnabled: runtime.codexAccountPriorityFailback === true,
|
|
54
|
+
fallbackConsumesSignedInQuota: true,
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { handleCodexAccountPreservation } from "./account-preservation";
|
|
1
2
|
import { persistCodexAccountSelection } from "./account-selection";
|
|
2
3
|
import { CODEX_ACCOUNT_LOG_LABEL_RE, codexAccountLogLabel } from "../account-label";
|
|
4
|
+
import { handleUseRemainingQuotaRoute } from "./use-remaining-route";
|
|
3
5
|
import { poolQuotaHistoryIdentity, readCodexAccountRecord } from "../account-store";
|
|
4
6
|
import { estimateCodexQuotaCapacity, insufficientCodexCapacity } from "../quota-capacity";
|
|
5
7
|
import type { CodexCapacityResult } from "../quota-capacity";
|
|
@@ -34,6 +36,12 @@ export async function handleCodexAuthAPI(
|
|
|
34
36
|
convergeCodexCatalog?: CodexAuthCatalogConvergence,
|
|
35
37
|
principal?: import("../../server/management-auth").ManagementPrincipal,
|
|
36
38
|
): Promise<Response | null> {
|
|
39
|
+
if (url.pathname === "/api/codex-auth/preserve-signed-in" && (req.method === "GET" || req.method === "PUT")) {
|
|
40
|
+
return handleCodexAccountPreservation(req, config);
|
|
41
|
+
}
|
|
42
|
+
if (url.pathname === "/api/codex-auth/accounts/use-remaining" && (req.method === "GET" || req.method === "PUT")) {
|
|
43
|
+
return handleUseRemainingQuotaRoute(req, url, config);
|
|
44
|
+
}
|
|
37
45
|
if (url.pathname === "/api/codex-auth/accounts" && req.method === "GET") {
|
|
38
46
|
const forceRefresh = url.searchParams.get("refresh") === "1" || url.searchParams.get("refresh") === "true";
|
|
39
47
|
return jsonResponse({ accounts: await listCodexAuthAccounts(config, forceRefresh) });
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { OcxConfig } from "../../types";
|
|
2
|
+
import { captureConfigTopLevelRollback } from "../../config/rebase-provenance";
|
|
3
|
+
import { MAIN_CODEX_ACCOUNT_ID, isValidCodexAccountId } from "../account-id";
|
|
4
|
+
import { getEffectiveCodexAutoSwitchThreshold } from "../account-auto-switch";
|
|
5
|
+
import { codexAccountUsesRemainingQuota, setCodexAccountUseRemainingQuota } from "../account-use-remaining";
|
|
6
|
+
import { getMainAccountHardLockStatus } from "../main-account-hard-lock";
|
|
7
|
+
import { configuredPoolAccount, getRuntimeConfig, saveRuntimeConfig } from "./runtime-config";
|
|
8
|
+
import { jsonResponse } from "./http";
|
|
9
|
+
|
|
10
|
+
/** Called only behind the existing Codex management authentication boundary. */
|
|
11
|
+
export async function handleUseRemainingQuotaRoute(req: Request, url: URL, config: OcxConfig): Promise<Response> {
|
|
12
|
+
const parsed: unknown = req.method === "PUT" ? await req.json().catch(() => null) : { id: url.searchParams.get("id") };
|
|
13
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return jsonResponse({ error: "body must be an object" }, 400);
|
|
14
|
+
const body = parsed as { id?: unknown; useRemainingQuota?: unknown };
|
|
15
|
+
if (typeof body.id !== "string" || (body.id !== MAIN_CODEX_ACCOUNT_ID && !isValidCodexAccountId(body.id))) {
|
|
16
|
+
return jsonResponse({ error: "Invalid account id format" }, 400);
|
|
17
|
+
}
|
|
18
|
+
const runtime = getRuntimeConfig(config);
|
|
19
|
+
if (body.id !== MAIN_CODEX_ACCOUNT_ID && !configuredPoolAccount(runtime, body.id)) return jsonResponse({ error: "Account not found" }, 404);
|
|
20
|
+
if (req.method === "PUT") {
|
|
21
|
+
if (typeof body.useRemainingQuota !== "boolean") return jsonResponse({ error: "useRemainingQuota must be a boolean" }, 400);
|
|
22
|
+
const rollback = captureConfigTopLevelRollback(runtime, ["codexUseRemainingQuotaAccountIds"]);
|
|
23
|
+
try {
|
|
24
|
+
setCodexAccountUseRemainingQuota(runtime, body.id, body.useRemainingQuota);
|
|
25
|
+
saveRuntimeConfig(config, runtime);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
rollback();
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return jsonResponse({ ok: true, id: body.id,
|
|
32
|
+
useRemainingQuota: codexAccountUsesRemainingQuota(runtime, body.id),
|
|
33
|
+
autoSwitchThreshold: getEffectiveCodexAutoSwitchThreshold(runtime, body.id),
|
|
34
|
+
...(body.id === MAIN_CODEX_ACCOUNT_ID ? { mainAccountHardLock: getMainAccountHardLockStatus(runtime) } : {}),
|
|
35
|
+
});
|
|
36
|
+
}
|
package/src/codex/auth-api.ts
CHANGED
|
@@ -33,7 +33,6 @@ export {
|
|
|
33
33
|
} from "./auth-api/pool-mode-gate";
|
|
34
34
|
export { createResetCreditWhamClient } from "./auth-api/reset-credit-service";
|
|
35
35
|
export type { CodexAuthCatalogConvergence } from "./auth-api/login-flow";
|
|
36
|
-
export { handleCodexAuthAPI } from "./auth-api/routes";
|
|
37
36
|
import { getEffectiveActiveCodexAccountId } from "./routing";
|
|
38
37
|
import { MAIN_CODEX_ACCOUNT_ID } from "./main-account";
|
|
39
38
|
import type { OcxConfig } from "../types";
|
|
@@ -632,7 +632,7 @@ export class CodexRecoveryWithheldError extends CodexAccountCooldownError {
|
|
|
632
632
|
|
|
633
633
|
export type CodexAuthPolicyConfig = Readonly<Pick<OcxConfig,
|
|
634
634
|
"codexMainAccountHardLock" | "codexMainAccountHardLockThresholds" | "codexDesktopAuthless" | "runtimeRole" | "pausedCodexAccountIds"
|
|
635
|
-
| "creditCodexAccountIds" | "codexAccounts"
|
|
635
|
+
| "creditCodexAccountIds" | "codexAccounts" | "codexUseRemainingQuotaAccountIds"
|
|
636
636
|
>>;
|
|
637
637
|
|
|
638
638
|
interface CodexAuthMaterializationOptions {
|
|
@@ -783,7 +783,7 @@ function mainCreditsHoldResetAt(config: Pick<OcxConfig, "creditCodexAccountIds">
|
|
|
783
783
|
}
|
|
784
784
|
|
|
785
785
|
function assertMainAccountPolicy(
|
|
786
|
-
config: Pick<OcxConfig, "codexMainAccountHardLock" | "codexMainAccountHardLockThresholds" | "creditCodexAccountIds"> | undefined,
|
|
786
|
+
config: Pick<OcxConfig, "codexMainAccountHardLock" | "codexMainAccountHardLockThresholds" | "creditCodexAccountIds" | "codexUseRemainingQuotaAccountIds"> | undefined,
|
|
787
787
|
): void {
|
|
788
788
|
if (config) {
|
|
789
789
|
const status = getMainAccountHardLockStatus(config);
|
|
@@ -480,20 +480,37 @@ function configHasProviderAutoReview(config: Pick<OcxConfig, "providers">): bool
|
|
|
480
480
|
provider.autoReviewModel !== undefined || provider.autoReviewModelOverrides !== undefined);
|
|
481
481
|
}
|
|
482
482
|
|
|
483
|
+
/**
|
|
484
|
+
* The global override selector, when the operator enabled it and named a model.
|
|
485
|
+
*
|
|
486
|
+
* Occupies the same slot as Codex's root `auto_review_model` fallback and wins over it: an
|
|
487
|
+
* enabled Model-settings choice is the operator's explicit pick, while a disabled or blank block
|
|
488
|
+
* leaves the root selector in charge. Provider-scoped stamps keep their precedence separately.
|
|
489
|
+
*/
|
|
490
|
+
export function resolveAutoReviewOverrideSelector(
|
|
491
|
+
config?: Pick<OcxConfig, "autoReviewOverride">,
|
|
492
|
+
): string | undefined {
|
|
493
|
+
const block = config?.autoReviewOverride;
|
|
494
|
+
if (block?.enabled !== true) return undefined;
|
|
495
|
+
const model = typeof block.model === "string" ? block.model.trim() : "";
|
|
496
|
+
return model.length > 0 ? model : undefined;
|
|
497
|
+
}
|
|
498
|
+
|
|
483
499
|
/** Apply the root Codex auto-review selector after the final catalog merge. */
|
|
484
500
|
export function finalizeAutoReviewModelOverride(
|
|
485
501
|
models: RawEntry[] | undefined,
|
|
486
502
|
sourceModels: readonly RawEntry[] = [],
|
|
487
|
-
config?: Pick<OcxConfig, "providers">,
|
|
503
|
+
config?: Pick<OcxConfig, "providers" | "autoReviewOverride">,
|
|
488
504
|
): AutoReviewModelOverrideResult {
|
|
489
505
|
if (models && sourceModels.length > 0) preserveNativeAutoReviewModelOverrides(
|
|
490
506
|
models.filter(entry => !isCodexControlPlaneModel(entry.slug)),
|
|
491
507
|
sourceModels.filter(entry => !isCodexControlPlaneModel(entry.slug)),
|
|
492
508
|
);
|
|
509
|
+
const rootValue = resolveAutoReviewOverrideSelector(config) ?? readConfiguredAutoReviewModel();
|
|
493
510
|
if (config && configHasProviderAutoReview(config)) {
|
|
494
|
-
return applyConfiguredAutoReviewModelOverride(models,
|
|
511
|
+
return applyConfiguredAutoReviewModelOverride(models, rootValue, config, sourceModels);
|
|
495
512
|
}
|
|
496
|
-
return applyAutoReviewModelOverride(models,
|
|
513
|
+
return applyAutoReviewModelOverride(models, rootValue, sourceModels);
|
|
497
514
|
}
|
|
498
515
|
/**
|
|
499
516
|
* Why an account-gated native model stopped being offered, but only when the answer is one the
|
|
@@ -48,7 +48,7 @@ import { NATIVE_RESERVE_MODEL } from "./native-models";
|
|
|
48
48
|
import { isReserveCatalogProjection, type ReserveCatalogProjection } from "./reserve";
|
|
49
49
|
import { deriveEntry, finishUpstreamNativeEntry, isExactComboCatalogEntry } from "./derive-entry";
|
|
50
50
|
import { PICKER_ORDER_PRIORITY_BASE, SPAWN_PRIORITY_FIELD } from "./subagent-roster";
|
|
51
|
-
import { applyConfiguredUltraFast } from "./configured-tiers";
|
|
51
|
+
import { applyConfiguredUltraFast, configuredUltraFastSlugs } from "./configured-tiers";
|
|
52
52
|
import { applyNativeUltraFastTier } from "./native-ultrafast";
|
|
53
53
|
|
|
54
54
|
export interface ObservedCatalogEntryBuildInput {
|
|
@@ -111,6 +111,14 @@ export function buildCatalogEntries(
|
|
|
111
111
|
accountNativeSlugs,
|
|
112
112
|
accountNativeSlugsBySelector,
|
|
113
113
|
});
|
|
114
|
+
// Live discovery does not run the disk merge; use the same captured declarations and row fences.
|
|
115
|
+
const configured = configuredUltraFastSlugs(goModels);
|
|
116
|
+
for (const entry of entries) {
|
|
117
|
+
if (typeof entry.slug !== "string" || !entry.slug.includes("/")
|
|
118
|
+
|| isNativeAliasCatalogEntry(entry) || isExactComboCatalogEntry(entry, exactComboSlugs)
|
|
119
|
+
|| trustedAccountBoundNativeCatalogSlug(entry) !== undefined) continue;
|
|
120
|
+
applyConfiguredUltraFast(entry, undefined, configured.has(entry.slug), false);
|
|
121
|
+
}
|
|
114
122
|
applyFullModelPickerOrder(entries, modelPickerOrder);
|
|
115
123
|
return entries;
|
|
116
124
|
}
|
|
@@ -100,7 +100,7 @@ import type {
|
|
|
100
100
|
CatalogSourceEvidence,
|
|
101
101
|
CatalogTrustedOpenAiApiPolicySnapshot,
|
|
102
102
|
} from "../convergence-types";
|
|
103
|
-
import { applyProviderConfigHints, configuredAutoCompactTokenLimit, positiveSafeInteger } from "./model-hints";
|
|
103
|
+
import { applyProviderConfigHints, configuredAutoCompactTokenLimit, positiveSafeInteger, withVisionSidecarInput } from "./model-hints";
|
|
104
104
|
|
|
105
105
|
/** Model ids each provider must retain for combo catalog derivation (OCX-111). */
|
|
106
106
|
export function configuredComboTargetModelsByProvider(
|
|
@@ -219,6 +219,7 @@ export function resolveComboCatalogMember(
|
|
|
219
219
|
callerFallback?: ComboCatalogMemberFallback,
|
|
220
220
|
metadataModelIdCaseFold?: boolean,
|
|
221
221
|
metadataConfigDir?: string,
|
|
222
|
+
visionSidecarEnabled = true,
|
|
222
223
|
): CatalogModel | undefined {
|
|
223
224
|
const existing = memberByKey.get(targetKey(target));
|
|
224
225
|
const prov = providers.get(target.provider);
|
|
@@ -227,6 +228,16 @@ export function resolveComboCatalogMember(
|
|
|
227
228
|
// is unusable for catalog derivation while the provider is off.
|
|
228
229
|
if (prov?.disabled === true) return undefined;
|
|
229
230
|
|
|
231
|
+
/** Preserve custom modality overrides while filling missing provider metadata. */
|
|
232
|
+
const withProviderHints = (model: CatalogModel): CatalogModel => {
|
|
233
|
+
const hinted = prov
|
|
234
|
+
? applyProviderConfigHints(target.provider, prov, model, contextCap, metadataModelIdCaseFold, undefined, metadataConfigDir, visionSidecarEnabled)
|
|
235
|
+
: model;
|
|
236
|
+
return model.catalogKind === CODEX_CUSTOM_MODEL_CATALOG_KIND && model.inputModalities !== undefined
|
|
237
|
+
? withVisionSidecarInput({ ...hinted, inputModalities: [...model.inputModalities] }, prov, visionSidecarEnabled)
|
|
238
|
+
: hinted;
|
|
239
|
+
};
|
|
240
|
+
|
|
230
241
|
const withFallbackMetadata = (member: CatalogModel): CatalogModel => {
|
|
231
242
|
const contextWindow = typeof member.contextWindow === "number" && member.contextWindow > 0
|
|
232
243
|
? member.contextWindow
|
|
@@ -276,7 +287,7 @@ export function resolveComboCatalogMember(
|
|
|
276
287
|
// supplies a vision sidecar. Apply the same provider hints used for thin
|
|
277
288
|
// rows before deriving a combo from this complete row.
|
|
278
289
|
const hinted = prov && isModelVisionSidecarConsumer(prov, existing.id)
|
|
279
|
-
?
|
|
290
|
+
? withProviderHints(existing)
|
|
280
291
|
: existing;
|
|
281
292
|
const capped = applyProviderContextCap(hinted.contextWindow, contextCap);
|
|
282
293
|
if (capped === undefined || capped === existing.contextWindow) {
|
|
@@ -298,9 +309,7 @@ export function resolveComboCatalogMember(
|
|
|
298
309
|
id: target.model,
|
|
299
310
|
provider: target.provider,
|
|
300
311
|
};
|
|
301
|
-
const hinted =
|
|
302
|
-
? applyProviderConfigHints(target.provider, prov, base, contextCap, metadataModelIdCaseFold, undefined, metadataConfigDir)
|
|
303
|
-
: base;
|
|
312
|
+
const hinted = withProviderHints(base);
|
|
304
313
|
const hintedContext = typeof hinted.contextWindow === "number" && hinted.contextWindow > 0
|
|
305
314
|
? hinted.contextWindow
|
|
306
315
|
: undefined;
|
|
@@ -12,7 +12,7 @@ export function supportsConfiguredUltraFast(policy: ResolvedFastPolicy | undefin
|
|
|
12
12
|
&& policy.fastWire.canonicalToWire.ultrafast === "ultrafast";
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
/** Carry captured capability to
|
|
15
|
+
/** Carry captured capability to catalog projection without treating persisted metadata as authority. */
|
|
16
16
|
export function configuredUltraFastSlugs(models: readonly CatalogModel[]): ReadonlySet<string> {
|
|
17
17
|
const declarations = new Map<string, boolean>();
|
|
18
18
|
for (const model of models) {
|