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