elanous 0.2.15 → 0.2.16
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-b46d45b705e8c9ae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{9103-7a3ae7f60af3b99f.js → 9103-60fbef33e66f0a46.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-e05a77df7e005b04.js → page-3cb52e5ea8fc2fa5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-5491c115b1f9a923.js → page-c1e20d7d338713f1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-b8c49f6b4fa2df91.js → page-482c5dbde699947c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-976813c91d7394ea.js → page-8c2d9526164f5d09.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-a0e2a6d1bfb52fc5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-72d3e7ff35a8c954.js → page-c53598cfce6156f2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-60c7a8e1a4fe62ce.js → page-13a63953f53c6a23.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-2a52b316cb143d9c.js → page-5d3c8429d04f3831.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-c3ce483c8d5db370.js → page-3193bdc0bbab047a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-96f074f6538e8a2c.js → page-6107ca8ddd5e3bdd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-e97be5334d83a9f5.js → layout-dce686e582c23937.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-1a61c7ca1ceb3ae7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-955ec2da3be4c560.js → page-51f811fa119308b6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-9f88fbc15d9058d1.js → page-dacff46049e1557e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-c1343f9499d86f3f.js → page-4167348049166852.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-8739a2e0ae793130.js → not-found-de52e8fdf9095f7a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/{page-c9fc5706b8abaa11.js → page-36a7d994f4ca0299.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-d23b12bb92242558.js → page-ca3952cd78a83ca2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-5b042736aa113756.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-c7fea475488201a8.js → page-acb749f1e432bcf3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-5efbf81124c474ae.js → page-1ebfe049c640e171.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-85f1a1fc0a82738c.js → page-57924393e3e4e539.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-06ee266953d9c879.js → page-31fa1ce4c4aff9ae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-153c2b3bcc89e8de.js → page-0f5cd57ca928f42b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-abe8e3b74bea1ffc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-0d6d23b40263a7ea.js → page-d28573d6792b4275.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-59f03fda76c03d22.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-d751de5f8062ff5e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-f5597e894c02cd33.js → page-fe1759f94003b32b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/{page-0337fdf7fb5e37b6.js → page-d64c984e855c8c68.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-8fcec4c9ea2e7f70.js → page-9afdee66b820c76e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-f7e631e9b4daf66e.js → page-d4aef67abf264874.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-1ea64acadd704ace.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-9817c7f9007466cc.js → page-3544f154c87b43ee.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.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/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/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/measure/author-section-contribution.ts +84 -0
- package/scripts/release-loop/cut-branch.ts +43 -13
- package/scripts/release-loop/gate-node.ts +3 -1
- package/scripts/release-loop/vault-note-node.ts +19 -2
- package/scripts/rhythm/monthly.ts +97 -0
- package/src/acp/client.ts +17 -2
- package/src/agent-mission/driver.ts +51 -1
- 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/briefing/brief-items.ts +191 -36
- package/src/budget/usage-outcomes.ts +204 -0
- package/src/cli/brief-cli.ts +73 -0
- package/src/cli/know-cli.ts +20 -0
- package/src/cli/logs-cli.ts +43 -1
- package/src/cli/pr-cli.ts +80 -12
- package/src/cli/release-cli.ts +11 -8
- package/src/cli/self-update.ts +200 -1
- package/src/cli/usage-cli.ts +26 -0
- package/src/discord-self-message.ts +9 -0
- package/src/flow-loop/split.ts +24 -0
- package/src/harness/harness-cli-command.ts +36 -2
- package/src/harness/harness-pod-dispatch.ts +25 -55
- 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 +114 -0
- package/src/hq/hq.ts +8 -2
- package/src/hq/lease.ts +5 -3
- package/src/hq/transfer.ts +185 -0
- package/src/index.ts +112 -6
- package/src/intake-plane/digest.ts +48 -26
- 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/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/placement.ts +17 -8
- package/src/seat-dispatch/seat-ask.ts +33 -6
- package/src/seat-loop/seat-loop.ts +90 -3
- package/src/self-dev/ask-launch-flow.ts +22 -1
- 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-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/goal-author.ts +19 -13
- package/src/self-implement/heal-triage.ts +44 -0
- package/src/self-implement/orchestrator.ts +138 -7
- 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-cards/card-cli.ts +25 -0
- package/src/task-cards/card-store.ts +5 -3
- 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 +14 -5
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +37 -1
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +19 -10
- package/src/task-orchestrator/surfaces/self-implement.ts +2 -0
- package/src/telegram-commands.ts +5 -0
- package/src/telegram.ts +47 -0
- package/src/user-config.ts +36 -3
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-0e2c57fe6add4b2a.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/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/control/page-1d4901eb7c2d8822.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/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/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/trace/page-73735b64bfe1ae67.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 → rv9cierV0jSOvDBm1g0XB}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{LZcstaO9_NXypPFmujolv → rv9cierV0jSOvDBm1g0XB}/_ssgManifest.js +0 -0
|
@@ -156,6 +156,50 @@ export interface HealTriageResult {
|
|
|
156
156
|
readonly evidence: readonly string[];
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/** 수리 한도를 다 쓴 런의 결함 셋. 힐 루프가 이 셋 중 하나만 고른다. */
|
|
160
|
+
export type ReworkCapDefectClass = 'test-defect' | 'goal-defect' | 'code-defect';
|
|
161
|
+
|
|
162
|
+
/** 분류 뒤 힐 루프가 고르는 다음 행동. */
|
|
163
|
+
export type ReworkCapNextAction = 'harvest' | 'relaunch' | 'card';
|
|
164
|
+
|
|
165
|
+
export interface ReworkCapHealInput {
|
|
166
|
+
/** 게이트가 이번 diff 가 들여온 실패 수. 없으면 0. */
|
|
167
|
+
readonly introduced?: readonly string[];
|
|
168
|
+
/** 마지막 리뷰 must-fix. 없으면 빈 목록. */
|
|
169
|
+
readonly mustFix?: readonly string[];
|
|
170
|
+
readonly summary?: string;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface ReworkCapHealDecision {
|
|
174
|
+
readonly defectClass: ReworkCapDefectClass;
|
|
175
|
+
readonly nextAction: ReworkCapNextAction;
|
|
176
|
+
readonly evidence: readonly string[];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const GOAL_DEFECT = /골|goal|acceptance|수용 기준|요구가 모순|스펙|경계:/i;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 수리 한도 소진 런을 «시험 결함 · 골 결함 · 코드 결함» 중 하나로 가르고
|
|
183
|
+
* 수확·재발사·카드 중 하나를 고른다. 근거는 게이트 introduced 목록과 마지막 리뷰 지적이다.
|
|
184
|
+
* 도입 실패가 있으면 코드 결함(재발사). 리뷰가 골·수용 기준을 짚으면 골 결함(카드).
|
|
185
|
+
* 그 밖(리뷰만 있거나 근거가 비면)은 시험 결함(수확)이다.
|
|
186
|
+
*/
|
|
187
|
+
export function classifyReworkCapExhaustion(input: ReworkCapHealInput): ReworkCapHealDecision {
|
|
188
|
+
const introduced = (input.introduced ?? []).map((item) => item.trim()).filter(Boolean);
|
|
189
|
+
const mustFix = (input.mustFix ?? []).map((item) => item.trim()).filter(Boolean);
|
|
190
|
+
const evidence = [
|
|
191
|
+
...introduced.map((item) => `gate introduced: ${item}`),
|
|
192
|
+
...mustFix.map((item) => `review must-fix: ${item}`),
|
|
193
|
+
];
|
|
194
|
+
if (introduced.length > 0) {
|
|
195
|
+
return { defectClass: 'code-defect', nextAction: 'relaunch', evidence };
|
|
196
|
+
}
|
|
197
|
+
if (mustFix.some((item) => GOAL_DEFECT.test(item)) || (input.summary ? GOAL_DEFECT.test(input.summary) : false)) {
|
|
198
|
+
return { defectClass: 'goal-defect', nextAction: 'card', evidence };
|
|
199
|
+
}
|
|
200
|
+
return { defectClass: 'test-defect', nextAction: 'harvest', evidence };
|
|
201
|
+
}
|
|
202
|
+
|
|
159
203
|
const UNTESTABLE_EXT = new Set(['.md', '.json', '.yaml', '.yml', '.txt']);
|
|
160
204
|
const ENVIRONMENT_CLAIM = /할당량|자격|기판|quota|credential|substrate/i;
|
|
161
205
|
|
|
@@ -66,9 +66,11 @@ import { appendRunLedgerEntry, loadRunLedger, parseRunShardIdentity, queryRunCha
|
|
|
66
66
|
import { emitSessionEvent, type SessionEventInput } from '../context-bus/session-events.js';
|
|
67
67
|
import { decideLineageSupersede, lineageSupersedeCloseComment, type LineageSupersedeOpenDraft } from './lineage-supersede.js';
|
|
68
68
|
import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
69
|
+
import { homedir } from 'node:os';
|
|
69
70
|
import { enqueueControlMemo, readSoftStopRequestStatus, type SoftStopRequestRead } from '../harness/control-inbox.js';
|
|
70
71
|
import { debug } from '../debug/log.js';
|
|
71
72
|
import { recordFailureEvent } from './heal-intake.js';
|
|
73
|
+
import { classifyReworkCapExhaustion, type ReworkCapHealDecision } from './heal-triage.js';
|
|
72
74
|
import { releasePathHold, releasePathHoldComment, RELEASE_PATH_LABEL } from '../self-dev/release-path-guard.js';
|
|
73
75
|
import { emitDecision } from '../live/detail-switch.js';
|
|
74
76
|
import { reworkDecision } from './decision-events.js';
|
|
@@ -101,6 +103,26 @@ import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
|
101
103
|
import { addNextMdReleaseNote, harnessReleaseNote, parseReleaseNoteSection, releaseNotesDir, renderReleaseNoteSection, writeReleaseNote, type ReleaseNoteFragment, type ReleaseNoteSection } from '../release-loop/release-note.js';
|
|
102
104
|
import { landingFreezeMessage } from '../release-loop/landing-freeze.js';
|
|
103
105
|
import { admitLandingMerge, owningRepoRoot } from './frozen-merges.js';
|
|
106
|
+
import { recordFollowUpOnMerge, type FollowUpMergeInput, type FollowUpSeams } from './follow-up-goals.js';
|
|
107
|
+
|
|
108
|
+
/** Merge result and exit stay untouched: a draft failure is one debug line. */
|
|
109
|
+
async function draftFollowUpAfterMerge(input: FollowUpMergeInput, followUp: FollowUpSeams | undefined): Promise<void> {
|
|
110
|
+
try {
|
|
111
|
+
await recordFollowUpOnMerge(input, {
|
|
112
|
+
...(followUp ? {
|
|
113
|
+
...(followUp.mode ? { mode: followUp.mode } : {}),
|
|
114
|
+
...(followUp.readDrafts ? { readDrafts: followUp.readDrafts } : {}),
|
|
115
|
+
...(followUp.appendDraft ? { appendDraft: followUp.appendDraft } : {}),
|
|
116
|
+
...(followUp.enqueue ? { enqueue: followUp.enqueue } : {}),
|
|
117
|
+
...(followUp.cellOwner ? { cellOwner: followUp.cellOwner } : {}),
|
|
118
|
+
...(followUp.stateRoot ? { stateRoot: followUp.stateRoot } : {}),
|
|
119
|
+
} : {}),
|
|
120
|
+
log: (category, event, data) => { debug.log(category, event, data); },
|
|
121
|
+
});
|
|
122
|
+
} catch (error) {
|
|
123
|
+
debug.log('self-implement.follow-up', 'draft-failed', { prNumber: input.prNumber, error: error instanceof Error ? error.message : String(error) });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
104
126
|
import { plannedSelfImplBranch } from '../harness/worktree-branch-prefix.js';
|
|
105
127
|
export { slugifyFeature } from '../harness/worktree-branch-prefix.js';
|
|
106
128
|
import { AUTO_REVIEW_LABEL, resolveAutoReviewLabels } from './context-capsule.js';
|
|
@@ -1056,7 +1078,7 @@ export interface SelfImplementSeams {
|
|
|
1056
1078
|
/** PR 생성 뒤 런의 라운드 대화를 게시한다. 실패는 PR 개설을 되돌리지 않는다. */
|
|
1057
1079
|
postPrComment?: (opts: { number: number; body: string; cwd: string }) => Promise<void>;
|
|
1058
1080
|
addPrLabel?: (opts: { number: number; label: string; cwd: string }) => Promise<void>;
|
|
1059
|
-
readPrFiles?: (opts: { number: number; cwd: string }) => Promise<string[]>;
|
|
1081
|
+
readPrFiles?: (opts: { number: number; cwd: string; base?: string }) => Promise<string[]>;
|
|
1060
1082
|
/** PR의 base/head SHA와 관측된 PR base를 한 응답에서 고정한다. 미주입·실패·빈 SHA면 검사 대상을 증명할 수 없어 fail-closed로 PR을 열어 둔다. */
|
|
1061
1083
|
readPrCommitShas?: (opts: { number: number; cwd: string }) => Promise<{ baseCommit: string; headCommit: string; baseRefName?: string }>;
|
|
1062
1084
|
/** 고정한 base/head SHA 쌍의 unified diff. 가변 PR 조회를 다시 사용하면 ABA head 변경에 안전하지 않으므로 두 SHA는 필수 입력이다. */
|
|
@@ -1090,6 +1112,12 @@ export interface SelfImplementSeams {
|
|
|
1090
1112
|
readRunLedger?: (runId: string) => RunLedgerEntry[] | null | Promise<RunLedgerEntry[] | null>;
|
|
1091
1113
|
closeDraft: (input: { number: number; comment: string }) => void | Promise<void>;
|
|
1092
1114
|
};
|
|
1115
|
+
/**
|
|
1116
|
+
* FOLLOWUP-LOOP① — after a successful merge, leftover findings become one follow-up draft.
|
|
1117
|
+
* Tests inject every store. Omission writes nothing (no operational ledger, no queue).
|
|
1118
|
+
* Production `defaultSeams` fills the state-root ledger and, only in `live`, the harness queue.
|
|
1119
|
+
*/
|
|
1120
|
+
followUp?: FollowUpSeams;
|
|
1093
1121
|
/** ⑥ HITL 게이트 — PR open 전 승인. ★fail-closed: 생략/false 면 PR 안 열림(자동 승인 없음). review 동봉
|
|
1094
1122
|
* (2026-07-21) — HITL 이 리뷰 verdict 를 보고 판단, auto 모드는 verdict 로 자동 결정. */
|
|
1095
1123
|
approvePr?: (summary: { branch: string; gateLog?: string; implSummary: string; review?: SelfImplementReview }) => Promise<boolean>;
|
|
@@ -3394,7 +3422,26 @@ function mergeDecisionEvidenceCoverage(evidenceCoverage: {
|
|
|
3394
3422
|
: { evidenceCoverageMeasured: false };
|
|
3395
3423
|
}
|
|
3396
3424
|
|
|
3425
|
+
function installedBuildForOrigin(env: NodeJS.ProcessEnv): { installedVersion: string; installedCommit: string } {
|
|
3426
|
+
const root = env.ELANOUS_TEST_HOME?.trim()
|
|
3427
|
+
? join(env.ELANOUS_TEST_HOME.trim(), '.local/share/elanous')
|
|
3428
|
+
: join(homedir(), '.local/share/elanous');
|
|
3429
|
+
try {
|
|
3430
|
+
const parsed: unknown = JSON.parse(readFileSync(join(root, 'install.json'), 'utf8'));
|
|
3431
|
+
if (typeof parsed !== 'object' || parsed === null) return { installedVersion: 'unknown', installedCommit: 'unknown' };
|
|
3432
|
+
const version = (parsed as { version?: unknown }).version;
|
|
3433
|
+
const commit = (parsed as { commit?: unknown }).commit;
|
|
3434
|
+
return {
|
|
3435
|
+
installedVersion: typeof version === 'string' && version.trim() ? version.trim() : 'unknown',
|
|
3436
|
+
installedCommit: typeof commit === 'string' && commit.trim() ? commit.trim() : 'unknown',
|
|
3437
|
+
};
|
|
3438
|
+
} catch {
|
|
3439
|
+
return { installedVersion: 'unknown', installedCommit: 'unknown' };
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
|
|
3397
3443
|
function runOriginData(env: NodeJS.ProcessEnv = process.env): RunOriginData {
|
|
3444
|
+
const installed = installedBuildForOrigin(env);
|
|
3398
3445
|
return {
|
|
3399
3446
|
hostId: resolveHostId(env),
|
|
3400
3447
|
hostname: hostname(),
|
|
@@ -3403,6 +3450,8 @@ function runOriginData(env: NodeJS.ProcessEnv = process.env): RunOriginData {
|
|
|
3403
3450
|
substrate: env.ELANOUS_SUBSTRATE || 'host',
|
|
3404
3451
|
instance: resolveInstanceName(),
|
|
3405
3452
|
elanousVersion: (JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8')) as { version: string }).version,
|
|
3453
|
+
installedVersion: installed.installedVersion,
|
|
3454
|
+
installedCommit: installed.installedCommit,
|
|
3406
3455
|
...(env.ELANOUS_POD_NAME ? { podName: env.ELANOUS_POD_NAME } : {}),
|
|
3407
3456
|
...(env.ELANOUS_NODE_NAME ? { nodeName: env.ELANOUS_NODE_NAME } : {}),
|
|
3408
3457
|
...(env.ELANOUS_POD_NAMESPACE ? { podNamespace: env.ELANOUS_POD_NAMESPACE } : {}),
|
|
@@ -3616,6 +3665,7 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
|
|
|
3616
3665
|
},
|
|
3617
3666
|
} satisfies LogSink);
|
|
3618
3667
|
let traversalFinalized = false;
|
|
3668
|
+
reworkCapProceed = false;
|
|
3619
3669
|
const progressDelivery = { delivered: 0, unwired: 0, callbackFailed: 0 };
|
|
3620
3670
|
let capturedPrNumber: number | undefined;
|
|
3621
3671
|
let openPrInvoked = false;
|
|
@@ -4179,10 +4229,31 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
|
|
|
4179
4229
|
const terminalStage = terminalResult?.stage ?? 'crashed';
|
|
4180
4230
|
try {
|
|
4181
4231
|
const mappedStatus = mapStageToRunStatus(terminalStage)?.runStatus;
|
|
4182
|
-
|
|
4232
|
+
const reworkCapStop = terminalResult?.stage === 'pr-opened' && (
|
|
4233
|
+
terminalResult.outcome === 'budget-exhausted'
|
|
4234
|
+
|| (terminalResult.outcome === 'completed' && reworkCapProceed)
|
|
4235
|
+
);
|
|
4236
|
+
if (!terminalResult || mappedStatus === 'failed' || (mappedStatus === undefined && terminalResult.ok === false) || signalIncompleteState(roundClassifications).latestSignalIncomplete || reworkCapStop) {
|
|
4237
|
+
const reworkCap = reworkCapStop ? classifyReworkCapExhaustion({
|
|
4238
|
+
introduced: terminalResult?.gate?.reflectGateFacts?.introduced
|
|
4239
|
+
? [`introduced=${terminalResult.gate.reflectGateFacts.introduced}`]
|
|
4240
|
+
: [],
|
|
4241
|
+
mustFix: terminalResult?.review?.mustFix ?? terminalResult?.mustFix,
|
|
4242
|
+
summary: terminalResult?.review?.summary,
|
|
4243
|
+
}) : undefined;
|
|
4244
|
+
if (reworkCap) {
|
|
4245
|
+
observeOuter('rework-cap-heal', {
|
|
4246
|
+
stage: terminalStage,
|
|
4247
|
+
defectClass: reworkCap.defectClass,
|
|
4248
|
+
nextAction: reworkCap.nextAction,
|
|
4249
|
+
evidence: reworkCap.evidence,
|
|
4250
|
+
});
|
|
4251
|
+
}
|
|
4183
4252
|
recordFailureEvent({
|
|
4184
|
-
source: 'harness-run', kind: terminalStage, ref: runId,
|
|
4185
|
-
summary:
|
|
4253
|
+
source: 'harness-run', kind: reworkCapStop ? 'rework-cap-exhausted' : terminalStage, ref: runId,
|
|
4254
|
+
summary: reworkCap
|
|
4255
|
+
? reworkCapFailureSummary(opts.feature, reworkCap)
|
|
4256
|
+
: opts.feature.split(/\r?\n/, 1)[0]?.trim() || `Harness run ${runId} failed`,
|
|
4186
4257
|
at: new Date().toISOString(),
|
|
4187
4258
|
});
|
|
4188
4259
|
}
|
|
@@ -4544,6 +4615,14 @@ function observeRunRollup(
|
|
|
4544
4615
|
}, effectiveOutcome.runStatus === 'failed' ? { level: 'warn' } : undefined);
|
|
4545
4616
|
}
|
|
4546
4617
|
|
|
4618
|
+
let reworkCapProceed = false;
|
|
4619
|
+
|
|
4620
|
+
function reworkCapFailureSummary(feature: string, decision: ReworkCapHealDecision): string {
|
|
4621
|
+
const title = feature.split(/\\r?\\n/, 1)[0]?.trim() || 'rework cap exhausted';
|
|
4622
|
+
const evidence = decision.evidence.length ? decision.evidence.join(' | ') : 'no gate introduced list and no review finding';
|
|
4623
|
+
return `${title} [${decision.defectClass} → ${decision.nextAction}] ${evidence}`;
|
|
4624
|
+
}
|
|
4625
|
+
|
|
4547
4626
|
async function runSelfImplementInner(
|
|
4548
4627
|
opts: SelfImplementOptions,
|
|
4549
4628
|
identity?: { runId: string; source: ReturnType<typeof resolveRunIdentity>['source'] },
|
|
@@ -5030,6 +5109,8 @@ async function runSelfImplementInner(
|
|
|
5030
5109
|
} | undefined;
|
|
5031
5110
|
let gate!: SelfImplementGateResult;
|
|
5032
5111
|
let review: SelfImplementReview | undefined;
|
|
5112
|
+
/** Review-budget leftovers and the body that carried them, so a later merge drafts from the same text. */
|
|
5113
|
+
let preparedReviewBudgetPrBody: string | undefined;
|
|
5033
5114
|
const reviewMustFixCountHistory: number[] = [];
|
|
5034
5115
|
const reviewMustFixTrend = (): string => {
|
|
5035
5116
|
if (reviewMustFixCountHistory.length < 2) return '아직 못 잰다';
|
|
@@ -5365,6 +5446,23 @@ async function runSelfImplementInner(
|
|
|
5365
5446
|
observe('pr.comment.seam-missing', { number: pr.number, buffered: prCommentBuffer.length }, { level: 'warn' });
|
|
5366
5447
|
return;
|
|
5367
5448
|
}
|
|
5449
|
+
const marker = '<!-- elanous:run-status -->';
|
|
5450
|
+
if (s.findPrComment && s.editPrComment) {
|
|
5451
|
+
try {
|
|
5452
|
+
const existing = await s.findPrComment({ number: pr.number, marker, cwd: wt.path });
|
|
5453
|
+
const history = prCommentBuffer.join('\n\n');
|
|
5454
|
+
const body = existing
|
|
5455
|
+
? existing.body.includes('</details>')
|
|
5456
|
+
? existing.body.replace(/<\/details>(?![\s\S]*<\/details>)/, `\n\n${history}\n</details>`)
|
|
5457
|
+
: `${existing.body}\n\n<details>\n<summary>Round history</summary>\n\n${history}\n</details>`
|
|
5458
|
+
: `${marker}\n<details>\n<summary>Round history</summary>\n\n${history}\n</details>`;
|
|
5459
|
+
if (existing) await s.editPrComment({ id: existing.id, body, cwd: wt.path });
|
|
5460
|
+
else await s.postPrComment({ number: pr.number, body, cwd: wt.path });
|
|
5461
|
+
} catch (error) {
|
|
5462
|
+
// Unknown lookup/edit outcome is not proof of absence: never create a second status comment.
|
|
5463
|
+
observe('pr.comment.status-failed', { number: pr.number, error: error instanceof Error ? error.message : String(error) }, { level: 'warn' });
|
|
5464
|
+
}
|
|
5465
|
+
}
|
|
5368
5466
|
for (const body of prCommentBuffer) {
|
|
5369
5467
|
try {
|
|
5370
5468
|
await s.postPrComment({ number: pr.number, body, cwd: wt.path });
|
|
@@ -5798,6 +5896,7 @@ async function runSelfImplementInner(
|
|
|
5798
5896
|
'self-implement-review-budget-pr',
|
|
5799
5897
|
followUpMustFix,
|
|
5800
5898
|
);
|
|
5899
|
+
preparedReviewBudgetPrBody = preparedPrBody.body;
|
|
5801
5900
|
const pr = await withStepTimeout(s.openPr({
|
|
5802
5901
|
title: prTitle(opts.feature),
|
|
5803
5902
|
body: preparedPrBody.body,
|
|
@@ -6211,6 +6310,7 @@ async function runSelfImplementInner(
|
|
|
6211
6310
|
if (budgetDecision.stop) {
|
|
6212
6311
|
if (budgetDecision.exit === 'proceed') {
|
|
6213
6312
|
finalizeSupervisorDeliveries('supervisor-rework-proceed');
|
|
6313
|
+
if (round > effectiveMax) reworkCapProceed = true;
|
|
6214
6314
|
break;
|
|
6215
6315
|
}
|
|
6216
6316
|
finalizeSupervisorDeliveries('supervisor-rework-blocked');
|
|
@@ -6271,12 +6371,25 @@ async function runSelfImplementInner(
|
|
|
6271
6371
|
if (isReviewRework(reworkKind)) {
|
|
6272
6372
|
if (reflectRejectedAudit.length && review) review.shouldFix = [...review.shouldFix, ...reflectRejectedAudit];
|
|
6273
6373
|
const reason = `review must-fix unresolved after ${round - 1} rework round(s)`;
|
|
6374
|
+
// Pod keeps review-blocked work as a labelled draft (or aborts with the preserved branch) instead of
|
|
6375
|
+
// accepting the review budget into a non-draft PR — restored after #24341 (0.2.16 gate shard 5).
|
|
6274
6376
|
if (!podReviewBlockedDraft) {
|
|
6275
6377
|
const acceptance = await acceptReviewBudget(
|
|
6276
6378
|
review?.mustFix ?? [],
|
|
6277
6379
|
reworkParts.some((part) => part.source === 'supervisor'),
|
|
6278
6380
|
);
|
|
6279
6381
|
if (acceptance.decision.action === 'accept') {
|
|
6382
|
+
if (acceptance.pr) {
|
|
6383
|
+
await draftFollowUpAfterMerge({
|
|
6384
|
+
prNumber: acceptance.pr.number,
|
|
6385
|
+
followUpMustFix: acceptance.decision.unresolvedMustFix,
|
|
6386
|
+
shouldFix: review?.shouldFix,
|
|
6387
|
+
prBody: preparedReviewBudgetPrBody,
|
|
6388
|
+
feature: opts.feature,
|
|
6389
|
+
...(opts.goalId ? { cellId: opts.goalId } : {}),
|
|
6390
|
+
...(opts.goalFile ? { goalFile: opts.goalFile } : {}),
|
|
6391
|
+
}, s.followUp);
|
|
6392
|
+
}
|
|
6280
6393
|
return {
|
|
6281
6394
|
ok: true,
|
|
6282
6395
|
stage: 'pr-opened',
|
|
@@ -7523,11 +7636,15 @@ async function runSelfImplementInner(
|
|
|
7523
7636
|
let releasePathInspectionError: string | undefined;
|
|
7524
7637
|
if (s.readPrFiles) {
|
|
7525
7638
|
try {
|
|
7526
|
-
|
|
7639
|
+
// Rate-limit recovery may wait up to 15 minutes for X-RateLimit-Reset; the PR step cap must not cut that wait.
|
|
7640
|
+
const files = await s.readPrFiles({ number: pr.number, cwd: wt.path, ...(opts.base ? { base: opts.base } : {}) });
|
|
7527
7641
|
if (!Array.isArray(files) || !files.every((path) => typeof path === 'string')) throw new Error('invalid PR file list');
|
|
7528
7642
|
releaseHoldPath = releasePathHold(files);
|
|
7529
7643
|
} catch (error) {
|
|
7530
|
-
|
|
7644
|
+
const rateLimited = error instanceof Error && (error as { rateLimited?: boolean }).rateLimited === true;
|
|
7645
|
+
releasePathInspectionError = rateLimited
|
|
7646
|
+
? `rate-limit: ${safeErrorDescription(error)}`
|
|
7647
|
+
: safeErrorDescription(error);
|
|
7531
7648
|
}
|
|
7532
7649
|
} else if (canAuto || labels?.includes(AUTO_REVIEW_LABEL)) {
|
|
7533
7650
|
releasePathInspectionError = 'readPrFiles seam unavailable';
|
|
@@ -7566,7 +7683,9 @@ async function runSelfImplementInner(
|
|
|
7566
7683
|
if (releasePathInspectionError && (canAuto || labels?.includes(AUTO_REVIEW_LABEL))) {
|
|
7567
7684
|
observe('release-path-inspection-failed', { number: pr.number, error: releasePathInspectionError }, { level: 'error' });
|
|
7568
7685
|
canAuto = false;
|
|
7569
|
-
mergeSkipReason = mergeReason = '
|
|
7686
|
+
mergeSkipReason = mergeReason = releasePathInspectionError.startsWith('rate-limit:')
|
|
7687
|
+
? 'release-path-inspection-failed: rate-limit'
|
|
7688
|
+
: 'release-path-inspection-failed';
|
|
7570
7689
|
recordMergeDecision();
|
|
7571
7690
|
progress('pr-opened', `OP 승인 대기 (#${pr.number}): PR 경로 조회 실패`);
|
|
7572
7691
|
return { ok: true, stage: 'pr-opened', node: 'open-pr', ...resolveRunOutcome({ termination: 'completed' }), ...decisionSignalResult, ...preservedCompletionResult, ...(completionStatus ? { completionStatus } : {}), sessionId, worktreePath: wt.path, branch: wt.branch, gate, ...(review ? { review } : {}), mergeReason, prUrl: pr.url, prNumber: pr.number };
|
|
@@ -7700,6 +7819,18 @@ async function runSelfImplementInner(
|
|
|
7700
7819
|
prNumber: pr.number,
|
|
7701
7820
|
openedAt: openedAtFromLedger(mergedLedger, new Date().toISOString()),
|
|
7702
7821
|
}, observe);
|
|
7822
|
+
// FOLLOWUP-LOOP① — leftover findings become one follow-up draft. Failure is a log line only;
|
|
7823
|
+
// this return (stage, merged, exit via ok) stays the merge result.
|
|
7824
|
+
await draftFollowUpAfterMerge({
|
|
7825
|
+
prNumber: pr.number,
|
|
7826
|
+
// 정상 병합은 리뷰를 통과한 뒤라 미해결 must-fix 가 없다 — 남은 것은 should-fix ⊕ PR 본문 «남은 조각».
|
|
7827
|
+
// 미해결 must-fix 는 리뷰 예산 수락 경로가 직접 넘긴다(위 acceptance 분기).
|
|
7828
|
+
shouldFix: review?.shouldFix,
|
|
7829
|
+
prBody: [preparedReviewBudgetPrBody, preparedPrBody.body].filter(Boolean).join('\n'),
|
|
7830
|
+
feature: opts.feature,
|
|
7831
|
+
...(opts.goalId ? { cellId: opts.goalId } : {}),
|
|
7832
|
+
...(opts.goalFile ? { goalFile: opts.goalFile } : {}),
|
|
7833
|
+
}, s.followUp);
|
|
7703
7834
|
return { ok: true, stage: 'merged', node: 'merge', ...resolveRunOutcome({ termination: 'completed' }), ...decisionSignalResult, ...preservedCompletionResult, ...(completionStatus ? { completionStatus } : {}), sessionId, worktreePath: wt.path, branch: wt.branch, gate, ...(review ? { review } : {}), merged: true, ...(mergedBase ? { mergedBase } : {}), prUrl: pr.url, prNumber: pr.number };
|
|
7704
7835
|
}
|
|
7705
7836
|
progress('pr-opened', `병합 실패 — PR 개설됨(#${pr.number}·수동 병합). ${m.detail ?? ''}`);
|
|
@@ -94,6 +94,10 @@ export interface RunOriginData extends Record<string, unknown> {
|
|
|
94
94
|
substrate: string;
|
|
95
95
|
instance: string;
|
|
96
96
|
elanousVersion: string;
|
|
97
|
+
/** install.json version. 못 읽으면 «unknown». */
|
|
98
|
+
installedVersion: string;
|
|
99
|
+
/** install.json commit. 못 읽으면 «unknown». */
|
|
100
|
+
installedCommit: string;
|
|
97
101
|
podName?: string;
|
|
98
102
|
nodeName?: string;
|
|
99
103
|
podNamespace?: string;
|
|
@@ -24,6 +24,9 @@ import { isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
|
24
24
|
import { promisify } from 'node:util';
|
|
25
25
|
import { forkSessionById, HARNESS_SESSION_ORIGIN, isHarnessSessionOrigin } from '../session/index.js';
|
|
26
26
|
import { configuredWorktreeRoot, getUserConfig, resolveRoleLlm } from '../user-config.js';
|
|
27
|
+
import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
28
|
+
import { addHarnessQueue } from '../harness/harness-queue.js';
|
|
29
|
+
import { readFollowUpMode } from './follow-up-goals.js';
|
|
27
30
|
import { getProviderForConfig } from '../llm.js';
|
|
28
31
|
import { createWorktree, DEFAULT_BRANCH_WORKTREE_BASE, linkWorktreeDependencies, removeWorktree, resolveDefaultBranchBase, resolveMainRepoRoot } from '../git-fs/worktree.js';
|
|
29
32
|
import { isWorktreeInUse, listActiveTerminalDirectories } from '../harness/harness-clean.js';
|
|
@@ -77,7 +80,8 @@ import { tscEnv, assessTypecheckExecution, classifyTypecheckErrors, diffTypechec
|
|
|
77
80
|
import { nestCapReached, childNestEnv, nestInfo } from '../agent/nest-depth.js';
|
|
78
81
|
import { harnessBoundaryEnv, harnessBoundaryRequestsEnv, harnessBoundaryResponsesEnv, harnessSpaceEnv, getHarnessSpace, normalizeSpaceId, getHarnessRunId, resolveRunIdentity } from '../harness/harness-space.js';
|
|
79
82
|
import { debug } from '../debug/log.js';
|
|
80
|
-
import { RELEASE_PATH_HOLD_MARKER, RELEASE_PATH_LABEL, releasePathHoldShouldPost, releasePathHoldCommentsArgs
|
|
83
|
+
import { RELEASE_PATH_HOLD_MARKER, RELEASE_PATH_LABEL, releasePathHoldShouldPost, releasePathHoldCommentsArgs } from '../self-dev/release-path-guard.js';
|
|
84
|
+
import { readPrFilesWithRateLimitFallback, runGhApp } from './app-api-budget.js';
|
|
81
85
|
import { emitDecision } from '../live/detail-switch.js';
|
|
82
86
|
import { gateDecision, reviewDecision, mergeDecision } from './decision-events.js';
|
|
83
87
|
import { LogStore } from '../mss/logging/log-store.js';
|
|
@@ -110,6 +114,10 @@ declare module './orchestrator.js' {
|
|
|
110
114
|
/** PR close(+브랜치 삭제). 기본 구성은 PrManager.closePr 위임(같은 인자·같은 반환).
|
|
111
115
|
* 오케스트레이터는 아직 부르지 않는다 — 그 배선은 조각 ②다. */
|
|
112
116
|
closePr?: (prUrl: string, comment?: string) => boolean;
|
|
117
|
+
/** Find an issue comment on this PR by its body marker; absence is distinct from a failed lookup. */
|
|
118
|
+
findPrComment?: (opts: { number: number; marker: string; cwd: string; comments?: unknown }) => Promise<{ id: number; body: string } | undefined>;
|
|
119
|
+
/** Replace an existing PR issue comment by ID, using the same GitHub App identity as postPrComment. */
|
|
120
|
+
editPrComment?: (opts: { id: number; body: string; cwd: string }) => Promise<void>;
|
|
113
121
|
}
|
|
114
122
|
}
|
|
115
123
|
import { harnessPolicyEnv } from './harness-policy.js';
|
|
@@ -1415,6 +1423,11 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
|
|
|
1415
1423
|
const { supersedeMergedGoalDrafts } = await import('../harness/harness-cli-command.js');
|
|
1416
1424
|
await supersedeMergedGoalDrafts(number, cwd);
|
|
1417
1425
|
},
|
|
1426
|
+
followUp: {
|
|
1427
|
+
mode: readFollowUpMode(),
|
|
1428
|
+
stateRoot: elanousStateRoot(),
|
|
1429
|
+
enqueue: (item) => addHarnessQueue({ seat: item.seat, say: item.say, idempotencyKey: item.idempotencyKey }),
|
|
1430
|
+
},
|
|
1418
1431
|
lineageSupersede: {
|
|
1419
1432
|
listOpenDrafts: () => listOpenDraftsForLineage(),
|
|
1420
1433
|
readRunLedger: (runId) => {
|
|
@@ -2394,46 +2407,62 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
|
|
|
2394
2407
|
postPrComment: async ({ number, body, cwd }) => {
|
|
2395
2408
|
// Round comments go out as the GitHub App like the PR itself — a personal `gh` login made the owner a
|
|
2396
2409
|
// participant and mailed them on merge (#22089).
|
|
2397
|
-
|
|
2410
|
+
if (body.startsWith(RELEASE_PATH_HOLD_MARKER)) {
|
|
2398
2411
|
// Several merge surfaces may hold the same PR; post the release-path hold comment once.
|
|
2399
2412
|
let history: unknown;
|
|
2400
2413
|
try {
|
|
2401
|
-
history = JSON.parse((await
|
|
2414
|
+
history = JSON.parse((await runGhApp('post-pr-comment-history', releasePathHoldCommentsArgs(number), {
|
|
2402
2415
|
cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024, env: ghAutomationEnv(process.env),
|
|
2403
2416
|
})).stdout);
|
|
2404
2417
|
} catch { history = undefined; }
|
|
2405
2418
|
if (!releasePathHoldShouldPost(() => history)) return;
|
|
2406
2419
|
}
|
|
2407
|
-
await
|
|
2420
|
+
await runGhApp('post-pr-comment', ['pr', 'comment', String(number), '--body', body], {
|
|
2421
|
+
cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024, env: ghAutomationEnv(process.env),
|
|
2422
|
+
});
|
|
2423
|
+
},
|
|
2424
|
+
|
|
2425
|
+
findPrComment: async ({ number, marker, cwd, comments }) => {
|
|
2426
|
+
const pages: unknown = comments ?? JSON.parse((await runGhApp('find-pr-comment', releasePathHoldCommentsArgs(number), {
|
|
2427
|
+
cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024, env: ghAutomationEnv(process.env),
|
|
2428
|
+
})).stdout);
|
|
2429
|
+
if (!Array.isArray(pages)) throw new Error('gh api PR comments returned an invalid list');
|
|
2430
|
+
const entries = pages.flatMap((page: unknown) => Array.isArray(page) ? page : [page]);
|
|
2431
|
+
for (const entry of entries) {
|
|
2432
|
+
if (!entry || typeof entry !== 'object' || !('body' in entry) || typeof entry.body !== 'string'
|
|
2433
|
+
|| !('id' in entry) || typeof entry.id !== 'number' || !Number.isSafeInteger(entry.id) || entry.id <= 0) {
|
|
2434
|
+
throw new Error('gh api PR comments returned an invalid comment');
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
return entries.find((entry: { id: number; body: string }) => entry.body.includes(marker));
|
|
2438
|
+
},
|
|
2439
|
+
|
|
2440
|
+
editPrComment: async ({ id, body, cwd }) => {
|
|
2441
|
+
await runGhApp('edit-pr-comment', ['api', '--method', 'PATCH', `repos/{owner}/{repo}/issues/comments/${id}`, '-f', `body=${body}`], {
|
|
2408
2442
|
cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024, env: ghAutomationEnv(process.env),
|
|
2409
2443
|
});
|
|
2410
2444
|
},
|
|
2411
2445
|
|
|
2412
2446
|
addPrLabel: async ({ number, label, cwd }) => {
|
|
2413
2447
|
if (label === RELEASE_PATH_LABEL) {
|
|
2414
|
-
const existing = await
|
|
2448
|
+
const existing = await runGhApp('add-pr-label-list', ['label', 'list', '--search', label, '--json', 'name'], {
|
|
2415
2449
|
cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, env: ghAutomationEnv(process.env),
|
|
2416
2450
|
});
|
|
2417
2451
|
const names: unknown = JSON.parse(existing.stdout);
|
|
2418
2452
|
if (!Array.isArray(names)) throw new Error('gh label list returned an invalid list');
|
|
2419
2453
|
if (!names.some((item: unknown) => !!item && typeof item === 'object' && 'name' in item && item.name === label)) {
|
|
2420
|
-
await
|
|
2454
|
+
await runGhApp('add-pr-label-create', ['label', 'create', label, '--color', 'D93F0B', '--description', 'Release path requires OP approval'], {
|
|
2421
2455
|
cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, env: ghAutomationEnv(process.env),
|
|
2422
2456
|
});
|
|
2423
2457
|
}
|
|
2424
2458
|
}
|
|
2425
|
-
await
|
|
2459
|
+
await runGhApp('add-pr-label', ['pr', 'edit', String(number), '--add-label', label], {
|
|
2426
2460
|
cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, env: ghAutomationEnv(process.env),
|
|
2427
2461
|
});
|
|
2428
2462
|
},
|
|
2429
|
-
readPrFiles: async ({ number, cwd }) => {
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
'api', '--paginate', '--slurp', '--method', 'GET', '-f', 'per_page=100',
|
|
2433
|
-
`repos/{owner}/{repo}/pulls/${number}/files`,
|
|
2434
|
-
], { cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024, env: ghAutomationEnv(process.env) });
|
|
2435
|
-
return releasePrFilePaths(JSON.parse(stdout));
|
|
2436
|
-
},
|
|
2463
|
+
readPrFiles: async ({ number, cwd, base }) => readPrFilesWithRateLimitFallback({
|
|
2464
|
+
number, cwd, env: ghAutomationEnv(process.env), ...(base ? { base } : {}),
|
|
2465
|
+
}),
|
|
2437
2466
|
|
|
2438
2467
|
...(o.approvePr ? { approvePr: o.approvePr } : {}),
|
|
2439
2468
|
|
|
@@ -25,6 +25,8 @@ import {
|
|
|
25
25
|
decideRetry,
|
|
26
26
|
DoomLoopTracker,
|
|
27
27
|
fingerprintError,
|
|
28
|
+
llmCallOutcomeKind,
|
|
29
|
+
type LlmCallOutcomeKind,
|
|
28
30
|
type RetryAction,
|
|
29
31
|
} from './retry-policy.js';
|
|
30
32
|
|
|
@@ -75,6 +77,35 @@ export interface FetchApiWithRetryOpts {
|
|
|
75
77
|
* row" pattern within a single request. Pass a session-scoped
|
|
76
78
|
* tracker if you want cross-request doom detection. */
|
|
77
79
|
doomTracker?: DoomLoopTracker;
|
|
80
|
+
/** Fallbacks still unused. When > 0, k consecutive overload-class
|
|
81
|
+
* failures throw OverloadFailoverError before another retry sleep. */
|
|
82
|
+
remainingFallbacks?: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Raised when overload repeats k times and a fallback is still unused.
|
|
86
|
+
* The caller switches provider; this is not a retry exhaustion. */
|
|
87
|
+
export class OverloadFailoverError extends Error {
|
|
88
|
+
readonly provider: string;
|
|
89
|
+
readonly status: number;
|
|
90
|
+
readonly kind: LlmCallOutcomeKind;
|
|
91
|
+
readonly streak: number;
|
|
92
|
+
readonly cause: unknown;
|
|
93
|
+
|
|
94
|
+
constructor(opts: {
|
|
95
|
+
provider: string;
|
|
96
|
+
status: number;
|
|
97
|
+
kind: LlmCallOutcomeKind;
|
|
98
|
+
streak: number;
|
|
99
|
+
cause: unknown;
|
|
100
|
+
}) {
|
|
101
|
+
super(`overload failover after ${opts.streak} ${opts.kind} from ${opts.provider} (status ${opts.status})`);
|
|
102
|
+
this.name = 'OverloadFailoverError';
|
|
103
|
+
this.provider = opts.provider;
|
|
104
|
+
this.status = opts.status;
|
|
105
|
+
this.kind = opts.kind;
|
|
106
|
+
this.streak = opts.streak;
|
|
107
|
+
this.cause = opts.cause;
|
|
108
|
+
}
|
|
78
109
|
}
|
|
79
110
|
|
|
80
111
|
/** POST a fetch with the standard retry policy. On 2xx returns the
|
|
@@ -91,13 +122,17 @@ export async function fetchApiWithRetry(
|
|
|
91
122
|
): Promise<Response> {
|
|
92
123
|
const maxAttempts = opts.maxAttempts ?? 4;
|
|
93
124
|
const tracker = opts.doomTracker ?? new DoomLoopTracker();
|
|
125
|
+
const remainingFallbacks = opts.remainingFallbacks ?? 0;
|
|
94
126
|
let attempt = 0;
|
|
127
|
+
let overloadStreak = 0;
|
|
95
128
|
let lastErr: unknown;
|
|
96
129
|
while (attempt < maxAttempts) {
|
|
97
130
|
let response: Response | null = null;
|
|
98
131
|
try {
|
|
99
132
|
response = await fetch(url, init);
|
|
100
133
|
if (response.ok) {
|
|
134
|
+
recordLlmCallOutcome(opts.provider, response.status, 'ok');
|
|
135
|
+
overloadStreak = 0;
|
|
101
136
|
return response;
|
|
102
137
|
}
|
|
103
138
|
// Drain body so the error can carry the API's explanation. We
|
|
@@ -136,6 +171,11 @@ export async function fetchApiWithRetry(
|
|
|
136
171
|
// would couple the helper to the caller's signal.
|
|
137
172
|
if (isAbortLike(err)) throw err;
|
|
138
173
|
|
|
174
|
+
const status = err instanceof ApiHttpError ? err.status : 0;
|
|
175
|
+
const kind = llmCallOutcomeKind(status, err instanceof Error ? err.message : String(err));
|
|
176
|
+
recordLlmCallOutcome(opts.provider, status, kind);
|
|
177
|
+
if (kind === 'overloaded' || kind === '5xx' || kind === 'rate-limit') overloadStreak += 1;
|
|
178
|
+
else overloadStreak = 0;
|
|
139
179
|
const fp = fingerprintError(err, `api:${opts.provider}`);
|
|
140
180
|
const doomStatus = tracker.record(fp);
|
|
141
181
|
const retryAfter = err instanceof ApiHttpError ? err.retryAfter : undefined;
|
|
@@ -143,6 +183,8 @@ export async function fetchApiWithRetry(
|
|
|
143
183
|
attempt,
|
|
144
184
|
doomStatus,
|
|
145
185
|
retryAfter,
|
|
186
|
+
remainingFallbacks,
|
|
187
|
+
overloadStreak,
|
|
146
188
|
});
|
|
147
189
|
debug.log('llm.retry', decisionEvent(decision.action), {
|
|
148
190
|
provider: opts.provider,
|
|
@@ -151,7 +193,19 @@ export async function fetchApiWithRetry(
|
|
|
151
193
|
reason: decision.reason,
|
|
152
194
|
delayMs: decision.delayMs,
|
|
153
195
|
status: err instanceof ApiHttpError ? err.status : undefined,
|
|
196
|
+
kind,
|
|
197
|
+
overloadStreak,
|
|
198
|
+
remainingFallbacks,
|
|
154
199
|
});
|
|
200
|
+
if (decision.action === 'switch-provider') {
|
|
201
|
+
throw new OverloadFailoverError({
|
|
202
|
+
provider: opts.provider,
|
|
203
|
+
status,
|
|
204
|
+
kind,
|
|
205
|
+
streak: overloadStreak,
|
|
206
|
+
cause: err,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
155
209
|
if (decision.action !== 'retry') {
|
|
156
210
|
throw err;
|
|
157
211
|
}
|
|
@@ -172,6 +226,39 @@ export async function fetchApiWithRetry(
|
|
|
172
226
|
throw lastErr ?? new Error(`${opts.errorPrefix}: retry loop terminated unexpectedly`);
|
|
173
227
|
}
|
|
174
228
|
|
|
229
|
+
type OutcomeWriter = (row: { provider: string; status: number; kind: LlmCallOutcomeKind }) => void;
|
|
230
|
+
|
|
231
|
+
let outcomeWriterForTesting: OutcomeWriter | undefined;
|
|
232
|
+
|
|
233
|
+
/** Test seam. Production uses the log store. `undefined` restores it. */
|
|
234
|
+
export function setLlmCallOutcomeWriterForTesting(writer: OutcomeWriter | undefined): void {
|
|
235
|
+
outcomeWriterForTesting = writer;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** The one event launch failover is allowed to read.
|
|
239
|
+
* Category `llm.call`, event `outcome`, payload {provider,status,kind}.
|
|
240
|
+
* Written straight to the log store — `debug.log` drops the row when no
|
|
241
|
+
* sink is armed, which is the normal launch path. */
|
|
242
|
+
export function recordLlmCallOutcome(provider: string, status: number, kind: LlmCallOutcomeKind): void {
|
|
243
|
+
const data = { provider, status, kind };
|
|
244
|
+
// Trace only — a store sink would otherwise write a second `llm.call`/`outcome` row and halve the streak threshold.
|
|
245
|
+
debug.log('llm.call', 'outcome-trace', data);
|
|
246
|
+
if (outcomeWriterForTesting) {
|
|
247
|
+
outcomeWriterForTesting(data);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (process.env.NODE_ENV === 'test') return;
|
|
251
|
+
try {
|
|
252
|
+
const { getDefaultLogStore } = require('../mss/logging/log-store.js') as typeof import('../mss/logging/log-store.js');
|
|
253
|
+
getDefaultLogStore()?.insertBatch([{
|
|
254
|
+
rec: { ts: new Date().toISOString(), category: 'llm.call', event: 'outcome', data },
|
|
255
|
+
surface: 'llm',
|
|
256
|
+
}]);
|
|
257
|
+
} catch {
|
|
258
|
+
// A failed observation must not fail the call. Launch then sees no sample.
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
175
262
|
function decisionEvent(action: RetryAction): string {
|
|
176
263
|
// Stable category names for log filtering. The action is the most
|
|
177
264
|
// useful axis ("how many turns hit retry vs abort"); the category
|