@chllming/wave-orchestration 0.8.2 → 0.8.4

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 -2
  2. package/README.md +47 -11
  3. package/docs/README.md +6 -2
  4. package/docs/concepts/what-is-a-wave.md +1 -1
  5. package/docs/plans/architecture-hardening-migration.md +8 -1
  6. package/docs/plans/current-state.md +17 -7
  7. package/docs/plans/end-state-architecture.md +82 -69
  8. package/docs/plans/examples/wave-example-live-proof.md +1 -1
  9. package/docs/plans/migration.md +235 -61
  10. package/docs/plans/wave-orchestrator.md +37 -11
  11. package/docs/reference/cli-reference.md +39 -15
  12. package/docs/reference/coordination-and-closure.md +30 -6
  13. package/docs/reference/npmjs-trusted-publishing.md +5 -4
  14. package/docs/reference/sample-waves.md +4 -4
  15. package/package.json +1 -1
  16. package/releases/manifest.json +39 -0
  17. package/scripts/wave-orchestrator/agent-state.mjs +0 -491
  18. package/scripts/wave-orchestrator/autonomous.mjs +10 -6
  19. package/scripts/wave-orchestrator/{launcher-closure.mjs → closure-engine.mjs} +190 -74
  20. package/scripts/wave-orchestrator/control-cli.mjs +8 -0
  21. package/scripts/wave-orchestrator/coord-cli.mjs +8 -0
  22. package/scripts/wave-orchestrator/{launcher-derived-state.mjs → derived-state-engine.mjs} +34 -146
  23. package/scripts/wave-orchestrator/feedback.mjs +11 -1
  24. package/scripts/wave-orchestrator/{launcher-gates.mjs → gate-engine.mjs} +395 -139
  25. package/scripts/wave-orchestrator/human-input-resolution.mjs +348 -0
  26. package/scripts/wave-orchestrator/human-input-workflow.mjs +104 -0
  27. package/scripts/wave-orchestrator/implementation-engine.mjs +120 -0
  28. package/scripts/wave-orchestrator/launcher-runtime.mjs +5 -6
  29. package/scripts/wave-orchestrator/launcher.mjs +271 -724
  30. package/scripts/wave-orchestrator/projection-writer.mjs +256 -0
  31. package/scripts/wave-orchestrator/reconcile-format.mjs +32 -0
  32. package/scripts/wave-orchestrator/reducer-snapshot.mjs +297 -0
  33. package/scripts/wave-orchestrator/replay.mjs +3 -1
  34. package/scripts/wave-orchestrator/result-envelope.mjs +589 -0
  35. package/scripts/wave-orchestrator/retry-control.mjs +5 -0
  36. package/scripts/wave-orchestrator/{launcher-retry.mjs → retry-engine.mjs} +267 -18
  37. package/scripts/wave-orchestrator/role-helpers.mjs +51 -0
  38. package/scripts/wave-orchestrator/{launcher-supervisor.mjs → session-supervisor.mjs} +178 -103
  39. package/scripts/wave-orchestrator/shared.mjs +1 -0
  40. package/scripts/wave-orchestrator/traces.mjs +10 -1
  41. package/scripts/wave-orchestrator/wave-files.mjs +11 -9
  42. package/scripts/wave-orchestrator/wave-state-reducer.mjs +52 -5
