elanous 0.2.27 → 0.2.28
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/{1454-a9c338f5bb556f42.js → 1454-29d1d773d5996a49.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{599-a914cc0468f56143.js → 599-56f2c0a5cc4a7295.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-5ab1e47978577c5e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-07110da72c45e68f.js → page-24453f6a302816b4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-d2d76d4a7eae91bd.js → page-6c131d30bb1cf871.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-fa46d4588e8442c2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-e5a9302c105725a3.js → page-dc0772b11861af51.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-66ad46ed0279feb9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-5159dc8752c6cd29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c5a2478861c08b87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-f722a9dec084e08b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/{page-6a16a48d1aa6e91f.js → page-256ffd365d32e100.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e0de78ae7b99f01c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-cfbdc44b708d9801.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-39f82acf619cca23.js → page-4c741eb760f2f77c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-7c2c8f81b185f9c0.js → page-e9ba98f5f1440a21.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/{page-ae7aff07e32676bb.js → page-dafc901bb1b72b5f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-482c60c1bc540d0a.js → page-2f6f303fa114ec24.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-7cb996c1aa08b6f5.js → layout-1634081c5272006c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/{page-623e08fe4477d606.js → page-ced0bcfc6542b3fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/{page-db460cd8aecb8490.js → page-bb1f05cd51c38944.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/{page-ff27902d0b11ef4f.js → page-646b50f63b6fe732.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-db069b48a59c8c00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-1917ea87ea71ab6b.js → page-f736324afa2117c1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-25a302273b054fd7.js → page-6f131b51193a36f9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-670510eda8b9443f.js → not-found-435f5e2faf02bc01.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-497e0eedf20e7890.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a8d28fdf995e16ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-4bb7556e2e97a750.js → page-8966452228753c5e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fd5d18656cb432b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-5663779cb1a37609.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/{page-4eb96fc7241c2456.js → page-66a67eb19ebd976b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-3b492aa78cf3c4a6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-0d64ff212dc55d03.js → page-c71267e7681da982.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-7f096248993fc2d4.js → page-a21d1e1797172692.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-e47fc72049149a38.js → page-cb249e81ed339318.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-f7d808e31e005895.js → page-781cfc5114e097da.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-5c2b24b5496dad73.js → page-478157214db6a023.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-76cdb531668b5191.js → page-86141f12cf090b7f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-7256c73bb1c36751.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-5c738ca7adef3de5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-0138c8091ec17224.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-cc2f24f16d85e735.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/{page-dab163b338e3b966.js → page-979d318a534e92da.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-182d394f7e6e096b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-0260ffc7eda50db1.js → page-21050928a697ed7a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-44b9d807005a9030.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-99c4e6c7345a9156.js → page-601fb77340e63c9a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-75d3fb5d47af0edf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-11bf9e5725a72c22.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +13 -13
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +13 -13
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +13 -13
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +13 -13
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +13 -13
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +13 -13
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +13 -13
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +13 -13
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +13 -13
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +13 -13
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +13 -13
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +13 -13
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +13 -13
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +13 -13
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +13 -13
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +13 -13
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +13 -13
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +13 -13
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +13 -13
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +13 -13
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +13 -13
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +13 -13
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +13 -13
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +13 -13
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +13 -13
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +13 -13
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +13 -13
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +13 -13
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +14 -14
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +13 -13
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +13 -13
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +13 -13
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +13 -13
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +13 -13
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +13 -13
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +13 -13
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +13 -13
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +13 -13
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +13 -13
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +13 -13
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +13 -13
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +13 -13
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +13 -13
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +13 -13
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +13 -13
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +13 -13
- package/graphs/implement-loop.yaml +38 -1
- package/graphs/steward/recipes.yaml +6 -0
- package/graphs/steward/release-day.yaml +23 -0
- package/graphs/steward/steward.yaml +9 -1
- package/graphs/work/code-pr.yaml +23 -0
- package/package.json +1 -1
- package/scripts/browser-observe-cycle.ts +76 -2
- package/scripts/ci-typecheck-changed.ts +50 -17
- package/scripts/detect-test-interference.ts +31 -1
- package/scripts/release-loop/docs-node.ts +51 -22
- package/scripts/release-loop/gate-node.ts +37 -4
- package/scripts/release-loop/node-verdict.ts +3 -1
- package/scripts/release-loop/publish-node.ts +3 -0
- package/scripts/release-loop/tui-node-checks.ts +5 -2
- package/scripts/skills/public-check.ts +5 -2
- package/scripts/test-deterministic.ts +520 -37
- package/scripts/ux-sim.ts +7 -1
- package/scripts/verify-kpack6-live.ts +230 -0
- package/src/acp/codex-plugins.ts +17 -0
- package/src/agent/task-notification.ts +2 -1
- package/src/agent-mission/in-agent-plugin.ts +8 -3
- package/src/agent-substrate/self-review-cli.ts +34 -10
- package/src/briefing/brief-items.ts +151 -35
- package/src/budget/llm-cost.ts +25 -2
- package/src/budget/usage-outcomes.ts +32 -0
- package/src/claims/claims-ledger.ts +23 -0
- package/src/claims/claims-render.ts +22 -2
- package/src/cli/brief-cli.ts +131 -10
- package/src/cli/claims-cli.ts +24 -4
- package/src/cli/msg-cli.ts +165 -9
- package/src/cli/plugin-cli.ts +11 -3
- package/src/cli/pr-cli.ts +80 -18
- package/src/cli/pr-land-refusal.ts +20 -0
- package/src/cli/release-cli.ts +2 -0
- package/src/cli/rfc-cli.ts +24 -0
- package/src/cli/tailscale-serve.ts +5 -8
- package/src/cli/tasks-cli.ts +393 -26
- package/src/cli/usage-cli.ts +9 -4
- package/src/dashboard/first-screen-band.ts +63 -13
- package/src/dashboard/index.ts +8 -3
- package/src/dashboard/skill-detection-runtime.ts +24 -4
- package/src/domains/morning-report.ts +3 -1
- package/src/github/pr-labels.ts +10 -4
- package/src/graph-runner/runner.ts +101 -14
- package/src/harness/harness-clean.ts +21 -3
- package/src/harness/harness-cli-command.ts +22 -6
- package/src/harness/harness-pod-dispatch.ts +11 -4
- package/src/hq/seats.ts +17 -3
- package/src/index.ts +34 -5
- package/src/intelligence-map/model-bench-scores.ts +102 -0
- package/src/loops/orchestrator/inbox.ts +73 -0
- package/src/loops/orchestrator/resync-shadow.ts +100 -12
- package/src/loops/orchestrator/tick.ts +73 -2
- package/src/maturity/feature-maturity.ts +1 -1
- package/src/msg/msg-sse-server.ts +141 -0
- package/src/msg/msg-store.ts +84 -14
- package/src/patents/ledger.ts +128 -0
- package/src/plugins/install/market-fetch.ts +32 -1
- package/src/pod-lease/admission.ts +116 -41
- package/src/release-loop/checklist.ts +11 -0
- package/src/release-loop/merged-pr-checklist.ts +24 -5
- package/src/release-loop/rubric.ts +70 -0
- package/src/rfc-registry/registry.ts +88 -0
- package/src/seat-dispatch/seat-request-ledger.ts +4 -0
- package/src/seat-loop/knowhow-lint.ts +233 -0
- package/src/seat-loop/seat-pack.ts +16 -4
- package/src/seat-loop/traffic-signal.ts +7 -0
- package/src/self-dev/ask-launch-flow.ts +48 -8
- package/src/self-dev/ask-path-candidates.ts +69 -14
- package/src/self-dev/decompose-proposal.ts +43 -1
- package/src/self-dev/graph-journey-nodes.ts +2 -0
- package/src/self-dev/launch-preflight-text.ts +5 -1
- package/src/self-dev/launch-preflight.ts +143 -10
- package/src/self-implement/gate-cli.ts +6 -2
- package/src/self-implement/goal-author.ts +2 -1
- package/src/self-implement/goal-new-paths.ts +4 -5
- package/src/self-implement/graph-authority.ts +17 -3
- package/src/self-implement/graph-yaml.ts +15 -7
- package/src/self-implement/headless-elanous-driver.ts +16 -1
- package/src/self-implement/host-regate.ts +44 -9
- package/src/self-implement/orchestrator.ts +3 -10
- package/src/self-implement/rework-policy.ts +80 -2
- package/src/self-implement/speed-claims.ts +145 -6
- package/src/skills/tools/persistent-grounding.ts +59 -4
- package/src/skills/tools/shell.ts +79 -1
- package/src/steward/rejudge-tick.ts +12 -1
- package/src/steward/release-day.ts +158 -0
- package/src/steward/resync-tick.ts +123 -0
- package/src/task-agent/actions.ts +1 -0
- package/src/task-agent/adopt-pr.ts +140 -0
- package/src/task-agent/board.ts +11 -1
- package/src/task-agent/card-evidence.ts +37 -5
- package/src/task-agent/cover.ts +84 -17
- package/src/task-agent/failure-notify.ts +61 -0
- package/src/task-agent/green-sweep.ts +170 -0
- package/src/task-agent/land-worktree.ts +52 -5
- package/src/task-agent/live-move-history.ts +1 -0
- package/src/task-agent/live-moves.ts +43 -12
- package/src/task-agent/parent-host.ts +1 -1
- package/src/task-agent/postland-card.ts +139 -0
- package/src/task-agent/rejudge-on-head.ts +106 -7
- package/src/task-agent/remote-rejudge-bind.ts +62 -0
- package/src/task-agent/repair-branch-cas.ts +182 -0
- package/src/task-agent/repair-cycle.ts +275 -0
- package/src/task-agent/repair-next-move.ts +31 -0
- package/src/task-agent/repair-review.ts +24 -0
- package/src/task-agent/repair-worker.ts +87 -0
- package/src/task-agent/review-result-capture.ts +2 -1
- package/src/task-agent/shadow.ts +13 -7
- package/src/task-agent/task-hand.ts +115 -6
- package/src/task-orchestrator/surfaces/pod-repair-commit.ts +96 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +70 -12
- package/src/telegram.ts +30 -1
- package/src/user-config.ts +8 -1
- package/src/ux-sim/fault-sqlite-busy.ts +50 -0
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-a1ce306364f08da9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-30007380f2e2a3c1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-b93c04b5311c97ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-718a49e86bf25f9c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-20662900d6b4b455.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-f707a509515ac50c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-fcb06e305f515527.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-6acf5ec4e6c6d1af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-8397a5c6ca569bf4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-d73c3f1e00d93986.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-65caacf54389081e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-77e04b4f8762c666.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-15c0a4095baddfba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-63e272a4cd3b6634.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e2b63345bba1a11f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-d376e0c83edc041a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-d6db55fe2c04d128.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-87b88f551f421d8b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-08019efef111ed2e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-980ce6fe36ad95eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-0ab42e0f05823013.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ad9220422d20ba33.js +0 -1
- /package/apps/pwa/out/_next/static/{Ev2jivDMTSoEc-5rIXdcy → y1ltt7oCZxVpC6zVPhZ2O}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{Ev2jivDMTSoEc-5rIXdcy → y1ltt7oCZxVpC6zVPhZ2O}/_ssgManifest.js +0 -0
|
@@ -628,17 +628,74 @@ function sameProviderStep(child: ExplicitChildForEscalation): EscalateTarget | n
|
|
|
628
628
|
return null;
|
|
629
629
|
}
|
|
630
630
|
|
|
631
|
+
/** escalateChild 매핑 해석 근원 — `env` 가 `config` 를 이기고, 둘 다 없으면 같은 provider 사다리(`ladder`). */
|
|
632
|
+
export type EscalateTargetSource = 'config' | 'env' | 'ladder';
|
|
633
|
+
|
|
634
|
+
/** `self-dev.rework`/`escalate-target` 관측 한 건. 관측 전용 — 판정·분기는 이 값을 읽지 않는다. */
|
|
635
|
+
export interface EscalateTargetObservation {
|
|
636
|
+
readonly category: 'self-dev.rework';
|
|
637
|
+
readonly event: 'escalate-target';
|
|
638
|
+
readonly data: {
|
|
639
|
+
readonly source: EscalateTargetSource;
|
|
640
|
+
readonly from: string;
|
|
641
|
+
readonly to: string;
|
|
642
|
+
readonly applied: boolean;
|
|
643
|
+
/** `applied=false` 의 이유 — provider 경계 또는 불완전한 모델 ID. */
|
|
644
|
+
readonly reason?: 'cross-provider' | 'invalid-target';
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/** 명시 자식 승급 해석 옵션. `escalateChild`(`tools.selfImplement.escalateChild` from→to 맵)는 호출부가
|
|
649
|
+
* config 에서 읽어 전달한다 — ⚠️ Pod 안엔 운영 config 가 없다(POD-CONFIG-SNAPSHOT 전) → Pod 자식은
|
|
650
|
+
* `ELANOUS_SELFDEV_ESCALATE_CHILD` env 로만 받는다. 관측 싱크도 호출부가 주입한다(정책은 순수 유지). */
|
|
651
|
+
export interface ExplicitChildEscalateOptions {
|
|
652
|
+
readonly escalateChild?: Readonly<Record<string, string>>;
|
|
653
|
+
readonly observe?: (observation: EscalateTargetObservation) => void;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/** 모델 id(`openrouter/z-ai/glm-5.3` 등)의 provider 세그먼트 — 첫 `/` 앞. */
|
|
657
|
+
function escalateModelProvider(model: string): string {
|
|
658
|
+
return (model.split('/')[0] ?? '').trim();
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/** 명시 매핑은 provider/model, OpenRouter 는 openrouter/vendor/model 형태의 완전한 ID 여야 한다. */
|
|
662
|
+
function validEscalateModelId(model: string): boolean {
|
|
663
|
+
const segments = model.split('/');
|
|
664
|
+
const required = segments[0] === 'openrouter' ? 3 : 2;
|
|
665
|
+
return segments.length === required && segments.every((segment) => segment.length > 0 && !/\s/.test(segment));
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/** `ELANOUS_SELFDEV_ESCALATE_CHILD=<from>=<to>` 해석 — 첫 `=` 기준. 빈 조각·구문 파손은 매핑 없음. */
|
|
669
|
+
function parseEscalateChildEnv(raw: string | undefined): { from: string; to: string } | undefined {
|
|
670
|
+
const value = raw?.trim();
|
|
671
|
+
if (!value) return undefined;
|
|
672
|
+
const separator = value.indexOf('=');
|
|
673
|
+
if (separator <= 0) return undefined;
|
|
674
|
+
const from = value.slice(0, separator).trim();
|
|
675
|
+
const to = value.slice(separator + 1).trim();
|
|
676
|
+
return from && to ? { from, to } : undefined;
|
|
677
|
+
}
|
|
678
|
+
|
|
631
679
|
/** 명시 자식의 승급 타깃 — 같은 provider 안에서만(위 규칙). `ELANOUS_SELFDEV_SOL_*` 는 운영자의 명시
|
|
632
680
|
* 덮어쓰기라 그대로 우선한다. ⛔ `effort` 가 빈 문자열이면 «안 보낸다»는 뜻이다(OpenRouter 는 노력을
|
|
633
|
-
* 보내면 덜 생각했다 — 2026-09-23 실측).
|
|
681
|
+
* 보내면 덜 생각했다 — 2026-09-23 실측).
|
|
682
|
+
* escalateChild 짝지기(env 가 config 를 이긴다)는 사다리(`sameProviderStep`)보다 «먼저» —
|
|
683
|
+
* 같은 provider 타깃이면 그 타깃으로, provider 가 다르면 무시 ⊕ 관측하고 사다리로 돌아간다.
|
|
684
|
+
* 둘 다 없으면 종전 그대로(바이트 불변). */
|
|
634
685
|
export function resolveExplicitChildEscalateTarget(
|
|
635
686
|
tier: EscalateTier,
|
|
636
687
|
env: Record<string, string | undefined> = process.env,
|
|
637
688
|
child?: ExplicitChildForEscalation,
|
|
689
|
+
options?: ExplicitChildEscalateOptions,
|
|
638
690
|
): EscalateTarget | null {
|
|
639
691
|
if (tier === 'none') return null;
|
|
692
|
+
const sink = options?.observe;
|
|
693
|
+
const emit = sink
|
|
694
|
+
? (data: EscalateTargetObservation['data']): void => sink({ category: 'self-dev.rework', event: 'escalate-target', data })
|
|
695
|
+
: undefined;
|
|
640
696
|
const overrideModel = env.ELANOUS_SELFDEV_SOL_MODEL?.trim();
|
|
641
697
|
if (overrideModel) {
|
|
698
|
+
if (child) emit?.({ source: 'env', from: child.model, to: overrideModel, applied: true });
|
|
642
699
|
return {
|
|
643
700
|
model: overrideModel,
|
|
644
701
|
provider: env.ELANOUS_SELFDEV_SOL_PROVIDER?.trim() || child?.provider || 'openai-codex',
|
|
@@ -646,9 +703,30 @@ export function resolveExplicitChildEscalateTarget(
|
|
|
646
703
|
};
|
|
647
704
|
}
|
|
648
705
|
if (child === undefined) return null;
|
|
706
|
+
const effortOverride = env.ELANOUS_SELFDEV_SOL_EFFORT?.trim();
|
|
707
|
+
// env → config 순서로 이 자식 모델에 대한 매핑을 모은다(없는 층은 건너뛴다).
|
|
708
|
+
const pairings: Array<{ source: 'env' | 'config'; to: string }> = [];
|
|
709
|
+
const envPairing = parseEscalateChildEnv(env.ELANOUS_SELFDEV_ESCALATE_CHILD);
|
|
710
|
+
if (envPairing && envPairing.from === child.model) pairings.push({ source: 'env', to: envPairing.to });
|
|
711
|
+
const configMap = options?.escalateChild;
|
|
712
|
+
const configTo = configMap && Object.hasOwn(configMap, child.model) ? configMap[child.model]?.trim() : undefined;
|
|
713
|
+
if (configTo) pairings.push({ source: 'config', to: configTo });
|
|
714
|
+
for (const pairing of pairings) {
|
|
715
|
+
if (!validEscalateModelId(pairing.to)) {
|
|
716
|
+
emit?.({ source: pairing.source, from: child.model, to: pairing.to, applied: false, reason: 'invalid-target' });
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
if (escalateModelProvider(pairing.to) === child.provider) {
|
|
720
|
+
emit?.({ source: pairing.source, from: child.model, to: pairing.to, applied: true });
|
|
721
|
+
return effortOverride
|
|
722
|
+
? { model: pairing.to, provider: child.provider, effort: effortOverride }
|
|
723
|
+
: { model: pairing.to, provider: child.provider, effort: '' };
|
|
724
|
+
}
|
|
725
|
+
emit?.({ source: pairing.source, from: child.model, to: pairing.to, applied: false, reason: 'cross-provider' });
|
|
726
|
+
}
|
|
649
727
|
const step = sameProviderStep(child);
|
|
650
728
|
if (step === null) return null;
|
|
651
|
-
|
|
729
|
+
emit?.({ source: 'ladder', from: child.model, to: step.model, applied: true });
|
|
652
730
|
return effortOverride ? { ...step, effort: effortOverride } : step;
|
|
653
731
|
}
|
|
654
732
|
|
|
@@ -2,7 +2,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
2
2
|
import { readFileSync, readdirSync } from 'node:fs';
|
|
3
3
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { prodInstanceRoot } from '../instance/resolve.js';
|
|
5
|
+
import { prodInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js';
|
|
6
6
|
import { seatOfTree } from '../loops/orchestrator/traffic.js';
|
|
7
7
|
import { getUserConfig, ORCHESTRATOR_DEFAULTS } from '../user-config.js';
|
|
8
8
|
import { isRunStatus } from './run-status-mapping.js';
|
|
@@ -30,6 +30,7 @@ export interface SpeedClaimsMeasurement {
|
|
|
30
30
|
rows: SpeedClaimsRow[];
|
|
31
31
|
note: string;
|
|
32
32
|
parent: ParentSpeedClaimsMeasurement;
|
|
33
|
+
daily: SpeedClaimsDailyMeasurement;
|
|
33
34
|
}
|
|
34
35
|
export interface ParentSpeedClaimsEvent {
|
|
35
36
|
ts: string;
|
|
@@ -58,6 +59,32 @@ export interface ParentSpeedClaimsMeasurement {
|
|
|
58
59
|
note: string;
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
export interface SpeedClaimsReleaseEvent {
|
|
63
|
+
ts: string;
|
|
64
|
+
version: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface SpeedClaimsDailyRow {
|
|
68
|
+
date: string;
|
|
69
|
+
landedPrCount: number | null;
|
|
70
|
+
releasedCount: number | null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface SpeedClaimsDailySourceStatus {
|
|
74
|
+
status: 'measured' | 'unmeasured';
|
|
75
|
+
undatedCount: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface SpeedClaimsDailyMeasurement {
|
|
79
|
+
scope: 'last-30-days';
|
|
80
|
+
windowStart: string;
|
|
81
|
+
windowEnd: string;
|
|
82
|
+
landedPrs: SpeedClaimsDailySourceStatus;
|
|
83
|
+
releases: SpeedClaimsDailySourceStatus;
|
|
84
|
+
rows: SpeedClaimsDailyRow[];
|
|
85
|
+
note: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
61
88
|
export interface SpeedClaimsOptions {
|
|
62
89
|
dir?: string;
|
|
63
90
|
list?: (path: string) => string[];
|
|
@@ -65,6 +92,8 @@ export interface SpeedClaimsOptions {
|
|
|
65
92
|
readSeat?: (runId: string, ledgerDirectory: string, entries: readonly RunLedgerEntry[]) => SpeedClaimsSeat;
|
|
66
93
|
readParentEvents?: () => readonly ParentSpeedClaimsEvent[];
|
|
67
94
|
readParentQueue?: () => unknown;
|
|
95
|
+
now?: number;
|
|
96
|
+
readReleaseEvents?: () => readonly SpeedClaimsReleaseEvent[];
|
|
68
97
|
}
|
|
69
98
|
|
|
70
99
|
const NOTE = 'Child-ledger elapsed minutes from the first child event (or checkpoint creation when unavailable) to the recorded child end, including retries and resumes; this is not the parent launch-to-done interval. Only finished runs enter quartiles (linear interpolation at ranks (n-1)*p). In-progress runs (including parked) and missing, unreadable or invalid timestamps are excluded, not assigned zero. Seats come from the same state root checkpoint, launch queue or recorded launch tree; unresolved seats are counted separately. A completed run without a recorded merge is not a merge.';
|
|
@@ -192,6 +221,98 @@ function readParentQueueFromState(): unknown {
|
|
|
192
221
|
return JSON.parse(readFileSync(join(prodInstanceRoot(), 'harness', 'queue.json'), 'utf8')) as unknown;
|
|
193
222
|
}
|
|
194
223
|
|
|
224
|
+
const DAILY_WINDOW_DAYS = 30;
|
|
225
|
+
const DAY_MS = 86_400_000;
|
|
226
|
+
const DAILY_NOTE = 'Daily rows cover the last 30 UTC calendar days ending on the measurement day. Landed PRs are child run-ledger merged events (merged=true), identified by PR number with the run id as fallback when no number is recorded, de-duplicated across runs, retries and resumes, and attributed to the earliest recorded landing timestamp. Issued releases use the release ledger publishedAt, never a tag target commit date, and are de-duplicated by version. Identities without any valid timestamp are counted as undated, never as a day. A count of zero means the day was queried and had no events; unmeasured means the source could not be queried, and the JSON value is null. Start-to-land and external-change-to-reflection timings stay with the parent and child measurements above.';
|
|
227
|
+
const VERSION_TAG = /^v?\d+(?:\.\d+)+(?:-[\w.]+)?$/;
|
|
228
|
+
|
|
229
|
+
interface DailyObservation {
|
|
230
|
+
key: string;
|
|
231
|
+
ts: number | null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function utcDayOf(ts: number): string {
|
|
235
|
+
return new Date(ts).toISOString().slice(0, 10);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Landed-PR identity: the recorded PR number, else the run id (one run lands at most one PR). */
|
|
239
|
+
function landedPrIdentity(data: unknown, runId: string): string {
|
|
240
|
+
const number = object(data)?.number;
|
|
241
|
+
if (typeof number === 'number' && Number.isInteger(number) && number > 0) return `pr-${number}`;
|
|
242
|
+
if (typeof number === 'string' && /^\d+$/.test(number.trim()) && Number(number.trim()) > 0) return `pr-${Number(number.trim())}`;
|
|
243
|
+
return runId;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** De-duplicate repeated identities by their earliest valid timestamp; identities without any valid timestamp are reported as undated, never dropped silently. */
|
|
247
|
+
function dailyBuckets(observations: readonly DailyObservation[]): { counts: Map<string, number>; undated: number } {
|
|
248
|
+
const earliest = new Map<string, number>();
|
|
249
|
+
const undated = new Set<string>();
|
|
250
|
+
for (const { key, ts } of observations) {
|
|
251
|
+
if (key.length === 0) continue;
|
|
252
|
+
if (ts === null) { undated.add(key); continue; }
|
|
253
|
+
const known = earliest.get(key);
|
|
254
|
+
if (known === undefined || ts < known) earliest.set(key, ts);
|
|
255
|
+
}
|
|
256
|
+
const counts = new Map<string, number>();
|
|
257
|
+
for (const ts of earliest.values()) {
|
|
258
|
+
const day = utcDayOf(ts);
|
|
259
|
+
counts.set(day, (counts.get(day) ?? 0) + 1);
|
|
260
|
+
}
|
|
261
|
+
let undatedCount = 0;
|
|
262
|
+
for (const key of undated) if (!earliest.has(key)) undatedCount++;
|
|
263
|
+
return { counts, undated: undatedCount };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Read-only daily census of landed PRs and issued releases over the last 30 UTC days. */
|
|
267
|
+
function measureDailySpeedClaims(
|
|
268
|
+
landed: readonly DailyObservation[],
|
|
269
|
+
landedMeasured: boolean,
|
|
270
|
+
releases: readonly SpeedClaimsReleaseEvent[] | null,
|
|
271
|
+
now: number,
|
|
272
|
+
): SpeedClaimsDailyMeasurement {
|
|
273
|
+
const windowEnd = utcDayOf(now);
|
|
274
|
+
const startMs = Date.parse(`${windowEnd}T00:00:00.000Z`) - (DAILY_WINDOW_DAYS - 1) * DAY_MS;
|
|
275
|
+
const landedBuckets = dailyBuckets(landed);
|
|
276
|
+
const releaseBuckets = releases === null
|
|
277
|
+
? null
|
|
278
|
+
: dailyBuckets(releases.map((event) => ({ key: event.version.trim().replace(/^v(?=\d)/, ''), ts: parseTime(event.ts) })));
|
|
279
|
+
const landedComplete = landedMeasured && landedBuckets.undated === 0;
|
|
280
|
+
const releasesComplete = releaseBuckets !== null && releaseBuckets.undated === 0;
|
|
281
|
+
return {
|
|
282
|
+
scope: 'last-30-days', windowStart: utcDayOf(startMs), windowEnd,
|
|
283
|
+
landedPrs: { status: landedComplete ? 'measured' : 'unmeasured', undatedCount: landedBuckets.undated },
|
|
284
|
+
releases: { status: releasesComplete ? 'measured' : 'unmeasured', undatedCount: releaseBuckets?.undated ?? 0 },
|
|
285
|
+
rows: Array.from({ length: DAILY_WINDOW_DAYS }, (_, index) => {
|
|
286
|
+
const date = utcDayOf(startMs + index * DAY_MS);
|
|
287
|
+
return {
|
|
288
|
+
date,
|
|
289
|
+
landedPrCount: landedComplete ? (landedBuckets.counts.get(date) ?? 0) : null,
|
|
290
|
+
releasedCount: releasesComplete ? (releaseBuckets!.counts.get(date) ?? 0) : null,
|
|
291
|
+
};
|
|
292
|
+
}),
|
|
293
|
+
note: DAILY_NOTE,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** Release manifests record the actual publication time; lightweight tags have no creation timestamp. */
|
|
298
|
+
function readReleaseEventsFromLedger(): SpeedClaimsReleaseEvent[] {
|
|
299
|
+
const root = join(releaseLedgerRoot(), 'release');
|
|
300
|
+
const versions = readdirSync(root).filter((version) => VERSION_TAG.test(version));
|
|
301
|
+
const events: SpeedClaimsReleaseEvent[] = [];
|
|
302
|
+
for (const version of versions) {
|
|
303
|
+
// Absence and corruption are both unmeasured; only a readable manifest proves preparation.
|
|
304
|
+
const value: unknown = JSON.parse(readFileSync(join(root, version, 'release.json'), 'utf8')) as unknown;
|
|
305
|
+
const record = object(value);
|
|
306
|
+
if (record?.version !== version) throw new Error(`release manifest version mismatch: ${version}`);
|
|
307
|
+
if (!Object.hasOwn(record, 'publishedAt')) continue; // Prepared, not yet published.
|
|
308
|
+
if (typeof record.publishedAt !== 'string' || parseTime(record.publishedAt) === null) throw new Error(`invalid release publication time: ${version}`);
|
|
309
|
+
events.push({ version, ts: record.publishedAt });
|
|
310
|
+
}
|
|
311
|
+
// A checkout without tags or a release history cannot establish a queried zero.
|
|
312
|
+
if (versions.length === 0) throw new Error('release publication history unavailable');
|
|
313
|
+
return events;
|
|
314
|
+
}
|
|
315
|
+
|
|
195
316
|
/** Parent launch-to-done census; no child ledger id is used as a join key. */
|
|
196
317
|
export function measureParentSpeedClaims(
|
|
197
318
|
events: readonly ParentSpeedClaimsEvent[], queue?: unknown,
|
|
@@ -262,7 +383,13 @@ export function measureParentSpeedClaims(
|
|
|
262
383
|
|
|
263
384
|
/** Read-only elapsed-time census of canonical local run ledgers, including unsuccessful runs. */
|
|
264
385
|
export function measureSpeedClaims(options: SpeedClaimsOptions = {}): SpeedClaimsMeasurement {
|
|
386
|
+
const now = options.now ?? Date.now();
|
|
265
387
|
const ledgerDirectory = resolve(options.dir ?? runLedgerDir());
|
|
388
|
+
let releaseEvents: readonly SpeedClaimsReleaseEvent[] | null = null;
|
|
389
|
+
try {
|
|
390
|
+
const events = (options.readReleaseEvents ?? readReleaseEventsFromLedger)();
|
|
391
|
+
releaseEvents = Array.isArray(events) ? events : null;
|
|
392
|
+
} catch { /* An unreadable release source marks releases unmeasured; it does not erase landed-PR observations. */ }
|
|
266
393
|
let parent: ParentSpeedClaimsMeasurement;
|
|
267
394
|
try {
|
|
268
395
|
const events = (options.readParentEvents ?? readParentEventsFromLogs)();
|
|
@@ -272,21 +399,27 @@ export function measureSpeedClaims(options: SpeedClaimsOptions = {}): SpeedClaim
|
|
|
272
399
|
} catch {
|
|
273
400
|
parent = { ...measureParentSpeedClaims([]), status: 'cannot-measure' };
|
|
274
401
|
}
|
|
275
|
-
const result: SpeedClaimsMeasurement = {
|
|
402
|
+
const result: Omit<SpeedClaimsMeasurement, 'daily'> = {
|
|
276
403
|
scope: 'self-implement-run-ledger', ledgerDirectory, status: 'measured',
|
|
277
404
|
completedCount: 0, inProgressCount: 0, cannotMeasureCount: 0, unknownSeatCount: 0, rows: [], note: NOTE, parent,
|
|
278
405
|
};
|
|
279
406
|
let files: string[];
|
|
280
407
|
try { files = (options.list ?? readdirSync)(ledgerDirectory); }
|
|
281
|
-
catch { return { ...result, status: 'cannot-measure' }; }
|
|
408
|
+
catch { return { ...result, status: 'cannot-measure', daily: measureDailySpeedClaims([], false, releaseEvents, now) }; }
|
|
282
409
|
|
|
410
|
+
const landed: DailyObservation[] = [];
|
|
411
|
+
let ledgerUnreadable = false;
|
|
283
412
|
const groups = new Map<string, { seat: SpeedClaimsSeat; outcome: SpeedClaimsOutcome; values: number[] }>();
|
|
284
413
|
for (const file of files.sort()) {
|
|
285
414
|
const runId = file.endsWith('.jsonl') ? file.slice(0, -'.jsonl'.length) : '';
|
|
286
415
|
if (!CANONICAL_RUN_ID.test(runId)) continue;
|
|
287
416
|
try {
|
|
288
417
|
const entries = loadRunLedger(runId, ledgerDirectory, options.read);
|
|
289
|
-
if (!entries || entries.length === 0) { result.cannotMeasureCount++; continue; }
|
|
418
|
+
if (!entries || entries.length === 0) { ledgerUnreadable = true; result.cannotMeasureCount++; continue; }
|
|
419
|
+
const merges = entries.filter((entry) => entry.event === 'merged' && entry.data.merged === true);
|
|
420
|
+
const numbered = new Set(merges.map((entry) => landedPrIdentity(entry.data, runId)).filter((key) => key !== runId));
|
|
421
|
+
if (numbered.size > 1) ledgerUnreadable = true; // Conflicting PR identities cannot establish a daily count.
|
|
422
|
+
else for (const entry of merges) landed.push({ key: numbered.size === 1 ? [...numbered][0]! : runId, ts: parseTime(entry.timestamp) });
|
|
290
423
|
const end = endOfRun(entries);
|
|
291
424
|
const first = time(entries[0]);
|
|
292
425
|
const createdAt = checkpointFor(runId, ledgerDirectory)?.createdAt;
|
|
@@ -306,14 +439,14 @@ export function measureSpeedClaims(options: SpeedClaimsOptions = {}): SpeedClaim
|
|
|
306
439
|
group.values.push((endTime! - start) / 60_000);
|
|
307
440
|
groups.set(key, group);
|
|
308
441
|
result.completedCount++;
|
|
309
|
-
} catch { result.cannotMeasureCount++; }
|
|
442
|
+
} catch { result.cannotMeasureCount++; ledgerUnreadable = true; }
|
|
310
443
|
}
|
|
311
444
|
result.rows = [...groups.values()].map(({ seat, outcome, values }) => {
|
|
312
445
|
values.sort((a, b) => a - b);
|
|
313
446
|
return { seat, outcome, count: values.length, underOneMinuteCount: values.filter((minutes) => minutes < 1).length, medianMinutes: percentile(values, 0.5), q1Minutes: percentile(values, 0.25), q3Minutes: percentile(values, 0.75) };
|
|
314
447
|
}).sort((a, b) => SEATS.indexOf(a.seat) - SEATS.indexOf(b.seat) || a.outcome.localeCompare(b.outcome));
|
|
315
448
|
if (result.cannotMeasureCount > 0) result.status = 'cannot-measure';
|
|
316
|
-
return result;
|
|
449
|
+
return { ...result, daily: measureDailySpeedClaims(landed, !ledgerUnreadable, releaseEvents, now) };
|
|
317
450
|
}
|
|
318
451
|
|
|
319
452
|
export function renderSpeedClaims(result: SpeedClaimsMeasurement): string {
|
|
@@ -334,5 +467,11 @@ export function renderSpeedClaims(result: SpeedClaimsMeasurement): string {
|
|
|
334
467
|
`parent cannot measure (excluded): ${result.parent.cannotMeasureCount}`,
|
|
335
468
|
...result.parent.rows.map((row) => `seat=${row.seat} stage=${row.stage} merged=${row.merged} count=${row.count} median=${row.medianMinutes}m q1=${row.q1Minutes}m q3=${row.q3Minutes}m`),
|
|
336
469
|
`parent note: ${result.parent.note}`,
|
|
470
|
+
'daily landed PRs / issued releases (last 30 days, UTC):',
|
|
471
|
+
`daily window: ${result.daily.windowStart}..${result.daily.windowEnd}`,
|
|
472
|
+
`landed PRs status: ${result.daily.landedPrs.status} (undated: ${result.daily.landedPrs.undatedCount})`,
|
|
473
|
+
`issued releases status: ${result.daily.releases.status} (undated: ${result.daily.releases.undatedCount})`,
|
|
474
|
+
...result.daily.rows.map((row) => `date=${row.date} landed=${row.landedPrCount ?? 'unmeasured'} released=${row.releasedCount ?? 'unmeasured'}`),
|
|
475
|
+
`daily note: ${result.daily.note}`,
|
|
337
476
|
].join('\n');
|
|
338
477
|
}
|
|
@@ -212,6 +212,26 @@ function groundingMessages(goal: string, terms: readonly string[]): LLMMessage[]
|
|
|
212
212
|
}];
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
function unreadEvidencePaths(evidence: string, readFiles: ReadonlySet<string>): string[] {
|
|
216
|
+
return Array.from(new Set(evidence.split(/\r?\n/)
|
|
217
|
+
.flatMap(repositoryRelativeCodePaths)
|
|
218
|
+
.filter((path) => isCodeRootPath(path) && !readFiles.has(path)))).slice(0, 8);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function rejectedEvidenceFeedback(evidence: string, readFiles: ReadonlySet<string>): LLMMessage {
|
|
222
|
+
const unread = unreadEvidencePaths(evidence, readFiles);
|
|
223
|
+
const read = Array.from(readFiles).slice(0, 8);
|
|
224
|
+
return {
|
|
225
|
+
role: 'user',
|
|
226
|
+
content: [
|
|
227
|
+
'Your completion evidence was rejected because it cited a repository path that Read did not verify.',
|
|
228
|
+
`Unverified paths: ${unread.length ? unread.join(', ') : '(none in allowed code roots)'}`,
|
|
229
|
+
`Paths actually Read: ${read.length ? read.join(', ') : '(none)'}`,
|
|
230
|
+
'Try once more. Read each relevant unverified path before citing it, or remove it from the evidence. Cite only repository-relative code paths that this grounding run actually Read, each with a concrete reason. If no path is relevant, do not invent evidence.',
|
|
231
|
+
].join('\n'),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
215
235
|
/** Bounded, shared, read-only grounding loop. Failure or no candidates returns null. */
|
|
216
236
|
export async function groundPersistently(goal: string, cwd: string, deps: PersistentGroundingDeps = {}): Promise<PersistentGroundingResult | null> {
|
|
217
237
|
const startedAt = performance.now();
|
|
@@ -250,20 +270,53 @@ export async function groundPersistently(goal: string, cwd: string, deps: Persis
|
|
|
250
270
|
},
|
|
251
271
|
};
|
|
252
272
|
const options: GoalLoopOptions = { maxIterations, objective: `Persistent repository grounding: ${goal.slice(0, 200)}` };
|
|
253
|
-
const
|
|
254
|
-
const
|
|
273
|
+
const first = await loop(ctx, options);
|
|
274
|
+
const firstVerified = first.goalComplete && first.stopReason === 'goal_complete'
|
|
255
275
|
? verifiedEvidenceFromCompletion(completionEvidence, readFiles)
|
|
256
276
|
: {
|
|
257
277
|
files: [],
|
|
258
278
|
evidence: [],
|
|
259
279
|
rejection: { noPath: 0, outsideRoot: 0, notRead: 0, noMeaningfulText: 0, samples: [] },
|
|
260
280
|
};
|
|
281
|
+
let result = first;
|
|
282
|
+
let verified = firstVerified;
|
|
283
|
+
let feedbackRetry = false;
|
|
284
|
+
let retryIterations = 0;
|
|
285
|
+
if (firstVerified.rejection.notRead > 0) {
|
|
286
|
+
feedbackRetry = true;
|
|
287
|
+
const feedback = rejectedEvidenceFeedback(completionEvidence, readFiles);
|
|
288
|
+
completionEvidence = '';
|
|
289
|
+
try {
|
|
290
|
+
const retry = await loop({ ...ctx, sessionId: `${sessionId}-evidence-retry`, messages: [...ctx.messages, feedback] },
|
|
291
|
+
{ ...options, maxIterations: 2 });
|
|
292
|
+
retryIterations = retry.iterations;
|
|
293
|
+
const retryVerified = retry.goalComplete && retry.stopReason === 'goal_complete'
|
|
294
|
+
? verifiedEvidenceFromCompletion(completionEvidence, readFiles)
|
|
295
|
+
: null;
|
|
296
|
+
if (retryVerified) {
|
|
297
|
+
verified = {
|
|
298
|
+
files: Array.from(new Set([...firstVerified.files, ...retryVerified.files])),
|
|
299
|
+
evidence: Array.from(new Set([...firstVerified.evidence, ...retryVerified.evidence])),
|
|
300
|
+
rejection: retryVerified.rejection,
|
|
301
|
+
};
|
|
302
|
+
result = retry;
|
|
303
|
+
}
|
|
304
|
+
} catch (error) {
|
|
305
|
+
try {
|
|
306
|
+
debug.log('grounding.persistent', 'evidence-retry-failed', { sessionId,
|
|
307
|
+
reason: error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200) }, { level: 'warn' });
|
|
308
|
+
} catch {
|
|
309
|
+
// A failed retry and its telemetry cannot erase evidence already verified by Read.
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
const totalIterations = first.iterations + retryIterations;
|
|
261
314
|
try {
|
|
262
315
|
debug.log('grounding.persistent', 'finished', {
|
|
263
316
|
sessionId,
|
|
264
317
|
maxIterations,
|
|
265
318
|
durationMs: durationMs(),
|
|
266
|
-
iterations:
|
|
319
|
+
iterations: totalIterations,
|
|
267
320
|
stopReason: result.stopReason,
|
|
268
321
|
// ⛔ 「끝났다」가 두 뜻이다 — ⓐ 스스로 목표를 끝냈다 ⓑ 턴이 «그냥» 끝났다.
|
|
269
322
|
// 📏 2026-08-11 72차 전수(400건): 후보 0인 finished 가 5건이고 ***다섯 다 end_turn*** 이었다.
|
|
@@ -273,6 +326,8 @@ export async function groundPersistently(goal: string, cwd: string, deps: Persis
|
|
|
273
326
|
candidateCount: verified.files.length,
|
|
274
327
|
evidenceCount: verified.evidence.length,
|
|
275
328
|
evidenceRejection: verified.rejection,
|
|
329
|
+
feedbackRetry,
|
|
330
|
+
...(feedbackRetry ? { firstEvidenceRejection: firstVerified.rejection } : {}),
|
|
276
331
|
toolCalls,
|
|
277
332
|
});
|
|
278
333
|
} catch {
|
|
@@ -281,7 +336,7 @@ export async function groundPersistently(goal: string, cwd: string, deps: Persis
|
|
|
281
336
|
return {
|
|
282
337
|
files: verified.files,
|
|
283
338
|
evidence: verified.evidence,
|
|
284
|
-
iterations:
|
|
339
|
+
iterations: totalIterations,
|
|
285
340
|
stopReason: result.stopReason,
|
|
286
341
|
};
|
|
287
342
|
} catch (error) {
|
|
@@ -31,9 +31,13 @@ import {
|
|
|
31
31
|
runShell as runnerDispatch,
|
|
32
32
|
getShellRunnerDeps,
|
|
33
33
|
} from '../../shell-runner/dispatch.js';
|
|
34
|
-
import type { ShellResult as RunnerResult } from '../../shell-runner/types.js';
|
|
34
|
+
import type { ShellHandle, ShellResult as RunnerResult } from '../../shell-runner/types.js';
|
|
35
35
|
import { debug } from '../../debug/log.js';
|
|
36
36
|
import { getSessionCwd } from '../../session/working-dir.js';
|
|
37
|
+
import {
|
|
38
|
+
globalTaskNotificationQueue,
|
|
39
|
+
type TaskNotificationQueue,
|
|
40
|
+
} from '../../agent/task-notification.js';
|
|
37
41
|
|
|
38
42
|
/** LLMToolSpec — the shape surfaced to the model. Kept narrow so
|
|
39
43
|
* accidental misuse is hard: no env override, no cwd outside the
|
|
@@ -178,6 +182,62 @@ function logShellDispatch(event: string, data: Record<string, unknown>): void {
|
|
|
178
182
|
debug.log('shell.dispatch', event, data);
|
|
179
183
|
}
|
|
180
184
|
|
|
185
|
+
// ── bg completion → turn notification queue (TUI-BG-SHELL) ───────────
|
|
186
|
+
// When a bg-mode shell handle settles, its result is no longer awaited
|
|
187
|
+
// by anyone — the spawn already returned the `outcome=started` stub.
|
|
188
|
+
// To surface the eventual result we enqueue one task-notification into
|
|
189
|
+
// the existing turn notification queue (injected at turn-start by
|
|
190
|
+
// chat/skill-runner). One notice per handle; opening an idle turn from
|
|
191
|
+
// the wake signal is deliberately NOT wired here (TUI-EVENT-TURN-QUEUE).
|
|
192
|
+
|
|
193
|
+
export interface ShellCompletionNoticeInput {
|
|
194
|
+
handle: ShellHandle;
|
|
195
|
+
ok: boolean;
|
|
196
|
+
exitCode: number | null;
|
|
197
|
+
durationMs: number;
|
|
198
|
+
/** Set when ok === false — the runner's failure reason. */
|
|
199
|
+
errorMessage?: string;
|
|
200
|
+
/** Caller-supplied free-form label (RunShell's `description` arg). */
|
|
201
|
+
label?: string;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Per-handle dedup guard — module-local, keyed by the handle OBJECT.
|
|
205
|
+
* A registry replacement may mint a NEW handle that reuses an id; the
|
|
206
|
+
* shell layer must not swallow that fresh completion as a duplicate
|
|
207
|
+
* settle (an id-keyed record here would). */
|
|
208
|
+
const notifiedHandles = new WeakSet<ShellHandle>();
|
|
209
|
+
let nextShellCompletionNoticeId = 0;
|
|
210
|
+
|
|
211
|
+
/** Enqueue one bg shell-handle completion notice. Returns true when this
|
|
212
|
+
* handle had not notified before (the notice was handed to `queue`),
|
|
213
|
+
* false on a duplicate settle of the same handle. The queue's own
|
|
214
|
+
* The shared queue deduplicates by taskId even after drain, so each
|
|
215
|
+
* completed handle gets its own notification id. The original handle id
|
|
216
|
+
* remains in the output for ShellPoll and human follow-up.
|
|
217
|
+
* `queue` defaults to the global turn notification queue; injectable
|
|
218
|
+
* so tests can isolate. */
|
|
219
|
+
export function enqueueShellCompletionNotice(
|
|
220
|
+
input: ShellCompletionNoticeInput,
|
|
221
|
+
queue: TaskNotificationQueue = globalTaskNotificationQueue,
|
|
222
|
+
): boolean {
|
|
223
|
+
if (notifiedHandles.has(input.handle)) return false;
|
|
224
|
+
notifiedHandles.add(input.handle);
|
|
225
|
+
const header = `RunShell bg handle=${input.handle.id} ${input.ok ? 'completed' : 'failed'}`;
|
|
226
|
+
const output = `${header}${input.ok || input.exitCode !== null ? ` exitCode=${input.exitCode ?? 'null'}` : ''} elapsed=${input.durationMs}ms${input.errorMessage ? ` error=${input.errorMessage}` : ''}`;
|
|
227
|
+
queue.enqueue({
|
|
228
|
+
taskId: `shell:${input.handle.id}:${++nextShellCompletionNoticeId}`,
|
|
229
|
+
agentName: 'RunShell',
|
|
230
|
+
...(input.label ? { label: input.label } : {}),
|
|
231
|
+
state: input.ok ? 'done' : 'error',
|
|
232
|
+
output,
|
|
233
|
+
truncated: false,
|
|
234
|
+
...(input.errorMessage ? { errorMessage: input.errorMessage } : {}),
|
|
235
|
+
durationMs: input.durationMs,
|
|
236
|
+
finishedAt: Date.now(),
|
|
237
|
+
});
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
181
241
|
export async function dispatchRunShell(
|
|
182
242
|
rawArgs: Record<string, unknown>,
|
|
183
243
|
opts: { signal?: AbortSignal } = {},
|
|
@@ -269,6 +329,16 @@ async function dispatchViaRunner(args: {
|
|
|
269
329
|
exitCode: result.exitCode ?? null,
|
|
270
330
|
durationMs: result.durationMs,
|
|
271
331
|
});
|
|
332
|
+
const ok = result.outcome === 'exit' && result.exitCode === 0;
|
|
333
|
+
enqueueShellCompletionNotice({
|
|
334
|
+
handle,
|
|
335
|
+
ok,
|
|
336
|
+
exitCode: result.exitCode ?? null,
|
|
337
|
+
durationMs: result.durationMs,
|
|
338
|
+
...(!ok ? { errorMessage: result.exitCode !== undefined
|
|
339
|
+
? `exitCode=${result.exitCode}` : `outcome=${result.outcome}` } : {}),
|
|
340
|
+
...(args.description !== undefined ? { label: args.description } : {}),
|
|
341
|
+
});
|
|
272
342
|
},
|
|
273
343
|
(err: unknown) => {
|
|
274
344
|
debug.log('skill-tool.shell', 'bg-finished', {
|
|
@@ -277,6 +347,14 @@ async function dispatchViaRunner(args: {
|
|
|
277
347
|
durationMs: Date.now() - bgStartedAt,
|
|
278
348
|
error: err instanceof Error ? err.message : String(err),
|
|
279
349
|
});
|
|
350
|
+
enqueueShellCompletionNotice({
|
|
351
|
+
handle,
|
|
352
|
+
ok: false,
|
|
353
|
+
exitCode: null,
|
|
354
|
+
durationMs: Date.now() - bgStartedAt,
|
|
355
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
356
|
+
...(args.description !== undefined ? { label: args.description } : {}),
|
|
357
|
+
});
|
|
280
358
|
},
|
|
281
359
|
);
|
|
282
360
|
return {
|
|
@@ -19,6 +19,7 @@ import { debug } from '../debug/log.js';
|
|
|
19
19
|
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
20
20
|
import { spawnElanous } from '../instance/spawn-elanous.js';
|
|
21
21
|
import { getUserConfig } from '../user-config.js';
|
|
22
|
+
import { resyncPendingPath } from './resync-tick.js';
|
|
22
23
|
|
|
23
24
|
export const REJUDGE_TICK_EVENT = 'rejudge-tick';
|
|
24
25
|
export const REJUDGE_DEFAULT_MAX_LOAD = 60;
|
|
@@ -49,6 +50,7 @@ export interface RejudgeTickDeps {
|
|
|
49
50
|
config?: () => { rejudgeEveryMinutes?: number; rejudgeMaxLoad?: number } | undefined;
|
|
50
51
|
load?: () => number;
|
|
51
52
|
stampPath?: string;
|
|
53
|
+
pendingPath?: string;
|
|
52
54
|
run?: () => Promise<RejudgeRun>;
|
|
53
55
|
log?: (event: string, data: Record<string, unknown>) => void;
|
|
54
56
|
}
|
|
@@ -110,7 +112,9 @@ export async function rejudgeTick(deps: RejudgeTickDeps = {}): Promise<RejudgeTi
|
|
|
110
112
|
const now = (deps.now ?? (() => new Date()))();
|
|
111
113
|
const stampPath = deps.stampPath ?? defaultStampPath();
|
|
112
114
|
const lastStartedAt = readStamp(stampPath);
|
|
113
|
-
|
|
115
|
+
const pendingAt = readPending(deps.pendingPath ?? resyncPendingPath());
|
|
116
|
+
const savedSinceLastRun = pendingAt !== null && (!lastStartedAt || Date.parse(pendingAt) > Date.parse(lastStartedAt));
|
|
117
|
+
if (!savedSinceLastRun && lastStartedAt && now.getTime() - Date.parse(lastStartedAt) < everyMinutes * 60_000 - REJUDGE_JITTER_MS) {
|
|
114
118
|
return emit({ decision: 'not-due', everyMinutes, lastStartedAt });
|
|
115
119
|
}
|
|
116
120
|
const maxLoad = positive(cfg?.rejudgeMaxLoad) ?? REJUDGE_DEFAULT_MAX_LOAD;
|
|
@@ -133,6 +137,13 @@ export async function rejudgeTick(deps: RejudgeTickDeps = {}): Promise<RejudgeTi
|
|
|
133
137
|
}
|
|
134
138
|
}
|
|
135
139
|
|
|
140
|
+
function readPending(path: string): string | null {
|
|
141
|
+
try {
|
|
142
|
+
const value = (JSON.parse(readFileSync(path, 'utf8')) as { savedAt?: unknown }).savedAt;
|
|
143
|
+
return typeof value === 'string' && Number.isFinite(Date.parse(value)) ? value : null;
|
|
144
|
+
} catch { return null; }
|
|
145
|
+
}
|
|
146
|
+
|
|
136
147
|
if (import.meta.main) {
|
|
137
148
|
try { await (await import('../domains/standalone-log-sink.js')).registerStandaloneLogSink('steward'); }
|
|
138
149
|
catch (error) { console.error(`steward rejudge: log sink registration failed — ${error instanceof Error ? error.message : String(error)}`); }
|