@chllming/wave-orchestration 0.7.1 → 0.7.2

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +8 -8
  3. package/docs/plans/component-cutover-matrix.json +50 -3
  4. package/docs/plans/current-state.md +1 -1
  5. package/docs/plans/end-state-architecture.md +927 -0
  6. package/docs/plans/examples/wave-example-live-proof.md +1 -1
  7. package/docs/plans/migration.md +2 -2
  8. package/docs/plans/waves/wave-1.md +376 -0
  9. package/docs/plans/waves/wave-2.md +292 -0
  10. package/docs/plans/waves/wave-3.md +342 -0
  11. package/docs/plans/waves/wave-4.md +391 -0
  12. package/docs/plans/waves/wave-5.md +382 -0
  13. package/docs/plans/waves/wave-6.md +321 -0
  14. package/docs/reference/npmjs-trusted-publishing.md +2 -2
  15. package/docs/reference/sample-waves.md +4 -4
  16. package/package.json +1 -1
  17. package/releases/manifest.json +19 -0
  18. package/scripts/wave-orchestrator/agent-state.mjs +447 -33
  19. package/scripts/wave-orchestrator/artifact-schemas.mjs +81 -0
  20. package/scripts/wave-orchestrator/control-cli.mjs +7 -1
  21. package/scripts/wave-orchestrator/coordination.mjs +11 -10
  22. package/scripts/wave-orchestrator/human-input-workflow.mjs +289 -0
  23. package/scripts/wave-orchestrator/install.mjs +22 -0
  24. package/scripts/wave-orchestrator/launcher-derived-state.mjs +915 -0
  25. package/scripts/wave-orchestrator/launcher-gates.mjs +1061 -0
  26. package/scripts/wave-orchestrator/launcher-retry.mjs +873 -0
  27. package/scripts/wave-orchestrator/launcher-supervisor.mjs +704 -0
  28. package/scripts/wave-orchestrator/launcher.mjs +153 -2922
  29. package/scripts/wave-orchestrator/task-entity.mjs +557 -0
  30. package/scripts/wave-orchestrator/wave-files.mjs +11 -2
  31. package/scripts/wave-orchestrator/wave-state-reducer.mjs +566 -0
  32. package/wave.config.json +1 -1
@@ -181,6 +181,118 @@ import {
181
181
  readWaveInfraGate as readWaveInfraGateImpl,
182
182
  runClosureSweepPhase as runClosureSweepPhaseImpl,
183
183
  } from "./launcher-closure.mjs";
184
+
185
+ // --- Re-exports from launcher-gates.mjs ---
186
+ import {
187
+ materializeAgentExecutionSummaryForRun,
188
+ readRunExecutionSummary,
189
+ materializeAgentExecutionSummaries,
190
+ readWaveContQaGate,
191
+ readWaveContEvalGate,
192
+ readWaveEvaluatorGate,
193
+ readWaveImplementationGate,
194
+ analyzePromotedComponentOwners,
195
+ buildSharedComponentSiblingPendingFailure,
196
+ readWaveComponentGate,
197
+ readWaveComponentMatrixGate,
198
+ readWaveDocumentationGate,
199
+ readWaveSecurityGate,
200
+ readWaveIntegrationGate,
201
+ readWaveIntegrationBarrier,
202
+ readClarificationBarrier,
203
+ readWaveAssignmentBarrier,
204
+ readWaveDependencyBarrier,
205
+ buildGateSnapshot as buildGateSnapshotImpl,
206
+ } from "./launcher-gates.mjs";
207
+
208
+ export {
209
+ readWaveContQaGate,
210
+ readWaveContEvalGate,
211
+ readWaveEvaluatorGate,
212
+ readWaveImplementationGate,
213
+ readWaveComponentGate,
214
+ readWaveComponentMatrixGate,
215
+ readWaveDocumentationGate,
216
+ readWaveSecurityGate,
217
+ readWaveIntegrationGate,
218
+ readWaveIntegrationBarrier,
219
+ readClarificationBarrier,
220
+ readWaveAssignmentBarrier,
221
+ readWaveDependencyBarrier,
222
+ };
223
+
224
+ // --- Re-exports from launcher-derived-state.mjs ---
225
+ import {
226
+ waveAssignmentsPath,
227
+ waveDependencySnapshotPath,
228
+ writeWaveDerivedState,
229
+ applyDerivedStateToDashboard,
230
+ buildWaveSecuritySummary,
231
+ buildWaveIntegrationSummary,
232
+ } from "./launcher-derived-state.mjs";
233
+
234
+ export {
235
+ buildWaveSecuritySummary,
236
+ buildWaveIntegrationSummary,
237
+ };
238
+
239
+ // --- Re-exports from launcher-retry.mjs ---
240
+ import {
241
+ readWaveRelaunchPlan,
242
+ writeWaveRelaunchPlan,
243
+ clearWaveRelaunchPlan,
244
+ resetPersistedWaveLaunchState,
245
+ persistedRelaunchPlanMatchesCurrentState,
246
+ resolveSharedComponentContinuationRuns,
247
+ relaunchReasonBuckets,
248
+ applySharedComponentWaitStateToDashboard,
249
+ reconcileFailuresAgainstSharedComponentState,
250
+ hasReusableSuccessStatus,
251
+ selectReusablePreCompletedAgentIds,
252
+ selectInitialWaveRuns,
253
+ resolveRelaunchRuns,
254
+ applyPersistedRelaunchPlan,
255
+ executorFallbackChain,
256
+ preflightWavesForExecutorAvailability,
257
+ } from "./launcher-retry.mjs";
258
+
259
+ export {
260
+ resetPersistedWaveLaunchState,
261
+ persistedRelaunchPlanMatchesCurrentState,
262
+ resolveSharedComponentContinuationRuns,
263
+ hasReusableSuccessStatus,
264
+ selectReusablePreCompletedAgentIds,
265
+ selectInitialWaveRuns,
266
+ resolveRelaunchRuns,
267
+ };
268
+
269
+ // --- Re-exports from launcher-supervisor.mjs ---
270
+ import {
271
+ markLauncherFailed,
272
+ acquireLauncherLock,
273
+ releaseLauncherLock,
274
+ reconcileStaleLauncherArtifacts,
275
+ collectUnexpectedSessionFailures,
276
+ launchAgentSession,
277
+ waitForWaveCompletion,
278
+ monitorWaveHumanFeedback,
279
+ buildResidentOrchestratorRun,
280
+ monitorResidentOrchestratorSession,
281
+ launchWaveDashboardSession,
282
+ cleanupLaneTmuxSessions,
283
+ pruneDryRunExecutorPreviewDirs,
284
+ runTmux,
285
+ } from "./launcher-supervisor.mjs";
286
+
287
+ export {
288
+ markLauncherFailed,
289
+ acquireLauncherLock,
290
+ releaseLauncherLock,
291
+ reconcileStaleLauncherArtifacts,
292
+ collectUnexpectedSessionFailures,
293
+ };
294
+
295
+ // --- Original re-exports that stay ---
184
296
  export { CODEX_SANDBOX_MODES, DEFAULT_CODEX_SANDBOX_MODE, normalizeCodexSandboxMode, buildCodexExecInvocation };
185
297
 
