@bitkyc08/opencodex 2.83.0 → 2.86.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-BjlttVLy.js +58 -0
- package/gui/dist/assets/Tray-DyR9yBee.js +1 -0
- package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
- package/gui/dist/assets/index-BMssP4k3.js +94 -0
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +2 -3
- package/src/adapters/anthropic/beta-allowlist.ts +87 -8
- package/src/adapters/anthropic/client-identity.ts +1 -1
- package/src/adapters/anthropic/passthrough.ts +33 -4
- package/src/adapters/azure.ts +19 -1
- package/src/adapters/client-fingerprint.ts +24 -0
- package/src/adapters/coding-agent/protocol.ts +15 -8
- package/src/adapters/coding-agent/turn.ts +22 -9
- 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/google-antigravity-wire.ts +22 -1
- package/src/adapters/google.ts +2 -2
- package/src/adapters/ollama-native.ts +29 -1
- package/src/adapters/openai-chat/messages.ts +8 -4
- package/src/adapters/openai-chat/response-events.ts +59 -1
- package/src/adapters/openai-chat.ts +8 -5
- package/src/adapters/openai-responses/empty-assistant.ts +32 -0
- package/src/adapters/openai-responses/passthrough.ts +3 -0
- package/src/adapters/openai-responses/request-strips.ts +10 -2
- package/src/adapters/qoder/adapter.ts +56 -15
- package/src/advisor/consult.ts +220 -0
- package/src/advisor/context.ts +226 -0
- package/src/advisor/disclosure.ts +20 -0
- package/src/advisor/runtime.ts +305 -0
- package/src/advisor/settings.ts +135 -0
- package/src/advisor/state.ts +369 -0
- package/src/advisor/synthetic-tool.ts +33 -0
- package/src/bridge/errors.ts +24 -0
- package/src/bridge.ts +1 -1
- package/src/chat/inbound.ts +30 -6
- package/src/chat/outbound.ts +5 -0
- package/src/claude/context-windows.ts +54 -1
- package/src/claude/desktop-3p-library.ts +43 -3
- package/src/claude/desktop-3p.ts +22 -8
- package/src/claude/desktop-picker-profile.ts +11 -4
- package/src/claude/desktop-profile.ts +10 -1
- package/src/claude/desktop-remote-store-io.ts +5 -3
- package/src/claude/inbound.ts +121 -50
- package/src/claude/intercept/connect-proxy.ts +10 -3
- package/src/claude/intercept/local-ca.ts +8 -1
- package/src/claude/intercept/picker-ca-persistence.ts +3 -1
- package/src/claude/intercept/picker-listener.ts +49 -1
- package/src/claude/intercept/settings-no-proxy.ts +146 -0
- package/src/claude/intercept/settings.ts +101 -9
- package/src/claude/model-default-settings.ts +154 -0
- package/src/claude/model-info.ts +7 -7
- package/src/claude/outbound.ts +13 -6
- package/src/cli/account-api.ts +65 -0
- package/src/cli/account-auth.ts +26 -2
- package/src/cli/account.ts +41 -5
- package/src/cli/advisor.ts +151 -0
- package/src/cli/auxiliary-listener-preflight.ts +37 -0
- package/src/cli/capabilities-access-remote.ts +8 -0
- package/src/cli/capabilities-agents-routing.ts +18 -0
- package/src/cli/capabilities-base.ts +14 -2
- package/src/cli/catalog-command-result.ts +2 -1
- package/src/cli/chatgpt-command.ts +35 -14
- package/src/cli/claude.ts +60 -11
- package/src/cli/config-command.ts +8 -2
- package/src/cli/debug.ts +8 -0
- package/src/cli/dispatch.ts +21 -4
- package/src/cli/doctor.ts +9 -0
- package/src/cli/ensure-desktop-cold-start.ts +329 -0
- package/src/cli/export-command.ts +15 -2
- package/src/cli/help.ts +2 -1
- package/src/cli/hub-disable.ts +99 -0
- package/src/cli/hub.ts +4 -2
- package/src/cli/index.ts +26 -8
- package/src/cli/integration-plan-dto.ts +2 -0
- package/src/cli/link-child.ts +104 -0
- package/src/cli/link-enrollment.ts +96 -0
- package/src/cli/link.ts +39 -24
- package/src/cli/observe-stream.ts +6 -1
- package/src/cli/observe.ts +10 -2
- package/src/cli/provider.ts +12 -0
- package/src/cli/registry.ts +20 -7
- package/src/cli/restart-scope.ts +3 -1
- package/src/cli/runtime-api.ts +27 -2
- package/src/cli/status.ts +104 -20
- package/src/client/connect.ts +39 -4
- package/src/client/hub-client.ts +14 -7
- package/src/client/hub-state.ts +25 -14
- package/src/client/link-home.ts +248 -0
- package/src/client/link-management-relay.ts +61 -0
- package/src/client/link-relay.ts +6 -1
- package/src/client/link-status.ts +14 -0
- package/src/client/machine-api.ts +14 -7
- package/src/client/machine-listener.ts +29 -4
- package/src/clients/config-export/contracts.ts +1 -0
- 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 +59 -16
- package/src/clients/omo-role-models.ts +6 -3
- package/src/codex/agent-role-models.ts +26 -8
- package/src/codex/auth-api/account-list.ts +17 -2
- package/src/codex/auth-api/account-selection.ts +17 -7
- package/src/codex/auth-api/pool-mode-gate.ts +5 -2
- package/src/codex/auth-api/routes.ts +5 -1
- package/src/codex/auth-context.ts +79 -5
- package/src/codex/autostart-health.ts +7 -1
- package/src/codex/catalog/build-entries.ts +11 -2
- package/src/codex/catalog/bundled.ts +5 -1
- package/src/codex/catalog/configured-tiers.ts +50 -0
- package/src/codex/catalog/effort.ts +6 -3
- package/src/codex/catalog/metadata.ts +4 -2
- package/src/codex/catalog/model-hints.ts +21 -3
- package/src/codex/catalog/native-models.ts +16 -2
- package/src/codex/catalog/native-ultrafast.ts +106 -0
- package/src/codex/catalog/parsing.ts +5 -0
- package/src/codex/catalog/provider-models.ts +4 -0
- package/src/codex/catalog/retained-sync.ts +2 -0
- package/src/codex/catalog/routed-gather.ts +22 -4
- package/src/codex/catalog/vendor-context.ts +82 -0
- package/src/codex/config-write-lock.ts +40 -2
- package/src/codex/convergence.ts +2 -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 +4 -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 +55 -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 +173 -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 +59 -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/history-provider.ts +1 -1
- package/src/codex/inject/config-write-section.ts +4 -1
- package/src/codex/inject/remove.ts +18 -6
- package/src/codex/journal.ts +16 -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 +4 -1
- package/src/codex/prompt-journal.ts +20 -2
- package/src/codex/prompt-layers/encoding.ts +49 -0
- package/src/codex/prompt-layers/import-source.ts +30 -0
- package/src/codex/prompt-layers/toml-edit.ts +269 -37
- package/src/codex/prompt-layers/toml-read.ts +52 -4
- package/src/codex/prompt-layers.ts +383 -20
- package/src/codex/prompt-lock-claim.ts +13 -3
- package/src/codex/prompt-lock-evidence.ts +3 -3
- package/src/codex/prompt-lock-owner.ts +14 -6
- package/src/codex/prompt-text-probe.ts +34 -5
- package/src/codex/quota.ts +13 -6
- package/src/codex/role-auto-assign.ts +2 -5
- package/src/codex/routing/active-account.ts +21 -0
- package/src/codex/routing/health-store.ts +1 -1
- package/src/codex/routing/pool-recovery.ts +79 -0
- package/src/codex/routing.ts +1 -1
- package/src/codex/runtime.ts +196 -8
- package/src/codex/shim-diagnostics.ts +6 -0
- package/src/codex/shim-overlay.ts +15 -6
- package/src/codex/shim.ts +1 -1
- package/src/combos/failover.ts +3 -0
- package/src/config/api-key-reconcile.ts +34 -0
- package/src/config/diagnostics.ts +18 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/live-reconcile.ts +56 -29
- package/src/config/live-topology.ts +29 -0
- package/src/config/load-degrade.ts +8 -2
- package/src/config/paths.ts +13 -3
- package/src/config/salvage.ts +3 -1
- package/src/config/schema/config-schema.ts +19 -0
- package/src/config/schema/desktop-compatibility.ts +9 -0
- package/src/config/schema/leaf-validators.ts +1 -0
- package/src/generated/compatibility-version.json +713 -285
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/grpc-web.ts +23 -1
- package/src/grok/reset-coupon-ledger.ts +75 -5
- package/src/grok/reset-coupons.ts +4 -2
- 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 +14 -7
- package/src/integrations/registry.ts +52 -11
- package/src/integrations/state.ts +19 -7
- package/src/integrations/store.ts +3 -0
- package/src/integrations/target.ts +42 -5
- package/src/integrations/writer.ts +24 -7
- package/src/lib/advisor-activation.ts +16 -0
- package/src/lib/app-owned-memory-stores.ts +4 -0
- package/src/lib/app-owned-memory.ts +11 -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/desktop-proxy-route.ts +30 -0
- package/src/lib/desktop-upstream-tunnel.ts +107 -0
- package/src/lib/errors.ts +7 -0
- package/src/lib/json-byte-size.ts +31 -0
- package/src/lib/local-internal-call-capability.ts +55 -0
- package/src/lib/optional-shutdown-hooks.ts +35 -0
- package/src/lib/outbound-credential-registry.ts +210 -0
- package/src/lib/package-tree-integrity.ts +2 -2
- package/src/lib/passive-read.ts +35 -0
- package/src/lib/shadow-call.ts +44 -5
- 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/link/codex-settings.ts +155 -0
- package/src/link/management-routes.ts +89 -0
- package/src/link/remote-probe.ts +38 -0
- package/src/link/ssh-argv.ts +26 -5
- package/src/link/ssh-runner.ts +58 -27
- package/src/link/status-projection.ts +22 -4
- package/src/link/supervisor.ts +144 -49
- package/src/link/tunnel-state.ts +11 -4
- package/src/oauth/anthropic-continuity.ts +4 -3
- package/src/oauth/anthropic-identity.ts +13 -2
- package/src/oauth/anthropic-model-quota.ts +13 -1
- package/src/oauth/anthropic-routing.ts +230 -23
- package/src/oauth/anthropic.ts +18 -4
- package/src/oauth/generic-account-failover.ts +29 -5
- package/src/oauth/google-antigravity.ts +24 -7
- package/src/oauth/index.ts +28 -17
- package/src/oauth/kiro-account-identity.ts +106 -0
- package/src/oauth/kiro-device-login.ts +2 -1
- package/src/oauth/kiro.ts +55 -6
- package/src/oauth/plan.ts +29 -0
- package/src/oauth/store.ts +40 -3
- package/src/oauth/types.ts +2 -0
- package/src/plugins/loader.ts +11 -0
- 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/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/settings.ts +8 -0
- package/src/providers/anthropic-billing-ownership.ts +74 -0
- package/src/providers/anthropic-billing-recovery.ts +31 -0
- package/src/providers/anthropic-billing-refusal.ts +55 -0
- package/src/providers/anthropic-reset-admission.ts +62 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +28 -6
- package/src/providers/antigravity-account-roster.ts +102 -0
- package/src/providers/antigravity-models.ts +85 -7
- package/src/providers/api-key-resolve.ts +17 -2
- package/src/providers/key-failover.ts +52 -5
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/model-rename-migration.ts +5 -3
- package/src/providers/model-rename-startup.ts +5 -3
- package/src/providers/quota/account-cache.ts +11 -0
- package/src/providers/quota/anthropic-account-quota.ts +4 -2
- package/src/providers/quota/report-cache.ts +14 -0
- package/src/providers/quota/vendor-probes-oauth.ts +3 -3
- package/src/providers/quota.ts +4 -1
- package/src/providers/registry/entries-core.ts +21 -20
- package/src/providers/registry/entries-extended.ts +19 -1
- package/src/providers/registry/model-seeds.ts +17 -0
- package/src/providers/request-pacing.ts +9 -0
- package/src/providers/stale-no-vision-seed-migration.ts +72 -0
- package/src/responses/compaction.ts +14 -0
- package/src/responses/parser.ts +37 -20
- package/src/responses/state.ts +32 -29
- package/src/routing/history/build-file.ts +92 -0
- package/src/routing/history/indexer.ts +248 -216
- package/src/routing/history/ingest.ts +238 -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/server/admission-model-scope.ts +4 -1
- package/src/server/antigravity-direct-eligibility.ts +208 -0
- package/src/server/auth-cors.ts +46 -0
- package/src/server/chat-completions.ts +75 -9
- package/src/server/claude-messages.ts +110 -20
- package/src/server/configured-credentials.ts +194 -0
- package/src/server/index/desktop-compatibility-startup.ts +56 -0
- package/src/server/index/link-management.ts +58 -0
- package/src/server/index/optional-listeners.ts +5 -1
- package/src/server/index/serve-options.ts +15 -2
- package/src/server/index/startup-warnings.ts +9 -0
- package/src/server/index/websocket-handler.ts +9 -0
- package/src/server/index.ts +13 -10
- package/src/server/inference/client-encoder-delivery.ts +6 -5
- package/src/server/lifecycle.ts +2 -1
- package/src/server/login-shell-path.ts +146 -0
- package/src/server/management/advisor-routes.ts +191 -0
- package/src/server/management/agent-settings-routes.ts +50 -14
- package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
- package/src/server/management/codex-agent-role-routes.ts +19 -6
- package/src/server/management/codex-prompt-routes.ts +136 -5
- package/src/server/management/codex-role-auto-assign.ts +1 -1
- package/src/server/management/companion-routes.ts +4 -0
- package/src/server/management/config-routes.ts +2 -2
- package/src/server/management/context.ts +4 -0
- 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/grok-coupon-routes.ts +78 -27
- package/src/server/management/link-enrollment.ts +192 -0
- package/src/server/management/link-route-state.ts +185 -0
- package/src/server/management/link-routes.ts +82 -402
- package/src/server/management/model-routes.ts +2 -2
- package/src/server/management/model-rows.ts +3 -1
- package/src/server/management/protocol-settings-patch.ts +1 -0
- package/src/server/management/provider-overwrite-carry.ts +10 -0
- package/src/server/management/provider-routes.ts +1 -0
- package/src/server/management/route-registry.ts +16 -3
- package/src/server/management/shared.ts +3 -0
- 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.ts +73 -43
- package/src/server/relay-eager.ts +2 -0
- package/src/server/request-log-filter.ts +6 -1
- package/src/server/request-log-terminal-status.ts +63 -6
- package/src/server/request-log.ts +7 -0
- package/src/server/responses/adapter-continuation.ts +35 -18
- package/src/server/responses/adapter-delivery.ts +24 -2
- package/src/server/responses/adapter-dispatch.ts +261 -81
- package/src/server/responses/advisor-plan-slot.ts +27 -0
- package/src/server/responses/advisor-slot.ts +336 -0
- package/src/server/responses/code-mode-collab-surface.ts +313 -0
- package/src/server/responses/codex-ws-exchange.ts +147 -6
- package/src/server/responses/codex-ws-wire.ts +203 -0
- package/src/server/responses/collaboration.ts +36 -7
- package/src/server/responses/compact.ts +126 -48
- package/src/server/responses/context-overflow.ts +13 -1
- package/src/server/responses/core-codex-account.ts +105 -1
- package/src/server/responses/core-combo-failure.ts +24 -1
- package/src/server/responses/core-combo.ts +28 -2
- package/src/server/responses/core-errors.ts +29 -1
- package/src/server/responses/core-lifetime.ts +2 -0
- package/src/server/responses/core-normalize.ts +11 -0
- package/src/server/responses/core-options.ts +48 -0
- package/src/server/responses/core-replay.ts +7 -4
- package/src/server/responses/core.ts +1 -1
- package/src/server/responses/deferred-collab-surface.ts +158 -0
- package/src/server/responses/deferred-send-reporter.ts +25 -0
- package/src/server/responses/fetch-helpers.ts +12 -1
- package/src/server/responses/passthrough-delivery.ts +20 -11
- package/src/server/responses/passthrough-dispatch.ts +29 -7
- package/src/server/responses/request-prepare.ts +74 -9
- package/src/server/responses/request-transport.ts +22 -8
- package/src/server/responses/run-turn-execution.ts +70 -8
- package/src/server/responses/sidecar-execution.ts +89 -5
- package/src/server/responses/terminal-guard.ts +2 -1
- package/src/server/responses/ws-upstream.ts +9 -1
- package/src/server/responses-undeclared-tool-guard.ts +30 -9
- package/src/server/startup-action-control.ts +38 -4
- package/src/server/startup-health-cache.ts +5 -0
- package/src/server/system-env-shell.ts +7 -2
- package/src/server/system-env.ts +11 -1
- package/src/service/cli.ts +5 -1
- package/src/service/desktop-startup.ts +161 -34
- package/src/service/desktop-supervision.d.mts +27 -1
- package/src/service/desktop-supervision.mjs +111 -5
- package/src/service/health-sentinel-launch.ts +176 -0
- package/src/service/health-sentinel-main.ts +466 -0
- package/src/service/health-sentinel-policy.ts +241 -0
- package/src/service/health-sentinel-probe.ts +71 -0
- package/src/service/health-sentinel-windows.ts +87 -0
- package/src/service/linux-linger.ts +20 -0
- package/src/service/logging.ts +72 -0
- package/src/tray/windows-tray.ps1 +43 -24
- package/src/types/config.ts +68 -6
- package/src/types/provider.ts +7 -0
- package/src/types/request.ts +15 -0
- package/src/types/tools.ts +2 -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/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/expected-prices.ts +16 -0
- package/src/usage/log.ts +19 -1
- package/src/web-search/gemini-executor.ts +2 -2
- package/assets/sponsors/packycode.png +0 -0
- package/gui/dist/assets/App-Ba8uG696.js +0 -54
- package/gui/dist/assets/Tray-IImbNPRy.js +0 -1
- package/gui/dist/assets/index-BHRZdJuv.js +0 -94
- package/gui/dist/assets/quota-summary-DiJO6sYY.js +0 -1
|
@@ -24,6 +24,7 @@ import { existsSync, lstatSync, readFileSync, realpathSync, statSync } from "nod
|
|
|
24
24
|
import { basename, dirname, isAbsolute, join, resolve, win32 } from "node:path";
|
|
25
25
|
import { shouldInjectApiAuthHeader, standaloneCodexRoutingTarget } from "../codex/inject";
|
|
26
26
|
import { FORMAT_MEDIA_TYPE, serializeDocument, type ConfigFormat } from "../integrations/serialize";
|
|
27
|
+
import { fileIO } from "../integrations/config-io";
|
|
27
28
|
import { canonicalizeReasoningEfforts } from "../reasoning-effort";
|
|
28
29
|
import { expandFastExportModels } from "./config-export/fast-models";
|
|
29
30
|
import { probeHostname } from "../server/proxy-liveness";
|
|
@@ -81,6 +82,8 @@ import {
|
|
|
81
82
|
|
|
82
83
|
export type { CommandCodeGeneratedConfig, CommandCodeModelEntry, CommandCodeProviderBlock };
|
|
83
84
|
export { buildCommandCodeClientConfig, summarizeCommandCode, buildCommandCodeContribution, commandCodeProviderRoot };
|
|
85
|
+
import { buildQoderClientConfig, summarizeQoder, buildQoderContribution, qoderConfigPath } from "./config-export/qoder";
|
|
86
|
+
export { buildQoderClientConfig, qoderConfigPath, qoderHomeDir, type QoderGeneratedConfig } from "./config-export/qoder";
|
|
84
87
|
|
|
85
88
|
|
|
86
89
|
|
|
@@ -470,20 +473,49 @@ export function mcodeConfigPath(env: OpencodeLaunchEnv = process.env, home: stri
|
|
|
470
473
|
return join(mcodeHomeDir(env, home), "config.yaml");
|
|
471
474
|
}
|
|
472
475
|
|
|
473
|
-
/**
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
*/
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
476
|
+
/** Bootstrap settings always stay at the default home, even after relocation. */
|
|
477
|
+
export type ZcodePathIO = {
|
|
478
|
+
readText: (path: string) => { kind: "text"; text: string } | { kind: "missing" } | { kind: "failed"; code?: string };
|
|
479
|
+
};
|
|
480
|
+
const zcodePathIO: ZcodePathIO = {
|
|
481
|
+
/** Shares the integration reader's four-MiB limit and regular-file-only bootstrap contract. */
|
|
482
|
+
readText: path => fileIO().readText(path),
|
|
483
|
+
};
|
|
484
|
+
export class ZcodeSettingsPathError extends ClientPathError {}
|
|
485
|
+
|
|
486
|
+
export function zcodeBootstrapSettingsPath(home: string = homedir()): string {
|
|
487
|
+
return join(home, ".zcode", "v2", "setting.json");
|
|
483
488
|
}
|
|
484
489
|
|
|
485
|
-
|
|
486
|
-
|
|
490
|
+
/** The pre-relocation path is retained solely to recognize historical ownership. */
|
|
491
|
+
export function zcodeLegacyHomeDir(env: OpencodeLaunchEnv = process.env, home: string = homedir()): string {
|
|
492
|
+
const override = env.ZCODE_DATA_DIR?.trim();
|
|
493
|
+
return override ? absoluteClientPath(override, home, "ZCODE_DATA_DIR") : join(home, ".zcode");
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/** ZCODE_DATA_DIR names the .zcode root; dataBaseDir names its parent. */
|
|
497
|
+
export function zcodeHomeDir(env: OpencodeLaunchEnv = process.env, home: string = homedir(), io: ZcodePathIO = zcodePathIO): string {
|
|
498
|
+
if (env.ZCODE_DATA_DIR?.trim()) return zcodeLegacyHomeDir(env, home);
|
|
499
|
+
const bootstrap = zcodeBootstrapSettingsPath(home);
|
|
500
|
+
const read = io.readText(bootstrap);
|
|
501
|
+
if (read.kind === "missing") return join(home, ".zcode");
|
|
502
|
+
const unknown = () => new ZcodeSettingsPathError(
|
|
503
|
+
`ZCode's active data root is unknown: ${bootstrap} must be readable JSON with an absolute dataBaseDir (or no dataBaseDir). Repair the bootstrap setting before enabling or refreshing; no legacy provider will be applied.`,
|
|
504
|
+
);
|
|
505
|
+
if (read.kind !== "text") throw unknown();
|
|
506
|
+
let settings: unknown;
|
|
507
|
+
try { settings = JSON.parse(read.text); } catch { throw unknown(); }
|
|
508
|
+
if (settings === null || typeof settings !== "object" || Array.isArray(settings)) throw unknown();
|
|
509
|
+
const base = (settings as Record<string, unknown>).dataBaseDir;
|
|
510
|
+
if (base === undefined) return join(home, ".zcode");
|
|
511
|
+
// Match the native path's value, without cwd or tilde expansion. The native
|
|
512
|
+
// ZCODE_DATA_BASE_DIR selector is not an alias for our ZCODE_DATA_DIR.
|
|
513
|
+
if (typeof base !== "string" || !isAbsolute(base) || base.includes("\0")) throw unknown();
|
|
514
|
+
return join(base, ".zcode");
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export function zcodeConfigPath(env: OpencodeLaunchEnv = process.env, home: string = homedir(), io?: ZcodePathIO): string {
|
|
518
|
+
return join(zcodeHomeDir(env, home, io), "v2", "config.json");
|
|
487
519
|
}
|
|
488
520
|
|
|
489
521
|
/**
|
|
@@ -532,8 +564,7 @@ export function commandCodeConfigPath(env: OpencodeLaunchEnv = process.env, home
|
|
|
532
564
|
* an install that has ever run, the import has already happened and never runs
|
|
533
565
|
* again — every later write to `v2/config.json` is read by nobody (#5348).
|
|
534
566
|
*
|
|
535
|
-
*
|
|
536
|
-
* tell whether its own write can still reach the client. The env override is
|
|
567
|
+
* The integration writes this store through its existing schema-aware writer. The env override is
|
|
537
568
|
* ZCode's own (`ZCODE_PERSONAL_PROVIDER_CONFIG_FILE`), so an operator who
|
|
538
569
|
* relocated the store is measured against the file their client actually opens
|
|
539
570
|
* rather than the default location. A relative override is refused for the same
|
|
@@ -541,10 +572,10 @@ export function commandCodeConfigPath(env: OpencodeLaunchEnv = process.env, home
|
|
|
541
572
|
* which file it names, and here that disagreement decides whether an apply is
|
|
542
573
|
* reported as effective.
|
|
543
574
|
*/
|
|
544
|
-
export function zcodeProviderStorePath(env: OpencodeLaunchEnv = process.env, home: string = homedir()): string {
|
|
575
|
+
export function zcodeProviderStorePath(env: OpencodeLaunchEnv = process.env, home: string = homedir(), io?: ZcodePathIO): string {
|
|
545
576
|
const override = env.ZCODE_PERSONAL_PROVIDER_CONFIG_FILE?.trim();
|
|
546
577
|
if (override) return absoluteClientPath(override, home, "ZCODE_PERSONAL_PROVIDER_CONFIG_FILE");
|
|
547
|
-
return join(zcodeHomeDir(env, home), "v2", "provider_config.json");
|
|
578
|
+
return join(zcodeHomeDir(env, home, io), "v2", "provider_config.json");
|
|
548
579
|
}
|
|
549
580
|
|
|
550
581
|
/**
|
|
@@ -1399,6 +1430,18 @@ export function clineSettingsDir(env: OpencodeLaunchEnv = process.env, home: str
|
|
|
1399
1430
|
}
|
|
1400
1431
|
|
|
1401
1432
|
export const EXPORT_CLIENTS: Record<ExportClientId, ExportClientSpec> = {
|
|
1433
|
+
qoder: {
|
|
1434
|
+
id: "qoder",
|
|
1435
|
+
filename: "qoder-settings.json",
|
|
1436
|
+
destination: env => qoderConfigPath(env),
|
|
1437
|
+
apiKeyEnv: "",
|
|
1438
|
+
exportHint: "Qoder reads a non-secret placeholder from settings.json; loopback needs no key.",
|
|
1439
|
+
build: buildQoderClientConfig,
|
|
1440
|
+
format: "json",
|
|
1441
|
+
summarize: summarizeQoder,
|
|
1442
|
+
buildContribution: buildQoderContribution,
|
|
1443
|
+
loopbackOnly: true,
|
|
1444
|
+
},
|
|
1402
1445
|
opencode: {
|
|
1403
1446
|
id: "opencode",
|
|
1404
1447
|
filename: "opencode.json",
|
|
@@ -120,10 +120,13 @@ function serialize(text: string, doc: JsonObject): string {
|
|
|
120
120
|
/**
|
|
121
121
|
* `reasoning` left undefined keeps the entry's level; null removes it, so a level LazyCodex cannot
|
|
122
122
|
* express does not leave a stale one behind for its next install to apply.
|
|
123
|
+
* `model` left undefined keeps the entry's model, so an effort-only save of a role that inherits
|
|
124
|
+
* its model never adds a pin here. LazyCodex declares both keys optional, so an entry may carry
|
|
125
|
+
* `reasoning` alone; an absent entry is created only when there is a value to put in it.
|
|
123
126
|
*/
|
|
124
127
|
export function writeOmoRoleModel(
|
|
125
128
|
role: string,
|
|
126
|
-
model: string,
|
|
129
|
+
model: string | undefined,
|
|
127
130
|
path: string = omoJsoncPath(),
|
|
128
131
|
reasoning?: string | null,
|
|
129
132
|
): OmoRoleModelWriteStatus {
|
|
@@ -137,10 +140,10 @@ export function writeOmoRoleModel(
|
|
|
137
140
|
if (!isObject(agents)) return "invalid";
|
|
138
141
|
const entry = agents[role] === undefined ? {} : agents[role];
|
|
139
142
|
if (!isObject(entry)) return "invalid";
|
|
140
|
-
const next: JsonObject = { ...entry, model };
|
|
143
|
+
const next: JsonObject = model === undefined ? { ...entry } : { ...entry, model };
|
|
141
144
|
if (reasoning === null) delete next.reasoning;
|
|
142
145
|
else if (reasoning !== undefined) next.reasoning = reasoning;
|
|
143
|
-
if (entry.model === model && entry.reasoning === next.reasoning) return "unchanged";
|
|
146
|
+
if (entry.model === next.model && entry.reasoning === next.reasoning) return "unchanged";
|
|
144
147
|
agents[role] = next;
|
|
145
148
|
codex.agents = agents;
|
|
146
149
|
doc[CODEX_SECTION] = codex;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The dashboard's write of one Codex agent role's model,
|
|
2
|
+
* The dashboard's write of one Codex agent role's model, its reasoning effort, or both.
|
|
3
3
|
*
|
|
4
4
|
* Codex overrides the spawn-time model with the root `model` pin in
|
|
5
5
|
* `$CODEX_HOME/agents/<role>.toml`, so that pin is what decides a child's model. This module
|
|
6
|
-
* edits exactly that value, plus the root `model_reasoning_effort`
|
|
7
|
-
* nothing else: every other byte of the file, including the
|
|
8
|
-
* usually mentions `model =` in prose, is left as it was.
|
|
6
|
+
* edits exactly that value when a caller passes a model, plus the root `model_reasoning_effort`
|
|
7
|
+
* when a caller passes an effort, and nothing else: every other byte of the file, including the
|
|
8
|
+
* instructions multiline string that usually mentions `model =` in prose, is left as it was. An
|
|
9
|
+
* omitted model leaves the pin as it is, so a role that inherits its model keeps inheriting it.
|
|
9
10
|
* It runs only on an explicit user action; no sync or startup path calls it.
|
|
10
11
|
*/
|
|
11
12
|
import { lstatSync, readFileSync } from "node:fs";
|
|
@@ -97,6 +98,21 @@ export function setTomlRootReasoningEffort(content: string, effort: string): str
|
|
|
97
98
|
return setTomlRootString(content, locateTomlReasoningEffortKey(content), "model_reasoning_effort", effort);
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
export function removeTomlRootReasoningEffort(content: string): string {
|
|
102
|
+
const location = locateTomlReasoningEffortKey(content);
|
|
103
|
+
if (!location?.inRootTable) return content;
|
|
104
|
+
if (!location.span) {
|
|
105
|
+
throw new AgentRoleModelError("unsupported_model_value", "the existing model_reasoning_effort value is not a one-line string");
|
|
106
|
+
}
|
|
107
|
+
const lines = content.split("\n");
|
|
108
|
+
// A terminated line goes with its own newline. An unterminated last line is emptied instead,
|
|
109
|
+
// so the previous line keeps its terminator (and its \r in a CRLF file).
|
|
110
|
+
if (location.line < lines.length - 1) lines.splice(location.line, 1);
|
|
111
|
+
else lines[location.line] = "";
|
|
112
|
+
const bom = location.line === 0 && content.startsWith("\ufeff") ? "\ufeff" : "";
|
|
113
|
+
return bom + lines.join("\n");
|
|
114
|
+
}
|
|
115
|
+
|
|
100
116
|
export function readCodexAgentRoleEffort(role: string, codexHome: string): string | null {
|
|
101
117
|
try {
|
|
102
118
|
const location = locateTomlReasoningEffortKey(readFileSync(roleFile(role, codexHome), "utf8"));
|
|
@@ -148,9 +164,9 @@ function assertValidRoleToml(role: string, text: string, stage: "before" | "afte
|
|
|
148
164
|
|
|
149
165
|
export function writeCodexAgentRoleModel(
|
|
150
166
|
role: string,
|
|
151
|
-
model: string,
|
|
167
|
+
model: string | undefined,
|
|
152
168
|
codexHome: string,
|
|
153
|
-
effort?: string,
|
|
169
|
+
effort?: string | null,
|
|
154
170
|
): { status: "written" | "unchanged" } {
|
|
155
171
|
requireKnownRole(role, codexHome);
|
|
156
172
|
const path = roleFile(role, codexHome);
|
|
@@ -159,10 +175,12 @@ export function writeCodexAgentRoleModel(
|
|
|
159
175
|
}
|
|
160
176
|
const before = readFileSync(path, "utf8");
|
|
161
177
|
assertValidRoleToml(role, before, "before");
|
|
162
|
-
const withModel = setTomlRootModel(before, validateAgentRoleModel(model));
|
|
178
|
+
const withModel = model === undefined ? before : setTomlRootModel(before, validateAgentRoleModel(model));
|
|
163
179
|
const after = effort === undefined
|
|
164
180
|
? withModel
|
|
165
|
-
:
|
|
181
|
+
: effort === null
|
|
182
|
+
? removeTomlRootReasoningEffort(withModel)
|
|
183
|
+
: setTomlRootReasoningEffort(withModel, validateAgentRoleEffort(effort));
|
|
166
184
|
if (after === before) return { status: "unchanged" };
|
|
167
185
|
assertValidRoleToml(role, after, "after");
|
|
168
186
|
assertIntegrationWriteOwnership(path);
|
|
@@ -33,6 +33,7 @@ import { fetchMainAccountInfoAttempt, EMPTY_MAIN_ACCOUNT_INFO, mainResetCreditsF
|
|
|
33
33
|
import { fetchPoolAccountQuota, PoolQuotaProbeBusyError, POOL_QUOTA_REFRESH_CONCURRENCY } from "./pool-quota-probe";
|
|
34
34
|
import type { PoolQuotaResult } from "./pool-quota-probe";
|
|
35
35
|
import { getRuntimeConfig, configuredPoolAccount, mapWithConcurrency } from "./runtime-config";
|
|
36
|
+
import { isPassiveRead } from "../../lib/passive-read";
|
|
36
37
|
|
|
37
38
|
export function quotaForPlan<T extends Omit<StoredAccountQuota, "updatedAt"> | StoredAccountQuota | null>(
|
|
38
39
|
quota: T,
|
|
@@ -274,13 +275,25 @@ export async function listCodexAuthAccountsSnapshot(
|
|
|
274
275
|
pruneCodexCredits([MAIN_CODEX_ACCOUNT_ID, ...poolAccounts.map(account => account.id)]);
|
|
275
276
|
// One redaction decision for the whole snapshot, read once from the operator's config (#3859).
|
|
276
277
|
const maskEmails = emailMaskingEnabled(runtimeConfig);
|
|
277
|
-
|
|
278
|
-
|
|
278
|
+
// A passive read (a Remote Link Child's, #6959) reports what this machine already observed: no
|
|
279
|
+
// token refresh, no WHAM probe, and so none of the plan or credential writes they make.
|
|
280
|
+
const passive = isPassiveRead();
|
|
281
|
+
const mainResult: Awaited<ReturnType<typeof fetchMainAccountInfoAttempt>> = passive
|
|
282
|
+
? { info: getMainAccountInfoCache() ?? EMPTY_MAIN_ACCOUNT_INFO, credentialChecked: false, hasCredential: false,
|
|
283
|
+
identityGeneration: captureMainAccountIdentityGeneration() }
|
|
284
|
+
: await fetchMainAccountInfoAttempt(forceRefresh, 1, undefined, false, forceRefresh, false, runtimeConfig);
|
|
279
285
|
const refreshedPool = await mapWithConcurrency(poolAccounts, POOL_QUOTA_REFRESH_CONCURRENCY, async account => {
|
|
280
286
|
const cred = getCodexAccountCredential(account.id);
|
|
281
287
|
let quotaResult: PoolQuotaResult;
|
|
282
288
|
if (!cred) {
|
|
283
289
|
quotaResult = { quota: null, needsReauth: true };
|
|
290
|
+
} else if (passive) {
|
|
291
|
+
quotaResult = {
|
|
292
|
+
quota: getAccountQuota(account.id),
|
|
293
|
+
needsReauth: false,
|
|
294
|
+
credentialGeneration: readCodexAccountRecord(account.id)?.generation,
|
|
295
|
+
quotaProbeSkipped: true,
|
|
296
|
+
};
|
|
284
297
|
} else {
|
|
285
298
|
try {
|
|
286
299
|
// Credits are process-local while pool quota is hydrated from disk, so right after a
|
|
@@ -410,6 +423,8 @@ export async function listCodexAuthAccountsSnapshot(
|
|
|
410
423
|
};
|
|
411
424
|
// Project the refusal and DTO in the same owned synchronous observation. A same-account grant
|
|
412
425
|
// replacement during earlier pool probes must not inherit the previous grant's refusal.
|
|
426
|
+
// A passive read opens no claim database (its lock file is hardened on open).
|
|
427
|
+
if (passive) return projectSnapshot();
|
|
413
428
|
const projectionLease = tryAcquireNativeMainProfileClaim();
|
|
414
429
|
if (!projectionLease) return projectSnapshot();
|
|
415
430
|
try {
|
|
@@ -6,11 +6,11 @@ import { deleteConfigTopLevelKey, fileBackedConfigPath, recordFileBackedConfig }
|
|
|
6
6
|
import { MAIN_CODEX_ACCOUNT_ID, hasLegacyMainCodexPoolAccount, isSelectableCodexPoolAccount, isValidCodexAccountId } from "../account-id";
|
|
7
7
|
import { isCodexAccountPaused } from "../account-pause";
|
|
8
8
|
import { readCodexAccountRecord } from "../account-store";
|
|
9
|
-
import { codexAccountPinDrainReason, resetCodexRoutingForManualSelection } from "../routing";
|
|
9
|
+
import { codexAccountPinDrainReason, resetCodexRoutingForClearedSelection, resetCodexRoutingForManualSelection } from "../routing";
|
|
10
10
|
import { jsonResponse } from "./http";
|
|
11
11
|
|
|
12
12
|
class SelectionRejected extends Error {
|
|
13
|
-
constructor(message: string, readonly status: number) { super(message); }
|
|
13
|
+
constructor(message: string, readonly status: number, readonly code?: string) { super(message); }
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
function validateSelection(config: OcxConfig, accountId: string | null): void {
|
|
@@ -42,8 +42,14 @@ function applySelection(config: OcxConfig, accountId: string | null): boolean {
|
|
|
42
42
|
return changed;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
function validateExpectedPin(config: OcxConfig, expectedPinnedAccountId: string | undefined): void {
|
|
46
|
+
if (expectedPinnedAccountId !== undefined && config.activeCodexAccountPinned !== expectedPinnedAccountId) {
|
|
47
|
+
throw new SelectionRejected("The pin changed; reload accounts before releasing it", 409, "account_pin_changed");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
45
51
|
/** A manual selection is an explicit command, even when equal to the live merge baseline. */
|
|
46
|
-
export function persistCodexAccountSelection(config: OcxConfig, accountId: string | null): Response {
|
|
52
|
+
export function persistCodexAccountSelection(config: OcxConfig, accountId: string | null, expectedPinnedAccountId?: string): Response {
|
|
47
53
|
const unknown = () => jsonResponse({
|
|
48
54
|
error: "Account selection could not be confirmed; reload settings before retrying",
|
|
49
55
|
code: "account_selection_unavailable",
|
|
@@ -63,7 +69,9 @@ export function persistCodexAccountSelection(config: OcxConfig, accountId: strin
|
|
|
63
69
|
const normalized = validateConfigCandidate(mergeConfigDefaults(config));
|
|
64
70
|
if (!normalized.ok) return unknown();
|
|
65
71
|
const initial = normalized.config;
|
|
66
|
-
|
|
72
|
+
validateExpectedPin(initial, expectedPinnedAccountId);
|
|
73
|
+
// Conditional release must keep the baseline pin until the locked mutation checks it.
|
|
74
|
+
if (expectedPinnedAccountId === undefined) applySelection(initial, accountId);
|
|
67
75
|
initializePersistedConfigIfMissing(initial);
|
|
68
76
|
}
|
|
69
77
|
const result = mutatePersistedConfig(persisted => {
|
|
@@ -71,12 +79,13 @@ export function persistCodexAccountSelection(config: OcxConfig, accountId: strin
|
|
|
71
79
|
// A removed or paused account must not be resurrected by a stale UI snapshot.
|
|
72
80
|
validateSelection(config, accountId);
|
|
73
81
|
validateSelection(persisted, accountId);
|
|
82
|
+
validateExpectedPin(persisted, expectedPinnedAccountId);
|
|
74
83
|
return { changed: applySelection(persisted, accountId), value: persisted };
|
|
75
84
|
});
|
|
76
85
|
if (result.status === "unavailable") return unknown();
|
|
77
86
|
committed = result.value;
|
|
78
87
|
} catch (error) {
|
|
79
|
-
if (error instanceof SelectionRejected) return jsonResponse({ error: error.message }, error.status);
|
|
88
|
+
if (error instanceof SelectionRejected) return jsonResponse({ error: error.message, ...(error.code ? { code: error.code } : {}) }, error.status);
|
|
80
89
|
// Atomic publication may succeed before generation/bookkeeping fails. Confirm the exact
|
|
81
90
|
// durable selection instead of rolling live state back behind an already published write.
|
|
82
91
|
const snapshot = readConfigFileSnapshot();
|
|
@@ -95,12 +104,13 @@ export function persistCodexAccountSelection(config: OcxConfig, accountId: strin
|
|
|
95
104
|
adoptPersistedCodexAccountSelection(config, committed);
|
|
96
105
|
recordFileBackedConfig(config, configPath);
|
|
97
106
|
// No failed save may clear affinity, spend the manual preference, or move the RR cursor.
|
|
98
|
-
|
|
107
|
+
if (accountId === null) resetCodexRoutingForClearedSelection();
|
|
108
|
+
else resetCodexRoutingForManualSelection(accountId);
|
|
99
109
|
const pinDrainReason = accountId === null ? undefined : codexAccountPinDrainReason(config, accountId);
|
|
100
110
|
return jsonResponse({
|
|
101
111
|
ok: true,
|
|
102
112
|
activeCodexAccountId: config.activeCodexAccountId ?? null,
|
|
103
|
-
appliesImmediately:
|
|
113
|
+
appliesImmediately: accountId !== null,
|
|
104
114
|
...(pinDrainReason !== undefined ? { pinDrained: true, pinDrainReason } : {}),
|
|
105
115
|
...(warning ? { warning: "config_bookkeeping_failed" } : {}),
|
|
106
116
|
});
|
|
@@ -25,6 +25,7 @@ import type { PoolQuotaResult } from "./pool-quota-probe";
|
|
|
25
25
|
import { fetchMainAccountInfoAttempt, fetchMainAccountInfo, MAIN_CACHE_TTL } from "./main-account-probe";
|
|
26
26
|
import { fetchPoolAccountQuota, PoolQuotaProbeBusyError, POOL_CACHE_TTL, POOL_QUOTA_REFRESH_CONCURRENCY } from "./pool-quota-probe";
|
|
27
27
|
import { getRuntimeConfig, configuredPoolAccount, mapWithConcurrency } from "./runtime-config";
|
|
28
|
+
import { trackBackgroundWork } from "../../lib/background-work";
|
|
28
29
|
|
|
29
30
|
let primeInFlight: Promise<void> | null = null;
|
|
30
31
|
let lastPriorityFailbackPrimeAt: number | undefined;
|
|
@@ -259,7 +260,9 @@ export async function primeCodexPoolQuotas(
|
|
|
259
260
|
&& now - lastPriorityFailbackPrimeAt < CODEX_PRIORITY_FAILBACK_REFRESH_MS) return;
|
|
260
261
|
lastPriorityFailbackPrimeAt = now;
|
|
261
262
|
}
|
|
262
|
-
|
|
263
|
+
// Tracked because most callers do not await it, yet it holds the native-main claim
|
|
264
|
+
// database open; whoever removes that directory settles it first (see background-work).
|
|
265
|
+
primeInFlight = trackBackgroundWork(`codex-pool-quota-prime:${reason}`, (async () => {
|
|
263
266
|
const pool = (runtimeConfig.codexAccounts ?? []).filter(isSelectableCodexPoolAccount);
|
|
264
267
|
const stale = pool.filter(a => {
|
|
265
268
|
const q = getAccountQuota(a.id);
|
|
@@ -344,7 +347,7 @@ export async function primeCodexPoolQuotas(
|
|
|
344
347
|
if (process.env.OPENCODEX_DEBUG_QUOTA === "1") {
|
|
345
348
|
console.warn(`[codex-quota] prime done (reason=${reason}, pool=${pool.length}, refreshed=${stale.length})`);
|
|
346
349
|
}
|
|
347
|
-
})().finally(() => { primeInFlight = null; });
|
|
350
|
+
})().finally(() => { primeInFlight = null; }));
|
|
348
351
|
return primeInFlight;
|
|
349
352
|
}
|
|
350
353
|
|
|
@@ -254,7 +254,11 @@ export async function handleCodexAuthAPI(
|
|
|
254
254
|
|| !("accountId" in body) || (body.accountId !== null && typeof body.accountId !== "string")) {
|
|
255
255
|
return jsonResponse({ error: "accountId must be an account id or null" }, 400);
|
|
256
256
|
}
|
|
257
|
-
|
|
257
|
+
const expectedPin = "expectedPinnedAccountId" in body ? body.expectedPinnedAccountId : undefined;
|
|
258
|
+
if (expectedPin !== undefined && (body.accountId !== null || !isCodexAccountPriorityKey(expectedPin))) {
|
|
259
|
+
return jsonResponse({ error: "expectedPinnedAccountId must be a valid account id for a release" }, 400);
|
|
260
|
+
}
|
|
261
|
+
return persistCodexAccountSelection(getRuntimeConfig(config), body.accountId, expectedPin as string | undefined);
|
|
258
262
|
}
|
|
259
263
|
|
|
260
264
|
if (url.pathname === "/api/codex-auth/active" && req.method === "GET") {
|
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
// The half-open TRANSIENT-HOLD lease (#4701). Not the quota-cooldown probe lease imported from
|
|
52
52
|
// ./routing above -- different module, different domain, and a request never holds both.
|
|
53
53
|
import { releaseTransientProbe } from "../routing/probe-lease";
|
|
54
|
+
import { trackBackgroundWork } from "../lib/background-work";
|
|
54
55
|
import {
|
|
55
56
|
codexConversationIdentity,
|
|
56
57
|
recordCodexThreadLineage,
|
|
@@ -66,7 +67,8 @@ import {
|
|
|
66
67
|
import { ACCOUNT_GATED_NATIVE_OPENAI_MODELS, NATIVE_RESERVE_MODEL } from "./catalog/native-models";
|
|
67
68
|
import type { CodexCooldownSource, CodexQuotaScope } from "./routing";
|
|
68
69
|
import { maskAccountId } from "../lib/privacy";
|
|
69
|
-
import { formatErrorResponse } from "../bridge";
|
|
70
|
+
import { formatErrorResponse, formatUsageLimitReachedResponse } from "../bridge";
|
|
71
|
+
import { nextCodexPoolRecoveryAt } from "./routing/pool-recovery";
|
|
70
72
|
import { CODEX_UNKNOWN_USAGE_SCORE, getAccountQuota, getMainPolicyQuota, parseUsageQuota, parseMainPolicyUsageQuota, setAccountQuotaFromParsed } from "./quota";
|
|
71
73
|
import { codexAccountUsesCreditsAfterLimit, codexUsageLimitResetAt } from "./account-credit-use";
|
|
72
74
|
import type { CodexAccountMode, OcxConfig, OcxProviderConfig } from "../types";
|
|
@@ -523,6 +525,11 @@ export class CodexAccountCooldownError extends Error {
|
|
|
523
525
|
|
|
524
526
|
export class CodexMainAccountHardLockError extends CodexAccountCooldownError {
|
|
525
527
|
readonly resetAt?: number;
|
|
528
|
+
/**
|
|
529
|
+
* Earliest known recovery of another stored pool account, set only when an unpinned pool
|
|
530
|
+
* selection reached main (#7046). A hint for the client's retry time; never routing input.
|
|
531
|
+
*/
|
|
532
|
+
poolRecoveryAt?: number;
|
|
526
533
|
|
|
527
534
|
constructor(resetAt?: number, thresholds = resolveMainAccountHardLockThresholds(undefined)) {
|
|
528
535
|
super(MAIN_CODEX_ACCOUNT_ID, resetAt ?? 0);
|
|
@@ -935,16 +942,40 @@ export function cooldownErrorMessage(err: CodexAccountCooldownError, accountSele
|
|
|
935
942
|
+ ` (source: ${err.cooldownSource ?? "default"}).${recovery}`;
|
|
936
943
|
}
|
|
937
944
|
|
|
945
|
+
/**
|
|
946
|
+
* The soonest a main hard-lock refusal is worth retrying: a sibling pool account's known
|
|
947
|
+
* recovery when it comes first, otherwise main's own reset (#7046). A pool hint is held to at
|
|
948
|
+
* least {@link MAIN_HARD_LOCK_MIN_POOL_RETRY_MS} so a hold about to lapse cannot turn the refusal
|
|
949
|
+
* into a tight retry loop. Null when neither time is known and still in the future.
|
|
950
|
+
*/
|
|
951
|
+
export const MAIN_HARD_LOCK_MIN_POOL_RETRY_MS = 60_000;
|
|
952
|
+
export function mainHardLockRetryAt(err: CodexMainAccountHardLockError, now: number): number | null {
|
|
953
|
+
const future = (value: number | undefined): number | null =>
|
|
954
|
+
typeof value === "number" && Number.isFinite(value) && value > now ? value : null;
|
|
955
|
+
const main = future(err.resetAt);
|
|
956
|
+
const pool = future(err.poolRecoveryAt);
|
|
957
|
+
const poolRetryAt = pool === null ? null : Math.max(pool, now + MAIN_HARD_LOCK_MIN_POOL_RETRY_MS);
|
|
958
|
+
if (poolRetryAt !== null && (main === null || poolRetryAt < main)) return poolRetryAt;
|
|
959
|
+
return main;
|
|
960
|
+
}
|
|
961
|
+
|
|
938
962
|
/** HTTP form of {@link cooldownErrorMessage}, carrying Retry-After for well-behaved clients. */
|
|
939
963
|
export function cooldownErrorResponse(
|
|
940
964
|
err: CodexAccountCooldownError,
|
|
941
965
|
now = Date.now(),
|
|
942
966
|
accountSelector?: string,
|
|
943
967
|
): Response {
|
|
968
|
+
if (err instanceof CodexMainAccountHardLockError) {
|
|
969
|
+
// A local policy refusal with a known horizon, not a transient throttle (#7046): Codex keeps
|
|
970
|
+
// a Goal active and sleeps through `rate_limit_exceeded` for the whole Retry-After, but
|
|
971
|
+
// stops on `usage_limit_reached` and shows the reset. The guard itself is unchanged.
|
|
972
|
+
const retryAt = mainHardLockRetryAt(err, now);
|
|
973
|
+
return formatUsageLimitReachedResponse(cooldownErrorMessage(err, accountSelector), retryAt,
|
|
974
|
+
retryAt === null ? null : Math.max(1, Math.ceil((retryAt - now) / 1000)));
|
|
975
|
+
}
|
|
944
976
|
const res = formatErrorResponse(429, "rate_limit_error", cooldownErrorMessage(err, accountSelector));
|
|
945
977
|
const headers = new Headers(res.headers);
|
|
946
|
-
if (!(err instanceof CodexReserveUnavailableError)
|
|
947
|
-
&& (!(err instanceof CodexMainAccountHardLockError) || err.resetAt !== undefined)) {
|
|
978
|
+
if (!(err instanceof CodexReserveUnavailableError)) {
|
|
948
979
|
headers.set("Retry-After", String(Math.max(1, Math.ceil((err.cooldownUntil - now) / 1000))));
|
|
949
980
|
}
|
|
950
981
|
return new Response(res.body, { status: res.status, headers });
|
|
@@ -1044,11 +1075,51 @@ async function awaitMainAccountPolicyBindingSettled(
|
|
|
1044
1075
|
if (!settled) throw new CodexMainProfileDrainingError();
|
|
1045
1076
|
}
|
|
1046
1077
|
|
|
1078
|
+
/**
|
|
1079
|
+
* Resolve the credential for one Codex turn. A main hard-lock reached through unpinned pool
|
|
1080
|
+
* selection is annotated with the earliest known pool recovery, so the refusal can name a retry
|
|
1081
|
+
* time that a sibling account may meet (#7046).
|
|
1082
|
+
*/
|
|
1047
1083
|
export async function resolveCodexAuthContext(
|
|
1048
1084
|
headers: Headers,
|
|
1049
1085
|
config: OcxConfig,
|
|
1050
1086
|
mode: CodexAccountMode,
|
|
1051
1087
|
options: ResolveCodexAuthContextOptions = {},
|
|
1088
|
+
): Promise<CodexAuthContext> {
|
|
1089
|
+
// Filled by the inner resolver with the request's own fenced model evidence, so the hint obeys it.
|
|
1090
|
+
const eligibility: RecoveryHintEligibility = {};
|
|
1091
|
+
try {
|
|
1092
|
+
return await resolveCodexAuthContextUnannotated(headers, config, mode, options, eligibility);
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
if (error instanceof CodexMainAccountHardLockError && mode === "pool" && options.accountId === undefined
|
|
1095
|
+
&& options.excludeAccountId !== MAIN_CODEX_ACCOUNT_ID
|
|
1096
|
+
&& !requiresReserveAuthorization(options.codexAuthPolicy ?? config, options.modelId, options.admission)
|
|
1097
|
+
// An account-gated model with no resolved roster has no known eligible sibling.
|
|
1098
|
+
&& (!options.modelId || !ACCOUNT_GATED_NATIVE_OPENAI_MODELS.has(options.modelId)
|
|
1099
|
+
|| eligibility.modelEligibleAccountIds !== undefined)) {
|
|
1100
|
+
try {
|
|
1101
|
+
const now = Date.now();
|
|
1102
|
+
error.poolRecoveryAt = nextCodexPoolRecoveryAt(config, now, codexQuotaScopeForModel(options.modelId), {
|
|
1103
|
+
deniedAccountIds: eligibility.deniedModelAccountIds,
|
|
1104
|
+
eligibleAccountIds: eligibility.modelEligibleAccountIds,
|
|
1105
|
+
});
|
|
1106
|
+
} catch { /* the hint is optional; the refusal stands without it */ }
|
|
1107
|
+
}
|
|
1108
|
+
throw error;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
interface RecoveryHintEligibility {
|
|
1113
|
+
modelEligibleAccountIds?: ReadonlySet<string>;
|
|
1114
|
+
deniedModelAccountIds?: ReadonlySet<string>;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
async function resolveCodexAuthContextUnannotated(
|
|
1118
|
+
headers: Headers,
|
|
1119
|
+
config: OcxConfig,
|
|
1120
|
+
mode: CodexAccountMode,
|
|
1121
|
+
options: ResolveCodexAuthContextOptions = {},
|
|
1122
|
+
eligibility: RecoveryHintEligibility = {},
|
|
1052
1123
|
): Promise<CodexAuthContext> {
|
|
1053
1124
|
const writerGeneration = captureConfigGeneration();
|
|
1054
1125
|
const policy = options.codexAuthPolicy ?? config;
|
|
@@ -1256,6 +1327,7 @@ export async function resolveCodexAuthContext(
|
|
|
1256
1327
|
const modelEligibleAccountIds = entitledAccountIds
|
|
1257
1328
|
? new Set([...entitledAccountIds].filter(candidate => !excludeAccountIds?.has(candidate)))
|
|
1258
1329
|
: undefined;
|
|
1330
|
+
eligibility.modelEligibleAccountIds = modelEligibleAccountIds;
|
|
1259
1331
|
// #4768: the flagships stay visible and never fail closed, so this is evidence routing may
|
|
1260
1332
|
// ORDER by, not evidence it may refuse on. Read synchronously from rosters discovery has
|
|
1261
1333
|
// already gathered -- no upstream fetch joins the request path for the most commonly
|
|
@@ -1273,6 +1345,7 @@ export async function resolveCodexAuthContext(
|
|
|
1273
1345
|
undefined,
|
|
1274
1346
|
{ excludeAccountIds },
|
|
1275
1347
|
);
|
|
1348
|
+
eligibility.deniedModelAccountIds = deniedModelAccountIds;
|
|
1276
1349
|
const selectionOptions = {
|
|
1277
1350
|
// Temporary switch drain keeps the candidate until the atomic claim rejects
|
|
1278
1351
|
// it. Retained recovery makes main wholly ineligible so pool routing continues.
|
|
@@ -1475,9 +1548,10 @@ export async function resolveCodexAuthContext(
|
|
|
1475
1548
|
if (options.primeCodexPoolQuotas) {
|
|
1476
1549
|
void options.primeCodexPoolQuotas(config, reason).catch(() => {});
|
|
1477
1550
|
} else {
|
|
1478
|
-
import
|
|
1551
|
+
// Tracked from the import on: the prime registers itself only once the module resolves.
|
|
1552
|
+
void trackBackgroundWork(`codex-pool-quota-prime-import:${reason}`, import("./auth-api")
|
|
1479
1553
|
.then(({ primeCodexPoolQuotas }) => primeCodexPoolQuotas(config, reason))
|
|
1480
|
-
.catch(() => {});
|
|
1554
|
+
.catch(() => {}));
|
|
1481
1555
|
}
|
|
1482
1556
|
}
|
|
1483
1557
|
// Snapshot (not just the deadline) so a refused request can report WHY it is cooled:
|
|
@@ -35,6 +35,7 @@ export interface StartupHealthInputs {
|
|
|
35
35
|
shimHealthy: boolean;
|
|
36
36
|
platform: NodeJS.Platform;
|
|
37
37
|
diagnosticStale?: boolean;
|
|
38
|
+
shimActivationPending?: boolean;
|
|
38
39
|
routingAdoption?: RoutingAdoptionEvidence;
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -55,6 +56,8 @@ export interface StartupHealth {
|
|
|
55
56
|
serviceConflict: boolean;
|
|
56
57
|
shimInstalled: boolean;
|
|
57
58
|
shimHealthy: boolean;
|
|
59
|
+
/** Intact PATH-overlay shim this process could not see active; activate it rather than repair it. */
|
|
60
|
+
shimActivationPending?: boolean;
|
|
58
61
|
shimCoverage: ShimCoverage;
|
|
59
62
|
serviceSupported: boolean;
|
|
60
63
|
platform: NodeJS.Platform;
|
|
@@ -171,6 +174,7 @@ export function collectStartupHealth(
|
|
|
171
174
|
serviceSupported: service.supported,
|
|
172
175
|
shimInstalled: shim.installed,
|
|
173
176
|
shimHealthy: shim.healthy,
|
|
177
|
+
...(shim.activationPending ? { shimActivationPending: true } : {}),
|
|
174
178
|
platform: process.platform,
|
|
175
179
|
...(routingAdoption ? { routingAdoption } : {}),
|
|
176
180
|
});
|
|
@@ -215,7 +219,9 @@ function classifyStartupHealthSummary(health: StartupHealth): string {
|
|
|
215
219
|
? "custom remote Codex routing (no local restart dependency)"
|
|
216
220
|
: "native Codex routing (no opencodex restart dependency)";
|
|
217
221
|
if (health.protection === "service") return "protected by background service";
|
|
218
|
-
if (health.protection === "desktop") return
|
|
222
|
+
if (health.protection === "desktop") return health.desktop?.loginRegistration === "registered-not-loaded"
|
|
223
|
+
? "protected by desktop app at next login and its proxy supervisor (Start at Login registered; not loaded in this session)"
|
|
224
|
+
: "protected by desktop app at login and its proxy supervisor";
|
|
219
225
|
const command = health.recommendedCommand ?? health.commands.restoreNative;
|
|
220
226
|
if (health.routingKind === "unknown") return `AT RISK after restart (Codex routing could not be verified; run '${command}')`;
|
|
221
227
|
if (health.routingKind === "custom-local") return `AT RISK after restart (custom local gateway lifecycle is not managed by opencodex; run '${command}')`;
|
|
@@ -48,7 +48,8 @@ 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 {
|
|
51
|
+
import { applyConfiguredUltraFast } from "./configured-tiers";
|
|
52
|
+
import { applyNativeUltraFastTier } from "./native-ultrafast";
|
|
52
53
|
|
|
53
54
|
export interface ObservedCatalogEntryBuildInput {
|
|
54
55
|
readonly template: RawEntry | null;
|
|
@@ -473,6 +474,8 @@ export function applyFullModelPickerOrder(entries: RawEntry[], order: readonly s
|
|
|
473
474
|
export interface ObservedCatalogMergeInput {
|
|
474
475
|
/** Preserve operator-supplied Ultra Fast only from the exact persisted routed row. */
|
|
475
476
|
readonly ultraFastTier?: boolean;
|
|
477
|
+
/** Current captured wire declarations; disk rows and templates never populate this set. */
|
|
478
|
+
readonly configuredUltraFastSlugs?: ReadonlySet<string>;
|
|
476
479
|
readonly catalogModels: readonly RawEntry[];
|
|
477
480
|
readonly baselineCatalogModels: readonly RawEntry[];
|
|
478
481
|
readonly routedEntries: readonly RawEntry[];
|
|
@@ -514,6 +517,7 @@ export interface ObservedCatalogMergeInput {
|
|
|
514
517
|
*/
|
|
515
518
|
export function mergeCatalogEntriesFromObservedState({
|
|
516
519
|
ultraFastTier = false,
|
|
520
|
+
configuredUltraFastSlugs = new Set(),
|
|
517
521
|
catalogModels,
|
|
518
522
|
baselineCatalogModels,
|
|
519
523
|
routedEntries,
|
|
@@ -865,7 +869,7 @@ export function mergeCatalogEntriesFromObservedState({
|
|
|
865
869
|
if (typeof entry.slug !== "string" || !entry.slug.includes("/")
|
|
866
870
|
|| isNativeAliasCatalogEntry(entry) || isExactComboCatalogEntry(entry, exactComboSlugs)
|
|
867
871
|
|| trustedAccountBoundNativeCatalogSlug(entry) !== undefined) continue;
|
|
868
|
-
|
|
872
|
+
applyConfiguredUltraFast(entry, operatorRows.get(entry.slug), configuredUltraFastSlugs.has(entry.slug), ultraFastTier);
|
|
869
873
|
}
|
|
870
874
|
const finalRoutedEntrySet = new Set(finalRoutedEntries);
|
|
871
875
|
const degradedPreservedCount = preservedRoutedEntries.filter(entry => {
|
|
@@ -932,6 +936,11 @@ export function mergeCatalogEntriesFromObservedState({
|
|
|
932
936
|
);
|
|
933
937
|
}
|
|
934
938
|
}
|
|
939
|
+
// Native Astra/Sol rows (bare and account-bound) gain or lose the documented Ultrafast
|
|
940
|
+
// choice with the switch; aliases, combos and reserve projections keep their own tiers.
|
|
941
|
+
if (!reserveProjection && !exactCombo && m.owned_by !== COMBO_NAMESPACE && !isNativeAliasCatalogEntry(e)) {
|
|
942
|
+
applyNativeUltraFastTier(e, ultraFastTier);
|
|
943
|
+
}
|
|
935
944
|
if (wsEnabled) e.supports_websockets = true;
|
|
936
945
|
else {
|
|
937
946
|
delete e.supports_websockets;
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
import type { NormalizedComboConfig } from "../../combos/types";
|
|
29
29
|
import { providerDestinationResolvedError } from "../../lib/destination-policy";
|
|
30
30
|
import { redactSecretString } from "../../lib/redact";
|
|
31
|
+
import { isPassiveRead } from "../../lib/passive-read";
|
|
31
32
|
import upstreamModelsSnapshot from "../data/upstream-models.json";
|
|
32
33
|
import { decodeOverlayState } from "../shim-state-file";
|
|
33
34
|
|
|
@@ -313,7 +314,8 @@ function* bundledCatalogSteps(
|
|
|
313
314
|
const output = yield command;
|
|
314
315
|
if (!current()) return null;
|
|
315
316
|
const catalog = output === null ? null : parseCatalogJson(output);
|
|
316
|
-
if (catalog &&
|
|
317
|
+
if (catalog && catalog.models?.every(row => row !== null && typeof row === "object" && !Array.isArray(row))
|
|
318
|
+
&& findNativeTemplate(catalog)) {
|
|
317
319
|
if (cacheKey) {
|
|
318
320
|
publishBundledCatalogCache(
|
|
319
321
|
cacheKey,
|
|
@@ -377,6 +379,8 @@ export function loadBundledCodexCatalogAsync(
|
|
|
377
379
|
if (useCache && bundledCatalogFlight?.inputs === inputs && bundledCatalogFlight.epoch === epoch) {
|
|
378
380
|
return view(bundledCatalogFlight.promise);
|
|
379
381
|
}
|
|
382
|
+
// A passive read (a Remote Link Child's, #6959) runs no Codex binary and persists no runtime.
|
|
383
|
+
if (isPassiveRead()) return Promise.resolve(null);
|
|
380
384
|
const controller = new AbortController();
|
|
381
385
|
const flight = { inputs, epoch, controller, promise: Promise.resolve<ReadonlyRawCatalog | null>(null) };
|
|
382
386
|
if (useCache && !signal && isCurrent === catalogAlwaysCurrent) {
|