@chllming/wave-orchestration 0.8.5 → 0.8.7

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 (58) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +14 -9
  3. package/docs/README.md +3 -1
  4. package/docs/context7/bundles.json +19 -20
  5. package/docs/context7/planner-agent/README.md +4 -1
  6. package/docs/guides/author-and-run-waves.md +4 -1
  7. package/docs/guides/planner.md +3 -1
  8. package/docs/guides/signal-wrappers.md +165 -0
  9. package/docs/guides/terminal-surfaces.md +15 -0
  10. package/docs/plans/context7-wave-orchestrator.md +24 -7
  11. package/docs/plans/current-state.md +7 -3
  12. package/docs/plans/end-state-architecture.md +16 -4
  13. package/docs/plans/examples/wave-example-design-handoff.md +1 -1
  14. package/docs/plans/examples/wave-example-live-proof.md +1 -1
  15. package/docs/plans/migration.md +179 -72
  16. package/docs/plans/wave-orchestrator.md +11 -5
  17. package/docs/reference/cli-reference.md +21 -4
  18. package/docs/reference/coordination-and-closure.md +26 -5
  19. package/docs/reference/live-proof-waves.md +9 -0
  20. package/docs/reference/npmjs-trusted-publishing.md +2 -2
  21. package/docs/reference/runtime-config/README.md +9 -3
  22. package/docs/reference/sample-waves.md +5 -5
  23. package/docs/reference/skills.md +9 -1
  24. package/docs/reference/wave-control.md +18 -0
  25. package/docs/reference/wave-planning-lessons.md +7 -1
  26. package/docs/research/coordination-failure-review.md +6 -6
  27. package/package.json +1 -1
  28. package/releases/manifest.json +38 -0
  29. package/scripts/context7-api-check.sh +57 -13
  30. package/scripts/wave-orchestrator/agent-state.mjs +42 -0
  31. package/scripts/wave-orchestrator/autonomous.mjs +42 -6
  32. package/scripts/wave-orchestrator/clarification-triage.mjs +4 -3
  33. package/scripts/wave-orchestrator/control-cli.mjs +145 -11
  34. package/scripts/wave-orchestrator/control-plane.mjs +12 -1
  35. package/scripts/wave-orchestrator/coordination-store.mjs +124 -4
  36. package/scripts/wave-orchestrator/coordination.mjs +35 -0
  37. package/scripts/wave-orchestrator/executors.mjs +11 -6
  38. package/scripts/wave-orchestrator/gate-engine.mjs +5 -5
  39. package/scripts/wave-orchestrator/install.mjs +2 -0
  40. package/scripts/wave-orchestrator/launcher-runtime.mjs +12 -1
  41. package/scripts/wave-orchestrator/launcher.mjs +236 -0
  42. package/scripts/wave-orchestrator/ledger.mjs +14 -12
  43. package/scripts/wave-orchestrator/reducer-snapshot.mjs +8 -6
  44. package/scripts/wave-orchestrator/retry-engine.mjs +19 -11
  45. package/scripts/wave-orchestrator/routing-state.mjs +50 -3
  46. package/scripts/wave-orchestrator/session-supervisor.mjs +119 -10
  47. package/scripts/wave-orchestrator/shared.mjs +1 -0
  48. package/scripts/wave-orchestrator/signals.mjs +681 -0
  49. package/scripts/wave-orchestrator/task-entity.mjs +4 -4
  50. package/scripts/wave-orchestrator/terminals.mjs +14 -14
  51. package/scripts/wave-orchestrator/wave-control-schema.mjs +2 -0
  52. package/scripts/wave-orchestrator/wave-files.mjs +15 -21
  53. package/scripts/wave-orchestrator/wave-state-reducer.mjs +72 -5
  54. package/scripts/wave-status.sh +200 -0
  55. package/scripts/wave-watch.sh +200 -0
  56. package/skills/README.md +3 -0
  57. package/skills/signal-hygiene/SKILL.md +51 -0
  58. package/skills/signal-hygiene/skill.json +20 -0
@@ -17,8 +17,8 @@ import {
17
17
  isSecurityReviewAgent,
18
18
  } from "./role-helpers.mjs";
19
19
  import {
20
+ coordinationRecordBlocksWave,
20
21
  isOpenCoordinationStatus,
21
- openClarificationLinkedRequests,
22
22
  } from "./coordination-store.mjs";
