@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
@@ -10,7 +10,7 @@ import {
10
10
  readWaveDependencyBarrier,
11
11
  } from "./gate-engine.mjs";
12
12
  import {
13
- isOpenCoordinationStatus,
13
+ coordinationRecordBlocksWave,
14
14
  openClarificationLinkedRequests,
15
15
  } from "./coordination-store.mjs";
16
16
  import {
@@ -197,6 +197,7 @@ export function relaunchReasonBuckets(runs, failures, derivedState) {
197
197
  const selectedAgentIds = new Set((runs || []).map((run) => run.agent.agentId));
198
198
  return {
199
199
  clarification: openClarificationLinkedRequests(derivedState?.coordinationState)
200
+ .filter((record) => coordinationRecordBlocksWave(record))
200
201
  .flatMap((record) => record.targets || [])
201
202
  .some((target) => {
202
203
  const agentId = String(target || "").startsWith("agent:")
@@ -212,7 +213,7 @@ export function relaunchReasonBuckets(runs, failures, derivedState) {
212
213
  )),
213
214
  blocker: (derivedState?.coordinationState?.blockers || []).some(
214
215
  (record) =>
215
- isOpenCoordinationStatus(record.status) &&
216
+ coordinationRecordBlocksWave(record) &&
216
217
  (selectedAgentIds.has(record.agentId) ||
217
218
  (record.targets || []).some((target) => {
218
219
  const agentId = String(target || "").startsWith("agent:")
@@ -239,7 +240,10 @@ const HUMAN_INPUT_BLOCKER_KINDS = new Set([
239
240
  ]);
240
241
 
241
242
  function isHumanInputBlocker(blocker) {
242
- return HUMAN_INPUT_BLOCKER_KINDS.has(String(blocker?.kind || "").trim().toLowerCase());
243
+ return (
244
+ blocker?.blocking !== false &&
245
+ HUMAN_INPUT_BLOCKER_KINDS.has(String(blocker?.kind || "").trim().toLowerCase())
246
+ );
243
247
  }
244
248
 
245
249
  function normalizeRetryTargets(retryTargetSet) {
@@ -672,10 +676,10 @@ function resolveRelaunchRunsLegacy(agentRuns, failures, derivedState, lanePaths,
672
676
  const roleBindings = resolveWaveRoleBindings(waveDefinition, lanePaths, waveDefinition?.agents);
673
677
  const runsByAgentId = new Map(agentRuns.map((run) => [run.agent.agentId, run]));
674
678
  const pendingFeedback = (derivedState?.coordinationState?.humanFeedback || []).filter((record) =>
675
- isOpenCoordinationStatus(record.status),
679
+ coordinationRecordBlocksWave(record),
676
680
  );
677
681
  const pendingHumanEscalations = (derivedState?.coordinationState?.humanEscalations || []).filter(
678
- (record) => isOpenCoordinationStatus(record.status),
682
+ (record) => coordinationRecordBlocksWave(record),
679
683
  );
680
684
  if (pendingFeedback.length > 0 || pendingHumanEscalations.length > 0) {
681
685
  return { runs: [], barrier: null };
@@ -693,7 +697,9 @@ function resolveRelaunchRunsLegacy(agentRuns, failures, derivedState, lanePaths,
693
697
  return { runs: [], barrier: retryBarrier };
694
698
  }
695
699
  const clarificationTargets = new Set();
696
- for (const record of openClarificationLinkedRequests(derivedState?.coordinationState)) {
700
+ for (const record of openClarificationLinkedRequests(derivedState?.coordinationState).filter((entry) =>
701
+ coordinationRecordBlocksWave(entry),
702
+ )) {
697
703
  for (const target of record.targets || []) {
698
704
  if (String(target).startsWith("agent:")) {
699
705
  clarificationTargets.add(String(target).slice("agent:".length));
@@ -782,7 +788,7 @@ function resolveRelaunchRunsLegacy(agentRuns, failures, derivedState, lanePaths,
782
788
  }
783
789
  const blockerAgentIds = new Set();
784
790
  for (const record of derivedState?.coordinationState?.blockers || []) {
785
- if (!isOpenCoordinationStatus(record.status)) {
791
+ if (!coordinationRecordBlocksWave(record)) {
786
792
  continue;
787
793
  }
788
794
  blockerAgentIds.add(record.agentId);
@@ -867,10 +873,10 @@ function resolveRelaunchRunsFromWaveState(
867
873
  ) {
868
874
  const roleBindings = resolveWaveRoleBindings(waveDefinition, lanePaths, waveDefinition?.agents);
869
875
  const pendingFeedback = (waveState?.coordinationState?.humanFeedback || []).filter((record) =>
870
- isOpenCoordinationStatus(record.status),
876
+ coordinationRecordBlocksWave(record),
871
877
  );
872
878
  const pendingHumanEscalations = (waveState?.coordinationState?.humanEscalations || []).filter(
873
- (record) => isOpenCoordinationStatus(record.status),
879
+ (record) => coordinationRecordBlocksWave(record),
874
880
  );
875
881
  if (pendingFeedback.length > 0 || pendingHumanEscalations.length > 0) {
876
882
  return { runs: [], barrier: null };
@@ -890,7 +896,9 @@ function resolveRelaunchRunsFromWaveState(
890
896
  }
891
897
 
892
898
  const clarificationTargets = new Set();
893
- for (const record of openClarificationLinkedRequests(waveState?.coordinationState)) {
899
+ for (const record of openClarificationLinkedRequests(waveState?.coordinationState).filter((entry) =>
900
+ coordinationRecordBlocksWave(entry),
901
+ )) {
894
902
  for (const target of record.targets || []) {
895
903
  if (String(target).startsWith("agent:")) {
896
904
  clarificationTargets.add(String(target).slice("agent:".length));
@@ -967,7 +975,7 @@ function resolveRelaunchRunsFromWaveState(
967
975
 
968
976
  const blockerAgentIds = new Set();
969
977
  for (const record of waveState?.coordinationState?.blockers || []) {
970
- if (!isOpenCoordinationStatus(record.status)) {
978
+ if (!coordinationRecordBlocksWave(record)) {
971
979
  continue;
972
980
  }
973
981
  blockerAgentIds.add(record.agentId);
@@ -2,6 +2,8 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import {
4
4
  appendCoordinationRecord,
5
+ coordinationBlockerSeverity,
6
+ coordinationRecordBlocksWave,
5
7
  isOpenCoordinationStatus,
6
8
  materializeCoordinationState,
7
9
  readCoordinationLog,
@@ -29,6 +31,32 @@ function openTaskCountForAgent(ledger, agentId) {
29
31
  ).length;
30
32
  }
31
33
 
34
+ function completedTaskCountForAgent(ledger, agentId) {
35
+ return (ledger?.tasks || []).filter(
36
+ (task) => task.owner === agentId && ["done", "closed", "resolved"].includes(task.state),
37
+ ).length;
38
+ }
39
+
40
+ function completedCapabilityCountForAgent(ledger, agentId, capability) {
41
+ const normalizedCapability = normalizeCapability(capability);
42
+ if (!normalizedCapability) {
43
+ return 0;
44
+ }
45
+ const taskCount = (ledger?.tasks || []).filter(
46
+ (task) =>
47
+ task.owner === agentId &&
48
+ ["done", "closed", "resolved"].includes(task.state) &&
49
+ normalizeCapability(task.capability) === normalizedCapability,
50
+ ).length;
51
+ const assignmentCount = (ledger?.capabilityAssignments || []).filter(
52
+ (assignment) =>
53
+ assignment.assignedAgentId === agentId &&
54
+ ["done", "closed", "resolved"].includes(String(assignment.state || "").trim().toLowerCase()) &&
55
+ normalizeCapability(assignment.capability) === normalizedCapability,
56
+ ).length;
57
+ return taskCount + assignmentCount;
58
+ }
59
+
32
60
  function resolveTargetAssignment(target, agents, ledger, capabilityRouting = {}) {
33
61
  const normalizedTarget = String(target || "").trim();
34
62
  if (!normalizedTarget) {
@@ -94,6 +122,12 @@ function resolveTargetAssignment(target, agents, ledger, capabilityRouting = {})
94
122
  (agent) => Array.isArray(agent.capabilities) && agent.capabilities.includes(capability),
95
123
  );
96
124
  candidates.sort((left, right) => {
125
+ const capabilityCompletionDiff =
126
+ completedCapabilityCountForAgent(ledger, right.agentId, capability) -
127
+ completedCapabilityCountForAgent(ledger, left.agentId, capability);
128
+ if (capabilityCompletionDiff !== 0) {
129
+ return capabilityCompletionDiff;
130
+ }
97
131
  const taskDiff = openTaskCountForAgent(ledger, left.agentId) - openTaskCountForAgent(ledger, right.agentId);
98
132
  if (taskDiff !== 0) {
99
133
  return taskDiff;
@@ -101,14 +135,25 @@ function resolveTargetAssignment(target, agents, ledger, capabilityRouting = {})
101
135
  return String(left.agentId).localeCompare(String(right.agentId));
102
136
  });
103
137
  if (candidates[0]) {
138
+ const demonstratedCapabilityCompletions = completedCapabilityCountForAgent(
139
+ ledger,
140
+ candidates[0].agentId,
141
+ capability,
142
+ );
104
143
  return {
105
144
  assignedAgentId: candidates[0].agentId,
106
145
  target: normalizedTarget,
107
146
  targetType: "capability",
108
147
  capability,
109
- assignmentReason: "least-busy-capability",
148
+ assignmentReason:
149
+ demonstratedCapabilityCompletions > 0
150
+ ? "same-wave-capability-owner"
151
+ : "least-busy-capability",
110
152
  blocking: true,
111
- detail: `Capability ${capability} routed to ${candidates[0].agentId}.`,
153
+ detail:
154
+ demonstratedCapabilityCompletions > 0
155
+ ? `Capability ${capability} routed to ${candidates[0].agentId} based on demonstrated same-wave capability work (${demonstratedCapabilityCompletions}).`
156
+ : `Capability ${capability} routed to ${candidates[0].agentId}.`,
112
157
  };
113
158
  }
114
159
  return {
@@ -265,6 +310,7 @@ export function buildRequestAssignments({
265
310
  });
266
311
  const resolvedByPolicy = Boolean(resolvedByPolicyRecord);
267
312
  const effectiveStatus = resolvedByPolicy ? "resolved" : record.status;
313
+ const blocking = !resolvedByPolicy && coordinationRecordBlocksWave(record);
268
314
  assignments.push({
269
315
  id: assignmentId,
270
316
  requestId: record.id,
@@ -282,7 +328,8 @@ export function buildRequestAssignments({
282
328
  assignedAgentId: resolution.assignedAgentId,
283
329
  assignmentReason: resolution.assignmentReason,
284
330
  assignmentDetail: resolution.detail,
285
- blocking: !resolvedByPolicy && isOpenCoordinationStatus(record.status),
331
+ blocking,
332
+ blockerSeverity: coordinationBlockerSeverity(record),
286
333
  artifactRefs: Array.isArray(record.artifactRefs) ? record.artifactRefs : [],
287
334
  dependsOn: Array.isArray(record.dependsOn) ? record.dependsOn : [],
288
335
  closureCondition: String(record.closureCondition || ""),
@@ -27,6 +27,7 @@ import {
27
27
  writeJsonAtomic,
28
28
  } from "./shared.mjs";
29
29
  import {
30
+ createWaveAgentSessionName,
30
31
  killTmuxSessionIfExists,
31
32
  terminalSurfaceUsesTerminalRegistry,
32
33
  pruneOrphanLaneTemporaryTerminalEntries,
@@ -40,6 +41,13 @@ import {
40
41
  launchAgentSession as launchAgentSessionImpl,
41
42
  waitForWaveCompletion as waitForWaveCompletionImpl,
42
43
  } from "./launcher-runtime.mjs";
44
+ import {
45
+ agentUsesSignalHygiene,
46
+ buildSignalStatusLine,
47
+ residentSignalAckPath,
48
+ residentSignalPath,
49
+ syncWaveSignalProjections,
50
+ } from "./signals.mjs";
43
51
 
44
52
  function isProcessAlive(pid) {
45
53
  if (!Number.isInteger(pid) || pid <= 0) {
@@ -342,9 +350,10 @@ export function buildResidentOrchestratorRun({
342
350
  executorResolved,
343
351
  };
344
352
  const baseName = `wave-${wave.wave}-resident-orchestrator`;
345
- const sessionName = `${lanePaths.tmuxSessionPrefix}${wave.wave}_resident_orchestrator_${runTag}`.replace(
346
- /[^a-zA-Z0-9_-]/g,
347
- "_",
353
+ const sessionName = createWaveAgentSessionName(
354
+ lanePaths,
355
+ wave.wave,
356
+ "resident_orchestrator",
348
357
  );
349
358
  return {
350
359
  run: {
@@ -363,6 +372,8 @@ export function buildResidentOrchestratorRun({
363
372
  sharedSummaryPath: derivedState.sharedSummaryPath,
364
373
  dashboardPath,
365
374
  triagePath: derivedState.clarificationTriage?.triagePath || null,
375
+ signalStatePath: residentSignalPath(lanePaths, wave.wave),
376
+ signalAckPath: residentSignalAckPath(lanePaths, wave.wave),
366
377
  rolePrompt: agent.prompt,
367
378
  }),
368
379
  },
@@ -434,15 +445,9 @@ function isWaveDashboardBackedByLiveSession(lanePaths, dashboardPath, activeSess
434
445
  if (!Number.isFinite(waveNumber)) {
435
446
  return false;
436
447
  }
437
- const dashboardState = readJsonOrNull(dashboardPath);
438
- const runTag = String(dashboardState?.runTag || "").trim();
439
448
  const agentPrefix = `${lanePaths.tmuxSessionPrefix}${waveNumber}_`;
440
- const dashboardPrefix = `${lanePaths.tmuxDashboardSessionPrefix}${waveNumber}_`;
441
449
  for (const sessionName of activeSessionNames) {
442
- if (!(sessionName.startsWith(agentPrefix) || sessionName.startsWith(dashboardPrefix))) {
443
- continue;
444
- }
445
- if (!runTag || sessionName.endsWith(`_${runTag}`)) {
450
+ if (sessionName.startsWith(agentPrefix)) {
446
451
  return true;
447
452
  }
448
453
  }
@@ -777,3 +782,107 @@ export function monitorWaveHumanFeedback({
777
782
  }
778
783
  return changed;
779
784
  }
785
+
786
+ export function syncLiveWaveSignals({
787
+ lanePaths,
788
+ wave,
789
+ statusPayload,
790
+ agentRuns,
791
+ residentEnabled = false,
792
+ recordCombinedEvent,
793
+ appendCoordination,
794
+ }) {
795
+ const activeSignalAgents = new Set(
796
+ (Array.isArray(agentRuns) ? agentRuns : [])
797
+ .filter((run) => agentUsesSignalHygiene(run?.agent))
798
+ .map((run) => run.agent.agentId),
799
+ );
800
+ const syncResult = syncWaveSignalProjections({
801
+ lanePaths,
802
+ wave,
803
+ statusPayload,
804
+ includeResident: residentEnabled,
805
+ });
806
+ if (syncResult.wave?.changed) {
807
+ appendWaveControlEvent(lanePaths, wave.wave, {
808
+ entityType: "wave_signal",
809
+ entityId: `wave-${wave.wave}`,
810
+ action: "updated",
811
+ source: "session-supervisor",
812
+ actor: "session-supervisor",
813
+ data: syncResult.wave.snapshot,
814
+ });
815
+ if (typeof recordCombinedEvent === "function") {
816
+ recordCombinedEvent({
817
+ level: syncResult.wave.snapshot.shouldWake ? "warn" : "info",
818
+ message: `Wave signal updated: ${buildSignalStatusLine(syncResult.wave.snapshot)}`,
819
+ });
820
+ }
821
+ }
822
+ for (const agentResult of syncResult.agents || []) {
823
+ if (!agentResult.changed) {
824
+ continue;
825
+ }
826
+ appendWaveControlEvent(lanePaths, wave.wave, {
827
+ entityType: "agent_signal",
828
+ entityId: `wave-${wave.wave}-agent-${agentResult.agentId}`,
829
+ action: "updated",
830
+ source: "session-supervisor",
831
+ actor: "session-supervisor",
832
+ data: agentResult.snapshot,
833
+ });
834
+ if (
835
+ agentResult.snapshot?.shouldWake &&
836
+ activeSignalAgents.has(agentResult.agentId) &&
837
+ typeof recordCombinedEvent === "function"
838
+ ) {
839
+ recordCombinedEvent({
840
+ level: "info",
841
+ agentId: agentResult.agentId,
842
+ message: `Signal changed: ${buildSignalStatusLine(agentResult.snapshot, {
843
+ lane: lanePaths.lane,
844
+ wave: wave.wave,
845
+ agentId: agentResult.agentId,
846
+ })}`,
847
+ });
848
+ }
849
+ }
850
+ if (syncResult.resident?.changed) {
851
+ appendWaveControlEvent(lanePaths, wave.wave, {
852
+ entityType: "agent_signal",
853
+ entityId: `wave-${wave.wave}-agent-resident-orchestrator`,
854
+ action: "updated",
855
+ source: "session-supervisor",
856
+ actor: "session-supervisor",
857
+ data: syncResult.resident.snapshot,
858
+ });
859
+ if (syncResult.resident.snapshot?.shouldWake && typeof recordCombinedEvent === "function") {
860
+ recordCombinedEvent({
861
+ level: "info",
862
+ agentId: "ORCH",
863
+ message: `Resident orchestrator signal changed: ${buildSignalStatusLine(
864
+ syncResult.resident.snapshot,
865
+ {
866
+ lane: lanePaths.lane,
867
+ wave: wave.wave,
868
+ agentId: "resident-orchestrator",
869
+ },
870
+ )}`,
871
+ });
872
+ }
873
+ if (
874
+ syncResult.resident.snapshot?.shouldWake &&
875
+ typeof appendCoordination === "function"
876
+ ) {
877
+ appendCoordination({
878
+ event: "resident_signal_updated",
879
+ waves: [wave.wave],
880
+ status: "running",
881
+ details: syncResult.resident.snapshot.reason || syncResult.resident.snapshot.signal,
882
+ actionRequested:
883
+ "Resident orchestrator should re-read the signal snapshot, shared summary, dashboard, and coordination log.",
884
+ });
885
+ }
886
+ }
887
+ return syncResult;
888
+ }
@@ -199,6 +199,7 @@ export function buildLanePaths(laneInput = DEFAULT_WAVE_LANE, options = {}) {
199
199
  telemetryDir: path.join(stateDir, "control-plane", "telemetry"),
200
200
  assignmentsDir: path.join(stateDir, "assignments"),
201
201
  inboxesDir: path.join(stateDir, "inboxes"),
202
+ signalsDir: path.join(stateDir, "signals"),
202
203
  ledgerDir: path.join(stateDir, "ledger"),
203
204
  integrationDir: path.join(stateDir, "integration"),
204
205
  resultsDir: path.join(stateDir, "results"),