elanous 0.2.15 → 0.2.17
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/{1307-33ffcc49ab45123b.js → 1307-aac7332576c4d6fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{6877-7c6208705c284124.js → 6877-fe94e76a5841ee9b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9103-7a3ae7f60af3b99f.js → 9103-60fbef33e66f0a46.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/{page-0e2c57fe6add4b2a.js → page-3143b07a817d1da9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-e05a77df7e005b04.js → page-17ba086ef82d339b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-7eb661ff23af7da2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-b8c49f6b4fa2df91.js → page-6e5c3a867557298b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-7110f732f45bf944.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-976813c91d7394ea.js → page-9a41d65355fc25b9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-5e77cd1802567c89.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-1d4901eb7c2d8822.js → page-41afec0b255ed2bb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-72d3e7ff35a8c954.js → page-4187666132fc98c4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-60c7a8e1a4fe62ce.js → page-09328d499162bae4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-2a52b316cb143d9c.js → page-29ff3a8f84f03b6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-c3ce483c8d5db370.js → page-80ec7401bc83f511.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ab93b240f687d69a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-96f074f6538e8a2c.js → page-fc7d86467291ae38.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-e97be5334d83a9f5.js → layout-de43a186e5ba5683.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-8b08cafad27759c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-0e2d6985fb42cd2f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-13e4108999f58d13.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e8296ee284aa1293.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-c1343f9499d86f3f.js → page-043a9111e74f6b24.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-8739a2e0ae793130.js → not-found-23a4601ceb7f452e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-d23b12bb92242558.js → page-10d958605a77b3bd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-166745e51ac0c7b0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-c7fea475488201a8.js → page-a04260b8ccc968d4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-7a1d85e121065cc8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-5efbf81124c474ae.js → page-12916918988ac97e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d8a3347a9d0c3f4a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-06ee266953d9c879.js → page-5a64cb5208c6a8fd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-153c2b3bcc89e8de.js → page-1407ec83ea64c1f9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-1d824510df025d18.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-0d6d23b40263a7ea.js → page-7f49fe645d5132f7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-81a4e7f8dbfe74cb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-27d71f131acd9320.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-73735b64bfe1ae67.js → page-424659bacfb8cdd7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-f7e631e9b4daf66e.js → page-fc2df261f8a2c7df.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-09461366af82ef13.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-9817c7f9007466cc.js → page-5d4fef9ccd7d406b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ea90a8ac73ce2749.js +1 -0
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +1 -0
- package/apps/pwa/out/_next/static/css/{04dabcbcc4bfa6db.css → 085673d826b98b62.css} +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +15 -15
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +15 -15
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +15 -15
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +15 -15
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +15 -15
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +15 -15
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +15 -15
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +15 -15
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +15 -15
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +15 -15
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +15 -15
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +15 -15
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +15 -15
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +15 -15
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +15 -15
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +15 -15
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +15 -15
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +15 -15
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +15 -15
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +15 -15
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +15 -15
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +15 -15
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +15 -15
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +15 -15
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +15 -15
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +15 -15
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +16 -16
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +15 -15
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +15 -15
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +15 -15
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +15 -15
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +15 -15
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +15 -15
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +15 -15
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +15 -15
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +15 -15
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +15 -15
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +15 -15
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +15 -15
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +15 -15
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +15 -15
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +15 -15
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +15 -15
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +15 -15
- package/graphs/doc-gen/doc-gen.yaml +24 -0
- package/graphs/doc-gen/recipes.yaml +3 -0
- package/graphs/intake/intake-daily.yaml +9 -2
- package/graphs/intake/news-sources.yaml +4 -0
- package/graphs/intake/recipes.yaml +6 -1
- package/graphs/orchestrator/orchestrator.yaml +7 -0
- package/graphs/orchestrator/recipes.yaml +3 -0
- package/graphs/steward/recipes.yaml +2 -1
- package/package.json +1 -1
- package/scripts/__fixtures__/intake-news.xml +7 -0
- package/scripts/ci-public-export-import-gate.ts +154 -0
- package/scripts/ci-test-interference-gate.ts +5 -1
- package/scripts/ci-typecheck-changed.ts +34 -15
- package/scripts/docs/doc-rot.ts +164 -0
- package/scripts/docs/generate-graph-anatomy.ts +131 -9
- package/scripts/docs-cli-check.ts +4 -4
- package/scripts/goals/goal-census.ts +343 -0
- package/scripts/intake-news.ts +169 -0
- package/scripts/measure/author-section-contribution.ts +84 -0
- package/scripts/release-loop/cut-branch.ts +43 -13
- package/scripts/release-loop/docs-land-node.ts +69 -10
- package/scripts/release-loop/gate-node.ts +3 -1
- package/scripts/release-loop/publish-node.ts +2 -2
- package/scripts/release-loop/release-docs.ts +134 -0
- package/scripts/release-loop/vault-note-node.ts +19 -2
- package/scripts/release-loop/version-node.ts +7 -4
- package/scripts/release-story/draft.ts +42 -14
- package/scripts/rhythm/monthly.ts +97 -0
- package/src/acp/client.ts +17 -2
- package/src/agent-mission/driver.ts +104 -11
- package/src/agent-mission/supervisor-metrics-cli.ts +40 -0
- package/src/agent-mission/supervisor-metrics.ts +218 -0
- package/src/autopilot/track-agent-shadow.ts +2 -1
- package/src/autopilot/track-agent.ts +5 -3
- package/src/briefing/brief-items.ts +191 -36
- package/src/budget/usage-outcomes.ts +204 -0
- package/src/cli/autopilot-cli.ts +66 -0
- package/src/cli/brief-cli.ts +73 -0
- package/src/cli/decisions-cli.ts +4 -1
- package/src/cli/intake-cli.ts +1 -1
- package/src/cli/know-cli.ts +20 -0
- package/src/cli/log-event-names.ts +1 -0
- package/src/cli/logs-cli.ts +43 -1
- package/src/cli/pr-cli.ts +92 -13
- package/src/cli/provider-cli.ts +14 -0
- package/src/cli/release-cli.ts +11 -8
- package/src/cli/self-update-canary.ts +46 -0
- package/src/cli/self-update.ts +399 -1
- package/src/cli/usage-cli.ts +26 -0
- package/src/context-card/index.ts +45 -0
- package/src/context-card/task.ts +76 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -0
- package/src/decisions/decision-ledger.ts +10 -1
- package/src/discord-self-message.ts +9 -0
- package/src/flow-loop/split.ts +24 -0
- package/src/harness/harness-cli-command.ts +178 -12
- package/src/harness/harness-pod-dispatch.ts +36 -55
- package/src/harness/harness-queue.ts +86 -7
- package/src/harness/harness-seams.ts +13 -3
- package/src/harness/helper-repair.ts +139 -8
- package/src/harness/helper-scan.ts +13 -2
- package/src/harness/nested-elanous-policy.ts +133 -0
- package/src/hq/fence-wrapper.ts +123 -0
- package/src/hq/hq.ts +16 -2
- package/src/hq/lease.ts +5 -3
- package/src/hq/transfer.ts +185 -0
- package/src/index.ts +168 -13
- package/src/intake-plane/digest.ts +85 -28
- package/src/knowledge/know-find.ts +105 -0
- package/src/llm.ts +75 -19
- package/src/loops/orchestrator/finish-rate.ts +14 -3
- package/src/loops/orchestrator/tick.ts +96 -25
- package/src/maturity/bot-slash-catalog.ts +11 -2
- package/src/maturity/feature-maturity.ts +2 -2
- package/src/maturity/tui-slash-availability.ts +9 -0
- package/src/nexus/api/grid.ts +61 -0
- package/src/nexus/api/http-server.ts +19 -1
- package/src/nexus/api/task-cards-api.ts +19 -0
- package/src/oauth/codex.ts +82 -2
- package/src/oauth/overload-failover-launch.ts +75 -0
- package/src/oauth/overload-failover.ts +137 -0
- package/src/release-loop/checklist.ts +10 -5
- package/src/release-loop/feature-store.ts +10 -9
- package/src/release-loop/landing-freeze.ts +18 -10
- package/src/release-loop/placement.ts +17 -8
- package/src/seat-dispatch/seat-ask.ts +33 -6
- package/src/seat-loop/seat-loop.ts +172 -21
- package/src/self-dev/ask-launch-flow.ts +48 -8
- package/src/self-dev/authored-goal-trace.ts +64 -0
- package/src/self-dev/dev-cli.ts +19 -1
- package/src/self-dev/dev-pipeline.ts +6 -5
- package/src/self-dev/entrance-registry.ts +10 -6
- package/src/self-dev/old-door.ts +73 -0
- package/src/self-dev/run-store.ts +5 -1
- package/src/self-dev/run-supervisor.ts +7 -1
- package/src/self-dev/supervisor-verdict-edges.ts +46 -0
- package/src/self-implement/app-api-budget.ts +166 -0
- package/src/self-implement/budget-gate.ts +7 -5
- package/src/self-implement/follow-up-goals.ts +289 -0
- package/src/self-implement/frozen-merges.ts +25 -20
- package/src/self-implement/goal-author-clarification.ts +81 -0
- package/src/self-implement/goal-author-cli.ts +33 -5
- package/src/self-implement/goal-author.ts +107 -25
- package/src/self-implement/goal-clarification-escalation.ts +37 -1
- package/src/self-implement/goal-document-write.ts +28 -0
- package/src/self-implement/heal-intake.ts +4 -0
- package/src/self-implement/heal-triage.ts +44 -0
- package/src/self-implement/host-regate.ts +3 -1
- package/src/self-implement/orchestrator.ts +157 -8
- package/src/self-implement/run-ledger.ts +4 -0
- package/src/self-implement/seams.ts +44 -15
- package/src/session-runtime/index.ts +2 -0
- package/src/session-runtime/retry-api.ts +87 -0
- package/src/session-runtime/retry-policy.ts +107 -6
- package/src/steward/working-backwards.ts +16 -1
- package/src/task-agent/actions.ts +118 -0
- package/src/task-agent/judge.ts +96 -0
- package/src/task-cards/card-cli.ts +25 -0
- package/src/task-cards/card-store.ts +9 -4
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +33 -4
- package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +4 -3
- package/src/task-orchestrator/surfaces/pod-ledger-prod-sink.ts +75 -0
- package/src/task-orchestrator/surfaces/pod-pool.ts +95 -18
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +37 -1
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +168 -37
- package/src/task-orchestrator/surfaces/self-implement.ts +2 -0
- package/src/telegram-commands.ts +6 -1
- package/src/telegram-loops-command.ts +21 -14
- package/src/telegram.ts +47 -0
- package/src/user-config.ts +59 -4
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9247-29d889b809288e65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-28b9448091776c06.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-955ec2da3be4c560.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e2ee2896811e98d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f1a1fc0a82738c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-da6c08aae0f92768.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-87a11e6a05f15199.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-7d4182964407c09c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-f5597e894c02cd33.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8fcec4c9ea2e7f70.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-97f9f43172a3a90e.js +0 -1
- package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.css +0 -1
- package/src/launch-head/seat-goal-queue-lock.fixture.ts +0 -15
- package/src/launch-head/seat-goal-queue.ts +0 -198
- /package/apps/pwa/out/_next/static/{LZcstaO9_NXypPFmujolv → yRA5ur6DcPl0YHSRheH2O}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{LZcstaO9_NXypPFmujolv → yRA5ur6DcPl0YHSRheH2O}/_ssgManifest.js +0 -0
|
@@ -364,7 +364,61 @@ export function decideProviderFallback(input: {
|
|
|
364
364
|
return { action: 'advance', nextProvider, category, attempts };
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
-
|
|
367
|
+
/** Consecutive overload/5xx/rate-limit outcomes that switch the child off codex
|
|
368
|
+
* before the same provider spends its remaining retries. Other errors (400)
|
|
369
|
+
* never count. Recovery is this many later non-overload outcomes. */
|
|
370
|
+
export const OVERLOAD_FAILOVER_STREAK = 3;
|
|
371
|
+
|
|
372
|
+
export type LlmCallOutcomeKind = 'ok' | 'overloaded' | '5xx' | 'rate-limit' | 'other';
|
|
373
|
+
|
|
374
|
+
/** One finished LLM call. Launch failover reads only this shape. */
|
|
375
|
+
export interface LlmCallOutcome {
|
|
376
|
+
readonly provider: string;
|
|
377
|
+
readonly status: number;
|
|
378
|
+
readonly kind: LlmCallOutcomeKind;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const CODEX_CALL_PROVIDERS = new Set(['codex', 'openai-codex']);
|
|
382
|
+
|
|
383
|
+
export function llmCallOutcomeKind(status: number, message = ''): LlmCallOutcomeKind {
|
|
384
|
+
if (status >= 200 && status < 300) return 'ok';
|
|
385
|
+
const msg = message.toLowerCase();
|
|
386
|
+
if (status === 429 || msg.includes('rate limit') || msg.includes('too many requests')) return 'rate-limit';
|
|
387
|
+
if (
|
|
388
|
+
status === 500 || status === 502 || status === 503 || status === 504 ||
|
|
389
|
+
msg.includes('overloaded') || msg.includes('server_busy') || msg.includes('server_error')
|
|
390
|
+
) {
|
|
391
|
+
return status >= 500 ? '5xx' : 'overloaded';
|
|
392
|
+
}
|
|
393
|
+
if (msg.includes('overloaded')) return 'overloaded';
|
|
394
|
+
return 'other';
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export function isOverloadOutcomeKind(kind: LlmCallOutcomeKind): boolean {
|
|
398
|
+
return kind === 'overloaded' || kind === '5xx' || kind === 'rate-limit';
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** True when the newest `limit` codex outcomes are all overload-class.
|
|
402
|
+
* A non-codex row or a non-overload row breaks the streak. Empty input is false. */
|
|
403
|
+
export function codexOverloadStreakReached(
|
|
404
|
+
outcomes: readonly LlmCallOutcome[],
|
|
405
|
+
limit = OVERLOAD_FAILOVER_STREAK,
|
|
406
|
+
): boolean {
|
|
407
|
+
if (limit < 1 || outcomes.length < limit) return false;
|
|
408
|
+
const tail = outcomes.slice(-limit);
|
|
409
|
+
return tail.every((row) => CODEX_CALL_PROVIDERS.has(row.provider) && isOverloadOutcomeKind(row.kind));
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** True when the newest `limit` outcomes are successful (any provider). */
|
|
413
|
+
export function overloadRecovered(
|
|
414
|
+
outcomes: readonly LlmCallOutcome[],
|
|
415
|
+
limit = OVERLOAD_FAILOVER_STREAK,
|
|
416
|
+
): boolean {
|
|
417
|
+
if (limit < 1 || outcomes.length < limit) return false;
|
|
418
|
+
return outcomes.slice(-limit).every((row) => row.kind === 'ok');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export type RetryAction = 'retry' | 'abort' | 'ask-user' | 'auto-undo' | 'switch-provider';
|
|
368
422
|
|
|
369
423
|
export interface RetryDecision {
|
|
370
424
|
action: RetryAction;
|
|
@@ -417,6 +471,16 @@ export interface RetryContext {
|
|
|
417
471
|
/** Optional: retry-after header string from a 429 response (ms or
|
|
418
472
|
* seconds or HTTP date). decideRetry parses it. */
|
|
419
473
|
retryAfter?: string;
|
|
474
|
+
/** Configured fallbacks still unused. When > 0, an overload-class
|
|
475
|
+
* failure that has already repeated `overloadStreak` times switches
|
|
476
|
+
* provider instead of spending another retry on the same one.
|
|
477
|
+
* Omitted or 0 keeps the historical retry-until-exhausted behaviour. */
|
|
478
|
+
remainingFallbacks?: number;
|
|
479
|
+
/** Consecutive overload-class failures already seen for this call,
|
|
480
|
+
* including the one under decision. Default threshold is
|
|
481
|
+
* OVERLOAD_FAILOVER_STREAK. */
|
|
482
|
+
overloadStreak?: number;
|
|
483
|
+
overloadStreakLimit?: number;
|
|
420
484
|
}
|
|
421
485
|
|
|
422
486
|
/** Classify an error into a RetryCategory without making an action
|
|
@@ -519,10 +583,42 @@ export function classifyError(err: unknown): RetryCategory {
|
|
|
519
583
|
return 'unknown';
|
|
520
584
|
}
|
|
521
585
|
|
|
586
|
+
function isOverloadSwitchCandidate(msg: string, code: string | null): boolean {
|
|
587
|
+
return (
|
|
588
|
+
code === '429' || code === '500' || code === '502' || code === '503' || code === '504' ||
|
|
589
|
+
msg.includes('rate limit') || msg.includes('too many requests') ||
|
|
590
|
+
msg.includes('overloaded') || msg.includes('server_busy') || msg.includes('server_error')
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function overloadSwitchDecision(ctx: RetryContext): RetryDecision | undefined {
|
|
595
|
+
const remaining = ctx.remainingFallbacks ?? 0;
|
|
596
|
+
if (remaining <= 0) return undefined;
|
|
597
|
+
const limit = ctx.overloadStreakLimit ?? OVERLOAD_FAILOVER_STREAK;
|
|
598
|
+
const streak = ctx.overloadStreak ?? 1;
|
|
599
|
+
if (streak < limit) return undefined;
|
|
600
|
+
return {
|
|
601
|
+
action: 'switch-provider',
|
|
602
|
+
category: 'overloaded',
|
|
603
|
+
reason: `overload streak ${streak} with ${remaining} fallback(s) left; switch before spending retries`,
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
|
|
522
607
|
/** Pure verdict. Caller sleeps + retries or aborts per the returned
|
|
523
608
|
* action. Exponential backoff baseline: 500ms * 2^attempt, clamped
|
|
524
609
|
* to [500, 30_000]. */
|
|
525
610
|
export function decideRetry(err: unknown, ctx: RetryContext): RetryDecision {
|
|
611
|
+
const msg = extractMessage(err).toLowerCase();
|
|
612
|
+
const code = extractErrorCode(err);
|
|
613
|
+
|
|
614
|
+
// Overload with a fallback still unused switches before the doom
|
|
615
|
+
// window (k === 3) turns the same 503 into auto-undo. A 400 is not
|
|
616
|
+
// a candidate. Doom still wins for every other repeated failure.
|
|
617
|
+
if (isOverloadSwitchCandidate(msg, code)) {
|
|
618
|
+
const switched = overloadSwitchDecision(ctx);
|
|
619
|
+
if (switched) return switched;
|
|
620
|
+
}
|
|
621
|
+
|
|
526
622
|
// Doom-loop always wins — even a technically-retryable error
|
|
527
623
|
// becomes auto-undo after N identical failures. The caller may
|
|
528
624
|
// still degrade to ask-user when plan mode is active or when the
|
|
@@ -535,9 +631,6 @@ export function decideRetry(err: unknown, ctx: RetryContext): RetryDecision {
|
|
|
535
631
|
};
|
|
536
632
|
}
|
|
537
633
|
|
|
538
|
-
const msg = extractMessage(err).toLowerCase();
|
|
539
|
-
const code = extractErrorCode(err);
|
|
540
|
-
|
|
541
634
|
// Context window exhausted — never retry (the next call will also
|
|
542
635
|
// be over-sized). Abort and surface the error to the caller; upstream
|
|
543
636
|
// can trigger compaction.
|
|
@@ -589,8 +682,16 @@ export function decideRetry(err: unknown, ctx: RetryContext): RetryDecision {
|
|
|
589
682
|
};
|
|
590
683
|
}
|
|
591
684
|
|
|
592
|
-
// Overloaded / server busy.
|
|
593
|
-
if (
|
|
685
|
+
// Overloaded / server busy / 5xx.
|
|
686
|
+
if (
|
|
687
|
+
msg.includes('overloaded') ||
|
|
688
|
+
msg.includes('server_busy') ||
|
|
689
|
+
msg.includes('server_error') ||
|
|
690
|
+
code === '500' ||
|
|
691
|
+
code === '502' ||
|
|
692
|
+
code === '503' ||
|
|
693
|
+
code === '504'
|
|
694
|
+
) {
|
|
594
695
|
return {
|
|
595
696
|
action: 'retry',
|
|
596
697
|
delayMs: backoffMs(ctx.attempt),
|
|
@@ -57,7 +57,22 @@ export async function recordWorkingBackwardsOnCards(
|
|
|
57
57
|
debug.log('steward.working-backwards', 'skipped-launched-before-draft', { issueId: issue.identifier });
|
|
58
58
|
continue;
|
|
59
59
|
}
|
|
60
|
-
|
|
60
|
+
// An invalid draft for the same issue text is not re-asked every tick — each ask is a 70-100 s LLM call (10-05 ELA-29 timed the schedule stage out 3×).
|
|
61
|
+
const invalidKey = `hitl:draft-invalid-${createHash('sha256').update(JSON.stringify({ title: issue.title, body: issue.body ?? '' })).digest('hex').slice(0, 16)}`;
|
|
62
|
+
if (card.sections.some(section => section.key === invalidKey)) {
|
|
63
|
+
debug.log('steward.working-backwards', 'skipped-known-invalid', { issueId: issue.identifier });
|
|
64
|
+
throw new InvalidWorkingBackwardsDraft();
|
|
65
|
+
}
|
|
66
|
+
let draft: WorkingBackwardsDraft;
|
|
67
|
+
try { draft = await draftWorkingBackwards(issue, ask); }
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (error instanceof InvalidWorkingBackwardsDraft) {
|
|
70
|
+
// The memo is best-effort — failing to write it must not replace the invalid-draft error the caller routes to hitl.
|
|
71
|
+
try { store.appendSection(card.id, { key: invalidKey, owner: 'steward', content: 'working-backwards draft invalid for this issue text — not re-asked until the issue changes' }); }
|
|
72
|
+
catch (memoError) { debug.log('steward.working-backwards', 'invalid-memo-failed', { issueId: issue.identifier, error: memoError instanceof Error ? memoError.message : String(memoError) }); }
|
|
73
|
+
}
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
61
76
|
for (const [section, content] of [['prfaq', draft.prfaq], ['manual', draft.manual]] as const) {
|
|
62
77
|
const key = `${section}:${createHash('sha256').update(content).digest('hex')}`;
|
|
63
78
|
if (!card.sections.some(item => item.key === key)) store.appendSection(card.id, { key, owner: 'steward', content });
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
export type NextActionKind = 'review' | 'land' | 'retry' | 'green' | 'decision';
|
|
2
|
+
export interface NextAction {
|
|
3
|
+
kind: NextActionKind;
|
|
4
|
+
taskId: string;
|
|
5
|
+
rationale: string;
|
|
6
|
+
pr: number;
|
|
7
|
+
runId: string;
|
|
8
|
+
original: string;
|
|
9
|
+
checklistId: string;
|
|
10
|
+
history?: string[];
|
|
11
|
+
mustFix?: string[];
|
|
12
|
+
cwd?: string;
|
|
13
|
+
risk?: 'money' | 'security' | 'irreversible-publication';
|
|
14
|
+
}
|
|
15
|
+
export interface ActionDeps {
|
|
16
|
+
command(args: string[]): Promise<{ status: number; stdout: string; stderr?: string }>;
|
|
17
|
+
mode?: string;
|
|
18
|
+
landingsToday?: number;
|
|
19
|
+
landingDay?: string;
|
|
20
|
+
saveState?(): void;
|
|
21
|
+
dailyLimit?: number;
|
|
22
|
+
failures?: number;
|
|
23
|
+
failureCounts?: Record<string, number>;
|
|
24
|
+
/** 하루 착지 칸을 잠금 안에서 먼저 확보한다 — 다른 프로세스와 같은 잔여 칸을 두 번 쓰지 않게. 없으면 메모리 셈. */
|
|
25
|
+
reserveLanding?(day: string, limit: number): boolean;
|
|
26
|
+
/** 병합이 확인되지 않으면 확보한 칸을 돌려준다. */
|
|
27
|
+
releaseLanding?(day: string): void;
|
|
28
|
+
/** 실패 한 번을 잠금 안에서 디스크에 더한다(다른 프로세스의 실패를 덮지 않게) · 더한 뒤의 값을 돌려준다. */
|
|
29
|
+
incrementFailure?(key: string): number;
|
|
30
|
+
observe(event: 'task-agent.action', data: { taskId: string; kind: NextActionKind; result: string; reason: string }): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function executeNextAction(action: NextAction, deps: ActionDeps): Promise<string> {
|
|
34
|
+
const record = (result: string, reason: string) => {
|
|
35
|
+
if (result === 'failed' && (deps.failureCounts || deps.incrementFailure)) {
|
|
36
|
+
const key = JSON.stringify([action.taskId, action.kind]);
|
|
37
|
+
const next = deps.incrementFailure ? deps.incrementFailure(key) : (deps.failureCounts![key] ?? 0) + 1;
|
|
38
|
+
if (deps.failureCounts) deps.failureCounts[key] = next;
|
|
39
|
+
if (!deps.incrementFailure) deps.saveState?.();
|
|
40
|
+
}
|
|
41
|
+
deps.observe('task-agent.action', { taskId: action.taskId, kind: action.kind, result, reason });
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
if (deps.mode !== 'live') return record('shadow', `would ${action.kind}: ${action.rationale}`);
|
|
45
|
+
const card = async (reason: string) => {
|
|
46
|
+
const evidence = JSON.stringify({ runId: action.runId, pr: action.pr, mustFix: action.mustFix ?? [], history: action.history ?? [], selected: action.kind, reason });
|
|
47
|
+
const result = await deps.command(['decisions', 'raise', '--title', `${action.taskId}: ${reason}`, '--category', action.risk === 'money' ? 'money' : action.risk === 'security' ? 'security' : action.risk === 'irreversible-publication' ? 'publish' : 'other', '--s', `Task ${action.taskId} needs a decision.`, '--c', reason, '--ref', evidence, '--q', 'Which action should be taken?', '--option', 'h=Keep on hold:no automatic execution', '--option', 'p=Approve action:execute after approval', '--skip-recommend', 'requires human decision', '--no-xcheck', 'automated escalation']);
|
|
48
|
+
return record(result.status === 0 ? 'decision' : 'failed', reason);
|
|
49
|
+
};
|
|
50
|
+
if (action.risk) return card(`risk: ${action.risk}`);
|
|
51
|
+
if ((deps.failureCounts?.[JSON.stringify([action.taskId, action.kind])] ?? deps.failures ?? 0) >= 2) return card('same task/action failed twice');
|
|
52
|
+
if (action.kind === 'land') {
|
|
53
|
+
if (!action.cwd) return card('landing worktree unknown');
|
|
54
|
+
const view = await deps.command(['gh', 'pr', 'view', String(action.pr), '--json', 'labels']);
|
|
55
|
+
if (view.status !== 0) return card('PR labels unavailable');
|
|
56
|
+
let labels: unknown;
|
|
57
|
+
try { labels = (JSON.parse(view.stdout) as { labels?: unknown }).labels; }
|
|
58
|
+
catch { return card('PR labels unavailable'); }
|
|
59
|
+
if (!Array.isArray(labels) || !labels.every(label => label && typeof label === 'object' && typeof label.name === 'string')) return card('PR labels unavailable');
|
|
60
|
+
if (labels.some(label => label.name === 'elanous:release-path')) return card('release-path');
|
|
61
|
+
const day = new Date().toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' });
|
|
62
|
+
if (deps.landingDay && deps.landingDay !== day) deps.landingsToday = 0;
|
|
63
|
+
deps.landingDay = day;
|
|
64
|
+
const limit = deps.dailyLimit ?? 5;
|
|
65
|
+
if (deps.reserveLanding ? !deps.reserveLanding(day, limit) : (deps.landingsToday ?? 0) >= limit) return card('daily landing limit');
|
|
66
|
+
}
|
|
67
|
+
if (action.kind === 'decision') return card(action.rationale);
|
|
68
|
+
if (action.kind === 'green') return card('green requires a verified landing in this execution');
|
|
69
|
+
if (action.kind === 'retry' && !action.mustFix?.length) return card('must-fix missing; cannot narrow retry');
|
|
70
|
+
if (action.kind === 'review') {
|
|
71
|
+
const review = await deps.command(['self', 'review', String(action.pr), '--json']);
|
|
72
|
+
if (review.status !== 0) return record('failed', 'review command failed');
|
|
73
|
+
let parsed: { verdict?: string; mustFix?: string[]; reviewed?: boolean };
|
|
74
|
+
try {
|
|
75
|
+
const payload: unknown = JSON.parse(review.stdout);
|
|
76
|
+
const item = Array.isArray(payload) ? payload.find((entry: unknown) => entry && typeof entry === 'object' && 'pr' in entry && Number(entry.pr) === action.pr) : payload;
|
|
77
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) return record('failed', 'invalid review JSON');
|
|
78
|
+
parsed = item as typeof parsed;
|
|
79
|
+
} catch { return record('failed', 'invalid review JSON'); }
|
|
80
|
+
if (parsed.reviewed !== true) return record('failed', 'review was not performed');
|
|
81
|
+
if (parsed.verdict === 'pass') { record('done', 'review pass'); return executeNextAction({ ...action, kind: 'land', history: [...(action.history ?? []), 'review pass'] }, deps); }
|
|
82
|
+
if (parsed.verdict === 'fail' && Array.isArray(parsed.mustFix) && parsed.mustFix.length > 0 && parsed.mustFix.every(fix => typeof fix === 'string' && fix.trim())) { record('done', 'review fail'); return executeNextAction({ ...action, kind: 'retry', mustFix: parsed.mustFix, history: [...(action.history ?? []), 'review fail'] }, deps); }
|
|
83
|
+
return card('review verdict or must-fix unavailable');
|
|
84
|
+
}
|
|
85
|
+
// 여기 닿는 종류는 land · retry 둘뿐이다(green·decision·review 는 위에서 끝난다).
|
|
86
|
+
const args = action.kind === 'land' ? ['pr', 'land', '--cwd', action.cwd!]
|
|
87
|
+
: ['harness', 'say', `${action.original}\n\nMust-fix:\n${(action.mustFix ?? []).join('\n')}\n\nPrevious run: ${action.runId}`];
|
|
88
|
+
const day = deps.landingDay ?? new Date().toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' });
|
|
89
|
+
const outcome = await deps.command(args);
|
|
90
|
+
if (outcome.status !== 0) {
|
|
91
|
+
if (action.kind === 'land') deps.releaseLanding?.(day);
|
|
92
|
+
return record('failed', `${action.kind} command failed: ${outcome.stderr ?? ''}`);
|
|
93
|
+
}
|
|
94
|
+
if (action.kind === 'land') {
|
|
95
|
+
const merged = await deps.command(['gh', 'pr', 'view', String(action.pr), '--json', 'state,mergeCommit']);
|
|
96
|
+
let sha: string | undefined;
|
|
97
|
+
let notMerged = false;
|
|
98
|
+
try {
|
|
99
|
+
const value = JSON.parse(merged.stdout) as { state?: string; mergeCommit?: { oid?: string } };
|
|
100
|
+
if (merged.status === 0 && value.state === 'MERGED' && /^[a-f0-9]{40}$/i.test(value.mergeCommit?.oid ?? '')) sha = value.mergeCommit?.oid;
|
|
101
|
+
else if (merged.status === 0 && typeof value.state === 'string' && value.state !== 'MERGED') notMerged = true;
|
|
102
|
+
} catch { /* unknown merge state is never green */ }
|
|
103
|
+
if (!sha) {
|
|
104
|
+
// «열림»으로 확인됐을 때만 칸을 돌려준다. 상태를 못 읽었으면 병합됐을 수 있으니 칸은 쓴 것으로 둔다(상한을 넘지 않게).
|
|
105
|
+
if (notMerged) deps.releaseLanding?.(day);
|
|
106
|
+
return card('landing commit unknown; checklist remains unchanged');
|
|
107
|
+
}
|
|
108
|
+
record('done', action.rationale);
|
|
109
|
+
if (!deps.reserveLanding) deps.landingsToday = (deps.landingsToday ?? 0) + 1;
|
|
110
|
+
deps.saveState?.();
|
|
111
|
+
const green = await deps.command(['release', 'checklist', 'set', action.checklistId, '--status', 'green', '--evidence', `${action.pr}·${sha}`]);
|
|
112
|
+
// 체크리스트 실패는 이 과제·land 의 실패로 센다(두 번이면 다음엔 카드).
|
|
113
|
+
if (green.status !== 0) return record('failed', `green command failed: ${green.stderr ?? ''}`);
|
|
114
|
+
deps.observe('task-agent.action', { taskId: action.taskId, kind: 'green', result: 'done', reason: action.rationale });
|
|
115
|
+
return 'done';
|
|
116
|
+
}
|
|
117
|
+
return record('done', action.rationale);
|
|
118
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TASK-AGENT 판단부 — 슈퍼바이저 종료 어휘(+ PR·리뷰 관측) → «다음 한 수».
|
|
3
|
+
*
|
|
4
|
+
* RFC-task-agent-any-task-to-completion-2026-10-06 §A2 표를 순수 함수로 옮긴다.
|
|
5
|
+
* 부작용이 없다(발사·리뷰·착지는 실행부 `actions.ts` 의 몫). 수확 원천 = run-c9ad6d6d `chooseTaskAction`.
|
|
6
|
+
*/
|
|
7
|
+
import type { SupervisorStopReason } from '../self-dev/run-supervisor.js';
|
|
8
|
+
import type { NextActionKind } from './actions.js';
|
|
9
|
+
|
|
10
|
+
export type TaskMove =
|
|
11
|
+
| 'propose-green'
|
|
12
|
+
| 'review'
|
|
13
|
+
| 'propose-land'
|
|
14
|
+
| 'narrow-relaunch'
|
|
15
|
+
| 'wait-retry'
|
|
16
|
+
| 'salvage-relaunch'
|
|
17
|
+
| 'alternative'
|
|
18
|
+
| 'decision-card'
|
|
19
|
+
| 'wait';
|
|
20
|
+
|
|
21
|
+
export interface TaskJudgeInput {
|
|
22
|
+
stopReason?: SupervisorStopReason;
|
|
23
|
+
prState?: 'OPEN' | 'MERGED' | 'CLOSED';
|
|
24
|
+
pr?: string | number;
|
|
25
|
+
/** `self review --json` 판정. 관측 못 했으면 비운다(«통과»로 읽지 않는다). */
|
|
26
|
+
review?: 'pass' | 'fail';
|
|
27
|
+
mustFix?: readonly string[];
|
|
28
|
+
/** 수마다 누적 실패 수. */
|
|
29
|
+
failures?: Partial<Record<TaskMove, number>>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface TaskJudgement {
|
|
33
|
+
move: TaskMove;
|
|
34
|
+
stage: 'observing' | 'closing' | 'reviewing' | 'repairing' | 'waiting' | 'escalated';
|
|
35
|
+
reason: string;
|
|
36
|
+
/** 실행부(`executeNextAction`)가 바로 받는 수면 그 종류. 없으면 발사·대기 계열(실행부 밖). */
|
|
37
|
+
executorKind?: NextActionKind;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 같은 수가 이 횟수만큼 실패하면 다른 수로 넘어간다(RFC §A2 «같은 수 2번 실패»). */
|
|
41
|
+
export const SAME_MOVE_FAILURE_LIMIT = 2;
|
|
42
|
+
|
|
43
|
+
const EXECUTOR_KIND: Partial<Record<TaskMove, NextActionKind>> = {
|
|
44
|
+
'propose-green': 'green',
|
|
45
|
+
review: 'review',
|
|
46
|
+
'propose-land': 'land',
|
|
47
|
+
'narrow-relaunch': 'retry',
|
|
48
|
+
'wait-retry': 'retry',
|
|
49
|
+
'salvage-relaunch': 'retry',
|
|
50
|
+
alternative: 'retry',
|
|
51
|
+
'decision-card': 'decision',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
function base(input: TaskJudgeInput): Omit<TaskJudgement, 'executorKind'> {
|
|
55
|
+
const stop = input.stopReason;
|
|
56
|
+
const merged = input.prState === 'MERGED' && input.pr !== undefined && input.pr !== '';
|
|
57
|
+
if (merged) {
|
|
58
|
+
return { move: 'propose-green', stage: 'closing', reason: `병합된 PR ${input.pr} 근거로 칸 green 제안` };
|
|
59
|
+
}
|
|
60
|
+
switch (stop) {
|
|
61
|
+
case 'harvestable-awaiting-human':
|
|
62
|
+
case 'needs-human':
|
|
63
|
+
if (input.review === 'pass') return { move: 'propose-land', stage: 'reviewing', reason: 'self review 통과 — 착지 제안' };
|
|
64
|
+
if (input.review === 'fail') {
|
|
65
|
+
return { move: 'narrow-relaunch', stage: 'repairing', reason: `must-fix 로 좁혀 재발사: ${(input.mustFix ?? []).join(' · ') || '(must-fix 비어 있음)'}` };
|
|
66
|
+
}
|
|
67
|
+
return { move: 'review', stage: 'reviewing', reason: '수확 가능 런 — self review 필요' };
|
|
68
|
+
case 'no-progress':
|
|
69
|
+
case 'decomposable-no-progress':
|
|
70
|
+
return { move: 'narrow-relaunch', stage: 'repairing', reason: `${stop} — 범위를 좁혀 한 번 재발사(판정선 유지)` };
|
|
71
|
+
case 'provider-exhausted':
|
|
72
|
+
case 'step-timeout':
|
|
73
|
+
return { move: 'wait-retry', stage: 'waiting', reason: `${stop} — 대기 후 재시도` };
|
|
74
|
+
case 'handed-off-to-salvage':
|
|
75
|
+
return { move: 'salvage-relaunch', stage: 'repairing', reason: '수확 브랜치 diff 를 가져와 이어 발사' };
|
|
76
|
+
case 'converged':
|
|
77
|
+
return { move: 'wait', stage: 'observing', reason: '완주했으나 PR 병합 근거 대기' };
|
|
78
|
+
case undefined:
|
|
79
|
+
return { move: 'wait', stage: 'observing', reason: '종료 관측 대기' };
|
|
80
|
+
default:
|
|
81
|
+
return { move: 'decision-card', stage: 'escalated', reason: `표에 없는 종료 어휘 ${stop} — 근거 붙여 사람 결정` };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** 종료 어휘·관측에서 다음 한 수를 고른다. 같은 수 2회 실패 → 다른 수 → 그것도 실패 → 결정 카드. */
|
|
86
|
+
export function judgeNextMove(input: TaskJudgeInput): TaskJudgement {
|
|
87
|
+
const failures = input.failures ?? {};
|
|
88
|
+
let judged = base(input);
|
|
89
|
+
if ((failures.alternative ?? 0) >= 1) {
|
|
90
|
+
judged = { move: 'decision-card', stage: 'escalated', reason: `대안도 실패 — 런·PR·must-fix 근거로 사람 결정 (직전: ${judged.move})` };
|
|
91
|
+
} else if (judged.move !== 'wait' && judged.move !== 'decision-card' && (failures[judged.move] ?? 0) >= SAME_MOVE_FAILURE_LIMIT) {
|
|
92
|
+
judged = { move: 'alternative', stage: 'repairing', reason: `같은 수 ${SAME_MOVE_FAILURE_LIMIT}회 실패 — 다른 수 선택 (${judged.move})` };
|
|
93
|
+
}
|
|
94
|
+
const executorKind = EXECUTOR_KIND[judged.move];
|
|
95
|
+
return executorKind ? { ...judged, executorKind } : judged;
|
|
96
|
+
}
|
|
@@ -46,6 +46,31 @@ export function registerCardCommand(program: Command, deps: CardCliDeps = {}): C
|
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
card.command('close <id>')
|
|
50
|
+
.description('Close a task card with a reason (owner operation)')
|
|
51
|
+
.requiredOption('--reason <text>', 'One-line close reason')
|
|
52
|
+
.action((id: string, options: { reason: string }) => {
|
|
53
|
+
const store = createStore();
|
|
54
|
+
try {
|
|
55
|
+
const reason = options.reason.trim();
|
|
56
|
+
if (!reason || /[\r\n]/.test(options.reason)) throw new Error('Close reason must be one line');
|
|
57
|
+
const existing = store.getCard(id);
|
|
58
|
+
if (!existing) {
|
|
59
|
+
process.stderr.write(`Card not found: ${id}\n`);
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (existing.status === 'closed') {
|
|
64
|
+
write(`이미 닫힘: ${id}\n`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
store.closeCard(id, reason);
|
|
68
|
+
write(`닫힘: ${id}\n`);
|
|
69
|
+
} finally {
|
|
70
|
+
store.close();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
49
74
|
card.command('list')
|
|
50
75
|
.description('List task cards')
|
|
51
76
|
.option('--open', 'Only show open cards')
|
|
@@ -16,6 +16,7 @@ export interface TaskCard {
|
|
|
16
16
|
goalId: string;
|
|
17
17
|
title: string;
|
|
18
18
|
status: CardStatus;
|
|
19
|
+
closedReason?: string;
|
|
19
20
|
createdAt: string;
|
|
20
21
|
sections: CardSection[];
|
|
21
22
|
}
|
|
@@ -32,7 +33,7 @@ export interface AppendSectionInput {
|
|
|
32
33
|
type CardEvent =
|
|
33
34
|
| { type: 'created'; id: string; goalId: string; title: string; createdAt: string }
|
|
34
35
|
| { type: 'section'; key: string; owner: string; content: string; createdAt: string }
|
|
35
|
-
| { type: 'closed'; createdAt: string };
|
|
36
|
+
| { type: 'closed'; createdAt: string; reason?: string };
|
|
36
37
|
|
|
37
38
|
/** RFC §3 (내부 문서 `RFC-execution-and-landing-loop-agents-parallel-first-2026-09-29`) — each card section has one owner.
|
|
38
39
|
* A key is `<section>` or `<section>:<idempotency key>`: the same section can be appended many times over a
|
|
@@ -197,14 +198,16 @@ export class CardStore {
|
|
|
197
198
|
}).immediate();
|
|
198
199
|
}
|
|
199
200
|
|
|
200
|
-
closeCard(id: string): TaskCard {
|
|
201
|
+
closeCard(id: string, reason?: string): TaskCard {
|
|
202
|
+
if (reason !== undefined && !reason.trim()) throw new Error('Close reason is required');
|
|
201
203
|
return this.db.transaction(() => {
|
|
202
204
|
this.reconcile();
|
|
203
205
|
const card = this.readCard(id);
|
|
204
206
|
if (!card) throw new Error(`Card not found: ${id}`);
|
|
205
207
|
if (card.status === 'closed') return card;
|
|
206
|
-
this.append(id, { type: 'closed', createdAt: new Date().toISOString()
|
|
207
|
-
|
|
208
|
+
this.append(id, { type: 'closed', createdAt: new Date().toISOString(),
|
|
209
|
+
...(reason === undefined ? {} : { reason: redactSecrets(reason.trim()) }) });
|
|
210
|
+
return { ...card, status: 'closed' as const, ...(reason === undefined ? {} : { closedReason: redactSecrets(reason.trim()) }) };
|
|
208
211
|
}).immediate();
|
|
209
212
|
}
|
|
210
213
|
|
|
@@ -214,8 +217,10 @@ export class CardStore {
|
|
|
214
217
|
} | null;
|
|
215
218
|
if (!row) return null;
|
|
216
219
|
const events = readFileSync(cardEventsPath(id, this.root), 'utf8').trimEnd().split('\n').map((line) => JSON.parse(line) as CardEvent);
|
|
220
|
+
const closed = events.find((event): event is Extract<CardEvent, { type: 'closed' }> => event.type === 'closed');
|
|
217
221
|
return {
|
|
218
222
|
id: row.id, goalId: row.goal_id, title: row.title, status: row.status, createdAt: row.created_at,
|
|
223
|
+
...(closed?.reason === undefined ? {} : { closedReason: closed.reason }),
|
|
219
224
|
sections: events.filter((event): event is Extract<CardEvent, { type: 'section' }> => event.type === 'section')
|
|
220
225
|
.map(({ key, owner, content, createdAt }) => ({ key, owner, content, createdAt })),
|
|
221
226
|
};
|
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
/** Executor kinds of the dev child's `--json` result line (`src/index.ts` writeStdoutJson · DevExecutor.kind). */
|
|
2
|
+
const CHILD_RESULT_KINDS = new Set(['self', 'external']);
|
|
3
|
+
|
|
4
|
+
/** The dev child's terminal line nests the stage: {ok, kind, result:{stage, ok, node, outcome, …}}. Other JSON rows are not results. */
|
|
5
|
+
export function nestedPodResult(row: Record<string, unknown>): { stage: string; ok: boolean; error?: string } | null {
|
|
6
|
+
if (typeof row.kind !== 'string' || !CHILD_RESULT_KINDS.has(row.kind) || typeof row.ok !== 'boolean') return null;
|
|
7
|
+
if (!row.result || typeof row.result !== 'object' || Array.isArray(row.result)) return null;
|
|
8
|
+
const result = row.result as Record<string, unknown>;
|
|
9
|
+
if (typeof result.stage !== 'string' || typeof result.ok !== 'boolean') return null;
|
|
10
|
+
if (result.ok) return { stage: result.stage, ok: true };
|
|
11
|
+
const classification = result.abandonedClassification;
|
|
12
|
+
const detail = typeof result.supervisorVerdict === 'string' ? result.supervisorVerdict
|
|
13
|
+
: classification && typeof classification === 'object' && !Array.isArray(classification)
|
|
14
|
+
? (classification as Record<string, unknown>).classification : undefined;
|
|
15
|
+
const reason = [result.node, result.outcome].filter((part): part is string => typeof part === 'string' && part.length > 0).join(' ');
|
|
16
|
+
const error = [reason || (typeof result.error === 'string' ? result.error : ''), typeof detail === 'string' && detail ? detail : ''].filter(Boolean).join(' · ');
|
|
17
|
+
return { stage: result.stage, ok: false, ...(error ? { error } : {}) };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A child terminal row: the legacy flat form {stage, ok, error?} (no kind, or a child kind) or the nested dev result. Any other kind is not a result. */
|
|
21
|
+
export function podTerminalRow(row: Record<string, unknown>): { stage: string; ok: boolean; error?: string } | null {
|
|
22
|
+
if (typeof row.kind === 'string' && !CHILD_RESULT_KINDS.has(row.kind)) return null;
|
|
23
|
+
if (typeof row.stage === 'string' && typeof row.ok === 'boolean') {
|
|
24
|
+
return { stage: row.stage, ok: row.ok, ...(typeof row.error === 'string' ? { error: row.error } : {}) };
|
|
25
|
+
}
|
|
26
|
+
return nestedPodResult(row);
|
|
27
|
+
}
|
|
28
|
+
|
|
1
29
|
/** Extract a human-readable reason from a failed child Pod without treating transfer markers as child errors. */
|
|
2
30
|
export function extractPodFailureReason(input: {
|
|
3
31
|
logs: string;
|
|
@@ -12,7 +40,7 @@ export function extractPodFailureReason(input: {
|
|
|
12
40
|
if (input.containerReason === 'OOMKilled') return 'OOMKilled: 컨테이너 메모리 한도 초과';
|
|
13
41
|
|
|
14
42
|
const isBookkeeping = (line: string): boolean =>
|
|
15
|
-
/^ELANOUS_(?:MEM|RUN_LEDGER|USAGE_ROLLUP|POD_)/u.test(line) || /^(?:at\s+\S|\.\.\.\s+\d+\s+more\b|cleanup\s+(?:complete|done|finished)\b)/iu.test(line);
|
|
43
|
+
/^(?:ELANOUS_(?:MEM|RUN_LEDGER|USAGE_ROLLUP|POD_)|\[graph\] )/u.test(line) || /^(?:at\s+\S|\.\.\.\s+\d+\s+more\b|cleanup\s+(?:complete|done|finished)\b)/iu.test(line);
|
|
16
44
|
const isError = (line: string): boolean =>
|
|
17
45
|
/\berror\b|\bfail(?:ed|ure)?\b|exception|fatal|denied|timed?\s*out|exceeded|overloaded|unavailable|exhausted|오류|실패/iu.test(line);
|
|
18
46
|
const meaningful: Array<{ text: string; terminal: boolean }> = [];
|
|
@@ -24,10 +52,11 @@ export function extractPodFailureReason(input: {
|
|
|
24
52
|
try { parsed = JSON.parse(trimmed); } catch { /* An ordinary line may begin with a brace. */ }
|
|
25
53
|
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
26
54
|
const row = parsed as Record<string, unknown>;
|
|
27
|
-
|
|
55
|
+
const terminal = podTerminalRow(row);
|
|
56
|
+
if (terminal) {
|
|
28
57
|
meaningful.length = 0;
|
|
29
|
-
if (
|
|
30
|
-
const errorLines =
|
|
58
|
+
if (terminal.ok === false && typeof terminal.error === 'string') {
|
|
59
|
+
const errorLines = terminal.error.split(/\r\n|\n|\r/u).map((part) => part.trim())
|
|
31
60
|
.filter((part) => part && !isBookkeeping(part));
|
|
32
61
|
const errorLine = [...errorLines].reverse().find(isError) ?? errorLines.at(-1);
|
|
33
62
|
if (errorLine) meaningful.push({ text: errorLine, terminal: true });
|
|
@@ -64,6 +64,9 @@ export interface PoolLeaseRecommendation {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/** Kubernetes quantity to bytes (memory) or millicores (CPU). */
|
|
67
|
+
/** Kubernetes memory quantity (`16Gi`, `2Gi`, `512Mi`) → bytes; null when unparseable. */
|
|
68
|
+
export function memoryQuantityBytes(raw: string): number | null { return quantity(raw, 'memory'); }
|
|
69
|
+
|
|
67
70
|
function quantity(raw: string, unit: 'memory' | 'cpu'): number | null {
|
|
68
71
|
const m = /^(\d+(?:\.\d+)?)(n|u|m|k|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$/.exec(raw);
|
|
69
72
|
if (!m) return null;
|
|
@@ -235,7 +238,7 @@ function runPoolDnsProbe(context: string, kubectl: PoolKubectl): PoolDnsProbe {
|
|
|
235
238
|
}
|
|
236
239
|
|
|
237
240
|
/** Each cluster reads resource reservations; DNS requires a bounded disposable Pod. */
|
|
238
|
-
export function measurePoolLease(members: readonly PodPoolMember[], deps: { kubectl?: PoolKubectl; dns?: (context: string) => PoolDnsProbe } = {}): PoolLeaseMeasure {
|
|
241
|
+
export function measurePoolLease(members: readonly PodPoolMember[], deps: { kubectl?: PoolKubectl; dns?: (context: string) => PoolDnsProbe; /** Read-only snapshots must not create the disposable DNS Pod or update its cache. */ skipDnsProbe?: boolean } = {}): PoolLeaseMeasure {
|
|
239
242
|
const kubectl = deps.kubectl ?? leaseKubectl;
|
|
240
243
|
return { members: members.map((member) => {
|
|
241
244
|
const base = ['--context', member.context, '--request-timeout=10s'];
|
|
@@ -354,7 +357,7 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
|
|
|
354
357
|
else if (!reservationsValid && !unknownPhase) reasons.push('cluster reservations: node assignment or memory request/limit missing/invalid');
|
|
355
358
|
}
|
|
356
359
|
result.reason = reasons.length ? reasons.join('; ').slice(0, 240) : null;
|
|
357
|
-
if (nodeItems?.length && jobItems && podItems) {
|
|
360
|
+
if (!deps.skipDnsProbe && nodeItems?.length && jobItems && podItems) {
|
|
358
361
|
let dnsState: PoolDnsProbe = 'unknown';
|
|
359
362
|
try { dnsState = (deps.dns ?? ((context) => probePoolDns(context, kubectl)))(member.context); } catch { /* probe could not be measured */ }
|
|
360
363
|
if (dnsState === 'dns') { result.capacity = 0; result.availableMemoryByNodeBytes = []; result.reason = 'dns'; }
|
|
@@ -3,6 +3,7 @@ import { gunzipSync } from 'node:zlib';
|
|
|
3
3
|
import { debug } from '../../debug/log.js';
|
|
4
4
|
import { runLedgerDir, runLedgerPath } from '../../self-implement/run-ledger.js';
|
|
5
5
|
import { ledgerLineToLogEvent } from './pod-ledger-events.js';
|
|
6
|
+
import { defaultPodReemit } from './pod-ledger-prod-sink.js';
|
|
6
7
|
|
|
7
8
|
type Ledger = { runId: string; jsonl: string };
|
|
8
9
|
type Incomplete = { runId: string; reason: string };
|
|
@@ -94,7 +95,7 @@ export function parsePodLedgerChunks(logs: string): ParseResult {
|
|
|
94
95
|
* except a ledger that THIS run's live follower created (`replace`): that one is a partial copy and the final is complete. */
|
|
95
96
|
export function collectPodLedgers(
|
|
96
97
|
logs: string,
|
|
97
|
-
{ dir = runLedgerDir(), log = (
|
|
98
|
+
{ dir = runLedgerDir(), log = defaultPodReemit(), emit = defaultPodReemit(), replace = new Set<string>() }: {
|
|
98
99
|
dir?: string;
|
|
99
100
|
log?: (category: string, event: string, data: Record<string, unknown>) => void;
|
|
100
101
|
emit?: Emit;
|
|
@@ -146,8 +147,8 @@ export function createPodLedgerFollower(opts: {
|
|
|
146
147
|
onStall?: (message: string) => void;
|
|
147
148
|
}): { poll(): void; readonly owned: boolean } {
|
|
148
149
|
const dir = opts.dir ?? runLedgerDir();
|
|
149
|
-
const log = opts.log ?? (
|
|
150
|
-
const emit: Emit = opts.emit ?? (
|
|
150
|
+
const log = opts.log ?? defaultPodReemit();
|
|
151
|
+
const emit: Emit = opts.emit ?? defaultPodReemit();
|
|
151
152
|
const now = opts.now ?? Date.now;
|
|
152
153
|
const stallMinutes = opts.stallMinutes ?? 30;
|
|
153
154
|
if (!Number.isFinite(stallMinutes) || stallMinutes <= 0) throw new RangeError('stallMinutes must be positive');
|