23
23
 
24
24
  export const TASK_TYPES = new Set([
@@ -647,7 +647,7 @@ export function buildTasksFromCoordinationState(coordinationState, feedbackReque
647
647
  const tasks = [];
648
648
 
649
649
  for (const record of coordinationState.clarifications || []) {
650
- if (!isOpenCoordinationStatus(record.status)) {
650
+ if (!coordinationRecordBlocksWave(record)) {
651
651
  continue;
652
652
  }
653
653
  const waveNumber = Number.isFinite(record.wave) ? record.wave : 0;
@@ -683,7 +683,7 @@ export function buildTasksFromCoordinationState(coordinationState, feedbackReque
683
683
  }
684
684
 
685
685
  for (const record of coordinationState.humanFeedback || []) {
686
- if (!isOpenCoordinationStatus(record.status)) {
686
+ if (!coordinationRecordBlocksWave(record)) {
687
687
  continue;
688
688
  }
689
689
  const waveNumber = Number.isFinite(record.wave) ? record.wave : 0;
@@ -719,7 +719,7 @@ export function buildTasksFromCoordinationState(coordinationState, feedbackReque
719
719
  }
720
720
 
721
721
  for (const record of coordinationState.humanEscalations || []) {
722
- if (!isOpenCoordinationStatus(record.status)) {
722
+ if (!coordinationRecordBlocksWave(record)) {
723
723
  continue;
724
724
  }
725
725
  const waveNumber = Number.isFinite(record.wave) ? record.wave : 0;
@@ -84,6 +84,14 @@ function extractTmuxSessionName(command, socketName) {
84
84
  return sessionName || null;
85
85
  }
86
86
 
87
+ function sanitizeTmuxSessionName(value) {
88
+ return String(value || "").replace(/[^a-zA-Z0-9:_-]/g, "_");
89
+ }
90
+
91
+ export function createWaveAgentSessionName(lanePaths, wave, agentSlug) {
92
+ return sanitizeTmuxSessionName(`${lanePaths.tmuxSessionPrefix}${wave}_${agentSlug}`);
93
+ }
94
+
87
95
  export function createTemporaryTerminalEntries(
88
96
  lanePaths,
89
97
  wave,
@@ -93,10 +101,7 @@ export function createTemporaryTerminalEntries(
93
101
  ) {
94
102
  const agentEntries = agents.map((agent) => {
95
103
  const terminalName = `${lanePaths.terminalNamePrefix}${wave}-${agent.slug}`;
96
- const sessionName = `${lanePaths.tmuxSessionPrefix}${wave}_${agent.slug}_${runTag}`.replace(
97
- /[^a-zA-Z0-9_-]/g,
98
- "_",
99
- );
104
+ const sessionName = createWaveAgentSessionName(lanePaths, wave, agent.slug);
100
105
  return {
101
106
  terminalName,
102
107
  sessionName,
@@ -111,9 +116,8 @@ export function createTemporaryTerminalEntries(
111
116
  if (!includeDashboard) {
112
117
  return agentEntries;
113
118
  }
114
- const dashboardSessionName = `${lanePaths.tmuxDashboardSessionPrefix}${wave}_${runTag}`.replace(
115
- /[^a-zA-Z0-9_-]/g,
116
- "_",
119
+ const dashboardSessionName = sanitizeTmuxSessionName(
120
+ `${lanePaths.tmuxDashboardSessionPrefix}${wave}`,
117
121
  );
118
122
  agentEntries.push({
119
123
  terminalName: `${lanePaths.dashboardTerminalNamePrefix}${wave}`,
@@ -129,9 +133,8 @@ export function createTemporaryTerminalEntries(
129
133
  }
130
134
 
131
135
  export function createGlobalDashboardTerminalEntry(lanePaths, runTag) {
132
- const sessionName = `${lanePaths.tmuxGlobalDashboardSessionPrefix}_current`.replace(
133
- /[^a-zA-Z0-9:_-]/g,
134
- "_",
136
+ const sessionName = sanitizeTmuxSessionName(
137
+ `${lanePaths.tmuxGlobalDashboardSessionPrefix}_current`,
135
138
  );
136
139
  return {
137
140
  terminalName: lanePaths.globalDashboardTerminalName,
@@ -146,10 +149,7 @@ export function createGlobalDashboardTerminalEntry(lanePaths, runTag) {
146
149
  }
147
150
 
148
151
  export function createCurrentWaveDashboardTerminalEntry(lanePaths) {
149
- const sessionName = `${lanePaths.tmuxDashboardSessionPrefix}_current`.replace(
150
- /[^a-zA-Z0-9:_-]/g,
151
- "_",
152
- );
152
+ const sessionName = sanitizeTmuxSessionName(`${lanePaths.tmuxDashboardSessionPrefix}_current`);
153
153
  const terminalName = currentWaveDashboardTerminalName(lanePaths);
154
154
  return {
155
155
  terminalName,
@@ -13,6 +13,8 @@ export const WAVE_CONTROL_ENTITY_TYPES = new Set([
13
13
  "fact",
14
14
  "wave_run",
15
15
  "agent_run",
16
+ "wave_signal",
17
+ "agent_signal",
16
18
  "coordination_record",
17
19
  "gate",
18
20
  "artifact",
@@ -36,7 +36,7 @@ import {
36
36
  } from "./shared.mjs";
37
37
  import { normalizeContext7Config, hashAgentPromptFingerprint } from "./context7.mjs";
38
38
  import {
39
- isOpenCoordinationStatus,
39
+ coordinationRecordBlocksWave,
40
40
  openClarificationLinkedRequests,
41
41
  readMaterializedCoordinationState,
42
42
  } from "./coordination-store.mjs";
@@ -2154,23 +2154,19 @@ export function resolveAgentExecutor(agent, options = {}) {
2154
2154
  ? "claude.maxTurns"
2155
2155
  : profile?.claude?.maxTurns !== null && profile?.claude?.maxTurns !== undefined
2156
2156
  ? "claude.maxTurns"
2157
- : runtimeBudget.turns !== null
2158
- ? "budget.turns"
2159
- : laneProfile.executors.claude.maxTurns !== null &&
2160
- laneProfile.executors.claude.maxTurns !== undefined
2161
- ? "claude.maxTurns"
2162
- : null;
2157
+ : laneProfile.executors.claude.maxTurns !== null &&
2158
+ laneProfile.executors.claude.maxTurns !== undefined
2159
+ ? "claude.maxTurns"
2160
+ : null;
2163
2161
  const opencodeStepsSource =
2164
2162
  executorConfig?.opencode?.steps !== null && executorConfig?.opencode?.steps !== undefined
2165
2163
  ? "opencode.steps"
2166
2164
  : profile?.opencode?.steps !== null && profile?.opencode?.steps !== undefined
2167
2165
  ? "opencode.steps"
2168
- : runtimeBudget.turns !== null
2169
- ? "budget.turns"
2170
- : laneProfile.executors.opencode.steps !== null &&
2171
- laneProfile.executors.opencode.steps !== undefined
2172
- ? "opencode.steps"
2173
- : null;
2166
+ : laneProfile.executors.opencode.steps !== null &&
2167
+ laneProfile.executors.opencode.steps !== undefined
2168
+ ? "opencode.steps"
2169
+ : null;
2174
2170
  return {
2175
2171
  id: executorId,
2176
2172
  initialExecutorId: executorId,
@@ -2253,7 +2249,6 @@ export function resolveAgentExecutor(agent, options = {}) {
2253
2249
  maxTurns:
2254
2250
  executorConfig?.claude?.maxTurns ??
2255
2251
  profile?.claude?.maxTurns ??
2256
- runtimeBudget.turns ??
2257
2252
  laneProfile.executors.claude.maxTurns,
2258
2253
  maxTurnsSource: claudeMaxTurnsSource,
2259
2254
  },
@@ -2271,7 +2266,6 @@ export function resolveAgentExecutor(agent, options = {}) {
2271
2266
  steps:
2272
2267
  executorConfig?.opencode?.steps ??
2273
2268
  profile?.opencode?.steps ??
2274
- runtimeBudget.turns ??
2275
2269
  laneProfile.executors.opencode.steps,
2276
2270
  stepsSource: opencodeStepsSource,
2277
2271
  },
@@ -3114,7 +3108,7 @@ function analyzeWaveCompletionFromStatusFiles(wave, statusDir, options = {}) {
3114
3108
  coordinationLogPath,
3115
3109
  );
3116
3110
  const openClarificationIds = coordinationState.clarifications
3117
- .filter((record) => isOpenCoordinationStatus(record.status))
3111
+ .filter((record) => coordinationRecordBlocksWave(record))
3118
3112
  .map((record) => record.id);
3119
3113
  if (openClarificationIds.length > 0) {
3120
3114
  pushWaveCompletionReason(
@@ -3123,9 +3117,9 @@ function analyzeWaveCompletionFromStatusFiles(wave, statusDir, options = {}) {
3123
3117
  `Open clarification records: ${openClarificationIds.join(", ")}.`,
3124
3118
  );
3125
3119
  }
3126
- const openClarificationRequestIds = openClarificationLinkedRequests(coordinationState).map(
3127
- (record) => record.id,
3128
- );
3120
+ const openClarificationRequestIds = openClarificationLinkedRequests(coordinationState)
3121
+ .filter((record) => coordinationRecordBlocksWave(record))
3122
+ .map((record) => record.id);
3129
3123
  if (openClarificationRequestIds.length > 0) {
3130
3124
  pushWaveCompletionReason(
3131
3125
  reasons,
@@ -3134,7 +3128,7 @@ function analyzeWaveCompletionFromStatusFiles(wave, statusDir, options = {}) {
3134
3128
  );
3135
3129
  }
3136
3130
  const openHumanEscalationIds = coordinationState.humanEscalations
3137
- .filter((record) => isOpenCoordinationStatus(record.status))
3131
+ .filter((record) => coordinationRecordBlocksWave(record))
3138
3132
  .map((record) => record.id);
3139
3133
  if (openHumanEscalationIds.length > 0) {
3140
3134
  pushWaveCompletionReason(
@@ -3144,7 +3138,7 @@ function analyzeWaveCompletionFromStatusFiles(wave, statusDir, options = {}) {
3144
3138
  );
3145
3139
  }
3146
3140
  const openHumanFeedbackIds = coordinationState.humanFeedback
3147
- .filter((record) => isOpenCoordinationStatus(record.status))
3141
+ .filter((record) => coordinationRecordBlocksWave(record))
3148
3142
  .map((record) => record.id);
3149
3143
  if (openHumanFeedbackIds.length > 0) {
3150
3144
  pushWaveCompletionReason(
@@ -1,6 +1,9 @@
1
1
  import { materializeControlPlaneState } from "./control-plane.mjs";
2
2
  import {
3
3
  buildCoordinationResponseMetrics,
4
+ coordinationBlockerSeverity,
5
+ coordinationRecordBlocksWave,
6
+ coordinationRecordIsHardBlocker,
4
7
  isOpenCoordinationStatus,
5
8
  materializeCoordinationState,
6
9
  openClarificationLinkedRequests,
@@ -135,18 +138,18 @@ function derivePhase({
135
138
  clarificationBarrier,
136
139
  }) {
137
140
  const blockers = (coordinationState?.blockers || []).filter(
138
- (record) =>
139
- isOpenCoordinationStatus(record.status) &&
140
- ["high", "urgent"].includes(record.priority),
141
+ (record) => coordinationRecordIsHardBlocker(record),
141
142
  );
142
143
  if (blockers.length > 0) {
143
144
  return "blocked";
144
145
  }
145
146
 
146
147
  const openClarifications = (coordinationState?.clarifications || []).filter(
147
- (record) => isOpenCoordinationStatus(record.status),
148
+ (record) => coordinationRecordBlocksWave(record),
149
+ );
150
+ const openClarificationRequests = openClarificationLinkedRequests(coordinationState).filter(
151
+ (record) => coordinationRecordBlocksWave(record),
148
152
  );
149
- const openClarificationRequests = openClarificationLinkedRequests(coordinationState);
150
153
  if (openClarifications.length > 0 || openClarificationRequests.length > 0) {
151
154
  return "clarifying";
152
155
  }
@@ -228,6 +231,51 @@ function derivePhase({
228
231
  return "running";
229
232
  }
230
233
 
234
+ function gateBlockerMetadata(gateName, gate) {
235
+ if (!gate || gate.ok !== false) {
236
+ return {
237
+ blocking: false,
238
+ blockerSeverity: "advisory",
239
+ };
240
+ }
241
+ if (gateName === "componentGate" && gate.statusCode === "shared-component-sibling-pending") {
242
+ return {
243
+ blocking: true,
244
+ blockerSeverity: "soft",
245
+ };
246
+ }
247
+ if (
248
+ ["implementationGate", "contEvalGate"].includes(gateName)
249
+ ) {
250
+ return {
251
+ blocking: true,
252
+ blockerSeverity: "proof-critical",
253
+ };
254
+ }
255
+ if (
256
+ [
257
+ "designGate",
258
+ "integrationBarrier",
259
+ "documentationGate",
260
+ "contQaGate",
261
+ "clarificationBarrier",
262
+ "helperAssignmentBarrier",
263
+ "dependencyBarrier",
264
+ "componentMatrixGate",
265
+ "componentGate",
266
+ ].includes(gateName)
267
+ ) {
268
+ return {
269
+ blocking: true,
270
+ blockerSeverity: "closure-critical",
271
+ };
272
+ }
273
+ return {
274
+ blocking: true,
275
+ blockerSeverity: "hard",
276
+ };
277
+ }
278
+
231
279
  /**
232
280
  * Map waveState from phase for end-state output.
233
281
  */
@@ -338,6 +386,8 @@ function deriveOpenBlockers(coordinationState, gateSnapshot) {
338
386
  if (!isOpenCoordinationStatus(record.status)) {
339
387
  continue;
340
388
  }
389
+ const blocking = coordinationRecordBlocksWave(record);
390
+ const blockerSeverity = coordinationBlockerSeverity(record);
341
391
  blockers.push({
342
392
  kind: "coordination-blocker",
343
393
  id: record.id,
@@ -348,6 +398,8 @@ function deriveOpenBlockers(coordinationState, gateSnapshot) {
348
398
  ? record.targets
349
399
  : (record.agentId ? [record.agentId] : []),
350
400
  resolutionHint: record.resolutionHint || null,
401
+ blocking,
402
+ blockerSeverity,
351
403
  });
352
404
  }
353
405
 
@@ -355,6 +407,8 @@ function deriveOpenBlockers(coordinationState, gateSnapshot) {
355
407
  if (!isOpenCoordinationStatus(record.status)) {
356
408
  continue;
357
409
  }
410
+ const blocking = coordinationRecordBlocksWave(record);
411
+ const blockerSeverity = coordinationBlockerSeverity(record);
358
412
  blockers.push({
359
413
  kind: "clarification",
360
414
  id: record.id,
@@ -365,6 +419,8 @@ function deriveOpenBlockers(coordinationState, gateSnapshot) {
365
419
  ? record.targets
366
420
  : (record.agentId ? [record.agentId] : []),
367
421
  resolutionHint: "Resolve clarification before proceeding.",
422
+ blocking,
423
+ blockerSeverity,
368
424
  });
369
425
  }
370
426
 
@@ -372,6 +428,8 @@ function deriveOpenBlockers(coordinationState, gateSnapshot) {
372
428
  if (!isOpenCoordinationStatus(record.status)) {
373
429
  continue;
374
430
  }
431
+ const blocking = coordinationRecordBlocksWave(record);
432
+ const blockerSeverity = coordinationBlockerSeverity(record);
375
433
  blockers.push({
376
434
  kind: "human-escalation",
377
435
  id: record.id,
@@ -382,6 +440,8 @@ function deriveOpenBlockers(coordinationState, gateSnapshot) {
382
440
  ? record.targets
383
441
  : (record.agentId ? [record.agentId] : []),
384
442
  resolutionHint: "Human intervention required.",
443
+ blocking,
444
+ blockerSeverity,
385
445
  });
386
446
  }
387
447
 
@@ -389,6 +449,8 @@ function deriveOpenBlockers(coordinationState, gateSnapshot) {
389
449
  if (!isOpenCoordinationStatus(record.status)) {
390
450
  continue;
391
451
  }
452
+ const blocking = coordinationRecordBlocksWave(record);
453
+ const blockerSeverity = coordinationBlockerSeverity(record);
392
454
  blockers.push({
393
455
  kind: "human-feedback",
394
456
  id: record.id,
@@ -399,6 +461,8 @@ function deriveOpenBlockers(coordinationState, gateSnapshot) {
399
461
  ? record.targets
400
462
  : (record.agentId ? [record.agentId] : []),
401
463
  resolutionHint: "Awaiting human feedback.",
464
+ blocking,
465
+ blockerSeverity,
402
466
  });
403
467
  }
404
468
 
@@ -407,12 +471,15 @@ function deriveOpenBlockers(coordinationState, gateSnapshot) {
407
471
  if (gateName === "overall" || !gate || gate.ok !== false) {
408
472
  continue;
409
473
  }
474
+ const metadata = gateBlockerMetadata(gateName, gate);
410
475
  blockers.push({
411
476
  kind: "gate-failure",
412
477
  id: gateName,
413
478
  detail: gate.detail || gate.statusCode || "",
414
479
  blockedAgentIds: gate.agentId ? [gate.agentId] : [],
415
480
  resolutionHint: `Gate ${gateName} must pass before wave closure.`,
481
+ blocking: metadata.blocking,
482
+ blockerSeverity: metadata.blockerSeverity,
416
483
  });
417
484
  }
418
485
  }
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ lane="main"
5
+ wave=""
6
+ agent=""
7
+ run_id=""
8
+ dry_run="0"
9
+ json_output="0"
10
+
11
+ usage() {
12
+ cat <<'EOF'
13
+ Usage:
14
+ scripts/wave-status.sh [--lane <lane>] [--wave <n>] [--agent <id>] [--run <id>] [--dry-run] [--json]
15
+
16
+ Exit codes:
17
+ 0 completed
18
+ 10 waiting or running
19
+ 20 input required
20
+ 40 failed
21
+ EOF
22
+ }
23
+
24
+ while [ "$#" -gt 0 ]; do
25
+ case "$1" in
26
+ --lane)
27
+ lane="${2:-}"
28
+ shift 2
29
+ ;;
30
+ --wave)
31
+ wave="${2:-}"
32
+ shift 2
33
+ ;;
34
+ --agent)
35
+ agent="${2:-}"
36
+ shift 2
37
+ ;;
38
+ --run)
39
+ run_id="${2:-}"
40
+ shift 2
41
+ ;;
42
+ --dry-run)
43
+ dry_run="1"
44
+ shift
45
+ ;;
46
+ --json)
47
+ json_output="1"
48
+ shift
49
+ ;;
50
+ --help|-h)
51
+ usage
52
+ exit 0
53
+ ;;
54
+ *)
55
+ echo "Unknown argument: $1" >&2
56
+ usage >&2
57
+ exit 2
58
+ ;;
59
+ esac
60
+ done
61
+
62
+ run_wave_cli() {
63
+ if [ -n "${WAVE_WRAPPER_ENTRY:-}" ]; then
64
+ node "$WAVE_WRAPPER_ENTRY" "$@"
65
+ return
66
+ fi
67
+ if [ -f "scripts/wave.mjs" ]; then
68
+ node "scripts/wave.mjs" "$@"
69
+ return
70
+ fi
71
+ if [ -f "node_modules/@chllming/wave-orchestration/scripts/wave.mjs" ]; then
72
+ node "node_modules/@chllming/wave-orchestration/scripts/wave.mjs" "$@"
73
+ return
74
+ fi
75
+ if command -v pnpm >/dev/null 2>&1; then
76
+ pnpm exec wave "$@"
77
+ return
78
+ fi
79
+ echo "Unable to locate Wave CLI. Set WAVE_WRAPPER_ENTRY or install the package locally." >&2
80
+ exit 2
81
+ }
82
+
83
+ infer_wave() {
84
+ node - "$lane" "$run_id" "$dry_run" <<'NODE'
85
+ const fs = require("node:fs");
86
+ const path = require("node:path");
87
+
88
+ const lane = process.argv[2] || "main";
89
+ const runId = process.argv[3] || "";
90
+ const dryRun = process.argv[4] === "1";
91
+ const stateDir = runId
92
+ ? path.join(process.cwd(), ".tmp", `${lane}-wave-launcher`, "adhoc", runId, dryRun ? "dry-run" : "")
93
+ : path.join(process.cwd(), ".tmp", `${lane}-wave-launcher`, dryRun ? "dry-run" : "");
94
+ const runStatePath = path.join(stateDir, "run-state.json");
95
+ let payload = null;
96
+ try {
97
+ payload = JSON.parse(fs.readFileSync(runStatePath, "utf8"));
98
+ } catch {
99
+ payload = null;
100
+ }
101
+ const waves = Object.values(payload?.waves || {})
102
+ .filter((entry) => entry && typeof entry === "object")
103
+ .map((entry) => ({
104
+ wave: Number.parseInt(String(entry.wave ?? ""), 10),
105
+ state: String(entry.currentState || "").trim().toLowerCase(),
106
+ }))
107
+ .filter((entry) => Number.isFinite(entry.wave))
108
+ .sort((left, right) => left.wave - right.wave);
109
+ const active = waves.findLast(
110
+ (entry) => !["completed", "failed", "timed_out", "timed-out"].includes(entry.state),
111
+ );
112
+ if (active) {
113
+ process.stdout.write(String(active.wave));
114
+ process.exit(0);
115
+ }
116
+ const completed = Array.isArray(payload?.completedWaves)
117
+ ? payload.completedWaves
118
+ .map((value) => Number.parseInt(String(value), 10))
119
+ .filter((value) => Number.isFinite(value))
120
+ .sort((left, right) => left - right)
121
+ : [];
122
+ process.stdout.write(String(completed.at(-1) ?? 0));
123
+ NODE
124
+ }
125
+
126
+ if [ -z "$wave" ]; then
127
+ wave="$(infer_wave)"
128
+ fi
129
+
130
+ status_args=(control status --lane "$lane" --wave "$wave" --json)
131
+ if [ -n "$agent" ]; then
132
+ status_args+=(--agent "$agent")
133
+ fi
134
+ if [ -n "$run_id" ]; then
135
+ status_args+=(--run "$run_id")
136
+ fi
137
+ if [ "$dry_run" = "1" ]; then
138
+ status_args+=(--dry-run)
139
+ fi
140
+
141
+ payload="$(run_wave_cli "${status_args[@]}")"
142
+
143
+ if [ "$json_output" = "1" ]; then
144
+ printf '%s\n' "$payload"
145
+ exit 0
146
+ fi
147
+
148
+ PAYLOAD="$payload" node - "$lane" "$wave" "$agent" <<'NODE'
149
+ const fs = require("node:fs");
150
+
151
+ const lane = process.argv[2] || "main";
152
+ const wave = Number.parseInt(String(process.argv[3] || "0"), 10) || 0;
153
+ const agentId = String(process.argv[4] || "").trim();
154
+ const payload = JSON.parse(process.env.PAYLOAD || "{}");
155
+ const signals = payload?.signals || {};
156
+ const snapshot = agentId
157
+ ? (Array.isArray(signals.agents) ? signals.agents.find((entry) => entry.agentId === agentId) : null)
158
+ : signals.wave;
159
+ const effective = snapshot || {
160
+ signal: payload?.blockingEdge?.kind === "human-input" ? "feedback-requested" : "waiting",
161
+ lane,
162
+ wave,
163
+ phase: payload?.phase || "unknown",
164
+ status: payload?.blockingEdge ? "blocked" : "running",
165
+ blocking: payload?.blockingEdge || null,
166
+ attempt: payload?.activeAttempt?.attemptNumber || 0,
167
+ targetAgentIds: agentId ? [agentId] : [],
168
+ shouldWake: agentId ? true : null,
169
+ version: 0,
170
+ };
171
+ const targetKey = agentId ? "agent" : "agents";
172
+ const targetValue = agentId || (effective.targetAgentIds || []).join(",") || "none";
173
+ const blocking = effective?.blocking?.kind || "none";
174
+ const shouldWake =
175
+ typeof effective.shouldWake === "boolean" ? (effective.shouldWake ? "yes" : "no") : "n/a";
176
+ console.log(
177
+ [
178
+ `signal=${effective.signal || "waiting"}`,
179
+ `lane=${lane}`,
180
+ `wave=${wave}`,
181
+ `phase=${effective.phase || "unknown"}`,
182
+ `status=${effective.status || "running"}`,
183
+ `blocking=${blocking}`,
184
+ `attempt=${effective.attempt || 0}`,
185
+ `${targetKey}=${targetValue}`,
186
+ `version=${effective.version || 0}`,
187
+ `should_wake=${shouldWake}`,
188
+ ].join(" "),
189
+ );
190
+ if (String(effective.signal || "").trim().toLowerCase() === "completed") {
191
+ process.exit(0);
192
+ }
193
+ if (String(effective.signal || "").trim().toLowerCase() === "failed") {
194
+ process.exit(40);
195
+ }
196
+ if (String(effective.signal || "").trim().toLowerCase() === "feedback-requested") {
197
+ process.exit(20);
198
+ }
199
+ process.exit(10);
200
+ NODE