elanous 0.2.19 → 0.2.20
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/{J5H7BvqgSI8DUjtRRcA_S → Bam1YziK2iH9bN4K0utGk}/_buildManifest.js +1 -1
- package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 1268.0840ff91e083fcbd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-77fad80cf54477c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +5 -0
- package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2977-e38442e86289f1d3.js → 211-9c9090c59e2c5907.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
- package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2489-ff55a35b202c0f94.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4847.56f5bc454fbcc827.js → 6110.1297d4093fdfbb0d.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/6158.af2e93b7adff709b.js +2 -0
- package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1470.98a6ab61ccb4131c.js → 726.83532d69452ee24b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 7327-2d69fb321ba73389.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{8870-9614a213b990b10b.js → 7748-6e51bfe927019f5c.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7647.2fcd30d894d5ee30.js → 935.4ddaa99fc1d1126c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{ae787e4c-e5b1ef00b2dbceca.js → a9f2b54d-33c0689b70ed8cf3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-c855c24de7db74fe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-85b316bb4235980b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-9b2b26a33cb2cd42.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-c9955fde67b1bcb5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-7a852956c74015a3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-4ba4abe096b6c2a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-575d4a8b4f7becb2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-f5d56ff5f45ad164.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-2260e9eef0357421.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-7c058069c5e7fa63.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-466bf0ee16ad1432.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-a23bc71713bcc08f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/{page-5b29d9b06ceadade.js → page-ed4a6f799f005d09.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-9cd565005b93aca6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-474cb5888ea0e8f3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-35bfec2e631d3eae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-9ca21f9ad3208a61.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-f9ccb1b1adbea14d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-ec102c9b8b691e37.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-a9c2cfa8dda922a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-f6055495e9ed7ea8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-db03ae3589a81c9e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4a6463b97f0ad94f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-75bffecc9aa1004e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-31d5b875760bb62d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-68793c6e10331d66.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-46d5201d2207a511.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-d81c07a7b4f618ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-be1c4e8697f22360.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-cf0a4b4abead71ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-0fd895fab9b62c1b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c6e4364d9de8f717.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-0acc8e198f7b0dfb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-34882c84f84021c5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → f6231284-45726550aba08fca.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{2ea96958.e8c7e112de50b067.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-c260571513bf43c2.js +1 -0
- package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +23 -23
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +22 -22
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +23 -23
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +23 -23
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +22 -22
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +23 -23
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +22 -22
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +23 -23
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +22 -22
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +22 -22
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +22 -22
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +23 -23
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +23 -23
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +22 -22
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +22 -22
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +22 -22
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +23 -23
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +23 -23
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +22 -22
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +23 -23
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +23 -23
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +23 -23
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +23 -23
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +23 -23
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +22 -22
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +23 -23
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +23 -23
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +23 -23
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +23 -23
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +23 -23
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +23 -23
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +23 -23
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +23 -23
- package/graphs/demo/editor-recipes.yaml +22 -0
- package/graphs/retro/daily.yaml +25 -0
- package/graphs/retro/recipes.yaml +24 -0
- package/package.json +1 -1
- package/scripts/ask-marker-check.ts +3 -1
- package/scripts/land-global-checks.ts +89 -0
- package/scripts/lib/pch-regress-map.ts +2 -1
- package/scripts/release-loop/gate-node.ts +296 -25
- package/scripts/release-loop/gate-timing.ts +116 -7
- package/scripts/release-loop/preflight.ts +6 -4
- package/scripts/release-loop/publish-window.ts +141 -0
- package/scripts/release-loop/release-docs.ts +1 -4
- package/scripts/release-loop/release-universe.ts +38 -0
- package/scripts/release-loop/resume-release.ts +4 -1
- package/scripts/release-loop/unattended-release.ts +10 -2
- package/scripts/release-story/draft.ts +5 -1
- package/scripts/retro/shadow-compare.ts +71 -0
- package/scripts/rhythm/daily.ts +6 -1
- package/src/agent-mission/driver.ts +34 -6
- package/src/autopilot/build/llm-conflict-merge.ts +132 -7
- package/src/autopilot/build/merge-intent.ts +135 -7
- package/src/away/release-watch.ts +20 -3
- package/src/cli/config-drift.ts +513 -0
- package/src/cli/log-event-names.ts +4 -0
- package/src/cli/pr-cli.ts +20 -0
- package/src/cli/release-cli.ts +20 -5
- package/src/cli/retro-cli.ts +78 -0
- package/src/cli/tasks-cli.ts +94 -8
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +5 -4
- package/src/dashboard/slash-runtime/help-from-registry.ts +57 -31
- package/src/domains/outbound-log-probe.ts +6 -1
- package/src/graph-runner/graph-run-api.ts +205 -0
- package/src/graph-runner/runner.ts +11 -1
- package/src/harness/harness-cli-command.ts +419 -52
- package/src/harness/harness-queue.ts +315 -25
- package/src/harness/harness-salvage-cli.ts +62 -0
- package/src/hq/fence-outcomes.ts +63 -0
- package/src/hq/fence-wrapper.ts +33 -0
- package/src/hq/hq.ts +11 -1
- package/src/index.ts +69 -6
- package/src/maturity/feature-maturity.ts +2 -2
- package/src/nexus/api/graph-kinds.ts +10 -4
- package/src/nexus/api/graphs-api.ts +178 -6
- package/src/nexus/api/http-server.ts +38 -3
- package/src/nexus/api/loop-edges.ts +4 -2
- package/src/nexus/api/ops-api.ts +22 -7
- package/src/nexus/api/ops-board-route.ts +48 -0
- package/src/onboarding/entry-hints.ts +5 -0
- package/src/onboarding.ts +24 -6
- package/src/release-loop/feature-store.ts +14 -11
- package/src/release-loop/gate-shards.ts +158 -0
- package/src/release-loop/release-schedule.ts +6 -2
- package/src/retro/daily.ts +372 -0
- package/src/seat-loop/op-decide.ts +173 -0
- package/src/seat-loop/seat-loop.ts +60 -3
- package/src/seat-loop/seat-report.ts +58 -3
- package/src/self-dev/ask-launch-flow.ts +5 -4
- package/src/self-dev/ask-launch-io.ts +49 -1
- package/src/self-dev/ask-path-candidates.ts +40 -2
- package/src/self-dev/draft-sweep.ts +371 -16
- package/src/self-dev/draft-triage-rules.ts +19 -4
- package/src/self-dev/pr-preview.ts +84 -16
- package/src/self-implement/blocked-draft-policy.ts +101 -0
- package/src/self-implement/execution-clarification.ts +11 -0
- package/src/self-implement/gate-env-retry.ts +83 -0
- package/src/self-implement/orchestrator.ts +218 -5
- package/src/self-implement/run-stop.ts +123 -0
- package/src/self-implement/run-ttl.ts +258 -0
- package/src/self-implement/salvage-retention.ts +123 -0
- package/src/self-implement/seams.ts +4 -0
- package/src/self-implement/self-implement-cli.ts +2 -0
- package/src/self-implement/sibling-resync.ts +223 -0
- package/src/self-implement/stop-autoheal.ts +204 -0
- package/src/task-agent/board.ts +1 -1
- package/src/task-agent/card-evidence.ts +99 -10
- package/src/task-agent/judge.ts +7 -0
- package/src/task-agent/live-moves.ts +120 -20
- package/src/task-agent/mission.ts +1 -1
- package/src/task-agent/shadow.ts +10 -1
- package/src/task-agent/task-hand.ts +7 -2
- package/src/task-orchestrator/surfaces/pod-command-job.ts +19 -5
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +4 -0
- package/src/task-orchestrator/surfaces/pod-install-slots.ts +89 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +93 -10
- package/src/task-orchestrator/surfaces/pod-pool.ts +147 -34
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +19 -3
- package/src/telegram-commands.ts +41 -0
- package/src/testing/real-cli-spawn.ts +106 -0
- package/src/user-config.ts +56 -3
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1157-cba0e8553d35a980.js +0 -1
- package/apps/pwa/out/_next/static/chunks/171-23a877ed11720de9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2336-e830c777eb6bce04.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2375-e061bb24cd2b5cde.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3094.67339318c7f72a3a.js +0 -2
- package/apps/pwa/out/_next/static/chunks/338-ceb956612dcfaa86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3395-835e4ceaa6335ba4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +0 -5
- package/apps/pwa/out/_next/static/chunks/3441-be82e3595aef3a20.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3689-89905039bf3e7257.js +0 -1
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3822-6f268bc26c6a84ac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4457-d7df81c90f9d0268.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4551-77dadae76f6cbace.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4978.6fd93b974441e87f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5012-f746f6549b869f91.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +0 -1
- package/apps/pwa/out/_next/static/chunks/62-5bfed1e23790b745.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6320-9bc8561fde416e07.js +0 -1
- package/apps/pwa/out/_next/static/chunks/649-ca96d1c5d09e02b8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6726-85a7db59b288c53f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6735-33267c4630d0d963.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7633-141682015873beb6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-143791231c99bd02.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8430-a10cfba8ef282d4e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-12c2f30dea62291c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/976-4a9b4ebdca7c739a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9989-15cdf568bafa9fc8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-deef5ee191830f14.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-b193da485f9a14b2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b6e1a79df0e214f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-e84df4d907f703c7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a5fb9a5db89ae4ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1499f481d7fc52d6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-c7cf753aeb648790.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-4118469e514e10fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-e968f7630654332a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-505915052f947022.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-c2f962fb4a3b06c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7b5ed0bd8a589ec4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-cf8193250dd42dd4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-cf3f5dfa35dea8e9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a15e7fcc283e9ca7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-d2915e7b70ff3d0e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-b70cd005d9169e25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-2595f0eba29f57e1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-c776322454156e36.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-77bbd17f4435aa10.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-049689e611ecc936.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-85954a4fb09c44eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-e773e552e1068ee6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5633c4bfa7684b4a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d829b92bd01b4994.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-061c317fc771becf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a45ece8865ed4b6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-0be990618c0d56bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-bfea4865dc244b73.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d6ba8e337741484.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-76a88b55d816482e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-bba1542df59f61ce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4185c95689bb372e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b699e1a8d5d34b8b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-c225e338c31bf274.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-449353f70507ca1f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-90b889714102476d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-f1ee7fbeb7f96ddc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-d59a95e1434dfaa0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-71491a23414d1a47.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-de7112bdb65fa4fe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-feb4d0d88079660d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f17d887b40dd6b1d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-8da77be2192bcd5d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-72c3b9a5e9582b29.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3afe4c8d0c48c39.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-b9308f2d8a931531.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-6f35f9d892fa630b.js +0 -1
- package/apps/pwa/out/_next/static/css/85f53c6f35d11382.css +0 -1
- /package/apps/pwa/out/_next/static/{J5H7BvqgSI8DUjtRRcA_S → Bam1YziK2iH9bN4K0utGk}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* STOP-AUTOHEAL (0.2.20 P0 · 첫 조각) — 멈춘 런의 stop 레코드(STOP-RECORD #24907)를 읽고
|
|
3
|
+
* 갈래마다 «한 번»만 스스로 고쳐 본다. 안 되면 `needs-owner` 로 사람에게 넘긴다.
|
|
4
|
+
*
|
|
5
|
+
* S: 런이 멈추면 원장에 `event='stop'` 한 줄(class·cause·evidenceRef·nextMove)이 남는다.
|
|
6
|
+
* C: 그 줄을 읽는 쪽이 없어서 환경 결손·main 동기화·Pod 사망·증거 누락처럼 «기계가 고칠 수 있는» 멈춤도 사람을 기다렸다.
|
|
7
|
+
* Q: 갈래별로 한 수를 정해 두고 «한 번만» 시도할 수 있나?
|
|
8
|
+
* A: `autohealFromStop` 하나가 class → action 을 고르고, 원장 `event='autoheal'` 줄로 시도를 남긴다.
|
|
9
|
+
* 같은 런·같은 갈래·같은 모드에 이미 줄이 있으면 다시 하지 않는다(원장 가드).
|
|
10
|
+
*
|
|
11
|
+
* 사다리(class → action):
|
|
12
|
+
* env-unrelated → clean-workspace-regate 깨끗한 자리에서 gate 만 다시 1회(GATE-ENV-RETRY 재사용 자리)
|
|
13
|
+
* main-sync → rebase-regate 리베이스(MERGE-INTENT-RESOLVE 의도 해석) 뒤 재게이트
|
|
14
|
+
* pod-died → resume-from-salvage `salvage/run-<6hex>/<leaf>` 가지에서 resume(DRAFT-NOT-ARCHIVE)
|
|
15
|
+
* evidence-uncovered → derive-evidence-rejudge 런 출력·시험·배선에서 증거를 끌어내 1회 재판정
|
|
16
|
+
* review-repeat · review-out-of-scope · fabric · launch-failed · unclassified → needs-owner(새 라운드 없음)
|
|
17
|
+
*
|
|
18
|
+
* 모드 `tools.selfImplement.autoheal.mode` = `shadow`(기본: 할 일을 원장에만 적고 손대지 않는다) | `live`.
|
|
19
|
+
* ⛔ live 에서도 행동은 주입 seam 으로만 한다 — seam 이 없는 행동은 `needs-owner`(action-unwired)로 떨어진다.
|
|
20
|
+
* «안 이어진 손»을 「고쳤다」로 읽지 않게.
|
|
21
|
+
* 관측 = `debug.log('self-implement.autoheal', …)` ⊕ 원장 `event='autoheal'`.
|
|
22
|
+
*/
|
|
23
|
+
import { debug } from '../debug/log.js';
|
|
24
|
+
import { appendRunLedgerEntry, loadRunLedger, type RunLedgerEntry, type RunLedgerWriter } from './run-ledger.js';
|
|
25
|
+
import type { RunStopClass, RunStopRecord } from './run-stop.js';
|
|
26
|
+
|
|
27
|
+
export const AUTOHEAL_EVENT = 'autoheal';
|
|
28
|
+
export type AutohealMode = 'shadow' | 'live';
|
|
29
|
+
export const DEFAULT_AUTOHEAL_MODE: AutohealMode = 'shadow';
|
|
30
|
+
|
|
31
|
+
export type AutohealAction =
|
|
32
|
+
| 'clean-workspace-regate'
|
|
33
|
+
| 'rebase-regate'
|
|
34
|
+
| 'resume-from-salvage'
|
|
35
|
+
| 'derive-evidence-rejudge'
|
|
36
|
+
| 'needs-owner';
|
|
37
|
+
|
|
38
|
+
/** 닫힌 목록 전체를 덮는다 — 새 class 가 생기면 여기서 타입 오류가 난다. */
|
|
39
|
+
export const AUTOHEAL_ACTION_BY_CLASS: Readonly<Record<RunStopClass, AutohealAction>> = {
|
|
40
|
+
'env-unrelated': 'clean-workspace-regate',
|
|
41
|
+
'main-sync': 'rebase-regate',
|
|
42
|
+
'pod-died': 'resume-from-salvage',
|
|
43
|
+
'evidence-uncovered': 'derive-evidence-rejudge',
|
|
44
|
+
'review-repeat': 'needs-owner',
|
|
45
|
+
'review-out-of-scope': 'needs-owner',
|
|
46
|
+
fabric: 'needs-owner',
|
|
47
|
+
'launch-failed': 'needs-owner',
|
|
48
|
+
unclassified: 'needs-owner',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** 설정 원값 → 모드. 모르는 값은 «좁은 쪽»(shadow). */
|
|
52
|
+
export function parseAutohealMode(raw: unknown): AutohealMode {
|
|
53
|
+
return raw === 'live' ? 'live' : 'shadow';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface AutohealContext {
|
|
57
|
+
runId: string;
|
|
58
|
+
goalId?: string;
|
|
59
|
+
record: RunStopRecord;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface AutohealActionResult {
|
|
63
|
+
ok: boolean;
|
|
64
|
+
/** 한 줄 — 무엇을 했나/왜 못 했나. */
|
|
65
|
+
detail?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type AutohealActionFn = (ctx: AutohealContext) => Promise<AutohealActionResult> | AutohealActionResult;
|
|
69
|
+
|
|
70
|
+
/** 행동 seam — needs-owner 를 뺀 넷. 없는 seam 은 live 에서도 실행하지 않는다. */
|
|
71
|
+
export type AutohealActions = Partial<Record<Exclude<AutohealAction, 'needs-owner'>, AutohealActionFn>>;
|
|
72
|
+
|
|
73
|
+
export type AutohealOutcome =
|
|
74
|
+
/** shadow — 할 일을 적기만 했다. */
|
|
75
|
+
| 'would-act'
|
|
76
|
+
/** live — 행동을 했고 성공했다. */
|
|
77
|
+
| 'healed'
|
|
78
|
+
/** 사람에게 넘긴다(갈래가 needs-owner 이거나 행동이 실패·미배선). */
|
|
79
|
+
| 'needs-owner'
|
|
80
|
+
/** 같은 런·갈래·모드에 이미 시도가 있다 — 아무것도 안 했다(원장에 새 줄도 안 쓴다). */
|
|
81
|
+
| 'already-attempted';
|
|
82
|
+
|
|
83
|
+
export interface AutohealResult {
|
|
84
|
+
runId: string;
|
|
85
|
+
class: RunStopClass;
|
|
86
|
+
action: AutohealAction;
|
|
87
|
+
mode: AutohealMode;
|
|
88
|
+
outcome: AutohealOutcome;
|
|
89
|
+
/** needs-owner 일 때 사람이 볼 이유(멈춘 원인 그대로 또는 행동 실패). */
|
|
90
|
+
reason?: string;
|
|
91
|
+
detail?: string;
|
|
92
|
+
entry?: RunLedgerEntry;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface AutohealDeps {
|
|
96
|
+
mode?: AutohealMode;
|
|
97
|
+
actions?: AutohealActions;
|
|
98
|
+
/** 가드용 원장 읽기 — 기본 `loadRunLedger(runId)`. null = 원장 없음(첫 시도). */
|
|
99
|
+
loadLedger?: (runId: string) => readonly RunLedgerEntry[] | null;
|
|
100
|
+
writeLedger?: RunLedgerWriter;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 이 프로세스가 쓴 autoheal 줄 — 원장 읽기가 주입 writer 와 다른 곳을 보거나(시험 seam) 비어 있어도
|
|
105
|
+
* 같은 프로세스 안에서는 «한 번»이 지켜지게 한다(리뷰 must-fix #24912).
|
|
106
|
+
*/
|
|
107
|
+
const writtenThisProcess = new Map<string, RunLedgerEntry[]>();
|
|
108
|
+
const WRITTEN_MAX_RUNS = 4096;
|
|
109
|
+
|
|
110
|
+
/** 시험용 — 프로세스 기억을 비운다. */
|
|
111
|
+
export function resetAutohealProcessMemory(): void {
|
|
112
|
+
writtenThisProcess.clear();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** 원장 가드 — 같은 런·같은 갈래·같은 모드의 autoheal 줄이 있으면 참. */
|
|
116
|
+
export function autohealAlreadyAttempted(ledger: readonly RunLedgerEntry[] | null, cls: RunStopClass, mode: AutohealMode): boolean {
|
|
117
|
+
if (!ledger) return false;
|
|
118
|
+
return ledger.some((e) => e.event === AUTOHEAL_EVENT && e.data?.class === cls && e.data?.mode === mode);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function observe(event: string, data: Record<string, unknown>): void {
|
|
122
|
+
try { debug.log('self-implement.autoheal', event, data); } catch { /* observation is fail-soft */ }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* stop 레코드 하나 → 최대 한 번의 힐 시도. 예외를 던지지 않는다(행동 seam 의 throw 도 needs-owner 로 접는다).
|
|
127
|
+
*/
|
|
128
|
+
export async function autohealFromStop(ctx: AutohealContext, deps: AutohealDeps = {}): Promise<AutohealResult> {
|
|
129
|
+
const mode = deps.mode ?? DEFAULT_AUTOHEAL_MODE;
|
|
130
|
+
const cls = ctx.record.class;
|
|
131
|
+
const action = AUTOHEAL_ACTION_BY_CLASS[cls] ?? 'needs-owner';
|
|
132
|
+
const base = { runId: ctx.runId, class: cls, action, mode };
|
|
133
|
+
|
|
134
|
+
let ledger: readonly RunLedgerEntry[] | null = null;
|
|
135
|
+
try {
|
|
136
|
+
ledger = (deps.loadLedger ?? ((id) => loadRunLedger(id)))(ctx.runId);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
// ⛔ 가드를 못 읽으면 «한 번»을 보장할 수 없다 — live 행동을 하지 않고 사람에게 넘긴다.
|
|
139
|
+
const reason = `ledger-unreadable: ${error instanceof Error ? error.message : String(error)}`;
|
|
140
|
+
observe('guard-unreadable', { ...base, reason });
|
|
141
|
+
return { ...base, outcome: 'needs-owner', reason };
|
|
142
|
+
}
|
|
143
|
+
if (autohealAlreadyAttempted(ledger, cls, mode) || autohealAlreadyAttempted(writtenThisProcess.get(ctx.runId) ?? null, cls, mode)) {
|
|
144
|
+
observe('already-attempted', base);
|
|
145
|
+
return { ...base, outcome: 'already-attempted' };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let outcome: AutohealOutcome;
|
|
149
|
+
let reason: string | undefined;
|
|
150
|
+
let detail: string | undefined;
|
|
151
|
+
if (action === 'needs-owner') {
|
|
152
|
+
outcome = 'needs-owner';
|
|
153
|
+
reason = `stop ${cls}: ${ctx.record.cause}`;
|
|
154
|
+
} else if (mode === 'shadow') {
|
|
155
|
+
outcome = 'would-act';
|
|
156
|
+
} else {
|
|
157
|
+
const fn = deps.actions?.[action];
|
|
158
|
+
if (!fn) {
|
|
159
|
+
outcome = 'needs-owner';
|
|
160
|
+
reason = `action-unwired: ${action} (stop ${cls}: ${ctx.record.cause})`;
|
|
161
|
+
} else {
|
|
162
|
+
// ⭐ 시도 «전»에 원장 줄을 쓴다 — 행동 중 프로세스가 죽어도 두 번째 시도가 막힌다.
|
|
163
|
+
// ⛔ 그 줄을 못 쓰면 «한 번»을 보장할 수 없으니 행동하지 않는다.
|
|
164
|
+
if (!writeEntry(ctx, deps, { ...base, phase: 'attempt' })) {
|
|
165
|
+
const unguarded = `guard-write-failed: ${action} not attempted (stop ${cls}: ${ctx.record.cause})`;
|
|
166
|
+
observe('needs-owner', { ...base, reason: unguarded });
|
|
167
|
+
return { ...base, outcome: 'needs-owner', reason: unguarded };
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
const res = await fn(ctx);
|
|
171
|
+
detail = res.detail;
|
|
172
|
+
outcome = res.ok ? 'healed' : 'needs-owner';
|
|
173
|
+
if (!res.ok) reason = `${action} failed${res.detail ? `: ${res.detail}` : ''}`;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
outcome = 'needs-owner';
|
|
176
|
+
reason = `${action} threw: ${error instanceof Error ? error.message : String(error)}`;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const entry = writeEntry(ctx, deps, {
|
|
182
|
+
...base, phase: 'result', outcome,
|
|
183
|
+
...(reason ? { reason } : {}), ...(detail ? { detail } : {}),
|
|
184
|
+
evidenceRef: ctx.record.evidenceRef, nextMove: ctx.record.nextMove,
|
|
185
|
+
});
|
|
186
|
+
observe(outcome, { ...base, ...(reason ? { reason } : {}), ...(detail ? { detail } : {}) });
|
|
187
|
+
return { ...base, outcome, ...(reason ? { reason } : {}), ...(detail ? { detail } : {}), ...(entry ? { entry } : {}) };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function writeEntry(ctx: AutohealContext, deps: AutohealDeps, data: Record<string, unknown>): RunLedgerEntry | undefined {
|
|
191
|
+
const entry: RunLedgerEntry = {
|
|
192
|
+
timestamp: new Date().toISOString(), runId: ctx.runId, event: AUTOHEAL_EVENT, data,
|
|
193
|
+
...(ctx.goalId ? { goalId: ctx.goalId } : {}),
|
|
194
|
+
};
|
|
195
|
+
try {
|
|
196
|
+
(deps.writeLedger ?? appendRunLedgerEntry)(entry);
|
|
197
|
+
if (!writtenThisProcess.has(ctx.runId) && writtenThisProcess.size >= WRITTEN_MAX_RUNS) writtenThisProcess.clear();
|
|
198
|
+
writtenThisProcess.set(ctx.runId, [...(writtenThisProcess.get(ctx.runId) ?? []), entry]);
|
|
199
|
+
return entry;
|
|
200
|
+
} catch (error) {
|
|
201
|
+
observe('ledger-write-failed', { runId: ctx.runId, error: error instanceof Error ? error.message : String(error) });
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
}
|
package/src/task-agent/board.ts
CHANGED
|
@@ -56,7 +56,7 @@ function aggregate(node: BoardNode): void {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
function cardStatus(card: TaskCard): BoardStatus {
|
|
59
|
-
if (card.status === 'launch-failed') return 'blocked';
|
|
59
|
+
if (card.status === 'launch-failed' || card.status === 'failed') return 'blocked';
|
|
60
60
|
return card.greenProposal ? 'done' : 'open';
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -7,9 +7,15 @@
|
|
|
7
7
|
* - 관측: `debug.log('task-agent', 'card-pr-bound', {card, runId, pr})`.
|
|
8
8
|
*/
|
|
9
9
|
import { existsSync } from 'node:fs';
|
|
10
|
+
import { STOP_CLASS_POD_FAILURE, STOP_CLASS_NO_LAUNCH } from '../task-orchestrator/surfaces/pod-failure-reason.js';
|
|
11
|
+
import { readLaunchBinding } from '../harness/launch-registry.js';
|
|
12
|
+
import { readRunExits, type RunExit } from '../harness/harness-incidents.js';
|
|
13
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
10
14
|
import { debug } from '../debug/log.js';
|
|
15
|
+
import { LogStore, logsDbPath } from '../mss/logging/log-store.js';
|
|
11
16
|
import type { RunLedgerEntry } from '../self-implement/run-ledger.js';
|
|
12
17
|
import { cardPrNumber, taskAgentStatePath, updateTaskCard, readTaskCard, type TaskCard } from './task-hand.js';
|
|
18
|
+
import { judgeNextMove } from './judge.js';
|
|
13
19
|
|
|
14
20
|
const GOAL_ID = /^[a-f0-9]{16}$/;
|
|
15
21
|
const PR_URL = /^https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/pull\/(\d+)$/;
|
|
@@ -19,7 +25,7 @@ const RUN_ID = /^run-[A-Za-z0-9_-]{4,64}$/;
|
|
|
19
25
|
export interface CardRunSnapshot {
|
|
20
26
|
host: readonly RunLedgerEntry[] | null;
|
|
21
27
|
children: Readonly<Record<string, readonly RunLedgerEntry[] | null>>;
|
|
22
|
-
podFinishes?: ReadonlyArray<{ childRunId: string; prUrl: string }>;
|
|
28
|
+
podFinishes?: ReadonlyArray<{ childRunId: string; prUrl: string; state?: string; childError?: string }>;
|
|
23
29
|
/** 읽기 «실패»(원장·logs) — 부재(null)와 갈린다. 있으면 refresh 가 card-bind-failed 를 남긴다. */
|
|
24
30
|
readErrors?: readonly string[];
|
|
25
31
|
}
|
|
@@ -92,11 +98,39 @@ export function bindCardEvidence(card: Pick<TaskCard, 'runId'>, snapshot: CardRu
|
|
|
92
98
|
return { ledgerFound: true, ...(goalId ? { goalId } : {}), ...(pr ? { pr } : {}), ...(childRunId ? { childRunId } : {}) };
|
|
93
99
|
}
|
|
94
100
|
|
|
101
|
+
/** DRAFT-NOT-ARCHIVE — 원장의 마지막 `salvaged`(self-implement.draft-not-archive) 줄이 남긴 수확 가지. 없으면 undefined. */
|
|
102
|
+
export function salvageBranchOf(entries: readonly RunLedgerEntry[] | null | undefined): string | undefined {
|
|
103
|
+
return lastSalvaged(entries)?.branch;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function lastSalvaged(entries: readonly RunLedgerEntry[] | null | undefined): { branch: string; at: number; order: number } | undefined {
|
|
107
|
+
for (let at = (entries?.length ?? 0) - 1; at >= 0; at--) {
|
|
108
|
+
const entry = entries![at]!;
|
|
109
|
+
const branch = entry.event === 'salvaged' ? entry.data?.salvageBranch : undefined;
|
|
110
|
+
if (typeof branch === 'string' && branch.startsWith('salvage/')) return { branch, at: Date.parse(entry.timestamp ?? ''), order: at };
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** 카드 런(호스트 → 자식 나중 것부터)의 수확 가지. 원장을 못 읽었으면 `readErrors` 로 갈린다(⛔ «없음» 이 아니다). */
|
|
116
|
+
export async function readCardSalvageBranch(card: Pick<TaskCard, 'runId' | 'createdAt'>, deps: CardRunEvidenceDeps = {}): Promise<{ salvageBranch: string | null; readErrors?: readonly string[] }> {
|
|
117
|
+
if (!card.runId) return { salvageBranch: null };
|
|
118
|
+
const snapshot = await readCardRunSnapshot(card, { ...deps, podFinishes: () => [] });
|
|
119
|
+
// 호스트 ⊕ Pod 자식 원장 전체에서 «가장 나중»(타임스탬프) 수확 가지. 시각이 같거나 못 읽으면 호스트가 이긴다.
|
|
120
|
+
const candidates = [lastSalvaged(snapshot.host), ...podChildRunIds(snapshot.host, card.runId).map((id) => lastSalvaged(snapshot.children[id]))]
|
|
121
|
+
.filter((row): row is NonNullable<typeof row> => row !== undefined);
|
|
122
|
+
const latest = candidates.reduce<typeof candidates[number] | undefined>((best, row) => !best || (Number.isFinite(row.at) && (!Number.isFinite(best.at) || row.at > best.at)) ? row : best, undefined);
|
|
123
|
+
return { salvageBranch: latest?.branch ?? null, ...(snapshot.readErrors?.length ? { readErrors: snapshot.readErrors } : {}) };
|
|
124
|
+
}
|
|
125
|
+
|
|
95
126
|
/** 원장·logs 읽기(시험 주입). */
|
|
96
127
|
export interface CardRunEvidenceDeps {
|
|
97
128
|
loadLedger?: (runId: string) => RunLedgerEntry[] | null;
|
|
98
|
-
podFinishes?: (childRunIds: readonly string[], sinceIso: string) => Array<{ childRunId: string; prUrl: string }>;
|
|
129
|
+
podFinishes?: (childRunIds: readonly string[], sinceIso: string) => Array<{ childRunId: string; prUrl: string; state?: string; childError?: string }>;
|
|
99
130
|
now?: () => Date;
|
|
131
|
+
launchBound?: (runId: string) => boolean;
|
|
132
|
+
dispatchPod?: (runId: string, sinceIso: string) => boolean;
|
|
133
|
+
runExit?: (runId: string) => RunExit | undefined;
|
|
100
134
|
}
|
|
101
135
|
|
|
102
136
|
async function defaultLoadLedger(): Promise<(runId: string) => RunLedgerEntry[] | null> {
|
|
@@ -120,20 +154,30 @@ export function ledgerLoader(dirs: readonly string[], load: (runId: string, dir:
|
|
|
120
154
|
};
|
|
121
155
|
}
|
|
122
156
|
|
|
157
|
+
function defaultDispatchPod(runId: string, sinceIso: string): boolean {
|
|
158
|
+
const path = logsDbPath();
|
|
159
|
+
if (!existsSync(path)) return false;
|
|
160
|
+
const store = LogStore.openReadOnly(path);
|
|
161
|
+
try {
|
|
162
|
+
const sinceMs = Date.parse(sinceIso);
|
|
163
|
+
return store.queryAll({ exactCategories: ['harness.substrate'], events: ['dispatch-pod'], ...(Number.isFinite(sinceMs) ? { sinceMs } : {}) })
|
|
164
|
+
.some((row) => { try { return (JSON.parse(row.data ?? '{}') as { runId?: string }).runId === runId; } catch { return false; } });
|
|
165
|
+
} finally { store.close(); }
|
|
166
|
+
}
|
|
167
|
+
|
|
123
168
|
async function defaultPodFinishes(): Promise<NonNullable<CardRunEvidenceDeps['podFinishes']>> {
|
|
124
|
-
const { LogStore, logsDbPath } = await import('../mss/logging/log-store.js');
|
|
125
169
|
return (childRunIds, sinceIso) => {
|
|
126
170
|
const path = logsDbPath();
|
|
127
171
|
if (!childRunIds.length || !existsSync(path)) return [];
|
|
128
172
|
const store = LogStore.openReadOnly(path);
|
|
129
173
|
try {
|
|
130
174
|
const wanted = new Set(childRunIds);
|
|
131
|
-
const rows: Array<{ childRunId: string; prUrl: string }> = [];
|
|
175
|
+
const rows: Array<{ childRunId: string; prUrl: string; state?: string; childError?: string }> = [];
|
|
132
176
|
const sinceMs = Date.parse(sinceIso);
|
|
133
177
|
for (const row of store.queryAll({ exactCategories: ['self-implement.pod'], events: ['job-finished'], ...(Number.isFinite(sinceMs) ? { sinceMs } : {}) })) {
|
|
134
|
-
let data: { childRunId?: unknown; prUrl?: unknown } | null = null;
|
|
178
|
+
let data: { childRunId?: unknown; prUrl?: unknown; state?: unknown; childError?: unknown } | null = null;
|
|
135
179
|
try { data = row.data ? JSON.parse(row.data) : null; } catch { continue; }
|
|
136
|
-
if (typeof data?.childRunId === 'string' && wanted.has(data.childRunId)
|
|
180
|
+
if (typeof data?.childRunId === 'string' && wanted.has(data.childRunId)) rows.push({ childRunId: data.childRunId, prUrl: typeof data.prUrl === 'string' ? data.prUrl : '', ...(typeof data.state === 'string' ? { state: data.state } : {}), ...(typeof data.childError === 'string' ? { childError: data.childError } : {}) });
|
|
137
181
|
}
|
|
138
182
|
return rows;
|
|
139
183
|
} finally { store.close(); }
|
|
@@ -150,10 +194,9 @@ export async function readCardRunSnapshot(card: Pick<TaskCard, 'runId' | 'create
|
|
|
150
194
|
const host = safe(card.runId);
|
|
151
195
|
const childIds = podChildRunIds(host, card.runId);
|
|
152
196
|
const children = Object.fromEntries(childIds.map((id) => [id, safe(id)]));
|
|
153
|
-
let podFinishes:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
try { podFinishes = (deps.podFinishes ?? await defaultPodFinishes())(missing, card.createdAt); } catch (error) { readErrors.push(`logs: ${reason(error)}`); }
|
|
197
|
+
let podFinishes: NonNullable<CardRunSnapshot['podFinishes']> = [];
|
|
198
|
+
if (childIds.length && (!host || !lastPr(host))) {
|
|
199
|
+
try { podFinishes = (deps.podFinishes ?? await defaultPodFinishes())(childIds, card.createdAt); } catch (error) { readErrors.push(`logs: ${reason(error)}`); }
|
|
157
200
|
}
|
|
158
201
|
return { host, children, podFinishes, ...(readErrors.length ? { readErrors } : {}) };
|
|
159
202
|
}
|
|
@@ -171,6 +214,52 @@ export async function refreshCardRunBinding(cardId: string, statePath = taskAgen
|
|
|
171
214
|
try { debug.log('task-agent', 'card-bind-failed', { card: cardId, runId: card.runId, errors: snapshot.readErrors }); } catch { /* fail-soft */ }
|
|
172
215
|
}
|
|
173
216
|
const evidence = bindCardEvidence(card, snapshot);
|
|
217
|
+
if (card.status === 'launched' && !card.pr && !evidence.pr && !snapshot.readErrors?.length) {
|
|
218
|
+
const rows = [...(snapshot.host ?? []), ...Object.values(snapshot.children).flatMap((entries) => entries ?? [])];
|
|
219
|
+
let exit: RunExit | undefined;
|
|
220
|
+
let exitReadFailed = false;
|
|
221
|
+
try { exit = (deps.runExit ?? ((id) => readRunExits(effectiveInstanceRoot()).find((row) => row.runId === id)))(card.runId); }
|
|
222
|
+
catch { exitReadFailed = true; }
|
|
223
|
+
const finished = [...(snapshot.podFinishes ?? [])].reverse().find((item) => item.state === 'failed');
|
|
224
|
+
const parentTerminal = [...(snapshot.host ?? [])].reverse().find((item) => item.event === 'run-status');
|
|
225
|
+
const parentFailed = parentTerminal?.data?.runStatus === 'failed';
|
|
226
|
+
const boundAt = card.history.find((item) => item.event === 'run-bound')?.at;
|
|
227
|
+
const elapsed = boundAt ? (deps.now ?? (() => new Date()))().getTime() - Date.parse(boundAt) : NaN;
|
|
228
|
+
const launched = rows.some((entry) => entry.event === 'pod-child-run');
|
|
229
|
+
let dispatched = false;
|
|
230
|
+
let dispatchReadFailed = false;
|
|
231
|
+
try { dispatched = (deps.dispatchPod ?? defaultDispatchPod)(card.runId, boundAt ?? card.createdAt); }
|
|
232
|
+
catch { dispatchReadFailed = true; }
|
|
233
|
+
let launchBound = false;
|
|
234
|
+
let launchReadFailed = false;
|
|
235
|
+
try { launchBound = (deps.launchBound ?? ((id) => !!readLaunchBinding(id)))(card.runId); }
|
|
236
|
+
catch { launchReadFailed = true; }
|
|
237
|
+
const timedOut = !finished && !launched && !dispatched && !dispatchReadFailed && !exit && !exitReadFailed && !launchBound && !launchReadFailed && Number.isFinite(elapsed) && elapsed >= 600_000;
|
|
238
|
+
const hasPr = !!card.pr || !!evidence.pr;
|
|
239
|
+
const exitHarvestBranch = exit?.lastLines?.some((line) => /(?:수확할 브랜치: |ELANOUS_POD_SALVAGE )(?:salvage\/|self-impl\/)[^\s]+/.test(line)) ?? false;
|
|
240
|
+
const ledgerFailure = rows.find((entry) => entry.event === 'run-status' && entry.data?.runStatus === 'failed'
|
|
241
|
+
&& (entry.data?.failureKind === STOP_CLASS_POD_FAILURE || entry.data?.stop_class === STOP_CLASS_POD_FAILURE));
|
|
242
|
+
const resultFailure = rows.find((entry) => entry.event === 'result-without-error' && entry.data?.ok === false);
|
|
243
|
+
const noLaunchExit = exit?.reason === STOP_CLASS_NO_LAUNCH && exit.status !== null && exit.status !== 0 && !launched && !dispatched && !dispatchReadFailed;
|
|
244
|
+
// 발사 근거가 있는 런이 nonzero 로 끝났고 분류기가 pod-failure 나 unknown(원인 미상)을 냈다 = 죽은 런(TA-JUDGE-DEAD-RUN ③ «exit 1 · unknown/pod-failure»).
|
|
245
|
+
const deadExit = (exit?.reason === STOP_CLASS_POD_FAILURE || exit?.reason === 'unknown') && exit.status !== null && exit.status !== 0;
|
|
246
|
+
const podFailure = !noLaunchExit && (launched || dispatched) && (parentFailed && (finished || !!ledgerFailure || !!resultFailure) || deadExit)
|
|
247
|
+
&& (finished || ledgerFailure || resultFailure || deadExit);
|
|
248
|
+
const failure = podFailure ? judgeNextMove({ terminalRun: {
|
|
249
|
+
kind: STOP_CLASS_POD_FAILURE,
|
|
250
|
+
reason: finished?.childError || (exit?.status != null ? `exit ${exit.status} · ${exit.reason || STOP_CLASS_POD_FAILURE}` : resultFailure ? 'result-without-error · failed' : finished ? `job-finished failed · ${STOP_CLASS_POD_FAILURE}` : `run-status failed · ${STOP_CLASS_POD_FAILURE}`),
|
|
251
|
+
hasPr,
|
|
252
|
+
hasHarvestBranch: exitHarvestBranch || rows.some((entry) => typeof entry.data?.harvestBranch === 'string' && !!entry.data.harvestBranch) || !!salvageBranchOf(rows),
|
|
253
|
+
} }) : noLaunchExit ? judgeNextMove({ terminalRun: { kind: STOP_CLASS_NO_LAUNCH, reason: `exit ${exit!.status} · ${exit!.reason}`, hasPr, hasHarvestBranch: false } })
|
|
254
|
+
: timedOut ? judgeNextMove({ terminalRun: { kind: STOP_CLASS_NO_LAUNCH, reason: 'card-run-bound 뒤 10분 동안 Pod 발사 근거 없음', hasPr: false, hasHarvestBranch: false } }) : undefined;
|
|
255
|
+
if (failure?.reason.startsWith('failed/')) {
|
|
256
|
+
const at = (deps.now ?? (() => new Date()))().toISOString();
|
|
257
|
+
const next = updateTaskCard(statePath, cardId, (current) => current?.status === 'launched' && current.runId === card.runId && !current.pr && current.refSource !== 'manual' ? {
|
|
258
|
+
...current, status: 'failed', history: [...current.history, { at, event: 'failed', detail: failure.reason, runId: card.runId }],
|
|
259
|
+
} : undefined);
|
|
260
|
+
return { card: next, evidence };
|
|
261
|
+
}
|
|
262
|
+
}
|
|
174
263
|
// 이미 묶인 PR 이 있으면 «같은» PR(번호 ⊕ 낸 자식)을 다시 찾았을 때만 뒤늦은 골을 채운다.
|
|
175
264
|
const samePr = (current: TaskCard) => current.pr === undefined
|
|
176
265
|
|| (evidence.pr !== undefined && cardPrNumber(current) === evidence.pr.number && current.runChildId === evidence.childRunId);
|
package/src/task-agent/judge.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* RFC-task-agent-any-task-to-completion-2026-10-06 §A2 표를 순수 함수로 옮긴다.
|
|
5
5
|
* 부작용이 없다(발사·리뷰·착지는 실행부 `actions.ts` 의 몫). 수확 원천 = run-c9ad6d6d `chooseTaskAction`.
|
|
6
6
|
*/
|
|
7
|
+
import { STOP_CLASS_POD_FAILURE, STOP_CLASS_NO_LAUNCH } from '../task-orchestrator/surfaces/pod-failure-reason.js';
|
|
7
8
|
import type { SupervisorStopReason } from '../self-dev/run-supervisor.js';
|
|
8
9
|
import type { NextActionKind, RetryVariant } from './actions.js';
|
|
9
10
|
import type { CompletionKind } from './task-hand.js';
|
|
@@ -33,6 +34,8 @@ export interface TaskJudgeInput {
|
|
|
33
34
|
completion?: CompletionKind;
|
|
34
35
|
/** code-pr 밖 종류의 확인 증거(`completion-evidence.ts` 판독). 비면 «아직 못 받았다» — 통과로 읽지 않는다. */
|
|
35
36
|
evidence?: { ok: boolean; ref: string };
|
|
37
|
+
/** Only classified terminal evidence; an absent input preserves the existing stop-reason table. */
|
|
38
|
+
terminalRun?: { kind: typeof STOP_CLASS_POD_FAILURE | typeof STOP_CLASS_NO_LAUNCH; reason: string; hasPr: boolean; hasHarvestBranch: boolean };
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
export interface TaskJudgement {
|
|
@@ -73,6 +76,10 @@ function base(input: TaskJudgeInput): Omit<TaskJudgement, 'executorKind'> {
|
|
|
73
76
|
if (input.deliveryEvidence) return input.deliveryEvidence.ok
|
|
74
77
|
? { move: 'propose-green', stage: 'closing', reason: `전달 근거 확인: ${input.deliveryEvidence.files.join(', ')}` }
|
|
75
78
|
: { move: 'wait', stage: 'waiting', reason: input.deliveryEvidence.reason };
|
|
79
|
+
if (input.terminalRun && !input.terminalRun.hasPr && !input.terminalRun.hasHarvestBranch) {
|
|
80
|
+
const disposition = input.terminalRun.kind === STOP_CLASS_POD_FAILURE ? 'needs-owner' : 'needs-relaunch';
|
|
81
|
+
return { move: 'wait', stage: 'escalated', reason: `failed/${disposition} — ${input.terminalRun.reason.replace(/[\r\n]+/gu, ' ').trim() || input.terminalRun.kind}` };
|
|
82
|
+
}
|
|
76
83
|
const stop = input.stopReason;
|
|
77
84
|
const kind = input.completion;
|
|
78
85
|
if (kind !== undefined && kind !== 'code-pr') {
|
|
@@ -8,8 +8,11 @@
|
|
|
8
8
|
* - 관측: 실행한 수마다 `debug.log('task-agent', 'live-move', {kind, card, ok, detail, live:true})`.
|
|
9
9
|
*/
|
|
10
10
|
import { spawn, spawnSync } from 'node:child_process';
|
|
11
|
-
import {
|
|
11
|
+
import { statSync } from 'node:fs';
|
|
12
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
12
13
|
import { debug } from '../debug/log.js';
|
|
14
|
+
import { findGitDir } from '../git-fs/locate.js';
|
|
15
|
+
import { getUserConfig } from '../user-config.js';
|
|
13
16
|
import type { DetachedSpawn } from '../cli/tasks-cli.js';
|
|
14
17
|
import { taskAgentStatePath, updateTaskCard, type TaskCard } from './task-hand.js';
|
|
15
18
|
|
|
@@ -53,22 +56,98 @@ export async function configuredTaskAgentLiveMoves(): Promise<ReadonlySet<TaskAg
|
|
|
53
56
|
export interface LiveMoveDeps {
|
|
54
57
|
statePath?: string;
|
|
55
58
|
now?: () => Date;
|
|
56
|
-
/** PR 머리 sha·상태 — 시험은 주입한다. 기본은 `gh pr view`(작업 트리 cwd). */
|
|
57
|
-
prHead?: (pr: number, cwd?: string) => Promise<
|
|
59
|
+
/** PR 머리 sha·상태(·브랜치·URL) — 시험은 주입한다. 기본은 `gh pr view`(작업 트리 cwd). */
|
|
60
|
+
prHead?: (pr: number, cwd?: string) => Promise<PrHeadView | null>;
|
|
61
|
+
/** TA-LIVE-REVIEW-POD — 작업 트리가 없는 런(Pod)의 리뷰 저장소 후보. 기본은 카드 project.target → harness.defaultRepo → 호스트 checkout. */
|
|
62
|
+
repoCandidates?: (card: TaskCard) => readonly ReviewRepoCandidate[];
|
|
58
63
|
/** 리뷰 요청(떼어 띄운다) — 시험은 반드시 주입한다. */
|
|
59
64
|
requestReview?: (pr: number, intent: string, cwd?: string) => Promise<void>;
|
|
60
65
|
log?: (category: string, event: string, data: Record<string, unknown>) => void;
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
/** `executed` = 부작용을 실제로 냈다(리뷰를 띄웠다 · 제안을 적었다). ok 이지만 이미 한 수(같은 머리·이미 제안)면 false. */
|
|
64
|
-
export interface LiveMoveResult {
|
|
69
|
+
export interface LiveMoveResult {
|
|
70
|
+
kind: TaskAgentLiveMove; card: string | null; ok: boolean; executed: boolean; detail: string;
|
|
71
|
+
/** TA-LIVE-REVIEW-POD — 작업 트리 없이 리뷰 저장소를 찾았을 때(또는 못 찾았을 때)의 근거 한 줄. */
|
|
72
|
+
reviewRepo?: { resolved: boolean; source?: ReviewRepoCandidate['source']; cwd?: string; proof?: 'head-sha' | 'branch'; reason: string };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface PrHeadView { head: string; state: string; branch?: string; url?: string }
|
|
76
|
+
|
|
77
|
+
/** 리뷰 저장소 후보 — 과제를 넘길 때 카드가 안 대상 · 하니스 기본 저장소(harness.defaultRepo · harness.repo 의 로컬 뿌리) · 호스트 checkout. */
|
|
78
|
+
export interface ReviewRepoCandidate { source: 'card.project' | 'harness.defaultRepo' | 'host'; cwd: string }
|
|
79
|
+
|
|
80
|
+
/** 기본 후보 — 존재하는 디렉터리만 · 같은 경로는 한 번. 읽기 실패는 그 후보를 뺀다(없음 ≠ 통과). */
|
|
81
|
+
export function defaultReviewRepoCandidates(card: TaskCard, opts: { defaultRepo?: () => string | undefined; hostRoot?: () => string | undefined } = {}): ReviewRepoCandidate[] {
|
|
82
|
+
const raw: ReviewRepoCandidate[] = [];
|
|
83
|
+
if (card.project?.target && isAbsolute(card.project.target)) raw.push({ source: 'card.project', cwd: card.project.target });
|
|
84
|
+
try {
|
|
85
|
+
const configured = opts.defaultRepo ? opts.defaultRepo() : getUserConfig().harness?.defaultRepo;
|
|
86
|
+
if (configured && isAbsolute(configured)) raw.push({ source: 'harness.defaultRepo', cwd: configured });
|
|
87
|
+
} catch { /* 후보에서 뺀다 */ }
|
|
88
|
+
try {
|
|
89
|
+
const host = opts.hostRoot ? opts.hostRoot() : findGitDir(process.cwd())?.root;
|
|
90
|
+
if (host) raw.push({ source: 'host', cwd: host });
|
|
91
|
+
} catch { /* 후보에서 뺀다 */ }
|
|
92
|
+
const seen = new Set<string>();
|
|
93
|
+
const out: ReviewRepoCandidate[] = [];
|
|
94
|
+
for (const candidate of raw) {
|
|
95
|
+
if (!isAbsolute(candidate.cwd)) continue;
|
|
96
|
+
const cwd = resolve(candidate.cwd);
|
|
97
|
+
if (seen.has(cwd)) continue;
|
|
98
|
+
seen.add(cwd);
|
|
99
|
+
try { if (statSync(cwd).isDirectory()) out.push({ source: candidate.source, cwd }); } catch { /* 없는 디렉터리는 뺀다 */ }
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* TA-LIVE-REVIEW-POD — 작업 트리가 없을 때(Pod 런) 리뷰 cwd 를 정한다.
|
|
106
|
+
* 후보 저장소에서 `gh pr view <pr>` 가 «런이 낸 머리»(기록된 sha · 없으면 런의 브랜치)를 돌려줄 때만 그 저장소를 쓴다 —
|
|
107
|
+
* «다른 저장소에서 gh·self review 를 돌리지 않는다»(TA-JUDGE-LIVE-SAFE)는 계약을 지킨다. 못 정하면 이유 한 줄.
|
|
108
|
+
*/
|
|
109
|
+
export async function resolveReviewRepo(
|
|
110
|
+
card: TaskCard, pr: number,
|
|
111
|
+
produced: { headCommit?: string; branch?: string; prUrl?: string } | undefined,
|
|
112
|
+
deps: LiveMoveDeps = {},
|
|
113
|
+
): Promise<{ resolved: true; cwd: string; source: ReviewRepoCandidate['source']; proof: 'head-sha' | 'branch'; view: PrHeadView; reason: string } | { resolved: false; reason: string }> {
|
|
114
|
+
// 호출자가 런 근거를 «안 넘긴» 것과 런이 «기록하지 않은» 것은 다른 사실이다 — 섞어 적지 않는다.
|
|
115
|
+
if (produced === undefined) return { resolved: false, reason: 'caller passed no run evidence (produced) — head/branch not checked' };
|
|
116
|
+
// 기록된 머리가 «있으면» 그것만 근거다 — 모양이 틀린 sha 를 브랜치로 대체하지 않는다(확인 못 한 머리로 리뷰하지 않게).
|
|
117
|
+
if (produced?.headCommit !== undefined && !/^[0-9a-f]{40}$/i.test(produced.headCommit)) {
|
|
118
|
+
return { resolved: false, reason: `run head ${JSON.stringify(produced.headCommit.slice(0, 40))} is not a full commit sha` };
|
|
119
|
+
}
|
|
120
|
+
const expectedHead = produced?.headCommit?.toLowerCase();
|
|
121
|
+
const expectedBranch = produced?.branch?.trim() || undefined;
|
|
122
|
+
if (!expectedHead && !expectedBranch) return { resolved: false, reason: 'run result carries no PR head or branch to confirm against' };
|
|
123
|
+
let candidates: readonly ReviewRepoCandidate[];
|
|
124
|
+
try { candidates = (deps.repoCandidates ?? defaultReviewRepoCandidates)(card); } catch { candidates = []; }
|
|
125
|
+
if (!candidates.length) return { resolved: false, reason: 'no target repository (card project · harness.defaultRepo · host checkout)' };
|
|
126
|
+
const sameUrl = (a: string, b: string) => a.trim().replace(/\/+$/, '').toLowerCase() === b.trim().replace(/\/+$/, '').toLowerCase();
|
|
127
|
+
const misses: string[] = [];
|
|
128
|
+
for (const candidate of candidates) {
|
|
129
|
+
let view: PrHeadView | null;
|
|
130
|
+
try { view = await (deps.prHead ?? defaultPrHead)(pr, candidate.cwd); } catch { view = null; }
|
|
131
|
+
if (!view) { misses.push(`${candidate.source}: PR #${pr} unreadable`); continue; }
|
|
132
|
+
if (produced?.prUrl && view.url && !sameUrl(view.url, produced.prUrl)) { misses.push(`${candidate.source}: PR url ${view.url} ≠ run ${produced.prUrl}`); continue; }
|
|
133
|
+
if (expectedHead) {
|
|
134
|
+
if (view.head.toLowerCase() !== expectedHead) { misses.push(`${candidate.source}: head ${view.head.slice(0, 12)} ≠ run ${expectedHead.slice(0, 12)}`); continue; }
|
|
135
|
+
return { resolved: true, cwd: candidate.cwd, source: candidate.source, proof: 'head-sha', view, reason: `${candidate.source} PR #${pr} head matches run` };
|
|
136
|
+
}
|
|
137
|
+
if (view.branch !== expectedBranch) { misses.push(`${candidate.source}: branch ${view.branch ?? '?'} ≠ run ${expectedBranch}`); continue; }
|
|
138
|
+
// 브랜치 근거는 sha 보다 약하다 — 같은 PR(저장소 · 번호)임을 URL 로 «양쪽 다» 확인할 때만 쓴다.
|
|
139
|
+
if (!produced?.prUrl || !view.url) { misses.push(`${candidate.source}: branch matches but PR url unconfirmed (run ${produced?.prUrl ?? 'none'} · repo ${view.url ?? 'none'})`); continue; }
|
|
140
|
+
return { resolved: true, cwd: candidate.cwd, source: candidate.source, proof: 'branch', view, reason: `${candidate.source} PR #${pr} head branch matches run` };
|
|
141
|
+
}
|
|
142
|
+
return { resolved: false, reason: misses.join(' · ') };
|
|
143
|
+
}
|
|
65
144
|
|
|
66
145
|
/** 받은 환경 그대로 먼저 묻고, 실패하면 프록시만 뺀 환경으로 한 번 더(이 저장소의 gh 운영 관행 — 프록시 경유가 gh 를 깨는 호스트). */
|
|
67
146
|
export type GhRun = (args: string[], options: { env: NodeJS.ProcessEnv; cwd?: string }) => { status: number | null; stdout: string };
|
|
68
147
|
const runGh: GhRun = (args, options) => spawnSync('gh', args, { encoding: 'utf8', timeout: 30_000, env: options.env, ...(options.cwd ? { cwd: options.cwd } : {}) });
|
|
69
148
|
|
|
70
|
-
export async function defaultPrHead(pr: number, cwd?: string, gh: GhRun = runGh): Promise<
|
|
71
|
-
const view = (env: NodeJS.ProcessEnv) => gh(['pr', 'view', String(pr), '--json', 'headRefOid,state'], { env, ...(cwd ? { cwd } : {}) });
|
|
149
|
+
export async function defaultPrHead(pr: number, cwd?: string, gh: GhRun = runGh): Promise<PrHeadView | null> {
|
|
150
|
+
const view = (env: NodeJS.ProcessEnv) => gh(['pr', 'view', String(pr), '--json', 'headRefOid,headRefName,state,url'], { env, ...(cwd ? { cwd } : {}) });
|
|
72
151
|
let out = view(process.env);
|
|
73
152
|
if (out.status !== 0) {
|
|
74
153
|
const noProxy = { ...process.env };
|
|
@@ -77,9 +156,13 @@ export async function defaultPrHead(pr: number, cwd?: string, gh: GhRun = runGh)
|
|
|
77
156
|
}
|
|
78
157
|
if (out.status !== 0) return null;
|
|
79
158
|
try {
|
|
80
|
-
const value = JSON.parse(out.stdout) as { headRefOid?: unknown; state?: unknown };
|
|
159
|
+
const value = JSON.parse(out.stdout) as { headRefOid?: unknown; headRefName?: unknown; state?: unknown; url?: unknown };
|
|
81
160
|
return typeof value.headRefOid === 'string' && /^[a-f0-9]{40}$/i.test(value.headRefOid) && typeof value.state === 'string'
|
|
82
|
-
? {
|
|
161
|
+
? {
|
|
162
|
+
head: value.headRefOid, state: value.state,
|
|
163
|
+
...(typeof value.headRefName === 'string' && value.headRefName ? { branch: value.headRefName } : {}),
|
|
164
|
+
...(typeof value.url === 'string' && value.url ? { url: value.url } : {}),
|
|
165
|
+
} : null;
|
|
83
166
|
} catch { return null; }
|
|
84
167
|
}
|
|
85
168
|
|
|
@@ -121,16 +204,33 @@ function observe(deps: LiveMoveDeps, result: LiveMoveResult): LiveMoveResult {
|
|
|
121
204
|
* 다음 틱에 한 번 더 요청될 수 있다(초과는 많아야 한 번 · 리뷰는 읽기 전용이라 착지·재발사로 번지지 않는다).
|
|
122
205
|
*/
|
|
123
206
|
/** `review` — PR 머리마다 한 번. 카드에 먼저 적고(잠금 안) 그다음 띄운다 · 띄우기 실패는 카드에 오류로 남기고 같은 머리는 다시 안 띄운다. */
|
|
124
|
-
export async function executeLiveReview(
|
|
207
|
+
export async function executeLiveReview(
|
|
208
|
+
card: TaskCard | undefined, pr: number | undefined,
|
|
209
|
+
ctx: { runId: string | null; stopReason: string; cwd?: string; produced?: { headCommit?: string; branch?: string; prUrl?: string } },
|
|
210
|
+
deps: LiveMoveDeps = {},
|
|
211
|
+
): Promise<LiveMoveResult> {
|
|
125
212
|
const kind = 'review' as const;
|
|
126
213
|
if (!card) return observe(deps, { kind, executed: false, card: null, ok: false, detail: 'no task card — stays shadow' });
|
|
127
214
|
if (!pr || !Number.isSafeInteger(pr) || pr < 1) return observe(deps, { kind, executed: false, card: card.id, ok: false, detail: 'no PR — stays shadow' });
|
|
128
|
-
// PR 의
|
|
129
|
-
|
|
130
|
-
let
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (
|
|
215
|
+
// PR 의 저장소를 모르면 보류한다 — 다른 저장소에서 gh·self review 를 돌리지 않게. 작업 트리가 없으면(Pod 런 ·
|
|
216
|
+
// TA-LIVE-REVIEW-POD) 후보 저장소 중 «런이 낸 PR 머리»가 확인되는 곳만 쓰고, 못 정하면 이유 한 줄을 관측에 싣는다.
|
|
217
|
+
let cwd = ctx.cwd;
|
|
218
|
+
let head: PrHeadView | null = null;
|
|
219
|
+
let reviewRepo: LiveMoveResult['reviewRepo'];
|
|
220
|
+
if (!cwd) {
|
|
221
|
+
const found = await resolveReviewRepo(card, pr, ctx.produced, deps);
|
|
222
|
+
if (!found.resolved) {
|
|
223
|
+
return observe(deps, { kind, executed: false, card: card.id, ok: false, detail: `PR #${pr} worktree unknown — stays shadow`, reviewRepo: { resolved: false, reason: found.reason } });
|
|
224
|
+
}
|
|
225
|
+
cwd = found.cwd;
|
|
226
|
+
head = found.view;
|
|
227
|
+
reviewRepo = { resolved: true, source: found.source, cwd: found.cwd, proof: found.proof, reason: found.reason };
|
|
228
|
+
} else {
|
|
229
|
+
try { head = await (deps.prHead ?? defaultPrHead)(pr, cwd); } catch { head = null; }
|
|
230
|
+
}
|
|
231
|
+
const withRepo = (result: LiveMoveResult): LiveMoveResult => reviewRepo ? { ...result, reviewRepo } : result;
|
|
232
|
+
if (!head) return observe(deps, withRepo({ kind, executed: false, card: card.id, ok: false, detail: `PR #${pr} head unknown` }));
|
|
233
|
+
if (head.state !== 'OPEN') return observe(deps, withRepo({ kind, executed: false, card: card.id, ok: false, detail: `PR #${pr} is ${head.state}` }));
|
|
134
234
|
const path = deps.statePath ?? taskAgentStatePath();
|
|
135
235
|
const at = (deps.now ?? (() => new Date()))().toISOString();
|
|
136
236
|
const claim = { claimed: false, missing: false };
|
|
@@ -141,18 +241,18 @@ export async function executeLiveReview(card: TaskCard | undefined, pr: number |
|
|
|
141
241
|
claim.claimed = true;
|
|
142
242
|
return { ...current, reviewRequests: [...(current.reviewRequests ?? []), { pr, head: head!.head, at }] };
|
|
143
243
|
});
|
|
144
|
-
if (claim.missing) return observe(deps, { kind, executed: false, card: card.id, ok: false, detail: 'task card not in state file' });
|
|
145
|
-
if (!claim.claimed) return observe(deps, { kind, executed: false, card: card.id, ok: true, detail: `already requested for #${pr} head ${head.head.slice(0, 12)}` });
|
|
244
|
+
if (claim.missing) return observe(deps, withRepo({ kind, executed: false, card: card.id, ok: false, detail: 'task card not in state file' }));
|
|
245
|
+
if (!claim.claimed) return observe(deps, withRepo({ kind, executed: false, card: card.id, ok: true, detail: `already requested for #${pr} head ${head.head.slice(0, 12)}` }));
|
|
146
246
|
try {
|
|
147
|
-
await (deps.requestReview ?? defaultRequestReview)(pr, liveReviewIntent(card, ctx.runId, ctx.stopReason),
|
|
247
|
+
await (deps.requestReview ?? defaultRequestReview)(pr, liveReviewIntent(card, ctx.runId, ctx.stopReason), cwd);
|
|
148
248
|
} catch (error) {
|
|
149
249
|
const reason = error instanceof Error ? error.message : String(error);
|
|
150
250
|
updateTaskCard(path, card.id, (current) => current?.reviewRequests?.some((entry) => entry.pr === pr && entry.head === head!.head && entry.at === at)
|
|
151
251
|
? { ...current, reviewRequests: current.reviewRequests.map((entry) => entry.pr === pr && entry.head === head!.head && entry.at === at ? { ...entry, error: reason } : entry) }
|
|
152
252
|
: undefined);
|
|
153
|
-
return observe(deps, { kind, executed: false, card: card.id, ok: false, detail: `review request failed for #${pr} head ${head.head.slice(0, 12)}: ${reason}` });
|
|
253
|
+
return observe(deps, withRepo({ kind, executed: false, card: card.id, ok: false, detail: `review request failed for #${pr} head ${head.head.slice(0, 12)}: ${reason}` }));
|
|
154
254
|
}
|
|
155
|
-
return observe(deps, { kind, executed: true, card: card.id, ok: true, detail: `review requested for #${pr} head ${head.head.slice(0, 12)}` });
|
|
255
|
+
return observe(deps, withRepo({ kind, executed: true, card: card.id, ok: true, detail: `review requested for #${pr} head ${head.head.slice(0, 12)}` }));
|
|
156
256
|
}
|
|
157
257
|
|
|
158
258
|
/** `propose-green` — 비지 않은 근거가 있을 때만 카드에 제안을 적는다(한 번). 체크리스트는 쓰지 않는다. */
|
|
@@ -296,7 +296,7 @@ export function recordPieceRef(pieceId: string, ref: { pr?: number; goalId?: str
|
|
|
296
296
|
|
|
297
297
|
/** 이미 넘긴 조각인가 — live 는 live 넘김·발사·발사 실패면 다시 안 넘긴다 · shadow 는 어떤 넘김이든 있으면 안 넘긴다. */
|
|
298
298
|
function alreadyHanded(card: TaskCard, mode: 'shadow' | 'live'): boolean {
|
|
299
|
-
if (card.status === 'launched' || card.status === 'launch-failed') return true;
|
|
299
|
+
if (card.status === 'launched' || card.status === 'launch-failed' || card.status === 'failed') return true;
|
|
300
300
|
if (!card.handed) return false;
|
|
301
301
|
return mode === 'shadow' || card.handed.mode === 'live';
|
|
302
302
|
}
|
package/src/task-agent/shadow.ts
CHANGED
|
@@ -227,7 +227,16 @@ export async function recordTaskAgentShadowMove(input: TaskAgentShadowInput, dep
|
|
|
227
227
|
if (live) {
|
|
228
228
|
if (liveKind === 'review') {
|
|
229
229
|
liveMove = await executeLiveReview(boundCard, prResult?.prNumber,
|
|
230
|
-
{
|
|
230
|
+
{
|
|
231
|
+
runId: input.runId, stopReason: input.stopReason,
|
|
232
|
+
...(prResult?.worktreePath ? { cwd: prResult.worktreePath } : {}),
|
|
233
|
+
// TA-LIVE-REVIEW-POD — 작업 트리가 없는 런(Pod)은 «런이 낸 머리»로 리뷰 저장소를 확인한다.
|
|
234
|
+
...(prResult && !prResult.worktreePath ? { produced: {
|
|
235
|
+
...(prResult.checkedHeadCommit !== undefined ? { headCommit: prResult.checkedHeadCommit } : {}),
|
|
236
|
+
...(prResult.branch ? { branch: prResult.branch } : {}),
|
|
237
|
+
...(prResult.prUrl ? { prUrl: prResult.prUrl } : {}),
|
|
238
|
+
} } : {}),
|
|
239
|
+
}, { ...(deps.log ? { log: deps.log } : {}), ...deps.live });
|
|
231
240
|
} else {
|
|
232
241
|
const evidence: Record<string, string> = {};
|
|
233
242
|
if (boundCard && !delivery && judgeInput.prState === 'MERGED' && prResult) evidence[boundCard.id] = `#${prResult.prNumber}`;
|