186
298
  export function formatReconcileBlockedWaveLine(blockedWave) {
@@ -431,2941 +543,60 @@ function parseArgs(argv) {
431
543
  return { help: false, lanePaths, options };
432
544
  }
433
545
 
434
- function isProcessAlive(pid) {
435
- if (!Number.isInteger(pid) || pid <= 0) {
436
- return false;
437
- }
438
- try {
439
- process.kill(pid, 0);
440
- return true;
441
- } catch {
442
- return false;
443
- }
444
- }
445
-
446
- export function readWaveContQaGate(wave, agentRuns, options = {}) {
447
- const mode = String(options.mode || "compat").trim().toLowerCase();
448
- const strict = mode === "live";
449
- const contQaAgentId = options.contQaAgentId || wave.contQaAgentId || "A0";
450
- const contQaRun =
451
- agentRuns.find((run) => run.agent.agentId === contQaAgentId) ?? null;
452
- if (!contQaRun) {
453
- return {
454
- ok: false,
455
- agentId: contQaAgentId,
456
- statusCode: "missing-cont-qa",
457
- detail: `Agent ${contQaAgentId} is missing.`,
458
- logPath: null,
459
- };
460
- }
461
- const summary = readRunExecutionSummary(contQaRun, strict ? wave : null);
462
- if (summary) {
463
- const validation = validateContQaSummary(contQaRun.agent, summary, { mode });
464
- return {
465
- ok: validation.ok,
466
- agentId: contQaRun.agent.agentId,
467
- statusCode: validation.statusCode,
468
- detail: validation.detail,
469
- logPath: summary.logPath || path.relative(REPO_ROOT, contQaRun.logPath),
470
- };
471
- }
472
- if (strict) {
473
- return {
474
- ok: false,
475
- agentId: contQaRun.agent.agentId,
476
- statusCode: "missing-wave-gate",
477
- detail: `Missing structured cont-QA summary for ${contQaRun.agent.agentId}.`,
478
- logPath: path.relative(REPO_ROOT, contQaRun.logPath),
479
- };
480
- }
481
- const contQaReportPath = wave.contQaReportPath
482
- ? path.resolve(REPO_ROOT, wave.contQaReportPath)
483
- : null;
484
- const reportText =
485
- contQaReportPath && fs.existsSync(contQaReportPath)
486
- ? fs.readFileSync(contQaReportPath, "utf8")
487
- : "";
488
- const reportVerdict = parseVerdictFromText(reportText, REPORT_VERDICT_REGEX);
489
- if (reportVerdict.verdict) {
490
- return {
491
- ok: reportVerdict.verdict === "pass",
492
- agentId: contQaRun.agent.agentId,
493
- statusCode: reportVerdict.verdict === "pass" ? "pass" : `cont-qa-${reportVerdict.verdict}`,
494
- detail: reportVerdict.detail || "Verdict read from cont-QA report.",
495
- logPath: path.relative(REPO_ROOT, contQaRun.logPath),
496
- };
497
- }
498
- const logVerdict = parseVerdictFromText(
499
- readFileTail(contQaRun.logPath, 30000),
500
- WAVE_VERDICT_REGEX,
501
- );
502
- if (logVerdict.verdict) {
503
- return {
504
- ok: logVerdict.verdict === "pass",
505
- agentId: contQaRun.agent.agentId,
506
- statusCode: logVerdict.verdict === "pass" ? "pass" : `cont-qa-${logVerdict.verdict}`,
507
- detail: logVerdict.detail || "Verdict read from cont-QA log marker.",
508
- logPath: path.relative(REPO_ROOT, contQaRun.logPath),
509
- };
510
- }
511
- return {
512
- ok: false,
513
- agentId: contQaRun.agent.agentId,
514
- statusCode: "missing-cont-qa-verdict",
515
- detail: contQaReportPath
516
- ? `Missing Verdict line in ${path.relative(REPO_ROOT, contQaReportPath)} and no [wave-verdict] marker in ${path.relative(REPO_ROOT, contQaRun.logPath)}.`
517
- : `Missing cont-QA report path and no [wave-verdict] marker in ${path.relative(REPO_ROOT, contQaRun.logPath)}.`,
518
- logPath: path.relative(REPO_ROOT, contQaRun.logPath),
519
- };
520
- }
521
-
522
- export function readWaveContEvalGate(wave, agentRuns, options = {}) {
523
- const mode = String(options.mode || "compat").trim().toLowerCase();
524
- const strict = mode === "live";
525
- const contEvalAgentId = options.contEvalAgentId || wave.contEvalAgentId || "E0";
526
- const contEvalRun =
527
- agentRuns.find((run) => run.agent.agentId === contEvalAgentId) ?? null;
528
- if (!contEvalRun) {
529
- return {
530
- ok: true,
531
- agentId: null,
532
- statusCode: "pass",
533
- detail: "Wave does not include cont-EVAL.",
534
- logPath: null,
535
- };
536
- }
537
- const summary = readRunExecutionSummary(contEvalRun, strict ? wave : null);
538
- if (summary) {
539
- const validation = validateContEvalSummary(contEvalRun.agent, summary, {
540
- mode,
541
- evalTargets: options.evalTargets || wave.evalTargets,
542
- benchmarkCatalogPath: options.benchmarkCatalogPath,
543
- });
544
- return {
545
- ok: validation.ok,
546
- agentId: contEvalRun.agent.agentId,
547
- statusCode: validation.statusCode,
548
- detail: validation.detail,
549
- logPath: summary.logPath || path.relative(REPO_ROOT, contEvalRun.logPath),
550
- };
551
- }
552
- return {
553
- ok: false,
554
- agentId: contEvalRun.agent.agentId,
555
- statusCode: "missing-wave-eval",
556
- detail: `Missing [wave-eval] marker for ${contEvalRun.agent.agentId}.`,
557
- logPath: path.relative(REPO_ROOT, contEvalRun.logPath),
558
- };
559
- }
560
-
561
- function materializeAgentExecutionSummaryForRun(wave, runInfo) {
562
- const statusRecord = readStatusRecordIfPresent(runInfo.statusPath);
563
- if (!statusRecord) {
564
- return null;
565
- }
566
- const reportPath = (() => {
567
- if (runInfo.agent.agentId === (wave.contQaAgentId || "A0") && wave.contQaReportPath) {
568
- return path.resolve(REPO_ROOT, wave.contQaReportPath);
569
- }
570
- if (runInfo.agent.agentId === (wave.contEvalAgentId || "E0") && wave.contEvalReportPath) {
571
- return path.resolve(REPO_ROOT, wave.contEvalReportPath);
572
- }
573
- if (isSecurityReviewAgent(runInfo.agent)) {
574
- const securityReportPath = resolveSecurityReviewReportPath(runInfo.agent);
575
- return securityReportPath ? path.resolve(REPO_ROOT, securityReportPath) : null;
576
- }
577
- return null;
578
- })();
579
- const summary = buildAgentExecutionSummary({
580
- agent: runInfo.agent,
581
- statusRecord,
582
- logPath: runInfo.logPath,
583
- reportPath,
584
- });
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
- }
612
- return summary;
613
- }
614
-
615
- function readRunExecutionSummary(runInfo, wave = null) {
616
- const applyProofRegistry = (summary) =>
617
- runInfo?.proofRegistry ? augmentSummaryWithProofRegistry(runInfo.agent, summary, runInfo.proofRegistry) : summary;
618
- if (runInfo?.summary && typeof runInfo.summary === "object") {
619
- return applyProofRegistry(runInfo.summary);
620
- }
621
- if (runInfo?.summaryPath && fs.existsSync(runInfo.summaryPath)) {
622
- return applyProofRegistry(readAgentExecutionSummary(runInfo.summaryPath));
623
- }
624
- if (runInfo?.statusPath && fs.existsSync(agentSummaryPathFromStatusPath(runInfo.statusPath))) {
625
- return applyProofRegistry(readAgentExecutionSummary(runInfo.statusPath));
626
- }
627
- if (wave && runInfo?.statusPath && runInfo?.logPath && fs.existsSync(runInfo.statusPath)) {
628
- return applyProofRegistry(materializeAgentExecutionSummaryForRun(wave, runInfo));
629
- }
630
- return null;
631
- }
632
-
633
- export function readWaveEvaluatorGate(wave, agentRuns, options = {}) {
634
- return readWaveContQaGate(wave, agentRuns, {
635
- ...options,
636
- contQaAgentId: options.evaluatorAgentId || options.contQaAgentId,
637
- });
638
- }
639
-
640
- function materializeAgentExecutionSummaries(wave, agentRuns) {
641
- return Object.fromEntries(
642
- agentRuns.map((runInfo) => [runInfo.agent.agentId, materializeAgentExecutionSummaryForRun(wave, runInfo)]),
643
- );
644
- }
645
-
646
- function waveCoordinationLogPath(lanePaths, waveNumber) {
647
- return path.join(lanePaths.coordinationDir, `wave-${waveNumber}.jsonl`);
648
- }
649
-
650
- function waveInboxDir(lanePaths, waveNumber) {
651
- return path.join(lanePaths.inboxesDir, `wave-${waveNumber}`);
652
- }
653
-
654
- function waveAssignmentsPath(lanePaths, waveNumber) {
655
- return path.join(lanePaths.assignmentsDir, `wave-${waveNumber}.json`);
656
- }
657
-
658
- function waveLedgerPath(lanePaths, waveNumber) {
659
- return path.join(lanePaths.ledgerDir, `wave-${waveNumber}.json`);
660
- }
661
-
662
- function waveDependencySnapshotPath(lanePaths, waveNumber) {
663
- return path.join(lanePaths.dependencySnapshotsDir, `wave-${waveNumber}.json`);
664
- }
665
-
666
- function waveDependencySnapshotMarkdownPath(lanePaths, waveNumber) {
667
- return path.join(lanePaths.dependencySnapshotsDir, `wave-${waveNumber}.md`);
668
- }
669
-
670
- function waveDocsQueuePath(lanePaths, waveNumber) {
671
- return path.join(lanePaths.docsQueueDir, `wave-${waveNumber}.json`);
672
- }
673
-
674
- function waveIntegrationPath(lanePaths, waveNumber) {
675
- return path.join(lanePaths.integrationDir, `wave-${waveNumber}.json`);
676
- }
677
-
678
- function waveIntegrationMarkdownPath(lanePaths, waveNumber) {
679
- return path.join(lanePaths.integrationDir, `wave-${waveNumber}.md`);
680
- }
681
-
682
- function readWaveRelaunchPlan(lanePaths, waveNumber) {
683
- return readWaveRelaunchPlanSnapshot(lanePaths, waveNumber);
684
- }
685
-
686
- function writeWaveRelaunchPlan(lanePaths, waveNumber, payload) {
687
- const filePath = waveRelaunchPlanPath(lanePaths, waveNumber);
688
- writeRelaunchPlan(filePath, payload, { wave: waveNumber });
689
- return filePath;
690
- }
691
-
692
- function clearWaveRelaunchPlan(lanePaths, waveNumber) {
693
- const filePath = waveRelaunchPlanPath(lanePaths, waveNumber);
694
- try {
695
- fs.rmSync(filePath, { force: true });
696
- } catch {
697
- // no-op
698
- }
699
- }
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
-
720
- function waveSecurityPath(lanePaths, waveNumber) {
721
- return path.join(lanePaths.securityDir, `wave-${waveNumber}.json`);
722
- }
723
-
724
- function waveSecurityMarkdownPath(lanePaths, waveNumber) {
725
- return path.join(lanePaths.securityDir, `wave-${waveNumber}.md`);
726
- }
727
-
728
- function uniqueStringEntries(values) {
729
- return Array.from(
730
- new Set(
731
- (Array.isArray(values) ? values : [])
732
- .map((value) => String(value || "").trim())
733
- .filter(Boolean),
734
- ),
735
- );
736
- }
737
-
738
- function summarizeIntegrationRecord(record, options = {}) {
739
- const summary = compactSingleLine(
740
- record?.summary || record?.detail || record?.kind || "coordination item",
741
- options.maxChars || 180,
742
- );
743
- return `${record.id}: ${summary}`;
744
- }
745
-
746
- function summarizeDocsQueueItem(item) {
747
- return `${item.id}: ${compactSingleLine(item.summary || item.path || item.detail || "documentation update required", 180)}`;
748
- }
749
-
750
- function summarizeGap(agentId, detail, fallback) {
751
- return `${agentId}: ${compactSingleLine(detail || fallback, 180)}`;
752
- }
753
-
754
- function textMentionsAnyKeyword(value, keywords) {
755
- const text = String(value || "").trim().toLowerCase();
756
- if (!text) {
757
- return false;
758
- }
759
- return keywords.some((keyword) => text.includes(String(keyword || "").trim().toLowerCase()));
760
- }
761
-
762
- function actionableIntegrationRecords(coordinationState) {
763
- return (coordinationState?.latestRecords || []).filter(
764
- (record) =>
765
- !["cancelled", "superseded"].includes(String(record?.status || "").trim().toLowerCase()) &&
766
- ![
767
- "human-feedback",
768
- "human-escalation",
769
- "orchestrator-guidance",
770
- "resolved-by-policy",
771
- "integration-summary",
772
- ].includes(record?.kind),
773
- );
774
- }
775
-
776
- function normalizeOwnedReference(value) {
777
- return String(value || "").trim().replace(/\/+$/, "");
778
- }
779
-
780
- function matchesOwnedPathArtifact(artifactRef, ownedPath) {
781
- const normalizedArtifact = normalizeOwnedReference(artifactRef);
782
- const normalizedOwnedPath = normalizeOwnedReference(ownedPath);
783
- if (!normalizedArtifact || !normalizedOwnedPath) {
784
- return false;
785
- }
786
- return (
787
- normalizedArtifact === normalizedOwnedPath ||
788
- normalizedArtifact.startsWith(`${normalizedOwnedPath}/`)
789
- );
790
- }
791
-
792
- function resolveArtifactOwners(artifactRef, agents) {
793
- const owners = [];
794
- const normalizedArtifact = normalizeOwnedReference(artifactRef);
795
- if (!normalizedArtifact) {
796
- return owners;
797
- }
798
- for (const agent of agents || []) {
799
- const ownedComponents = Array.isArray(agent?.components) ? agent.components : [];
800
- const ownedPaths = Array.isArray(agent?.ownedPaths) ? agent.ownedPaths : [];
801
- if (
802
- ownedComponents.some((componentId) => normalizeOwnedReference(componentId) === normalizedArtifact) ||
803
- ownedPaths.some((ownedPath) => matchesOwnedPathArtifact(normalizedArtifact, ownedPath))
804
- ) {
805
- owners.push(agent.agentId);
806
- }
807
- }
808
- return owners;
809
- }
810
-
811
- function inferIntegrationRecommendation(evidence) {
812
- if ((evidence.unresolvedBlockers || []).length > 0) {
813
- return {
814
- recommendation: "needs-more-work",
815
- detail: `${evidence.unresolvedBlockers.length} unresolved blocker(s) remain.`,
816
- };
817
- }
818
- if ((evidence.conflictingClaims || []).length > 0) {
819
- return {
820
- recommendation: "needs-more-work",
821
- detail: `${evidence.conflictingClaims.length} conflicting claim(s) remain.`,
822
- };
823
- }
824
- if ((evidence.proofGaps || []).length > 0) {
825
- return {
826
- recommendation: "needs-more-work",
827
- detail: `${evidence.proofGaps.length} proof gap(s) remain.`,
828
- };
829
- }
830
- if ((evidence.deployRisks || []).length > 0) {
831
- return {
832
- recommendation: "needs-more-work",
833
- detail: `${evidence.deployRisks.length} deploy or ops risk(s) remain.`,
834
- };
835
- }
836
- return {
837
- recommendation: "ready-for-doc-closure",
838
- detail:
839
- "No unresolved blockers, contradictions, proof gaps, or deploy risks remain in integration state.",
840
- };
841
- }
842
-
843
- export function buildWaveSecuritySummary({
844
- lanePaths,
845
- wave,
846
- attempt,
847
- summariesByAgentId = {},
848
- }) {
849
- const createdAt = toIsoTimestamp();
850
- const securityAgents = (wave.agents || []).filter((agent) => isSecurityReviewAgent(agent));
851
- if (securityAgents.length === 0) {
852
- return {
853
- wave: wave.wave,
854
- lane: lanePaths.lane,
855
- attempt,
856
- overallState: "not-applicable",
857
- totalFindings: 0,
858
- totalApprovals: 0,
859
- concernAgentIds: [],
860
- blockedAgentIds: [],
861
- detail: "No security reviewer declared for this wave.",
862
- agents: [],
863
- createdAt,
864
- updatedAt: createdAt,
865
- };
866
- }
867
- const agents = securityAgents.map((agent) => {
868
- const summary = summariesByAgentId?.[agent.agentId] || null;
869
- const validation = validateSecuritySummary(agent, summary);
870
- const explicitState = summary?.security?.state || null;
871
- return {
872
- agentId: agent.agentId,
873
- title: agent.title || agent.agentId,
874
- state: validation.ok
875
- ? explicitState || "clear"
876
- : explicitState === "blocked"
877
- ? "blocked"
878
- : "pending",
879
- findings: summary?.security?.findings || 0,
880
- approvals: summary?.security?.approvals || 0,
881
- detail: validation.ok
882
- ? summary?.security?.detail || validation.detail || ""
883
- : validation.detail,
884
- reportPath: summary?.reportPath || resolveSecurityReviewReportPath(agent) || null,
885
- statusCode: validation.statusCode,
886
- ok: validation.ok,
887
- };
888
- });
889
- const blockedAgentIds = agents
890
- .filter((entry) => entry.state === "blocked")
891
- .map((entry) => entry.agentId);
892
- const concernAgentIds = agents
893
- .filter((entry) => entry.state === "concerns")
894
- .map((entry) => entry.agentId);
895
- const pendingAgentIds = agents
896
- .filter((entry) => entry.state === "pending")
897
- .map((entry) => entry.agentId);
898
- const overallState =
899
- blockedAgentIds.length > 0
900
- ? "blocked"
901
- : pendingAgentIds.length > 0
902
- ? "pending"
903
- : concernAgentIds.length > 0
904
- ? "concerns"
905
- : "clear";
906
- const totalFindings = agents.reduce((sum, entry) => sum + (entry.findings || 0), 0);
907
- const totalApprovals = agents.reduce((sum, entry) => sum + (entry.approvals || 0), 0);
908
- const detail =
909
- overallState === "blocked"
910
- ? `Security review blocked by ${blockedAgentIds.join(", ")}.`
911
- : overallState === "pending"
912
- ? `Security review output is incomplete for ${pendingAgentIds.join(", ")}.`
913
- : overallState === "concerns"
914
- ? `Security review reported advisory concerns from ${concernAgentIds.join(", ")}.`
915
- : "Security review is clear.";
916
- return {
917
- wave: wave.wave,
918
- lane: lanePaths.lane,
919
- attempt,
920
- overallState,
921
- totalFindings,
922
- totalApprovals,
923
- concernAgentIds,
924
- blockedAgentIds,
925
- detail,
926
- agents,
927
- createdAt,
928
- updatedAt: createdAt,
929
- };
930
- }
931
-
932
- function renderWaveSecuritySummaryMarkdown(securitySummary) {
933
- return [
934
- `# Wave ${securitySummary.wave} Security Summary`,
935
- "",
936
- `- State: ${securitySummary.overallState || "unknown"}`,
937
- `- Detail: ${securitySummary.detail || "n/a"}`,
938
- `- Total findings: ${securitySummary.totalFindings || 0}`,
939
- `- Total approvals: ${securitySummary.totalApprovals || 0}`,
940
- `- Reviewers: ${(securitySummary.agents || []).length}`,
941
- "",
942
- "## Reviews",
943
- ...((securitySummary.agents || []).length > 0
944
- ? securitySummary.agents.map(
945
- (entry) =>
946
- `- ${entry.agentId}: state=${entry.state || "unknown"} findings=${entry.findings || 0} approvals=${entry.approvals || 0}${entry.reportPath ? ` report=${entry.reportPath}` : ""}${entry.detail ? ` detail=${entry.detail}` : ""}`,
947
- )
948
- : ["- None."]),
949
- "",
950
- ].join("\n");
951
- }
952
-
953
- function padReportedEntries(entries, minimumCount, label) {
954
- const padded = [...entries];
955
- for (let index = padded.length + 1; index <= minimumCount; index += 1) {
956
- padded.push(`${label} #${index}`);
957
- }
958
- return padded;
959
- }
960
-
961
- function buildIntegrationEvidence({
962
- lanePaths,
963
- wave,
964
- coordinationState,
965
- summariesByAgentId,
966
- docsQueue,
967
- agentRuns,
968
- dependencySnapshot = null,
969
- capabilityAssignments = [],
970
- securitySummary = null,
971
- }) {
972
- const openClaims = (coordinationState?.claims || [])
973
- .filter((record) => isOpenCoordinationStatus(record.status))
974
- .map((record) => summarizeIntegrationRecord(record));
975
- const conflictingClaims = (coordinationState?.claims || [])
976
- .filter(
977
- (record) =>
978
- isOpenCoordinationStatus(record.status) &&
979
- /conflict|contradict/i.test(`${record.summary || ""}\n${record.detail || ""}`),
980
- )
981
- .map((record) => summarizeIntegrationRecord(record));
982
- const unresolvedBlockers = (coordinationState?.blockers || [])
983
- .filter((record) => isOpenCoordinationStatus(record.status))
984
- .map((record) => summarizeIntegrationRecord(record));
985
-
986
- const interfaceKeywords = ["interface", "contract", "api", "schema", "migration", "signature"];
987
- const changedInterfaces = actionableIntegrationRecords(coordinationState)
988
- .filter((record) =>
989
- textMentionsAnyKeyword(
990
- [record.summary, record.detail, ...(record.artifactRefs || [])].join("\n"),
991
- interfaceKeywords,
992
- ),
993
- )
994
- .map((record) => summarizeIntegrationRecord(record));
995
-
996
- const crossComponentImpacts = actionableIntegrationRecords(coordinationState)
997
- .flatMap((record) => {
998
- const owners = new Set();
999
- for (const artifactRef of record.artifactRefs || []) {
1000
- for (const owner of resolveArtifactOwners(artifactRef, wave.agents)) {
1001
- owners.add(owner);
1002
- }
1003
- }
1004
- for (const target of record.targets || []) {
1005
- if (String(target).startsWith("agent:")) {
1006
- owners.add(String(target).slice("agent:".length));
1007
- } else if ((wave.agents || []).some((agent) => agent.agentId === target)) {
1008
- owners.add(String(target));
1009
- }
1010
- }
1011
- if (owners.size <= 1) {
1012
- return [];
1013
- }
1014
- return [
1015
- `${summarizeIntegrationRecord(record)} [owners: ${Array.from(owners).toSorted().join(", ")}]`,
1016
- ];
1017
- });
1018
-
1019
- const proofGapEntries = [];
1020
- const docGapEntries = Array.isArray(docsQueue?.items)
1021
- ? docsQueue.items.map((item) => summarizeDocsQueueItem(item))
1022
- : [];
1023
- const deployRiskEntries = [];
1024
- const securityFindingEntries = [];
1025
- const securityApprovalEntries = [];
1026
- for (const agent of wave.agents || []) {
1027
- const summary = summariesByAgentId?.[agent.agentId] || null;
1028
- const contEvalImplementationOwning =
1029
- agent.agentId === lanePaths.contEvalAgentId &&
1030
- isContEvalImplementationOwningAgent(agent, {
1031
- contEvalAgentId: lanePaths.contEvalAgentId,
1032
- });
1033
- if (isSecurityReviewAgent(agent)) {
1034
- continue;
1035
- }
1036
- if (agent.agentId === lanePaths.contEvalAgentId) {
1037
- const validation = validateContEvalSummary(agent, summary, {
1038
- mode: "live",
1039
- evalTargets: wave.evalTargets,
1040
- benchmarkCatalogPath: lanePaths.laneProfile?.paths?.benchmarkCatalogPath,
1041
- });
1042
- if (!validation.ok) {
1043
- proofGapEntries.push(
1044
- summarizeGap(agent.agentId, validation.detail, "cont-EVAL target is not yet satisfied."),
1045
- );
1046
- }
1047
- }
1048
- if (
1049
- ![
1050
- lanePaths.contQaAgentId,
1051
- lanePaths.integrationAgentId,
1052
- lanePaths.documentationAgentId,
1053
- ].includes(agent.agentId) &&
1054
- (agent.agentId !== lanePaths.contEvalAgentId || contEvalImplementationOwning)
1055
- ) {
1056
- const validation = validateImplementationSummary(agent, summary);
1057
- if (!validation.ok) {
1058
- const entry = summarizeGap(agent.agentId, validation.detail, "Implementation validation failed.");
1059
- if (["missing-doc-delta", "doc-impact-gap"].includes(validation.statusCode)) {
1060
- docGapEntries.push(entry);
1061
- } else {
1062
- proofGapEntries.push(entry);
1063
- }
1064
- }
1065
- }
1066
- for (const gap of summary?.gaps || []) {
1067
- const entry = summarizeGap(
1068
- agent.agentId,
1069
- gap.detail,
1070
- `${gap.kind || "unknown"} gap reported.`,
1071
- );
1072
- if (gap.kind === "docs") {
1073
- docGapEntries.push(entry);
1074
- } else if (gap.kind === "ops") {
1075
- deployRiskEntries.push(entry);
1076
- } else {
1077
- proofGapEntries.push(entry);
1078
- }
1079
- }
1080
- }
1081
-
1082
- for (const run of agentRuns || []) {
1083
- const signals = parseStructuredSignalsFromLog(run.logPath);
1084
- if (signals?.deployment && signals.deployment.state !== "healthy") {
1085
- deployRiskEntries.push(
1086
- summarizeGap(
1087
- run.agent.agentId,
1088
- `Deployment ${signals.deployment.service} ended in state ${signals.deployment.state}${signals.deployment.detail ? ` (${signals.deployment.detail})` : ""}.`,
1089
- "Deployment did not finish healthy.",
1090
- ),
1091
- );
1092
- }
1093
- if (
1094
- signals?.infra &&
1095
- !["conformant", "action-complete"].includes(
1096
- String(signals.infra.state || "").trim().toLowerCase(),
1097
- )
1098
- ) {
1099
- deployRiskEntries.push(
1100
- summarizeGap(
1101
- run.agent.agentId,
1102
- `Infra ${signals.infra.kind || "unknown"} on ${signals.infra.target || "unknown"} ended in state ${signals.infra.state || "unknown"}${signals.infra.detail ? ` (${signals.infra.detail})` : ""}.`,
1103
- "Infra risk remains open.",
1104
- ),
1105
- );
1106
- }
1107
- }
1108
-
1109
- const inboundDependencies = (dependencySnapshot?.openInbound || []).map(
1110
- (record) =>
1111
- `${record.id}: ${compactSingleLine(record.summary || record.detail || "inbound dependency", 180)}${record.assignedAgentId ? ` -> ${record.assignedAgentId}` : ""}`,
1112
- );
1113
- const outboundDependencies = (dependencySnapshot?.openOutbound || []).map(
1114
- (record) =>
1115
- `${record.id}: ${compactSingleLine(record.summary || record.detail || "outbound dependency", 180)}`,
1116
- );
1117
- const helperAssignments = (capabilityAssignments || [])
1118
- .filter((assignment) => assignment.blocking)
1119
- .map(
1120
- (assignment) =>
1121
- `${assignment.requestId}: ${assignment.target}${assignment.assignedAgentId ? ` -> ${assignment.assignedAgentId}` : " -> unresolved"} (${assignment.assignmentReason || "n/a"})`,
1122
- );
1123
-
1124
- for (const review of securitySummary?.agents || []) {
1125
- if (review.state === "blocked" || review.state === "concerns") {
1126
- securityFindingEntries.push(
1127
- summarizeGap(
1128
- review.agentId,
1129
- review.detail,
1130
- review.state === "blocked"
1131
- ? "Security review blocked the wave."
1132
- : "Security review reported advisory concerns.",
1133
- ),
1134
- );
1135
- }
1136
- if ((review.approvals || 0) > 0) {
1137
- securityApprovalEntries.push(
1138
- summarizeGap(
1139
- review.agentId,
1140
- review.detail,
1141
- `${review.approvals} security approval(s) remain open.`,
1142
- ),
1143
- );
1144
- }
1145
- }
1146
-
1147
- return {
1148
- openClaims: uniqueStringEntries(openClaims),
1149
- conflictingClaims: uniqueStringEntries(conflictingClaims),
1150
- unresolvedBlockers: uniqueStringEntries(unresolvedBlockers),
1151
- changedInterfaces: uniqueStringEntries(changedInterfaces),
1152
- crossComponentImpacts: uniqueStringEntries(crossComponentImpacts),
1153
- proofGaps: uniqueStringEntries(proofGapEntries),
1154
- docGaps: uniqueStringEntries(docGapEntries),
1155
- deployRisks: uniqueStringEntries(deployRiskEntries),
1156
- inboundDependencies: uniqueStringEntries(inboundDependencies),
1157
- outboundDependencies: uniqueStringEntries(outboundDependencies),
1158
- helperAssignments: uniqueStringEntries(helperAssignments),
1159
- securityState: securitySummary?.overallState || "not-applicable",
1160
- securityFindings: uniqueStringEntries(securityFindingEntries),
1161
- securityApprovals: uniqueStringEntries(securityApprovalEntries),
1162
- };
1163
- }
546
+ // --- Wrappers that bind local scope ---
1164
547
 
