@iislee/opencodex 2.11.0 → 2.31.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/AGENTS_INSTALL.md +109 -0
- package/README.md +114 -19
- package/bin/ocx.mjs +164 -36
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-BF38heuV.js +104 -0
- package/gui/dist/assets/index-DMiI18Kv.css +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +19 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +376 -52
- package/src/adapters/base.ts +54 -7
- package/src/adapters/client-fingerprint.ts +18 -12
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +601 -0
- package/src/adapters/cursor/checkpoint-store.ts +303 -0
- package/src/adapters/cursor/cursor-errors.ts +129 -6
- package/src/adapters/cursor/discovery.ts +61 -4
- package/src/adapters/cursor/effort-map.ts +27 -3
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/live-models.ts +174 -58
- package/src/adapters/cursor/live-transport.ts +609 -170
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-fs.ts +10 -7
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +0 -1
- package/src/adapters/cursor/native-exec.ts +101 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +383 -65
- package/src/adapters/cursor/request-builder.ts +237 -31
- package/src/adapters/cursor/tool-definitions.ts +242 -12
- package/src/adapters/cursor/tool-result-normalize.ts +92 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +28 -1
- package/src/adapters/cursor.ts +133 -7
- package/src/adapters/google-antigravity-replay.ts +558 -34
- package/src/adapters/google-antigravity-wire.ts +43 -10
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +618 -74
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +10 -1
- package/src/adapters/kiro.ts +92 -54
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1311 -336
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +830 -56
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +104 -21
- package/src/adapters/xai-web-search.ts +185 -0
- package/src/bridge.ts +428 -61
- package/src/chat/inbound.ts +43 -19
- package/src/chat/outbound.ts +82 -26
- package/src/claude/agents-inject.ts +32 -9
- package/src/claude/context-windows.ts +21 -5
- package/src/claude/desktop-3p.ts +243 -9
- package/src/claude/gateway-cache.ts +41 -4
- package/src/claude/inbound.ts +72 -3
- package/src/claude/model-info.ts +38 -15
- package/src/claude/outbound.ts +70 -16
- package/src/cli/account-api.ts +35 -1
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +389 -2
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +30 -6
- package/src/cli/agent.ts +45 -1
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +29 -4
- package/src/cli/claude.ts +103 -34
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +8 -0
- package/src/cli/config-command.ts +74 -10
- package/src/cli/dispatch.ts +593 -0
- package/src/cli/doctor.ts +315 -43
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +20 -271
- package/src/cli/index.ts +378 -519
- package/src/cli/init.ts +4 -17
- package/src/cli/integrations.ts +120 -2
- package/src/cli/lab.ts +607 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/minimax.ts +497 -0
- package/src/cli/models-runtime.ts +35 -2
- package/src/cli/models.ts +100 -14
- package/src/cli/observe.ts +92 -3
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +18 -1
- package/src/cli/provider.ts +24 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +437 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +6 -3
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +10 -3
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/v2.ts +105 -10
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1358 -21
- package/src/codex/account-label.ts +14 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/account-store.ts +29 -2
- package/src/codex/account-usability.ts +25 -2
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/app-server-processes.ts +493 -106
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +849 -242
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +345 -32
- package/src/codex/autostart-health.ts +8 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +68 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +121 -30
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +529 -45
- package/src/codex/catalog/native-models.ts +72 -0
- package/src/codex/catalog/parsing.ts +224 -30
- package/src/codex/catalog/provider-fetch.ts +1460 -134
- package/src/codex/catalog/sync.ts +1449 -186
- package/src/codex/catalog-admission.ts +199 -0
- package/src/codex/catalog-refresh-status.ts +105 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +6 -3
- package/src/codex/codex-write-lock.ts +384 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +651 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +407 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-migration-guardian.ts +26 -20
- package/src/codex/history-provider.ts +231 -28
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +220 -0
- package/src/codex/inject-coordination.ts +290 -0
- package/src/codex/inject.ts +1073 -152
- package/src/codex/injected-marker.ts +37 -3
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +80 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +506 -0
- package/src/codex/log-guard/lock.ts +150 -0
- package/src/codex/log-guard/maintenance.ts +403 -0
- package/src/codex/log-guard/path-safety.ts +88 -0
- package/src/codex/log-guard/policy.ts +44 -0
- package/src/codex/log-guard/processes.ts +205 -0
- package/src/codex/log-guard/protection.ts +489 -0
- package/src/codex/log-guard/sqlite-errors.ts +9 -0
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/main-account.ts +29 -1
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +56 -10
- package/src/codex/model-entitlements.ts +353 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +178 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +492 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +682 -0
- package/src/codex/paths.ts +80 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +1 -1
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/project-config-warnings.ts +20 -6
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +175 -13
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +505 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1009 -28
- package/src/codex/subagent-model-fallback.ts +350 -35
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +612 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +187 -81
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +20 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +81 -9
- package/src/config/provider-name.ts +24 -0
- package/src/config.ts +1762 -140
- package/src/generated/compatibility-version.json +3164 -0
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
- package/src/grok/inject.ts +16 -5
- package/src/grok/inspect.ts +45 -0
- package/src/grok/sync.ts +2 -2
- package/src/images/loop.ts +152 -29
- package/src/images/plan.ts +23 -13
- package/src/integrations/config-io.ts +269 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/mutation-flight.ts +71 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/omp-yaml-source.ts +358 -0
- package/src/integrations/owned-refresh.ts +74 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +159 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +361 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +691 -0
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +499 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +64 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +164 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +68 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +214 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +229 -0
- package/src/lab/events/validate.ts +781 -0
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +489 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +54 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +310 -0
- package/src/lab/ledger/store.ts +532 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +336 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +118 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +301 -0
- package/src/lab/observe/from-live.ts +117 -0
- package/src/lab/paths.ts +153 -0
- package/src/lab/projection/rebuild.ts +495 -0
- package/src/lab/projection/schema.ts +135 -0
- package/src/lab/projection/verdicts.ts +474 -0
- package/src/lab/projection/verification.ts +412 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +243 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lab/query/catalog.ts +101 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +45 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +112 -0
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +74 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +153 -9
- package/src/lib/bun-runtime.ts +125 -12
- package/src/lib/bun-stream-caps.ts +13 -9
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +6 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +44 -2
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-activation.ts +223 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +56 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/local-provider-reload-contract.ts +100 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +4 -1
- package/src/lib/provider-outbound.ts +49 -9
- package/src/lib/redact.ts +419 -3
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/shadow-call.ts +35 -4
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/state-store-registrations.ts +10 -2
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/token-estimate.ts +19 -2
- package/src/lib/tool-argument-integers.ts +202 -0
- package/src/lib/translator-budget.ts +44 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +156 -3
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-elevation.ts +70 -2
- package/src/lib/windows-secret-acl.ts +409 -69
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/lib/windows-text.ts +106 -0
- package/src/lib/windows-user-principal.ts +341 -0
- package/src/lib/winsw.ts +33 -5
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +8 -2
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +398 -66
- package/src/oauth/key-providers.ts +16 -0
- package/src/oauth/kimi.ts +16 -2
- package/src/oauth/kiro.ts +50 -6
- package/src/oauth/local-token-detect.ts +11 -2
- package/src/oauth/log.ts +3 -1
- package/src/oauth/login-cli.ts +88 -28
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/store.ts +119 -21
- package/src/oauth/token-guardian.ts +9 -3
- package/src/pi/models.ts +2 -2
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/antigravity-models.ts +521 -31
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +144 -0
- package/src/providers/context-cap.ts +22 -5
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/derive.ts +253 -6
- package/src/providers/fastwire.ts +501 -0
- package/src/providers/free-directory.ts +10 -7
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/label.ts +1 -1
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/openai-sidecar.ts +72 -4
- package/src/providers/openai-tier-startup.ts +31 -2
- package/src/providers/openai-tiers.ts +119 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +29 -0
- package/src/providers/quota.ts +1319 -38
- package/src/providers/registry.ts +1429 -111
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +42 -6
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/providers/xai-responses-opt-in.ts +15 -0
- package/src/providers/xai-transport.ts +11 -4
- package/src/reasoning-effort.ts +49 -1
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +266 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +355 -0
- package/src/responses/parser.ts +220 -38
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +426 -0
- package/src/responses/schema.ts +7 -1
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +565 -27
- package/src/responses/thought-signature-replay.ts +347 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/router.ts +366 -30
- package/src/routing/analytics.ts +378 -0
- package/src/routing/capability.ts +244 -0
- package/src/routing/compatibility/assemble.ts +73 -0
- package/src/routing/compatibility/behavior.ts +278 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +495 -0
- package/src/routing/health.ts +412 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +547 -0
- package/src/routing/quota.ts +145 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +2 -29
- package/src/server/auth-cors.ts +267 -46
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +130 -56
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +426 -0
- package/src/server/claude-messages.ts +159 -43
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +18 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +94 -12
- package/src/server/index.ts +865 -181
- package/src/server/lifecycle.ts +284 -13
- package/src/server/live.ts +136 -17
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/local-provider-reload-client.ts +137 -0
- package/src/server/management/agent-settings-routes.ts +398 -103
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/combo-routes.ts +62 -24
- package/src/server/management/config-routes.ts +464 -51
- package/src/server/management/context.ts +80 -2
- package/src/server/management/integration-routes.ts +498 -0
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +563 -0
- package/src/server/management/logs-usage-routes.ts +101 -32
- package/src/server/management/model-routes.ts +189 -131
- package/src/server/management/model-rows.ts +163 -0
- package/src/server/management/native-integration-routes.ts +769 -0
- package/src/server/management/oauth-account-routes.ts +80 -4
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +764 -157
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +380 -0
- package/src/server/management/shared.ts +27 -11
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/storage-log-guard-routes.ts +186 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +288 -32
- package/src/server/management/system-routes.ts +77 -0
- package/src/server/management/usage-summary-cache.ts +9 -1
- package/src/server/management/vision-sidecar-options.ts +167 -0
- package/src/server/management/web-search-sidecar-options.ts +120 -0
- package/src/server/management-api.ts +115 -14
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +132 -5
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +82 -42
- package/src/server/relay.ts +236 -76
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log.ts +235 -22
- package/src/server/responses/agent-task-recovery-cache.ts +143 -0
- package/src/server/responses/agent-task-recovery.ts +465 -0
- package/src/server/responses/collaboration.ts +204 -35
- package/src/server/responses/compact.ts +442 -55
- package/src/server/responses/core.ts +2872 -331
- package/src/server/responses/empty-completion-guard.ts +276 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +79 -4
- package/src/server/responses/input-admission.ts +185 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +251 -0
- package/src/server/responses/terminal-guard.ts +26 -5
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses/ws-upstream.ts +308 -0
- package/src/server/responses-custom-tool-repair.ts +282 -0
- package/src/server/responses-item-id-repair.ts +54 -6
- package/src/server/responses-json-events.ts +90 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses-terminal-repair.ts +342 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +153 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +110 -22
- package/src/server/startup-action-control.ts +8 -1
- package/src/server/startup-health-cache.ts +19 -1
- package/src/server/system-env.ts +80 -9
- package/src/server/ws-bridge.ts +39 -38
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1111 -90
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/scanner.ts +1 -1
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +83 -9
- package/src/tray/windows.ts +43 -16
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +845 -0
- package/src/types/provider.ts +545 -0
- package/src/types/request.ts +384 -0
- package/src/types/tools.ts +131 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +104 -1236
- package/src/update/index.ts +32 -19
- package/src/update/job.ts +442 -67
- package/src/update/notify.ts +12 -6
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +259 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +268 -16
- package/src/usage/log.ts +606 -41
- package/src/usage/summary.ts +177 -9
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +9 -3
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +238 -24
- package/src/vision/reasoning.ts +55 -0
- package/src/vision/routed-describe.ts +175 -0
- package/src/vision/timeout-bounds.ts +9 -0
- package/src/web-search/anthropic-executor.ts +13 -7
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +88 -0
- package/src/web-search/executor.ts +11 -3
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +279 -50
- package/src/web-search/parse.ts +125 -30
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/xai-executor.ts +219 -0
- package/gui/dist/assets/index-DTpMHS4F.js +0 -67
- package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
- package/src/cli/internal-dispatch.ts +0 -20
|
@@ -0,0 +1,1044 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CodexPreStreamRejection,
|
|
3
|
+
CodexResetEligibleExhaustionCode,
|
|
4
|
+
} from "./quota-rejection";
|
|
5
|
+
import { isValidCodexAccountId, MAIN_CODEX_ACCOUNT_ID } from "./account-id";
|
|
6
|
+
|
|
7
|
+
export type CodexResetCreditRecoveryGeneration = Readonly<{
|
|
8
|
+
accountId: string;
|
|
9
|
+
credentialGeneration: number;
|
|
10
|
+
exhaustionGeneration: number;
|
|
11
|
+
}>;
|
|
12
|
+
|
|
13
|
+
export type CodexResetCreditRevalidationResult =
|
|
14
|
+
| Readonly<{
|
|
15
|
+
kind: "eligible";
|
|
16
|
+
accountId: string;
|
|
17
|
+
credentialGeneration: number;
|
|
18
|
+
exhaustionGeneration: number;
|
|
19
|
+
availableCredits: number;
|
|
20
|
+
}>
|
|
21
|
+
| { kind: "stale-generation" }
|
|
22
|
+
| { kind: "no-credit" };
|
|
23
|
+
|
|
24
|
+
export type CodexResetCreditConsumeCode =
|
|
25
|
+
| "reset"
|
|
26
|
+
| "already_redeemed"
|
|
27
|
+
| "nothing_to_reset"
|
|
28
|
+
| "no_credit";
|
|
29
|
+
|
|
30
|
+
export type CodexResetCreditRecoveryAuthorization = Readonly<{
|
|
31
|
+
enabled: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Live commitment guard; checked initially, before dispatch, and on completion.
|
|
34
|
+
* Once it reports true, the adapter must keep reporting true for this turn.
|
|
35
|
+
*/
|
|
36
|
+
isOutputExposed: () => boolean;
|
|
37
|
+
rejection: CodexPreStreamRejection;
|
|
38
|
+
}>;
|
|
39
|
+
|
|
40
|
+
export type CodexResetCreditDispatchOutcome =
|
|
41
|
+
| Readonly<{ kind: "refresh-required"; code: "reset" | "already_redeemed" }>
|
|
42
|
+
| Readonly<{ kind: "stopped"; code: "nothing_to_reset" | "no_credit" }>
|
|
43
|
+
| Readonly<{
|
|
44
|
+
kind: "ambiguous";
|
|
45
|
+
reason: "consume-failed" | "consume-timeout" | "invalid-outcome";
|
|
46
|
+
operationId: string;
|
|
47
|
+
}>;
|
|
48
|
+
|
|
49
|
+
export type CodexResetCreditRecoveryResult =
|
|
50
|
+
| CodexResetCreditDispatchOutcome
|
|
51
|
+
| Readonly<{
|
|
52
|
+
kind: "not-dispatched";
|
|
53
|
+
reason:
|
|
54
|
+
| "disabled"
|
|
55
|
+
| "output-already-exposed"
|
|
56
|
+
| "ineligible-rejection"
|
|
57
|
+
| "cancelled-before-dispatch"
|
|
58
|
+
| "stale-generation"
|
|
59
|
+
| "no-credit-after-revalidate"
|
|
60
|
+
| "revalidation-failed"
|
|
61
|
+
| "invalid-revalidation"
|
|
62
|
+
| "coordination-mismatch"
|
|
63
|
+
| "recovery-state-capacity"
|
|
64
|
+
| "operation-expired-before-dispatch";
|
|
65
|
+
}>
|
|
66
|
+
| Readonly<{
|
|
67
|
+
kind: "detached";
|
|
68
|
+
reason: "cancelled-after-dispatch" | "output-exposed-after-dispatch";
|
|
69
|
+
outcome: CodexResetCreditDispatchOutcome;
|
|
70
|
+
}>;
|
|
71
|
+
|
|
72
|
+
export interface CodexResetCreditLogicalTurn {
|
|
73
|
+
readonly operationId: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type CodexResetCreditRecoveryCapacity = Readonly<{
|
|
77
|
+
/** Outstanding adapter executions, including timed-out work that has not settled. */
|
|
78
|
+
activeFlights: number;
|
|
79
|
+
trackedAccounts: number;
|
|
80
|
+
}>;
|
|
81
|
+
|
|
82
|
+
export type CodexResetCreditRecoveryDependencies = {
|
|
83
|
+
/**
|
|
84
|
+
* Stable process-wide identity for one adapter contract. Coordinators may
|
|
85
|
+
* share a generation flight only when this identity and every executable
|
|
86
|
+
* dependency are identical.
|
|
87
|
+
*/
|
|
88
|
+
coordinationScope: object;
|
|
89
|
+
/** Revalidates both the supplied generations and current credit availability. */
|
|
90
|
+
revalidate: (
|
|
91
|
+
generation: CodexResetCreditRecoveryGeneration,
|
|
92
|
+
signal: AbortSignal,
|
|
93
|
+
) => Promise<unknown>;
|
|
94
|
+
/**
|
|
95
|
+
* Dispatches one irreversible consume operation. The normalized result must
|
|
96
|
+
* echo the supplied operationId so already_redeemed is generation-bound.
|
|
97
|
+
* Revalidation and consume adapters must settle promptly when their signal
|
|
98
|
+
* aborts; JavaScript cannot forcibly stop an adapter that ignores it.
|
|
99
|
+
*/
|
|
100
|
+
consume: (input: {
|
|
101
|
+
generation: CodexResetCreditRecoveryGeneration;
|
|
102
|
+
operationId: string;
|
|
103
|
+
signal: AbortSignal;
|
|
104
|
+
}) => Promise<unknown>;
|
|
105
|
+
/** Shared shutdown signal; callers may add a request/client signal per turn. */
|
|
106
|
+
lifecycleSignal?: AbortSignal;
|
|
107
|
+
/** Total revalidation plus consume deadline. Defaults to 10 seconds. */
|
|
108
|
+
operationTimeoutMs?: number;
|
|
109
|
+
/** Bounded consume transport attempts using the same operationId. Defaults to 2. */
|
|
110
|
+
maxConsumeAttempts?: number;
|
|
111
|
+
/** Count-only operational signal; account and operation identifiers are omitted. */
|
|
112
|
+
onCapacitySaturated?: (capacity: CodexResetCreditRecoveryCapacity) => void;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
type LogicalTurnState = {
|
|
116
|
+
attempt?: Promise<CodexResetCreditRecoveryResult>;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
type OutputGuardRegistration = Readonly<{
|
|
120
|
+
guard: () => boolean;
|
|
121
|
+
}>;
|
|
122
|
+
|
|
123
|
+
type RecoveryFlight = {
|
|
124
|
+
generation: CodexResetCreditRecoveryGeneration;
|
|
125
|
+
operationId: string;
|
|
126
|
+
contract: RecoveryContract;
|
|
127
|
+
activeWaiters: number;
|
|
128
|
+
dispatchStarted: boolean;
|
|
129
|
+
expired: boolean;
|
|
130
|
+
finished: boolean;
|
|
131
|
+
executionSettled: boolean;
|
|
132
|
+
discardedForTests: boolean;
|
|
133
|
+
preDispatchAbort: AbortController;
|
|
134
|
+
operationAbort: AbortController;
|
|
135
|
+
outputGuards: Set<OutputGuardRegistration>;
|
|
136
|
+
deadlineTimer?: ReturnType<typeof setTimeout>;
|
|
137
|
+
executionPromise?: Promise<Awaited<RecoveryFlight["promise"]>>;
|
|
138
|
+
resetForTestsResolve?: (result: Awaited<RecoveryFlight["promise"]>) => void;
|
|
139
|
+
promise: Promise<CodexResetCreditDispatchOutcome | Extract<
|
|
140
|
+
CodexResetCreditRecoveryResult,
|
|
141
|
+
{ kind: "not-dispatched" }
|
|
142
|
+
>>;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
type RecoveryContract = Readonly<{
|
|
146
|
+
coordinationScope: object;
|
|
147
|
+
revalidate: CodexResetCreditRecoveryDependencies["revalidate"];
|
|
148
|
+
consume: CodexResetCreditRecoveryDependencies["consume"];
|
|
149
|
+
lifecycleSignal?: AbortSignal;
|
|
150
|
+
operationTimeoutMs: number;
|
|
151
|
+
maxConsumeAttempts: number;
|
|
152
|
+
}>;
|
|
153
|
+
|
|
154
|
+
type TerminalGeneration = {
|
|
155
|
+
generation: CodexResetCreditRecoveryGeneration;
|
|
156
|
+
outcome: CodexResetCreditDispatchOutcome;
|
|
157
|
+
contract: RecoveryContract;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const DEFAULT_OPERATION_TIMEOUT_MS = 10_000;
|
|
161
|
+
export const MAX_OPERATION_TIMEOUT_MS = 60_000;
|
|
162
|
+
const DEFAULT_MAX_CONSUME_ATTEMPTS = 2;
|
|
163
|
+
export const MAX_TRACKED_RECOVERY_ACCOUNTS = 128;
|
|
164
|
+
export const MAX_TRACKED_RECOVERY_FLIGHTS = 128;
|
|
165
|
+
export const MAX_TRACKED_RECOVERY_WAITERS_PER_FLIGHT = 128;
|
|
166
|
+
const RESET_PROCESS_STATE_FOR_TESTS = Symbol("reset-credit-recovery-process-state-for-tests");
|
|
167
|
+
const OPERATION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
168
|
+
const ADD_EVENT_LISTENER = EventTarget.prototype.addEventListener;
|
|
169
|
+
const REMOVE_EVENT_LISTENER = EventTarget.prototype.removeEventListener;
|
|
170
|
+
|
|
171
|
+
function freezeResult<T extends CodexResetCreditRecoveryResult>(result: T): T {
|
|
172
|
+
return Object.freeze(result);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const CANCELLED_BEFORE_DISPATCH = freezeResult({
|
|
176
|
+
kind: "not-dispatched",
|
|
177
|
+
reason: "cancelled-before-dispatch",
|
|
178
|
+
} as const);
|
|
179
|
+
|
|
180
|
+
const CONSUME_CODES: ReadonlySet<string> = new Set([
|
|
181
|
+
"reset",
|
|
182
|
+
"already_redeemed",
|
|
183
|
+
"nothing_to_reset",
|
|
184
|
+
"no_credit",
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
const RESET_ELIGIBLE_CODES = {
|
|
188
|
+
usage_limit_exceeded: true,
|
|
189
|
+
insufficient_quota: true,
|
|
190
|
+
} as const satisfies Record<CodexResetEligibleExhaustionCode, true>;
|
|
191
|
+
|
|
192
|
+
function snapshotGeneration(input: unknown): CodexResetCreditRecoveryGeneration {
|
|
193
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
194
|
+
throw new TypeError("generation must be an object");
|
|
195
|
+
}
|
|
196
|
+
const value = input as Record<string, unknown>;
|
|
197
|
+
const hasOwn = Object.prototype.hasOwnProperty;
|
|
198
|
+
if (!hasOwn.call(value, "accountId")
|
|
199
|
+
|| !hasOwn.call(value, "credentialGeneration")
|
|
200
|
+
|| !hasOwn.call(value, "exhaustionGeneration")) {
|
|
201
|
+
throw new TypeError("generation fields must be own properties");
|
|
202
|
+
}
|
|
203
|
+
const accountId = value.accountId;
|
|
204
|
+
const credentialGeneration = value.credentialGeneration;
|
|
205
|
+
const exhaustionGeneration = value.exhaustionGeneration;
|
|
206
|
+
if (accountId !== MAIN_CODEX_ACCOUNT_ID && !isValidCodexAccountId(accountId)) {
|
|
207
|
+
throw new TypeError("accountId must be the main sentinel or a canonical pool-account id");
|
|
208
|
+
}
|
|
209
|
+
if (!Number.isSafeInteger(credentialGeneration) || Number(credentialGeneration) < 0) {
|
|
210
|
+
throw new TypeError("credentialGeneration must be a non-negative safe integer");
|
|
211
|
+
}
|
|
212
|
+
if (!Number.isSafeInteger(exhaustionGeneration) || Number(exhaustionGeneration) < 0) {
|
|
213
|
+
throw new TypeError("exhaustionGeneration must be a non-negative safe integer");
|
|
214
|
+
}
|
|
215
|
+
return Object.freeze({
|
|
216
|
+
accountId,
|
|
217
|
+
credentialGeneration: credentialGeneration as number,
|
|
218
|
+
exhaustionGeneration: exhaustionGeneration as number,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function snapshotRequestSignal(input: unknown): AbortSignal | undefined {
|
|
223
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
224
|
+
throw new TypeError("options must be an object");
|
|
225
|
+
}
|
|
226
|
+
const signal = (input as { signal?: unknown }).signal;
|
|
227
|
+
if (signal !== undefined && !(signal instanceof AbortSignal)) {
|
|
228
|
+
throw new TypeError("options.signal must be an AbortSignal");
|
|
229
|
+
}
|
|
230
|
+
return signal;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function generationKey(generation: CodexResetCreditRecoveryGeneration): string {
|
|
234
|
+
return JSON.stringify([
|
|
235
|
+
generation.accountId,
|
|
236
|
+
generation.credentialGeneration,
|
|
237
|
+
generation.exhaustionGeneration,
|
|
238
|
+
]);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function compareGenerationOrder(
|
|
242
|
+
left: CodexResetCreditRecoveryGeneration,
|
|
243
|
+
right: CodexResetCreditRecoveryGeneration,
|
|
244
|
+
): -1 | 0 | 1 {
|
|
245
|
+
if (left.credentialGeneration !== right.credentialGeneration) {
|
|
246
|
+
return left.credentialGeneration < right.credentialGeneration ? -1 : 1;
|
|
247
|
+
}
|
|
248
|
+
if (left.exhaustionGeneration !== right.exhaustionGeneration) {
|
|
249
|
+
return left.exhaustionGeneration < right.exhaustionGeneration ? -1 : 1;
|
|
250
|
+
}
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function authorizedResetRejection(authorization: CodexResetCreditRecoveryAuthorization): boolean {
|
|
255
|
+
const hasOwn = Object.prototype.hasOwnProperty;
|
|
256
|
+
if (!hasOwn.call(authorization, "enabled")
|
|
257
|
+
|| !hasOwn.call(authorization, "rejection")) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
const rejection = authorization.rejection as unknown;
|
|
261
|
+
if (!rejection || typeof rejection !== "object" || Array.isArray(rejection)) return false;
|
|
262
|
+
const value = rejection as Record<string, unknown>;
|
|
263
|
+
if (!hasOwn.call(value, "status")
|
|
264
|
+
|| !hasOwn.call(value, "kind")
|
|
265
|
+
|| !hasOwn.call(value, "resetCreditEligible")
|
|
266
|
+
|| !hasOwn.call(value, "semanticCode")) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
return authorization.enabled === true
|
|
270
|
+
&& (value.status === 429 || value.status === 402)
|
|
271
|
+
&& value.kind === "reset-eligible-exhaustion"
|
|
272
|
+
&& value.resetCreditEligible === true
|
|
273
|
+
&& typeof value.semanticCode === "string"
|
|
274
|
+
&& Object.hasOwn(RESET_ELIGIBLE_CODES, value.semanticCode);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function ownStringField(input: unknown, field: string): string | undefined {
|
|
278
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return undefined;
|
|
279
|
+
if (!Object.prototype.hasOwnProperty.call(input, field)) return undefined;
|
|
280
|
+
const value = (input as Record<string, unknown>)[field];
|
|
281
|
+
return typeof value === "string" ? value : undefined;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function revalidationKind(
|
|
285
|
+
input: unknown,
|
|
286
|
+
expected: CodexResetCreditRecoveryGeneration,
|
|
287
|
+
): CodexResetCreditRevalidationResult["kind"] | undefined {
|
|
288
|
+
const kind = ownStringField(input, "kind");
|
|
289
|
+
if (kind === "stale-generation" || kind === "no-credit") return kind;
|
|
290
|
+
if (kind !== "eligible" || !input || typeof input !== "object" || Array.isArray(input)) {
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
const value = input as Record<string, unknown>;
|
|
294
|
+
const hasOwn = Object.prototype.hasOwnProperty;
|
|
295
|
+
if (!hasOwn.call(value, "accountId")
|
|
296
|
+
|| !hasOwn.call(value, "credentialGeneration")
|
|
297
|
+
|| !hasOwn.call(value, "exhaustionGeneration")
|
|
298
|
+
|| !hasOwn.call(value, "availableCredits")) {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
const availableCredits = value.availableCredits;
|
|
302
|
+
return value.accountId === expected.accountId
|
|
303
|
+
&& value.credentialGeneration === expected.credentialGeneration
|
|
304
|
+
&& value.exhaustionGeneration === expected.exhaustionGeneration
|
|
305
|
+
&& typeof availableCredits === "number"
|
|
306
|
+
&& Number.isSafeInteger(availableCredits)
|
|
307
|
+
&& availableCredits > 0
|
|
308
|
+
? "eligible"
|
|
309
|
+
: undefined;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function outputIsExposed(guard: () => boolean): boolean {
|
|
313
|
+
try {
|
|
314
|
+
return guard() !== false;
|
|
315
|
+
} catch {
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function hasLiveUnexposedGuard(flight: RecoveryFlight): boolean {
|
|
321
|
+
// Set iteration also visits registrations appended by a re-entrant guard.
|
|
322
|
+
// A one-time snapshot could strand a newly joined, still-live waiter.
|
|
323
|
+
// Bound those live additions so a guard cannot extend this synchronous loop
|
|
324
|
+
// forever. Exhaustion fails closed before the irreversible dispatch.
|
|
325
|
+
let remainingVisits = flight.outputGuards.size * 2 + 1;
|
|
326
|
+
for (const registration of flight.outputGuards) {
|
|
327
|
+
if (remainingVisits <= 0) return false;
|
|
328
|
+
remainingVisits -= 1;
|
|
329
|
+
if (outputIsExposed(registration.guard)) continue;
|
|
330
|
+
// A guard may synchronously abort and unregister its own waiter. Its stale
|
|
331
|
+
// return value must not authorize a different, already-exposed waiter.
|
|
332
|
+
if (flight.outputGuards.has(registration)) return true;
|
|
333
|
+
}
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function expireRecoveryFlight(flight: RecoveryFlight): void {
|
|
338
|
+
flight.expired = true;
|
|
339
|
+
if (flight.dispatchStarted) flight.operationAbort.abort();
|
|
340
|
+
else flight.preDispatchAbort.abort();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function recoveryDeadlineExpired(flight: RecoveryFlight, deadlineAt: number): boolean {
|
|
344
|
+
if (!flight.expired && performance.now() < deadlineAt) return false;
|
|
345
|
+
expireRecoveryFlight(flight);
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function recoveryContractsMatch(left: RecoveryContract, right: RecoveryContract): boolean {
|
|
350
|
+
return left.coordinationScope === right.coordinationScope
|
|
351
|
+
&& left.revalidate === right.revalidate
|
|
352
|
+
&& left.consume === right.consume
|
|
353
|
+
&& left.lifecycleSignal === right.lifecycleSignal
|
|
354
|
+
&& left.operationTimeoutMs === right.operationTimeoutMs
|
|
355
|
+
&& left.maxConsumeAttempts === right.maxConsumeAttempts;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function consumeCode(
|
|
359
|
+
input: unknown,
|
|
360
|
+
expectedOperationId: string,
|
|
361
|
+
): CodexResetCreditConsumeCode | undefined {
|
|
362
|
+
const code = ownStringField(input, "code");
|
|
363
|
+
const operationId = ownStringField(input, "operationId");
|
|
364
|
+
return code && CONSUME_CODES.has(code) && operationId === expectedOperationId
|
|
365
|
+
? code as CodexResetCreditConsumeCode
|
|
366
|
+
: undefined;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function mapConsumeOutcome(
|
|
370
|
+
input: unknown,
|
|
371
|
+
operationId: string,
|
|
372
|
+
): CodexResetCreditDispatchOutcome {
|
|
373
|
+
const code = consumeCode(input, operationId);
|
|
374
|
+
if (code === "reset" || code === "already_redeemed") {
|
|
375
|
+
// Replay is not authorized here. The caller must first refresh authoritative
|
|
376
|
+
// quota state and separately prove that its replay capsule is still valid.
|
|
377
|
+
return freezeResult({ kind: "refresh-required", code });
|
|
378
|
+
}
|
|
379
|
+
if (code === "nothing_to_reset" || code === "no_credit") {
|
|
380
|
+
return freezeResult({ kind: "stopped", code });
|
|
381
|
+
}
|
|
382
|
+
return freezeResult({ kind: "ambiguous", reason: "invalid-outcome", operationId });
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function notDispatched(
|
|
386
|
+
reason: Extract<CodexResetCreditRecoveryResult, { kind: "not-dispatched" }>["reason"],
|
|
387
|
+
): Extract<CodexResetCreditRecoveryResult, { kind: "not-dispatched" }> {
|
|
388
|
+
return freezeResult({ kind: "not-dispatched", reason });
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function isDispatchOutcome(
|
|
392
|
+
result: CodexResetCreditRecoveryResult,
|
|
393
|
+
): result is CodexResetCreditDispatchOutcome {
|
|
394
|
+
return result.kind === "refresh-required"
|
|
395
|
+
|| result.kind === "stopped"
|
|
396
|
+
|| result.kind === "ambiguous";
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Proves reset-credit trigger, single-flight, idempotency, cancellation, and
|
|
401
|
+
* one-per-turn invariants without owning account selection, HTTP, or replay.
|
|
402
|
+
*
|
|
403
|
+
* Flight and terminal-generation registries are process-shared so independent
|
|
404
|
+
* request handlers cannot bypass the account/generation fence. This foundation
|
|
405
|
+
* is intentionally process-local; durable crash recovery belongs to the future
|
|
406
|
+
* HTTP adapter that persists and reuses the same operation identity. A future
|
|
407
|
+
* adapter for the main sentinel must advance or invalidate credentialGeneration
|
|
408
|
+
* whenever the physical ChatGPT identity changes.
|
|
409
|
+
*
|
|
410
|
+
* Terminal fences intentionally remain for the process lifetime. Time- or
|
|
411
|
+
* LRU-based eviction could re-authorize an uncertain irreversible consume. A
|
|
412
|
+
* runtime adapter must replace this bounded map with a durable generation-aware
|
|
413
|
+
* ledger before supporting more accounts; only an authoritative idempotency and
|
|
414
|
+
* replay-window contract may define safe retirement. Timed-out adapter work is
|
|
415
|
+
* retired from the public flight registry so its waiters settle, but remains in
|
|
416
|
+
* the orphan registry until the adapter settles. It still counts toward both
|
|
417
|
+
* MAX_TRACKED_RECOVERY_FLIGHTS and MAX_TRACKED_RECOVERY_ACCOUNTS, so an
|
|
418
|
+
* abort-ignoring adapter withholds admission instead of allowing unbounded work.
|
|
419
|
+
*/
|
|
420
|
+
export class CodexResetCreditRecoveryCoordinator {
|
|
421
|
+
private static readonly activeFlights = new Map<string, RecoveryFlight>();
|
|
422
|
+
/** Public flight promises that still participate in admission capacity. */
|
|
423
|
+
private static readonly allFlights = new Set<RecoveryFlight>();
|
|
424
|
+
/** Bounded bookkeeping for adapter executions that outlive public deadlines. */
|
|
425
|
+
private static readonly orphanedFlights = new Set<RecoveryFlight>();
|
|
426
|
+
private static readonly terminalByAccount = new Map<string, TerminalGeneration>();
|
|
427
|
+
|
|
428
|
+
private readonly logicalTurns = new WeakMap<CodexResetCreditLogicalTurn, LogicalTurnState>();
|
|
429
|
+
private readonly operationTimeoutMs: number;
|
|
430
|
+
private readonly maxConsumeAttempts: number;
|
|
431
|
+
private readonly contract: RecoveryContract;
|
|
432
|
+
private readonly onCapacitySaturated?: CodexResetCreditRecoveryDependencies["onCapacitySaturated"];
|
|
433
|
+
|
|
434
|
+
constructor(dependencies: CodexResetCreditRecoveryDependencies) {
|
|
435
|
+
const coordinationScope = dependencies.coordinationScope;
|
|
436
|
+
if (coordinationScope === null
|
|
437
|
+
|| (typeof coordinationScope !== "object"
|
|
438
|
+
&& typeof coordinationScope !== "function")) {
|
|
439
|
+
throw new TypeError("coordinationScope must be a stable object identity");
|
|
440
|
+
}
|
|
441
|
+
const revalidate = dependencies.revalidate;
|
|
442
|
+
const consume = dependencies.consume;
|
|
443
|
+
if (typeof revalidate !== "function") {
|
|
444
|
+
throw new TypeError("revalidate must be a function");
|
|
445
|
+
}
|
|
446
|
+
if (typeof consume !== "function") {
|
|
447
|
+
throw new TypeError("consume must be a function");
|
|
448
|
+
}
|
|
449
|
+
const lifecycleSignal = dependencies.lifecycleSignal;
|
|
450
|
+
if (lifecycleSignal !== undefined && !(lifecycleSignal instanceof AbortSignal)) {
|
|
451
|
+
throw new TypeError("lifecycleSignal must be an AbortSignal");
|
|
452
|
+
}
|
|
453
|
+
this.operationTimeoutMs = dependencies.operationTimeoutMs ?? DEFAULT_OPERATION_TIMEOUT_MS;
|
|
454
|
+
this.maxConsumeAttempts = dependencies.maxConsumeAttempts ?? DEFAULT_MAX_CONSUME_ATTEMPTS;
|
|
455
|
+
if (!Number.isSafeInteger(this.operationTimeoutMs)
|
|
456
|
+
|| this.operationTimeoutMs <= 0
|
|
457
|
+
|| this.operationTimeoutMs > MAX_OPERATION_TIMEOUT_MS) {
|
|
458
|
+
throw new TypeError("operationTimeoutMs must be an integer from 1 to 60000");
|
|
459
|
+
}
|
|
460
|
+
if (!Number.isSafeInteger(this.maxConsumeAttempts)
|
|
461
|
+
|| this.maxConsumeAttempts < 1
|
|
462
|
+
|| this.maxConsumeAttempts > 3) {
|
|
463
|
+
throw new TypeError("maxConsumeAttempts must be an integer from 1 to 3");
|
|
464
|
+
}
|
|
465
|
+
this.contract = Object.freeze({
|
|
466
|
+
coordinationScope,
|
|
467
|
+
revalidate,
|
|
468
|
+
consume,
|
|
469
|
+
lifecycleSignal,
|
|
470
|
+
operationTimeoutMs: this.operationTimeoutMs,
|
|
471
|
+
maxConsumeAttempts: this.maxConsumeAttempts,
|
|
472
|
+
});
|
|
473
|
+
this.onCapacitySaturated = dependencies.onCapacitySaturated;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
createLogicalTurn(): CodexResetCreditLogicalTurn {
|
|
477
|
+
const operationId = crypto.randomUUID();
|
|
478
|
+
if (!OPERATION_ID_PATTERN.test(operationId)) {
|
|
479
|
+
throw new TypeError("operationId must be an RFC 4122 version 4 UUID");
|
|
480
|
+
}
|
|
481
|
+
const turn = Object.freeze({ operationId });
|
|
482
|
+
this.logicalTurns.set(turn, {});
|
|
483
|
+
return turn;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
recover(
|
|
487
|
+
turn: CodexResetCreditLogicalTurn,
|
|
488
|
+
generation: CodexResetCreditRecoveryGeneration,
|
|
489
|
+
authorization: CodexResetCreditRecoveryAuthorization,
|
|
490
|
+
options: { signal?: AbortSignal } = {},
|
|
491
|
+
): Promise<CodexResetCreditRecoveryResult> {
|
|
492
|
+
const turnState = this.logicalTurns.get(turn);
|
|
493
|
+
if (!turnState) throw new TypeError("logical turn was not created by this coordinator");
|
|
494
|
+
if (turnState.attempt) return turnState.attempt;
|
|
495
|
+
|
|
496
|
+
let resolveAttempt!: (result: CodexResetCreditRecoveryResult) => void;
|
|
497
|
+
let rejectAttempt!: (reason?: unknown) => void;
|
|
498
|
+
const attempt = new Promise<CodexResetCreditRecoveryResult>((resolve, reject) => {
|
|
499
|
+
resolveAttempt = resolve;
|
|
500
|
+
rejectAttempt = reject;
|
|
501
|
+
});
|
|
502
|
+
// Reserve the logical turn before reading caller-supplied option or generation
|
|
503
|
+
// accessors or invoking an output guard. A synchronous re-entry must observe this
|
|
504
|
+
// exact promise instead of creating a second flight with the same operation identity.
|
|
505
|
+
turnState.attempt = attempt;
|
|
506
|
+
let generationSnapshot: CodexResetCreditRecoveryGeneration;
|
|
507
|
+
let requestSignal: AbortSignal | undefined;
|
|
508
|
+
try {
|
|
509
|
+
requestSignal = snapshotRequestSignal(options);
|
|
510
|
+
generationSnapshot = snapshotGeneration(generation);
|
|
511
|
+
} catch (error) {
|
|
512
|
+
rejectAttempt(error);
|
|
513
|
+
return attempt;
|
|
514
|
+
}
|
|
515
|
+
try {
|
|
516
|
+
void this.startReservedAttempt(
|
|
517
|
+
turn,
|
|
518
|
+
generationSnapshot,
|
|
519
|
+
authorization,
|
|
520
|
+
requestSignal,
|
|
521
|
+
).then(resolveAttempt, rejectAttempt);
|
|
522
|
+
} catch (error) {
|
|
523
|
+
rejectAttempt(error);
|
|
524
|
+
}
|
|
525
|
+
return attempt;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
private startReservedAttempt(
|
|
529
|
+
turn: CodexResetCreditLogicalTurn,
|
|
530
|
+
generationSnapshot: CodexResetCreditRecoveryGeneration,
|
|
531
|
+
authorization: CodexResetCreditRecoveryAuthorization,
|
|
532
|
+
requestSignal: AbortSignal | undefined,
|
|
533
|
+
): Promise<CodexResetCreditRecoveryResult> {
|
|
534
|
+
if (authorization.enabled !== true) {
|
|
535
|
+
return Promise.resolve(notDispatched("disabled"));
|
|
536
|
+
}
|
|
537
|
+
if (outputIsExposed(authorization.isOutputExposed)) {
|
|
538
|
+
return Promise.resolve(notDispatched("output-already-exposed"));
|
|
539
|
+
}
|
|
540
|
+
if (!authorizedResetRejection(authorization)) {
|
|
541
|
+
return Promise.resolve(notDispatched("ineligible-rejection"));
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const signals = this.recoverySignals(requestSignal);
|
|
545
|
+
if (signals.some(signal => signal.aborted)) {
|
|
546
|
+
return Promise.resolve(CANCELLED_BEFORE_DISPATCH);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const key = generationKey(generationSnapshot);
|
|
550
|
+
const terminal = CodexResetCreditRecoveryCoordinator.terminalByAccount.get(
|
|
551
|
+
generationSnapshot.accountId,
|
|
552
|
+
);
|
|
553
|
+
if (terminal) {
|
|
554
|
+
if (!recoveryContractsMatch(terminal.contract, this.contract)) {
|
|
555
|
+
return Promise.resolve(notDispatched("coordination-mismatch"));
|
|
556
|
+
}
|
|
557
|
+
const order = compareGenerationOrder(generationSnapshot, terminal.generation);
|
|
558
|
+
if (order === 0) {
|
|
559
|
+
return this.resolveTerminalOutcome(
|
|
560
|
+
terminal.outcome,
|
|
561
|
+
signals,
|
|
562
|
+
authorization.isOutputExposed,
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
if (order < 0) {
|
|
566
|
+
return Promise.resolve(notDispatched("stale-generation"));
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
let flight = CodexResetCreditRecoveryCoordinator.activeFlights.get(key);
|
|
571
|
+
if (flight && !recoveryContractsMatch(flight.contract, this.contract)) {
|
|
572
|
+
return Promise.resolve(notDispatched("coordination-mismatch"));
|
|
573
|
+
}
|
|
574
|
+
if (flight
|
|
575
|
+
&& !flight.dispatchStarted
|
|
576
|
+
&& (flight.preDispatchAbort.signal.aborted || flight.activeWaiters === 0)) {
|
|
577
|
+
return Promise.resolve(CANCELLED_BEFORE_DISPATCH);
|
|
578
|
+
}
|
|
579
|
+
if (!flight) {
|
|
580
|
+
if (!this.hasFlightCapacity(
|
|
581
|
+
generationSnapshot.accountId,
|
|
582
|
+
)) {
|
|
583
|
+
return Promise.resolve(notDispatched("recovery-state-capacity"));
|
|
584
|
+
}
|
|
585
|
+
flight = this.createFlight(key, generationSnapshot, turn.operationId);
|
|
586
|
+
CodexResetCreditRecoveryCoordinator.activeFlights.set(key, flight);
|
|
587
|
+
}
|
|
588
|
+
if (flight.activeWaiters >= MAX_TRACKED_RECOVERY_WAITERS_PER_FLIGHT) {
|
|
589
|
+
this.reportCapacitySaturated(this.trackedCapacity());
|
|
590
|
+
return Promise.resolve(notDispatched("recovery-state-capacity"));
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return this.joinFlight(flight, signals, authorization.isOutputExposed);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/** Exposes no flight contents; intended for deterministic cleanup tests. */
|
|
597
|
+
activeFlightCountForTests(): number {
|
|
598
|
+
return CodexResetCreditRecoveryCoordinator.allFlights.size;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
terminalGenerationCountForTests(): number {
|
|
602
|
+
return CodexResetCreditRecoveryCoordinator.terminalByAccount.size;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
async waitForIdleForTests(): Promise<void> {
|
|
606
|
+
for (;;) {
|
|
607
|
+
const flights = [...new Set([
|
|
608
|
+
...CodexResetCreditRecoveryCoordinator.allFlights,
|
|
609
|
+
...CodexResetCreditRecoveryCoordinator.orphanedFlights,
|
|
610
|
+
])];
|
|
611
|
+
if (flights.length === 0) return;
|
|
612
|
+
await Promise.allSettled(flights.flatMap(flight => [
|
|
613
|
+
flight.promise,
|
|
614
|
+
...(flight.executionPromise ? [flight.executionPromise] : []),
|
|
615
|
+
]));
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
static async [RESET_PROCESS_STATE_FOR_TESTS](): Promise<void> {
|
|
620
|
+
if (process.env.OCX_TEST_HOME_GUARD !== "1") {
|
|
621
|
+
throw new Error("resetProcessStateForTests is available only under the repository test preload");
|
|
622
|
+
}
|
|
623
|
+
const flights = [...new Set([...this.allFlights, ...this.orphanedFlights])];
|
|
624
|
+
for (const flight of flights) {
|
|
625
|
+
flight.discardedForTests = true;
|
|
626
|
+
flight.expired = true;
|
|
627
|
+
if (flight.deadlineTimer !== undefined) {
|
|
628
|
+
clearTimeout(flight.deadlineTimer);
|
|
629
|
+
flight.deadlineTimer = undefined;
|
|
630
|
+
}
|
|
631
|
+
flight.preDispatchAbort.abort();
|
|
632
|
+
flight.operationAbort.abort();
|
|
633
|
+
flight.resetForTestsResolve?.(flight.dispatchStarted
|
|
634
|
+
? freezeResult({
|
|
635
|
+
kind: "ambiguous",
|
|
636
|
+
reason: "consume-timeout",
|
|
637
|
+
operationId: flight.operationId,
|
|
638
|
+
})
|
|
639
|
+
: CANCELLED_BEFORE_DISPATCH);
|
|
640
|
+
}
|
|
641
|
+
this.activeFlights.clear();
|
|
642
|
+
this.terminalByAccount.clear();
|
|
643
|
+
await Promise.allSettled(flights.map(flight => flight.promise));
|
|
644
|
+
// An adapter may ignore its abort signal forever. Test reset must detach
|
|
645
|
+
// that handled execution after marking it discarded instead of hanging the
|
|
646
|
+
// entire suite; waitForIdleForTests drains every retained execution.
|
|
647
|
+
await Promise.resolve();
|
|
648
|
+
for (const flight of flights) {
|
|
649
|
+
this.allFlights.delete(flight);
|
|
650
|
+
this.orphanedFlights.delete(flight);
|
|
651
|
+
}
|
|
652
|
+
this.activeFlights.clear();
|
|
653
|
+
this.terminalByAccount.clear();
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
private static retireFlightWhenSettled(flight: RecoveryFlight): void {
|
|
657
|
+
if (!flight.finished) return;
|
|
658
|
+
this.allFlights.delete(flight);
|
|
659
|
+
if (flight.executionSettled) this.orphanedFlights.delete(flight);
|
|
660
|
+
else this.orphanedFlights.add(flight);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
private trackedCapacity(): {
|
|
664
|
+
outstandingExecutions: number;
|
|
665
|
+
trackedAccounts: Set<string>;
|
|
666
|
+
} {
|
|
667
|
+
const outstandingFlights = new Set([
|
|
668
|
+
...CodexResetCreditRecoveryCoordinator.allFlights,
|
|
669
|
+
...CodexResetCreditRecoveryCoordinator.orphanedFlights,
|
|
670
|
+
]);
|
|
671
|
+
const trackedAccounts = new Set(
|
|
672
|
+
CodexResetCreditRecoveryCoordinator.terminalByAccount.keys(),
|
|
673
|
+
);
|
|
674
|
+
for (const flight of outstandingFlights) {
|
|
675
|
+
trackedAccounts.add(flight.generation.accountId);
|
|
676
|
+
}
|
|
677
|
+
return { outstandingExecutions: outstandingFlights.size, trackedAccounts };
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
private reportCapacitySaturated(capacity: {
|
|
681
|
+
outstandingExecutions: number;
|
|
682
|
+
trackedAccounts: Set<string>;
|
|
683
|
+
}): void {
|
|
684
|
+
try {
|
|
685
|
+
this.onCapacitySaturated?.(Object.freeze({
|
|
686
|
+
activeFlights: capacity.outstandingExecutions,
|
|
687
|
+
trackedAccounts: capacity.trackedAccounts.size,
|
|
688
|
+
}));
|
|
689
|
+
} catch {
|
|
690
|
+
// Operational reporting must never weaken the fail-closed capacity result.
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
private hasFlightCapacity(accountId: string): boolean {
|
|
695
|
+
const capacity = this.trackedCapacity();
|
|
696
|
+
const allowed = capacity.outstandingExecutions < MAX_TRACKED_RECOVERY_FLIGHTS
|
|
697
|
+
&& (capacity.trackedAccounts.has(accountId)
|
|
698
|
+
|| capacity.trackedAccounts.size < MAX_TRACKED_RECOVERY_ACCOUNTS);
|
|
699
|
+
if (!allowed) this.reportCapacitySaturated(capacity);
|
|
700
|
+
return allowed;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
private recoverySignals(requestSignal?: AbortSignal): AbortSignal[] {
|
|
704
|
+
return [...new Set(
|
|
705
|
+
[requestSignal, this.contract.lifecycleSignal]
|
|
706
|
+
.filter((signal): signal is AbortSignal => signal !== undefined),
|
|
707
|
+
)];
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
private resolveTerminalOutcome(
|
|
711
|
+
outcome: CodexResetCreditDispatchOutcome,
|
|
712
|
+
signals: readonly AbortSignal[],
|
|
713
|
+
outputGuard: () => boolean,
|
|
714
|
+
): Promise<CodexResetCreditRecoveryResult> {
|
|
715
|
+
return Promise.resolve().then(() => {
|
|
716
|
+
if (signals.some(signal => signal.aborted)) {
|
|
717
|
+
return freezeResult({
|
|
718
|
+
kind: "detached",
|
|
719
|
+
reason: "cancelled-after-dispatch",
|
|
720
|
+
outcome,
|
|
721
|
+
} as const);
|
|
722
|
+
}
|
|
723
|
+
const outputExposed = outputIsExposed(outputGuard);
|
|
724
|
+
if (signals.some(signal => signal.aborted)) {
|
|
725
|
+
return freezeResult({
|
|
726
|
+
kind: "detached",
|
|
727
|
+
reason: "cancelled-after-dispatch",
|
|
728
|
+
outcome,
|
|
729
|
+
} as const);
|
|
730
|
+
}
|
|
731
|
+
if (outputExposed) {
|
|
732
|
+
return freezeResult({
|
|
733
|
+
kind: "detached",
|
|
734
|
+
reason: "output-exposed-after-dispatch",
|
|
735
|
+
outcome,
|
|
736
|
+
} as const);
|
|
737
|
+
}
|
|
738
|
+
return outcome;
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
private createFlight(
|
|
743
|
+
key: string,
|
|
744
|
+
generation: CodexResetCreditRecoveryGeneration,
|
|
745
|
+
operationId: string,
|
|
746
|
+
): RecoveryFlight {
|
|
747
|
+
const flight: RecoveryFlight = {
|
|
748
|
+
generation: Object.freeze({ ...generation }),
|
|
749
|
+
operationId,
|
|
750
|
+
contract: this.contract,
|
|
751
|
+
activeWaiters: 0,
|
|
752
|
+
dispatchStarted: false,
|
|
753
|
+
expired: false,
|
|
754
|
+
finished: false,
|
|
755
|
+
executionSettled: false,
|
|
756
|
+
discardedForTests: false,
|
|
757
|
+
preDispatchAbort: new AbortController(),
|
|
758
|
+
operationAbort: new AbortController(),
|
|
759
|
+
outputGuards: new Set(),
|
|
760
|
+
promise: undefined as unknown as RecoveryFlight["promise"],
|
|
761
|
+
};
|
|
762
|
+
CodexResetCreditRecoveryCoordinator.allFlights.add(flight);
|
|
763
|
+
|
|
764
|
+
// Start on the next microtask so the creating caller joins before
|
|
765
|
+
// revalidation can observe the active-waiter count.
|
|
766
|
+
flight.promise = Promise.resolve()
|
|
767
|
+
.then(() => this.runFlightWithDeadline(flight))
|
|
768
|
+
.catch(() => (
|
|
769
|
+
flight.dispatchStarted
|
|
770
|
+
? freezeResult({
|
|
771
|
+
kind: "ambiguous",
|
|
772
|
+
reason: "consume-failed",
|
|
773
|
+
operationId: flight.operationId,
|
|
774
|
+
} as const)
|
|
775
|
+
: notDispatched("revalidation-failed")
|
|
776
|
+
))
|
|
777
|
+
.then(result => {
|
|
778
|
+
if (!flight.discardedForTests && isDispatchOutcome(result)) {
|
|
779
|
+
const current = CodexResetCreditRecoveryCoordinator.terminalByAccount.get(
|
|
780
|
+
flight.generation.accountId,
|
|
781
|
+
);
|
|
782
|
+
if (!current || compareGenerationOrder(flight.generation, current.generation) > 0) {
|
|
783
|
+
CodexResetCreditRecoveryCoordinator.terminalByAccount.set(
|
|
784
|
+
flight.generation.accountId,
|
|
785
|
+
{ generation: flight.generation, outcome: result, contract: flight.contract },
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return result;
|
|
790
|
+
})
|
|
791
|
+
.finally(() => {
|
|
792
|
+
flight.finished = true;
|
|
793
|
+
CodexResetCreditRecoveryCoordinator.retireFlightWhenSettled(flight);
|
|
794
|
+
if (CodexResetCreditRecoveryCoordinator.activeFlights.get(key) === flight) {
|
|
795
|
+
CodexResetCreditRecoveryCoordinator.activeFlights.delete(key);
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
return flight;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
private async runFlightWithDeadline(
|
|
802
|
+
flight: RecoveryFlight,
|
|
803
|
+
): Promise<Awaited<RecoveryFlight["promise"]>> {
|
|
804
|
+
if (flight.discardedForTests) {
|
|
805
|
+
flight.executionSettled = true;
|
|
806
|
+
return CANCELLED_BEFORE_DISPATCH;
|
|
807
|
+
}
|
|
808
|
+
const deadlineAt = performance.now() + flight.contract.operationTimeoutMs;
|
|
809
|
+
const execution = this.runFlight(flight, deadlineAt);
|
|
810
|
+
flight.executionPromise = execution;
|
|
811
|
+
void execution.then(
|
|
812
|
+
() => {
|
|
813
|
+
flight.executionSettled = true;
|
|
814
|
+
CodexResetCreditRecoveryCoordinator.retireFlightWhenSettled(flight);
|
|
815
|
+
},
|
|
816
|
+
() => {
|
|
817
|
+
flight.executionSettled = true;
|
|
818
|
+
CodexResetCreditRecoveryCoordinator.retireFlightWhenSettled(flight);
|
|
819
|
+
},
|
|
820
|
+
);
|
|
821
|
+
const deadline = new Promise<Awaited<RecoveryFlight["promise"]>>(resolve => {
|
|
822
|
+
flight.deadlineTimer = setTimeout(() => {
|
|
823
|
+
expireRecoveryFlight(flight);
|
|
824
|
+
if (!flight.dispatchStarted) {
|
|
825
|
+
resolve(notDispatched("operation-expired-before-dispatch"));
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
resolve(freezeResult({
|
|
829
|
+
kind: "ambiguous",
|
|
830
|
+
reason: "consume-timeout",
|
|
831
|
+
operationId: flight.operationId,
|
|
832
|
+
}));
|
|
833
|
+
}, Math.max(0, deadlineAt - performance.now()));
|
|
834
|
+
});
|
|
835
|
+
const resetForTests = new Promise<Awaited<RecoveryFlight["promise"]>>(resolve => {
|
|
836
|
+
flight.resetForTestsResolve = resolve;
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
try {
|
|
840
|
+
return await Promise.race([execution, deadline, resetForTests]);
|
|
841
|
+
} finally {
|
|
842
|
+
if (flight.deadlineTimer !== undefined) {
|
|
843
|
+
clearTimeout(flight.deadlineTimer);
|
|
844
|
+
flight.deadlineTimer = undefined;
|
|
845
|
+
}
|
|
846
|
+
flight.resetForTestsResolve = undefined;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
private async runFlight(
|
|
851
|
+
flight: RecoveryFlight,
|
|
852
|
+
deadlineAt: number,
|
|
853
|
+
): Promise<Awaited<RecoveryFlight["promise"]>> {
|
|
854
|
+
if (flight.preDispatchAbort.signal.aborted || flight.activeWaiters === 0) {
|
|
855
|
+
return CANCELLED_BEFORE_DISPATCH;
|
|
856
|
+
}
|
|
857
|
+
let revalidation: unknown;
|
|
858
|
+
try {
|
|
859
|
+
revalidation = await flight.contract.revalidate(
|
|
860
|
+
flight.generation,
|
|
861
|
+
flight.preDispatchAbort.signal,
|
|
862
|
+
);
|
|
863
|
+
} catch {
|
|
864
|
+
if (flight.preDispatchAbort.signal.aborted && flight.activeWaiters === 0) {
|
|
865
|
+
return CANCELLED_BEFORE_DISPATCH;
|
|
866
|
+
}
|
|
867
|
+
if (recoveryDeadlineExpired(flight, deadlineAt)) {
|
|
868
|
+
return notDispatched("operation-expired-before-dispatch");
|
|
869
|
+
}
|
|
870
|
+
return notDispatched("revalidation-failed");
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
if (flight.preDispatchAbort.signal.aborted || flight.activeWaiters === 0) {
|
|
874
|
+
return CANCELLED_BEFORE_DISPATCH;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
const kind = revalidationKind(revalidation, flight.generation);
|
|
878
|
+
if (kind === "stale-generation") return notDispatched("stale-generation");
|
|
879
|
+
if (kind === "no-credit") return notDispatched("no-credit-after-revalidate");
|
|
880
|
+
if (kind !== "eligible") return notDispatched("invalid-revalidation");
|
|
881
|
+
|
|
882
|
+
if (!hasLiveUnexposedGuard(flight)) {
|
|
883
|
+
return notDispatched("output-already-exposed");
|
|
884
|
+
}
|
|
885
|
+
if (recoveryDeadlineExpired(flight, deadlineAt)) {
|
|
886
|
+
return notDispatched("operation-expired-before-dispatch");
|
|
887
|
+
}
|
|
888
|
+
if (flight.preDispatchAbort.signal.aborted || flight.activeWaiters === 0) {
|
|
889
|
+
return CANCELLED_BEFORE_DISPATCH;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// No await is allowed between this final commitment check and marking the
|
|
893
|
+
// irreversible dispatch. That closes the last pre-dispatch cancellation race.
|
|
894
|
+
flight.dispatchStarted = true;
|
|
895
|
+
for (let attempt = 1; attempt <= flight.contract.maxConsumeAttempts; attempt += 1) {
|
|
896
|
+
if (recoveryDeadlineExpired(flight, deadlineAt)) {
|
|
897
|
+
return freezeResult({
|
|
898
|
+
kind: "ambiguous",
|
|
899
|
+
reason: "consume-timeout",
|
|
900
|
+
operationId: flight.operationId,
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
try {
|
|
904
|
+
const outcome = await flight.contract.consume({
|
|
905
|
+
generation: flight.generation,
|
|
906
|
+
operationId: flight.operationId,
|
|
907
|
+
signal: flight.operationAbort.signal,
|
|
908
|
+
});
|
|
909
|
+
const mappedOutcome = mapConsumeOutcome(outcome, flight.operationId);
|
|
910
|
+
if (recoveryDeadlineExpired(flight, deadlineAt)) {
|
|
911
|
+
return freezeResult({
|
|
912
|
+
kind: "ambiguous",
|
|
913
|
+
reason: "consume-timeout",
|
|
914
|
+
operationId: flight.operationId,
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
return mappedOutcome;
|
|
918
|
+
} catch {
|
|
919
|
+
if (recoveryDeadlineExpired(flight, deadlineAt)) {
|
|
920
|
+
return freezeResult({
|
|
921
|
+
kind: "ambiguous",
|
|
922
|
+
reason: "consume-timeout",
|
|
923
|
+
operationId: flight.operationId,
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
if (attempt === flight.contract.maxConsumeAttempts) {
|
|
927
|
+
return freezeResult({
|
|
928
|
+
kind: "ambiguous",
|
|
929
|
+
reason: "consume-failed",
|
|
930
|
+
operationId: flight.operationId,
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
return freezeResult({
|
|
937
|
+
kind: "ambiguous",
|
|
938
|
+
reason: "consume-failed",
|
|
939
|
+
operationId: flight.operationId,
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
private joinFlight(
|
|
944
|
+
flight: RecoveryFlight,
|
|
945
|
+
signals: readonly AbortSignal[],
|
|
946
|
+
outputGuard: () => boolean,
|
|
947
|
+
): Promise<CodexResetCreditRecoveryResult> {
|
|
948
|
+
flight.activeWaiters += 1;
|
|
949
|
+
const outputGuardRegistration = Object.freeze({ guard: outputGuard });
|
|
950
|
+
flight.outputGuards.add(outputGuardRegistration);
|
|
951
|
+
|
|
952
|
+
return new Promise((resolve, reject) => {
|
|
953
|
+
let settled = false;
|
|
954
|
+
let counted = true;
|
|
955
|
+
let detachedAfterDispatch = false;
|
|
956
|
+
const registeredSignals: AbortSignal[] = [];
|
|
957
|
+
|
|
958
|
+
const releasePreDispatchWaiter = () => {
|
|
959
|
+
if (!counted) return;
|
|
960
|
+
counted = false;
|
|
961
|
+
if (flight.dispatchStarted) return;
|
|
962
|
+
flight.activeWaiters = Math.max(0, flight.activeWaiters - 1);
|
|
963
|
+
flight.outputGuards.delete(outputGuardRegistration);
|
|
964
|
+
if (flight.activeWaiters === 0 && !flight.finished) {
|
|
965
|
+
flight.preDispatchAbort.abort();
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
|
|
969
|
+
const removeAbortListeners = () => {
|
|
970
|
+
for (const signal of registeredSignals) {
|
|
971
|
+
try {
|
|
972
|
+
REMOVE_EVENT_LISTENER.call(signal, "abort", onAbort);
|
|
973
|
+
} catch {
|
|
974
|
+
// Waiter state still rolls back if a caller overrides EventTarget methods.
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
registeredSignals.length = 0;
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
const finish = (result: Awaited<RecoveryFlight["promise"]>) => {
|
|
981
|
+
if (settled) return;
|
|
982
|
+
settled = true;
|
|
983
|
+
if (!flight.dispatchStarted) releasePreDispatchWaiter();
|
|
984
|
+
if (isDispatchOutcome(result)) {
|
|
985
|
+
const outputExposed = outputIsExposed(outputGuard);
|
|
986
|
+
const cancelled = detachedAfterDispatch
|
|
987
|
+
|| signals.some(signal => signal.aborted);
|
|
988
|
+
removeAbortListeners();
|
|
989
|
+
if (!cancelled && !outputExposed) {
|
|
990
|
+
resolve(result);
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
resolve(freezeResult({
|
|
994
|
+
kind: "detached",
|
|
995
|
+
reason: cancelled
|
|
996
|
+
? "cancelled-after-dispatch"
|
|
997
|
+
: "output-exposed-after-dispatch",
|
|
998
|
+
outcome: result,
|
|
999
|
+
}));
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
removeAbortListeners();
|
|
1003
|
+
resolve(result);
|
|
1004
|
+
};
|
|
1005
|
+
|
|
1006
|
+
const onAbort = () => {
|
|
1007
|
+
if (flight.dispatchStarted) {
|
|
1008
|
+
detachedAfterDispatch = true;
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
releasePreDispatchWaiter();
|
|
1012
|
+
finish(CANCELLED_BEFORE_DISPATCH);
|
|
1013
|
+
};
|
|
1014
|
+
|
|
1015
|
+
try {
|
|
1016
|
+
for (const signal of signals) {
|
|
1017
|
+
registeredSignals.push(signal);
|
|
1018
|
+
ADD_EVENT_LISTENER.call(signal, "abort", onAbort, { once: true });
|
|
1019
|
+
}
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
settled = true;
|
|
1022
|
+
releasePreDispatchWaiter();
|
|
1023
|
+
removeAbortListeners();
|
|
1024
|
+
reject(error);
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
if (signals.some(signal => signal.aborted)) onAbort();
|
|
1028
|
+
void flight.promise.then(finish, () => {
|
|
1029
|
+
finish(flight.dispatchStarted
|
|
1030
|
+
? freezeResult({
|
|
1031
|
+
kind: "ambiguous",
|
|
1032
|
+
reason: "consume-failed",
|
|
1033
|
+
operationId: flight.operationId,
|
|
1034
|
+
})
|
|
1035
|
+
: notDispatched("revalidation-failed"));
|
|
1036
|
+
});
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/** @internal Test-support entry point; unavailable without the repository preload. */
|
|
1042
|
+
export async function resetCodexResetCreditRecoveryProcessStateForTests(): Promise<void> {
|
|
1043
|
+
await CodexResetCreditRecoveryCoordinator[RESET_PROCESS_STATE_FOR_TESTS]();
|
|
1044
|
+
}
|