elanous 0.2.23 → 0.2.24
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/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{272-b23d2e92161d11fd.js → 272-0eed0fb0aab716b6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{3961-fbef064c213b2b18.js → 3961-aaae04ed1d32622f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/{page-d5341ac96203a983.js → page-3b9fc39362473ae7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f2b62c19ca68fef8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/{page-55c631c032dcc47e.js → page-d7602d489edf5f37.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-ad3bd8084048bee4.js → page-24282bf9f90031a5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-8b7c5ab6b5bc8d64.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/{page-5c5d0e370f495ef6.js → page-4eb84d454f8c3415.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-c325e21543cd48a8.js → page-b2e494bdad57fcb5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-6168a7c43e6ef8f5.js → page-c0ab229673a8b0eb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-3e2b45f3c36dbac4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-c22aa511b28743ad.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-26345242e4049f55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-25bb425afdb27541.js → page-bf88c0f558d8ef54.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-32d72fdef1afe11a.js → page-52fec523e5b5d3ad.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-67723dfdcde92b31.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/{page-2bd9ce9c020d02d4.js → page-22725e2570f1126b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-a34096245e63b9f2.js → page-1891335950d68b70.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-3267af1ebdc73cf5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/{page-4cc66d22ca4ec502.js → page-c314b75c11234998.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/{page-83b14c729fcea837.js → page-9f9c85415f61782b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-c1d4276059085587.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-833aab587f26ad4c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-ccdb83ed40c8d724.js → page-8526c17ae38e6a50.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-7ad3de8fa06fdea9.js → page-f998637a8402115c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-b7bb53828b17b335.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-1b1850de0257e46d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-786111f30b6f0969.js → page-6f23159fedb41f67.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-48fb31810693c357.js → page-1d809a87139dd024.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-4e17cce5e2ed0fee.js → page-da9e570234d481fd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/{page-cb154acadb025414.js → page-b92caddb5c715f30.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/page-8ce3a38e210b810a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-c2dd2b19c93f90ab.js → page-57ffeeb6019440fd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-abbb417bf514630b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-d3c007c8a0ddd7ee.js → page-2c2b3f29cec1efa4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-7837ce2aacb97b04.js → page-9369279f72fc7b51.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-64861f14039499c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-ddc4c93efd8ab94b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-901a1391b81053bc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-68d9bbaac9841a58.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-119be9a0444d2482.js → page-b5c0c996f329371e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-fa806c018833e332.js → page-89881ccbcf728978.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-0e87a803b3d7165e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/{page-d431fbdea6ad93dd.js → page-58d4e5cb30ef4871.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-02ce4c3b5f39e4d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-e620d1bb8eadcec2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-847c3de6ca8a4416.js → page-acf868c64d77f950.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-54fa605fa6dba457.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-74e69ba2c8e970d4.js → page-bc79f0833533fa84.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-70038f41ae7cf931.js → page-d5c4398eea03d8f3.js} +1 -1
- package/apps/pwa/out/_next/static/css/72becfd42807b50d.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +14 -14
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/catalog/models/openrouter/deepseek-v4-pro-0813.yaml +39 -0
- package/catalog/models/openrouter/deepseek-v4.1-flash.yaml +38 -0
- package/catalog/models/openrouter/glm-5.3-flash.yaml +35 -0
- package/catalog/providers/openrouter.yaml +2 -0
- package/graphs/implement-loop.yaml +31 -0
- package/graphs/ops/harness-gc.yaml +17 -0
- package/graphs/ops/recipes.yaml +3 -0
- package/graphs/overlays/README.md +1 -1
- package/graphs/overlays/stop-heal-ladder.yaml +105 -0
- package/package.json +1 -1
- package/scripts/add-api-key.sh +1 -1
- package/scripts/ci-typecheck-changed.ts +71 -14
- package/scripts/fleet/bedrock-pod-secret.ts +122 -0
- package/scripts/fleet/node-setup-linux.sh +97 -0
- package/scripts/fleet/node-setup.sh +11 -0
- package/scripts/fleet/zai-pod-secret.ts +86 -0
- package/scripts/land-global-checks.ts +3 -0
- package/scripts/measure-log-diet-2.ts +213 -0
- package/scripts/measure-tsc-affected-roots.ts +289 -0
- package/scripts/release-loop/gate-executor.ts +28 -0
- package/scripts/release-loop/gate-node.ts +262 -26
- package/scripts/release-loop/resume-release.ts +9 -3
- package/scripts/release-loop/resume.ts +41 -0
- package/scripts/release-loop/unattended-release.ts +22 -7
- package/scripts/tsc-baseline-cache.ts +147 -0
- package/scripts/usage-rollup.ts +58 -6
- package/src/agent/run-context.ts +10 -0
- package/src/agent-mission/driver.ts +18 -3
- package/src/agent-mission/resource-ladder.ts +8 -3
- package/src/agent-mission/review-loop.ts +8 -2
- package/src/agent-substrate/acp-implementer.ts +139 -0
- package/src/agent-substrate/pr-reviewer.ts +31 -4
- package/src/agent-substrate/self-review-cli.ts +48 -9
- package/src/autopilot/mission-critique.ts +18 -5
- package/src/autopilot/mission-feasibility-negotiate.ts +11 -10
- package/src/autopilot/mission-trace.ts +12 -1
- package/src/autopilot/system-lookback-run.ts +16 -10
- package/src/bench/bench.ts +183 -0
- package/src/budget/fetchers/grok.ts +15 -5
- package/src/budget/llm-cost.ts +37 -5
- package/src/budget/types.ts +8 -2
- package/src/budget/unified-usage.ts +88 -6
- package/src/cli/bench-cli.ts +34 -0
- package/src/cli/guardian-cli.ts +26 -1
- package/src/cli/image-cli.ts +101 -0
- package/src/cli/log-event-names.ts +4 -0
- package/src/cli/pr-cli.ts +62 -5
- package/src/cli/pr-land-overlap-evidence.ts +160 -12
- package/src/cli/release-cli.ts +18 -9
- package/src/cli/rfc-status.ts +52 -2
- package/src/cli/self-update.ts +104 -0
- package/src/cli/tasks-cli.ts +109 -4
- package/src/cli/usage-cli.ts +91 -8
- package/src/core-turn/run-goal-loop.ts +5 -4
- package/src/dashboard/ad-run-setup-bindings.ts +279 -0
- package/src/dashboard/index.ts +18 -276
- package/src/dashboard/stale-install-check.ts +11 -0
- package/src/debug/log-diet.ts +38 -7
- package/src/debug/log.ts +19 -1
- package/src/domains/release-tool.ts +39 -0
- package/src/error-report/report.ts +21 -4
- package/src/github/pr-labels.ts +1 -0
- package/src/graph-runner/graph-cli.ts +42 -1
- package/src/graph-runner/runner.ts +167 -6
- package/src/grok/usage.ts +23 -2
- package/src/grounding/ref-index.ts +253 -0
- package/src/harness/dispatch-backpressure.ts +10 -2
- package/src/harness/harness-clean.ts +199 -78
- package/src/harness/harness-cli-command.ts +122 -7
- package/src/harness/harness-gc.ts +331 -0
- package/src/harness/harness-stop.ts +4 -2
- package/src/harness/harness-substrate-default.ts +142 -7
- package/src/harness/launch-registry.ts +6 -0
- package/src/harness/worktree-branch-prefix.ts +9 -0
- package/src/hq/fence.ts +187 -0
- package/src/hq/seats.ts +38 -22
- package/src/images/image-ledger.ts +179 -0
- package/src/index.ts +93 -11
- package/src/instance/child-scope.ts +32 -1
- package/src/instance/dev-project.ts +133 -0
- package/src/instance/resolve.ts +24 -0
- package/src/instance/spawn-elanous.ts +45 -0
- package/src/intake-plane/ref-prefetch.ts +110 -0
- package/src/intelligence-map/model-catalog.ts +72 -0
- package/src/llm/bedrock-eventstream.ts +215 -0
- package/src/llm/bedrock-provider.ts +114 -41
- package/src/llm/bedrock.ts +174 -28
- package/src/llm/openrouter-byok-cost.ts +96 -0
- package/src/llm/openrouter-deepseek.ts +151 -0
- package/src/llm/openrouter-upstream-pins.config.ts +25 -0
- package/src/llm/zai-coding-plan.ts +700 -0
- package/src/llm.ts +87 -16
- package/src/loops/checker.ts +148 -9
- package/src/loops/orchestrator/resync-shadow.ts +34 -7
- package/src/maturity/feature-maturity.ts +2 -2
- package/src/model-tier/llm-tier-map.ts +9 -5
- package/src/models/prompts.ts +2 -1
- package/src/mss/logging/log-store.ts +58 -11
- package/src/nexus/api/missions.ts +73 -2
- package/src/nexus/api/ops-api.ts +18 -0
- package/src/nexus/index.ts +7 -75
- package/src/nexus/tools-kind.ts +78 -0
- package/src/oauth/codex-account-rotation.ts +39 -2
- package/src/oauth/codex-account-store.ts +323 -24
- package/src/oauth/fallback-chain.ts +53 -15
- package/src/onboarding/first-run.ts +37 -18
- package/src/onboarding/web-first.ts +20 -2
- package/src/ops/machine-heartbeat.ts +185 -0
- package/src/ops/overview-cache.ts +175 -0
- package/src/ops/overview.ts +8 -0
- package/src/prompt-cache/openai.ts +3 -0
- package/src/prompt-cache/types.ts +5 -0
- package/src/prompt-enhance/enhance.ts +23 -0
- package/src/release-loop/placement.ts +16 -1
- package/src/requirements/requirement-ledger.ts +536 -0
- package/src/retro/daily.ts +5 -5
- package/src/seat-loop/seat-loop.ts +32 -10
- package/src/self-dev/dev-cli.ts +44 -10
- package/src/self-dev/dev-pipeline.ts +7 -2
- package/src/self-dev/draft-residue.ts +14 -2
- package/src/self-dev/draft-sweep.ts +103 -0
- package/src/self-dev/graph-journey-route.ts +6 -0
- package/src/self-dev/orchestrate-cli.ts +25 -3
- package/src/self-dev/orchestrate.ts +41 -6
- package/src/self-dev/run-supervisor.ts +188 -15
- package/src/self-dev/run-types.ts +13 -0
- package/src/self-dev/supervisor-verdict-edges.ts +1 -0
- package/src/self-implement/budget-gate.ts +53 -13
- package/src/self-implement/child-context-composition.ts +139 -0
- package/src/self-implement/child-llm-preference.ts +13 -5
- package/src/self-implement/claude-acp-implement.ts +67 -0
- package/src/self-implement/flex-signals.ts +59 -0
- package/src/self-implement/frozen-merges.ts +8 -3
- package/src/self-implement/goal-author-decompose.ts +10 -2
- package/src/self-implement/goal-author.ts +24 -9
- package/src/self-implement/graph-overlay-condition.ts +28 -0
- package/src/self-implement/graph-overlay-yaml.ts +41 -7
- package/src/self-implement/graph-templates.ts +33 -1
- package/src/self-implement/graph-yaml.ts +56 -0
- package/src/self-implement/heal-intake.ts +5 -1
- package/src/self-implement/orchestrator.ts +144 -15
- package/src/self-implement/running-runs.ts +75 -5
- package/src/self-implement/seams.ts +47 -1
- package/src/self-implement/self-implement-cli.ts +9 -4
- package/src/self-implement/stop-autoheal.ts +69 -2
- package/src/session-runtime/retry-policy.ts +2 -1
- package/src/steward/launch.ts +5 -4
- package/src/steward/triage-budget.ts +62 -0
- package/src/steward/triage.ts +83 -22
- package/src/task-agent/actions.ts +62 -5
- package/src/task-agent/completion-evidence.ts +5 -0
- package/src/task-agent/cover.ts +101 -3
- package/src/task-agent/judge.ts +16 -0
- package/src/task-agent/land-order-ledger.ts +95 -0
- package/src/task-agent/live-move-history.ts +87 -1
- package/src/task-agent/live-moves.ts +350 -20
- package/src/task-agent/rejudge-on-head.ts +75 -20
- package/src/task-agent/review-prior-fix.ts +146 -0
- package/src/task-agent/run-adopt.ts +136 -0
- package/src/task-agent/shadow.ts +53 -9
- package/src/task-agent/task-hand.ts +54 -7
- package/src/task-orchestrator/surfaces/pod-account-broker.ts +38 -5
- package/src/task-orchestrator/surfaces/pod-bedrock-child.ts +74 -0
- package/src/task-orchestrator/surfaces/pod-child-default.ts +50 -0
- package/src/task-orchestrator/surfaces/pod-child-providers.ts +14 -1
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +79 -6
- package/src/task-orchestrator/surfaces/pod-lease.ts +17 -5
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
- package/src/task-orchestrator/surfaces/pod-pool.ts +20 -4
- package/src/task-orchestrator/surfaces/pod-zai-child.ts +101 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +213 -23
- package/src/task-orchestrator/surfaces/self-implement.ts +19 -1
- package/src/typecheck-ratchet.ts +1 -0
- package/src/user-config.ts +51 -11
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/triggers/registry.ts +59 -0
- package/src/workflow-runtime/triggers/webhook-source.ts +35 -5
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-0160ab83c8685a12.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e6784759a2531031.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-31417d3604154a6d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-e9606dc0bdec9305.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-a648bc464c9dc754.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-d27b794c8ba76bbb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-22170f4f00a15496.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-6e2781c523dad761.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-26dd415c8f13f341.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-015c02c4fec4023e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-6b0ae5b5c42f6d2f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-16258e956ea93fc2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f35dd1dd96ae87.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-7350ee93f4fe1033.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-00723458575f2b19.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-745d9b6c015076f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-736d33c7414a8a1a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-c782041b5f5199ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-0c056abff82b9891.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-242d668e4fbf72c4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-81dd0d8ae923ed72.js +0 -1
- package/apps/pwa/out/_next/static/css/5619d59e87eddf66.css +0 -1
- /package/apps/pwa/out/_next/static/{rfkMm4movqkqMebBrCXiR → Q-7iofoxOEv1WX18S35of}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{rfkMm4movqkqMebBrCXiR → Q-7iofoxOEv1WX18S35of}/_ssgManifest.js +0 -0
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// ⛔ 토큰 «값»은 어디에도 출력하지 않는다 — accountId 앞 8자만(R-LLM2).
|
|
7
7
|
|
|
8
8
|
import { readFile } from 'node:fs/promises';
|
|
9
|
-
import { mkdirSync, openSync, closeSync, unlinkSync, readFileSync, writeFileSync, statSync } from 'node:fs';
|
|
9
|
+
import { mkdirSync, openSync, closeSync, unlinkSync, readFileSync, writeFileSync, statSync, renameSync } from 'node:fs';
|
|
10
10
|
import { dirname, join, resolve } from 'node:path';
|
|
11
11
|
import { consumeCodexResetCredits, listCodexResetCredits } from '../budget/codex-reset-credits.js';
|
|
12
12
|
import { codexCredentialRoot, readAvailabilityState, writeAvailabilityState } from '../budget/codex-reset-credit-state.js';
|
|
@@ -20,7 +20,7 @@ import { creditPaceStatus } from '../budget/codex-credit-pace.js';
|
|
|
20
20
|
import { loadLlmPolicy } from '../policy/llm-policy.js';
|
|
21
21
|
import { getUserConfig } from '../user-config.js';
|
|
22
22
|
import {
|
|
23
|
-
decideCodexRotation, observeRotation, applyRotation, readCodexAccountRotationConfig, rotatedChildEnv,
|
|
23
|
+
decideCodexRotation, observeRotation, applyRotation, readCodexAccountRotationConfig, rotatedChildEnv, creditCandidatesByBalance, CODEX_CREDIT_RESERVE_DEFAULT,
|
|
24
24
|
codexAccountThresholdPercent,
|
|
25
25
|
type RotationCandidate,
|
|
26
26
|
} from './codex-account-rotation.js';
|
|
@@ -29,7 +29,7 @@ import { emitDecision } from '../live/detail-switch.js';
|
|
|
29
29
|
import { debug } from '../debug/log.js';
|
|
30
30
|
import {
|
|
31
31
|
decideFallback, normalizeFallbackChain, grokQuotaFromUsageSnapshot,
|
|
32
|
-
type FallbackDecision, type FallbackInput, type RotationOutcome,
|
|
32
|
+
type FallbackDecision, type FallbackInput, type FallbackStep, type RotationOutcome,
|
|
33
33
|
} from './fallback-chain.js';
|
|
34
34
|
import { resolveGrokCredential } from '../grok/credential.js';
|
|
35
35
|
import { getOpenRouterApiKey } from '../config.js';
|
|
@@ -52,7 +52,7 @@ export function _setCodexAccountOutboundSenderForTesting(sender: CodexAccountOut
|
|
|
52
52
|
/** ⛔⭐ 정적 import 로 읽는다 — 종전 `require()` 는 ESM 에서 «정의되지 않을 수 있고», 그러면
|
|
53
53
|
* fail-soft 가 삼켜 ***config false 가 영영 무시된다***(리뷰 should-fix: 노브가 no-op 이 된다).
|
|
54
54
|
* ⚠️ 순환 없음을 확인했다 — user-config 는 oauth 를 안 끌어온다. */
|
|
55
|
-
type RotationConfig = { llm?: { codexAccountRotation?: boolean; codexAccountAlerts?: boolean; codexAccountRotationThresholdPercent?: unknown; codexAccountRotationThresholdPercentByAccount?: Readonly<Record<string, unknown>>; codexCreditsAllowed?: boolean; codexResetAutoConsumePerDay?: number; codexQuotaPolicy?: unknown; fallbackChain?: unknown; openrouter?: { fallbackModel?: unknown } } };
|
|
55
|
+
type RotationConfig = { llm?: { codexCreditReserve?: unknown; codexAccountRotation?: boolean; codexAccountAlerts?: boolean; codexAccountRotationThresholdPercent?: unknown; codexAccountRotationThresholdPercentByAccount?: Readonly<Record<string, unknown>>; codexCreditsAllowed?: boolean; codexResetAutoConsumePerDay?: number; codexQuotaPolicy?: unknown; fallbackChain?: unknown; openrouter?: { fallbackModel?: unknown } } };
|
|
56
56
|
|
|
57
57
|
function readUserConfig(): RotationConfig {
|
|
58
58
|
return getUserConfig();
|
|
@@ -259,6 +259,64 @@ function codexCreditsAllowedFromConfig(): boolean {
|
|
|
259
259
|
return codexPolicyAllowsCredits(codexQuotaPolicyFromConfig());
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
/** SUBSCRIPTION-EXHAUST-REROUTE — 크레딧 예비분 기본. 10-11 운영 실측: 잔액 29,401 · 하루 소모 25,132 → 2,000 ≈ 하루 소모의 8%
|
|
263
|
+
* (폴백이 서는 동안 계정을 0 으로 만들지 않는 몫). `llm.codexCreditReserve` 로 바꾼다(0 = 잔액 0 일 때만 넘김). */
|
|
264
|
+
export const DEFAULT_CODEX_CREDIT_RESERVE = CODEX_CREDIT_RESERVE_DEFAULT;
|
|
265
|
+
/** 예비분 넘김의 OpenRouter 모델(설정 `llm.openrouter.fallbackModel` 이 없을 때) — 대표 라우팅 «중» 시작 모델. */
|
|
266
|
+
export const CREDIT_RESERVE_OPENROUTER_MODEL = 'openrouter/z-ai/glm-5.3';
|
|
267
|
+
|
|
268
|
+
/** `llm.codexCreditReserve` — 유효하지 않거나 못 읽으면 기본(크레딧은 돈이라 예비분을 지킨다). */
|
|
269
|
+
export function codexCreditReserveFromConfig(): number {
|
|
270
|
+
try {
|
|
271
|
+
const raw = configReader().llm?.codexCreditReserve;
|
|
272
|
+
return typeof raw === 'number' && Number.isFinite(raw) && raw >= 0 ? raw : DEFAULT_CODEX_CREDIT_RESERVE;
|
|
273
|
+
} catch { return DEFAULT_CODEX_CREDIT_RESERVE; }
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** grok 자격 «존재» 판독 — 테스트 격리 심(실 자격을 안 읽게). */
|
|
277
|
+
let grokCredentialPresent: () => boolean = () => resolveGrokCredential() !== null;
|
|
278
|
+
export function _setGrokCredentialPresentForTesting(fn: (() => boolean) | null): void {
|
|
279
|
+
grokCredentialPresent = fn ?? (() => resolveGrokCredential() !== null);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** 회전 판정이 쓸 크레딧 허가와 그 출처.
|
|
283
|
+
* - `policy` 정책이 credits(옛 codexCreditsAllowed=true) — 종전 그대로.
|
|
284
|
+
* - `chain-step` CODEX-CREDITS-STEP: 정책은 fallback 이지만 체인이 `codex-credits` 칸에 «닿는다»
|
|
285
|
+
* (앞 칸 grok 등을 지금 못 쓰고 · 잔액 확인된 계정이 있다) ⇒ 이번 판정만 크레딧 계정을 고른다.
|
|
286
|
+
* - `none` 크레딧 ✗(`why` = 칸을 못 쓴 이유 · 칸이 없으면 생략).
|
|
287
|
+
* ⛔ 칸 판정은 `decideFallback` 를 그대로 부른다 — 체인 의미를 여기서 다시 짓지 않는다. 이 판독은 네트워크를 안 친다. */
|
|
288
|
+
type RotationCreditsGrant =
|
|
289
|
+
| { readonly allowed: true; readonly via: 'policy' }
|
|
290
|
+
| { readonly allowed: true; readonly via: 'chain-step'; readonly skipped: readonly { readonly step: FallbackStep; readonly why: string }[]; readonly creditAccountCount: number }
|
|
291
|
+
| { readonly allowed: false; readonly via: 'none'; readonly why?: string };
|
|
292
|
+
|
|
293
|
+
function rotationCreditsGrant(candidates: readonly RotationCandidate[]): RotationCreditsGrant {
|
|
294
|
+
const policy = codexQuotaPolicyFromConfig();
|
|
295
|
+
if (codexPolicyAllowsCredits(policy)) return { allowed: true, via: 'policy' };
|
|
296
|
+
if (!codexPolicyAllowsFallback(policy)) return { allowed: false, via: 'none' };
|
|
297
|
+
try {
|
|
298
|
+
const { chain } = normalizeFallbackChain(configReader().llm?.fallbackChain);
|
|
299
|
+
if (!chain.includes('codex-credits')) return { allowed: false, via: 'none' };
|
|
300
|
+
const creditAccountCount = codexCreditAccountNames(candidates, codexCreditReserveFromConfig()).length;
|
|
301
|
+
const fallbackModel = configReader().llm?.openrouter?.fallbackModel;
|
|
302
|
+
const decision = decideFallback({
|
|
303
|
+
rotation: { reason: 'no-candidate' },
|
|
304
|
+
chain,
|
|
305
|
+
grokAvailable: grokCredentialPresent(),
|
|
306
|
+
grokQuota: readCachedGrokQuota(),
|
|
307
|
+
...(chain.includes('openrouter') ? {
|
|
308
|
+
openrouterFallbackModel: typeof fallbackModel === 'string' ? fallbackModel : undefined,
|
|
309
|
+
openrouterAvailable: Boolean(getOpenRouterApiKey()?.trim()),
|
|
310
|
+
} : {}),
|
|
311
|
+
codexCreditsAvailable: creditAccountCount > 0,
|
|
312
|
+
});
|
|
313
|
+
if (decision.action === 'codex-credits') return { allowed: true, via: 'chain-step', skipped: decision.skipped, creditAccountCount };
|
|
314
|
+
return { allowed: false, via: 'none', why: decision.action === 'stay' ? decision.why : `earlier-step:${decision.action === 'switch-backend' ? decision.backend : decision.action}` };
|
|
315
|
+
} catch {
|
|
316
|
+
return { allowed: false, via: 'none', why: 'chain-unreadable' };
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
262
320
|
function buildRotationCandidates(path: string, now: number, env: NodeJS.ProcessEnv = process.env): RotationCandidate[] {
|
|
263
321
|
return listCodexAccountsInStore(path)
|
|
264
322
|
.map((row) => {
|
|
@@ -277,12 +335,13 @@ function buildRotationCandidates(path: string, now: number, env: NodeJS.ProcessE
|
|
|
277
335
|
storeKey: row.storeKey,
|
|
278
336
|
home,
|
|
279
337
|
reached: readQuotaSignal(now, home),
|
|
338
|
+
resetCreditAvailability: resetCreditAvailability(home, now),
|
|
280
339
|
...(usedPercent === undefined ? {} : { usedPercent }),
|
|
281
340
|
...(credits.balance === undefined ? {} : { creditBalance: credits.balance }),
|
|
282
341
|
...(credits.hasCredits === undefined ? {} : { hasCredits: credits.hasCredits }),
|
|
283
|
-
};
|
|
342
|
+
} satisfies RotationCandidate;
|
|
284
343
|
})
|
|
285
|
-
.filter((c): c is
|
|
344
|
+
.filter((c): c is NonNullable<typeof c> => c !== null);
|
|
286
345
|
}
|
|
287
346
|
|
|
288
347
|
function rotationCreditPace(candidates: readonly RotationCandidate[], now: number) {
|
|
@@ -364,7 +423,7 @@ function codexAccountAlertsEnabled(): boolean {
|
|
|
364
423
|
}
|
|
365
424
|
}
|
|
366
425
|
|
|
367
|
-
function notifyCodexAccountEvent(event: 'rotation' | 'reset-credit-consumed', details: Record<string, unknown>, text: string):
|
|
426
|
+
function notifyCodexAccountEvent(event: 'rotation' | 'reset-credit-consumed' | 'credit-spend-start', details: Record<string, unknown>, text: string): boolean {
|
|
368
427
|
// ⛔ 끄더라도 «조용히» 사라지지 않는다 — 「왜 알림이 안 오나」를 나중에 로그로 답할 수 있어야 한다.
|
|
369
428
|
if (!codexAccountAlertsEnabled()) {
|
|
370
429
|
debug.log('oauth.codex-account', 'outbound-suppressed', {
|
|
@@ -373,20 +432,106 @@ function notifyCodexAccountEvent(event: 'rotation' | 'reset-credit-consumed', de
|
|
|
373
432
|
knob: 'llm.codexAccountAlerts',
|
|
374
433
|
...details,
|
|
375
434
|
});
|
|
376
|
-
return;
|
|
435
|
+
return false;
|
|
377
436
|
}
|
|
378
|
-
|
|
437
|
+
try {
|
|
379
438
|
const delivered = codexAccountOutboundSender(text, 'alert');
|
|
380
439
|
debug.log('oauth.codex-account', 'outbound', { event, delivered, ...details });
|
|
381
|
-
|
|
382
|
-
try {
|
|
383
|
-
attempt();
|
|
440
|
+
return delivered;
|
|
384
441
|
} catch (error) {
|
|
385
442
|
debug.log('oauth.codex-account', 'outbound-failed', {
|
|
386
443
|
event,
|
|
387
444
|
message: error instanceof Error ? error.message : String(error),
|
|
388
445
|
...details,
|
|
389
446
|
}, { level: 'warn' });
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/** 크레딧 알림 잠금 — 이식 가능한 `wx` 잠금 파일(⛔ `flock(1)` 은 macOS 운영 호스트에 «없다» — mbp·node-b 실측 10-11).
|
|
452
|
+
* 기다림 상한을 넘기거나 오래된(죽은 소유자) 잠금이면 «알림을 잃지 않는 쪽»으로 간다:
|
|
453
|
+
* 오래된 잠금은 걷고, 상한을 넘기면 잠금 없이 보낸다(최악은 같은 날 중복 1통 — 누락보다 낫다). */
|
|
454
|
+
const CREDIT_SPEND_ALERT_LOCK_STALE_MS = 60_000;
|
|
455
|
+
let creditSpendAlertLockWaitMs = 5_000;
|
|
456
|
+
/** 시험 전용 — 기다림 상한을 줄인다(null = 기본 5초). */
|
|
457
|
+
export function _setCreditSpendAlertLockWaitMsForTesting(ms: number | null): void {
|
|
458
|
+
creditSpendAlertLockWaitMs = ms ?? 5_000;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function sleepSync(ms: number): void {
|
|
462
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** 잠갔으면 true(호출자가 풀어야 한다) · 상한을 넘겨 잠금 없이 가면 false. */
|
|
466
|
+
function acquireCreditSpendAlertLock(path: string, account: string): boolean {
|
|
467
|
+
const deadline = Date.now() + creditSpendAlertLockWaitMs;
|
|
468
|
+
for (;;) {
|
|
469
|
+
try {
|
|
470
|
+
const fd = openSync(path, 'wx');
|
|
471
|
+
try { writeFileSync(fd, String(process.pid)); } finally { closeSync(fd); }
|
|
472
|
+
return true;
|
|
473
|
+
} catch (error) {
|
|
474
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
|
|
475
|
+
}
|
|
476
|
+
try {
|
|
477
|
+
if (Date.now() - statSync(path).mtimeMs > CREDIT_SPEND_ALERT_LOCK_STALE_MS) {
|
|
478
|
+
debug.log('oauth.codex-account', 'credit-spend-alert-lock-stale', { account });
|
|
479
|
+
try { unlinkSync(path); } catch { /* 다른 소유자가 먼저 걷었다 */ }
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
} catch { continue; /* 그 사이 풀렸다 */ }
|
|
483
|
+
if (Date.now() >= deadline) {
|
|
484
|
+
debug.log('oauth.codex-account', 'credit-spend-alert-lock-timeout', { account, waitMs: creditSpendAlertLockWaitMs }, { level: 'warn' });
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
sleepSync(50);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/** UTC 일자별 성공한 계정 알림만 기록한다. 잠근 채 발송하여 동시 런도 중복 발송하지 않는다. */
|
|
492
|
+
function notifyCreditSpendStart(
|
|
493
|
+
account: string,
|
|
494
|
+
reason: 'credits-allowed' | 'credit-pace',
|
|
495
|
+
usage: readonly RotationCandidate[],
|
|
496
|
+
now: number,
|
|
497
|
+
counterPath: string,
|
|
498
|
+
): void {
|
|
499
|
+
const day = new Date(now).toISOString().slice(0, 10);
|
|
500
|
+
const lockPath = `${counterPath}.lock`;
|
|
501
|
+
let locked = false;
|
|
502
|
+
try {
|
|
503
|
+
mkdirSync(dirname(counterPath), { recursive: true });
|
|
504
|
+
locked = acquireCreditSpendAlertLock(lockPath, account);
|
|
505
|
+
let accounts: string[] = [];
|
|
506
|
+
try {
|
|
507
|
+
const parsed = JSON.parse(readFileSync(counterPath, 'utf8')) as { day?: unknown; accounts?: unknown };
|
|
508
|
+
if (parsed.day === day && Array.isArray(parsed.accounts)) {
|
|
509
|
+
accounts = parsed.accounts.filter((value): value is string => typeof value === 'string');
|
|
510
|
+
}
|
|
511
|
+
} catch (error) {
|
|
512
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
513
|
+
}
|
|
514
|
+
if (accounts.includes(account)) {
|
|
515
|
+
debug.log('oauth.codex-account', 'credit-spend-alert-skipped', { account, reason: 'already-alerted-today' });
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
const usageSummary = usage.map((candidate) => `${candidate.name}=${formatUsedPercent(candidate.usedPercent)}`).join(', ') || 'unknown';
|
|
519
|
+
if (!notifyCodexAccountEvent('credit-spend-start', { account, reason, day },
|
|
520
|
+
`💳 Codex credits in use\naccount: ${account}\nreason: ${reason}\nusage: ${usageSummary}`)) return;
|
|
521
|
+
const tempPath = `${counterPath}.${process.pid}.${Date.now()}.tmp`;
|
|
522
|
+
try {
|
|
523
|
+
writeFileSync(tempPath, `${JSON.stringify({ day, accounts: [...accounts, account] })}\n`, 'utf8');
|
|
524
|
+
renameSync(tempPath, counterPath);
|
|
525
|
+
} finally {
|
|
526
|
+
try { unlinkSync(tempPath); } catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
|
|
527
|
+
}
|
|
528
|
+
debug.log('oauth.codex-account', 'credit-spend-start', { account, reason, day }, { level: 'warn' });
|
|
529
|
+
} catch (error) {
|
|
530
|
+
debug.log('oauth.codex-account', 'credit-spend-alert-failed', {
|
|
531
|
+
account, message: error instanceof Error ? error.message : String(error),
|
|
532
|
+
}, { level: 'warn' });
|
|
533
|
+
} finally {
|
|
534
|
+
if (locked) { try { unlinkSync(lockPath); } catch { /* 이미 없다 */ } }
|
|
390
535
|
}
|
|
391
536
|
}
|
|
392
537
|
|
|
@@ -511,6 +656,8 @@ export function inspectCodexRotation(
|
|
|
511
656
|
readonly candidateCount: number | undefined;
|
|
512
657
|
readonly accountThresholds: ReturnType<typeof decideCodexRotation>['accountThresholds'];
|
|
513
658
|
readonly to: string | undefined;
|
|
659
|
+
/** SUBSCRIPTION-EXHAUST-REROUTE — 크레딧 예비분에 닿아 `no-candidate` 가 됐을 때만 있다. */
|
|
660
|
+
readonly creditReserveHit?: { readonly balance: number; readonly reserve: number };
|
|
514
661
|
} {
|
|
515
662
|
const path = deps.storePath ?? authStorePath();
|
|
516
663
|
const now = deps.now ?? Date.now();
|
|
@@ -531,7 +678,7 @@ export function inspectCodexRotation(
|
|
|
531
678
|
const rawThreshold = rotationThresholdFromConfig();
|
|
532
679
|
const thresholdPercentByAccount = rotationThresholdsByAccountFromConfig();
|
|
533
680
|
const decision = decideCodexRotation({
|
|
534
|
-
current, explicit, enabled, disabledProvenance: rotationConfig.state, currentReached, currentUsedPercent, accountOrder: accountOrderFromConfig(), resetCreditAvailability: resetCreditAvailability(currentHome, now), thresholdPercent: rawThreshold, thresholdPercentByAccount, creditsAllowed:
|
|
681
|
+
current, explicit, enabled, disabledProvenance: rotationConfig.state, currentReached, currentUsedPercent, accountOrder: accountOrderFromConfig(), resetCreditAvailability: resetCreditAvailability(currentHome, now), thresholdPercent: rawThreshold, thresholdPercentByAccount, creditsAllowed: rotationCreditsGrant(candidates).allowed, creditReserve: codexCreditReserveFromConfig(), ...currentCredits, candidates, creditPace: rotationCreditPace(candidates, now),
|
|
535
682
|
});
|
|
536
683
|
// ⛔⭐ 나이는 «만료돼도» 낸다 — 「65분 전」과 「3일 전」은 다른 진단이다(리뷰 must-fix).
|
|
537
684
|
const observedAtByHome: Record<string, number> = {};
|
|
@@ -553,6 +700,7 @@ export function inspectCodexRotation(
|
|
|
553
700
|
candidates, observedAtByHome, freshByHome,
|
|
554
701
|
knownAccountCount: listCodexAccountsInStore(path).length,
|
|
555
702
|
reason: decision.reason, candidateCount: decision.candidateCount, accountThresholds: decision.accountThresholds, to: decision.to?.name,
|
|
703
|
+
...(decision.creditReserveHit ? { creditReserveHit: decision.creditReserveHit } : {}),
|
|
556
704
|
};
|
|
557
705
|
}
|
|
558
706
|
|
|
@@ -561,7 +709,7 @@ export function resolveCodexAccountForRun(
|
|
|
561
709
|
/** ⛔ 「설정을 우회하는 노브」를 두지 않는다(리뷰 must-fix) — 종전 `rotationEnabled` 는
|
|
562
710
|
* 아무도 안 주면서 «config 를 덮을 수 있는» 인자였다. 그런 인자는 수용 기준을 스스로 깬다.
|
|
563
711
|
* ⊕ `now` 도 아무도 안 주는 죽은 계약이라 지웠다. 남는 것은 «테스트 격리»뿐이다. */
|
|
564
|
-
deps: { readonly storePath?: string } = {},
|
|
712
|
+
deps: { readonly storePath?: string; readonly counterPath?: string } = {},
|
|
565
713
|
): CodexAccountResolution {
|
|
566
714
|
// ⛔ 「이름만 주고 홈을 안 준」 경우를 정본 기록으로 메운다 — 안 그러면 조용히 default 로 떨어지고
|
|
567
715
|
// 관측은 `explicit` 이라 말한다(2026-08-11 72차 실측).
|
|
@@ -618,6 +766,7 @@ export function resolveCodexAccountForRun(
|
|
|
618
766
|
const currentSignalAttributable = signalAttributableToAccount(currentHomeInfo);
|
|
619
767
|
|
|
620
768
|
const creditPace = rotationCreditPace(candidates, now);
|
|
769
|
+
const creditsGrant = rotationCreditsGrant(candidates);
|
|
621
770
|
const decision = decideCodexRotation({
|
|
622
771
|
current,
|
|
623
772
|
explicit,
|
|
@@ -629,11 +778,13 @@ export function resolveCodexAccountForRun(
|
|
|
629
778
|
resetCreditAvailability: resetCreditAvailability(currentHome, now),
|
|
630
779
|
thresholdPercent: rotationThresholdFromConfig(),
|
|
631
780
|
thresholdPercentByAccount: rotationThresholdsByAccountFromConfig(),
|
|
632
|
-
creditsAllowed:
|
|
781
|
+
creditsAllowed: creditsGrant.allowed,
|
|
782
|
+
creditReserve: codexCreditReserveFromConfig(),
|
|
633
783
|
...currentCreditsInput(currentSignalAttributable ? currentHome : undefined, now),
|
|
634
784
|
candidates,
|
|
635
785
|
creditPace,
|
|
636
786
|
});
|
|
787
|
+
observeCodexCreditsStep(creditsGrant, decision, current.name);
|
|
637
788
|
debug.log('codex.rotation', 'credit-pace', {
|
|
638
789
|
active: creditPace.active,
|
|
639
790
|
todaySpent: creditPace.todaySpent,
|
|
@@ -641,6 +792,14 @@ export function resolveCodexAccountForRun(
|
|
|
641
792
|
to: decision.reason === 'credit-pace' ? decision.to?.name ?? current.name : decision.to?.name,
|
|
642
793
|
});
|
|
643
794
|
observeRotation(decision, current.name);
|
|
795
|
+
// 발송 의무는 이 실행 경로에서 잠금 해제까지 기다린 뒤 하루 카운터에 기록한다.
|
|
796
|
+
if ((decision.reason === 'credits-allowed' || decision.reason === 'credit-pace') && !deps.counterPath && isUnderTestRunner(process.env)) {
|
|
797
|
+
// 시험 러너 가드(autoConsume 의 test-runtime-guard 와 같은 관용구) — 카운터를 주입 안 한 시험이 실제 카운터·송신기에 닿지 않는다.
|
|
798
|
+
debug.log('oauth.codex-account', 'credit-spend-alert-skipped', { account: decision.to?.name ?? current.name, reason: 'test-runtime-guard' });
|
|
799
|
+
} else if (decision.reason === 'credits-allowed' || decision.reason === 'credit-pace') {
|
|
800
|
+
notifyCreditSpendStart(decision.to?.name ?? current.name, decision.reason, candidates, now,
|
|
801
|
+
deps.counterPath ?? join(codexCredentialRoot(), 'budget', 'codex-credit-spend-alert.json'));
|
|
802
|
+
}
|
|
644
803
|
// 순서 ② — 구독 남은 계정이 없고 리셋권이 있으면 «스스로 1개» 쓴다(일일 상한 · 비동기 · 이번 호출은 종전대로 머문다).
|
|
645
804
|
// ⛔ 판정이 `reset-credit-available` 일 때만 닿는다 — 구독 남은 계정이 있으면(rotated) 여기 안 온다.
|
|
646
805
|
if (decision.reason === 'reset-credit-available') void autoConsumeCodexResetCredit({ storePath: path });
|
|
@@ -844,6 +1003,65 @@ export function listCodexAccountsInStore(storePath?: string): StoredCodexAccount
|
|
|
844
1003
|
* ⛔ 실패는 삼키되 «조용히»는 아니다 — 못 정했으면 그 사실을 관측에 남기고 `stay` 로 답한다.
|
|
845
1004
|
* (판정이 못 돌아서 백엔드가 안 뜨는 일은 없어야 한다.)
|
|
846
1005
|
*/
|
|
1006
|
+
/** CODEX-CREDITS-STEP 관측 — 회전이 «구독이 다 찬» 판정에 닿았을 때만(크레딧으로 갔든 · 칸을 못 써 건너뛰었든) 한 줄.
|
|
1007
|
+
* ⛔ 비밀·홈 경로를 넣지 않는다(계정 이름 · 이유 · 건너뛴 칸). */
|
|
1008
|
+
function observeCodexCreditsStep(
|
|
1009
|
+
grant: RotationCreditsGrant,
|
|
1010
|
+
decision: { readonly reason: string; readonly to?: RotationCandidate; readonly accountThresholds?: readonly { readonly name: string; readonly status: string }[] },
|
|
1011
|
+
currentName: string,
|
|
1012
|
+
): void {
|
|
1013
|
+
if (grant.via === 'policy') return;
|
|
1014
|
+
// 회전이 «크레딧 계정»으로 옮겼나 — 고른 계정의 구독 창이 이미 찼으면 크레딧 회전이다(구독 회전과 가른다).
|
|
1015
|
+
const toStatus = decision.to ? decision.accountThresholds?.find((row) => row.name === decision.to!.name)?.status : undefined;
|
|
1016
|
+
const creditRotation = decision.reason === 'rotated' && (toStatus === 'reached' || toStatus === 'threshold-reached');
|
|
1017
|
+
if (grant.via === 'chain-step' && (decision.reason === 'credits-allowed' || creditRotation)) {
|
|
1018
|
+
debug.log('oauth.fallback-chain', 'codex-credits-step', {
|
|
1019
|
+
site: 'rotation', outcome: 'credits', rotationReason: decision.reason, from: currentName, to: decision.to?.name ?? currentName,
|
|
1020
|
+
skipped: grant.skipped, creditAccountCount: grant.creditAccountCount,
|
|
1021
|
+
}, { level: 'warn' });
|
|
1022
|
+
} else if (grant.via === 'none' && decision.reason === 'no-candidate' && grant.why === 'codex-credits-unavailable') {
|
|
1023
|
+
debug.log('oauth.fallback-chain', 'codex-credits-step', {
|
|
1024
|
+
site: 'rotation', outcome: 'skipped', rotationReason: decision.reason, why: grant.why,
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
/** 크레딧 잔액이 «확인된» codex 계정 이름(잔액 > 0 · 보유 ≠ false · 홈을 안다). 잔액을 모르면 넣지 않는다 — 돈이라 fail-closed.
|
|
1030
|
+
* (budget-gate `codexCreditCandidate` 와 같은 자.) */
|
|
1031
|
+
export function codexCreditAccountNames(candidates: readonly RotationCandidate[], reserve = 0): string[] {
|
|
1032
|
+
return creditCandidatesByBalance(candidates, reserve).map((c) => c.name);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
/** 명시 계정 «자신»의 크레딧 잔액이 확인되나(잔액 > 0 · 보유 ≠ false) — 신호를 귀속 못 하면 후보 목록의 같은 이름 줄. */
|
|
1036
|
+
function explicitAccountHasCredits(snapshot: {
|
|
1037
|
+
readonly current: { readonly name: string };
|
|
1038
|
+
readonly candidates: readonly RotationCandidate[];
|
|
1039
|
+
readonly currentCreditBalance?: number;
|
|
1040
|
+
readonly currentHasCredits?: boolean;
|
|
1041
|
+
}, reserve = 0): boolean {
|
|
1042
|
+
const self = snapshot.candidates.find((c) => c.name === snapshot.current.name);
|
|
1043
|
+
const balance = snapshot.currentCreditBalance ?? self?.creditBalance;
|
|
1044
|
+
const has = snapshot.currentHasCredits ?? self?.hasCredits;
|
|
1045
|
+
return typeof balance === 'number' && Number.isFinite(balance) && balance > reserve && has !== false;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/** 명시 계정 자신을 후보 모양으로 — 홈을 모르면 없다(그때는 호스트 홈 그대로). */
|
|
1049
|
+
function explicitAccountCandidate(snapshot: {
|
|
1050
|
+
readonly current: { readonly name: string; readonly storeKey: string };
|
|
1051
|
+
readonly currentHome?: string;
|
|
1052
|
+
readonly candidates: readonly RotationCandidate[];
|
|
1053
|
+
}): RotationCandidate | undefined {
|
|
1054
|
+
const self = snapshot.candidates.find((c) => c.name === snapshot.current.name && c.home.length > 0);
|
|
1055
|
+
if (self) return self;
|
|
1056
|
+
const home = snapshot.currentHome?.trim();
|
|
1057
|
+
return home ? { name: snapshot.current.name, storeKey: snapshot.current.storeKey, home, reached: undefined } : undefined;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/** 잔액이 가장 많은(확인된) 계정 — 크레딧 칸이 실제로 쓸 계정. */
|
|
1061
|
+
function richestCreditCandidate(candidates: readonly RotationCandidate[], reserve = 0): RotationCandidate | undefined {
|
|
1062
|
+
return creditCandidatesByBalance(candidates, reserve)[0];
|
|
1063
|
+
}
|
|
1064
|
+
|
|
847
1065
|
export function resolveRunFallback(
|
|
848
1066
|
env: NodeJS.ProcessEnv = process.env,
|
|
849
1067
|
deps: {
|
|
@@ -853,6 +1071,8 @@ export function resolveRunFallback(
|
|
|
853
1071
|
readonly grokQuota?: 'usable' | 'exhausted' | 'unknown';
|
|
854
1072
|
/** OpenRouter 키 «존재» seam(테스트 격리) — 안 주면 `getOpenRouterApiKey()` 로 잰다. 키 값은 기록하지 않는다. */
|
|
855
1073
|
readonly openrouterAvailable?: boolean;
|
|
1074
|
+
/** codex 크레딧 잔액 «확인» seam — 안 주면 회전 후보의 크레딧 신호(잔액 > 0 · 보유 ≠ false)로 잰다. */
|
|
1075
|
+
readonly codexCreditsAvailable?: boolean;
|
|
856
1076
|
} & Pick<FallbackInput, 'currentStep' | 'currentCredentialRateLimited'> = {},
|
|
857
1077
|
): FallbackDecision {
|
|
858
1078
|
try {
|
|
@@ -888,26 +1108,62 @@ export function resolveRunFallback(
|
|
|
888
1108
|
// ⛔ grok 가용성은 «호출자가 확정»해서 줄 수 있다(테스트 격리). 안 주면 여기서 잰다.
|
|
889
1109
|
// ⛔ «가용성 판정»이라 갱신을 유도하지 않는다 — 「자격이 있나」만 묻지 「지금 쓸 수 있나」를 묻지 않는다.
|
|
890
1110
|
// (쓸 수 있는지는 실제 요청 경로가 접힌 해석으로 확인한다.)
|
|
891
|
-
const grokAvailable = deps.grokAvailable ??
|
|
1111
|
+
const grokAvailable = deps.grokAvailable ?? grokCredentialPresent();
|
|
892
1112
|
// ⛔ 「지금 쓸 수 있나」는 «자격과 다른 축»이다. 안 주면 'unknown' — 그리고 unknown 은 «통과»다.
|
|
893
1113
|
// ⇒ 「모르고 갔다」와 「알고 갔다」가 관측에서 갈린다(아래 grokQuota).
|
|
894
1114
|
const grokQuota = deps.grokQuota ?? readCachedGrokQuota();
|
|
895
1115
|
const fallbackModel = configReader().llm?.openrouter?.fallbackModel;
|
|
896
|
-
|
|
897
|
-
|
|
1116
|
+
// SUBSCRIPTION-EXHAUST-REROUTE — 크레딧 예비분에 닿은 «넘김»은 OpenRouter 가 먼저다(대표 OP 10-11: grok 이 아니다).
|
|
1117
|
+
// 운영 체인 `['codex-rotate','grok']` 에는 openrouter 칸이 없다 ⇒ 이 경로에만 codex 바로 뒤에 openrouter 를 끼운다
|
|
1118
|
+
// (나머지 칸은 그 뒤 · 키가 없으면 종전 체인으로). 모델은 `llm.openrouter.fallbackModel`, 없으면 «중» 기본 glm-5.3.
|
|
1119
|
+
// ⛔ 정책이 within-quota 면 체인 밖으로 안 나간다(종전 규칙 그대로).
|
|
1120
|
+
const reserveHit = snapshot.creditReserveHit;
|
|
1121
|
+
const reserveReroute = reserveHit !== undefined && codexPolicyAllowsFallback(quotaPolicy);
|
|
1122
|
+
const effectiveChain: readonly FallbackStep[] = reserveReroute
|
|
1123
|
+
? ['codex-rotate', 'openrouter', ...chain.filter((step) => step !== 'codex-rotate' && step !== 'openrouter')]
|
|
1124
|
+
: chain;
|
|
1125
|
+
const openrouterFallbackModel = typeof fallbackModel === 'string' && fallbackModel.trim()
|
|
1126
|
+
? fallbackModel
|
|
1127
|
+
: reserveReroute ? CREDIT_RESERVE_OPENROUTER_MODEL : undefined;
|
|
1128
|
+
const openrouterAvailable = effectiveChain.includes('openrouter')
|
|
898
1129
|
&& (deps.openrouterAvailable ?? Boolean(getOpenRouterApiKey()?.trim()));
|
|
899
|
-
|
|
1130
|
+
// CODEX-CREDITS-STEP — 체인에 그 칸이 있을 때만 잰다(없으면 판정이 안 읽는다). 잔액을 «모르면» 없다고 본다(돈 · fail-closed).
|
|
1131
|
+
// ⛔ 사람이 계정을 «명시»했으면 그 계정의 잔액만 본다 — 다른 계정 잔액으로 칸을 열고 명시 계정으로 쏘면 fail-closed 가 깨진다.
|
|
1132
|
+
const explicitAccount = snapshot.reason === 'explicit';
|
|
1133
|
+
const creditReserve = codexCreditReserveFromConfig();
|
|
1134
|
+
const creditAccounts = !effectiveChain.includes('codex-credits') ? []
|
|
1135
|
+
: explicitAccount ? (explicitAccountHasCredits(snapshot, creditReserve) ? [snapshot.current.name] : [])
|
|
1136
|
+
: codexCreditAccountNames(snapshot.candidates, creditReserve);
|
|
1137
|
+
const codexCreditsAvailable = effectiveChain.includes('codex-credits') && (deps.codexCreditsAvailable ?? creditAccounts.length > 0);
|
|
1138
|
+
const fallbackBase = { rotation, chain: effectiveChain, grokAvailable, grokQuota, openrouterFallbackModel, openrouterAvailable, codexCreditsAvailable };
|
|
900
1139
|
const fallbackInput: FallbackInput = deps.currentCredentialRateLimited === true && deps.currentStep
|
|
901
1140
|
? { ...fallbackBase, currentStep: deps.currentStep, currentCredentialRateLimited: true }
|
|
902
1141
|
: { ...fallbackBase, ...(deps.currentStep ? { currentStep: deps.currentStep } : {}) };
|
|
903
|
-
const
|
|
1142
|
+
const judged = decideFallback(fallbackInput);
|
|
1143
|
+
// CODEX-CREDITS-STEP — 칸에 닿았으면 «어느 계정 크레딧으로» 갈지도 정한다(잔액 많은 순 · 홈을 안다).
|
|
1144
|
+
// 런타임 재시도(grok 429)는 회전을 다시 거치지 않으므로 여기서 계정을 실어 보내야 자식이 그 계정으로 뜬다.
|
|
1145
|
+
// ⛔ 사람이 계정을 «명시»했으면(explicit) 계정을 바꾸지 않는다 — 의도가 이긴다(그 계정 그대로 크레딧).
|
|
1146
|
+
// 명시 계정이면 «그 계정»의 홈을 싣는다 — 계정은 안 바꾸되, 잔액을 확인한 계정과 발사 홈이 갈리지 않게.
|
|
1147
|
+
const creditTo = judged.action !== 'codex-credits' ? undefined
|
|
1148
|
+
: explicitAccount ? explicitAccountCandidate(snapshot)
|
|
1149
|
+
: richestCreditCandidate(snapshot.candidates, creditReserve);
|
|
1150
|
+
const decision: FallbackDecision = judged.action === 'codex-credits' && creditTo ? { ...judged, to: creditTo } : judged;
|
|
1151
|
+
if (reserveHit) {
|
|
1152
|
+
debug.log('llm.codex', 'credit-reserve-hit', {
|
|
1153
|
+
balance: reserveHit.balance, reserve: reserveHit.reserve,
|
|
1154
|
+
next: decision.action === 'switch-backend' ? decision.backend : decision.action === 'codex-rotate' ? 'codex-rotate' : decision.action === 'codex-credits' ? 'codex-credits' : `stay:${decision.why}`,
|
|
1155
|
+
...(decision.action === 'switch-backend' && decision.backend === 'openrouter' ? { model: decision.model } : {}),
|
|
1156
|
+
openrouterAvailable, rerouted: reserveReroute,
|
|
1157
|
+
}, { level: 'warn' });
|
|
1158
|
+
}
|
|
904
1159
|
// tier-map 칸(가격·창 근거) — 유료 전환은 «무슨 값으로 가는지»를 관측에 남긴다. 미등록이면 'unregistered'.
|
|
905
1160
|
const openrouterSpec = decision.action === 'switch-backend' && decision.backend === 'openrouter'
|
|
906
1161
|
? lookupOpenRouterModelSpec(decision.model) : undefined;
|
|
907
1162
|
|
|
908
1163
|
debug.log('oauth.fallback-chain', 'decide', {
|
|
909
1164
|
rotationReason: rotation.reason,
|
|
910
|
-
chain, usedDefault, quotaPolicy,
|
|
1165
|
+
chain: effectiveChain, usedDefault, quotaPolicy,
|
|
1166
|
+
...(reserveHit ? { creditReserveHit: true } : {}),
|
|
911
1167
|
rotationResult: rotation.reason,
|
|
912
1168
|
// ⛔⭐ 「안 줬다」를 «false 로 접지 않는다»(무인 리뷰 must-fix · UNKNOWN-DEFAULT).
|
|
913
1169
|
// 호출자가 이 값을 생략하면 그것은 「한도가 아니었다」가 «아니라» ***「안 쟀다」***다.
|
|
@@ -915,11 +1171,11 @@ export function resolveRunFallback(
|
|
|
915
1171
|
credentialRateLimited: fallbackInput.currentCredentialRateLimited === undefined
|
|
916
1172
|
? 'unknown'
|
|
917
1173
|
: fallbackInput.currentCredentialRateLimited === true,
|
|
918
|
-
selectedChainStep: decision.action === 'codex-rotate' ? 'codex-rotate' : decision.action === 'switch-backend' ? decision.backend : null,
|
|
1174
|
+
selectedChainStep: decision.action === 'codex-rotate' ? 'codex-rotate' : decision.action === 'switch-backend' ? decision.backend : decision.action === 'codex-credits' ? 'codex-credits' : null,
|
|
919
1175
|
...(dropped.length > 0 ? { droppedSteps: dropped } : {}),
|
|
920
1176
|
grokAvailable,
|
|
921
1177
|
grokQuota,
|
|
922
|
-
...(
|
|
1178
|
+
...(effectiveChain.includes('openrouter') ? { openrouterAvailable, openrouterModelConfigured: Boolean(openrouterFallbackModel?.trim()) } : {}),
|
|
923
1179
|
action: decision.action,
|
|
924
1180
|
...(decision.action === 'switch-backend' ? { backend: decision.backend } : {}),
|
|
925
1181
|
...(decision.action === 'switch-backend' && decision.backend === 'openrouter'
|
|
@@ -928,7 +1184,50 @@ export function resolveRunFallback(
|
|
|
928
1184
|
...(decision.action === 'stay' ? { why: decision.why } : {}),
|
|
929
1185
|
...(decision.action === 'codex-rotate' ? { to: decision.to.name } : {}),
|
|
930
1186
|
});
|
|
931
|
-
|
|
1187
|
+
// 칸에 «닿았나» — 판정이 체인을 걸었고(한도 오류 재시도 · codex 축이 끝남) 그 걸음이 칸을 지났나.
|
|
1188
|
+
// 최종 결정과 무관하다: 칸을 건너뛰고 뒤 칸으로 갔든, 뒤 칸까지 못 써 머물렀든 도달은 도달이다.
|
|
1189
|
+
const selectedStep: FallbackStep | undefined = decision.action === 'switch-backend'
|
|
1190
|
+
? (decision.backend === 'claude' ? 'claude-pty' : decision.backend) : undefined;
|
|
1191
|
+
const walked = fallbackInput.currentCredentialRateLimited === true
|
|
1192
|
+
|| rotation.reason === 'no-candidate' || rotation.reason === 'disabled' || rotation.reason === 'reset-credit-available'
|
|
1193
|
+
|| ('to' in rotation && !chain.includes('codex-rotate'));
|
|
1194
|
+
const walkStart = fallbackInput.currentCredentialRateLimited === true ? fallbackInput.currentStep : 'codex-rotate';
|
|
1195
|
+
const walkIdx = walkStart === undefined ? -1 : chain.indexOf(walkStart);
|
|
1196
|
+
const walkRest = walkIdx === -1 ? chain : chain.slice(walkIdx + 1);
|
|
1197
|
+
const reachedCredits = walked && walkRest.includes('codex-credits')
|
|
1198
|
+
&& (selectedStep === undefined || walkRest.indexOf('codex-credits') < walkRest.indexOf(selectedStep));
|
|
1199
|
+
if (reachedCredits && decision.action !== 'codex-credits') {
|
|
1200
|
+
// 칸에 «닿았지만» 잔액 확인된 계정이 없어 못 썼다 — 실패한 도달도 이유와 함께 남긴다.
|
|
1201
|
+
debug.log('oauth.fallback-chain', 'codex-credits-step', {
|
|
1202
|
+
site: 'chain', outcome: 'skipped', why: 'codex-credits-unavailable', next: selectedStep ?? null, rotationReason: rotation.reason, quotaPolicy, chain,
|
|
1203
|
+
grokAvailable, grokQuota, currentStep: fallbackInput.currentStep ?? null,
|
|
1204
|
+
credentialRateLimited: fallbackInput.currentCredentialRateLimited === true,
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
if (decision.action === 'codex-credits') {
|
|
1208
|
+
// ⭐ 체인이 «마지막 칸»(유료)에 닿을 때마다 한 줄 — 왜 앞 칸들을 못 썼나를 값으로(비밀·홈 경로 없음).
|
|
1209
|
+
debug.log('oauth.fallback-chain', 'codex-credits-step', {
|
|
1210
|
+
site: 'chain', outcome: 'credits',
|
|
1211
|
+
to: decision.to?.name ?? null,
|
|
1212
|
+
rotationReason: rotation.reason,
|
|
1213
|
+
quotaPolicy,
|
|
1214
|
+
chain,
|
|
1215
|
+
skipped: decision.skipped,
|
|
1216
|
+
grokAvailable,
|
|
1217
|
+
grokQuota,
|
|
1218
|
+
currentStep: fallbackInput.currentStep ?? null,
|
|
1219
|
+
credentialRateLimited: fallbackInput.currentCredentialRateLimited === true,
|
|
1220
|
+
creditAccountCount: deps.codexCreditsAvailable === undefined ? creditAccounts.length : 'seam',
|
|
1221
|
+
}, { level: 'warn' });
|
|
1222
|
+
emitDecision({
|
|
1223
|
+
kind: 'ROUTE',
|
|
1224
|
+
what: '공급자 codex(크레딧) — 체인 마지막 칸',
|
|
1225
|
+
reason: `회전 ${rotation.reason} · 정책 ${quotaPolicy} · 건너뜀 ${decision.skipped.map((x) => `${x.step}:${x.why}`).join(', ') || '없음'}${fallbackInput.currentCredentialRateLimited ? ' · 한도 오류' : ''}`,
|
|
1226
|
+
purpose: '구독·grok 이 모두 막혀 선불 크레딧으로 작업을 이어 간다',
|
|
1227
|
+
target: 'openai-codex(credits)',
|
|
1228
|
+
paths: chain.length,
|
|
1229
|
+
});
|
|
1230
|
+
} else if (decision.action === 'switch-backend') {
|
|
932
1231
|
const openrouterNote = decision.backend === 'openrouter'
|
|
933
1232
|
? ` · API 과금 ${decision.model} (${openrouterSpec ? `${openrouterSpec.label} · ${openrouterSpec.rationale}` : 'tier-map 미등록'})`
|
|
934
1233
|
: '';
|