1165
- export function buildWaveIntegrationSummary({
548
+ export async function runClosureSweepPhase({
1166
549
  lanePaths,
1167
550
  wave,
1168
- attempt,
1169
- coordinationState,
1170
- summariesByAgentId,
1171
- docsQueue,
1172
- runtimeAssignments,
1173
- agentRuns,
1174
- capabilityAssignments = [],
1175
- dependencySnapshot = null,
1176
- securitySummary = null,
551
+ closureRuns,
552
+ coordinationLogPath,
553
+ refreshDerivedState,
554
+ dashboardState,
555
+ recordCombinedEvent,
556
+ flushDashboards,
557
+ options,
558
+ feedbackStateByRequestId,
559
+ appendCoordination,
560
+ launchAgentSessionFn = launchAgentSession,
561
+ waitForWaveCompletionFn = waitForWaveCompletion,
1177
562
  }) {
1178
- const explicitIntegration = summariesByAgentId[lanePaths.integrationAgentId]?.integration || null;
1179
- const evidence = buildIntegrationEvidence({
563
+ return runClosureSweepPhaseImpl({
1180
564
  lanePaths,
1181
565
  wave,
1182
- coordinationState,
1183
- summariesByAgentId,
1184
- docsQueue,
1185
- agentRuns,
1186
- capabilityAssignments,
1187
- dependencySnapshot,
1188
- securitySummary,
566
+ closureRuns,
567
+ coordinationLogPath,
568
+ refreshDerivedState,
569
+ dashboardState,
570
+ recordCombinedEvent,
571
+ flushDashboards,
572
+ options,
573
+ feedbackStateByRequestId,
574
+ appendCoordination,
575
+ launchAgentSessionFn,
576
+ waitForWaveCompletionFn,
577
+ readWaveContEvalGateFn: readWaveContEvalGate,
578
+ readWaveSecurityGateFn: readWaveSecurityGate,
579
+ readWaveIntegrationBarrierFn: readWaveIntegrationBarrier,
580
+ readWaveDocumentationGateFn: readWaveDocumentationGate,
581
+ readWaveComponentMatrixGateFn: readWaveComponentMatrixGate,
582
+ readWaveContQaGateFn: readWaveContQaGate,
583
+ materializeAgentExecutionSummaryForRunFn: materializeAgentExecutionSummaryForRun,
584
+ monitorWaveHumanFeedbackFn: monitorWaveHumanFeedback,
1189
585
  });
1190
- if (explicitIntegration) {
1191
- return {
1192
- wave: wave.wave,
1193
- lane: lanePaths.lane,
1194
- agentId: lanePaths.integrationAgentId,
1195
- attempt,
1196
- openClaims: padReportedEntries(
1197
- evidence.openClaims,
1198
- explicitIntegration.claims || 0,
1199
- "Integration steward reported unresolved claim",
1200
- ),
1201
- conflictingClaims: padReportedEntries(
1202
- evidence.conflictingClaims,
1203
- explicitIntegration.conflicts || 0,
1204
- "Integration steward reported unresolved conflict",
1205
- ),
1206
- unresolvedBlockers: padReportedEntries(
1207
- evidence.unresolvedBlockers,
1208
- explicitIntegration.blockers || 0,
1209
- "Integration steward reported unresolved blocker",
1210
- ),
1211
- changedInterfaces: evidence.changedInterfaces,
1212
- crossComponentImpacts: evidence.crossComponentImpacts,
1213
- proofGaps: evidence.proofGaps,
1214
- docGaps: evidence.docGaps,
1215
- deployRisks: evidence.deployRisks,
1216
- securityState: evidence.securityState,
1217
- securityFindings: evidence.securityFindings,
1218
- securityApprovals: evidence.securityApprovals,
1219
- inboundDependencies: evidence.inboundDependencies,
1220
- outboundDependencies: evidence.outboundDependencies,
1221
- helperAssignments: evidence.helperAssignments,
1222
- runtimeAssignments,
1223
- recommendation: explicitIntegration.state,
1224
- detail: explicitIntegration.detail || "",
1225
- createdAt: toIsoTimestamp(),
1226
- updatedAt: toIsoTimestamp(),
1227
- };
1228
- }
1229
- const inferred = inferIntegrationRecommendation(evidence);
1230
- return {
1231
- wave: wave.wave,
1232
- lane: lanePaths.lane,
1233
- agentId: "launcher",
1234
- attempt,
1235
- ...evidence,
1236
- runtimeAssignments,
1237
- recommendation: inferred.recommendation,
1238
- detail: inferred.detail,
1239
- createdAt: toIsoTimestamp(),
1240
- updatedAt: toIsoTimestamp(),
1241
- };
1242
586
  }
1243
587
 
1244
- function renderIntegrationSection(title, items) {
1245
- return [
1246
- title,
1247
- ...((items || []).length > 0 ? items.map((item) => `- ${item}`) : ["- None."]),
1248
- "",
1249
- ];
588
+ export function readWaveInfraGate(agentRuns) {
589
+ return readWaveInfraGateImpl(agentRuns);
1250
590
  }
1251
591
 
1252
- function renderIntegrationSummaryMarkdown(integrationSummary) {
1253
- return [
1254
- `# Wave ${integrationSummary.wave} Integration Summary`,
1255
- "",
1256
- `- Recommendation: ${integrationSummary.recommendation || "unknown"}`,
1257
- `- Detail: ${integrationSummary.detail || "n/a"}`,
1258
- `- Open claims: ${(integrationSummary.openClaims || []).length}`,
1259
- `- Conflicting claims: ${(integrationSummary.conflictingClaims || []).length}`,
1260
- `- Unresolved blockers: ${(integrationSummary.unresolvedBlockers || []).length}`,
1261
- `- Changed interfaces: ${(integrationSummary.changedInterfaces || []).length}`,
1262
- `- Cross-component impacts: ${(integrationSummary.crossComponentImpacts || []).length}`,
1263
- `- Proof gaps: ${(integrationSummary.proofGaps || []).length}`,
1264
- `- Deploy risks: ${(integrationSummary.deployRisks || []).length}`,
1265
- `- Documentation gaps: ${(integrationSummary.docGaps || []).length}`,
1266
- `- Security review: ${integrationSummary.securityState || "not-applicable"}`,
1267
- `- Security findings: ${(integrationSummary.securityFindings || []).length}`,
1268
- `- Security approvals: ${(integrationSummary.securityApprovals || []).length}`,
1269
- `- Inbound dependencies: ${(integrationSummary.inboundDependencies || []).length}`,
1270
- `- Outbound dependencies: ${(integrationSummary.outboundDependencies || []).length}`,
1271
- `- Helper assignments: ${(integrationSummary.helperAssignments || []).length}`,
1272
- "",
1273
- ...renderIntegrationSection("## Open Claims", integrationSummary.openClaims),
1274
- ...renderIntegrationSection("## Conflicting Claims", integrationSummary.conflictingClaims),
1275
- ...renderIntegrationSection("## Unresolved Blockers", integrationSummary.unresolvedBlockers),
1276
- ...renderIntegrationSection("## Changed Interfaces", integrationSummary.changedInterfaces),
1277
- ...renderIntegrationSection(
1278
- "## Cross-Component Impacts",
1279
- integrationSummary.crossComponentImpacts,
1280
- ),
1281
- ...renderIntegrationSection("## Proof Gaps", integrationSummary.proofGaps),
1282
- ...renderIntegrationSection("## Deploy Risks", integrationSummary.deployRisks),
1283
- ...renderIntegrationSection("## Security Findings", integrationSummary.securityFindings),
1284
- ...renderIntegrationSection("## Security Approvals", integrationSummary.securityApprovals),
1285
- ...renderIntegrationSection("## Inbound Dependencies", integrationSummary.inboundDependencies),
1286
- ...renderIntegrationSection("## Outbound Dependencies", integrationSummary.outboundDependencies),
1287
- ...renderIntegrationSection("## Helper Assignments", integrationSummary.helperAssignments),
1288
- "## Runtime Assignments",
1289
- ...((integrationSummary.runtimeAssignments || []).length > 0
1290
- ? integrationSummary.runtimeAssignments.map(
1291
- (assignment) =>
1292
- `- ${assignment.agentId}: executor=${assignment.executorId || "n/a"} role=${assignment.role || "n/a"} profile=${assignment.profile || "none"} fallback_used=${assignment.fallbackUsed ? "yes" : "no"}`,
1293
- )
1294
- : ["- None."]),
1295
- "",
1296
- ...renderIntegrationSection("## Documentation Gaps", integrationSummary.docGaps),
1297
- ].join("\n");
592
+ export function buildGateSnapshot(params) {
593
+ return buildGateSnapshotImpl({
594
+ ...params,
595
+ readWaveInfraGateFn: readWaveInfraGate,
596
+ });
1298
597
  }
1299
598
 
1300
- function writeWaveDerivedState({
1301
- lanePaths,
1302
- wave,
1303
- agentRuns = [],
1304
- summariesByAgentId = {},
1305
- feedbackRequests = [],
1306
- attempt = 0,
1307
- orchestratorId = null,
1308
- }) {
1309
- const coordinationLogPath = waveCoordinationLogPath(lanePaths, wave.wave);
1310
- const existingDocsQueue = readDocsQueue(waveDocsQueuePath(lanePaths, wave.wave));
1311
- const existingIntegrationSummary = readJsonOrNull(waveIntegrationPath(lanePaths, wave.wave));
1312
- const existingLedger = readWaveLedger(waveLedgerPath(lanePaths, wave.wave));
1313
- updateSeedRecords(coordinationLogPath, {
1314
- lane: lanePaths.lane,
1315
- wave: wave.wave,
1316
- agents: wave.agents,
1317
- componentPromotions: wave.componentPromotions,
1318
- sharedPlanDocs: lanePaths.sharedPlanDocs,
1319
- contQaAgentId: lanePaths.contQaAgentId,
1320
- contEvalAgentId: lanePaths.contEvalAgentId,
1321
- integrationAgentId: lanePaths.integrationAgentId,
1322
- documentationAgentId: lanePaths.documentationAgentId,
1323
- feedbackRequests,
1324
- });
1325
- let coordinationState = readMaterializedCoordinationState(coordinationLogPath);
1326
- const clarificationTriage = triageClarificationRequests({
1327
- lanePaths,
1328
- wave,
1329
- coordinationLogPath,
1330
- coordinationState,
1331
- orchestratorId,
1332
- attempt,
1333
- resolutionContext: {
1334
- docsQueue: existingDocsQueue,
1335
- integrationSummary: existingIntegrationSummary,
1336
- ledger: existingLedger,
1337
- summariesByAgentId,
1338
- },
1339
- });
1340
- if (clarificationTriage.changed) {
1341
- coordinationState = readMaterializedCoordinationState(coordinationLogPath);
1342
- }
1343
- const capabilityAssignments = buildRequestAssignments({
1344
- coordinationState,
1345
- agents: wave.agents,
1346
- ledger: existingLedger,
1347
- capabilityRouting: lanePaths.capabilityRouting,
1348
- });
1349
- syncAssignmentRecords(coordinationLogPath, {
1350
- lane: lanePaths.lane,
1351
- wave: wave.wave,
1352
- assignments: capabilityAssignments,
1353
- });
1354
- coordinationState = readMaterializedCoordinationState(coordinationLogPath);
1355
- const dependencySnapshot = buildDependencySnapshot({
1356
- dirPath: lanePaths.crossLaneDependenciesDir,
1357
- lane: lanePaths.lane,
1358
- waveNumber: wave.wave,
1359
- agents: wave.agents,
1360
- ledger: existingLedger,
1361
- capabilityRouting: lanePaths.capabilityRouting,
1362
- });
1363
- writeAssignmentSnapshot(waveAssignmentsPath(lanePaths, wave.wave), capabilityAssignments, {
1364
- lane: lanePaths.lane,
1365
- wave: wave.wave,
1366
- });
1367
- writeDependencySnapshot(waveDependencySnapshotPath(lanePaths, wave.wave), dependencySnapshot, {
1368
- lane: lanePaths.lane,
1369
- wave: wave.wave,
1370
- });
1371
- writeDependencySnapshotMarkdown(
1372
- waveDependencySnapshotMarkdownPath(lanePaths, wave.wave),
1373
- dependencySnapshot,
1374
- );
1375
- const runtimeAssignments = wave.agents.map((agent) => ({
1376
- agentId: agent.agentId,
1377
- role: agent.executorResolved?.role || null,
1378
- initialExecutorId: agent.executorResolved?.initialExecutorId || null,
1379
- executorId: agent.executorResolved?.id || null,
1380
- profile: agent.executorResolved?.profile || null,
1381
- selectedBy: agent.executorResolved?.selectedBy || null,
1382
- retryPolicy: agent.executorResolved?.retryPolicy || null,
1383
- allowFallbackOnRetry: agent.executorResolved?.allowFallbackOnRetry !== false,
1384
- fallbacks: agent.executorResolved?.fallbacks || [],
1385
- fallbackUsed: agent.executorResolved?.fallbackUsed === true,
1386
- fallbackReason: agent.executorResolved?.fallbackReason || null,
1387
- executorHistory: agent.executorResolved?.executorHistory || [],
1388
- }));
1389
- const docsQueue = buildDocsQueue({
1390
- lane: lanePaths.lane,
1391
- wave,
1392
- summariesByAgentId,
1393
- sharedPlanDocs: lanePaths.sharedPlanDocs,
1394
- componentPromotions: wave.componentPromotions,
1395
- runtimeAssignments,
1396
- });
1397
- writeDocsQueue(waveDocsQueuePath(lanePaths, wave.wave), docsQueue);
1398
- const securitySummary = buildWaveSecuritySummary({
1399
- lanePaths,
1400
- wave,
1401
- attempt,
1402
- summariesByAgentId,
1403
- });
1404
- writeJsonArtifact(waveSecurityPath(lanePaths, wave.wave), securitySummary);
1405
- writeTextAtomic(
1406
- waveSecurityMarkdownPath(lanePaths, wave.wave),
1407
- `${renderWaveSecuritySummaryMarkdown(securitySummary)}\n`,
1408
- );
1409
- const integrationSummary = buildWaveIntegrationSummary({
1410
- lanePaths,
1411
- wave,
1412
- attempt,
1413
- coordinationState,
1414
- summariesByAgentId,
1415
- docsQueue,
1416
- runtimeAssignments,
1417
- agentRuns,
1418
- capabilityAssignments,
1419
- dependencySnapshot,
1420
- securitySummary,
1421
- });
1422
- writeJsonArtifact(waveIntegrationPath(lanePaths, wave.wave), integrationSummary);
1423
- writeTextAtomic(
1424
- waveIntegrationMarkdownPath(lanePaths, wave.wave),
1425
- `${renderIntegrationSummaryMarkdown(integrationSummary)}\n`,
1426
- );
1427
- const ledger = deriveWaveLedger({
1428
- lane: lanePaths.lane,
1429
- wave,
1430
- summariesByAgentId,
1431
- coordinationState,
1432
- integrationSummary,
1433
- docsQueue,
1434
- attempt,
1435
- contQaAgentId: lanePaths.contQaAgentId,
1436
- contEvalAgentId: lanePaths.contEvalAgentId,
1437
- integrationAgentId: lanePaths.integrationAgentId,
1438
- documentationAgentId: lanePaths.documentationAgentId,
1439
- benchmarkCatalogPath: lanePaths.laneProfile?.paths?.benchmarkCatalogPath,
1440
- capabilityAssignments,
1441
- dependencySnapshot,
1442
- });
1443
- writeWaveLedger(waveLedgerPath(lanePaths, wave.wave), ledger);
1444
- const inboxDir = waveInboxDir(lanePaths, wave.wave);
1445
- ensureDirectory(inboxDir);
1446
- const sharedSummary = compileSharedSummary({
1447
- wave,
1448
- state: coordinationState,
1449
- ledger,
1450
- integrationSummary,
1451
- capabilityAssignments,
1452
- dependencySnapshot,
1453
- });
1454
- const sharedSummaryPath = path.join(inboxDir, "shared-summary.md");
1455
- writeCompiledInbox(sharedSummaryPath, sharedSummary.text);
1456
- const inboxesByAgentId = {};
1457
- for (const agent of wave.agents) {
1458
- const inbox = compileAgentInbox({
1459
- wave,
1460
- agent,
1461
- state: coordinationState,
1462
- ledger,
1463
- docsQueue,
1464
- integrationSummary,
1465
- capabilityAssignments,
1466
- dependencySnapshot,
1467
- });
1468
- const inboxPath = path.join(inboxDir, `${agent.agentId}.md`);
1469
- writeCompiledInbox(inboxPath, inbox.text);
1470
- inboxesByAgentId[agent.agentId] = { path: inboxPath, text: inbox.text, truncated: inbox.truncated };
1471
- }
1472
- const boardText = renderCoordinationBoardProjection({
1473
- wave: wave.wave,
1474
- waveFile: wave.file,
1475
- agents: wave.agents,
1476
- state: coordinationState,
1477
- capabilityAssignments,
1478
- dependencySnapshot,
1479
- });
1480
- const responseMetrics = buildCoordinationResponseMetrics(coordinationState);
1481
- const messageBoardPath = path.join(lanePaths.messageboardsDir, `wave-${wave.wave}.md`);
1482
- writeCoordinationBoardProjection(messageBoardPath, {
1483
- wave: wave.wave,
1484
- waveFile: wave.file,
1485
- agents: wave.agents,
1486
- state: coordinationState,
1487
- capabilityAssignments,
1488
- dependencySnapshot,
1489
- });
1490
- return {
1491
- coordinationLogPath,
1492
- coordinationState,
1493
- clarificationTriage,
1494
- docsQueue,
1495
- capabilityAssignments,
1496
- dependencySnapshot,
1497
- securitySummary,
1498
- integrationSummary,
1499
- integrationMarkdownPath: waveIntegrationMarkdownPath(lanePaths, wave.wave),
1500
- securityMarkdownPath: waveSecurityMarkdownPath(lanePaths, wave.wave),
1501
- ledger,
1502
- responseMetrics,
1503
- sharedSummaryPath,
1504
- sharedSummaryText: sharedSummary.text,
1505
- inboxesByAgentId,
1506
- messageBoardPath,
1507
- messageBoardText: boardText,
1508
- };
1509
- }
1510
-
1511
- function applyDerivedStateToDashboard(dashboardState, derivedState) {
1512
- if (!dashboardState || !derivedState) {
1513
- return;
1514
- }
1515
- dashboardState.helperAssignmentsOpen = (derivedState.capabilityAssignments || []).filter(
1516
- (assignment) => assignment.blocking,
1517
- ).length;
1518
- dashboardState.inboundDependenciesOpen = (derivedState.dependencySnapshot?.openInbound || []).length;
1519
- dashboardState.outboundDependenciesOpen = (derivedState.dependencySnapshot?.openOutbound || []).length;
1520
- dashboardState.coordinationOpen = derivedState.coordinationState?.openRecords?.length || 0;
1521
- dashboardState.openClarifications =
1522
- (derivedState.coordinationState?.clarifications || []).filter((record) =>
1523
- isOpenCoordinationStatus(record.status),
1524
- ).length;
1525
- dashboardState.openHumanEscalations =
1526
- derivedState.responseMetrics?.openHumanEscalationCount ||
1527
- (derivedState.coordinationState?.humanEscalations || []).filter((record) =>
1528
- isOpenCoordinationStatus(record.status),
1529
- ).length;
1530
- dashboardState.oldestOpenCoordinationAgeMs =
1531
- derivedState.responseMetrics?.oldestOpenCoordinationAgeMs ?? null;
1532
- dashboardState.oldestUnackedRequestAgeMs =
1533
- derivedState.responseMetrics?.oldestUnackedRequestAgeMs ?? null;
1534
- dashboardState.overdueAckCount = derivedState.responseMetrics?.overdueAckCount || 0;
1535
- dashboardState.overdueClarificationCount =
1536
- derivedState.responseMetrics?.overdueClarificationCount || 0;
1537
- }
1538
-
1539
- export function readWaveImplementationGate(wave, agentRuns) {
1540
- const contQaAgentId = wave.contQaAgentId || "A0";
1541
- const contEvalAgentId = wave.contEvalAgentId || "E0";
1542
- const integrationAgentId = wave.integrationAgentId || "A8";
1543
- const documentationAgentId = wave.documentationAgentId || "A9";
1544
- for (const runInfo of agentRuns) {
1545
- if (
1546
- [contQaAgentId, integrationAgentId, documentationAgentId].includes(runInfo.agent.agentId) ||
1547
- isContEvalReportOnlyAgent(runInfo.agent, { contEvalAgentId }) ||
1548
- isSecurityReviewAgent(runInfo.agent)
1549
- ) {
1550
- continue;
1551
- }
1552
- const summary = readRunExecutionSummary(runInfo, wave);
1553
- const validation = validateImplementationSummary(runInfo.agent, summary);
1554
- if (!validation.ok) {
1555
- return {
1556
- ok: false,
1557
- agentId: runInfo.agent.agentId,
1558
- statusCode: validation.statusCode,
1559
- detail: validation.detail,
1560
- logPath: summary?.logPath || path.relative(REPO_ROOT, runInfo.logPath),
1561
- };
1562
- }
1563
- }
1564
- return {
1565
- ok: true,
1566
- agentId: null,
1567
- statusCode: "pass",
1568
- detail: "All implementation exit contracts are satisfied.",
1569
- logPath: null,
1570
- };
1571
- }
1572
-
1573
- function analyzePromotedComponentOwners(componentId, agentRuns, summariesByAgentId) {
1574
- const ownerRuns = (agentRuns || []).filter((runInfo) =>
1575
- runInfo.agent.components?.includes(componentId),
1576
- );
1577
- const ownerAgentIds = ownerRuns.map((runInfo) => runInfo.agent.agentId);
1578
- const satisfiedAgentIds = [];
1579
- const waitingOnAgentIds = [];
1580
- const failedOwnContractAgentIds = [];
1581
- for (const runInfo of ownerRuns) {
1582
- const summary = summariesByAgentId?.[runInfo.agent.agentId] || null;
1583
- const implementationValidation = validateImplementationSummary(runInfo.agent, summary);
1584
- const componentMarkers = new Map(
1585
- Array.isArray(summary?.components)
1586
- ? summary.components.map((component) => [component.componentId, component])
1587
- : [],
1588
- );
1589
- const marker = componentMarkers.get(componentId);
1590
- const expectedLevel = runInfo.agent.componentTargets?.[componentId] || null;
1591
- const componentSatisfied =
1592
- marker &&
1593
- marker.state === "met" &&
1594
- (!expectedLevel || marker.level === expectedLevel);
1595
- if (implementationValidation.ok && componentSatisfied) {
1596
- satisfiedAgentIds.push(runInfo.agent.agentId);
1597
- continue;
1598
- }
1599
- waitingOnAgentIds.push(runInfo.agent.agentId);
1600
- if (!implementationValidation.ok) {
1601
- failedOwnContractAgentIds.push(runInfo.agent.agentId);
1602
- }
1603
- }
1604
- return {
1605
- componentId,
1606
- ownerRuns,
1607
- ownerAgentIds,
1608
- satisfiedAgentIds,
1609
- waitingOnAgentIds,
1610
- failedOwnContractAgentIds,
1611
- };
1612
- }
1613
-
1614
- function buildSharedComponentSiblingPendingFailure(componentState) {
1615
- if (
1616
- !componentState ||
1617
- componentState.satisfiedAgentIds.length === 0 ||
1618
- componentState.waitingOnAgentIds.length === 0
1619
- ) {
1620
- return null;
1621
- }
1622
- const landedSummary =
1623
- componentState.satisfiedAgentIds.length === 1
1624
- ? `${componentState.satisfiedAgentIds[0]} desired-state slice landed`
1625
- : `${componentState.satisfiedAgentIds.join(", ")} desired-state slices landed`;
1626
- const ownerRun =
1627
- componentState.ownerRuns.find((runInfo) =>
1628
- componentState.waitingOnAgentIds.includes(runInfo.agent.agentId),
1629
- ) ||
1630
- componentState.ownerRuns[0] ||
1631
- null;
1632
- return {
1633
- ok: false,
1634
- agentId: componentState.waitingOnAgentIds[0] || ownerRun?.agent?.agentId || null,
1635
- componentId: componentState.componentId || null,
1636
- statusCode: "shared-component-sibling-pending",
1637
- detail: `${landedSummary}; shared component closure still depends on ${componentState.waitingOnAgentIds.join("/")}.`,
1638
- logPath: ownerRun ? path.relative(REPO_ROOT, ownerRun.logPath) : null,
1639
- ownerAgentIds: componentState.ownerAgentIds,
1640
- satisfiedAgentIds: componentState.satisfiedAgentIds,
1641
- waitingOnAgentIds: componentState.waitingOnAgentIds,
1642
- failedOwnContractAgentIds: componentState.failedOwnContractAgentIds,
1643
- };
1644
- }
1645
-
1646
- export function readWaveComponentGate(wave, agentRuns, options = {}) {
1647
- const summariesByAgentId = Object.fromEntries(
1648
- agentRuns.map((runInfo) => [runInfo.agent.agentId, readRunExecutionSummary(runInfo, wave)]),
1649
- );
1650
- const validation = validateWaveComponentPromotions(wave, summariesByAgentId, options);
1651
- const sharedPending = (wave.componentPromotions || [])
1652
- .map((promotion) =>
1653
- buildSharedComponentSiblingPendingFailure(
1654
- analyzePromotedComponentOwners(promotion.componentId, agentRuns, summariesByAgentId),
1655
- ),
1656
- )
1657
- .find(Boolean);
1658
- if (sharedPending) {
1659
- return sharedPending;
1660
- }
1661
- if (validation.ok) {
1662
- return {
1663
- ok: true,
1664
- agentId: null,
1665
- componentId: null,
1666
- statusCode: validation.statusCode,
1667
- detail: validation.detail,
1668
- logPath: null,
1669
- };
1670
- }
1671
- const componentState = analyzePromotedComponentOwners(
1672
- validation.componentId,
1673
- agentRuns,
1674
- summariesByAgentId,
1675
- );
1676
- const ownerRun = componentState.ownerRuns[0] ?? null;
1677
- return {
1678
- ok: false,
1679
- agentId: ownerRun?.agent?.agentId || null,
1680
- componentId: validation.componentId || null,
1681
- statusCode: validation.statusCode,
1682
- detail: validation.detail,
1683
- logPath: ownerRun ? path.relative(REPO_ROOT, ownerRun.logPath) : null,
1684
- ownerAgentIds: componentState.ownerAgentIds,
1685
- satisfiedAgentIds: componentState.satisfiedAgentIds,
1686
- waitingOnAgentIds: componentState.waitingOnAgentIds,
1687
- failedOwnContractAgentIds: componentState.failedOwnContractAgentIds,
1688
- };
1689
- }
1690
-
1691
- export function readWaveComponentMatrixGate(wave, agentRuns, options = {}) {
1692
- const validation = validateWaveComponentMatrixCurrentLevels(wave, options);
1693
- if (validation.ok) {
1694
- return {
1695
- ok: true,
1696
- agentId: null,
1697
- componentId: null,
1698
- statusCode: validation.statusCode,
1699
- detail: validation.detail,
1700
- logPath: null,
1701
- };
1702
- }
1703
- const documentationAgentId =
1704
- options.documentationAgentId || wave.documentationAgentId || "A9";
1705
- const docRun =
1706
- agentRuns.find((runInfo) => runInfo.agent.agentId === documentationAgentId) ?? null;
1707
- return {
1708
- ok: false,
1709
- agentId: docRun?.agent?.agentId || null,
1710
- componentId: validation.componentId || null,
1711
- statusCode: validation.statusCode,
1712
- detail: validation.detail,
1713
- logPath: docRun ? path.relative(REPO_ROOT, docRun.logPath) : null,
1714
- };
1715
- }
1716
-
1717
- export function readWaveDocumentationGate(wave, agentRuns) {
1718
- const documentationAgentId = wave.documentationAgentId || "A9";
1719
- const docRun =
1720
- agentRuns.find((run) => run.agent.agentId === documentationAgentId) ?? null;
1721
- if (!docRun) {
1722
- return {
1723
- ok: true,
1724
- agentId: null,
1725
- statusCode: "pass",
1726
- detail: "No documentation steward declared for this wave.",
1727
- logPath: null,
1728
- };
1729
- }
1730
- const summary = readRunExecutionSummary(docRun, wave);
1731
- const validation = validateDocumentationClosureSummary(docRun.agent, summary);
1732
- return {
1733
- ok: validation.ok,
1734
- agentId: docRun.agent.agentId,
1735
- statusCode: validation.statusCode,
1736
- detail: validation.detail,
1737
- logPath: summary?.logPath || path.relative(REPO_ROOT, docRun.logPath),
1738
- };
1739
- }
1740
-
1741
- export function readWaveSecurityGate(wave, agentRuns) {
1742
- const securityRuns = (agentRuns || []).filter((run) => isSecurityReviewAgent(run.agent));
1743
- if (securityRuns.length === 0) {
1744
- return {
1745
- ok: true,
1746
- agentId: null,
1747
- statusCode: "pass",
1748
- detail: "No security reviewer declared for this wave.",
1749
- logPath: null,
1750
- };
1751
- }
1752
- const concernAgentIds = [];
1753
- for (const runInfo of securityRuns) {
1754
- const summary = readRunExecutionSummary(runInfo, wave);
1755
- const validation = validateSecuritySummary(runInfo.agent, summary);
1756
- if (!validation.ok) {
1757
- return {
1758
- ok: false,
1759
- agentId: runInfo.agent.agentId,
1760
- statusCode: validation.statusCode,
1761
- detail: validation.detail,
1762
- logPath: summary?.logPath || path.relative(REPO_ROOT, runInfo.logPath),
1763
- };
1764
- }
1765
- if (summary?.security?.state === "concerns") {
1766
- concernAgentIds.push(runInfo.agent.agentId);
1767
- }
1768
- }
1769
- if (concernAgentIds.length > 0) {
1770
- return {
1771
- ok: true,
1772
- agentId: null,
1773
- statusCode: "security-concerns",
1774
- detail: `Security review reported advisory concerns (${concernAgentIds.join(", ")}).`,
1775
- logPath: null,
1776
- };
1777
- }
1778
- return {
1779
- ok: true,
1780
- agentId: null,
1781
- statusCode: "pass",
1782
- detail: "Security review is clear.",
1783
- logPath: null,
1784
- };
1785
- }
1786
-
1787
- export function readWaveIntegrationGate(wave, agentRuns, options = {}) {
1788
- const integrationAgentId =
1789
- options.integrationAgentId || wave.integrationAgentId || "A8";
1790
- const requireIntegration =
1791
- options.requireIntegrationSteward === true ||
1792
- (options.requireIntegrationStewardFromWave !== null &&
1793
- options.requireIntegrationStewardFromWave !== undefined &&
1794
- wave.wave >= options.requireIntegrationStewardFromWave);
1795
- const integrationRun =
1796
- agentRuns.find((run) => run.agent.agentId === integrationAgentId) ?? null;
1797
- if (!integrationRun) {
1798
- return {
1799
- ok: !requireIntegration,
1800
- agentId: requireIntegration ? integrationAgentId : null,
1801
- statusCode: requireIntegration ? "missing-integration" : "pass",
1802
- detail: requireIntegration
1803
- ? `Agent ${integrationAgentId} is missing.`
1804
- : "No explicit integration steward declared for this wave.",
1805
- logPath: null,
1806
- };
1807
- }
1808
- const summary = readRunExecutionSummary(integrationRun, wave);
1809
- const validation = validateIntegrationSummary(integrationRun.agent, summary);
1810
- return {
1811
- ok: validation.ok,
1812
- agentId: integrationRun.agent.agentId,
1813
- statusCode: validation.statusCode,
1814
- detail: validation.detail,
1815
- logPath: summary?.logPath || path.relative(REPO_ROOT, integrationRun.logPath),
1816
- };
1817
- }
1818
-
1819
- export function readWaveIntegrationBarrier(wave, agentRuns, derivedState, options = {}) {
1820
- const markerGate = readWaveIntegrationGate(wave, agentRuns, options);
1821
- if (!markerGate.ok) {
1822
- return markerGate;
1823
- }
1824
- const integrationSummary = derivedState?.integrationSummary || null;
1825
- if (!integrationSummary) {
1826
- return {
1827
- ok: false,
1828
- agentId: markerGate.agentId,
1829
- statusCode: "missing-integration-summary",
1830
- detail: `Missing integration summary artifact for wave ${wave.wave}.`,
1831
- logPath: markerGate.logPath,
1832
- };
1833
- }
1834
- if (integrationSummary.recommendation !== "ready-for-doc-closure") {
1835
- return {
1836
- ok: false,
1837
- agentId: markerGate.agentId,
1838
- statusCode: "integration-needs-more-work",
1839
- detail:
1840
- integrationSummary.detail ||
1841
- `Integration summary still reports ${integrationSummary.recommendation}.`,
1842
- logPath: markerGate.logPath,
1843
- };
1844
- }
1845
- return markerGate;
1846
- }
1847
-
1848
- export async function runClosureSweepPhase({
1849
- lanePaths,
1850
- wave,
1851
- closureRuns,
1852
- coordinationLogPath,
1853
- refreshDerivedState,
1854
- dashboardState,
1855
- recordCombinedEvent,
1856
- flushDashboards,
1857
- options,
1858
- feedbackStateByRequestId,
1859
- appendCoordination,
1860
- launchAgentSessionFn = launchAgentSession,
1861
- waitForWaveCompletionFn = waitForWaveCompletion,
1862
- }) {
1863
- return runClosureSweepPhaseImpl({
1864
- lanePaths,
1865
- wave,
1866
- closureRuns,
1867
- coordinationLogPath,
1868
- refreshDerivedState,
1869
- dashboardState,
1870
- recordCombinedEvent,
1871
- flushDashboards,
1872
- options,
1873
- feedbackStateByRequestId,
1874
- appendCoordination,
1875
- launchAgentSessionFn,
1876
- waitForWaveCompletionFn,
1877
- readWaveContEvalGateFn: readWaveContEvalGate,
1878
- readWaveSecurityGateFn: readWaveSecurityGate,
1879
- readWaveIntegrationBarrierFn: readWaveIntegrationBarrier,
1880
- readWaveDocumentationGateFn: readWaveDocumentationGate,
1881
- readWaveComponentMatrixGateFn: readWaveComponentMatrixGate,
1882
- readWaveContQaGateFn: readWaveContQaGate,
1883
- materializeAgentExecutionSummaryForRunFn: materializeAgentExecutionSummaryForRun,
1884
- monitorWaveHumanFeedbackFn: monitorWaveHumanFeedback,
1885
- });
1886
- }
1887
-
1888
- export function readWaveInfraGate(agentRuns) {
1889
- return readWaveInfraGateImpl(agentRuns);
1890
- }
1891
-
1892
- export function markLauncherFailed(
1893
- globalDashboard,
1894
- lanePaths,
1895
- selectedWaves,
1896
- appendCoordination,
1897
- error,
1898
- ) {
1899
- if (globalDashboard) {
1900
- globalDashboard.status = "failed";
1901
- recordGlobalDashboardEvent(globalDashboard, {
1902
- level: "error",
1903
- message: error instanceof Error ? error.message : String(error),
1904
- });
1905
- writeGlobalDashboard(lanePaths.globalDashboardPath, globalDashboard);
1906
- }
1907
- appendCoordination({
1908
- event: "launcher_finish",
1909
- waves: selectedWaves,
1910
- status: "failed",
1911
- details: error instanceof Error ? error.message : String(error),
1912
- actionRequested: `Lane ${lanePaths.lane} owners should inspect the failing wave logs and dashboards before retrying.`,
1913
- });
1914
- }
1915
-
1916
- export function acquireLauncherLock(lockPath, options) {
1917
- ensureDirectory(path.dirname(lockPath));
1918
- const payload = {
1919
- lane: options.lane,
1920
- pid: process.pid,
1921
- startedAt: toIsoTimestamp(),
1922
- argv: process.argv.slice(2),
1923
- cwd: REPO_ROOT,
1924
- mode: options.reconcileStatus ? "reconcile" : "launch",
1925
- };
1926
- try {
1927
- const fd = fs.openSync(lockPath, "wx");
1928
- fs.writeFileSync(fd, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
1929
- fs.closeSync(fd);
1930
- return payload;
1931
- } catch (error) {
1932
- if (error?.code !== "EEXIST") {
1933
- throw error;
1934
- }
1935
- const existing = readJsonOrNull(lockPath);
1936
- const existingPid = Number.parseInt(String(existing?.pid ?? ""), 10);
1937
- if (isProcessAlive(existingPid)) {
1938
- const lockError = new Error(
1939
- `Another launcher is active (pid ${existingPid}, started ${existing?.startedAt || "unknown"}). Lock: ${path.relative(REPO_ROOT, lockPath)}`,
1940
- { cause: error },
1941
- );
1942
- lockError.exitCode = 32;
1943
- throw lockError;
1944
- }
1945
- fs.rmSync(lockPath, { force: true });
1946
- const retryFd = fs.openSync(lockPath, "wx");
1947
- fs.writeFileSync(retryFd, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
1948
- fs.closeSync(retryFd);
1949
- return payload;
1950
- }
1951
- }
1952
-
1953
- export function releaseLauncherLock(lockPath) {
1954
- fs.rmSync(lockPath, { force: true });
1955
- }
1956
-
1957
- function isLaneSessionName(lanePaths, sessionName) {
1958
- return (
1959
- sessionName.startsWith(lanePaths.tmuxSessionPrefix) ||
1960
- sessionName.startsWith(lanePaths.tmuxDashboardSessionPrefix) ||
1961
- sessionName.startsWith(lanePaths.tmuxGlobalDashboardSessionPrefix)
1962
- );
1963
- }
1964
-
1965
- function listLaneTmuxSessionNames(lanePaths) {
1966
- return listTmuxSessionNames(lanePaths).filter((sessionName) =>
1967
- isLaneSessionName(lanePaths, sessionName),
1968
- );
1969
- }
1970
-
1971
- function residentOrchestratorRolePromptPath() {
1972
- return path.join(REPO_ROOT, "docs", "agents", "wave-orchestrator-role.md");
1973
- }
1974
-
1975
- function loadResidentOrchestratorRolePrompt() {
1976
- const filePath = residentOrchestratorRolePromptPath();
1977
- if (!fs.existsSync(filePath)) {
1978
- return "Monitor the wave, triage clarification timing, and intervene through coordination records only.";
1979
- }
1980
- return fs.readFileSync(filePath, "utf8");
1981
- }
1982
-
1983
- function defaultResidentExecutorState(options) {
1984
- if (options.executorMode === "claude") {
1985
- return {
1986
- id: "claude",
1987
- role: "orchestrator",
1988
- selectedBy: "resident-orchestrator",
1989
- budget: { minutes: options.timeoutMinutes },
1990
- claude: {
1991
- command: "claude",
1992
- },
1993
- };
1994
- }
1995
- if (options.executorMode === "opencode") {
1996
- return {
1997
- id: "opencode",
1998
- role: "orchestrator",
1999
- selectedBy: "resident-orchestrator",
2000
- budget: { minutes: options.timeoutMinutes },
2001
- opencode: {
2002
- command: "opencode",
2003
- },
2004
- };
2005
- }
2006
- return {
2007
- id: "codex",
2008
- role: "orchestrator",
2009
- selectedBy: "resident-orchestrator",
2010
- budget: { minutes: options.timeoutMinutes },
2011
- codex: {
2012
- command: "codex",
2013
- sandbox: options.codexSandboxMode,
2014
- },
2015
- };
2016
- }
2017
-
2018
- function buildResidentExecutorState(executorTemplate, options) {
2019
- const source = executorTemplate
2020
- ? JSON.parse(JSON.stringify(executorTemplate))
2021
- : defaultResidentExecutorState(options);
2022
- source.role = "orchestrator";
2023
- source.selectedBy = "resident-orchestrator";
2024
- source.budget = {
2025
- ...(source.budget || {}),
2026
- minutes: Math.max(
2027
- Number.parseInt(String(source?.budget?.minutes || 0), 10) || 0,
2028
- options.timeoutMinutes,
2029
- ),
2030
- };
2031
- if (source.id === "codex") {
2032
- source.codex = {
2033
- ...(source.codex || {}),
2034
- command: source?.codex?.command || "codex",
2035
- sandbox: source?.codex?.sandbox || options.codexSandboxMode,
2036
- };
2037
- } else if (source.id === "claude") {
2038
- source.claude = {
2039
- ...(source.claude || {}),
2040
- command: source?.claude?.command || "claude",
2041
- };
2042
- } else if (source.id === "opencode") {
2043
- source.opencode = {
2044
- ...(source.opencode || {}),
2045
- command: source?.opencode?.command || "opencode",
2046
- };
2047
- }
2048
- return source;
2049
- }
2050
-
2051
- function buildResidentOrchestratorRun({
2052
- lanePaths,
2053
- wave,
2054
- agentRuns,
2055
- derivedState,
2056
- dashboardPath,
2057
- runTag,
2058
- options,
2059
- }) {
2060
- const executorTemplate =
2061
- agentRuns.find((run) => run.agent.executorResolved?.id === options.executorMode)?.agent
2062
- ?.executorResolved ||
2063
- agentRuns.find((run) => run.agent.executorResolved)?.agent?.executorResolved ||
2064
- null;
2065
- const executorResolved = buildResidentExecutorState(executorTemplate, options);
2066
- if (executorResolved.id === "local") {
2067
- return {
2068
- run: null,
2069
- skipReason: "Resident orchestrator requires codex, claude, or opencode; local executor is not suitable.",
2070
- };
2071
- }
2072
- const agent = {
2073
- agentId: "ORCH",
2074
- title: "Resident Orchestrator",
2075
- slug: `${wave.wave}-resident-orchestrator`,
2076
- prompt: loadResidentOrchestratorRolePrompt(),
2077
- executorResolved,
2078
- };
2079
- const baseName = `wave-${wave.wave}-resident-orchestrator`;
2080
- const sessionName = `${lanePaths.tmuxSessionPrefix}${wave.wave}_resident_orchestrator_${runTag}`.replace(
2081
- /[^a-zA-Z0-9_-]/g,
2082
- "_",
2083
- );
2084
- return {
2085
- run: {
2086
- agent,
2087
- sessionName,
2088
- promptPath: path.join(lanePaths.promptsDir, `${baseName}.prompt.md`),
2089
- logPath: path.join(lanePaths.logsDir, `${baseName}.log`),
2090
- statusPath: path.join(lanePaths.statusDir, `${baseName}.status`),
2091
- promptOverride: buildResidentOrchestratorPrompt({
2092
- lane: lanePaths.lane,
2093
- wave: wave.wave,
2094
- waveFile: wave.file,
2095
- orchestratorId: options.orchestratorId,
2096
- coordinationLogPath: derivedState.coordinationLogPath,
2097
- messageBoardPath: derivedState.messageBoardPath,
2098
- sharedSummaryPath: derivedState.sharedSummaryPath,
2099
- dashboardPath,
2100
- triagePath: derivedState.clarificationTriage?.triagePath || null,
2101
- rolePrompt: agent.prompt,
2102
- }),
2103
- },
2104
- skipReason: "",
2105
- };
2106
- }
2107
-
2108
- function monitorResidentOrchestratorSession({
2109
- lanePaths,
2110
- run,
2111
- waveNumber,
2112
- recordCombinedEvent,
2113
- appendCoordination,
2114
- sessionState,
2115
- }) {
2116
- if (!run || sessionState?.closed === true) {
2117
- return false;
2118
- }
2119
- if (fs.existsSync(run.statusPath)) {
2120
- sessionState.closed = true;
2121
- const exitCode = readStatusCodeIfPresent(run.statusPath);
2122
- recordCombinedEvent({
2123
- level: exitCode === 0 ? "info" : "warn",
2124
- agentId: run.agent.agentId,
2125
- message:
2126
- exitCode === 0
2127
- ? "Resident orchestrator exited; launcher continues as the control plane."
2128
- : `Resident orchestrator exited with code ${exitCode}; launcher continues as the control plane.`,
2129
- });
2130
- appendCoordination({
2131
- event: "resident_orchestrator_exit",
2132
- waves: [waveNumber],
2133
- status: exitCode === 0 ? "resolved" : "warn",
2134
- details:
2135
- exitCode === 0
2136
- ? "Resident orchestrator session ended before wave completion."
2137
- : `Resident orchestrator session ended with code ${exitCode} before wave completion.`,
2138
- actionRequested: "None",
2139
- });
2140
- return true;
2141
- }
2142
- const activeSessions = new Set(listLaneTmuxSessionNames(lanePaths));
2143
- if (!activeSessions.has(run.sessionName)) {
2144
- sessionState.closed = true;
2145
- recordCombinedEvent({
2146
- level: "warn",
2147
- agentId: run.agent.agentId,
2148
- message:
2149
- "Resident orchestrator session disappeared before writing a status file; launcher continues as the control plane.",
2150
- });
2151
- appendCoordination({
2152
- event: "resident_orchestrator_missing",
2153
- waves: [waveNumber],
2154
- status: "warn",
2155
- details: `tmux session ${run.sessionName} disappeared before ${path.relative(REPO_ROOT, run.statusPath)} was written.`,
2156
- actionRequested: "None",
2157
- });
2158
- return true;
2159
- }
2160
- return false;
2161
- }
2162
-
2163
- function isWaveDashboardBackedByLiveSession(lanePaths, dashboardPath, activeSessionNames) {
2164
- const waveMatch = path.basename(dashboardPath).match(/^wave-(\d+)\.json$/);
2165
- if (!waveMatch) {
2166
- return false;
2167
- }
2168
- const waveNumber = Number.parseInt(waveMatch[1], 10);
2169
- if (!Number.isFinite(waveNumber)) {
2170
- return false;
2171
- }
2172
- const dashboardState = readJsonOrNull(dashboardPath);
2173
- const runTag = String(dashboardState?.runTag || "").trim();
2174
- const agentPrefix = `${lanePaths.tmuxSessionPrefix}${waveNumber}_`;
2175
- const dashboardPrefix = `${lanePaths.tmuxDashboardSessionPrefix}${waveNumber}_`;
2176
- for (const sessionName of activeSessionNames) {
2177
- if (!(sessionName.startsWith(agentPrefix) || sessionName.startsWith(dashboardPrefix))) {
2178
- continue;
2179
- }
2180
- if (!runTag || sessionName.endsWith(`_${runTag}`)) {
2181
- return true;
2182
- }
2183
- }
2184
- return false;
2185
- }
2186
-
2187
- function removeOrphanWaveDashboards(lanePaths, activeSessionNames) {
2188
- if (!fs.existsSync(lanePaths.dashboardsDir)) {
2189
- return [];
2190
- }
2191
- const removedDashboardPaths = [];
2192
- for (const fileName of fs.readdirSync(lanePaths.dashboardsDir)) {
2193
- if (!/^wave-\d+\.json$/.test(fileName)) {
2194
- continue;
2195
- }
2196
- const dashboardPath = path.join(lanePaths.dashboardsDir, fileName);
2197
- if (isWaveDashboardBackedByLiveSession(lanePaths, dashboardPath, activeSessionNames)) {
2198
- continue;
2199
- }
2200
- fs.rmSync(dashboardPath, { force: true });
2201
- removedDashboardPaths.push(path.relative(REPO_ROOT, dashboardPath));
2202
- }
2203
- return removedDashboardPaths;
2204
- }
2205
-
2206
- function pruneDryRunExecutorPreviewDirs(lanePaths, waves) {
2207
- if (!fs.existsSync(lanePaths.executorOverlaysDir)) {
2208
- return [];
2209
- }
2210
- const expectedSlugsByWave = new Map(
2211
- (waves || []).map((wave) => [wave.wave, new Set((wave.agents || []).map((agent) => agent.slug))]),
2212
- );
2213
- const removedPaths = [];
2214
- for (const entry of fs.readdirSync(lanePaths.executorOverlaysDir, { withFileTypes: true })) {
2215
- if (!entry.isDirectory() || !/^wave-\d+$/.test(entry.name)) {
2216
- continue;
2217
- }
2218
- const waveNumber = Number.parseInt(entry.name.slice("wave-".length), 10);
2219
- const waveDir = path.join(lanePaths.executorOverlaysDir, entry.name);
2220
- const expectedSlugs = expectedSlugsByWave.get(waveNumber);
2221
- if (!expectedSlugs) {
2222
- fs.rmSync(waveDir, { recursive: true, force: true });
2223
- removedPaths.push(path.relative(REPO_ROOT, waveDir));
2224
- continue;
2225
- }
2226
- for (const child of fs.readdirSync(waveDir, { withFileTypes: true })) {
2227
- if (!child.isDirectory() || expectedSlugs.has(child.name)) {
2228
- continue;
2229
- }
2230
- const childPath = path.join(waveDir, child.name);
2231
- fs.rmSync(childPath, { recursive: true, force: true });
2232
- removedPaths.push(path.relative(REPO_ROOT, childPath));
2233
- }
2234
- }
2235
- return removedPaths.toSorted();
2236
- }
2237
-
2238
- export function reconcileStaleLauncherArtifacts(lanePaths, options = {}) {
2239
- const outcome = {
2240
- removedLock: false,
2241
- removedSessions: [],
2242
- removedTerminalNames: [],
2243
- clearedDashboards: false,
2244
- removedDashboardPaths: [],
2245
- staleWaves: [],
2246
- activeLockPid: null,
2247
- };
2248
-
2249
- if (fs.existsSync(lanePaths.launcherLockPath)) {
2250
- const existing = readJsonOrNull(lanePaths.launcherLockPath);
2251
- const existingPid = Number.parseInt(String(existing?.pid ?? ""), 10);
2252
- if (isProcessAlive(existingPid)) {
2253
- outcome.activeLockPid = existingPid;
2254
- return outcome;
2255
- }
2256
- fs.rmSync(lanePaths.launcherLockPath, { force: true });
2257
- outcome.removedLock = true;
2258
- }
2259
-
2260
- outcome.removedSessions = cleanupLaneTmuxSessions(lanePaths);
2261
- const activeSessionNames = new Set(listLaneTmuxSessionNames(lanePaths));
2262
- if (terminalSurfaceUsesTerminalRegistry(options.terminalSurface || "vscode")) {
2263
- const terminalCleanup = pruneOrphanLaneTemporaryTerminalEntries(
2264
- lanePaths.terminalsPath,
2265
- lanePaths,
2266
- activeSessionNames,
2267
- );
2268
- outcome.removedTerminalNames = terminalCleanup.removedNames;
2269
- }
2270
-
2271
- const globalDashboard = readJsonOrNull(lanePaths.globalDashboardPath);
2272
- if (globalDashboard && typeof globalDashboard === "object" && Array.isArray(globalDashboard.waves)) {
2273
- const staleWaves = new Set();
2274
- for (const waveEntry of globalDashboard.waves) {
2275
- const waveNumber = Number.parseInt(String(waveEntry?.wave ?? ""), 10);
2276
- if (Number.isFinite(waveNumber)) {
2277
- staleWaves.add(waveNumber);
2278
- }
2279
- }
2280
- outcome.staleWaves = Array.from(staleWaves).toSorted((a, b) => a - b);
2281
- }
2282
-
2283
- if (fs.existsSync(lanePaths.globalDashboardPath)) {
2284
- fs.rmSync(lanePaths.globalDashboardPath, { force: true });
2285
- outcome.removedDashboardPaths.push(path.relative(REPO_ROOT, lanePaths.globalDashboardPath));
2286
- }
2287
- outcome.removedDashboardPaths.push(
2288
- ...removeOrphanWaveDashboards(lanePaths, activeSessionNames),
2289
- );
2290
- outcome.clearedDashboards = outcome.removedDashboardPaths.length > 0;
2291
- return outcome;
2292
- }
2293
-
2294
- function runTmux(lanePaths, args, description) {
2295
- const result = spawnSync("tmux", ["-L", lanePaths.tmuxSocketName, ...args], {
2296
- cwd: REPO_ROOT,
2297
- encoding: "utf8",
2298
- env: { ...process.env, TMUX: "" },
2299
- timeout: TMUX_COMMAND_TIMEOUT_MS,
2300
- });
2301
- if (result.error) {
2302
- if (result.error.code === "ETIMEDOUT") {
2303
- throw new Error(
2304
- `${description} failed: tmux command timed out after ${TMUX_COMMAND_TIMEOUT_MS}ms`,
2305
- );
2306
- }
2307
- throw new Error(`${description} failed: ${result.error.message}`);
2308
- }
2309
- if (result.status !== 0) {
2310
- throw new Error(
2311
- `${description} failed: ${(result.stderr || "").trim() || "tmux command failed"}`,
2312
- );
2313
- }
2314
- }
2315
-
2316
- function listTmuxSessionNames(lanePaths) {
2317
- const result = spawnSync(
2318
- "tmux",
2319
- ["-L", lanePaths.tmuxSocketName, "list-sessions", "-F", "#{session_name}"],
2320
- {
2321
- cwd: REPO_ROOT,
2322
- encoding: "utf8",
2323
- env: { ...process.env, TMUX: "" },
2324
- timeout: TMUX_COMMAND_TIMEOUT_MS,
2325
- },
2326
- );
2327
- if (result.error) {
2328
- if (result.error.code === "ENOENT") {
2329
- return [];
2330
- }
2331
- if (result.error.code === "ETIMEDOUT") {
2332
- throw new Error(`list tmux sessions failed: timed out after ${TMUX_COMMAND_TIMEOUT_MS}ms`);
2333
- }
2334
- throw new Error(`list tmux sessions failed: ${result.error.message}`);
2335
- }
2336
- if (result.status !== 0) {
2337
- const combined = `${String(result.stderr || "").toLowerCase()}\n${String(result.stdout || "").toLowerCase()}`;
2338
- if (
2339
- combined.includes("no server running") ||
2340
- combined.includes("failed to connect") ||
2341
- combined.includes("error connecting")
2342
- ) {
2343
- return [];
2344
- }
2345
- throw new Error(
2346
- `list tmux sessions failed: ${(result.stderr || "").trim() || "unknown error"}`,
2347
- );
2348
- }
2349
- return String(result.stdout || "")
2350
- .split(/\r?\n/)
2351
- .map((line) => line.trim())
2352
- .filter(Boolean);
2353
- }
2354
-
2355
- function cleanupLaneTmuxSessions(lanePaths, { excludeSessionNames = new Set() } = {}) {
2356
- const sessionNames = listTmuxSessionNames(lanePaths);
2357
- const killed = [];
2358
- for (const sessionName of sessionNames) {
2359
- if (excludeSessionNames.has(sessionName) || !isLaneSessionName(lanePaths, sessionName)) {
2360
- continue;
2361
- }
2362
- killTmuxSessionIfExists(lanePaths.tmuxSocketName, sessionName);
2363
- killed.push(sessionName);
2364
- }
2365
- return killed;
2366
- }
2367
-
2368
- export function collectUnexpectedSessionFailures(lanePaths, agentRuns, pendingAgentIds) {
2369
- return collectUnexpectedSessionFailuresImpl(lanePaths, agentRuns, pendingAgentIds, {
2370
- listLaneTmuxSessionNamesFn: listLaneTmuxSessionNames,
2371
- });
2372
- }
2373
-
2374
- function launchWaveDashboardSession(lanePaths, { sessionName, dashboardPath, messageBoardPath }) {
2375
- killTmuxSessionIfExists(lanePaths.tmuxSocketName, sessionName);
2376
- const messageBoardArg = messageBoardPath
2377
- ? ` --message-board ${shellQuote(messageBoardPath)}`
2378
- : "";
2379
- const command = [
2380
- `cd ${shellQuote(REPO_ROOT)}`,
2381
- `node ${shellQuote(path.join(PACKAGE_ROOT, "scripts", "wave-dashboard.mjs"))} --dashboard-file ${shellQuote(
2382
- dashboardPath,
2383
- )}${messageBoardArg} --lane ${shellQuote(lanePaths.lane)} --watch`,
2384
- "exec bash -l",
2385
- ].join("; ");
2386
- runTmux(
2387
- lanePaths,
2388
- ["new-session", "-d", "-s", sessionName, `bash -lc ${shellQuote(command)}`],
2389
- `launch dashboard session ${sessionName}`,
2390
- );
2391
- }
2392
-
2393
- async function launchAgentSession(lanePaths, params) {
2394
- return launchAgentSessionImpl(lanePaths, params, { runTmuxFn: runTmux });
2395
- }
2396
-
2397
- async function waitForWaveCompletion(lanePaths, agentRuns, timeoutMinutes, onProgress = null) {
2398
- return waitForWaveCompletionImpl(lanePaths, agentRuns, timeoutMinutes, onProgress, {
2399
- collectUnexpectedSessionFailuresFn: collectUnexpectedSessionFailures,
2400
- });
2401
- }
2402
-
2403
- function monitorWaveHumanFeedback({
2404
- lanePaths,
2405
- waveNumber,
2406
- agentRuns,
2407
- orchestratorId,
2408
- coordinationLogPath,
2409
- feedbackStateByRequestId,
2410
- recordCombinedEvent,
2411
- appendCoordination,
2412
- }) {
2413
- const triageLogPath = path.join(lanePaths.feedbackTriageDir, `wave-${waveNumber}.jsonl`);
2414
- const requests = readWaveHumanFeedbackRequests({
2415
- feedbackRequestsDir: lanePaths.feedbackRequestsDir,
2416
- lane: lanePaths.lane,
2417
- waveNumber,
2418
- agentIds: agentRuns.map((run) => run.agent.agentId),
2419
- orchestratorId,
2420
- });
2421
- let changed = false;
2422
- for (const request of requests) {
2423
- const signature = feedbackStateSignature(request);
2424
- if (feedbackStateByRequestId.get(request.id) === signature) {
2425
- continue;
2426
- }
2427
- feedbackStateByRequestId.set(request.id, signature);
2428
- changed = true;
2429
- const question = request.question || "n/a";
2430
- const context = request.context ? `; context=${request.context}` : "";
2431
- const responseOperator = request.responseOperator || "human-operator";
2432
- const responseText = request.responseText || "(empty response)";
2433
- if (request.status === "pending") {
2434
- recordCombinedEvent({
2435
- level: "warn",
2436
- agentId: request.agentId,
2437
- message: `Human feedback requested (${request.id}): ${question}`,
2438
- });
2439
- console.warn(
2440
- `[human-feedback] wave=${waveNumber} agent=${request.agentId} request=${request.id} pending: ${question}`,
2441
- );
2442
- console.warn(
2443
- `[human-feedback] respond with: pnpm exec wave control task act answer --lane ${lanePaths.lane} --wave ${waveNumber} --id ${request.id} --response "<answer>" --operator "<name>"`,
2444
- );
2445
- appendCoordination({
2446
- event: "human_feedback_requested",
2447
- waves: [waveNumber],
2448
- status: "waiting-human",
2449
- details: `request_id=${request.id}; agent=${request.agentId}; question=${question}${context}`,
2450
- actionRequested: `Launcher operator should ask or answer in the parent session, then run: pnpm exec wave control task act answer --lane ${lanePaths.lane} --wave ${waveNumber} --id ${request.id} --response "<answer>" --operator "<name>"`,
2451
- });
2452
- if (coordinationLogPath) {
2453
- appendCoordinationRecord(coordinationLogPath, {
2454
- id: request.id,
2455
- lane: lanePaths.lane,
2456
- wave: waveNumber,
2457
- agentId: request.agentId || "human",
2458
- kind: "human-feedback",
2459
- targets: request.agentId ? [`agent:${request.agentId}`] : [],
2460
- priority: "high",
2461
- summary: question,
2462
- detail: request.context || "",
2463
- status: "open",
2464
- source: "feedback",
2465
- });
2466
- }
2467
- } else if (request.status === "answered") {
2468
- recordCombinedEvent({
2469
- level: "info",
2470
- agentId: request.agentId,
2471
- message: `Human feedback answered (${request.id}) by ${responseOperator}: ${responseText}`,
2472
- });
2473
- appendCoordination({
2474
- event: "human_feedback_answered",
2475
- waves: [waveNumber],
2476
- status: "resolved",
2477
- details: `request_id=${request.id}; agent=${request.agentId}; operator=${responseOperator}; response=${responseText}`,
2478
- });
2479
- if (coordinationLogPath) {
2480
- const escalationId = `escalation-${request.id}`;
2481
- const existingEscalation =
2482
- (fs.existsSync(triageLogPath)
2483
- ? readMaterializedCoordinationState(triageLogPath).byId.get(escalationId)
2484
- : null) ||
2485
- readMaterializedCoordinationState(coordinationLogPath).byId.get(escalationId) ||
2486
- null;
2487
- if (fs.existsSync(triageLogPath)) {
2488
- appendCoordinationRecord(triageLogPath, {
2489
- id: escalationId,
2490
- lane: lanePaths.lane,
2491
- wave: waveNumber,
2492
- agentId: request.agentId || "human",
2493
- kind: "human-escalation",
2494
- targets:
2495
- existingEscalation?.targets ||
2496
- (request.agentId ? [`agent:${request.agentId}`] : []),
2497
- dependsOn: existingEscalation?.dependsOn || [],
2498
- closureCondition: existingEscalation?.closureCondition || "",
2499
- priority: "high",
2500
- summary: question,
2501
- detail: responseText,
2502
- artifactRefs: [request.id],
2503
- status: "resolved",
2504
- source: "feedback",
2505
- });
2506
- }
2507
- appendCoordinationRecord(coordinationLogPath, {
2508
- id: escalationId,
2509
- lane: lanePaths.lane,
2510
- wave: waveNumber,
2511
- agentId: request.agentId || "human",
2512
- kind: "human-escalation",
2513
- targets:
2514
- existingEscalation?.targets ||
2515
- (request.agentId ? [`agent:${request.agentId}`] : []),
2516
- dependsOn: existingEscalation?.dependsOn || [],
2517
- closureCondition: existingEscalation?.closureCondition || "",
2518
- priority: "high",
2519
- summary: question,
2520
- detail: responseText,
2521
- artifactRefs: [request.id],
2522
- status: "resolved",
2523
- source: "feedback",
2524
- });
2525
- appendCoordinationRecord(coordinationLogPath, {
2526
- id: request.id,
2527
- lane: lanePaths.lane,
2528
- wave: waveNumber,
2529
- agentId: request.agentId || "human",
2530
- kind: "human-feedback",
2531
- targets: request.agentId ? [`agent:${request.agentId}`] : [],
2532
- priority: "high",
2533
- summary: question,
2534
- detail: responseText,
2535
- status: "resolved",
2536
- source: "feedback",
2537
- });
2538
- }
2539
- }
2540
- }
2541
- return changed;
2542
- }
2543
-
2544
- function proofCentricReuseBlocked(derivedState) {
2545
- if (!derivedState) {
2546
- return false;
2547
- }
2548
- return (
2549
- readClarificationBarrier(derivedState).ok === false ||
2550
- readWaveAssignmentBarrier(derivedState).ok === false ||
2551
- readWaveDependencyBarrier(derivedState).ok === false
2552
- );
2553
- }
2554
-
2555
- function sameAgentIdSet(left = [], right = []) {
2556
- const leftIds = Array.from(new Set((left || []).filter(Boolean))).toSorted();
2557
- const rightIds = Array.from(new Set((right || []).filter(Boolean))).toSorted();
2558
- return leftIds.length === rightIds.length && leftIds.every((agentId, index) => agentId === rightIds[index]);
2559
- }
2560
-
2561
- export function persistedRelaunchPlanMatchesCurrentState(
2562
- agentRuns,
2563
- persistedPlan,
2564
- lanePaths,
2565
- waveDefinition,
2566
- ) {
2567
- if (!persistedPlan || !Array.isArray(persistedPlan.selectedAgentIds)) {
2568
- return false;
2569
- }
2570
- const componentGate = readWaveComponentGate(waveDefinition, agentRuns, {
2571
- laneProfile: lanePaths?.laneProfile,
2572
- });
2573
- if (componentGate?.statusCode !== "shared-component-sibling-pending") {
2574
- return true;
2575
- }
2576
- return sameAgentIdSet(
2577
- persistedPlan.selectedAgentIds,
2578
- componentGate.waitingOnAgentIds || [],
2579
- );
2580
- }
2581
-
2582
- function applyPersistedRelaunchPlan(agentRuns, persistedPlan, lanePaths, waveDefinition) {
2583
- if (!persistedPlan || !Array.isArray(persistedPlan.selectedAgentIds)) {
2584
- return [];
2585
- }
2586
- const runsByAgentId = new Map(agentRuns.map((run) => [run.agent.agentId, run]));
2587
- for (const [agentId, executorState] of Object.entries(persistedPlan.executorStates || {})) {
2588
- const run = runsByAgentId.get(agentId);
2589
- if (!run || !executorState || typeof executorState !== "object") {
2590
- continue;
2591
- }
2592
- run.agent.executorResolved = executorState;
2593
- refreshResolvedSkillsForRun(run, waveDefinition, lanePaths);
2594
- }
2595
- return persistedPlan.selectedAgentIds
2596
- .map((agentId) => runsByAgentId.get(agentId))
2597
- .filter(Boolean);
2598
- }
2599
-
2600
- export function resolveSharedComponentContinuationRuns(
2601
- currentRuns,
2602
- agentRuns,
2603
- failures,
2604
- derivedState,
2605
- lanePaths,
2606
- waveDefinition = null,
2607
- ) {
2608
- if (!Array.isArray(currentRuns) || currentRuns.length === 0 || !Array.isArray(failures) || failures.length === 0) {
2609
- return [];
2610
- }
2611
- if (!failures.every((failure) => failure.statusCode === "shared-component-sibling-pending")) {
2612
- return [];
2613
- }
2614
- const currentRunIds = new Set(currentRuns.map((run) => run.agent.agentId));
2615
- const waitingAgentIds = new Set(
2616
- failures.flatMap((failure) => failure.waitingOnAgentIds || []).filter(Boolean),
2617
- );
2618
- if (Array.from(currentRunIds).some((agentId) => waitingAgentIds.has(agentId))) {
2619
- return [];
2620
- }
2621
- const relaunchResolution = resolveRelaunchRuns(
2622
- agentRuns,
2623
- failures,
2624
- derivedState,
2625
- lanePaths,
2626
- waveDefinition,
2627
- );
2628
- if (relaunchResolution.barrier || relaunchResolution.runs.length === 0) {
2629
- return [];
2630
- }
2631
- return relaunchResolution.runs.some((run) => !currentRunIds.has(run.agent.agentId))
2632
- ? relaunchResolution.runs
2633
- : [];
2634
- }
2635
-
2636
- function relaunchReasonBuckets(runs, failures, derivedState) {
2637
- const selectedAgentIds = new Set((runs || []).map((run) => run.agent.agentId));
2638
- return {
2639
- clarification: openClarificationLinkedRequests(derivedState?.coordinationState)
2640
- .flatMap((record) => record.targets || [])
2641
- .some((target) => {
2642
- const agentId = String(target || "").startsWith("agent:")
2643
- ? String(target).slice("agent:".length)
2644
- : String(target || "");
2645
- return selectedAgentIds.has(agentId);
2646
- }),
2647
- helperAssignment: (derivedState?.capabilityAssignments || []).some(
2648
- (assignment) => assignment.blocking && selectedAgentIds.has(assignment.assignedAgentId),
2649
- ),
2650
- dependency: ((derivedState?.dependencySnapshot?.openInbound || []).some((record) =>
2651
- selectedAgentIds.has(record.assignedAgentId),
2652
- )),
2653
- blocker: (derivedState?.coordinationState?.blockers || []).some(
2654
- (record) =>
2655
- isOpenCoordinationStatus(record.status) &&
2656
- (selectedAgentIds.has(record.agentId) ||
2657
- (record.targets || []).some((target) => {
2658
- const agentId = String(target || "").startsWith("agent:")
2659
- ? String(target).slice("agent:".length)
2660
- : String(target || "");
2661
- return selectedAgentIds.has(agentId);
2662
- })),
2663
- ),
2664
- closureGate: (failures || []).some(
2665
- (failure) => failure.agentId && selectedAgentIds.has(failure.agentId),
2666
- ),
2667
- sharedComponentSiblingWait: (failures || []).some(
2668
- (failure) =>
2669
- failure.statusCode === "shared-component-sibling-pending" &&
2670
- (failure.waitingOnAgentIds || []).some((agentId) => selectedAgentIds.has(agentId)),
2671
- ),
2672
- };
2673
- }
2674
-
2675
- function applySharedComponentWaitStateToDashboard(componentGate, dashboardState) {
2676
- const waitingSummary = (componentGate?.waitingOnAgentIds || []).join("/");
2677
- if (!waitingSummary) {
2678
- return;
2679
- }
2680
- for (const agentId of componentGate?.satisfiedAgentIds || []) {
2681
- setWaveDashboardAgent(dashboardState, agentId, {
2682
- state: "completed",
2683
- detail: `Desired-state slice landed; waiting on ${waitingSummary} for shared component closure`,
2684
- });
2685
- }
2686
- }
2687
-
2688
- function reconcileFailuresAgainstSharedComponentState(wave, agentRuns, failures) {
2689
- if (!Array.isArray(failures) || failures.length === 0) {
2690
- return failures;
2691
- }
2692
- const summariesByAgentId = Object.fromEntries(
2693
- (agentRuns || []).map((runInfo) => [runInfo.agent.agentId, readRunExecutionSummary(runInfo, wave)]),
2694
- );
2695
- const failureAgentIds = new Set(failures.map((failure) => failure.agentId).filter(Boolean));
2696
- const consumedSatisfiedAgentIds = new Set();
2697
- const synthesizedFailures = [];
2698
- for (const promotion of wave?.componentPromotions || []) {
2699
- const componentState = analyzePromotedComponentOwners(
2700
- promotion.componentId,
2701
- agentRuns,
2702
- summariesByAgentId,
2703
- );
2704
- if (
2705
- componentState.satisfiedAgentIds.length === 0 ||
2706
- componentState.waitingOnAgentIds.length === 0 ||
2707
- !componentState.satisfiedAgentIds.some((agentId) => failureAgentIds.has(agentId))
2708
- ) {
2709
- continue;
2710
- }
2711
- for (const agentId of componentState.satisfiedAgentIds) {
2712
- if (failureAgentIds.has(agentId)) {
2713
- consumedSatisfiedAgentIds.add(agentId);
2714
- }
2715
- }
2716
- synthesizedFailures.push(buildSharedComponentSiblingPendingFailure(componentState));
2717
- }
2718
- return [
2719
- ...synthesizedFailures.filter(Boolean),
2720
- ...failures.filter((failure) => !consumedSatisfiedAgentIds.has(failure.agentId)),
2721
- ];
2722
- }
2723
-
2724
- export function hasReusableSuccessStatus(agent, statusPath, options = {}) {
2725
- const statusRecord = readStatusRecordIfPresent(statusPath);
2726
- const basicReuseOk = Boolean(
2727
- statusRecord && statusRecord.code === 0 && statusRecord.promptHash === hashAgentPromptFingerprint(agent),
2728
- );
2729
- if (!basicReuseOk) {
2730
- return false;
2731
- }
2732
- const proofCentric =
2733
- agentRequiresProofCentricValidation(agent) || waveRequiresProofCentricValidation(options.wave);
2734
- if (!proofCentric) {
2735
- return true;
2736
- }
2737
- const summary = readAgentExecutionSummary(statusPath);
2738
- if (!summary) {
2739
- return false;
2740
- }
2741
- const effectiveSummary = options.proofRegistry
2742
- ? augmentSummaryWithProofRegistry(agent, summary, options.proofRegistry)
2743
- : summary;
2744
- if (!validateImplementationSummary(agent, effectiveSummary).ok) {
2745
- return false;
2746
- }
2747
- if (proofCentricReuseBlocked(options.derivedState)) {
2748
- return false;
2749
- }
2750
- return true;
2751
- }
2752
-
2753
- function isClosureAgentId(agent, lanePaths) {
2754
- return [
2755
- lanePaths.contEvalAgentId || "E0",
2756
- lanePaths.integrationAgentId || "A8",
2757
- lanePaths.documentationAgentId || "A9",
2758
- lanePaths.contQaAgentId || "A0",
2759
- ].includes(agent?.agentId) || isSecurityReviewAgent(agent);
2760
- }
2761
-
2762
- export function selectReusablePreCompletedAgentIds(
2763
- agentRuns,
2764
- lanePaths,
2765
- { retryOverride = null, wave = null, derivedState = null, proofRegistry = null } = {},
2766
- ) {
2767
- const retryOverrideClearedAgentIds = new Set(retryOverride?.clearReusableAgentIds || []);
2768
- return new Set(
2769
- (agentRuns || [])
2770
- .filter(
2771
- (run) =>
2772
- !retryOverrideClearedAgentIds.has(run.agent.agentId) &&
2773
- !isClosureAgentId(run.agent, lanePaths) &&
2774
- hasReusableSuccessStatus(run.agent, run.statusPath, {
2775
- wave,
2776
- derivedState,
2777
- proofRegistry,
2778
- }),
2779
- )
2780
- .map((run) => run.agent.agentId),
2781
- );
2782
- }
2783
-
2784
- export function selectInitialWaveRuns(agentRuns, lanePaths) {
2785
- const implementationRuns = (agentRuns || []).filter(
2786
- (run) => !isClosureAgentId(run?.agent, lanePaths),
2787
- );
2788
- return implementationRuns.length > 0 ? implementationRuns : agentRuns;
2789
- }
2790
-
2791
- function isLauncherSeedRequest(record) {
2792
- return (
2793
- record?.source === "launcher" &&
2794
- /^wave-\d+-agent-[^-]+-request$/.test(String(record.id || "")) &&
2795
- !String(record.closureCondition || "").trim() &&
2796
- (!Array.isArray(record.dependsOn) || record.dependsOn.length === 0)
2797
- );
2798
- }
2799
-
2800
- function runtimeMixValidationForRuns(agentRuns, lanePaths) {
2801
- return validateWaveRuntimeMixAssignments(
2802
- {
2803
- wave: 0,
2804
- agents: agentRuns.map((run) => run.agent),
2805
- },
2806
- { laneProfile: lanePaths.laneProfile },
2807
- );
2808
- }
2809
-
2810
- function nextExecutorModel(executorState, executorId) {
2811
- if (executorId === "claude") {
2812
- return executorState?.claude?.model || null;
2813
- }
2814
- if (executorId === "opencode") {
2815
- return executorState?.opencode?.model || null;
2816
- }
2817
- return null;
2818
- }
2819
-
2820
- function executorFallbackChain(executorState) {
2821
- if (
2822
- executorState?.retryPolicy === "sticky" ||
2823
- executorState?.allowFallbackOnRetry === false
2824
- ) {
2825
- return [];
2826
- }
2827
- return Array.isArray(executorState?.fallbacks)
2828
- ? executorState.fallbacks.filter(Boolean)
2829
- : [];
2830
- }
2831
-
2832
- function buildFallbackExecutorState(executorState, executorId, attempt, reason) {
2833
- const history = Array.isArray(executorState?.executorHistory)
2834
- ? executorState.executorHistory
2835
- : [];
2836
- return {
2837
- ...executorState,
2838
- id: executorId,
2839
- model: nextExecutorModel(executorState, executorId),
2840
- selectedBy: "retry-fallback",
2841
- fallbackUsed: true,
2842
- fallbackReason: reason,
2843
- initialExecutorId: executorState?.initialExecutorId || executorState?.id || executorId,
2844
- executorHistory: [
2845
- ...history,
2846
- {
2847
- attempt,
2848
- executorId,
2849
- reason,
2850
- },
2851
- ],
2852
- };
2853
- }
2854
-
2855
- function applyRetryFallbacks(agentRuns, failures, lanePaths, attemptNumber, waveDefinition = null) {
2856
- const failedAgentIds = new Set(
2857
- failures
2858
- .filter((failure) => failure.statusCode !== "shared-component-sibling-pending")
2859
- .map((failure) => failure.agentId),
2860
- );
2861
- let changed = false;
2862
- const outcomes = new Map();
2863
- for (const run of agentRuns) {
2864
- if (!failedAgentIds.has(run.agent.agentId)) {
2865
- continue;
2866
- }
2867
- const executorState = run.agent.executorResolved;
2868
- if (!executorState) {
2869
- outcomes.set(run.agent.agentId, {
2870
- applied: false,
2871
- blocking: false,
2872
- statusCode: "no-executor-state",
2873
- detail: `Agent ${run.agent.agentId} has no resolved executor state.`,
2874
- });
2875
- continue;
2876
- }
2877
- const fallbackChain = executorFallbackChain(executorState);
2878
- if (fallbackChain.length === 0) {
2879
- outcomes.set(run.agent.agentId, {
2880
- applied: false,
2881
- blocking: false,
2882
- statusCode: "no-fallback-configured",
2883
- detail: `Agent ${run.agent.agentId} has no configured fallback executors.`,
2884
- });
2885
- continue;
2886
- }
2887
- const attemptedExecutors = new Set(
2888
- Array.isArray(executorState.executorHistory)
2889
- ? executorState.executorHistory.map((entry) => entry.executorId)
2890
- : [executorState.id],
2891
- );
2892
- const fallbackReason = failures.find((failure) => failure.agentId === run.agent.agentId);
2893
- const blockedCandidates = [];
2894
- for (const candidate of fallbackChain) {
2895
- if (!candidate || candidate === executorState.id || attemptedExecutors.has(candidate)) {
2896
- if (candidate) {
2897
- blockedCandidates.push(`${candidate}: already tried`);
2898
- }
2899
- continue;
2900
- }
2901
- const command = commandForExecutor(executorState, candidate);
2902
- if (!isExecutorCommandAvailable(command)) {
2903
- blockedCandidates.push(`${candidate}: command unavailable`);
2904
- continue;
2905
- }
2906
- const nextState = buildFallbackExecutorState(
2907
- executorState,
2908
- candidate,
2909
- attemptNumber,
2910
- `retry:${fallbackReason?.statusCode || "failed-attempt"}`,
2911
- );
2912
- const validation = runtimeMixValidationForRuns(
2913
- agentRuns.map((entry) =>
2914
- entry.agent.agentId === run.agent.agentId
2915
- ? { ...entry, agent: { ...entry.agent, executorResolved: nextState } }
2916
- : entry,
2917
- ),
2918
- lanePaths,
2919
- );
2920
- if (!validation.ok) {
2921
- blockedCandidates.push(`${candidate}: ${validation.detail}`);
2922
- continue;
2923
- }
2924
- run.agent.executorResolved = nextState;
2925
- refreshResolvedSkillsForRun(run, waveDefinition, lanePaths);
2926
- changed = true;
2927
- outcomes.set(run.agent.agentId, {
2928
- applied: true,
2929
- blocking: false,
2930
- statusCode: "fallback-applied",
2931
- detail: `Agent ${run.agent.agentId} will retry on ${candidate}.`,
2932
- executorId: candidate,
2933
- });
2934
- break;
2935
- }
2936
- if (!outcomes.has(run.agent.agentId)) {
2937
- outcomes.set(run.agent.agentId, {
2938
- applied: false,
2939
- blocking: true,
2940
- statusCode: "retry-fallback-blocked",
2941
- detail: `Agent ${run.agent.agentId} cannot retry safely on a configured fallback (${blockedCandidates.join("; ") || "no safe fallback remained"}).`,
2942
- });
2943
- }
2944
- }
2945
- return {
2946
- changed,
2947
- outcomes,
2948
- };
2949
- }
2950
-
2951
- function retryBarrierFromOutcomes(outcomes, failures) {
2952
- const blockingFailures = [];
2953
- for (const failure of failures) {
2954
- const outcome = outcomes.get(failure.agentId);
2955
- if (!outcome?.blocking) {
2956
- continue;
2957
- }
2958
- blockingFailures.push({
2959
- agentId: failure.agentId,
2960
- statusCode: outcome.statusCode,
2961
- logPath: failure.logPath,
2962
- detail: outcome.detail,
2963
- });
2964
- }
2965
- if (blockingFailures.length === 0) {
2966
- return null;
2967
- }
2968
- return {
2969
- statusCode: "retry-fallback-blocked",
2970
- detail: blockingFailures.map((failure) => failure.detail).join(" "),
2971
- failures: blockingFailures,
2972
- };
2973
- }
2974
-
2975
- export function readClarificationBarrier(derivedState) {
2976
- const openClarifications = (derivedState?.coordinationState?.clarifications || []).filter(
2977
- (record) => isOpenCoordinationStatus(record.status),
2978
- );
2979
- if (openClarifications.length > 0) {
2980
- return {
2981
- ok: false,
2982
- statusCode: "clarification-open",
2983
- detail: `Open clarifications remain (${openClarifications.map((record) => record.id).join(", ")}).`,
2984
- };
2985
- }
2986
- const openClarificationRequests = openClarificationLinkedRequests(
2987
- derivedState?.coordinationState,
2988
- );
2989
- if (openClarificationRequests.length > 0) {
2990
- return {
2991
- ok: false,
2992
- statusCode: "clarification-follow-up-open",
2993
- detail: `Clarification follow-up requests remain open (${openClarificationRequests.map((record) => record.id).join(", ")}).`,
2994
- };
2995
- }
2996
- const pendingHuman = [
2997
- ...((derivedState?.coordinationState?.humanEscalations || []).filter((record) =>
2998
- isOpenCoordinationStatus(record.status),
2999
- )),
3000
- ...((derivedState?.coordinationState?.humanFeedback || []).filter((record) =>
3001
- isOpenCoordinationStatus(record.status),
3002
- )),
3003
- ];
3004
- if (pendingHuman.length > 0) {
3005
- return {
3006
- ok: false,
3007
- statusCode: "human-feedback-open",
3008
- detail: `Pending human input remains (${pendingHuman.map((record) => record.id).join(", ")}).`,
3009
- };
3010
- }
3011
- return {
3012
- ok: true,
3013
- statusCode: "pass",
3014
- detail: "",
3015
- };
3016
- }
3017
-
3018
- export function readWaveAssignmentBarrier(derivedState) {
3019
- const blockingAssignments = (derivedState?.capabilityAssignments || []).filter(
3020
- (assignment) => assignment.blocking,
3021
- );
3022
- if (blockingAssignments.length === 0) {
3023
- return {
3024
- ok: true,
3025
- statusCode: "pass",
3026
- detail: "",
3027
- };
3028
- }
3029
- const unresolvedAssignments = blockingAssignments.filter((assignment) => !assignment.assignedAgentId);
3030
- if (unresolvedAssignments.length > 0) {
3031
- return {
3032
- ok: false,
3033
- statusCode: "helper-assignment-unresolved",
3034
- detail: `Helper assignments remain unresolved (${unresolvedAssignments.map((assignment) => assignment.requestId).join(", ")}).`,
3035
- };
3036
- }
3037
- return {
3038
- ok: false,
3039
- statusCode: "helper-assignment-open",
3040
- detail: `Helper assignments remain open (${blockingAssignments.map((assignment) => assignment.requestId).join(", ")}).`,
3041
- };
3042
- }
3043
-
3044
- export function readWaveDependencyBarrier(derivedState) {
3045
- const requiredInbound = derivedState?.dependencySnapshot?.requiredInbound || [];
3046
- const requiredOutbound = derivedState?.dependencySnapshot?.requiredOutbound || [];
3047
- const unresolvedInboundAssignments =
3048
- derivedState?.dependencySnapshot?.unresolvedInboundAssignments || [];
3049
- if (unresolvedInboundAssignments.length > 0) {
3050
- return {
3051
- ok: false,
3052
- statusCode: "dependency-assignment-unresolved",
3053
- detail: `Required inbound dependencies are unassigned (${unresolvedInboundAssignments.map((record) => record.id).join(", ")}).`,
3054
- };
3055
- }
3056
- if (requiredInbound.length > 0 || requiredOutbound.length > 0) {
3057
- return {
3058
- ok: false,
3059
- statusCode: "dependency-open",
3060
- detail: `Open required dependencies remain (${[...requiredInbound, ...requiredOutbound].map((record) => record.id).join(", ")}).`,
3061
- };
3062
- }
3063
- return {
3064
- ok: true,
3065
- statusCode: "pass",
3066
- detail: "",
3067
- };
3068
- }
3069
-
3070
- export function buildGateSnapshot({
3071
- wave,
3072
- agentRuns,
3073
- derivedState,
3074
- lanePaths,
3075
- componentMatrixPayload,
3076
- componentMatrixJsonPath,
3077
- validationMode = "compat",
3078
- }) {
3079
- const implementationGate = readWaveImplementationGate(wave, agentRuns);
3080
- const componentGate = readWaveComponentGate(wave, agentRuns, {
3081
- laneProfile: lanePaths?.laneProfile,
3082
- });
3083
- const integrationGate = readWaveIntegrationGate(wave, agentRuns, {
3084
- integrationAgentId: lanePaths?.integrationAgentId,
3085
- requireIntegrationStewardFromWave: lanePaths?.requireIntegrationStewardFromWave,
3086
- });
3087
- const integrationBarrier = readWaveIntegrationBarrier(wave, agentRuns, derivedState, {
3088
- integrationAgentId: lanePaths?.integrationAgentId,
3089
- requireIntegrationStewardFromWave: lanePaths?.requireIntegrationStewardFromWave,
3090
- });
3091
- const documentationGate = readWaveDocumentationGate(wave, agentRuns);
3092
- const componentMatrixGate = readWaveComponentMatrixGate(wave, agentRuns, {
3093
- laneProfile: lanePaths?.laneProfile,
3094
- documentationAgentId: lanePaths?.documentationAgentId,
3095
- componentMatrixPayload,
3096
- componentMatrixJsonPath,
3097
- });
3098
- const contEvalGate = readWaveContEvalGate(wave, agentRuns, {
3099
- contEvalAgentId: lanePaths?.contEvalAgentId,
3100
- mode: validationMode,
3101
- evalTargets: wave.evalTargets,
3102
- benchmarkCatalogPath: lanePaths?.laneProfile?.paths?.benchmarkCatalogPath,
3103
- });
3104
- const securityGate = readWaveSecurityGate(wave, agentRuns);
3105
- const contQaGate = readWaveContQaGate(wave, agentRuns, {
3106
- contQaAgentId: lanePaths?.contQaAgentId,
3107
- mode: validationMode,
3108
- });
3109
- const infraGate = readWaveInfraGate(agentRuns);
3110
- const clarificationBarrier = readClarificationBarrier(derivedState);
3111
- const helperAssignmentBarrier = readWaveAssignmentBarrier(derivedState);
3112
- const dependencyBarrier = readWaveDependencyBarrier(derivedState);
3113
- const orderedGates = [
3114
- ["implementationGate", implementationGate],
3115
- ["componentGate", componentGate],
3116
- ["helperAssignmentBarrier", helperAssignmentBarrier],
3117
- ["dependencyBarrier", dependencyBarrier],
3118
- ["contEvalGate", contEvalGate],
3119
- ["securityGate", securityGate],
3120
- ["integrationBarrier", integrationBarrier],
3121
- ["documentationGate", documentationGate],
3122
- ["componentMatrixGate", componentMatrixGate],
3123
- ["contQaGate", contQaGate],
3124
- ["infraGate", infraGate],
3125
- ["clarificationBarrier", clarificationBarrier],
3126
- ];
3127
- const firstFailure = orderedGates.find(([, gate]) => gate?.ok === false);
3128
- return {
3129
- implementationGate,
3130
- componentGate,
3131
- integrationGate,
3132
- integrationBarrier,
3133
- documentationGate,
3134
- componentMatrixGate,
3135
- contEvalGate,
3136
- securityGate,
3137
- contQaGate,
3138
- infraGate,
3139
- clarificationBarrier,
3140
- helperAssignmentBarrier,
3141
- dependencyBarrier,
3142
- overall: firstFailure
3143
- ? {
3144
- ok: false,
3145
- gate: firstFailure[0],
3146
- statusCode: firstFailure[1].statusCode,
3147
- detail: firstFailure[1].detail,
3148
- agentId: firstFailure[1].agentId || null,
3149
- }
3150
- : {
3151
- ok: true,
3152
- gate: "pass",
3153
- statusCode: "pass",
3154
- detail: "All replayed wave gates passed.",
3155
- agentId: null,
3156
- },
3157
- };
3158
- }
3159
-
3160
- export function resolveRelaunchRuns(agentRuns, failures, derivedState, lanePaths, waveDefinition = null) {
3161
- const runsByAgentId = new Map(agentRuns.map((run) => [run.agent.agentId, run]));
3162
- const pendingFeedback = (derivedState?.coordinationState?.humanFeedback || []).filter((record) =>
3163
- isOpenCoordinationStatus(record.status),
3164
- );
3165
- const pendingHumanEscalations = (derivedState?.coordinationState?.humanEscalations || []).filter(
3166
- (record) => isOpenCoordinationStatus(record.status),
3167
- );
3168
- if (pendingFeedback.length > 0 || pendingHumanEscalations.length > 0) {
3169
- return { runs: [], barrier: null };
3170
- }
3171
- const nextAttemptNumber = Number(derivedState?.ledger?.attempt || 0) + 1;
3172
- const fallbackResolution = applyRetryFallbacks(
3173
- agentRuns,
3174
- failures,
3175
- lanePaths,
3176
- nextAttemptNumber,
3177
- waveDefinition,
3178
- );
3179
- const retryBarrier = retryBarrierFromOutcomes(fallbackResolution.outcomes, failures);
3180
- if (retryBarrier) {
3181
- return { runs: [], barrier: retryBarrier };
3182
- }
3183
- const clarificationTargets = new Set();
3184
- for (const record of openClarificationLinkedRequests(derivedState?.coordinationState)) {
3185
- for (const target of record.targets || []) {
3186
- if (String(target).startsWith("agent:")) {
3187
- clarificationTargets.add(String(target).slice("agent:".length));
3188
- } else if (runsByAgentId.has(target)) {
3189
- clarificationTargets.add(target);
3190
- }
3191
- }
3192
- }
3193
- if (clarificationTargets.size > 0) {
3194
- return {
3195
- runs: Array.from(clarificationTargets)
3196
- .map((agentId) => runsByAgentId.get(agentId))
3197
- .filter(Boolean),
3198
- barrier: null,
3199
- };
3200
- }
3201
- const blockingAssignments = (derivedState?.capabilityAssignments || []).filter(
3202
- (assignment) => assignment.blocking,
3203
- );
3204
- const effectiveAssignments =
3205
- blockingAssignments.length > 0
3206
- ? blockingAssignments
3207
- : buildRequestAssignments({
3208
- coordinationState: derivedState?.coordinationState,
3209
- agents: agentRuns.map((run) => run.agent),
3210
- ledger: derivedState?.ledger,
3211
- capabilityRouting: lanePaths?.capabilityRouting,
3212
- }).filter((assignment) => assignment.blocking);
3213
- const assignmentSource = effectiveAssignments.length > 0 ? effectiveAssignments : blockingAssignments;
3214
- const unresolvedFromSource = assignmentSource.filter((assignment) => !assignment.assignedAgentId);
3215
- if (unresolvedFromSource.length > 0) {
3216
- return {
3217
- runs: [],
3218
- barrier: {
3219
- statusCode: "helper-assignment-unresolved",
3220
- detail: `No matching assignee exists for helper requests (${unresolvedFromSource.map((assignment) => assignment.requestId).join(", ")}).`,
3221
- failures: unresolvedFromSource.map((assignment) => ({
3222
- agentId: null,
3223
- statusCode: "helper-assignment-unresolved",
3224
- logPath: null,
3225
- detail: assignment.assignmentDetail || assignment.summary || assignment.requestId,
3226
- })),
3227
- },
3228
- };
3229
- }
3230
- const assignedAgentIds = new Set(
3231
- assignmentSource.map((assignment) => assignment.assignedAgentId).filter(Boolean),
3232
- );
3233
- if (assignedAgentIds.size > 0) {
3234
- return {
3235
- runs: Array.from(assignedAgentIds)
3236
- .map((agentId) => runsByAgentId.get(agentId))
3237
- .filter(Boolean),
3238
- barrier: null,
3239
- };
3240
- }
3241
- const unresolvedInboundAssignments =
3242
- derivedState?.dependencySnapshot?.unresolvedInboundAssignments || [];
3243
- if (unresolvedInboundAssignments.length > 0) {
3244
- return {
3245
- runs: [],
3246
- barrier: {
3247
- statusCode: "dependency-assignment-unresolved",
3248
- detail: `Required inbound dependencies are not assigned (${unresolvedInboundAssignments.map((record) => record.id).join(", ")}).`,
3249
- failures: unresolvedInboundAssignments.map((record) => ({
3250
- agentId: null,
3251
- statusCode: "dependency-assignment-unresolved",
3252
- logPath: null,
3253
- detail: record.assignmentDetail || record.summary || record.id,
3254
- })),
3255
- },
3256
- };
3257
- }
3258
- const inboundDependencyAgentIds = new Set(
3259
- (derivedState?.dependencySnapshot?.openInbound || [])
3260
- .map((record) => record.assignedAgentId)
3261
- .filter(Boolean),
3262
- );
3263
- if (inboundDependencyAgentIds.size > 0) {
3264
- return {
3265
- runs: Array.from(inboundDependencyAgentIds)
3266
- .map((agentId) => runsByAgentId.get(agentId))
3267
- .filter(Boolean),
3268
- barrier: null,
3269
- };
3270
- }
3271
- const blockerAgentIds = new Set();
3272
- for (const record of derivedState?.coordinationState?.blockers || []) {
3273
- if (!isOpenCoordinationStatus(record.status)) {
3274
- continue;
3275
- }
3276
- blockerAgentIds.add(record.agentId);
3277
- for (const target of record.targets || []) {
3278
- if (String(target).startsWith("agent:")) {
3279
- blockerAgentIds.add(String(target).slice("agent:".length));
3280
- }
3281
- }
3282
- }
3283
- if (blockerAgentIds.size > 0) {
3284
- return {
3285
- runs: Array.from(blockerAgentIds)
3286
- .map((agentId) => runsByAgentId.get(agentId))
3287
- .filter(Boolean),
3288
- barrier: null,
3289
- };
3290
- }
3291
- if (derivedState?.ledger?.phase === "docs-closure") {
3292
- return {
3293
- runs: [runsByAgentId.get(lanePaths.documentationAgentId)].filter(Boolean),
3294
- barrier: null,
3295
- };
3296
- }
3297
- if (derivedState?.ledger?.phase === "security-review") {
3298
- return {
3299
- runs: agentRuns.filter((run) => isSecurityReviewAgent(run.agent)),
3300
- barrier: null,
3301
- };
3302
- }
3303
- if (derivedState?.ledger?.phase === "cont-eval") {
3304
- return {
3305
- runs: [runsByAgentId.get(lanePaths.contEvalAgentId)].filter(Boolean),
3306
- barrier: null,
3307
- };
3308
- }
3309
- if (derivedState?.ledger?.phase === "cont-qa-closure") {
3310
- return {
3311
- runs: [runsByAgentId.get(lanePaths.contQaAgentId)].filter(Boolean),
3312
- barrier: null,
3313
- };
3314
- }
3315
- if (derivedState?.ledger?.phase === "integrating") {
3316
- return {
3317
- runs: [runsByAgentId.get(lanePaths.integrationAgentId)].filter(Boolean),
3318
- barrier: null,
3319
- };
3320
- }
3321
- const sharedComponentWaitingAgentIds = new Set(
3322
- (failures || [])
3323
- .filter((failure) => failure.statusCode === "shared-component-sibling-pending")
3324
- .flatMap((failure) => failure.waitingOnAgentIds || [])
3325
- .filter((agentId) => runsByAgentId.has(agentId)),
3326
- );
3327
- if (sharedComponentWaitingAgentIds.size > 0) {
3328
- return {
3329
- runs: Array.from(sharedComponentWaitingAgentIds)
3330
- .map((agentId) => runsByAgentId.get(agentId))
3331
- .filter(Boolean),
3332
- barrier: null,
3333
- };
3334
- }
3335
- const failedAgentIds = new Set(failures.map((failure) => failure.agentId));
3336
- return {
3337
- runs: agentRuns.filter((run) => failedAgentIds.has(run.agent.agentId)),
3338
- barrier: null,
3339
- };
3340
- }
3341
-
3342
- function preflightWavesForExecutorAvailability(waves, lanePaths) {
3343
- for (const wave of waves) {
3344
- const mixValidation = validateWaveRuntimeMixAssignments(wave, {
3345
- laneProfile: lanePaths.laneProfile,
3346
- });
3347
- if (!mixValidation.ok) {
3348
- throw new Error(
3349
- `Wave ${wave.wave} exceeds lane runtime mix targets (${mixValidation.detail})`,
3350
- );
3351
- }
3352
- for (const agent of wave.agents) {
3353
- const executorState = agent.executorResolved;
3354
- if (!executorState) {
3355
- continue;
3356
- }
3357
- const chain = [executorState.id, ...executorFallbackChain(executorState)];
3358
- const availableExecutorId = chain.find((executorId) =>
3359
- isExecutorCommandAvailable(commandForExecutor(executorState, executorId)),
3360
- );
3361
- if (!availableExecutorId) {
3362
- throw new Error(
3363
- `Agent ${agent.agentId} has no available executor command in its configured chain (${chain.join(" -> ")})`,
3364
- );
3365
- }
3366
- }
3367
- }
3368
- }
599
+ // --- Main entry point ---
3369
600
 
3370
601
  export async function runLauncherCli(argv) {
3371
602
  const parsed = parseArgs(argv);