@chllming/wave-orchestration 0.9.0 → 0.9.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 (68) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +133 -20
  4. package/docs/README.md +12 -4
  5. package/docs/agents/wave-security-role.md +1 -0
  6. package/docs/architecture/README.md +1498 -0
  7. package/docs/concepts/operating-modes.md +2 -2
  8. package/docs/guides/author-and-run-waves.md +14 -4
  9. package/docs/guides/planner.md +2 -2
  10. package/docs/guides/{recommendations-0.9.0.md → recommendations-0.9.2.md} +8 -7
  11. package/docs/guides/sandboxed-environments.md +158 -0
  12. package/docs/guides/terminal-surfaces.md +14 -12
  13. package/docs/plans/current-state.md +11 -3
  14. package/docs/plans/end-state-architecture.md +3 -1
  15. package/docs/plans/examples/wave-example-design-handoff.md +1 -1
  16. package/docs/plans/examples/wave-example-live-proof.md +1 -1
  17. package/docs/plans/migration.md +70 -19
  18. package/docs/plans/sandbox-end-state-architecture.md +153 -0
  19. package/docs/reference/cli-reference.md +71 -7
  20. package/docs/reference/coordination-and-closure.md +18 -1
  21. package/docs/reference/corridor.md +225 -0
  22. package/docs/reference/github-packages-setup.md +1 -1
  23. package/docs/reference/migration-0.2-to-0.5.md +9 -7
  24. package/docs/reference/npmjs-token-publishing.md +53 -0
  25. package/docs/reference/npmjs-trusted-publishing.md +4 -50
  26. package/docs/reference/package-publishing-flow.md +272 -0
  27. package/docs/reference/runtime-config/README.md +61 -3
  28. package/docs/reference/sample-waves.md +5 -5
  29. package/docs/reference/skills.md +1 -1
  30. package/docs/reference/wave-control.md +358 -27
  31. package/docs/roadmap.md +39 -204
  32. package/package.json +1 -1
  33. package/releases/manifest.json +38 -0
  34. package/scripts/wave-cli-bootstrap.mjs +52 -1
  35. package/scripts/wave-orchestrator/agent-process-runner.mjs +344 -0
  36. package/scripts/wave-orchestrator/agent-state.mjs +0 -1
  37. package/scripts/wave-orchestrator/artifact-schemas.mjs +7 -0
  38. package/scripts/wave-orchestrator/autonomous.mjs +47 -14
  39. package/scripts/wave-orchestrator/closure-engine.mjs +138 -17
  40. package/scripts/wave-orchestrator/config.mjs +199 -3
  41. package/scripts/wave-orchestrator/context7.mjs +231 -29
  42. package/scripts/wave-orchestrator/control-cli.mjs +42 -5
  43. package/scripts/wave-orchestrator/coordination.mjs +14 -0
  44. package/scripts/wave-orchestrator/corridor.mjs +363 -0
  45. package/scripts/wave-orchestrator/dashboard-renderer.mjs +115 -43
  46. package/scripts/wave-orchestrator/derived-state-engine.mjs +44 -4
  47. package/scripts/wave-orchestrator/gate-engine.mjs +126 -38
  48. package/scripts/wave-orchestrator/install.mjs +46 -0
  49. package/scripts/wave-orchestrator/launcher-progress.mjs +91 -0
  50. package/scripts/wave-orchestrator/launcher-runtime.mjs +290 -75
  51. package/scripts/wave-orchestrator/launcher.mjs +201 -53
  52. package/scripts/wave-orchestrator/ledger.mjs +7 -2
  53. package/scripts/wave-orchestrator/planner.mjs +1 -0
  54. package/scripts/wave-orchestrator/projection-writer.mjs +36 -1
  55. package/scripts/wave-orchestrator/provider-runtime.mjs +104 -0
  56. package/scripts/wave-orchestrator/reducer-snapshot.mjs +6 -0
  57. package/scripts/wave-orchestrator/retry-control.mjs +3 -3
  58. package/scripts/wave-orchestrator/retry-engine.mjs +93 -6
  59. package/scripts/wave-orchestrator/role-helpers.mjs +30 -0
  60. package/scripts/wave-orchestrator/session-supervisor.mjs +94 -85
  61. package/scripts/wave-orchestrator/shared.mjs +1 -0
  62. package/scripts/wave-orchestrator/supervisor-cli.mjs +1306 -0
  63. package/scripts/wave-orchestrator/terminals.mjs +12 -32
  64. package/scripts/wave-orchestrator/tmux-adapter.mjs +300 -0
  65. package/scripts/wave-orchestrator/traces.mjs +25 -0
  66. package/scripts/wave-orchestrator/wave-control-client.mjs +14 -1
  67. package/scripts/wave-orchestrator/wave-files.mjs +38 -5
  68. package/scripts/wave.mjs +13 -0