@@ -8,7 +8,7 @@ import {
8
8
  readClarificationBarrier,
9
9
  readWaveAssignmentBarrier,
10
10
  readWaveDependencyBarrier,
11
- } from "./launcher-gates.mjs";
11
+ } from "./gate-engine.mjs";
12
12
  import {
13
13
  isOpenCoordinationStatus,
14
14
  openClarificationLinkedRequests,
@@ -33,6 +33,7 @@ import {
33
33
  import { hashAgentPromptFingerprint } from "./context7.mjs";
34
34
  import {
35
35
  isSecurityReviewAgent,
36
+ resolveWaveRoleBindings,
36
37
  } from "./role-helpers.mjs";
37
38
  import {
38
39
  commandForExecutor,
@@ -121,6 +122,7 @@ export function persistedRelaunchPlanMatchesCurrentState(
121
122
  }
122
123
  const componentGate = readWaveComponentGate(waveDefinition, agentRuns, {
123
124
  laneProfile: lanePaths?.laneProfile,
125
+ mode: "live",
124
126
  });
125
127
  if (componentGate?.statusCode !== "shared-component-sibling-pending") {
126
128
  return true;
@@ -271,7 +273,10 @@ export function reconcileFailuresAgainstSharedComponentState(wave, agentRuns, fa
271
273
  return failures;
272
274
  }
273
275
  const summariesByAgentId = Object.fromEntries(
274
- (agentRuns || []).map((runInfo) => [runInfo.agent.agentId, readRunExecutionSummary(runInfo, wave)]),
276
+ (agentRuns || []).map((runInfo) => [
277
+ runInfo.agent.agentId,
278
+ readRunExecutionSummary(runInfo, wave, { mode: "live" }),
279
+ ]),
275
280
  );
276
281
  const failureAgentIds = new Set(failures.map((failure) => failure.agentId).filter(Boolean));
277
282
  const consumedSatisfiedAgentIds = new Set();
@@ -337,13 +342,12 @@ export function hasReusableSuccessStatus(agent, statusPath, options = {}) {
337
342
  return true;
338
343
  }
339
344
 
340
- function isClosureAgentId(agent, lanePaths) {
341
- return [
342
- lanePaths.contEvalAgentId || "E0",
343
- lanePaths.integrationAgentId || "A8",
344
- lanePaths.documentationAgentId || "A9",
345
- lanePaths.contQaAgentId || "A0",
346
- ].includes(agent?.agentId) || isSecurityReviewAgent(agent);
345
+ function isClosureAgentId(agent, lanePaths, waveDefinition = null) {
346
+ return (
347
+ resolveWaveRoleBindings(waveDefinition, lanePaths, waveDefinition?.agents).closureAgentIds.includes(
348
+ agent?.agentId,
349
+ ) || isSecurityReviewAgent(agent)
350
+ );
347
351
  }
348
352
 
349
353
  export function selectReusablePreCompletedAgentIds(
@@ -357,7 +361,7 @@ export function selectReusablePreCompletedAgentIds(
357
361
  .filter(
358
362
  (run) =>
359
363
  !retryOverrideClearedAgentIds.has(run.agent.agentId) &&
360
- !isClosureAgentId(run.agent, lanePaths) &&
364
+ !isClosureAgentId(run.agent, lanePaths, wave) &&
361
365
  hasReusableSuccessStatus(run.agent, run.statusPath, {
362
366
  wave,
363
367
  derivedState,
@@ -369,9 +373,9 @@ export function selectReusablePreCompletedAgentIds(
369
373
  );
370
374
  }
371
375
 
372
- export function selectInitialWaveRuns(agentRuns, lanePaths) {
376
+ export function selectInitialWaveRuns(agentRuns, lanePaths, waveDefinition = null) {
373
377
  const implementationRuns = (agentRuns || []).filter(
374
- (run) => !isClosureAgentId(run?.agent, lanePaths),
378
+ (run) => !isClosureAgentId(run?.agent, lanePaths, waveDefinition),
375
379
  );
376
380
  return implementationRuns.length > 0 ? implementationRuns : agentRuns;
377
381
  }
@@ -560,7 +564,35 @@ function retryBarrierFromOutcomes(outcomes, failures) {
560
564
  };
561
565
  }
562
566
 
563
- export function resolveRelaunchRuns(agentRuns, failures, derivedState, lanePaths, waveDefinition = null) {
567
+ function runsFromAgentIds(agentRuns, agentIds) {
568
+ const runsByAgentId = new Map((agentRuns || []).map((run) => [run.agent.agentId, run]));
569
+ return Array.from(new Set((agentIds || []).filter(Boolean)))
570
+ .map((agentId) => runsByAgentId.get(agentId))
571
+ .filter(Boolean);
572
+ }
573
+
574
+ function resolveRunsForResumePhase(agentRuns, lanePaths, resumePhase, waveDefinition = null) {
575
+ const roleBindings = resolveWaveRoleBindings(waveDefinition, lanePaths, waveDefinition?.agents);
576
+ if (resumePhase === "integrating") {
577
+ return runsFromAgentIds(agentRuns, [roleBindings.integrationAgentId]);
578
+ }
579
+ if (resumePhase === "security-review") {
580
+ return (agentRuns || []).filter((run) => isSecurityReviewAgent(run.agent));
581
+ }
582
+ if (resumePhase === "docs-closure") {
583
+ return runsFromAgentIds(agentRuns, [roleBindings.documentationAgentId]);
584
+ }
585
+ if (resumePhase === "cont-qa-closure") {
586
+ return runsFromAgentIds(agentRuns, [roleBindings.contQaAgentId]);
587
+ }
588
+ if (resumePhase === "cont-eval") {
589
+ return runsFromAgentIds(agentRuns, [roleBindings.contEvalAgentId]);
590
+ }
591
+ return [];
592
+ }
593
+
594
+ function resolveRelaunchRunsLegacy(agentRuns, failures, derivedState, lanePaths, waveDefinition = null) {
595
+ const roleBindings = resolveWaveRoleBindings(waveDefinition, lanePaths, waveDefinition?.agents);
564
596
  const runsByAgentId = new Map(agentRuns.map((run) => [run.agent.agentId, run]));
565
597
  const pendingFeedback = (derivedState?.coordinationState?.humanFeedback || []).filter((record) =>
566
598
  isOpenCoordinationStatus(record.status),
@@ -693,7 +725,7 @@ export function resolveRelaunchRuns(agentRuns, failures, derivedState, lanePaths
693
725
  }
694
726
  if (derivedState?.ledger?.phase === "docs-closure") {
695
727
  return {
696
- runs: [runsByAgentId.get(lanePaths.documentationAgentId)].filter(Boolean),
728
+ runs: [runsByAgentId.get(roleBindings.documentationAgentId)].filter(Boolean),
697
729
  barrier: null,
698
730
  };
699
731
  }
@@ -705,19 +737,19 @@ export function resolveRelaunchRuns(agentRuns, failures, derivedState, lanePaths
705
737
  }
706
738
  if (derivedState?.ledger?.phase === "cont-eval") {
707
739
  return {
708
- runs: [runsByAgentId.get(lanePaths.contEvalAgentId)].filter(Boolean),
740
+ runs: [runsByAgentId.get(roleBindings.contEvalAgentId)].filter(Boolean),
709
741
  barrier: null,
710
742
  };
711
743
  }
712
744
  if (derivedState?.ledger?.phase === "cont-qa-closure") {
713
745
  return {
714
- runs: [runsByAgentId.get(lanePaths.contQaAgentId)].filter(Boolean),
746
+ runs: [runsByAgentId.get(roleBindings.contQaAgentId)].filter(Boolean),
715
747
  barrier: null,
716
748
  };
717
749
  }
718
750
  if (derivedState?.ledger?.phase === "integrating") {
719
751
  return {
720
- runs: [runsByAgentId.get(lanePaths.integrationAgentId)].filter(Boolean),
752
+ runs: [runsByAgentId.get(roleBindings.integrationAgentId)].filter(Boolean),
721
753
  barrier: null,
722
754
  };
723
755
  }
@@ -742,6 +774,216 @@ export function resolveRelaunchRuns(agentRuns, failures, derivedState, lanePaths
742
774
  };
743
775
  }
744
776
 
777
+ function resolveRelaunchRunsFromWaveState(
778
+ agentRuns,
779
+ failures,
780
+ derivedState,
781
+ lanePaths,
782
+ waveDefinition,
783
+ waveState,
784
+ ) {
785
+ const roleBindings = resolveWaveRoleBindings(waveDefinition, lanePaths, waveDefinition?.agents);
786
+ const pendingFeedback = (waveState?.coordinationState?.humanFeedback || []).filter((record) =>
787
+ isOpenCoordinationStatus(record.status),
788
+ );
789
+ const pendingHumanEscalations = (waveState?.coordinationState?.humanEscalations || []).filter(
790
+ (record) => isOpenCoordinationStatus(record.status),
791
+ );
792
+ if (pendingFeedback.length > 0 || pendingHumanEscalations.length > 0) {
793
+ return { runs: [], barrier: null };
794
+ }
795
+
796
+ const nextAttemptNumber = Number(derivedState?.ledger?.attempt || 0) + 1;
797
+ const fallbackResolution = applyRetryFallbacks(
798
+ agentRuns,
799
+ failures,
800
+ lanePaths,
801
+ nextAttemptNumber,
802
+ waveDefinition,
803
+ );
804
+ const retryBarrier = retryBarrierFromOutcomes(fallbackResolution.outcomes, failures);
805
+ if (retryBarrier) {
806
+ return { runs: [], barrier: retryBarrier };
807
+ }
808
+
809
+ const clarificationTargets = new Set();
810
+ for (const record of openClarificationLinkedRequests(waveState?.coordinationState)) {
811
+ for (const target of record.targets || []) {
812
+ if (String(target).startsWith("agent:")) {
813
+ clarificationTargets.add(String(target).slice("agent:".length));
814
+ } else {
815
+ clarificationTargets.add(target);
816
+ }
817
+ }
818
+ }
819
+ if (clarificationTargets.size > 0) {
820
+ return {
821
+ runs: runsFromAgentIds(agentRuns, Array.from(clarificationTargets)),
822
+ barrier: null,
823
+ };
824
+ }
825
+
826
+ const blockingAssignments = (waveState?.capabilityAssignments || []).filter(
827
+ (assignment) => assignment.blocking,
828
+ );
829
+ if (blockingAssignments.length > 0) {
830
+ const unresolvedAssignments = blockingAssignments.filter((assignment) => !assignment.assignedAgentId);
831
+ if (unresolvedAssignments.length > 0) {
832
+ return {
833
+ runs: [],
834
+ barrier: {
835
+ statusCode: "helper-assignment-unresolved",
836
+ detail: `No matching assignee exists for helper requests (${unresolvedAssignments.map((assignment) => assignment.requestId).join(", ")}).`,
837
+ failures: unresolvedAssignments.map((assignment) => ({
838
+ agentId: null,
839
+ statusCode: "helper-assignment-unresolved",
840
+ logPath: null,
841
+ detail: assignment.assignmentDetail || assignment.summary || assignment.requestId,
842
+ })),
843
+ },
844
+ };
845
+ }
846
+ return {
847
+ runs: runsFromAgentIds(
848
+ agentRuns,
849
+ blockingAssignments.map((assignment) => assignment.assignedAgentId),
850
+ ),
851
+ barrier: null,
852
+ };
853
+ }
854
+
855
+ const unresolvedInboundAssignments =
856
+ waveState?.dependencySnapshot?.unresolvedInboundAssignments || [];
857
+ if (unresolvedInboundAssignments.length > 0) {
858
+ return {
859
+ runs: [],
860
+ barrier: {
861
+ statusCode: "dependency-assignment-unresolved",
862
+ detail: `Required inbound dependencies are not assigned (${unresolvedInboundAssignments.map((record) => record.id).join(", ")}).`,
863
+ failures: unresolvedInboundAssignments.map((record) => ({
864
+ agentId: null,
865
+ statusCode: "dependency-assignment-unresolved",
866
+ logPath: null,
867
+ detail: record.assignmentDetail || record.summary || record.id,
868
+ })),
869
+ },
870
+ };
871
+ }
872
+
873
+ const inboundDependencyAgentIds = new Set(
874
+ (waveState?.dependencySnapshot?.openInbound || [])
875
+ .map((record) => record.assignedAgentId)
876
+ .filter(Boolean),
877
+ );
878
+ if (inboundDependencyAgentIds.size > 0) {
879
+ return {
880
+ runs: runsFromAgentIds(agentRuns, Array.from(inboundDependencyAgentIds)),
881
+ barrier: null,
882
+ };
883
+ }
884
+
885
+ const blockerAgentIds = new Set();
886
+ for (const record of waveState?.coordinationState?.blockers || []) {
887
+ if (!isOpenCoordinationStatus(record.status)) {
888
+ continue;
889
+ }
890
+ blockerAgentIds.add(record.agentId);
891
+ for (const target of record.targets || []) {
892
+ if (String(target).startsWith("agent:")) {
893
+ blockerAgentIds.add(String(target).slice("agent:".length));
894
+ }
895
+ }
896
+ }
897
+ if (blockerAgentIds.size > 0) {
898
+ return {
899
+ runs: runsFromAgentIds(agentRuns, Array.from(blockerAgentIds)),
900
+ barrier: null,
901
+ };
902
+ }
903
+
904
+ const sharedComponentWaitingAgentIds = new Set(
905
+ (failures || [])
906
+ .filter((failure) => failure.statusCode === "shared-component-sibling-pending")
907
+ .flatMap((failure) => failure.waitingOnAgentIds || [])
908
+ .filter(Boolean),
909
+ );
910
+ if (sharedComponentWaitingAgentIds.size > 0) {
911
+ return {
912
+ runs: runsFromAgentIds(agentRuns, Array.from(sharedComponentWaitingAgentIds)),
913
+ barrier: null,
914
+ };
915
+ }
916
+
917
+ const resumePlan = buildResumePlan(waveState, {
918
+ waveDefinition,
919
+ lanePaths,
920
+ });
921
+ if (!resumePlan.canResume || resumePlan.reason === "human-request") {
922
+ return { runs: [], barrier: null };
923
+ }
924
+
925
+ const phaseRuns = resolveRunsForResumePhase(
926
+ agentRuns,
927
+ lanePaths,
928
+ resumePlan.resumeFromPhase,
929
+ waveDefinition,
930
+ );
931
+ if (phaseRuns.length > 0 && resumePlan.resumeFromPhase !== "implementation") {
932
+ return {
933
+ runs: phaseRuns,
934
+ barrier: null,
935
+ };
936
+ }
937
+
938
+ const retryTargetAgentIds = normalizeRetryTargets(waveState?.retryTargetSet).map(
939
+ (target) => target.agentId,
940
+ );
941
+ const implementationAgentIds =
942
+ resumePlan.invalidatedAgentIds.length > 0
943
+ ? resumePlan.invalidatedAgentIds
944
+ : retryTargetAgentIds;
945
+ if (implementationAgentIds.length > 0) {
946
+ return {
947
+ runs: runsFromAgentIds(agentRuns, implementationAgentIds),
948
+ barrier: null,
949
+ };
950
+ }
951
+
952
+ const failedAgentIds = new Set(failures.map((failure) => failure.agentId));
953
+ return {
954
+ runs: agentRuns.filter((run) => failedAgentIds.has(run.agent.agentId)),
955
+ barrier: null,
956
+ };
957
+ }
958
+
959
+ export function resolveRelaunchRuns(
960
+ agentRuns,
961
+ failures,
962
+ derivedState,
963
+ lanePaths,
964
+ waveDefinition = null,
965
+ options = {},
966
+ ) {
967
+ const waveState = options?.waveState || null;
968
+ if (!waveState) {
969
+ return resolveRelaunchRunsLegacy(
970
+ agentRuns,
971
+ failures,
972
+ derivedState,
973
+ lanePaths,
974
+ waveDefinition,
975
+ );
976
+ }
977
+ return resolveRelaunchRunsFromWaveState(
978
+ agentRuns,
979
+ failures,
980
+ derivedState,
981
+ lanePaths,
982
+ waveDefinition,
983
+ waveState,
984
+ );
985
+ }
986
+
745
987
  export function preflightWavesForExecutorAvailability(waves, lanePaths) {
746
988
  for (const wave of waves) {
747
989
  const mixValidation = validateWaveRuntimeMixAssignments(wave, {
@@ -778,10 +1020,17 @@ function phaseFromGate(gateName) {
778
1020
  switch (gateName) {
779
1021
  case "implementationGate":
780
1022
  case "componentGate":
781
- case "contEvalGate":
1023
+ case "helperAssignmentBarrier":
1024
+ case "dependencyBarrier":
1025
+ case "clarificationBarrier":
782
1026
  return "implementation";
1027
+ case "contEvalGate":
1028
+ return "cont-eval";
1029
+ case "securityGate":
1030
+ return "security-review";
783
1031
  case "integrationBarrier":
784
1032
  return "integrating";
1033
+ case "componentMatrixGate":
785
1034
  case "documentationGate":
786
1035
  return "docs-closure";
787
1036
  case "contQaGate":
@@ -1,5 +1,8 @@
1
1
  import {
2
+ DEFAULT_CONT_QA_AGENT_ID,
2
3
  DEFAULT_CONT_EVAL_AGENT_ID,
4
+ DEFAULT_DOCUMENTATION_AGENT_ID,
5
+ DEFAULT_INTEGRATION_AGENT_ID,
3
6
  DEFAULT_SECURITY_ROLE_PROMPT_PATH,
4
7
  } from "./config.mjs";
5
8
 
@@ -82,3 +85,51 @@ export function resolveSecurityReviewReportPath(agent) {
82
85
  const ownedPaths = Array.isArray(agent?.ownedPaths) ? agent.ownedPaths.map(cleanPath).filter(Boolean) : [];
83
86
  return ownedPaths.find((ownedPath) => isSecurityReportPath(ownedPath)) || null;
84
87
  }
88
+
89
+ export function resolveWaveRoleBindings(wave = {}, lanePaths = {}, agents = wave?.agents || []) {
90
+ const contQaAgentId =
91
+ wave?.contQaAgentId || lanePaths?.contQaAgentId || DEFAULT_CONT_QA_AGENT_ID;
92
+ const contEvalAgentId =
93
+ wave?.contEvalAgentId || lanePaths?.contEvalAgentId || DEFAULT_CONT_EVAL_AGENT_ID;
94
+ const integrationAgentId =
95
+ wave?.integrationAgentId || lanePaths?.integrationAgentId || DEFAULT_INTEGRATION_AGENT_ID;
96
+ const documentationAgentId =
97
+ wave?.documentationAgentId ||
98
+ lanePaths?.documentationAgentId ||
99
+ DEFAULT_DOCUMENTATION_AGENT_ID;
100
+ const securityReviewerAgentIds = Array.from(
101
+ new Set(
102
+ (Array.isArray(agents) ? agents : [])
103
+ .filter((agent) =>
104
+ isSecurityReviewAgent(agent, {
105
+ securityRolePromptPath: lanePaths?.securityRolePromptPath,
106
+ }),
107
+ )
108
+ .map((agent) => agent.agentId)
109
+ .filter(Boolean),
110
+ ),
111
+ ).sort();
112
+ const closureAgentIds = Array.from(
113
+ new Set(
114
+ [
115
+ contEvalAgentId,
116
+ integrationAgentId,
117
+ documentationAgentId,
118
+ contQaAgentId,
119
+ ...securityReviewerAgentIds,
120
+ ].filter(Boolean),
121
+ ),
122
+ ).sort();
123
+ return {
124
+ contQaAgentId,
125
+ contEvalAgentId,
126
+ integrationAgentId,
127
+ documentationAgentId,
128
+ securityReviewerAgentIds,
129
+ closureAgentIds,
130
+ };
131
+ }
132
+
133
+ export function isClosureRoleAgentId(agentId, roleBindings) {
134
+ return (roleBindings?.closureAgentIds || []).includes(agentId);
135
+ }