@chllming/wave-orchestration 0.7.0 → 0.7.1
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/CHANGELOG.md +25 -0
- package/README.md +9 -8
- package/docs/guides/planner.md +19 -0
- package/docs/guides/terminal-surfaces.md +12 -0
- package/docs/plans/current-state.md +1 -1
- package/docs/plans/examples/wave-example-live-proof.md +1 -1
- package/docs/plans/migration.md +26 -0
- package/docs/plans/wave-orchestrator.md +4 -7
- package/docs/reference/cli-reference.md +547 -0
- package/docs/reference/coordination-and-closure.md +1 -1
- package/docs/reference/npmjs-trusted-publishing.md +2 -2
- package/docs/reference/runtime-config/README.md +2 -2
- package/docs/reference/runtime-config/codex.md +2 -1
- package/docs/reference/sample-waves.md +4 -4
- package/package.json +1 -1
- package/releases/manifest.json +24 -2
- package/scripts/wave-orchestrator/agent-state.mjs +11 -2
- package/scripts/wave-orchestrator/control-cli.mjs +112 -19
- package/scripts/wave-orchestrator/dashboard-renderer.mjs +82 -2
- package/scripts/wave-orchestrator/install.mjs +98 -3
- package/scripts/wave-orchestrator/launcher-runtime.mjs +9 -9
- package/scripts/wave-orchestrator/launcher.mjs +88 -1
- package/scripts/wave-orchestrator/terminals.mjs +1 -1
- package/scripts/wave-orchestrator/wave-files.mjs +127 -18
|
@@ -198,6 +198,22 @@ export function formatReconcileBlockedWaveLine(blockedWave) {
|
|
|
198
198
|
}`;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
export function formatReconcilePreservedWaveLine(preservedWave) {
|
|
202
|
+
const parts = Array.isArray(preservedWave?.reasons)
|
|
203
|
+
? preservedWave.reasons
|
|
204
|
+
.map((reason) => {
|
|
205
|
+
const code = compactSingleLine(reason?.code || "", 80);
|
|
206
|
+
const detail = compactSingleLine(reason?.detail || "", 240);
|
|
207
|
+
return code && detail ? `${code}=${detail}` : "";
|
|
208
|
+
})
|
|
209
|
+
.filter(Boolean)
|
|
210
|
+
: [];
|
|
211
|
+
const previousState = compactSingleLine(preservedWave?.previousState || "completed", 80);
|
|
212
|
+
return `[reconcile] wave ${preservedWave?.wave ?? "unknown"} preserved as ${previousState}: ${
|
|
213
|
+
parts.join("; ") || "unknown reason"
|
|
214
|
+
}`;
|
|
215
|
+
}
|
|
216
|
+
|
|
201
217
|
function printUsage(lanePaths, terminalSurface) {
|
|
202
218
|
console.log(`Usage: pnpm exec wave launch [options]
|
|
203
219
|
|
|
@@ -206,6 +222,7 @@ Options:
|
|
|
206
222
|
--start-wave <n> Start from wave number (default: 0)
|
|
207
223
|
--end-wave <n> End at wave number (default: last available)
|
|
208
224
|
--auto-next Start from the next unfinished wave and continue forward
|
|
225
|
+
--resume-control-state Preserve the prior auto-generated relaunch plan for this wave
|
|
209
226
|
--reconcile-status Reconcile run-state from agent status files and exit
|
|
210
227
|
--state-file <path> Path to run-state JSON (default: ${path.relative(REPO_ROOT, lanePaths.defaultRunStatePath)})
|
|
211
228
|
--timeout-minutes <n> Max minutes to wait per wave (default: ${DEFAULT_TIMEOUT_MINUTES})
|
|
@@ -252,6 +269,7 @@ function parseArgs(argv) {
|
|
|
252
269
|
startWave: 0,
|
|
253
270
|
endWave: null,
|
|
254
271
|
autoNext: false,
|
|
272
|
+
resumeControlState: false,
|
|
255
273
|
reconcileStatus: false,
|
|
256
274
|
runStatePath: lanePaths.defaultRunStatePath,
|
|
257
275
|
timeoutMinutes: DEFAULT_TIMEOUT_MINUTES,
|
|
@@ -301,6 +319,8 @@ function parseArgs(argv) {
|
|
|
301
319
|
options.cleanupSessions = false;
|
|
302
320
|
} else if (arg === "--auto-next") {
|
|
303
321
|
options.autoNext = true;
|
|
322
|
+
} else if (arg === "--resume-control-state") {
|
|
323
|
+
options.resumeControlState = true;
|
|
304
324
|
} else if (arg === "--reconcile-status") {
|
|
305
325
|
options.reconcileStatus = true;
|
|
306
326
|
} else if (arg === "--keep-terminals") {
|
|
@@ -563,6 +583,32 @@ function materializeAgentExecutionSummaryForRun(wave, runInfo) {
|
|
|
563
583
|
reportPath,
|
|
564
584
|
});
|
|
565
585
|
writeAgentExecutionSummary(runInfo.statusPath, summary);
|
|
586
|
+
if (runInfo?.previewPath && fs.existsSync(runInfo.previewPath)) {
|
|
587
|
+
const previewPayload = readJsonOrNull(runInfo.previewPath);
|
|
588
|
+
if (previewPayload && typeof previewPayload === "object") {
|
|
589
|
+
const nextLimits =
|
|
590
|
+
previewPayload.limits && typeof previewPayload.limits === "object" && !Array.isArray(previewPayload.limits)
|
|
591
|
+
? { ...previewPayload.limits }
|
|
592
|
+
: {};
|
|
593
|
+
const observedTurnLimit = Number(summary?.terminationObservedTurnLimit);
|
|
594
|
+
if (Number.isFinite(observedTurnLimit) && observedTurnLimit > 0) {
|
|
595
|
+
nextLimits.observedTurnLimit = observedTurnLimit;
|
|
596
|
+
nextLimits.observedTurnLimitSource = "runtime-log";
|
|
597
|
+
if (runInfo.agent.executorResolved?.id === "codex") {
|
|
598
|
+
const existingNotes = Array.isArray(nextLimits.notes) ? nextLimits.notes.slice() : [];
|
|
599
|
+
const observedNote = `Observed runtime stop at ${observedTurnLimit} turns from executor log output.`;
|
|
600
|
+
if (!existingNotes.includes(observedNote)) {
|
|
601
|
+
existingNotes.push(observedNote);
|
|
602
|
+
}
|
|
603
|
+
nextLimits.notes = existingNotes;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
writeJsonAtomic(runInfo.previewPath, {
|
|
607
|
+
...previewPayload,
|
|
608
|
+
limits: nextLimits,
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
}
|
|
566
612
|
return summary;
|
|
567
613
|
}
|
|
568
614
|
|
|
@@ -652,6 +698,25 @@ function clearWaveRelaunchPlan(lanePaths, waveNumber) {
|
|
|
652
698
|
}
|
|
653
699
|
}
|
|
654
700
|
|
|
701
|
+
export function resetPersistedWaveLaunchState(lanePaths, waveNumber, options = {}) {
|
|
702
|
+
if (options?.dryRun || options?.resumeControlState) {
|
|
703
|
+
return {
|
|
704
|
+
clearedRelaunchPlan: false,
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
const persistedRelaunchPlan = readWaveRelaunchPlan(lanePaths, waveNumber);
|
|
708
|
+
if (!persistedRelaunchPlan) {
|
|
709
|
+
return {
|
|
710
|
+
clearedRelaunchPlan: false,
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
clearWaveRelaunchPlan(lanePaths, waveNumber);
|
|
714
|
+
return {
|
|
715
|
+
clearedRelaunchPlan: true,
|
|
716
|
+
relaunchPlan: persistedRelaunchPlan,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
|
|
655
720
|
function waveSecurityPath(lanePaths, waveNumber) {
|
|
656
721
|
return path.join(lanePaths.securityDir, `wave-${waveNumber}.json`);
|
|
657
722
|
}
|
|
@@ -3469,6 +3534,9 @@ export async function runLauncherCli(argv) {
|
|
|
3469
3534
|
for (const blockedWave of reconciliation.blockedFromStatus || []) {
|
|
3470
3535
|
console.log(formatReconcileBlockedWaveLine(blockedWave));
|
|
3471
3536
|
}
|
|
3537
|
+
for (const preservedWave of reconciliation.preservedWithDrift || []) {
|
|
3538
|
+
console.log(formatReconcilePreservedWaveLine(preservedWave));
|
|
3539
|
+
}
|
|
3472
3540
|
console.log(`[reconcile] completed waves now: ${completedSummary}`);
|
|
3473
3541
|
return;
|
|
3474
3542
|
}
|
|
@@ -3628,7 +3696,7 @@ export async function runLauncherCli(argv) {
|
|
|
3628
3696
|
dashboardPath: lanePaths.globalDashboardPath,
|
|
3629
3697
|
});
|
|
3630
3698
|
console.log(
|
|
3631
|
-
`[dashboard]
|
|
3699
|
+
`[dashboard] attach global: pnpm exec wave dashboard --lane ${lanePaths.lane} --attach global`,
|
|
3632
3700
|
);
|
|
3633
3701
|
}
|
|
3634
3702
|
|
|
@@ -3730,6 +3798,12 @@ export async function runLauncherCli(argv) {
|
|
|
3730
3798
|
promptPath: path.join(lanePaths.promptsDir, `${safeName}.prompt.md`),
|
|
3731
3799
|
logPath: path.join(lanePaths.logsDir, `${safeName}.log`),
|
|
3732
3800
|
statusPath: path.join(lanePaths.statusDir, `${safeName}.status`),
|
|
3801
|
+
previewPath: path.join(
|
|
3802
|
+
lanePaths.executorOverlaysDir,
|
|
3803
|
+
`wave-${wave.wave}`,
|
|
3804
|
+
agent.slug,
|
|
3805
|
+
"launch-preview.json",
|
|
3806
|
+
),
|
|
3733
3807
|
messageBoardPath,
|
|
3734
3808
|
messageBoardSnapshot: derivedState.messageBoardText,
|
|
3735
3809
|
sharedSummaryPath: derivedState.sharedSummaryPath,
|
|
@@ -3777,6 +3851,16 @@ export async function runLauncherCli(argv) {
|
|
|
3777
3851
|
};
|
|
3778
3852
|
|
|
3779
3853
|
refreshDerivedState(0);
|
|
3854
|
+
const launchStateReset = resetPersistedWaveLaunchState(lanePaths, wave.wave, options);
|
|
3855
|
+
if (launchStateReset.clearedRelaunchPlan) {
|
|
3856
|
+
appendCoordination({
|
|
3857
|
+
event: "wave_launch_state_reset",
|
|
3858
|
+
waves: [wave.wave],
|
|
3859
|
+
status: "running",
|
|
3860
|
+
details: `cleared_relaunch_plan=yes; previous_agents=${(launchStateReset.relaunchPlan?.selectedAgentIds || []).join(",") || "none"}`,
|
|
3861
|
+
actionRequested: "None",
|
|
3862
|
+
});
|
|
3863
|
+
}
|
|
3780
3864
|
let persistedRelaunchPlan = readWaveRelaunchPlan(lanePaths, wave.wave);
|
|
3781
3865
|
let retryOverride = readWaveRetryOverride(lanePaths, wave.wave);
|
|
3782
3866
|
|
|
@@ -3891,6 +3975,9 @@ export async function runLauncherCli(argv) {
|
|
|
3891
3975
|
dashboardPath,
|
|
3892
3976
|
messageBoardPath,
|
|
3893
3977
|
});
|
|
3978
|
+
console.log(
|
|
3979
|
+
`[dashboard] attach current: pnpm exec wave dashboard --lane ${lanePaths.lane} --attach current`,
|
|
3980
|
+
);
|
|
3894
3981
|
}
|
|
3895
3982
|
|
|
3896
3983
|
if (options.residentOrchestrator) {
|
|
@@ -129,7 +129,7 @@ export function createTemporaryTerminalEntries(
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
export function createGlobalDashboardTerminalEntry(lanePaths, runTag) {
|
|
132
|
-
const sessionName = `${lanePaths.tmuxGlobalDashboardSessionPrefix}
|
|
132
|
+
const sessionName = `${lanePaths.tmuxGlobalDashboardSessionPrefix}_current`.replace(
|
|
133
133
|
/[^a-zA-Z0-9:_-]/g,
|
|
134
134
|
"_",
|
|
135
135
|
);
|
|
@@ -2421,6 +2421,17 @@ function relativeRepoPathOrNull(filePath) {
|
|
|
2421
2421
|
return filePath ? path.relative(REPO_ROOT, filePath) : null;
|
|
2422
2422
|
}
|
|
2423
2423
|
|
|
2424
|
+
const RUN_STATE_COMPLETED_VALUES = new Set(["completed", "completed_with_drift"]);
|
|
2425
|
+
const PROMPT_DRIFT_REASON_CODES = new Set(["prompt-hash-mismatch", "prompt-hash-missing"]);
|
|
2426
|
+
|
|
2427
|
+
function isCompletedRunStateValue(value) {
|
|
2428
|
+
return RUN_STATE_COMPLETED_VALUES.has(String(value || "").trim().toLowerCase());
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
function completedRunStateEntries(waves) {
|
|
2432
|
+
return Object.values(waves || {}).filter((entry) => isCompletedRunStateValue(entry?.currentState));
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2424
2435
|
function normalizeRunStateWaveEntry(rawEntry, waveNumber) {
|
|
2425
2436
|
const source = rawEntry && typeof rawEntry === "object" && !Array.isArray(rawEntry) ? rawEntry : {};
|
|
2426
2437
|
const normalizedWave = normalizeCompletedWaves([waveNumber])[0] ?? normalizeCompletedWaves([source.wave])[0] ?? null;
|
|
@@ -2465,9 +2476,7 @@ function normalizeRunStateHistoryEntry(rawEntry, seqFallback) {
|
|
|
2465
2476
|
|
|
2466
2477
|
function completedWavesFromStateEntries(waves) {
|
|
2467
2478
|
return normalizeCompletedWaves(
|
|
2468
|
-
|
|
2469
|
-
.filter((entry) => entry?.currentState === "completed")
|
|
2470
|
-
.map((entry) => entry.wave),
|
|
2479
|
+
completedRunStateEntries(waves).map((entry) => entry.wave),
|
|
2471
2480
|
);
|
|
2472
2481
|
}
|
|
2473
2482
|
|
|
@@ -2744,6 +2753,64 @@ function pushWaveCompletionReason(reasons, code, detail) {
|
|
|
2744
2753
|
reasons.push({ code: normalizedCode, detail: normalizedDetail });
|
|
2745
2754
|
}
|
|
2746
2755
|
|
|
2756
|
+
function promptDriftReasonForStatus(agent, statusPath, statusRecord, expectedPromptHash) {
|
|
2757
|
+
const actualPromptHash = String(statusRecord?.promptHash || "").trim();
|
|
2758
|
+
if (!actualPromptHash) {
|
|
2759
|
+
return {
|
|
2760
|
+
code: "prompt-hash-missing",
|
|
2761
|
+
detail: `${agent.agentId} status in ${path.relative(REPO_ROOT, statusPath)} is missing prompt-hash metadata required to match the current prompt fingerprint.`,
|
|
2762
|
+
};
|
|
2763
|
+
}
|
|
2764
|
+
if (actualPromptHash !== expectedPromptHash) {
|
|
2765
|
+
return {
|
|
2766
|
+
code: "prompt-hash-mismatch",
|
|
2767
|
+
detail: `${agent.agentId} status in ${path.relative(REPO_ROOT, statusPath)} does not match the current prompt fingerprint.`,
|
|
2768
|
+
};
|
|
2769
|
+
}
|
|
2770
|
+
return null;
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
function diagnosticHasOnlyPromptDriftReasons(diagnostic) {
|
|
2774
|
+
return (
|
|
2775
|
+
Array.isArray(diagnostic?.reasons) &&
|
|
2776
|
+
diagnostic.reasons.length > 0 &&
|
|
2777
|
+
diagnostic.reasons.every((reason) => PROMPT_DRIFT_REASON_CODES.has(String(reason?.code || "").trim()))
|
|
2778
|
+
);
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
function isAuthoritativeCompletedRunStateEntry(entry) {
|
|
2782
|
+
if (!isCompletedRunStateValue(entry?.currentState)) {
|
|
2783
|
+
return false;
|
|
2784
|
+
}
|
|
2785
|
+
const source = String(entry?.lastSource || "").trim().toLowerCase();
|
|
2786
|
+
return source !== "" && source !== "legacy-run-state";
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
function buildPreservedCompletionEvidence(previousEntry, diagnostic) {
|
|
2790
|
+
const baseEvidence =
|
|
2791
|
+
diagnostic?.evidence && typeof diagnostic.evidence === "object" && !Array.isArray(diagnostic.evidence)
|
|
2792
|
+
? { ...diagnostic.evidence }
|
|
2793
|
+
: {};
|
|
2794
|
+
baseEvidence.preservedCompletion = {
|
|
2795
|
+
preserved: true,
|
|
2796
|
+
preservedFromState: previousEntry?.currentState || "completed",
|
|
2797
|
+
preservedFromSource: previousEntry?.lastSource || null,
|
|
2798
|
+
preservedFromReasonCode: previousEntry?.lastReasonCode || null,
|
|
2799
|
+
driftReasons: (diagnostic?.reasons || [])
|
|
2800
|
+
.filter((reason) => PROMPT_DRIFT_REASON_CODES.has(String(reason?.code || "").trim()))
|
|
2801
|
+
.map((reason) => ({
|
|
2802
|
+
code: String(reason?.code || "").trim(),
|
|
2803
|
+
detail: String(reason?.detail || "").trim(),
|
|
2804
|
+
})),
|
|
2805
|
+
previousEvidence: previousEntry?.lastEvidence || null,
|
|
2806
|
+
};
|
|
2807
|
+
return baseEvidence;
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
function shouldPreserveCompletedWave(previousEntry, diagnostic) {
|
|
2811
|
+
return isAuthoritativeCompletedRunStateEntry(previousEntry) && diagnosticHasOnlyPromptDriftReasons(diagnostic);
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2747
2814
|
function analyzeWaveCompletionFromStatusFiles(wave, statusDir, options = {}) {
|
|
2748
2815
|
const logsDir = options.logsDir || path.join(path.resolve(statusDir, ".."), "logs");
|
|
2749
2816
|
const coordinationDir =
|
|
@@ -2770,6 +2837,7 @@ function analyzeWaveCompletionFromStatusFiles(wave, statusDir, options = {}) {
|
|
|
2770
2837
|
const statusEntries = [];
|
|
2771
2838
|
const missingStatusAgents = [];
|
|
2772
2839
|
let statusesReady = wave.agents.length > 0;
|
|
2840
|
+
let summaryValidationReady = wave.agents.length > 0;
|
|
2773
2841
|
const coordinationLogPath = path.join(coordinationDir, `wave-${wave.wave}.jsonl`);
|
|
2774
2842
|
const assignmentsPath = path.join(assignmentsDir, `wave-${wave.wave}.json`);
|
|
2775
2843
|
const dependencySnapshotPath = path.join(dependencySnapshotsDir, `wave-${wave.wave}.json`);
|
|
@@ -2780,6 +2848,7 @@ function analyzeWaveCompletionFromStatusFiles(wave, statusDir, options = {}) {
|
|
|
2780
2848
|
if (!statusRecord) {
|
|
2781
2849
|
missingStatusAgents.push(agent.agentId);
|
|
2782
2850
|
statusesReady = false;
|
|
2851
|
+
summaryValidationReady = false;
|
|
2783
2852
|
continue;
|
|
2784
2853
|
}
|
|
2785
2854
|
const summaryPath = agentSummaryPathFromStatusPath(statusPath);
|
|
@@ -2797,16 +2866,18 @@ function analyzeWaveCompletionFromStatusFiles(wave, statusDir, options = {}) {
|
|
|
2797
2866
|
`${agent.agentId} exited ${statusRecord.code} in ${path.relative(REPO_ROOT, statusPath)}.`,
|
|
2798
2867
|
);
|
|
2799
2868
|
statusesReady = false;
|
|
2869
|
+
summaryValidationReady = false;
|
|
2800
2870
|
continue;
|
|
2801
2871
|
}
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2872
|
+
const promptDriftReason = promptDriftReasonForStatus(
|
|
2873
|
+
agent,
|
|
2874
|
+
statusPath,
|
|
2875
|
+
statusRecord,
|
|
2876
|
+
expectedPromptHash,
|
|
2877
|
+
);
|
|
2878
|
+
if (promptDriftReason) {
|
|
2879
|
+
pushWaveCompletionReason(reasons, promptDriftReason.code, promptDriftReason.detail);
|
|
2808
2880
|
statusesReady = false;
|
|
2809
|
-
continue;
|
|
2810
2881
|
}
|
|
2811
2882
|
const summary = materializeLiveExecutionSummaryIfMissing({
|
|
2812
2883
|
wave,
|
|
@@ -2917,7 +2988,7 @@ function analyzeWaveCompletionFromStatusFiles(wave, statusDir, options = {}) {
|
|
|
2917
2988
|
}
|
|
2918
2989
|
|
|
2919
2990
|
if (
|
|
2920
|
-
|
|
2991
|
+
summaryValidationReady &&
|
|
2921
2992
|
componentThreshold !== null &&
|
|
2922
2993
|
wave.wave >= componentThreshold
|
|
2923
2994
|
) {
|
|
@@ -3071,32 +3142,63 @@ export function reconcileRunStateFromStatusFiles(allWaves, runStatePath, statusD
|
|
|
3071
3142
|
const before = readRunState(runStatePath);
|
|
3072
3143
|
const firstMerge = normalizeCompletedWaves(
|
|
3073
3144
|
diagnostics
|
|
3074
|
-
.filter((diagnostic) =>
|
|
3145
|
+
.filter((diagnostic) => {
|
|
3146
|
+
if (diagnostic.ok) {
|
|
3147
|
+
return true;
|
|
3148
|
+
}
|
|
3149
|
+
const previousEntry = before.waves[String(diagnostic.wave)] || null;
|
|
3150
|
+
return shouldPreserveCompletedWave(previousEntry, diagnostic);
|
|
3151
|
+
})
|
|
3075
3152
|
.map((diagnostic) => diagnostic.wave)
|
|
3076
3153
|
.concat(
|
|
3077
3154
|
before.completedWaves.filter((waveNumber) => {
|
|
3078
3155
|
const diagnostic = diagnostics.find((entry) => entry.wave === waveNumber);
|
|
3079
|
-
|
|
3156
|
+
if (!diagnostic) {
|
|
3157
|
+
return true;
|
|
3158
|
+
}
|
|
3159
|
+
const previousEntry = before.waves[String(waveNumber)] || null;
|
|
3160
|
+
return diagnostic.ok || shouldPreserveCompletedWave(previousEntry, diagnostic);
|
|
3080
3161
|
}),
|
|
3081
3162
|
),
|
|
3082
3163
|
);
|
|
3083
3164
|
const latest = readRunState(runStatePath);
|
|
3084
3165
|
let nextState = latest;
|
|
3166
|
+
const preservedWithDrift = [];
|
|
3085
3167
|
for (const diagnostic of diagnostics) {
|
|
3086
|
-
const
|
|
3168
|
+
const previousEntry = before.waves[String(diagnostic.wave)] || null;
|
|
3169
|
+
const preserveCompleted = shouldPreserveCompletedWave(previousEntry, diagnostic);
|
|
3170
|
+
const toState = diagnostic.ok
|
|
3171
|
+
? "completed"
|
|
3172
|
+
: preserveCompleted
|
|
3173
|
+
? "completed_with_drift"
|
|
3174
|
+
: "blocked";
|
|
3087
3175
|
const reasonCode = diagnostic.ok
|
|
3088
3176
|
? "status-reconcile-complete"
|
|
3089
|
-
:
|
|
3177
|
+
: preserveCompleted
|
|
3178
|
+
? "status-reconcile-completed-with-drift"
|
|
3179
|
+
: diagnostic.reasons[0]?.code || "status-reconcile-blocked";
|
|
3090
3180
|
const detail = diagnostic.ok
|
|
3091
3181
|
? `Wave ${diagnostic.wave} reconstructed as complete from status files.`
|
|
3092
|
-
:
|
|
3182
|
+
: preserveCompleted
|
|
3183
|
+
? `Wave ${diagnostic.wave} preserved as completed with prompt drift: ${diagnostic.reasons.map((reason) => reason.detail).filter(Boolean).join(" ")}`
|
|
3184
|
+
: diagnostic.reasons.map((reason) => reason.detail).filter(Boolean).join(" ");
|
|
3185
|
+
const evidence = preserveCompleted
|
|
3186
|
+
? buildPreservedCompletionEvidence(previousEntry, diagnostic)
|
|
3187
|
+
: diagnostic.evidence || null;
|
|
3188
|
+
if (preserveCompleted) {
|
|
3189
|
+
preservedWithDrift.push({
|
|
3190
|
+
wave: diagnostic.wave,
|
|
3191
|
+
reasons: diagnostic.reasons,
|
|
3192
|
+
previousState: previousEntry?.currentState || "completed",
|
|
3193
|
+
});
|
|
3194
|
+
}
|
|
3093
3195
|
nextState = appendRunStateTransition(nextState, {
|
|
3094
3196
|
waveNumber: diagnostic.wave,
|
|
3095
3197
|
toState,
|
|
3096
3198
|
source: "status-reconcile",
|
|
3097
3199
|
reasonCode,
|
|
3098
3200
|
detail,
|
|
3099
|
-
evidence
|
|
3201
|
+
evidence,
|
|
3100
3202
|
at: diagnostic.evidence?.statusFiles?.find((entry) => entry.completedAt)?.completedAt || toIsoTimestamp(),
|
|
3101
3203
|
});
|
|
3102
3204
|
}
|
|
@@ -3106,7 +3208,14 @@ export function reconcileRunStateFromStatusFiles(allWaves, runStatePath, statusD
|
|
|
3106
3208
|
completedFromStatus,
|
|
3107
3209
|
addedFromBefore: firstMerge.filter((waveNumber) => !before.completedWaves.includes(waveNumber)),
|
|
3108
3210
|
addedFromLatest: merged.filter((waveNumber) => !latest.completedWaves.includes(waveNumber)),
|
|
3109
|
-
blockedFromStatus: diagnostics.filter((diagnostic) =>
|
|
3211
|
+
blockedFromStatus: diagnostics.filter((diagnostic) => {
|
|
3212
|
+
if (diagnostic.ok) {
|
|
3213
|
+
return false;
|
|
3214
|
+
}
|
|
3215
|
+
const previousEntry = before.waves[String(diagnostic.wave)] || null;
|
|
3216
|
+
return !shouldPreserveCompletedWave(previousEntry, diagnostic);
|
|
3217
|
+
}),
|
|
3218
|
+
preservedWithDrift,
|
|
3110
3219
|
state,
|
|
3111
3220
|
};
|
|
3112
3221
|
}
|