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/models/prompts.ts
CHANGED
|
@@ -63,7 +63,8 @@ export function getModelFamily(modelId: string | undefined): ModelFamily {
|
|
|
63
63
|
// OpenRouter gateway ids only (`openrouter/<vendor>/<model>`). Bare vendor ids such as
|
|
64
64
|
// `qwen/qwen3-coder-30b` stay 'other' on purpose: LM Studio publishes native ids in that
|
|
65
65
|
// same shape (see getModelTier's local pattern), and this family must not reach local runs.
|
|
66
|
-
|
|
66
|
+
// OR-DEEPSEEK-TUNE (2026-10-11) — `openrouter/deepseek/*` 도 같은 군(종전 'other' → 도구 규율 null).
|
|
67
|
+
if (m.startsWith('openrouter/') && (m.includes('glm') || m.includes('kimi') || m.includes('qwen') || m.startsWith('openrouter/deepseek/'))) {
|
|
67
68
|
return 'open-weight';
|
|
68
69
|
}
|
|
69
70
|
return 'other';
|
|
@@ -101,13 +101,27 @@ export const STORE_SAFETY_MAX = 100_000;
|
|
|
101
101
|
export const LOG_STORE_READONLY_BUSY_TIMEOUT_MS = 2_000;
|
|
102
102
|
const LOG_STORE_WRITE_BUSY_TIMEOUT_MS = 5_000;
|
|
103
103
|
const LOG_STORE_OPEN_RETRY_DELAYS_MS = [100, 200, 400, 800, 1600] as const;
|
|
104
|
+
const LOG_STORE_BUSY_RETRY_MAX_ATTEMPTS = 3;
|
|
105
|
+
const LOG_STORE_BUSY_RETRY_BUFFER_MAX_ROWS = 2_000;
|
|
106
|
+
type LogStoreInsertRow = { rec: LogRecord; surface: string };
|
|
107
|
+
type LogStoreRetryBatch = { rows: LogStoreInsertRow[]; attempts: number; preserveRows: boolean };
|
|
104
108
|
const sqliteOpenWait = new Int32Array(new SharedArrayBuffer(4));
|
|
105
109
|
let pendingBusyOpenLosses: Array<{ path: string; loss: { store: 'logs'; op: 'open'; waitedMs: number } }> = [];
|
|
106
|
-
|
|
107
110
|
function sqliteBusy(error: unknown): boolean {
|
|
108
111
|
return /SQLITE_BUSY|SQLITE_LOCKED|database is locked|database is busy/i.test(String(error));
|
|
109
112
|
}
|
|
110
113
|
|
|
114
|
+
function emitInsertBusyLost(data: Record<string, unknown>): void {
|
|
115
|
+
try { process.stderr.write(`[store.sqlite] busy-lost ${JSON.stringify(data)}\n`); } catch { /* diagnostics must not block callers */ }
|
|
116
|
+
try { debug.log('store.sqlite', 'busy-lost', data); } catch { /* diagnostics must not block callers */ }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function topCategories(rows: readonly LogStoreInsertRow[]): string[] {
|
|
120
|
+
const counts = new Map<string, number>();
|
|
121
|
+
for (const { rec } of rows) counts.set(rec.category, (counts.get(rec.category) ?? 0) + 1);
|
|
122
|
+
return [...counts].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 3).map(([category]) => category);
|
|
123
|
+
}
|
|
124
|
+
|
|
111
125
|
export class LogCursorNotFoundError extends Error {
|
|
112
126
|
constructor(public readonly beforeId: number) {
|
|
113
127
|
super(`page cursor row ${beforeId} not found in this store`);
|
|
@@ -196,6 +210,7 @@ export class LogStore {
|
|
|
196
210
|
readonly instance: string;
|
|
197
211
|
/** read-only open (LF7-b 연합 조회) — 스키마 생성/마이그레이션/insert 불가. */
|
|
198
212
|
readonly readonly: boolean;
|
|
213
|
+
private busyRetryBuffer: LogStoreRetryBatch[] = [];
|
|
199
214
|
|
|
200
215
|
/** 타 인스턴스 스토어의 연합 조회용 read-only open — "연합은 read-only 로만"
|
|
201
216
|
* 불변식의 구조적 집행(마이그레이션 포함 어떤 write 도 발생 불가). */
|
|
@@ -296,10 +311,12 @@ export class LogStore {
|
|
|
296
311
|
})();
|
|
297
312
|
}
|
|
298
313
|
|
|
299
|
-
/** 배치 insert(단일 트랜잭션).
|
|
300
|
-
* 책임(ops-log 의 recordOpsEvent/Safe 분업 동형). */
|
|
314
|
+
/** 배치 insert(단일 트랜잭션). SQLITE_BUSY 는 bounded in-memory buffer 후 throw — fail-soft 는 호출측(StoreSink) 책임. */
|
|
301
315
|
insertBatch(rows: Array<{ rec: LogRecord; surface: string }>, opts: { preserveRows?: boolean } = {}): void {
|
|
302
316
|
if (rows.length === 0) return;
|
|
317
|
+
const retrying = this.busyRetryBuffer;
|
|
318
|
+
const current: LogStoreRetryBatch = { rows, attempts: 0, preserveRows: opts.preserveRows === true };
|
|
319
|
+
const batches = [...retrying, current];
|
|
303
320
|
const stmt = this.db.prepare(
|
|
304
321
|
`INSERT INTO logs (ts, ts_ms, level, instance, host_id, surface, category, event, session_id, trace_id, data)
|
|
305
322
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -307,8 +324,8 @@ export class LogStore {
|
|
|
307
324
|
const decisionRow = this.db.prepare(`SELECT id FROM logs WHERE instance = ? AND category = 'loop.steward' AND event = 'decision'
|
|
308
325
|
AND json_valid(data) AND json_extract(data, '$.runId') = ? ORDER BY id DESC LIMIT 1`);
|
|
309
326
|
const updateDecision = this.db.prepare(`UPDATE logs SET ts = ?, ts_ms = ?, level = ?, surface = ?, data = ? WHERE id = ?`);
|
|
310
|
-
const insertAll = this.db.transaction((batch: Array<{
|
|
311
|
-
for (const { rec, surface } of batch) {
|
|
327
|
+
const insertAll = this.db.transaction((batch: Array<{ row: LogStoreInsertRow; preserveRows: boolean }>) => {
|
|
328
|
+
for (const { row: { rec, surface }, preserveRows } of batch) {
|
|
312
329
|
const tsMs = Date.parse(rec.ts);
|
|
313
330
|
const timestamp = Number.isFinite(tsMs) ? tsMs : Date.now();
|
|
314
331
|
const level = deriveLogLevel(rec);
|
|
@@ -316,7 +333,7 @@ export class LogStore {
|
|
|
316
333
|
const runId = rec.category === 'loop.steward' && rec.event === 'decision' &&
|
|
317
334
|
rec.data && typeof rec.data === 'object' && !Array.isArray(rec.data)
|
|
318
335
|
? (rec.data as Record<string, unknown>).runId : undefined;
|
|
319
|
-
const existing = !
|
|
336
|
+
const existing = !preserveRows && typeof runId === 'string' && runId
|
|
320
337
|
? decisionRow.get(this.instance, runId) as { id: number } | undefined : undefined;
|
|
321
338
|
if (existing) {
|
|
322
339
|
updateDecision.run(rec.ts, timestamp, level, surface, data, existing.id);
|
|
@@ -340,14 +357,43 @@ export class LogStore {
|
|
|
340
357
|
);
|
|
341
358
|
}
|
|
342
359
|
});
|
|
360
|
+
const allRows = batches.flatMap((batch) => batch.rows.map((row) => ({ row, preserveRows: batch.preserveRows })));
|
|
343
361
|
const startedAt = Date.now();
|
|
344
362
|
try {
|
|
345
|
-
insertAll(
|
|
363
|
+
insertAll(allRows);
|
|
364
|
+
this.busyRetryBuffer = [];
|
|
365
|
+
for (const batch of retrying) {
|
|
366
|
+
try { debug.log('store.sqlite', 'busy-recovered', { rows: batch.rows.length, attempts: batch.attempts + 1 }); }
|
|
367
|
+
catch { /* diagnostics must not affect writes */ }
|
|
368
|
+
}
|
|
346
369
|
} catch (error) {
|
|
347
|
-
if (sqliteBusy(error))
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
370
|
+
if (!sqliteBusy(error)) throw error;
|
|
371
|
+
emitInsertBusyLost({
|
|
372
|
+
store: 'logs', op: 'insertBatch', waitedMs: Date.now() - startedAt,
|
|
373
|
+
rows: allRows.length, categories: topCategories(allRows.map(({ row }) => row)),
|
|
374
|
+
});
|
|
375
|
+
const retained: LogStoreRetryBatch[] = [];
|
|
376
|
+
for (const batch of batches) {
|
|
377
|
+
const attempts = batch.attempts + 1;
|
|
378
|
+
if (attempts >= LOG_STORE_BUSY_RETRY_MAX_ATTEMPTS) {
|
|
379
|
+
emitInsertBusyLost({ store: 'logs', op: 'insertBatch', reason: 'retry-exhausted', droppedRows: batch.rows.length });
|
|
380
|
+
} else {
|
|
381
|
+
retained.push({ ...batch, attempts });
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
let bufferedRows = retained.reduce((count, batch) => count + batch.rows.length, 0);
|
|
385
|
+
let droppedRows = 0;
|
|
386
|
+
while (bufferedRows > LOG_STORE_BUSY_RETRY_BUFFER_MAX_ROWS && retained.length > 0) {
|
|
387
|
+
const oldest = retained[0]!;
|
|
388
|
+
const drop = Math.min(bufferedRows - LOG_STORE_BUSY_RETRY_BUFFER_MAX_ROWS, oldest.rows.length);
|
|
389
|
+
oldest.rows.splice(0, drop);
|
|
390
|
+
bufferedRows -= drop;
|
|
391
|
+
droppedRows += drop;
|
|
392
|
+
if (oldest.rows.length === 0) retained.shift();
|
|
393
|
+
}
|
|
394
|
+
this.busyRetryBuffer = retained;
|
|
395
|
+
if (droppedRows > 0) {
|
|
396
|
+
emitInsertBusyLost({ store: 'logs', op: 'insertBatch', reason: 'retry-buffer-full', droppedRows });
|
|
351
397
|
}
|
|
352
398
|
throw error;
|
|
353
399
|
}
|
|
@@ -835,6 +881,7 @@ export class StoreSink implements LogSink {
|
|
|
835
881
|
const batch = this.buf;
|
|
836
882
|
this.buf = [];
|
|
837
883
|
try {
|
|
884
|
+
// insertBatch retries SQLITE_BUSY batches on the next write; this sink remains fail-soft on final failure.
|
|
838
885
|
this.store.insertBatch(batch);
|
|
839
886
|
} catch {
|
|
840
887
|
/* fail-soft — 파일 트레일이 진실원. 배치는 버린다(재시도 루프 금지 —
|
|
@@ -35,6 +35,18 @@ import {
|
|
|
35
35
|
type MissionStatus,
|
|
36
36
|
} from '../../task-orchestrator/mission.js';
|
|
37
37
|
import type { Task } from '../../task-orchestrator/types.js';
|
|
38
|
+
import {
|
|
39
|
+
openSchedulesDb,
|
|
40
|
+
listSchedules,
|
|
41
|
+
scheduleHealth,
|
|
42
|
+
} from '../../domains/schedule-registry.js';
|
|
43
|
+
import {
|
|
44
|
+
cronRowsToDerived,
|
|
45
|
+
rollupDerived,
|
|
46
|
+
taskStatusToDerived,
|
|
47
|
+
type DerivedJob,
|
|
48
|
+
type MissionRollup,
|
|
49
|
+
} from '../../autopilot/mission-trace.js';
|
|
38
50
|
|
|
39
51
|
import { checkAuth, type MetaApiOpts } from './meta-api.js';
|
|
40
52
|
import { jsonResponse } from './http-server.js';
|
|
@@ -68,12 +80,63 @@ export interface MissionCardWire {
|
|
|
68
80
|
* Mission. 자율 Mission 이면 실행모델·apm 상태·계보 키를 담아 통합 Missions
|
|
69
81
|
* 표면이 autopilot 여부와 lineage 를 한눈에 렌더한다. */
|
|
70
82
|
autopilot: MissionAutopilot | null;
|
|
83
|
+
/** 파생 작업 롤업(태스크 + 크론). ⛔ **결손 수리(MISSIONS-STALE-MARK · 2026-10-11)**:
|
|
84
|
+
* 이 칸이 없어 PWA 미션 카드의 `RollupChips` 는 항상 `undefined` 를 받아
|
|
85
|
+
* «아무것도» 안 그렸다 — 그래서 «파생 0»과 «파생 미측정»을 화면이 «구분 못 했다».
|
|
86
|
+
* 카드는 목록 엔드포인트만 보므로(미션별 `traceAutopilotMission` 을 안 부름)
|
|
87
|
+
* 여기서 «이미 로드한 태스크» ⊕ «요청당 한 번 읽은 schedule_registry」로
|
|
88
|
+
* 계산한다. 자율행동(surface_events)·자식 미션은 상세 트레이스
|
|
89
|
+
* (`ops_status action=mission`)의 몫으로 남는다(목록에서 N× 무거운 조인 금지).
|
|
90
|
+
* 매핑은 `rollupDerived`·`taskStatusToDerived`·`cronRowsToDerived` — «단일 소유»라
|
|
91
|
+
* 트레이스와 갈림이 없다. `total: 0` = «파생이 정말 0». */
|
|
92
|
+
derived: MissionRollup;
|
|
71
93
|
createdAt: number;
|
|
72
94
|
updatedAt: number;
|
|
73
95
|
closedAt: number | null;
|
|
74
96
|
}
|
|
75
97
|
|
|
76
|
-
|
|
98
|
+
/** 태스크 행 → 파생물(순수). 트레이스의 `fetchTaskDerived` 와 같은 상태 매핑
|
|
99
|
+
* (`taskStatusToDerived`)을 쓴다 — «두 가지 상태 어휘»를 만들지 않는다. */
|
|
100
|
+
export function taskRowsToDerived(tasks: readonly Task[]): DerivedJob[] {
|
|
101
|
+
return tasks.map((t) => ({
|
|
102
|
+
kind: 'task',
|
|
103
|
+
id: t.id,
|
|
104
|
+
name: t.title,
|
|
105
|
+
status: taskStatusToDerived(t.status),
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** 요청당 «한 번» schedule_registry 를 읽어 apmId 별 크론 파생물을 나눠 담는다.
|
|
110
|
+
* ⛔ 미션별로 DB 를 열면 목록 N건 = N× 열기 — 그래서 한 번 읽고 in-process 분배.
|
|
111
|
+
* fail-soft: 레지스트리가 없으면 빈 Map(derived 는 태스크만 집계). */
|
|
112
|
+
export function loadCronDerivedByMission(): Map<string, DerivedJob[]> {
|
|
113
|
+
const out = new Map<string, DerivedJob[]>();
|
|
114
|
+
try {
|
|
115
|
+
const db = openSchedulesDb();
|
|
116
|
+
try {
|
|
117
|
+
const rows = listSchedules(db);
|
|
118
|
+
const staleIds = new Set(scheduleHealth(rows).stale.map((s) => s.id));
|
|
119
|
+
const rowsByMission = new Map<string, typeof rows>();
|
|
120
|
+
for (const row of rows) {
|
|
121
|
+
const apmId = row.autopilot_id;
|
|
122
|
+
if (!apmId) continue;
|
|
123
|
+
const bucket = rowsByMission.get(apmId);
|
|
124
|
+
if (bucket) bucket.push(row);
|
|
125
|
+
else rowsByMission.set(apmId, [row]);
|
|
126
|
+
}
|
|
127
|
+
for (const [apmId, missionRows] of rowsByMission) {
|
|
128
|
+
out.set(apmId, cronRowsToDerived(missionRows, staleIds));
|
|
129
|
+
}
|
|
130
|
+
} finally { db.close(); }
|
|
131
|
+
} catch { /* fail-soft */ }
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function summarise(
|
|
136
|
+
mission: Mission,
|
|
137
|
+
tasks: readonly Task[],
|
|
138
|
+
crons: readonly DerivedJob[] = [],
|
|
139
|
+
): MissionCardWire {
|
|
77
140
|
const taskStatusCounts: Record<string, number> = {};
|
|
78
141
|
const surfaceKindCounts: Record<string, number> = {};
|
|
79
142
|
for (const t of tasks) {
|
|
@@ -92,6 +155,7 @@ export function summarise(mission: Mission, tasks: readonly Task[]): MissionCard
|
|
|
92
155
|
taskStatusCounts,
|
|
93
156
|
surfaceKindCounts,
|
|
94
157
|
autopilot: mission.autopilot ?? null,
|
|
158
|
+
derived: rollupDerived([...taskRowsToDerived(tasks), ...crons]),
|
|
95
159
|
createdAt: mission.createdAt,
|
|
96
160
|
updatedAt: mission.updatedAt,
|
|
97
161
|
closedAt: mission.closedAt ?? null,
|
|
@@ -115,8 +179,15 @@ export function handleMissionsList(
|
|
|
115
179
|
const store = new TaskStore();
|
|
116
180
|
try {
|
|
117
181
|
const missions = store.listMissions(filter);
|
|
182
|
+
// 크론 레지스트리는 «요청당 한 번»만 읽고 apmId 로 분배(미션별 DB 열기 금지).
|
|
183
|
+
const cronsByMission = loadCronDerivedByMission();
|
|
118
184
|
const cards = missions.map((m) =>
|
|
119
|
-
summarise(
|
|
185
|
+
summarise(
|
|
186
|
+
m,
|
|
187
|
+
store.listTasksForMission(m.id),
|
|
188
|
+
// schedule_registry.autopilot_id 는 계보 키(apmId)다 — TOX 미션 id 와 다를 수 있다.
|
|
189
|
+
cronsByMission.get(m.autopilot?.apmId ?? m.id) ?? cronsByMission.get(m.id) ?? [],
|
|
190
|
+
),
|
|
120
191
|
);
|
|
121
192
|
return jsonResponse({
|
|
122
193
|
total: cards.length,
|
package/src/nexus/api/ops-api.ts
CHANGED
|
@@ -9,6 +9,8 @@ import { jsonResponse } from './json-response.js';
|
|
|
9
9
|
import { operatorSignal } from './operator.js';
|
|
10
10
|
import { createSeatsCache, todayKst } from './ops-seats.js';
|
|
11
11
|
import { liveSeatsSources } from './ops-seats-sources.js';
|
|
12
|
+
import { createOverviewCache } from '../../ops/overview-cache.js';
|
|
13
|
+
import { parseSince } from '../../ops/overview-collect.js';
|
|
12
14
|
import type { MetaApiOpts } from './meta-api.js';
|
|
13
15
|
|
|
14
16
|
const VERSION = /^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/;
|
|
@@ -17,6 +19,11 @@ const DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
|
17
19
|
let seatsCache: ReturnType<typeof createSeatsCache> | null = null;
|
|
18
20
|
/** Tests swap the sources; production builds them lazily on first use. */
|
|
19
21
|
export function setSeatsCacheForTest(cache: ReturnType<typeof createSeatsCache> | null): void { seatsCache = cache; }
|
|
22
|
+
let overviewCache: ReturnType<typeof createOverviewCache> | null = null;
|
|
23
|
+
export function setOverviewCacheForTest(cache: ReturnType<typeof createOverviewCache> | null): void {
|
|
24
|
+
overviewCache?.stop();
|
|
25
|
+
overviewCache = cache;
|
|
26
|
+
}
|
|
20
27
|
const LOG_PATH = /^\/v1\/ops\/release\/runs\/([^/]+)\/nodes\/([^/]+)\/log$/;
|
|
21
28
|
|
|
22
29
|
interface RunNode { nodeId: string; ok?: unknown; output?: unknown; startedAt?: unknown; endedAt?: unknown }
|
|
@@ -190,6 +197,17 @@ export function handleOpsApi(req: Request, metaApi: MetaApiOpts | undefined): Re
|
|
|
190
197
|
response.headers.set('cache-control', 'no-store');
|
|
191
198
|
return response;
|
|
192
199
|
};
|
|
200
|
+
if (pathname === '/v1/ops/overview') {
|
|
201
|
+
let since: ReturnType<typeof parseSince>;
|
|
202
|
+
try { since = parseSince(url.searchParams.get('since') ?? undefined); }
|
|
203
|
+
catch { return jsonResponse({ error: 'invalid-since' }, 400); }
|
|
204
|
+
const version = url.searchParams.get('version');
|
|
205
|
+
if (version !== null && !VERSION.test(version)) return jsonResponse({ error: 'invalid-version' }, 400);
|
|
206
|
+
overviewCache ??= createOverviewCache();
|
|
207
|
+
return overviewCache.get({ sinceMs: since.ms, sinceLabel: since.label,
|
|
208
|
+
...(version ? { version } : {}), ...(url.searchParams.has('owner') ? { owner: url.searchParams.get('owner')! } : {}) })
|
|
209
|
+
.then((overview) => served(jsonResponse(overview)), () => jsonResponse({ error: 'overview-unavailable' }, 500));
|
|
210
|
+
}
|
|
193
211
|
if (pathname === '/v1/ops/seats') {
|
|
194
212
|
const date = url.searchParams.get('date') ?? todayKst();
|
|
195
213
|
if (!DATE.test(date) || Number.isNaN(Date.parse(`${date}T00:00:00+09:00`))) return jsonResponse({ error: 'invalid-date' }, 400);
|
package/src/nexus/index.ts
CHANGED
|
@@ -4021,78 +4021,10 @@ function shouldRegisterWebterm(): boolean {
|
|
|
4021
4021
|
// `boot/register-daemon.ts` and re-exported above (line 32) so existing
|
|
4022
4022
|
// test imports (`from '../src/nexus/index.js'`) keep working.
|
|
4023
4023
|
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
* 2. `ELANOUS_TOOLS` env ← shell session
|
|
4032
|
-
* 3. UserConfig switch `global.tools` (PWA Settings 토글이 여기) ← 영구
|
|
4033
|
-
* 4. switch default = `'webterm'` (`builtins/global.ts:12`) ← fallback
|
|
4034
|
-
*
|
|
4035
|
-
* Pre-fix the in-process branch fell back to `'none'` while
|
|
4036
|
-
* `deriveChildEnv` fell back to the switch default `'webterm'`. That
|
|
4037
|
-
* meant a clean-machine `elanous nexus` (no flag/env) gave the in-process
|
|
4038
|
-
* LLM call an empty tool catalog while a separately-launched
|
|
4039
|
-
* `elanous serve` daemon honored the switch default's full surface —
|
|
4040
|
-
* surprising divergence the user had no way to discover.
|
|
4041
|
-
*
|
|
4042
|
-
* `'all'` is in the switch enum but has no `toolSurface()` impl yet;
|
|
4043
|
-
* fall back to `'webterm'` (the most-capable wired surface). */
|
|
4044
|
-
const daemonToolSurfaceKinds: Record<DaemonToolSurfaceKind, true> = {
|
|
4045
|
-
none: true,
|
|
4046
|
-
readonly: true,
|
|
4047
|
-
chat: true,
|
|
4048
|
-
webterm: true,
|
|
4049
|
-
};
|
|
4050
|
-
|
|
4051
|
-
function resolveKnownToolsKind(raw: string): DaemonToolSurfaceKind | undefined {
|
|
4052
|
-
return Object.hasOwn(daemonToolSurfaceKinds, raw)
|
|
4053
|
-
? raw as DaemonToolSurfaceKind
|
|
4054
|
-
: undefined;
|
|
4055
|
-
}
|
|
4056
|
-
|
|
4057
|
-
export function resolveToolsKind(opts: { tools?: string }): DaemonToolSurfaceKind {
|
|
4058
|
-
let raw: string | undefined = opts.tools;
|
|
4059
|
-
if (!raw) raw = process.env.ELANOUS_TOOLS?.trim();
|
|
4060
|
-
if (!raw) {
|
|
4061
|
-
try {
|
|
4062
|
-
const cfg = readUserConfig();
|
|
4063
|
-
const v = readSwitchValue(cfg, 'global.tools');
|
|
4064
|
-
if (typeof v === 'string') raw = v;
|
|
4065
|
-
} catch { /* swallow — boot must not fail on a malformed config */ }
|
|
4066
|
-
}
|
|
4067
|
-
if (!raw) raw = 'webterm';
|
|
4068
|
-
return resolveKnownToolsKind(raw) ?? 'webterm';
|
|
4069
|
-
}
|
|
4070
|
-
|
|
4071
|
-
/** Resolve the `:agent` (통합 모드 webterm dock) tool surface kind.
|
|
4072
|
-
*
|
|
4073
|
-
* Distinct from {@link resolveToolsKind} because the integrated
|
|
4074
|
-
* webterm-dock chat is **defined by** its PTY surface — WebTerminal*
|
|
4075
|
-
* tools (Snapshot · Input · Screenshot) are what make `:agent`
|
|
4076
|
-
* meaningful next to a live terminal. If the user has narrowed their
|
|
4077
|
-
* user-config (`global.tools chat` / `readonly`) to keep the
|
|
4078
|
-
* standalone `/chat` lighter, that preference must NOT bleed into the
|
|
4079
|
-
* integrated dock — otherwise the `:agent` LLM loses access to the
|
|
4080
|
-
* very terminal it's supposed to drive.
|
|
4081
|
-
*
|
|
4082
|
-
* Resolution order:
|
|
4083
|
-
* 1. CLI flag (`opts.tools`) ← explicit operator intent wins
|
|
4084
|
-
* 2. **`'webterm'`** (hardcoded) ← env + global.tools intentionally
|
|
4085
|
-
* skipped vs. resolveToolsKind
|
|
4086
|
-
*
|
|
4087
|
-
* CLI flag is honored so a developer debugging tool selection
|
|
4088
|
-
* (`elanous nexus run --tools readonly`) still sees a readonly `:agent`;
|
|
4089
|
-
* but a clean-machine boot or a config-narrowed boot always gets
|
|
4090
|
-
* webterm for the integrated dock. */
|
|
4091
|
-
export function resolveAgentTurnToolsKind(opts: { tools?: string }): DaemonToolSurfaceKind {
|
|
4092
|
-
const raw = opts.tools?.trim();
|
|
4093
|
-
if (!raw) return 'webterm';
|
|
4094
|
-
// 'all' (or any unrecognized string) → webterm fallback, same as
|
|
4095
|
-
// resolveToolsKind so the two resolvers stay consistent for valid
|
|
4096
|
-
// CLI input.
|
|
4097
|
-
return resolveKnownToolsKind(raw) ?? 'webterm';
|
|
4098
|
-
}
|
|
4024
|
+
// C-2a (same pattern as `shouldRegisterDaemon` above): the tool-surface
|
|
4025
|
+
// resolution block (`daemonToolSurfaceKinds` · `resolveKnownToolsKind` ·
|
|
4026
|
+
// `resolveToolsKind` · `resolveAgentTurnToolsKind`) moved to
|
|
4027
|
+
// `nexus/tools-kind.ts` and re-exported here so existing test imports
|
|
4028
|
+
// (`from '../src/nexus/index.js'`) keep working.
|
|
4029
|
+
import { resolveAgentTurnToolsKind, resolveToolsKind } from './tools-kind.js';
|
|
4030
|
+
export { resolveToolsKind, resolveAgentTurnToolsKind } from './tools-kind.js';
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { readUserConfig, readSwitchValue } from './config/user-config.js';
|
|
2
|
+
import type { DaemonToolSurfaceKind } from '../boot/daemon-tools/types.js';
|
|
3
|
+
|
|
4
|
+
/** Resolve the active daemon tool surface kind for runNexus.
|
|
5
|
+
*
|
|
6
|
+
* Resolution order (parity with `deriveChildEnv` for daemon tab spawn —
|
|
7
|
+
* the same switch fed into a daemon child env should govern the in-
|
|
8
|
+
* process path too):
|
|
9
|
+
*
|
|
10
|
+
* 1. CLI flag (`opts.tools`) ← 1회 boot
|
|
11
|
+
* 2. `ELANOUS_TOOLS` env ← shell session
|
|
12
|
+
* 3. UserConfig switch `global.tools` (PWA Settings 토글이 여기) ← 영구
|
|
13
|
+
* 4. switch default = `'webterm'` (`builtins/global.ts:12`) ← fallback
|
|
14
|
+
*
|
|
15
|
+
* Pre-fix the in-process branch fell back to `'none'` while
|
|
16
|
+
* `deriveChildEnv` fell back to the switch default `'webterm'`. That
|
|
17
|
+
* meant a clean-machine `elanous nexus` (no flag/env) gave the in-process
|
|
18
|
+
* LLM call an empty tool catalog while a separately-launched
|
|
19
|
+
* `elanous serve` daemon honored the switch default's full surface —
|
|
20
|
+
* surprising divergence the user had no way to discover.
|
|
21
|
+
*
|
|
22
|
+
* `'all'` is in the switch enum but has no `toolSurface()` impl yet;
|
|
23
|
+
* fall back to `'webterm'` (the most-capable wired surface). */
|
|
24
|
+
const daemonToolSurfaceKinds: Record<DaemonToolSurfaceKind, true> = {
|
|
25
|
+
none: true,
|
|
26
|
+
readonly: true,
|
|
27
|
+
chat: true,
|
|
28
|
+
webterm: true,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function resolveKnownToolsKind(raw: string): DaemonToolSurfaceKind | undefined {
|
|
32
|
+
return Object.hasOwn(daemonToolSurfaceKinds, raw)
|
|
33
|
+
? raw as DaemonToolSurfaceKind
|
|
34
|
+
: undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function resolveToolsKind(opts: { tools?: string }): DaemonToolSurfaceKind {
|
|
38
|
+
let raw: string | undefined = opts.tools;
|
|
39
|
+
if (!raw) raw = process.env.ELANOUS_TOOLS?.trim();
|
|
40
|
+
if (!raw) {
|
|
41
|
+
try {
|
|
42
|
+
const cfg = readUserConfig();
|
|
43
|
+
const v = readSwitchValue(cfg, 'global.tools');
|
|
44
|
+
if (typeof v === 'string') raw = v;
|
|
45
|
+
} catch { /* swallow — boot must not fail on a malformed config */ }
|
|
46
|
+
}
|
|
47
|
+
if (!raw) raw = 'webterm';
|
|
48
|
+
return resolveKnownToolsKind(raw) ?? 'webterm';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Resolve the `:agent` (통합 모드 webterm dock) tool surface kind.
|
|
52
|
+
*
|
|
53
|
+
* Distinct from {@link resolveToolsKind} because the integrated
|
|
54
|
+
* webterm-dock chat is **defined by** its PTY surface — WebTerminal*
|
|
55
|
+
* tools (Snapshot · Input · Screenshot) are what make `:agent`
|
|
56
|
+
* meaningful next to a live terminal. If the user has narrowed their
|
|
57
|
+
* user-config (`global.tools chat` / `readonly`) to keep the
|
|
58
|
+
* standalone `/chat` lighter, that preference must NOT bleed into the
|
|
59
|
+
* integrated dock — otherwise the `:agent` LLM loses access to the
|
|
60
|
+
* very terminal it's supposed to drive.
|
|
61
|
+
*
|
|
62
|
+
* Resolution order:
|
|
63
|
+
* 1. CLI flag (`opts.tools`) ← explicit operator intent wins
|
|
64
|
+
* 2. **`'webterm'`** (hardcoded) ← env + global.tools intentionally
|
|
65
|
+
* skipped vs. resolveToolsKind
|
|
66
|
+
*
|
|
67
|
+
* CLI flag is honored so a developer debugging tool selection
|
|
68
|
+
* (`elanous nexus run --tools readonly`) still sees a readonly `:agent`;
|
|
69
|
+
* but a clean-machine boot or a config-narrowed boot always gets
|
|
70
|
+
* webterm for the integrated dock. */
|
|
71
|
+
export function resolveAgentTurnToolsKind(opts: { tools?: string }): DaemonToolSurfaceKind {
|
|
72
|
+
const raw = opts.tools?.trim();
|
|
73
|
+
if (!raw) return 'webterm';
|
|
74
|
+
// 'all' (or any unrecognized string) → webterm fallback, same as
|
|
75
|
+
// resolveToolsKind so the two resolvers stay consistent for valid
|
|
76
|
+
// CLI input.
|
|
77
|
+
return resolveKnownToolsKind(raw) ?? 'webterm';
|
|
78
|
+
}
|
|
@@ -29,6 +29,7 @@ export interface RotationCandidate {
|
|
|
29
29
|
/** 선불 크레딧 잔액·보유(모르면 없다). 크레딧 정책에서 «어느 계정 크레딧으로» 갈지 고른다. */
|
|
30
30
|
readonly creditBalance?: number;
|
|
31
31
|
readonly hasCredits?: boolean;
|
|
32
|
+
readonly resetCreditAvailability?: 'available' | 'unavailable' | 'unknown';
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/** ⛔ 비공개 — 밖에서 이름으로 부를 소비처가 없다(리뷰 must-fix: dead export 금지). */
|
|
@@ -69,6 +70,10 @@ interface RotationDecision {
|
|
|
69
70
|
}[];
|
|
70
71
|
/** 넘겼을 때만 있다. */
|
|
71
72
|
readonly to?: RotationCandidate;
|
|
73
|
+
/** 다른 계정의 리셋권이 이 판정을 연 경우의 관측용 이름. 소비 대상은 소비기가 별도로 고른다. */
|
|
74
|
+
readonly resetCreditAccount?: string;
|
|
75
|
+
/** SUBSCRIPTION-EXHAUST-REROUTE — 크레딧 예비분에 닿아 `no-candidate` 로 넘겼을 때만 있다(잔액은 «알려진» 값). */
|
|
76
|
+
readonly creditReserveHit?: { readonly balance: number; readonly reserve: number };
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
/** ⛔ 비공개 — 호출자는 객체 리터럴로 준다(구조적 타이핑). */
|
|
@@ -104,6 +109,9 @@ interface RotationInput {
|
|
|
104
109
|
readonly currentCreditBalance?: number;
|
|
105
110
|
readonly currentHasCredits?: boolean;
|
|
106
111
|
readonly creditPace?: { active: boolean };
|
|
112
|
+
/** SUBSCRIPTION-EXHAUST-REROUTE (`llm.codexCreditReserve`) — 크레딧 잔액이 이 값 «이하»면 크레딧을 쓰지 않는다.
|
|
113
|
+
* ⛔ 없으면 종전(잔액 > 0 이면 쓴다). 잔액을 «모르면» 이 값으로 막지 않는다(fail-open). */
|
|
114
|
+
readonly creditReserve?: number;
|
|
107
115
|
}
|
|
108
116
|
|
|
109
117
|
/**
|
|
@@ -215,6 +223,10 @@ export function decideCodexRotation(input: RotationInput): RotationDecision {
|
|
|
215
223
|
const to = usable[0];
|
|
216
224
|
if (!to) {
|
|
217
225
|
if (input.resetCreditAvailability === 'available') return { reason: 'reset-credit-available', candidateCount, accountThresholds };
|
|
226
|
+
const resetCreditAccount = otherCandidates
|
|
227
|
+
.filter((candidate) => candidate.home.trim() && candidate.resetCreditAvailability === 'available')
|
|
228
|
+
.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)[0]?.name;
|
|
229
|
+
if (resetCreditAccount) return { reason: 'reset-credit-available', candidateCount, accountThresholds, resetCreditAccount };
|
|
218
230
|
if (input.creditPace?.active) {
|
|
219
231
|
const self = input.candidates.find((candidate) => candidate.name === input.current.name);
|
|
220
232
|
const eligible = (name: string, balance: number | undefined, hasCredits: boolean | undefined, home: string | undefined): boolean =>
|
|
@@ -235,18 +247,27 @@ export function decideCodexRotation(input: RotationInput): RotationDecision {
|
|
|
235
247
|
// 한 계정 크레딧이 바닥나면 남은 크레딧을 두고 폴백(grok)으로 갔다. ⇒ 크레딧이 «더 많이» 남은 계정으로 옮긴다.
|
|
236
248
|
// 흔들림 방지: 지금 계정 크레딧이 없거나(보유 false · 잔액 ≤0) 다른 계정이 20% 넘게 많을 때만.
|
|
237
249
|
if (input.creditsAllowed === true) {
|
|
250
|
+
// SUBSCRIPTION-EXHAUST-REROUTE — 예비분이 있으면 «예비분 초과»만 쓸 크레딧이다(없으면 종전 > 0).
|
|
251
|
+
const reserve = typeof input.creditReserve === 'number' && Number.isFinite(input.creditReserve) && input.creditReserve >= 0
|
|
252
|
+
? input.creditReserve : undefined;
|
|
253
|
+
const floor = reserve ?? 0;
|
|
238
254
|
const richest = otherCandidates
|
|
239
|
-
.filter((c) => c.home.length > 0 && c.hasCredits !== false && typeof c.creditBalance === 'number' && c.creditBalance >
|
|
255
|
+
.filter((c) => c.home.length > 0 && c.hasCredits !== false && typeof c.creditBalance === 'number' && c.creditBalance > floor)
|
|
240
256
|
.sort((a, b) => (b.creditBalance! - a.creditBalance!) || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))[0];
|
|
241
257
|
// 지금 계정 신호를 귀속할 수 없을 때(기본 계정 `source=default`)는 후보 목록의 같은 이름 줄을 쓴다 —
|
|
242
258
|
// 후보는 같은 홈 해석으로 신호를 읽었다(09-28 운영: default 가 «사용=? · 찼나=모름»이라 잔액을 못 봐 머물렀다).
|
|
243
259
|
const self = input.candidates.find((c) => c.name === input.current.name);
|
|
244
260
|
const cur = input.currentCreditBalance ?? self?.creditBalance;
|
|
245
261
|
const curHas = input.currentHasCredits ?? self?.hasCredits;
|
|
246
|
-
const currentDry = curHas === false || (typeof cur === 'number' && cur <=
|
|
262
|
+
const currentDry = curHas === false || (typeof cur === 'number' && cur <= floor);
|
|
247
263
|
if (richest && (currentDry || (typeof cur === 'number' && richest.creditBalance! > cur * 1.2))) {
|
|
248
264
|
return { reason: 'rotated', candidateCount, accountThresholds, to: richest };
|
|
249
265
|
}
|
|
266
|
+
// 🩸 10-11: 크레딧이 0 이 돼도 여기 머물러 codex 로 계속 쐈다. 잔액이 «알려져 있고» 예비분 이하이며
|
|
267
|
+
// 예비분을 넘는 다른 계정도 없으면 «갈 곳 없음» — 폴백 체인이 다음 칸을 정한다. 모르면 종전대로 머문다.
|
|
268
|
+
if (reserve !== undefined && typeof cur === 'number' && Number.isFinite(cur) && cur <= reserve) {
|
|
269
|
+
return { reason: 'no-candidate', candidateCount, accountThresholds, creditReserveHit: { balance: cur, reserve } };
|
|
270
|
+
}
|
|
250
271
|
return { reason: 'credits-allowed', candidateCount, accountThresholds };
|
|
251
272
|
}
|
|
252
273
|
return { reason: 'no-candidate', candidateCount, accountThresholds };
|
|
@@ -255,6 +276,21 @@ export function decideCodexRotation(input: RotationInput): RotationDecision {
|
|
|
255
276
|
return { reason: 'rotated', candidateCount, accountThresholds, to };
|
|
256
277
|
}
|
|
257
278
|
|
|
279
|
+
/** SUBSCRIPTION-EXHAUST-REROUTE — 크레딧 예비분 기본(`llm.codexCreditReserve` 가 없을 때 · 근거는 codex-account-store 주석).
|
|
280
|
+
* 여기 두는 이유: Pod 계획(pod-account-broker)이 store 를 끌어오지 않고 같은 값을 쓴다. */
|
|
281
|
+
export const CODEX_CREDIT_RESERVE_DEFAULT = 2000;
|
|
282
|
+
|
|
283
|
+
/** CODEX-CREDITS-STEP — 크레딧 잔액이 «확인된» 후보(잔액 > 예비분 · 보유 ≠ false · 홈을 안다)를 잔액 많은 순으로.
|
|
284
|
+
* 잔액을 모르면 넣지 않는다(돈 · fail-closed). 회전 수집기와 Pod 계획이 «같은 자»를 쓴다. */
|
|
285
|
+
export function creditCandidatesByBalance(candidates: readonly RotationCandidate[], reserve = 0): RotationCandidate[] {
|
|
286
|
+
// SUBSCRIPTION-EXHAUST-REROUTE — 잔액이 예비분(`llm.codexCreditReserve`) «초과»인 계정만(0 = 종전 > 0).
|
|
287
|
+
const floor = Number.isFinite(reserve) && reserve >= 0 ? reserve : 0;
|
|
288
|
+
return candidates
|
|
289
|
+
.filter((c) => c.home.length > 0 && c.hasCredits !== false
|
|
290
|
+
&& typeof c.creditBalance === 'number' && Number.isFinite(c.creditBalance) && c.creditBalance > floor)
|
|
291
|
+
.sort((a, b) => (b.creditBalance! - a.creditBalance!) || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
292
|
+
}
|
|
293
|
+
|
|
258
294
|
/**
|
|
259
295
|
* ⛔⭐⭐⭐⭐⭐ **회전한 계정을 «자식 env»로 내려보낼 덧칠** (2026-08-07 · 크레딧 유출 차단).
|
|
260
296
|
*
|
|
@@ -285,6 +321,7 @@ export function observeRotation(decision: RotationDecision, from: string): void
|
|
|
285
321
|
from,
|
|
286
322
|
to: decision.to?.name,
|
|
287
323
|
reason: decision.reason,
|
|
324
|
+
...(decision.resetCreditAccount ? { resetCreditAccount: decision.resetCreditAccount } : {}),
|
|
288
325
|
candidateCount: decision.candidateCount ?? 'unknown',
|
|
289
326
|
...(decision.reason === 'disabled' && decision.disabledProvenance
|
|
290
327
|
? { disabledProvenance: decision.disabledProvenance }
|