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
package/src/budget/llm-cost.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { findGrokModel } from '../grok/models.js';
|
|
|
11
11
|
import { getUserConfig } from '../user-config.js';
|
|
12
12
|
import { BUILTIN_CATALOG } from '../intelligence-map/model-catalog.js';
|
|
13
13
|
import { getCatalog } from '../registry/loader.js';
|
|
14
|
+
import { byokCostFor, type ByokActualCost, type ByokPartialCost } from '../llm/openrouter-byok-cost.js';
|
|
14
15
|
|
|
15
16
|
export interface LlmPricingUsd {
|
|
16
17
|
inputPerM: number;
|
|
@@ -23,6 +24,13 @@ export interface LlmPricingUsd {
|
|
|
23
24
|
longContext?: { thresholdTokens: number; inputPerM: number; outputPerM: number; cacheReadPerM?: number; cacheWritePerM?: number };
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
/** AWS Bedrock Global Standard prices, USD per million tokens (AWS pricing API, ap-northeast-2). */
|
|
28
|
+
export const BEDROCK_PRICING_USD: Readonly<Record<string, LlmPricingUsd>> = {
|
|
29
|
+
'claude-haiku-5-5': { inputPerM: 0.1, outputPerM: 0.5, cacheWritePerM: 0.125, cacheReadPerM: 0.01 },
|
|
30
|
+
'claude-sonnet-5-5': { inputPerM: 2, outputPerM: 10, cacheWritePerM: 2.5, cacheReadPerM: 0.1 },
|
|
31
|
+
'claude-opus-5-5': { inputPerM: 4, outputPerM: 20, cacheWritePerM: 5, cacheReadPerM: 0.2 },
|
|
32
|
+
};
|
|
33
|
+
|
|
26
34
|
export interface LlmCostUsage {
|
|
27
35
|
model: string;
|
|
28
36
|
inputTokens?: number;
|
|
@@ -46,7 +54,7 @@ export type LlmCostEstimate =
|
|
|
46
54
|
kind: 'known';
|
|
47
55
|
model: string;
|
|
48
56
|
usd: number;
|
|
49
|
-
source: 'catalog' | 'config' | 'registry';
|
|
57
|
+
source: 'catalog' | 'bedrock-catalog' | 'config' | 'registry';
|
|
50
58
|
cacheReadPricedAt: 'cache-read' | 'input-rate';
|
|
51
59
|
cacheWritePricedAt: 'cache-write' | 'input-rate';
|
|
52
60
|
/** 장문맥 단가로 매겼으면 `'long-context'` — 없으면 기본 단가. */
|
|
@@ -56,7 +64,7 @@ export type LlmCostEstimate =
|
|
|
56
64
|
kind: 'partial';
|
|
57
65
|
model: string;
|
|
58
66
|
usd: number;
|
|
59
|
-
source: 'catalog' | 'config' | 'registry';
|
|
67
|
+
source: 'catalog' | 'bedrock-catalog' | 'config' | 'registry';
|
|
60
68
|
cacheReadPricedAt: 'cache-read' | 'input-rate';
|
|
61
69
|
cacheWritePricedAt: 'cache-write' | 'input-rate';
|
|
62
70
|
pricedTier?: 'long-context';
|
|
@@ -147,6 +155,21 @@ export function normalizePricingModelId(model: string): string {
|
|
|
147
155
|
return m;
|
|
148
156
|
}
|
|
149
157
|
|
|
158
|
+
/** Normalize a Bedrock Anthropic model id to its bare Claude pricing id. */
|
|
159
|
+
export function bedrockPricingModelId(model: string): string {
|
|
160
|
+
return model.trim()
|
|
161
|
+
.replace(/^bedrock\//, '')
|
|
162
|
+
.replace(/^[a-z0-9-]+\.anthropic\./i, '')
|
|
163
|
+
.replace(/^anthropic\./, '')
|
|
164
|
+
.replace(/-v1:0$/, '');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function isBedrockPricingModel(model: string): boolean {
|
|
168
|
+
const trimmed = model.trim();
|
|
169
|
+
return /^(?:bedrock\/|[a-z0-9-]+\.anthropic\.|anthropic\.)/i.test(trimmed)
|
|
170
|
+
&& bedrockPricingModelId(trimmed).startsWith('claude-');
|
|
171
|
+
}
|
|
172
|
+
|
|
150
173
|
/** 저장소의 모델 정본(intelligence-map BUILTIN_CATALOG) — 손 카탈로그 넷에 없는 모델(Claude 5 계열 등)의 입력·출력 단가.
|
|
151
174
|
* 캐시 단가는 거기 없다 → Anthropic 모델은 공식 규칙(읽기 0.1× · 쓰기 5분 1.25×)으로 파생한다. 다른 벤더는 입력 단가로 매기고
|
|
152
175
|
* `cacheReadPricedAt: 'input-rate'` 로 그 사실을 드러낸다(추측하지 않는다). */
|
|
@@ -174,8 +197,8 @@ function registryPricing(model: string): LlmPricingUsd | undefined {
|
|
|
174
197
|
function resolvePricing(
|
|
175
198
|
model: string,
|
|
176
199
|
opts?: EstimateLlmCostOptions,
|
|
177
|
-
): { pricing: LlmPricingUsd; source: 'catalog' | 'config' | 'registry' } | undefined {
|
|
178
|
-
// ⭐ 순서: 손 카탈로그(정확) → 정규화 id → config 오버레이 → 저장소 정본 → 레지스트리. 앞이 이긴다.
|
|
200
|
+
): { pricing: LlmPricingUsd; source: 'catalog' | 'bedrock-catalog' | 'config' | 'registry' } | undefined {
|
|
201
|
+
// ⭐ 순서: 손 카탈로그(정확) → 정규화 id → config 오버레이 → Bedrock 표 → 저장소 정본 → 레지스트리. 앞이 이긴다.
|
|
179
202
|
const normalized = normalizePricingModelId(model);
|
|
180
203
|
for (const id of normalized === model ? [model] : [model, normalized]) {
|
|
181
204
|
const catalog = catalogPricing(id);
|
|
@@ -185,6 +208,10 @@ function resolvePricing(
|
|
|
185
208
|
const overlay = opts?.configPricing?.[id];
|
|
186
209
|
if (validPricing(overlay)) return { pricing: overlay, source: 'config' };
|
|
187
210
|
}
|
|
211
|
+
if (isBedrockPricingModel(model)) {
|
|
212
|
+
const bedrock = BEDROCK_PRICING_USD[bedrockPricingModelId(model)];
|
|
213
|
+
if (validPricing(bedrock)) return { pricing: bedrock, source: 'bedrock-catalog' };
|
|
214
|
+
}
|
|
188
215
|
const builtin = builtinCatalogPricing(normalized);
|
|
189
216
|
if (validPricing(builtin)) return { pricing: builtin, source: 'catalog' };
|
|
190
217
|
const registry = registryPricing(model);
|
|
@@ -297,11 +324,16 @@ export function llmUsageCostFields(
|
|
|
297
324
|
usage: Omit<LlmCostUsage, 'model'>,
|
|
298
325
|
opts?: EstimateLlmCostOptions,
|
|
299
326
|
billing?: BillingRoute,
|
|
300
|
-
): { cost: LlmCostEstimate | IncludedLlmCost | ActualLlmCost } {
|
|
327
|
+
): { cost: LlmCostEstimate | IncludedLlmCost | ActualLlmCost | ByokActualCost | ByokPartialCost } {
|
|
301
328
|
const resolved = opts ?? { configPricing: readLlmConfigPricing() };
|
|
302
329
|
const estimate = estimateLlmCost({ model, ...usage }, resolved);
|
|
303
330
|
// ⛔⭐ 구독을 API 단가로 «known» 이라 적지 않는다 — 09-25 벤치에서 codex 구독 팔이 `$7.81 known` 으로 보였다.
|
|
304
331
|
// ⭐ BACKLOG C7 — provider 가 실어 보낸 «실제 청구액»이 있으면 그것이 비용이다(추정은 곁에 남긴다).
|
|
332
|
+
// OR-BYOK-COST — BYOK 면 «OR 청구 + 상류 청구»가 비용이다(상류를 모르면 partial · 0 으로 가정하지 않는다).
|
|
333
|
+
if (billing !== 'subscription' && billing !== 'local') {
|
|
334
|
+
const byok = byokCostFor(model, usage as Parameters<typeof byokCostFor>[1], estimate.kind === 'known' || estimate.kind === 'partial' ? estimate.usd : undefined);
|
|
335
|
+
if (byok) return { cost: byok };
|
|
336
|
+
}
|
|
305
337
|
const reported = (usage as { reportedCostUsd?: unknown }).reportedCostUsd;
|
|
306
338
|
if (typeof reported === 'number' && Number.isFinite(reported) && billing !== 'subscription' && billing !== 'local') {
|
|
307
339
|
return { cost: { kind: 'actual', model, usd: reported, source: 'provider-reported', ...(estimate.kind === 'known' || estimate.kind === 'partial' ? { estimateUsd: estimate.usd } : {}) } };
|
package/src/budget/types.ts
CHANGED
|
@@ -158,18 +158,24 @@ export type ResetCreditExpiryAxis =
|
|
|
158
158
|
| { readonly status: 'expired'; readonly expiresAt: string; readonly hasUnknownExpiry: boolean };
|
|
159
159
|
|
|
160
160
|
export interface AccountUsageRow {
|
|
161
|
-
|
|
161
|
+
/** `zai` = GLM Coding Plan(ZAI-CODING-PLAN-PROVIDER) — `ZAI_API_KEY` 가 있을 때만 행이 생긴다. */
|
|
162
|
+
readonly provider: 'codex' | 'grok' | 'openrouter' | 'zai';
|
|
162
163
|
readonly accountName: string;
|
|
163
164
|
readonly accountCount: number;
|
|
164
165
|
readonly soleAccount: boolean;
|
|
165
166
|
readonly credits: CreditAxis;
|
|
166
167
|
readonly subscription: SubscriptionAxis;
|
|
167
168
|
readonly resetCredits: ResetCreditExpiryAxis;
|
|
169
|
+
/** 행 끝에 붙는 한 줄(출처·실수치) — 없으면 종전 문면과 바이트 동일. */
|
|
170
|
+
readonly note?: string;
|
|
168
171
|
}
|
|
169
172
|
|
|
170
173
|
export interface UnifiedUsageReport {
|
|
171
174
|
readonly rows: readonly AccountUsageRow[];
|
|
172
|
-
|
|
175
|
+
/** `zai` 는 키가 있을 때만 실린다(없으면 종전 JSON 과 같다). */
|
|
176
|
+
readonly accountCounts: Readonly<Record<'codex' | 'grok' | 'openrouter', number>> & { readonly zai?: number };
|
|
177
|
+
/** GLM Coding Plan 상세(창 · 우리 적산 · 잔액 사용 · 마지막 한도 코드) — 키가 있을 때만. */
|
|
178
|
+
readonly zaiCodingPlan?: import('../llm/zai-coding-plan.js').ZaiUsageView;
|
|
173
179
|
}
|
|
174
180
|
|
|
175
181
|
export interface TurnSummary {
|
|
@@ -9,6 +9,7 @@ import { createCodexFetcher, type CodexFetcherOpts } from './fetchers/codex.js';
|
|
|
9
9
|
import {
|
|
10
10
|
fetchGrokUsage,
|
|
11
11
|
GROK_SUBSCRIPTION_AXIS,
|
|
12
|
+
grokUsageToSubscriptionAxis,
|
|
12
13
|
grokUsageToCreditAxis,
|
|
13
14
|
type GrokUsageResult,
|
|
14
15
|
} from '../grok/usage.js';
|
|
@@ -20,6 +21,10 @@ import { envLiteral } from '../platform/env-literal.js';
|
|
|
20
21
|
import { describeResetCreditExpiry } from './codex-reset-credit-state.js';
|
|
21
22
|
import { visibleWidth } from '../tui.js';
|
|
22
23
|
import { listCodexResetCredits, type CodexResetCreditsResult } from './codex-reset-credits.js';
|
|
24
|
+
import {
|
|
25
|
+
buildZaiUsageView, fetchZaiQuota, getZaiApiKey, readZaiLedger,
|
|
26
|
+
type ZaiLedgerRecord, type ZaiQuotaResult, type ZaiUsageView,
|
|
27
|
+
} from '../llm/zai-coding-plan.js';
|
|
23
28
|
import type {
|
|
24
29
|
AccountUsageRow,
|
|
25
30
|
CreditAxis,
|
|
@@ -50,24 +55,93 @@ export interface UnifiedUsageDeps {
|
|
|
50
55
|
/** 결정 2026-09-23 — OpenRouter 선불 크레딧 조회 seam. 없으면 실키로 `/api/v1/credits`. */
|
|
51
56
|
readonly openRouterKey?: () => string | undefined;
|
|
52
57
|
readonly fetchOpenRouterImpl?: typeof fetch;
|
|
58
|
+
/** ZAI-CODING-PLAN-PROVIDER seam — 키 «있음» 판정(값은 안 쓴다) · 공식 조회 · 원장. */
|
|
59
|
+
readonly zaiKey?: () => string | undefined;
|
|
60
|
+
readonly fetchZaiQuota?: () => Promise<ZaiQuotaResult>;
|
|
61
|
+
readonly readZaiLedger?: () => ZaiLedgerRecord[];
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
export async function collectUnifiedUsage(deps: UnifiedUsageDeps = {}): Promise<UnifiedUsageReport> {
|
|
56
|
-
const [codexRows, grokRows, openRouterRows] = await Promise.all([
|
|
65
|
+
const [codexRows, grokRows, openRouterRows, zai] = await Promise.all([
|
|
57
66
|
collectCodexRows(deps),
|
|
58
67
|
collectGrokRows(deps),
|
|
59
68
|
collectOpenRouterRows(deps),
|
|
69
|
+
collectZaiRows(deps),
|
|
60
70
|
]);
|
|
61
71
|
return {
|
|
62
|
-
rows: [...codexRows, ...grokRows, ...openRouterRows],
|
|
72
|
+
rows: [...codexRows, ...grokRows, ...openRouterRows, ...(zai?.rows ?? [])],
|
|
63
73
|
accountCounts: {
|
|
64
74
|
codex: codexRows.length,
|
|
65
75
|
grok: grokRows.length,
|
|
66
76
|
openrouter: openRouterRows.length,
|
|
77
|
+
// ⛔ 키가 없으면 칸 자체를 안 싣는다 — 종전 JSON·문면과 바이트 동일.
|
|
78
|
+
...(zai ? { zai: 1 } : {}),
|
|
67
79
|
},
|
|
80
|
+
...(zai ? { zaiCodingPlan: zai.view } : {}),
|
|
68
81
|
};
|
|
69
82
|
}
|
|
70
83
|
|
|
84
|
+
/** ZAI-CODING-PLAN-PROVIDER — GLM Coding Plan 의 5h·주 창 ⊕ 잔액(extra usage) 사용.
|
|
85
|
+
* ⓒ 공식 조회(`/api/monitor/usage/quota/limit`)가 되면 그 값 · 안 되면 ⓐ 우리 적산 ⊕ ⓑ 429 리셋 시각으로 «추정».
|
|
86
|
+
* ⛔ 키가 없으면 행을 «안» 만든다. ⛔ 시험 런타임은 seam 이 없으면 «키 없음»(키 캐시는 파일이라 스크럽을 통과한다). */
|
|
87
|
+
async function collectZaiRows(deps: UnifiedUsageDeps): Promise<{ rows: AccountUsageRow[]; view: ZaiUsageView } | null> {
|
|
88
|
+
// ⛔ 시험 런타임(NODE_ENV=test)은 seam 이 없으면 «env 의 ZAI_API_KEY 만» 본다 — 키 캐시 «파일»은 env 스크럽을 통과해
|
|
89
|
+
// seam 을 모르는 옛 usage 시험이 실제 Z.ai 를 치게 되기 때문이다. env 로 키를 주면 test 에서도 행이 선다.
|
|
90
|
+
const key = (deps.zaiKey ?? (process.env.NODE_ENV === 'test' ? () => process.env.ZAI_API_KEY?.trim() || undefined : () => getZaiApiKey()))();
|
|
91
|
+
if (!key) return null;
|
|
92
|
+
const nowMs = (deps.now ?? Date.now)();
|
|
93
|
+
// ⛔ 조회가 «던져도» usage 전체를 죽이지 않는다 — 실패는 결과값으로 바꿔 추정 표로 간다.
|
|
94
|
+
const quota: ZaiQuotaResult = await (deps.fetchZaiQuota ?? (() => fetchZaiQuota()))()
|
|
95
|
+
.catch((error: unknown) => ({ status: 'error' as const, detail: error instanceof Error ? error.message : String(error) }));
|
|
96
|
+
const view = buildZaiUsageView(quota, (deps.readZaiLedger ?? (() => readZaiLedger()))(), nowMs);
|
|
97
|
+
const base = { provider: 'zai' as const, accountCount: 1, soleAccount: true, resetCredits: { status: 'not-applicable' } as const };
|
|
98
|
+
const sourceLabel = { official: '공식', next_flush_time: 'next_flush_time', estimated: '추정' } as const;
|
|
99
|
+
const rows: AccountUsageRow[] = view.windows.map((w) => {
|
|
100
|
+
const windowMs = w.window === '5h' ? 5 * 3_600_000 : 7 * 86_400_000;
|
|
101
|
+
const usedPercent = w.limit > 0 ? Math.round((w.used / w.limit) * 1000) / 10 : null;
|
|
102
|
+
const end = w.resetAt !== null && isValidEpochMs(w.resetAt) ? new Date(w.resetAt) : null;
|
|
103
|
+
const kind: WindowKind = w.window === '5h' ? 'session' : 'weekly';
|
|
104
|
+
return {
|
|
105
|
+
...base,
|
|
106
|
+
accountName: w.window === '5h' ? 'plan-5h' : 'plan-week',
|
|
107
|
+
credits: {
|
|
108
|
+
status: 'ok',
|
|
109
|
+
usedPercent,
|
|
110
|
+
periodType: kind,
|
|
111
|
+
periodStart: end ? new Date(end.getTime() - windowMs).toISOString() : null,
|
|
112
|
+
periodEnd: end?.toISOString() ?? null,
|
|
113
|
+
monthlyLimit: null,
|
|
114
|
+
used: w.used,
|
|
115
|
+
onDemandCap: null,
|
|
116
|
+
onDemandUsed: null,
|
|
117
|
+
prepaidBalance: null,
|
|
118
|
+
balance: null,
|
|
119
|
+
hasCredits: null,
|
|
120
|
+
unlimited: false,
|
|
121
|
+
},
|
|
122
|
+
// 리셋 시각을 모르면 «available» 로 NaN 을 흘리지 않는다.
|
|
123
|
+
subscription: usedPercent === null || !end
|
|
124
|
+
? { status: 'unavailable', reason: 'query-does-not-supply' }
|
|
125
|
+
: { status: 'available', remainingPercent: Math.round((100 - usedPercent) * 10) / 10, resetsAt: end.getTime(), windowKind: kind },
|
|
126
|
+
// ⓐ 우리 적산을 공식 값 옆에 같이 둔다 — 공식은 정수 크레딧으로 내려 작은 호출이 0 으로 보인다(10-11 실측).
|
|
127
|
+
note: `${w.used}/${w.limit} 크레딧 · 출처=${sourceLabel[w.source]}${w.resetAt === null ? ' · 리셋 시각 모름' : ''}`
|
|
128
|
+
+ (w.source === 'official' ? ` · elanous 적산 ${w.window === '5h' ? view.local.fiveHour.used : view.local.weekly.used}` : ''),
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
rows.push({
|
|
132
|
+
...base,
|
|
133
|
+
accountName: 'extra-usage',
|
|
134
|
+
credits: {
|
|
135
|
+
status: 'ok', usedPercent: null, periodType: 'extra-usage', periodStart: null, periodEnd: null,
|
|
136
|
+
monthlyLimit: null, used: view.balanceCredits, onDemandCap: null, onDemandUsed: null,
|
|
137
|
+
prepaidBalance: null, balance: null, hasCredits: null, unlimited: false,
|
|
138
|
+
},
|
|
139
|
+
subscription: { status: 'unavailable', reason: 'not-a-subscription' },
|
|
140
|
+
note: `잔액 사용 ${view.balanceCredits} 크레딧(추정 · 최근 7일)${view.lastLimit ? ` · 마지막 한도 코드 ${view.lastLimit.code}` : ''}`,
|
|
141
|
+
});
|
|
142
|
+
return { rows, view };
|
|
143
|
+
}
|
|
144
|
+
|
|
71
145
|
async function collectCodexRows(deps: UnifiedUsageDeps): Promise<AccountUsageRow[]> {
|
|
72
146
|
const listed = (deps.listCodexAccounts ?? listCodexAccountsInStore)();
|
|
73
147
|
const accounts = listed;
|
|
@@ -138,7 +212,11 @@ async function collectGrokRows(deps: UnifiedUsageDeps): Promise<AccountUsageRow[
|
|
|
138
212
|
resetCredits: { status: 'not-applicable' } as const,
|
|
139
213
|
};
|
|
140
214
|
if (result.status === 'ok') {
|
|
141
|
-
return [{
|
|
215
|
+
return [{
|
|
216
|
+
...base,
|
|
217
|
+
subscription: grokUsageToSubscriptionAxis(result.usage),
|
|
218
|
+
credits: grokUsageToCreditAxis(result.usage),
|
|
219
|
+
}];
|
|
142
220
|
}
|
|
143
221
|
if (result.status === 'no-subscription') {
|
|
144
222
|
return [{ ...base, credits: { status: 'no-subscription' } }];
|
|
@@ -284,7 +362,7 @@ export function formatUsageCompact(report: UnifiedUsageReport, options: { width:
|
|
|
284
362
|
const reset = subscription.status === 'available' ? subscription.resetsAt
|
|
285
363
|
: credits.status === 'ok' && credits.periodEnd ? Date.parse(credits.periodEnd) : Number.NaN;
|
|
286
364
|
const periodLabel = period ? `${formatCompactPeriod(period)} ` : '';
|
|
287
|
-
const when = period === 'prepaid-usd' ? '' : !isValidEpochMs(reset)
|
|
365
|
+
const when = period === 'prepaid-usd' || period === 'extra-usage' ? '' : !isValidEpochMs(reset)
|
|
288
366
|
? ` · ${periodLabel}시각 모름`
|
|
289
367
|
: reset <= options.nowMs
|
|
290
368
|
? ` · ${periodLabel}재조회 필요`
|
|
@@ -351,6 +429,8 @@ export function formatUnifiedUsage(report: UnifiedUsageReport): string {
|
|
|
351
429
|
const count = report.accountCounts[provider];
|
|
352
430
|
lines.push(` ${provider} accounts=${count}${count === 1 ? ' (하나뿐)' : count === 0 ? ' (없음)' : ''}`);
|
|
353
431
|
}
|
|
432
|
+
// ⛔ 키가 없으면 줄을 안 낸다 — 종전 문면과 바이트 동일.
|
|
433
|
+
if (report.accountCounts.zai !== undefined) lines.push(` zai accounts=${report.accountCounts.zai} (GLM Coding Plan)`);
|
|
354
434
|
lines.push(' provider account accounts credits.usedPercent credits.window window-resets-in reset-credit-expiry subscription');
|
|
355
435
|
for (const row of report.rows) {
|
|
356
436
|
lines.push(formatRow(row));
|
|
@@ -364,7 +444,7 @@ function formatRow(row: AccountUsageRow): string {
|
|
|
364
444
|
const resetsIn = formatWindowResetsIn(row.credits);
|
|
365
445
|
const resetCredits = formatResetCreditExpiryCell(row.resetCredits);
|
|
366
446
|
const subscription = formatSubscriptionCell(row.subscription);
|
|
367
|
-
return ` ${row.provider.padEnd(10)} ${row.accountName.padEnd(11)} ${String(row.accountCount).padEnd(8)} ${credits.padEnd(20)} ${window.padEnd(26)} ${resetsIn.padEnd(22)} ${resetCredits.padEnd(36)} ${subscription}`;
|
|
447
|
+
return ` ${row.provider.padEnd(10)} ${row.accountName.padEnd(11)} ${String(row.accountCount).padEnd(8)} ${credits.padEnd(20)} ${window.padEnd(26)} ${resetsIn.padEnd(22)} ${resetCredits.padEnd(36)} ${subscription}${row.note ? ` — ${row.note}` : ''}`;
|
|
368
448
|
}
|
|
369
449
|
|
|
370
450
|
function formatResetCreditExpiryCell(expiry: ResetCreditExpiryAxis): string {
|
|
@@ -381,6 +461,7 @@ function formatResetCreditExpiryCell(expiry: ResetCreditExpiryAxis): string {
|
|
|
381
461
|
|
|
382
462
|
function formatCreditsCell(credits: CreditAxis): string {
|
|
383
463
|
if (credits.status !== 'ok') return credits.status;
|
|
464
|
+
if (credits.periodType === 'extra-usage') return `${credits.used ?? 0} cr`;
|
|
384
465
|
if (credits.usedPercent === null) return 'unknown';
|
|
385
466
|
// 선불 달러 — 퍼센트만으로는 «얼마 남았나»를 못 읽는다.
|
|
386
467
|
if (credits.periodType === 'prepaid-usd' && credits.balance !== null) return `${credits.usedPercent}% ($${credits.balance} left)`;
|
|
@@ -390,6 +471,7 @@ function formatCreditsCell(credits: CreditAxis): string {
|
|
|
390
471
|
function formatCreditsWindow(credits: CreditAxis): string {
|
|
391
472
|
if (credits.status !== 'ok') return '—';
|
|
392
473
|
if (credits.periodType === 'prepaid-usd') return 'prepaid (창 없음)';
|
|
474
|
+
if (credits.periodType === 'extra-usage') return 'extra-usage (잔액)';
|
|
393
475
|
const type = credits.periodType ?? '—';
|
|
394
476
|
if (credits.periodStart && credits.periodEnd) {
|
|
395
477
|
return `${type} ${credits.periodStart.slice(0, 10)}~${credits.periodEnd.slice(0, 10)}`;
|
|
@@ -407,7 +489,7 @@ function formatCreditsWindow(credits: CreditAxis): string {
|
|
|
407
489
|
*/
|
|
408
490
|
export function formatWindowResetsIn(credits: CreditAxis, nowMs: number = Date.now()): string {
|
|
409
491
|
if (credits.status !== 'ok') return '—';
|
|
410
|
-
if (credits.periodType === 'prepaid-usd') return 'n/a (리셋 없음)';
|
|
492
|
+
if (credits.periodType === 'prepaid-usd' || credits.periodType === 'extra-usage') return 'n/a (리셋 없음)';
|
|
411
493
|
const end = credits.periodEnd ? Date.parse(credits.periodEnd) : Number.NaN;
|
|
412
494
|
if (!Number.isFinite(end)) return 'unknown';
|
|
413
495
|
const remainMs = end - nowMs;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** BENCH-ISOLATED-ARMS ④ — `elanous bench ls <id>`: 벤치 원장(`<config dir>/bench/<id>.jsonl`)을 읽는 한 자리. */
|
|
2
|
+
import type { Command } from 'commander';
|
|
3
|
+
import { benchLedgerPath, parseBenchSpec, readBenchLedger, renderBenchLs } from '../bench/bench.js';
|
|
4
|
+
import { getElanousConfigDir } from '../elanous-config-dir.js';
|
|
5
|
+
|
|
6
|
+
export interface BenchCliDeps {
|
|
7
|
+
readonly configDir?: () => string;
|
|
8
|
+
readonly write?: (line: string) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function registerBenchCommands(program: Command, deps: BenchCliDeps = {}): Command {
|
|
12
|
+
const bench = program.command('bench').description('벤치 팔(같은 골 · 모델만 다른 발사) 원장 — harness ask/say --bench 가 쓴다');
|
|
13
|
+
bench.command('ls <id>')
|
|
14
|
+
.description('벤치 원장 줄(bench id · base sha · 이미지 pin · 자식 모델 · 라운드)을 읽는다')
|
|
15
|
+
.option('--json', 'JSON 배열로')
|
|
16
|
+
.action((id: string, opts: { json?: boolean }) => {
|
|
17
|
+
const write = deps.write ?? ((line: string) => console.log(line));
|
|
18
|
+
let benchId: string;
|
|
19
|
+
try { benchId = parseBenchSpec(id).id; }
|
|
20
|
+
catch (error) {
|
|
21
|
+
console.error(`❌ ${error instanceof Error ? error.message : String(error)}`);
|
|
22
|
+
process.exitCode = 2;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const configDir = (deps.configDir ?? getElanousConfigDir)();
|
|
26
|
+
const rows = readBenchLedger(benchId, configDir);
|
|
27
|
+
if (opts.json) write(JSON.stringify(rows));
|
|
28
|
+
else {
|
|
29
|
+
write(renderBenchLs(benchId, rows));
|
|
30
|
+
if (rows.length === 0) write(`원장 경로: ${benchLedgerPath(benchId, configDir)}`);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return bench;
|
|
34
|
+
}
|
package/src/cli/guardian-cli.ts
CHANGED
|
@@ -27,7 +27,32 @@ export function registerGuardianCommands(program: Command): void {
|
|
|
27
27
|
const cfg = getUserConfig();
|
|
28
28
|
// Live activation (and crontab retirement) is a separate operational change.
|
|
29
29
|
if (cfg.guardian?.mode === 'live') throw new Error('guardian live activation is outside GUARD-ONE-a');
|
|
30
|
-
|
|
30
|
+
// The shadow observation is for the tick's entry time, however long the GC's evidence collection takes.
|
|
31
|
+
const tickAt = new Date();
|
|
32
|
+
// Local orphan GC is separate from the guardian's shadow-only schedule execution.
|
|
33
|
+
const orphanGc = cfg.guardian?.orphanGc ?? 'shadow';
|
|
34
|
+
try {
|
|
35
|
+
// off never loads the collector, so no ps snapshot is taken.
|
|
36
|
+
if (orphanGc !== 'off') {
|
|
37
|
+
// Loading the collector is inside the guard too: a GC that cannot even load only observes.
|
|
38
|
+
const { runMachineHeartbeat } = await import('../ops/machine-heartbeat.js');
|
|
39
|
+
await runMachineHeartbeat(plan.jobs.filter((job): job is typeof job & { command: string; cron: string } =>
|
|
40
|
+
job.source === 'crontab' && job.run_via === 'crontab' && !!job.enabled
|
|
41
|
+
&& typeof job.command === 'string' && typeof job.cron === 'string')
|
|
42
|
+
.map(job => ({ command: job.command, cron: job.cron })), { timeZone: plan.timeZone, mode: orphanGc });
|
|
43
|
+
} else {
|
|
44
|
+
// Still one decision line per tick, so «switched off» is distinguishable from «tick never ran».
|
|
45
|
+
const { debug } = await import('../debug/log.js');
|
|
46
|
+
debug.log('ops.orphan-cli', 'decision', { mode: 'off', candidates: 0, collected: 0, alreadyEvidenced: 0, killed: 0 });
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
// The logger import is inside the guard as well: if it is the failing module, the shadow tick still runs.
|
|
50
|
+
try {
|
|
51
|
+
const { debug } = await import('../debug/log.js');
|
|
52
|
+
debug.log('ops.orphan-cli', 'observation-failed', { error: String(error) });
|
|
53
|
+
} catch { /* guardian shadow still runs */ }
|
|
54
|
+
}
|
|
55
|
+
const result = await guardianTick(tickAt, plan, { config: cfg });
|
|
31
56
|
await writeStdoutJson(JSON.stringify(result, null, 2) + '\n');
|
|
32
57
|
} finally { db.close(); }
|
|
33
58
|
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import { debug } from '../debug/log.js';
|
|
3
|
+
import {
|
|
4
|
+
appendImageLedgerEvent, baselinePinEvent, foldImageLedger, imageLedgerPath, readImageLedger, readRegistryTags,
|
|
5
|
+
type ImageLedgerRow, type RegistryRead,
|
|
6
|
+
} from '../images/image-ledger.js';
|
|
7
|
+
import { isPodImageCommit, podImageRegistry } from '../task-orchestrator/surfaces/pod-image-source.js';
|
|
8
|
+
|
|
9
|
+
const SSH_HOST = /^[A-Za-z0-9][A-Za-z0-9._@-]*$/u;
|
|
10
|
+
|
|
11
|
+
export interface ImageLsDeps {
|
|
12
|
+
readonly ledgerPath?: string;
|
|
13
|
+
readonly readRegistry?: (opts: { port: string; sshHost?: string }) => RegistryRead;
|
|
14
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
15
|
+
readonly out?: (line: string) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ImageLsResult {
|
|
19
|
+
readonly ledger: { readonly path: string; readonly exists: boolean; readonly events: number; readonly unreadableLines: number };
|
|
20
|
+
readonly registry: { readonly read: boolean; readonly where: string; readonly tags: number | null; readonly reason?: string };
|
|
21
|
+
readonly rows: readonly ImageLedgerRow[];
|
|
22
|
+
readonly counts: { readonly baseline: number; readonly transient: number | null; readonly ledgered: number };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** `image ls` — read only: ledger rows ⊕ registry tags without a ledger row (= transient). */
|
|
26
|
+
export function imageLs(opts: { json?: boolean; ssh?: string; registry?: boolean }, deps: ImageLsDeps = {}): ImageLsResult {
|
|
27
|
+
const out = deps.out ?? ((line: string) => console.log(line));
|
|
28
|
+
if (opts.ssh !== undefined && !SSH_HOST.test(opts.ssh)) throw new Error(`invalid --ssh host: ${opts.ssh}`);
|
|
29
|
+
const ledger = readImageLedger(deps.ledgerPath ?? imageLedgerPath());
|
|
30
|
+
const registryAddr = podImageRegistry(deps.env ?? process.env);
|
|
31
|
+
const port = registryAddr.slice(registryAddr.lastIndexOf(':') + 1);
|
|
32
|
+
const where = `${opts.ssh ?? 'this machine'} localhost:${port}`;
|
|
33
|
+
let registry: RegistryRead | null = null;
|
|
34
|
+
if (opts.registry !== false) {
|
|
35
|
+
registry = /^[1-9][0-9]{0,4}$/u.test(port) && Number(port) <= 65535
|
|
36
|
+
? (deps.readRegistry ?? readRegistryTags)({ port, ...(opts.ssh ? { sshHost: opts.ssh } : {}) })
|
|
37
|
+
: { ok: false, reason: `invalid registry port: ${registryAddr}` };
|
|
38
|
+
}
|
|
39
|
+
const tags = registry?.ok ? registry.tags : null;
|
|
40
|
+
const rows = foldImageLedger(ledger.events, tags);
|
|
41
|
+
const result: ImageLsResult = {
|
|
42
|
+
ledger: { path: ledger.path, exists: ledger.exists, events: ledger.events.length, unreadableLines: ledger.unreadableLines },
|
|
43
|
+
registry: registry === null
|
|
44
|
+
? { read: false, where, tags: null, reason: 'skipped (--no-registry)' }
|
|
45
|
+
: registry.ok ? { read: true, where, tags: registry.tags.length } : { read: false, where, tags: null, reason: registry.reason },
|
|
46
|
+
rows,
|
|
47
|
+
counts: {
|
|
48
|
+
baseline: rows.filter((r) => r.current).length,
|
|
49
|
+
transient: tags === null ? null : rows.filter((r) => r.unledgered).length,
|
|
50
|
+
ledgered: rows.filter((r) => !r.unledgered).length,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
try { debug.log('image.ledger', 'ls', { ledgerEvents: result.ledger.events, unreadableLines: result.ledger.unreadableLines, registryRead: result.registry.read, registryTags: result.registry.tags, transient: result.counts.transient }); } catch { /* log sink optional */ }
|
|
54
|
+
if (opts.json) { out(JSON.stringify(result, null, 2)); return result; }
|
|
55
|
+
out(`image ledger · ${ledger.path}${ledger.exists ? '' : ' (no ledger yet)'} · events ${ledger.events.length}${ledger.unreadableLines ? ` · ⚠️ unreadable lines ${ledger.unreadableLines}` : ''}`);
|
|
56
|
+
out(result.registry.read
|
|
57
|
+
? `registry · ${where} · tags ${result.registry.tags}`
|
|
58
|
+
: `registry · ${where} · ⚠️ not read — ${result.registry.reason} (transient count unknown, not 0)`);
|
|
59
|
+
for (const r of rows.filter((x) => !x.unledgered)) {
|
|
60
|
+
const reg = r.inRegistry === null ? '?' : r.inRegistry ? 'reg' : '⚠️ not-in-registry';
|
|
61
|
+
out(` ${r.tag} ${r.role}${r.role === 'baseline' ? (r.current ? '*' : ' (previous)') : ''} keep=${r.keep} ${r.origin ? `${r.origin.reason}@${r.origin.host}` : '-'} ${r.lastTs} ${reg}`);
|
|
62
|
+
}
|
|
63
|
+
const transient = rows.filter((x) => x.unledgered);
|
|
64
|
+
if (transient.length > 0) out(` transient (registry tags without a ledger row) · ${transient.length} · ${transient.slice(0, 8).map((r) => r.tag).join(' ')}${transient.length > 8 ? ' …' : ''}`);
|
|
65
|
+
out(`baseline ${result.counts.baseline} · ledgered ${result.counts.ledgered} · transient ${result.counts.transient ?? 'unknown'}`);
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Recording site: `config set harness.imageCommit <commit>` (the rail pins the baseline through it).
|
|
71
|
+
* Appends a `baseline ⊕ pin` event (none when that tag is already the current baseline). A failed ledger write never fails the config write; it warns
|
|
72
|
+
* and leaves an observation.
|
|
73
|
+
*/
|
|
74
|
+
export function recordImagePinFromConfigSet(path: string, value: unknown, deps: { ledgerPath?: string; warn?: (msg: string) => void; now?: () => Date; host?: string } = {}): 'recorded' | 'unchanged' | 'skipped' | 'failed' {
|
|
75
|
+
if (path !== 'harness.imageCommit' || !isPodImageCommit(value)) return 'skipped';
|
|
76
|
+
const ledgerPath = deps.ledgerPath ?? imageLedgerPath();
|
|
77
|
+
try {
|
|
78
|
+
// The rail re-pins the same commit on quiet cycles — one event per baseline change, not per call.
|
|
79
|
+
const current = foldImageLedger(readImageLedger(ledgerPath).events, null).find((r) => r.current);
|
|
80
|
+
if (current && current.tag === value.trim().toLowerCase().slice(0, 12)) return 'unchanged';
|
|
81
|
+
const event = baselinePinEvent(value, { ...(deps.now ? { now: deps.now } : {}), ...(deps.host ? { host: deps.host } : {}) });
|
|
82
|
+
appendImageLedgerEvent(event, ledgerPath);
|
|
83
|
+
try { debug.log('image.ledger', 'baseline-pinned', { tag: event.tag, commit: event.commit, reason: event.origin.reason, host: event.origin.host, ledgerPath }); } catch { /* */ }
|
|
84
|
+
return 'recorded';
|
|
85
|
+
} catch (e) {
|
|
86
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
87
|
+
try { debug.log('image.ledger', 'baseline-pin-record-failed', { error: message, ledgerPath }); } catch { /* */ }
|
|
88
|
+
(deps.warn ?? ((m: string) => console.error(m)))(`⚠️ image ledger: baseline pin was set in config but not recorded in ${ledgerPath} — ${message}`);
|
|
89
|
+
return 'failed';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function registerImageCommands(program: Command): void {
|
|
94
|
+
const image = program.command('image').description('하니스 이미지 원장 — 신분 표지(역할·보존·출신) · 원장 없는 레지스트리 태그 = transient');
|
|
95
|
+
image.command('ls')
|
|
96
|
+
.description('원장 행 ⊕ 레지스트리 태그(원장에 없으면 transient) — 읽기 전용')
|
|
97
|
+
.option('--json', 'JSON 출력')
|
|
98
|
+
.option('--ssh <host>', '레지스트리가 있는 노드(그 노드의 localhost 포트로 조회)')
|
|
99
|
+
.option('--no-registry', '레지스트리를 읽지 않는다(원장만)')
|
|
100
|
+
.action((opts: { json?: boolean; ssh?: string; registry?: boolean }) => { imageLs(opts); });
|
|
101
|
+
}
|
|
@@ -121,7 +121,9 @@ export const KNOWN_LOG_EVENT_NAMES: ReadonlySet<string> = new Set([
|
|
|
121
121
|
"background-drain",
|
|
122
122
|
"background-finish",
|
|
123
123
|
"barge.in",
|
|
124
|
+
"base-ensured",
|
|
124
125
|
"base-preexisting",
|
|
126
|
+
"base-refused",
|
|
125
127
|
"base-selection",
|
|
126
128
|
"base.default-branch",
|
|
127
129
|
"base.remote-unreachable",
|
|
@@ -736,6 +738,7 @@ export const KNOWN_LOG_EVENT_NAMES: ReadonlySet<string> = new Set([
|
|
|
736
738
|
"launch-overrides",
|
|
737
739
|
"layered-load",
|
|
738
740
|
"ledger",
|
|
741
|
+
"ledger-unwritten",
|
|
739
742
|
"ledger-write-failed",
|
|
740
743
|
"legacy-codex-invoked",
|
|
741
744
|
"legacy-finalize",
|
|
@@ -773,6 +776,7 @@ export const KNOWN_LOG_EVENT_NAMES: ReadonlySet<string> = new Set([
|
|
|
773
776
|
"luna-failed",
|
|
774
777
|
"luna-no-allowlist",
|
|
775
778
|
"made-public",
|
|
779
|
+
"main-sync-skipped",
|
|
776
780
|
"main-tree-reject",
|
|
777
781
|
"malformed-tail",
|
|
778
782
|
"manifest-frame-fail",
|
package/src/cli/pr-cli.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
-
import { basename, join, relative, resolve } from 'node:path';
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
3
3
|
import { createInterface } from 'node:readline';
|
|
4
4
|
import type { Command } from 'commander';
|
|
5
5
|
import {
|
|
@@ -57,7 +57,8 @@ import { branchLineageSlug, findSiblingPrs } from './pr-lineage.js';
|
|
|
57
57
|
import { parseReleaseNoteSection } from '../release-loop/release-note.js';
|
|
58
58
|
import { landingFreezeMessage } from '../release-loop/landing-freeze.js';
|
|
59
59
|
import { admitLandingMerge } from '../self-implement/frozen-merges.js';
|
|
60
|
-
import { verifyOverlapEvidence } from './pr-land-overlap-evidence.js';
|
|
60
|
+
import { verifyAncestorAutoPass, verifyOverlapEvidence } from './pr-land-overlap-evidence.js';
|
|
61
|
+
import { taskAgentStatePath } from '../task-agent/task-hand.js';
|
|
61
62
|
import { enqueueL8ShadowQueue, runL8ShadowQueue, shadowPrNumber, statusL8ShadowQueue, type L8ShadowQueueDeps } from '../self-implement/l8-merge-queue.js';
|
|
62
63
|
|
|
63
64
|
export { branchLineageSlug, findSiblingPrs };
|
|
@@ -107,6 +108,8 @@ export interface PrLandDeps {
|
|
|
107
108
|
resolveBase?: (run: CmdRunner, cwd: string, explicit?: string) => string | undefined;
|
|
108
109
|
currentBranch?: (cwd: string) => string | undefined;
|
|
109
110
|
readFile?: (path: string) => string;
|
|
111
|
+
/** 증거 파일 없는 already-ancestor 자동 판정이 리뷰 결과를 찾는 자리(기본 `<TA 상태 디렉터리>/review-results`). */
|
|
112
|
+
reviewResultsDir?: string;
|
|
110
113
|
listUnfinishedRuns?: () => FederatedUnfinishedRunLedgerQuery | readonly FederatedUnfinishedRunLedgerEntry[];
|
|
111
114
|
queryRunningRuns?: () => RunningRunsResult;
|
|
112
115
|
listOpenPrs?: () => readonly { number: number; headRefName: string }[] | null;
|
|
@@ -1038,6 +1041,8 @@ const SOURCE_SPLIT_PR_LIST_LIMIT = 200;
|
|
|
1038
1041
|
*/
|
|
1039
1042
|
const MERGED_PR_LOOKUP_RUNAWAY_CAP = 1000;
|
|
1040
1043
|
const SOURCE_LOOKUP_FAILED_LINE = '⚠ source: 브랜치 조회에 실패해 갈래를 내지 못했습니다.';
|
|
1044
|
+
/** BENCH-ISOLATED-ARMS — 벤치 팔 PR 라벨(착지 금지). */
|
|
1045
|
+
export const BENCH_LABEL = 'elanous:bench';
|
|
1041
1046
|
|
|
1042
1047
|
type MergedPrHeadRefsLookup = {
|
|
1043
1048
|
map: Map<number, string>;
|
|
@@ -1616,9 +1621,23 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
|
|
|
1616
1621
|
if (verdict.ok) {
|
|
1617
1622
|
out.log(verdict.mode === 'already-ancestor'
|
|
1618
1623
|
? `✓ overlap-evidence: already-ancestor — 겹친 최근 착지 ${overlappingLandings.length}개가 전부 head ${opts.expectedHead!.slice(0, 12)} 의 조상 · review pass must-fix 0 (${verdict.reviewResult})`
|
|
1619
|
-
:
|
|
1624
|
+
: verdict.mode === 'base-advanced-disjoint'
|
|
1625
|
+
? `✓ overlap-evidence: base-advanced-disjoint — gate base ${verdict.gateBase.slice(0, 12)} ⊂ head ${opts.expectedHead!.slice(0, 12)} · ${ref.remote}/${ref.branch} ${verdict.baseTip.slice(0, 12)} 까지 ${verdict.windowCommits}개 커밋이 PR 파일을 안 건드린다 · review pass|warn must-fix 0 (${verdict.reviewResult})`
|
|
1626
|
+
: `✓ overlap-evidence: ${ref.remote}/${ref.branch} ${verdict.baseTip.slice(0, 12)} ⊂ head ${opts.expectedHead!.slice(0, 12)} · gate passed on that base · review pass must-fix 0 (${verdict.reviewResult})`);
|
|
1620
1627
|
}
|
|
1621
1628
|
else out.error(`✗ overlap-evidence: ${verdict.reason}`);
|
|
1629
|
+
} else if (pinned && overlapDecision?.outcome === 'unavailable') {
|
|
1630
|
+
// 증거 파일이 없을 때 — 겹친 착지가 전부 머리의 조상이고 표준 자리의 리뷰가 pass|warn ⊕ must-fix 0 이면 통과(아니면 종전 거부).
|
|
1631
|
+
const verdict = verifyAncestorAutoPass({
|
|
1632
|
+
pr: Number(opts.pr), expectedHead: opts.expectedHead!, cwd, run, readFile, overlappingLandings,
|
|
1633
|
+
reviewResultsDir: deps.reviewResultsDir ?? join(dirname(taskAgentStatePath()), 'review-results'),
|
|
1634
|
+
listDir: (dir) => readdirSync(dir),
|
|
1635
|
+
});
|
|
1636
|
+
overlapEvidenceAccepted = verdict.ok;
|
|
1637
|
+
if (verdict.ok) {
|
|
1638
|
+
out.log(`✓ overlap-evidence: already-ancestor-auto — 겹친 최근 착지 ${overlappingLandings.length}개가 전부 head ${opts.expectedHead!.slice(0, 12)} 의 조상 · review pass|warn must-fix 0 (${verdict.reviewResult})`);
|
|
1639
|
+
}
|
|
1640
|
+
else out.error(`✗ ancestor-auto: ${verdict.reason}`);
|
|
1622
1641
|
}
|
|
1623
1642
|
if (pinned && overlapDecision && overlapDecision.outcome !== 'user-merge' && !overlapEvidenceAccepted) {
|
|
1624
1643
|
out.error('✗ expected-head: overlap merge approval unavailable for the reviewed head');
|
|
@@ -1695,6 +1714,37 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
|
|
|
1695
1714
|
}
|
|
1696
1715
|
}
|
|
1697
1716
|
|
|
1717
|
+
// LAND-BASE-GUARD-ORDER — 재사용할 PR 의 base·라벨은 upsert «전에» 본다(10-11 #26150: upsert 가 base 를 main 으로
|
|
1718
|
+
// 바꾼 뒤에 읽어 #26117 base-guard 가 무력 · 거부돼도 PR 이 바뀌어 남았다). 거부는 PR 을 건드리지 않는다.
|
|
1719
|
+
// LAND-BASE-MAIN-GUARD — 재사용한 PR 의 base 가 기본 가지가 아니면 병합 «전에» 거부한다(10-11 #26022 가 bench/base-night-* 로 병합됨).
|
|
1720
|
+
// 명시 --base 를 준 경우만 통과한다. 못 읽은 base 는 종전 흐름대로 둔다(병합 뒤 되읽기).
|
|
1721
|
+
// BENCH-ISOLATED-ARMS — `elanous:bench` 라벨 PR 은 벤치 팔 산출이라 착지하지 않는다(승자는 주인이 main 위로 따로 수확).
|
|
1722
|
+
// 명시 --base 로도 통과하지 않는다. 못 읽은 라벨은 base-guard 와 같이 종전 흐름대로 둔다.
|
|
1723
|
+
const refuseReusedPr = (url: string): number | undefined => {
|
|
1724
|
+
const reusedNumber = /\/(\d+)$/.exec(url)?.[1] ?? url;
|
|
1725
|
+
if (!opts.base?.trim()) {
|
|
1726
|
+
const reusedBase = run('gh', ['pr', 'view', url, '--json', 'baseRefName', '--jq', '.baseRefName'], { cwd });
|
|
1727
|
+
const reusedBaseName = reusedBase.ok ? reusedBase.out.trim() : '';
|
|
1728
|
+
if (reusedBaseName && reusedBaseName !== baseBranchName) {
|
|
1729
|
+
record('base-guard', false, { url, prBase: reusedBaseName, expected: baseBranchName });
|
|
1730
|
+
out.error(`✗ base-guard: base=${reusedBaseName} ≠ ${baseBranchName} · gh pr edit ${reusedNumber} --base ${baseBranchName} 뒤 다시`);
|
|
1731
|
+
return 1;
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
const reusedLabels = run('gh', ['pr', 'view', url, '--json', 'labels', '--jq', '.labels[].name'], { cwd });
|
|
1735
|
+
if (reusedLabels.ok && reusedLabels.out.split('\n').some((name) => name.trim() === BENCH_LABEL)) {
|
|
1736
|
+
record('bench-guard', false, { url, label: BENCH_LABEL });
|
|
1737
|
+
out.error(`✗ bench-guard: #${reusedNumber} 은 ${BENCH_LABEL} 벤치 팔 PR 이라 착지하지 않는다 · 승자 diff 를 main 위 새 가지로 옮겨 착지`);
|
|
1738
|
+
return 1;
|
|
1739
|
+
}
|
|
1740
|
+
return undefined;
|
|
1741
|
+
};
|
|
1742
|
+
const guardedBeforeUpsert = !hold && !!existing.url;
|
|
1743
|
+
if (guardedBeforeUpsert) {
|
|
1744
|
+
const refused = refuseReusedPr(existing.url!);
|
|
1745
|
+
if (refused !== undefined) return refused;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1698
1748
|
// A pinned, already-reviewed commit must not go through upsertPr (git add/commit/force-push/PR edit).
|
|
1699
1749
|
const upsert = pinned
|
|
1700
1750
|
? existing.status === 'ok OUTPUT' && existing.url === pinnedUrl
|
|
@@ -1703,7 +1753,8 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
|
|
|
1703
1753
|
: manager.upsertPr({
|
|
1704
1754
|
branch,
|
|
1705
1755
|
worktreePath: cwd,
|
|
1706
|
-
base
|
|
1756
|
+
// 기존 PR 재사용이면 명시 --base 일 때만 넘긴다 — 추론한 base 는 PR 을 main 으로 재타기팅한다(#26150).
|
|
1757
|
+
...(opts.base?.trim() || !existing.url ? { base } : {}),
|
|
1707
1758
|
draft: hold,
|
|
1708
1759
|
title: title ?? `Land ${branch}`,
|
|
1709
1760
|
// ⛔⭐ 본문 기본값도 «내용이 있는» 쪽으로 — 종전 34자 플레이스홀더는 ***아무에게도 쓸모가 없었고***
|
|
@@ -1741,6 +1792,12 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
|
|
|
1741
1792
|
return 0;
|
|
1742
1793
|
}
|
|
1743
1794
|
|
|
1795
|
+
// 착지 전 PR 을 못 찾았는데(조회 실패) upsert 가 기존 PR 을 재사용했다면 여기서 한 번 더 본다.
|
|
1796
|
+
if (upsert.reused && !guardedBeforeUpsert) {
|
|
1797
|
+
const refused = refuseReusedPr(upsert.url);
|
|
1798
|
+
if (refused !== undefined) return refused;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1744
1801
|
const prNumber = Number(/\/(\d+)$/.exec(upsert.url)?.[1]);
|
|
1745
1802
|
const landedHead = run('git', ['rev-parse', 'HEAD'], { cwd });
|
|
1746
1803
|
const headCommit = landedHead.ok && /^[a-f0-9]{40}$/i.test(landedHead.out.trim()) ? landedHead.out.trim() : undefined;
|