@@ -74,6 +74,7 @@ import {
74
74
  appendTerminalEntries,
75
75
  createGlobalDashboardTerminalEntry,
76
76
  createTemporaryTerminalEntries,
77
+ createWaveAgentSessionName,
77
78
  killTmuxSessionIfExists,
78
79
  normalizeTerminalSurface,
79
80
  pruneOrphanLaneTemporaryTerminalEntries,
@@ -144,7 +145,7 @@ import {
144
145
  summarizeResolvedSkills,
145
146
  } from "./skills.mjs";
146
147
  import {
147
- collectUnexpectedSessionFailures as collectUnexpectedSessionFailuresImpl,
148
+ applyLaunchResultToRun,
148
149
  launchAgentSession as launchAgentSessionImpl,
149
150
  refreshResolvedSkillsForRun,
150
151
  waitForWaveCompletion as waitForWaveCompletionImpl,
@@ -200,7 +201,7 @@ import {
200
201
  acquireLauncherLock,
201
202
  releaseLauncherLock,
202
203
  reconcileStaleLauncherArtifacts,
203
- collectUnexpectedSessionFailures,
204
+ collectUnexpectedSessionWarnings,
204
205
  launchAgentSession,
205
206
  waitForWaveCompletion,
206
207
  monitorWaveHumanFeedback,
@@ -208,10 +209,10 @@ import {
208
209
  monitorResidentOrchestratorSession,
209
210
  launchWaveDashboardSession,
210
211
  cleanupLaneTmuxSessions,
212
+ cleanupLaunchedRun,
211
213
  pruneDryRunExecutorPreviewDirs,
212
214
  recordAttemptState,
213
215
  recordWaveRunState,
214
- runTmux,
215
216
  syncLiveWaveSignals,
216
217
  } from "./session-supervisor.mjs";
217
218
  import { buildControlStatusPayload } from "./control-cli.mjs";
@@ -230,6 +231,10 @@ import {
230
231
  formatReconcilePreservedWaveLine,
231
232
  } from "./reconcile-format.mjs";
232
233
  import { computeReducerSnapshot } from "./reducer-snapshot.mjs";
234
+ import {
235
+ launcherProgressPathForRun,
236
+ updateLauncherProgress,
237
+ } from "./launcher-progress.mjs";
233
238
 
234
239
  function printUsage(lanePaths, terminalSurface) {
235
240
  console.log(`Usage: pnpm exec wave launch [options]
@@ -255,7 +260,7 @@ Options:
255
260
  --agent-launch-stagger-ms <n>
256
261
  Delay between agent launches (default: ${DEFAULT_AGENT_LAUNCH_STAGGER_MS})
257
262
  --executor <mode> Default agent executor mode: ${SUPPORTED_EXECUTOR_MODES.join(" | ")} (default: ${lanePaths.executors.default})
258
- --codex-sandbox <mode> Codex sandbox mode: ${CODEX_SANDBOX_MODES.join(" | ")} (default: ${DEFAULT_CODEX_SANDBOX_MODE})
263
+ --codex-sandbox <mode> Codex sandbox mode override: ${CODEX_SANDBOX_MODES.join(" | ")} (default: lane config)
259
264
  --manifest-out <path> Write parsed wave manifest JSON (default: ${path.relative(REPO_ROOT, lanePaths.defaultManifestPath)})
260
265
  --dry-run Parse waves and update manifest only
261
266
  --terminal-surface <mode>
@@ -298,7 +303,7 @@ function parseArgs(argv) {
298
303
  agentRateLimitMaxDelaySeconds: DEFAULT_AGENT_RATE_LIMIT_MAX_DELAY_SECONDS,
299
304
  agentLaunchStaggerMs: DEFAULT_AGENT_LAUNCH_STAGGER_MS,
300
305
  executorMode: lanePaths.executors.default,
301
- codexSandboxMode: DEFAULT_CODEX_SANDBOX_MODE,
306
+ codexSandboxMode: null,
302
307
  manifestOut: lanePaths.defaultManifestPath,
303
308
  dryRun: false,
304
309
  terminalSurface: resolveDefaultTerminalSurface(
@@ -319,6 +324,7 @@ function parseArgs(argv) {
319
324
  let manifestOutProvided = false;
320
325
  let orchestratorBoardProvided = false;
321
326
  let executorProvided = false;
327
+ let terminalSurfaceProvided = false;
322
328
 
323
329
  for (let i = 0; i < argv.length; i += 1) {
324
330
  const arg = argv[i];
@@ -332,6 +338,7 @@ function parseArgs(argv) {
332
338
  options.dryRun = true;
333
339
  } else if (arg === "--terminal-surface") {
334
340
  options.terminalSurface = normalizeTerminalSurface(argv[++i], "--terminal-surface");
341
+ terminalSurfaceProvided = true;
335
342
  } else if (arg === "--no-dashboard") {
336
343
  options.dashboard = false;
337
344
  } else if (arg === "--cleanup-sessions") {
@@ -360,9 +367,11 @@ function parseArgs(argv) {
360
367
  project: options.project,
361
368
  adhocRunId: options.adhocRunId,
362
369
  });
363
- options.terminalSurface = resolveDefaultTerminalSurface(
364
- readProjectProfile({ config, project: options.project }),
365
- );
370
+ if (!terminalSurfaceProvided) {
371
+ options.terminalSurface = resolveDefaultTerminalSurface(
372
+ readProjectProfile({ config, project: options.project }),
373
+ );
374
+ }
366
375
  } else if (arg === "--lane") {
367
376
  options.lane = String(argv[++i] || "").trim();
368
377
  lanePaths = buildLanePaths(options.lane, {
@@ -654,7 +663,7 @@ function recoverableFailureReason(failure, summary = null) {
654
663
  return statusCode;
655
664
  }
656
665
  const terminationReason = String(summary?.terminationReason || "").trim().toLowerCase();
657
- if (["timeout", "max-turns", "session-missing"].includes(terminationReason)) {
666
+ if (["timeout", "max-turns"].includes(terminationReason)) {
658
667
  return terminationReason;
659
668
  }
660
669
  const detailText = `${failure?.detail || ""} ${summary?.terminationHint || ""}`.toLowerCase();
@@ -747,6 +756,23 @@ export async function runLauncherCli(argv) {
747
756
  return;
748
757
  }
749
758
  const { lanePaths, options } = parsed;
759
+ const supervisorRunId = String(process.env.WAVE_SUPERVISOR_RUN_ID || "").trim() || null;
760
+ const launcherProgressPath = launcherProgressPathForRun(lanePaths, supervisorRunId);
761
+ const writeLauncherRunProgress = (patch = {}) => {
762
+ if (!launcherProgressPath || !supervisorRunId) {
763
+ return null;
764
+ }
765
+ return updateLauncherProgress(
766
+ launcherProgressPath,
767
+ {
768
+ runId: supervisorRunId,
769
+ ...patch,
770
+ },
771
+ {
772
+ runId: supervisorRunId,
773
+ },
774
+ );
775
+ };
750
776
  if (!options.reconcileStatus) {
751
777
  await maybeAnnouncePackageUpdate();
752
778
  }
@@ -824,7 +850,7 @@ export async function runLauncherCli(argv) {
824
850
  }
825
851
 
826
852
  try {
827
- const staleArtifactCleanup = reconcileStaleLauncherArtifacts(lanePaths, {
853
+ const staleArtifactCleanup = await reconcileStaleLauncherArtifacts(lanePaths, {
828
854
  terminalSurface: options.terminalSurface,
829
855
  });
830
856
  const context7BundleIndex = loadContext7BundleIndex(lanePaths.context7BundleIndexPath);
@@ -1036,6 +1062,18 @@ export async function runLauncherCli(argv) {
1036
1062
  feedbackRequestsDir: lanePaths.feedbackRequestsDir,
1037
1063
  });
1038
1064
  writeDashboardProjections({ lanePaths, globalDashboard });
1065
+ writeLauncherRunProgress({
1066
+ phase: "starting",
1067
+ waveNumber: null,
1068
+ attemptNumber: null,
1069
+ selectedAgentIds: [],
1070
+ launchedAgentIds: [],
1071
+ completedAgentIds: [],
1072
+ resumeFromPhase: null,
1073
+ forwardedClosureGaps: [],
1074
+ finalized: false,
1075
+ finalDisposition: null,
1076
+ });
1039
1077
 
1040
1078
  if (terminalRegistryEnabled && !options.keepTerminals) {
1041
1079
  const removed = removeLaneTemporaryTerminalEntries(lanePaths.terminalsPath, lanePaths);
@@ -1047,7 +1085,7 @@ export async function runLauncherCli(argv) {
1047
1085
  }
1048
1086
 
1049
1087
  if (options.cleanupSessions) {
1050
- const killed = cleanupLaneTmuxSessions(lanePaths);
1088
+ const killed = await cleanupLaneTmuxSessions(lanePaths);
1051
1089
  if (killed.length > 0) {
1052
1090
  recordGlobalDashboardEvent(globalDashboard, {
1053
1091
  message: `Pre-run cleanup removed ${killed.length} stale tmux sessions for lane ${lanePaths.lane}.`,
@@ -1070,7 +1108,7 @@ export async function runLauncherCli(argv) {
1070
1108
  globalDashboardTerminalAppended = true;
1071
1109
  currentWaveDashboardTerminalAppended = true;
1072
1110
  }
1073
- launchWaveDashboardSession(lanePaths, {
1111
+ await launchWaveDashboardSession(lanePaths, {
1074
1112
  sessionName: globalDashboardTerminalEntry.sessionName,
1075
1113
  dashboardPath: lanePaths.globalDashboardPath,
1076
1114
  });
@@ -1154,36 +1192,18 @@ export async function runLauncherCli(argv) {
1154
1192
  };
1155
1193
 
1156
1194
  try {
1157
- terminalEntries = createTemporaryTerminalEntries(
1158
- lanePaths,
1159
- wave.wave,
1160
- wave.agents,
1161
- runTag,
1162
- false,
1163
- );
1164
- if (terminalRegistryEnabled) {
1165
- appendTerminalEntries(lanePaths.terminalsPath, terminalEntries);
1166
- terminalsAppended = true;
1167
- }
1168
-
1169
1195
  const agentRuns = wave.agents.map((agent) => {
1170
1196
  const safeName = `wave-${wave.wave}-${agent.slug}`;
1171
- const terminalName = `${lanePaths.terminalNamePrefix}${wave.wave}-${agent.slug}`;
1172
- const sessionName = terminalEntries.find(
1173
- (entry) => entry.terminalName === terminalName,
1174
- )?.sessionName;
1175
- if (!sessionName) {
1176
- throw new Error(`Failed to resolve session name for ${agent.agentId}`);
1177
- }
1178
1197
  return {
1179
1198
  agent,
1180
1199
  lane: lanePaths.lane,
1181
1200
  wave: wave.wave,
1182
1201
  resultsDir: lanePaths.resultsDir,
1183
- sessionName,
1202
+ sessionName: createWaveAgentSessionName(lanePaths, wave.wave, agent.slug),
1184
1203
  promptPath: path.join(lanePaths.promptsDir, `${safeName}.prompt.md`),
1185
1204
  logPath: path.join(lanePaths.logsDir, `${safeName}.log`),
1186
1205
  statusPath: path.join(lanePaths.statusDir, `${safeName}.status`),
1206
+ runtimePath: path.join(lanePaths.statusDir, `${safeName}.runtime.json`),
1187
1207
  previewPath: path.join(
1188
1208
  lanePaths.executorOverlaysDir,
1189
1209
  `wave-${wave.wave}`,
@@ -1198,6 +1218,17 @@ export async function runLauncherCli(argv) {
1198
1218
  inboxText: derivedState.inboxesByAgentId[agent.agentId]?.text || "",
1199
1219
  };
1200
1220
  });
1221
+ terminalEntries = createTemporaryTerminalEntries(
1222
+ lanePaths,
1223
+ wave.wave,
1224
+ agentRuns,
1225
+ runTag,
1226
+ false,
1227
+ );
1228
+ if (terminalRegistryEnabled) {
1229
+ appendTerminalEntries(lanePaths.terminalsPath, terminalEntries);
1230
+ terminalsAppended = true;
1231
+ }
1201
1232
  const roleBindings = resolveWaveRoleBindings(wave, lanePaths, wave.agents);
1202
1233
 
1203
1234
  const refreshDerivedState = (attemptNumber = 0) => {
@@ -1207,7 +1238,12 @@ export async function runLauncherCli(argv) {
1207
1238
  }
1208
1239
  const summariesByAgentId = Object.fromEntries(
1209
1240
  agentRuns
1210
- .map((run) => [run.agent.agentId, readRunExecutionSummary(run, wave)])
1241
+ .map((run) => [
1242
+ run.agent.agentId,
1243
+ readRunExecutionSummary(run, wave, {
1244
+ securityRolePromptPath: lanePaths.securityRolePromptPath,
1245
+ }),
1246
+ ])
1211
1247
  .filter(([, summary]) => summary),
1212
1248
  );
1213
1249
  const feedbackRequests = readWaveHumanFeedbackRequests({
@@ -1396,7 +1432,7 @@ export async function runLauncherCli(argv) {
1396
1432
  syncWaveSignals();
1397
1433
 
1398
1434
  if (options.dashboard && currentWaveDashboardTerminalEntry) {
1399
- launchWaveDashboardSession(lanePaths, {
1435
+ await launchWaveDashboardSession(lanePaths, {
1400
1436
  sessionName: currentWaveDashboardTerminalEntry.sessionName,
1401
1437
  dashboardPath,
1402
1438
  messageBoardPath,
@@ -1431,6 +1467,7 @@ export async function runLauncherCli(argv) {
1431
1467
  promptPath: residentOrchestratorRun.promptPath,
1432
1468
  logPath: residentOrchestratorRun.logPath,
1433
1469
  statusPath: residentOrchestratorRun.statusPath,
1470
+ runtimePath: residentOrchestratorRun.runtimePath,
1434
1471
  messageBoardPath: derivedState.messageBoardPath,
1435
1472
  messageBoardSnapshot: derivedState.messageBoardText,
1436
1473
  sharedSummaryPath: derivedState.sharedSummaryPath,
@@ -1444,18 +1481,19 @@ export async function runLauncherCli(argv) {
1444
1481
  agentRateLimitMaxDelaySeconds: options.agentRateLimitMaxDelaySeconds,
1445
1482
  context7Enabled: options.context7Enabled,
1446
1483
  });
1447
- residentOrchestratorRun.lastPromptHash = launchResult?.promptHash || null;
1448
- residentOrchestratorRun.lastExecutorId =
1449
- launchResult?.executorId || residentOrchestratorRun.agent.executorResolved?.id || null;
1484
+ applyLaunchResultToRun(residentOrchestratorRun, launchResult, {
1485
+ fallbackExecutorId: residentOrchestratorRun.agent.executorResolved?.id || null,
1486
+ fallbackSkills: summarizeResolvedSkills(residentOrchestratorRun.agent.skillsResolved),
1487
+ });
1450
1488
  recordCombinedEvent({
1451
1489
  agentId: residentOrchestratorRun.agent.agentId,
1452
- message: `Resident orchestrator launched in tmux session ${residentOrchestratorRun.sessionName}`,
1490
+ message: `Resident orchestrator launched via ${launchResult?.sessionBackend || "process"} backend`,
1453
1491
  });
1454
1492
  appendCoordination({
1455
1493
  event: "resident_orchestrator_start",
1456
1494
  waves: [wave.wave],
1457
1495
  status: "running",
1458
- details: `session=${residentOrchestratorRun.sessionName}; executor=${residentOrchestratorRun.lastExecutorId || "unknown"}`,
1496
+ details: `backend=${launchResult?.sessionBackend || "process"}; session=${residentOrchestratorRun.sessionName}; executor=${residentOrchestratorRun.lastExecutorId || "unknown"}`,
1459
1497
  actionRequested: "None",
1460
1498
  });
1461
1499
  syncWaveSignals();
@@ -1497,12 +1535,36 @@ export async function runLauncherCli(argv) {
1497
1535
  let traceAttempt = 1;
1498
1536
  let completionGateSnapshot = null;
1499
1537
  let completionTraceDir = null;
1538
+ const terminalAgentIdsForWave = () =>
1539
+ agentRuns
1540
+ .filter((run) => readStatusRecordIfPresent(run.statusPath))
1541
+ .map((run) => run.agent.agentId);
1500
1542
  recordWaveRunState(lanePaths, wave.wave, "started", {
1501
1543
  agentIds: wave.agents.map((agent) => agent.agentId),
1502
1544
  runVariant: lanePaths.runVariant || "live",
1503
1545
  });
1546
+ writeLauncherRunProgress({
1547
+ waveNumber: wave.wave,
1548
+ attemptNumber: 0,
1549
+ phase: "wave-started",
1550
+ selectedAgentIds: runsToLaunch.map((run) => run.agent.agentId),
1551
+ launchedAgentIds: [],
1552
+ completedAgentIds: terminalAgentIdsForWave(),
1553
+ finalized: false,
1554
+ finalDisposition: null,
1555
+ });
1504
1556
 
1505
1557
  while (attempt <= options.maxRetriesPerWave + 1) {
1558
+ writeLauncherRunProgress({
1559
+ waveNumber: wave.wave,
1560
+ attemptNumber: attempt,
1561
+ phase: "attempt-starting",
1562
+ selectedAgentIds: runsToLaunch.map((run) => run.agent.agentId),
1563
+ launchedAgentIds: [],
1564
+ completedAgentIds: [],
1565
+ finalized: false,
1566
+ finalDisposition: null,
1567
+ });
1506
1568
  refreshDerivedState(attempt - 1);
1507
1569
  lastLiveCoordinationRefreshAt = Date.now();
1508
1570
  dashboardState.attempt = attempt;
@@ -1569,6 +1631,7 @@ export async function runLauncherCli(argv) {
1569
1631
  promptPath: runInfo.promptPath,
1570
1632
  logPath: runInfo.logPath,
1571
1633
  statusPath: runInfo.statusPath,
1634
+ runtimePath: runInfo.runtimePath,
1572
1635
  messageBoardPath: runInfo.messageBoardPath,
1573
1636
  messageBoardSnapshot: runInfo.messageBoardSnapshot || "",
1574
1637
  sharedSummaryPath: runInfo.sharedSummaryPath,
@@ -1596,19 +1659,27 @@ export async function runLauncherCli(argv) {
1596
1659
  attempt,
1597
1660
  },
1598
1661
  });
1599
- runInfo.lastLaunchAttempt = attempt;
1600
- runInfo.lastPromptHash = launchResult?.promptHash || null;
1601
- runInfo.lastContext7 = launchResult?.context7 || null;
1602
- runInfo.lastExecutorId = launchResult?.executorId || runInfo.agent.executorResolved?.id || null;
1603
- runInfo.lastSkillProjection =
1604
- launchResult?.skills || summarizeResolvedSkills(runInfo.agent.skillsResolved);
1662
+ applyLaunchResultToRun(runInfo, launchResult, {
1663
+ attempt,
1664
+ fallbackExecutorId: runInfo.agent.executorResolved?.id || null,
1665
+ fallbackSkills: summarizeResolvedSkills(runInfo.agent.skillsResolved),
1666
+ });
1667
+ writeLauncherRunProgress({
1668
+ waveNumber: wave.wave,
1669
+ attemptNumber: attempt,
1670
+ phase: "attempt-running",
1671
+ selectedAgentIds: runsToLaunch.map((run) => run.agent.agentId),
1672
+ launchedAgentIds: runsToLaunch
1673
+ .filter((run) => run.lastLaunchAttempt === attempt)
1674
+ .map((run) => run.agent.agentId),
1675
+ });
1605
1676
  setWaveDashboardAgent(dashboardState, runInfo.agent.agentId, {
1606
1677
  state: "running",
1607
1678
  detail: "Session launched",
1608
1679
  });
1609
1680
  recordCombinedEvent({
1610
1681
  agentId: runInfo.agent.agentId,
1611
- message: `Launched in tmux session ${runInfo.sessionName}`,
1682
+ message: `Launched via ${launchResult?.sessionBackend || "process"} backend`,
1612
1683
  });
1613
1684
  const context7Mode = launchResult?.context7?.mode || "none";
1614
1685
  if (runInfo.agent.context7Resolved?.bundleId !== "none") {
@@ -1678,9 +1749,23 @@ export async function runLauncherCli(argv) {
1678
1749
  timedOut = waitResult.timedOut;
1679
1750
  }
1680
1751
 
1681
- materializeAgentExecutionSummaries(wave, agentRuns);
1752
+ materializeAgentExecutionSummaries(wave, agentRuns, {
1753
+ securityRolePromptPath: lanePaths.securityRolePromptPath,
1754
+ });
1682
1755
  failures = annotateFailuresWithRecoveryHints(failures, agentRuns);
1683
1756
  refreshDerivedState(attempt);
1757
+ const reducerDecisionForProgress = latestReducerSnapshot || refreshReducerSnapshot(attempt);
1758
+ writeLauncherRunProgress({
1759
+ waveNumber: wave.wave,
1760
+ attemptNumber: attempt,
1761
+ phase: failures.length === 0 ? "attempt-succeeded" : "attempt-failed",
1762
+ selectedAgentIds: runsToLaunch.map((run) => run.agent.agentId),
1763
+ launchedAgentIds: runsToLaunch.map((run) => run.agent.agentId),
1764
+ completedAgentIds: terminalAgentIdsForWave(),
1765
+ resumeFromPhase: reducerDecisionForProgress?.resumePlan?.resumeFromPhase || null,
1766
+ forwardedClosureGaps: reducerDecisionForProgress?.resumePlan?.forwardedClosureGaps || [],
1767
+ gateSnapshotSummary: reducerDecisionForProgress?.reducerState?.gateSnapshot?.overall || null,
1768
+ });
1684
1769
  syncWaveSignals();
1685
1770
  lastLiveCoordinationRefreshAt = Date.now();
1686
1771
  emitCoordinationAlertEvents(derivedState);
@@ -1765,7 +1850,9 @@ export async function runLauncherCli(argv) {
1765
1850
  }
1766
1851
  }
1767
1852
  if (failures.length === 0) {
1768
- const implementationGate = readWaveImplementationGate(wave, agentRuns);
1853
+ const implementationGate = readWaveImplementationGate(wave, agentRuns, {
1854
+ securityRolePromptPath: lanePaths.securityRolePromptPath,
1855
+ });
1769
1856
  if (!implementationGate.ok) {
1770
1857
  failures = [
1771
1858
  {
@@ -1889,7 +1976,9 @@ export async function runLauncherCli(argv) {
1889
1976
  });
1890
1977
  failures = closureResult.failures;
1891
1978
  timedOut = timedOut || closureResult.timedOut;
1892
- materializeAgentExecutionSummaries(wave, agentRuns);
1979
+ materializeAgentExecutionSummaries(wave, agentRuns, {
1980
+ securityRolePromptPath: lanePaths.securityRolePromptPath,
1981
+ });
1893
1982
  failures = annotateFailuresWithRecoveryHints(failures, agentRuns);
1894
1983
  refreshDerivedState(attempt);
1895
1984
  }
@@ -2097,6 +2186,19 @@ export async function runLauncherCli(argv) {
2097
2186
  runs: runsToLaunch,
2098
2187
  failures,
2099
2188
  derivedState,
2189
+ resumePlan: (latestReducerSnapshot || refreshReducerSnapshot(attempt))?.resumePlan || null,
2190
+ });
2191
+ writeLauncherRunProgress({
2192
+ waveNumber: wave.wave,
2193
+ attemptNumber: attempt,
2194
+ phase: "waiting-shared-component",
2195
+ selectedAgentIds: runsToLaunch.map((run) => run.agent.agentId),
2196
+ launchedAgentIds: runsToLaunch.map((run) => run.agent.agentId),
2197
+ completedAgentIds: terminalAgentIdsForWave(),
2198
+ resumeFromPhase:
2199
+ (latestReducerSnapshot || refreshReducerSnapshot(attempt))?.resumePlan?.resumeFromPhase || null,
2200
+ forwardedClosureGaps:
2201
+ (latestReducerSnapshot || refreshReducerSnapshot(attempt))?.resumePlan?.forwardedClosureGaps || [],
2100
2202
  });
2101
2203
  flushDashboards();
2102
2204
  traceAttempt += 1;
@@ -2121,6 +2223,16 @@ export async function runLauncherCli(argv) {
2121
2223
  updateWaveDashboardMessageBoard(dashboardState, messageBoardPath);
2122
2224
  flushDashboards();
2123
2225
  await flushWaveControlTelemetry();
2226
+ writeLauncherRunProgress({
2227
+ waveNumber: wave.wave,
2228
+ attemptNumber: attempt,
2229
+ phase: "wave-completed",
2230
+ selectedAgentIds: [],
2231
+ launchedAgentIds: [],
2232
+ completedAgentIds: terminalAgentIdsForWave(),
2233
+ resumeFromPhase: "completed",
2234
+ forwardedClosureGaps: [],
2235
+ });
2124
2236
  break;
2125
2237
  }
2126
2238
 
@@ -2179,6 +2291,7 @@ export async function runLauncherCli(argv) {
2179
2291
  runs: recoveryPlan.selectedRuns,
2180
2292
  failures,
2181
2293
  derivedState,
2294
+ resumePlan: reducerDecision?.resumePlan || null,
2182
2295
  });
2183
2296
  }
2184
2297
  recordAttemptState(lanePaths, wave.wave, attempt, "failed", {
@@ -2218,6 +2331,16 @@ export async function runLauncherCli(argv) {
2218
2331
  actionRequested:
2219
2332
  `Lane ${lanePaths.lane} owners should resume the queued targeted recovery or let autonomous relaunch the selected agents.`,
2220
2333
  });
2334
+ writeLauncherRunProgress({
2335
+ waveNumber: wave.wave,
2336
+ attemptNumber: attempt,
2337
+ phase: "recovery-queued",
2338
+ selectedAgentIds: recoverySelectedAgentIds,
2339
+ launchedAgentIds: runsToLaunch.map((run) => run.agent.agentId),
2340
+ completedAgentIds: terminalAgentIdsForWave(),
2341
+ resumeFromPhase: reducerDecision?.resumePlan?.resumeFromPhase || null,
2342
+ forwardedClosureGaps: reducerDecision?.resumePlan?.forwardedClosureGaps || [],
2343
+ });
2221
2344
  await flushWaveControlTelemetry();
2222
2345
  const error = new Error(
2223
2346
  `Wave ${wave.wave} queued targeted recovery request ${queuedRecovery.requestId} after recoverable execution failures.`,
@@ -2386,6 +2509,19 @@ export async function runLauncherCli(argv) {
2386
2509
  runs: runsToLaunch,
2387
2510
  failures,
2388
2511
  derivedState,
2512
+ resumePlan: (latestReducerSnapshot || refreshReducerSnapshot(attempt))?.resumePlan || null,
2513
+ });
2514
+ writeLauncherRunProgress({
2515
+ waveNumber: wave.wave,
2516
+ attemptNumber: attempt + 1,
2517
+ phase: "retry-queued",
2518
+ selectedAgentIds: runsToLaunch.map((run) => run.agent.agentId),
2519
+ launchedAgentIds: [],
2520
+ completedAgentIds: terminalAgentIdsForWave(),
2521
+ resumeFromPhase:
2522
+ (latestReducerSnapshot || refreshReducerSnapshot(attempt))?.resumePlan?.resumeFromPhase || null,
2523
+ forwardedClosureGaps:
2524
+ (latestReducerSnapshot || refreshReducerSnapshot(attempt))?.resumePlan?.forwardedClosureGaps || [],
2389
2525
  });
2390
2526
  flushDashboards();
2391
2527
  attempt += 1;
@@ -2418,7 +2554,7 @@ export async function runLauncherCli(argv) {
2418
2554
  });
2419
2555
  } finally {
2420
2556
  if (residentOrchestratorRun) {
2421
- killTmuxSessionIfExists(lanePaths.tmuxSocketName, residentOrchestratorRun.sessionName);
2557
+ await cleanupLaunchedRun(lanePaths, residentOrchestratorRun);
2422
2558
  }
2423
2559
  if (terminalsAppended && !options.keepTerminals) {
2424
2560
  removeTerminalEntries(lanePaths.terminalsPath, terminalEntries);
@@ -2431,7 +2567,7 @@ export async function runLauncherCli(argv) {
2431
2567
  if (currentWaveDashboardTerminalEntry) {
2432
2568
  excludeSessionNames.add(currentWaveDashboardTerminalEntry.sessionName);
2433
2569
  }
2434
- cleanupLaneTmuxSessions(lanePaths, { excludeSessionNames });
2570
+ await cleanupLaneTmuxSessions(lanePaths, { excludeSessionNames });
2435
2571
  }
2436
2572
  if (globalWave && globalWave.status === "running") {
2437
2573
  globalWave.status = dashboardState?.status || "failed";
@@ -2446,6 +2582,12 @@ export async function runLauncherCli(argv) {
2446
2582
  globalDashboard.status = "completed";
2447
2583
  recordGlobalDashboardEvent(globalDashboard, { message: "All selected waves completed." });
2448
2584
  writeDashboardProjections({ lanePaths, globalDashboard });
2585
+ writeLauncherRunProgress({
2586
+ phase: "completed",
2587
+ finalized: true,
2588
+ finalDisposition: "completed",
2589
+ exitCode: 0,
2590
+ });
2449
2591
  appendCoordination({
2450
2592
  event: "launcher_finish",
2451
2593
  waves: selectedWavesForCoordination,
@@ -2460,6 +2602,12 @@ export async function runLauncherCli(argv) {
2460
2602
  appendCoordination,
2461
2603
  error,
2462
2604
  );
2605
+ writeLauncherRunProgress({
2606
+ phase: "failed",
2607
+ finalized: true,
2608
+ finalDisposition: "failed",
2609
+ exitCode: Number.isInteger(error?.exitCode) ? error.exitCode : 1,
2610
+ });
2463
2611
  writeDashboardProjections({ lanePaths, globalDashboard });
2464
2612
  throw error;
2465
2613
  } finally {
@@ -2475,14 +2623,14 @@ export async function runLauncherCli(argv) {
2475
2623
  }
2476
2624
  if (options.cleanupSessions && globalDashboardTerminalEntry) {
2477
2625
  try {
2478
- killTmuxSessionIfExists(lanePaths.tmuxSocketName, globalDashboardTerminalEntry.sessionName);
2626
+ await killTmuxSessionIfExists(lanePaths.tmuxSocketName, globalDashboardTerminalEntry.sessionName);
2479
2627
  } catch {
2480
2628
  // no-op
2481
2629
  }
2482
2630
  }
2483
2631
  if (options.cleanupSessions && currentWaveDashboardTerminalEntry) {
2484
2632
  try {
2485
- killTmuxSessionIfExists(
2633
+ await killTmuxSessionIfExists(
2486
2634
  lanePaths.tmuxSocketName,
2487
2635
  currentWaveDashboardTerminalEntry.sessionName,
2488
2636
  );
@@ -54,6 +54,7 @@ export function buildSeedWaveLedger({
54
54
  contEvalAgentId = DEFAULT_CONT_EVAL_AGENT_ID,
55
55
  integrationAgentId = DEFAULT_INTEGRATION_AGENT_ID,
56
56
  documentationAgentId = DEFAULT_DOCUMENTATION_AGENT_ID,
57
+ securityRolePromptPath = null,
57
58
  }) {
58
59
  const tasks = [];
59
60
  for (const agent of wave.agents) {
@@ -69,7 +70,7 @@ export function buildSeedWaveLedger({
69
70
  ? "documentation"
70
71
  : isDesignAgent(agent)
71
72
  ? "design"
72
- : isSecurityReviewAgent(agent)
73
+ : isSecurityReviewAgent(agent, { securityRolePromptPath })
73
74
  ? "security"
74
75
  : "implementation";
75
76
  const runtime = agent.executorResolved
@@ -220,6 +221,7 @@ export function deriveWaveLedger({
220
221
  benchmarkCatalogPath = null,
221
222
  capabilityAssignments = [],
222
223
  dependencySnapshot = null,
224
+ securityRolePromptPath = null,
223
225
  }) {
224
226
  const seed = buildSeedWaveLedger({
225
227
  lane,
@@ -228,6 +230,7 @@ export function deriveWaveLedger({
228
230
  contEvalAgentId,
229
231
  integrationAgentId,
230
232
  documentationAgentId,
233
+ securityRolePromptPath,
231
234
  });
232
235
  const primaryTasks = seed.tasks.map((task) => {
233
236
  const agent = wave.agents.find((item) => item.agentId === task.owner);
@@ -343,7 +346,9 @@ export function deriveWaveLedger({
343
346
  const docAgent = wave.agents.find((agent) => agent.agentId === documentationAgentId);
344
347
  const contEvalAgent = wave.agents.find((agent) => agent.agentId === contEvalAgentId);
345
348
  const contQaAgent = wave.agents.find((agent) => agent.agentId === contQaAgentId);
346
- const securityAgents = (wave.agents || []).filter((agent) => isSecurityReviewAgent(agent));
349
+ const securityAgents = (wave.agents || []).filter((agent) =>
350
+ isSecurityReviewAgent(agent, { securityRolePromptPath }),
351
+ );
347
352
  const contEvalValidation = (() => {
348
353
  if (!contEvalAgent) {
349
354
  return { ok: true };
@@ -2625,6 +2625,7 @@ async function runAgenticDraftFlow(options = {}) {
2625
2625
  request,
2626
2626
  });
2627
2627
  const plannerContext7Prefetch = await prefetchContext7ForSelection(plannerContext7Selection, {
2628
+ lanePaths,
2628
2629
  cacheDir: lanePaths.context7CacheDir,
2629
2630
  });
2630
2631
  const promptText = buildPlannerPromptText({