stageflow 0.3.0 → 0.8.0

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 (153) hide show
  1. package/README.md +22 -1
  2. package/dist/agent/activity.d.ts +2 -0
  3. package/dist/agent/cursorProvider.d.ts +1 -10
  4. package/dist/agent/cursorProvider.js +18 -10
  5. package/dist/agent/fakeAgent.js +11 -7
  6. package/dist/agent/piAdapter.d.ts +2 -1
  7. package/dist/agent/piAdapter.js +28 -25
  8. package/dist/agent/port.d.ts +6 -1
  9. package/dist/agent/port.js +3 -0
  10. package/dist/cli/artifactCommand.d.ts +10 -0
  11. package/dist/cli/artifactCommand.js +120 -0
  12. package/dist/cli/envelopeCommand.d.ts +32 -0
  13. package/dist/cli/envelopeCommand.js +285 -0
  14. package/dist/cli/exportRunCommand.d.ts +10 -0
  15. package/dist/cli/exportRunCommand.js +150 -0
  16. package/dist/cli/handoffFormat.d.ts +22 -0
  17. package/dist/cli/handoffFormat.js +61 -0
  18. package/dist/cli/runCommand.d.ts +5 -1
  19. package/dist/cli/runCommand.js +36 -5
  20. package/dist/cli/runOutput.d.ts +4 -1
  21. package/dist/cli/runOutput.js +36 -8
  22. package/dist/cli/skillsCommand.d.ts +16 -0
  23. package/dist/cli/skillsCommand.js +459 -0
  24. package/dist/cli.js +78 -5
  25. package/dist/config/loadPipeline.js +5 -1
  26. package/dist/config/normalizePipelineStageEntry.d.ts +2 -0
  27. package/dist/config/normalizePipelineStageEntry.js +25 -1
  28. package/dist/config/pipelineStageKeys.js +1 -1
  29. package/dist/config/resolveForkEmitContext.d.ts +2 -1
  30. package/dist/config/resolveForkEmitContext.js +23 -1
  31. package/dist/config/resolvePipelineDag.js +36 -4
  32. package/dist/envelope/check.d.ts +2 -0
  33. package/dist/envelope/check.js +51 -0
  34. package/dist/envelope/cloneForks.d.ts +2 -0
  35. package/dist/envelope/cloneForks.js +62 -0
  36. package/dist/envelope/forkChoice.js +1 -1
  37. package/dist/index.d.ts +2 -0
  38. package/dist/index.js +2 -0
  39. package/dist/mcp/catalogTools.d.ts +3 -0
  40. package/dist/mcp/catalogTools.js +187 -0
  41. package/dist/mcp/controlTools.d.ts +3 -0
  42. package/dist/mcp/controlTools.js +147 -0
  43. package/dist/mcp/deps.d.ts +11 -0
  44. package/dist/mcp/deps.js +1 -0
  45. package/dist/mcp/projectRun.d.ts +1 -40
  46. package/dist/mcp/projectRun.js +1 -64
  47. package/dist/mcp/resources.d.ts +5 -0
  48. package/dist/mcp/resources.js +45 -0
  49. package/dist/mcp/server.d.ts +16 -3
  50. package/dist/mcp/server.js +159 -3
  51. package/dist/mcp/toolResults.d.ts +7 -0
  52. package/dist/mcp/toolResults.js +6 -0
  53. package/dist/mcp/tools.d.ts +2 -7
  54. package/dist/mcp/tools.js +52 -103
  55. package/dist/mcp/waitRun.d.ts +46 -0
  56. package/dist/mcp/waitRun.js +146 -0
  57. package/dist/mcp/waitingGates.d.ts +12 -0
  58. package/dist/mcp/waitingGates.js +83 -0
  59. package/dist/projection/projectRun.d.ts +44 -0
  60. package/dist/projection/projectRun.js +71 -0
  61. package/dist/prompt/priorEnvelope.d.ts +1 -1
  62. package/dist/prompt/priorEnvelope.js +4 -1
  63. package/dist/runstore/paths.js +2 -0
  64. package/dist/runstore/pipelineDagSnapshot.d.ts +9 -0
  65. package/dist/runstore/pipelineDagSnapshot.js +90 -1
  66. package/dist/runstore/port.d.ts +13 -2
  67. package/dist/runstore/port.js +4 -1
  68. package/dist/runstore/runProjection.d.ts +1 -1
  69. package/dist/runstore/runProjection.js +14 -3
  70. package/dist/runstore/sqlite/SqliteRunStore.d.ts +3 -2
  71. package/dist/runstore/sqlite/SqliteRunStore.js +35 -3
  72. package/dist/runstore/stageInstanceId.d.ts +4 -0
  73. package/dist/runstore/stageInstanceId.js +23 -0
  74. package/dist/runstore/syntheticStageSnapshot.d.ts +1 -1
  75. package/dist/runstore/syntheticStageSnapshot.js +2 -1
  76. package/dist/runstore/trackProjection.js +28 -12
  77. package/dist/runstore/workspaceLayout.d.ts +1 -0
  78. package/dist/runstore/workspaceLayout.js +1 -1
  79. package/dist/runtime/cloneSchedule.d.ts +18 -0
  80. package/dist/runtime/cloneSchedule.js +233 -0
  81. package/dist/runtime/envelopeRouting.d.ts +2 -1
  82. package/dist/runtime/envelopeRouting.js +80 -13
  83. package/dist/runtime/pipelineScheduler.d.ts +6 -2
  84. package/dist/runtime/pipelineScheduler.js +166 -30
  85. package/dist/runtime/resumeReconstruct.js +8 -3
  86. package/dist/runtime/runChangeBus.d.ts +18 -0
  87. package/dist/runtime/runChangeBus.js +83 -0
  88. package/dist/runtime/stageAttemptBootstrap.d.ts +1 -0
  89. package/dist/runtime/stageAttemptBootstrap.js +17 -9
  90. package/dist/runtime/stageRunner.d.ts +1 -0
  91. package/dist/runtime/stageRunner.js +17 -15
  92. package/dist/runtime/stageWorker.js +11 -4
  93. package/dist/server/bootstrap.d.ts +32 -0
  94. package/dist/server/bootstrap.js +56 -0
  95. package/dist/server/createHttpHost.d.ts +34 -0
  96. package/dist/server/createHttpHost.js +70 -0
  97. package/dist/server/http.d.ts +6 -13
  98. package/dist/server/http.js +295 -374
  99. package/dist/server/mcpHost.d.ts +22 -0
  100. package/dist/server/mcpHost.js +21 -0
  101. package/dist/server/operatorResults.d.ts +13 -0
  102. package/dist/server/operatorResults.js +22 -0
  103. package/dist/tools/emitStageEnvelope.d.ts +11 -2
  104. package/dist/tools/emitStageEnvelope.js +22 -6
  105. package/dist/types/envelope.d.ts +2 -0
  106. package/dist/types/forkChoice.d.ts +22 -0
  107. package/dist/types/pipeline.d.ts +8 -0
  108. package/dist/ui/assets/{index-DefBlEvN.css → index-C3N7MXAC.css} +1 -1
  109. package/dist/ui/assets/index-CSoGDA3A.js +118 -0
  110. package/dist/ui/index.html +2 -2
  111. package/package.json +2 -1
  112. package/skills/install-suite.sh +107 -0
  113. package/skills/stageflow/SKILL.md +26 -0
  114. package/skills/stageflow/references/control-surface.md +26 -0
  115. package/skills/stageflow/scripts/detect-host.mjs +54 -0
  116. package/skills/stageflow/scripts/detect-host.test.mjs +97 -0
  117. package/skills/stageflow-author/SKILL.md +58 -0
  118. package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +10 -0
  119. package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +12 -0
  120. package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +13 -0
  121. package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +10 -0
  122. package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +9 -0
  123. package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +10 -0
  124. package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +10 -0
  125. package/skills/stageflow-author/assets/examples/linear-review/review.yaml +15 -0
  126. package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +9 -0
  127. package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +10 -0
  128. package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +10 -0
  129. package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +9 -0
  130. package/skills/stageflow-author/references/catalog-mapping.md +131 -0
  131. package/skills/stageflow-author/references/catalog-write-conventions.md +29 -0
  132. package/skills/stageflow-author/references/stage-prompt-template.md +55 -0
  133. package/skills/stageflow-author/references/validate-and-report.md +38 -0
  134. package/skills/stageflow-delegate/SKILL.md +18 -0
  135. package/skills/stageflow-delegate/references/authoring-or-run.md +15 -0
  136. package/skills/stageflow-delegate/references/example-walkthrough.md +30 -0
  137. package/skills/stageflow-delegate/references/pattern-detection.md +44 -0
  138. package/skills/stageflow-run/SKILL.md +151 -0
  139. package/skills/stageflow-run/references/mcp-call.md +17 -0
  140. package/skills/stageflow-run/references/task-and-pipeline-selection.md +52 -0
  141. package/skills/stageflow-run/scripts/mcp-call.mjs +299 -0
  142. package/skills/stageflow-run/scripts/mcp-call.test.mjs +271 -0
  143. package/skills/stageflow-session-capture/SKILL.md +94 -0
  144. package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +7 -0
  145. package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +8 -0
  146. package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +8 -0
  147. package/skills/stageflow-session-capture/references/catalog-authoring.md +40 -0
  148. package/skills/stageflow-session-capture/references/transcript-sources.md +25 -0
  149. package/skills/stageflow-session-capture/scripts/check-provider-gate.mjs +43 -0
  150. package/skills/stageflow-session-capture/scripts/locate-session-transcript.mjs +141 -0
  151. package/skills/stageflow-session-capture/scripts/resolve-catalog-id.mjs +119 -0
  152. package/skills/stageflow-setup/SKILL.md +110 -0
  153. package/dist/ui/assets/index-CFSzDZje.js +0 -118
@@ -0,0 +1,18 @@
1
+ import type { RunPipelineDagSnapshot } from "../runstore/port.js";
2
+ import type { StageEnvelope } from "../types/envelope.js";
3
+ import type { ResolvedPipelineDag } from "../types/pipeline.js";
4
+ import type { StageScheduleState } from "./pipelineScheduler.js";
5
+ export declare function isCloneInstance(dag: ResolvedPipelineDag, stageId: string): boolean;
6
+ export declare function sequentialLaterCloneIds(dag: ResolvedPipelineDag, stageId: string, completedEnvelopes: Map<string, StageEnvelope>): string[];
7
+ export declare function cloneFanoutConflict(dag: ResolvedPipelineDag, envelope: StageEnvelope): string | undefined;
8
+ export declare function protectedClonableChildIds(dag: ResolvedPipelineDag, predecessorId: string, envelope: StageEnvelope): Set<string>;
9
+ export declare function cloneRetryDownstream(dag: ResolvedPipelineDag, stageIds: string[]): Set<string>;
10
+ export declare function cloneFailureContinuesSchedule(dag: ResolvedPipelineDag, stageId: string, completedEnvelopes: Map<string, StageEnvelope>): boolean;
11
+ export declare function cloneFailFastSkipIds(dag: ResolvedPipelineDag, stageId: string, completedEnvelopes: Map<string, StageEnvelope>): string[];
12
+ export declare function cloneScheduleAllowsRun(dag: ResolvedPipelineDag, stageId: string, states: Map<string, StageScheduleState>, completedEnvelopes: Map<string, StageEnvelope>): boolean;
13
+ export type ApplyCloneForksResult = {
14
+ dag: RunPipelineDagSnapshot;
15
+ skippedIds: string[];
16
+ };
17
+ export declare function applyCloneForksToSchedule(dag: ResolvedPipelineDag, predecessorId: string, envelope: StageEnvelope, states: Map<string, StageScheduleState>): ApplyCloneForksResult;
18
+ export declare function applyCloneForksFromEnvelopes(dag: ResolvedPipelineDag, states: Map<string, StageScheduleState>, completedEnvelopes: Map<string, StageEnvelope>): ApplyCloneForksResult;
@@ -0,0 +1,233 @@
1
+ import { appendCloneInstances, instancesOfDefinition, } from "../runstore/pipelineDagSnapshot.js";
2
+ import { definitionIdForInstance } from "../runstore/stageInstanceId.js";
3
+ import { collectDownstreamClosure, collectDownstreamStageIds } from "./dagTraversal.js";
4
+ function asDagSnapshot(dag) {
5
+ const snapshot = dag;
6
+ if (Array.isArray(snapshot.stage_ids))
7
+ return snapshot;
8
+ return {
9
+ ...dag,
10
+ stage_ids: dag.nodes.map((n) => n.id),
11
+ };
12
+ }
13
+ function definitionInstances(dag, catalogId) {
14
+ const snapshot = dag;
15
+ if (Array.isArray(snapshot.stage_ids)) {
16
+ return instancesOfDefinition(snapshot, catalogId);
17
+ }
18
+ return dag.nodes.some((n) => n.id === catalogId) ? [catalogId] : [];
19
+ }
20
+ export function isCloneInstance(dag, stageId) {
21
+ return definitionIdForInstance(asDagSnapshot(dag), stageId) !== stageId;
22
+ }
23
+ function isParallelCloneInstance(dag, stageId, completedEnvelopes) {
24
+ if (!isCloneInstance(dag, stageId))
25
+ return false;
26
+ const defId = definitionIdForInstance(asDagSnapshot(dag), stageId);
27
+ for (const envelope of completedEnvelopes.values()) {
28
+ for (const item of envelope.clone_forks ?? []) {
29
+ if (item.action === "fanout" &&
30
+ item.mode === "parallel" &&
31
+ item.successor_id === defId &&
32
+ definitionInstances(dag, defId).includes(stageId)) {
33
+ return true;
34
+ }
35
+ }
36
+ }
37
+ return false;
38
+ }
39
+ function isSequentialFanoutSuccessor(completedEnvelopes, successorId) {
40
+ for (const envelope of completedEnvelopes.values()) {
41
+ for (const item of envelope.clone_forks ?? []) {
42
+ if (item.action === "fanout" &&
43
+ item.mode === "sequential" &&
44
+ item.successor_id === successorId) {
45
+ return true;
46
+ }
47
+ }
48
+ }
49
+ return false;
50
+ }
51
+ function isSequentialCloneInstance(dag, stageId, completedEnvelopes) {
52
+ if (!isCloneInstance(dag, stageId))
53
+ return false;
54
+ const defId = definitionIdForInstance(asDagSnapshot(dag), stageId);
55
+ if (!isSequentialFanoutSuccessor(completedEnvelopes, defId))
56
+ return false;
57
+ return definitionInstances(dag, defId).includes(stageId);
58
+ }
59
+ function sequentialInstanceList(dag, stageId, completedEnvelopes) {
60
+ for (const envelope of completedEnvelopes.values()) {
61
+ for (const item of envelope.clone_forks ?? []) {
62
+ if (item.action !== "fanout" || item.mode !== "sequential")
63
+ continue;
64
+ const instances = definitionInstances(dag, item.successor_id);
65
+ if (instances.includes(stageId))
66
+ return instances;
67
+ }
68
+ }
69
+ return undefined;
70
+ }
71
+ function sequentialPreviousUnsatisfied(dag, stageId, states, completedEnvelopes) {
72
+ const instances = sequentialInstanceList(dag, stageId, completedEnvelopes);
73
+ if (instances === undefined)
74
+ return false;
75
+ const i = instances.indexOf(stageId);
76
+ if (i <= 0)
77
+ return false;
78
+ return states.get(instances[i - 1]) !== "succeeded";
79
+ }
80
+ export function sequentialLaterCloneIds(dag, stageId, completedEnvelopes) {
81
+ const instances = sequentialInstanceList(dag, stageId, completedEnvelopes);
82
+ if (instances === undefined)
83
+ return [];
84
+ const i = instances.indexOf(stageId);
85
+ if (i === -1)
86
+ return [];
87
+ return instances.slice(i + 1);
88
+ }
89
+ function joinSuccessorIds(dag, successorId) {
90
+ return dag.nodes.filter((n) => n.needs === successorId).map((n) => n.id);
91
+ }
92
+ function joinAndDownstreamIds(dag, cloneStageId) {
93
+ const defId = definitionIdForInstance(asDagSnapshot(dag), cloneStageId);
94
+ const ids = [];
95
+ for (const joinId of joinSuccessorIds(dag, defId)) {
96
+ ids.push(joinId);
97
+ for (const desc of collectDownstreamStageIds(dag, joinId)) {
98
+ ids.push(desc);
99
+ }
100
+ }
101
+ return ids;
102
+ }
103
+ export function cloneFanoutConflict(dag, envelope) {
104
+ const snapshot = asDagSnapshot(dag);
105
+ for (const item of envelope.clone_forks ?? []) {
106
+ if (item.action !== "fanout")
107
+ continue;
108
+ if (definitionInstances(dag, item.successor_id).some((id) => id !== item.successor_id)) {
109
+ return `clone fan-out for "${item.successor_id}" is already instanced`;
110
+ }
111
+ if (!snapshot.stage_ids.includes(item.successor_id)) {
112
+ return `clone fan-out for "${item.successor_id}" is already instanced`;
113
+ }
114
+ }
115
+ return undefined;
116
+ }
117
+ export function protectedClonableChildIds(dag, predecessorId, envelope) {
118
+ const ids = new Set();
119
+ for (const childId of dag.childrenOf[predecessorId] ?? []) {
120
+ const child = dag.nodes.find((n) => n.id === childId);
121
+ if (child?.clonable === true)
122
+ ids.add(childId);
123
+ }
124
+ for (const item of envelope.clone_forks ?? []) {
125
+ ids.add(item.successor_id);
126
+ }
127
+ return ids;
128
+ }
129
+ export function cloneRetryDownstream(dag, stageIds) {
130
+ const downstream = collectDownstreamClosure(dag, stageIds);
131
+ for (const id of stageIds) {
132
+ const defId = definitionIdForInstance(asDagSnapshot(dag), id);
133
+ for (const child of dag.childrenOf[defId] ?? []) {
134
+ downstream.add(child);
135
+ for (const desc of collectDownstreamStageIds(dag, child)) {
136
+ downstream.add(desc);
137
+ }
138
+ }
139
+ }
140
+ return downstream;
141
+ }
142
+ export function cloneFailureContinuesSchedule(dag, stageId, completedEnvelopes) {
143
+ return (isParallelCloneInstance(dag, stageId, completedEnvelopes) ||
144
+ isSequentialCloneInstance(dag, stageId, completedEnvelopes));
145
+ }
146
+ export function cloneFailFastSkipIds(dag, stageId, completedEnvelopes) {
147
+ if (isParallelCloneInstance(dag, stageId, completedEnvelopes)) {
148
+ return joinAndDownstreamIds(dag, stageId);
149
+ }
150
+ if (!isSequentialCloneInstance(dag, stageId, completedEnvelopes))
151
+ return [];
152
+ const instances = sequentialInstanceList(dag, stageId, completedEnvelopes);
153
+ if (instances === undefined)
154
+ return [];
155
+ const idx = instances.indexOf(stageId);
156
+ const ids = [];
157
+ for (const id of instances.slice(idx + 1)) {
158
+ ids.push(id);
159
+ }
160
+ ids.push(...joinAndDownstreamIds(dag, stageId));
161
+ return ids;
162
+ }
163
+ export function cloneScheduleAllowsRun(dag, stageId, states, completedEnvelopes) {
164
+ const node = dag.nodes.find((n) => n.id === stageId);
165
+ if (!node)
166
+ return false;
167
+ if (definitionInstances(dag, stageId).some((id) => id !== stageId)) {
168
+ return false;
169
+ }
170
+ if (node.needs === null) {
171
+ return !sequentialPreviousUnsatisfied(dag, stageId, states, completedEnvelopes);
172
+ }
173
+ const instances = definitionInstances(dag, node.needs);
174
+ if (instances.length <= 1) {
175
+ const parentId = instances[0] ?? node.needs;
176
+ if (states.get(parentId) !== "succeeded")
177
+ return false;
178
+ }
179
+ else if (!instances.every((id) => states.get(id) === "succeeded")) {
180
+ return false;
181
+ }
182
+ return !sequentialPreviousUnsatisfied(dag, stageId, states, completedEnvelopes);
183
+ }
184
+ export function applyCloneForksToSchedule(dag, predecessorId, envelope, states) {
185
+ let current = asDagSnapshot(dag);
186
+ const skippedIds = [];
187
+ const skipPending = (stageId) => {
188
+ if (states.get(stageId) === "pending") {
189
+ states.set(stageId, "skipped");
190
+ skippedIds.push(stageId);
191
+ }
192
+ };
193
+ for (const item of envelope.clone_forks ?? []) {
194
+ if (item.action === "skip") {
195
+ skipPending(item.successor_id);
196
+ for (const desc of collectDownstreamStageIds(current, item.successor_id)) {
197
+ skipPending(desc);
198
+ }
199
+ continue;
200
+ }
201
+ if (item.action !== "fanout")
202
+ continue;
203
+ const existing = definitionInstances(current, item.successor_id);
204
+ if (existing.some((id) => id !== item.successor_id)) {
205
+ continue;
206
+ }
207
+ const { snapshot, instanceIds } = appendCloneInstances(current, {
208
+ catalogId: item.successor_id,
209
+ predecessorId,
210
+ count: item.clones.length,
211
+ });
212
+ current = snapshot;
213
+ states.delete(item.successor_id);
214
+ for (const id of instanceIds) {
215
+ states.set(id, "pending");
216
+ }
217
+ }
218
+ return { dag: current, skippedIds };
219
+ }
220
+ export function applyCloneForksFromEnvelopes(dag, states, completedEnvelopes) {
221
+ let current = asDagSnapshot(dag);
222
+ const skippedIds = [];
223
+ const predecessors = [...current.nodes];
224
+ for (const node of predecessors) {
225
+ const envelope = completedEnvelopes.get(node.id);
226
+ if (!envelope?.clone_forks?.length)
227
+ continue;
228
+ const applied = applyCloneForksToSchedule(current, node.id, envelope, states);
229
+ current = applied.dag;
230
+ skippedIds.push(...applied.skippedIds);
231
+ }
232
+ return { dag: current, skippedIds };
233
+ }
@@ -2,11 +2,12 @@ import type { RunStore, StageSnapshot } from "../runstore/port.js";
2
2
  import type { StageEnvelope } from "../types/envelope.js";
3
3
  import type { LoadedPipeline, ResolvedPipelineDag } from "../types/pipeline.js";
4
4
  import type { StageConfig } from "../types/stage.js";
5
- export declare function buildCompletedEnvelopesFromRun(store: RunStore, runId: string, stages?: StageSnapshot[]): Promise<Map<string, StageEnvelope>>;
5
+ export declare function buildCompletedEnvelopesFromRun(store: RunStore, runId: string, stages?: StageSnapshot[], dag?: ResolvedPipelineDag): Promise<Map<string, StageEnvelope>>;
6
6
  export declare function buildStageConfigById(loaded: LoadedPipeline): Map<string, StageConfig>;
7
7
  export type ResolvePriorEnvelopeResult = {
8
8
  ok: true;
9
9
  prior: StageEnvelope | null;
10
+ joinPriors?: StageEnvelope[];
10
11
  } | {
11
12
  ok: false;
12
13
  reason: string;
@@ -1,15 +1,34 @@
1
- export async function buildCompletedEnvelopesFromRun(store, runId, stages) {
1
+ import { instancesOfDefinition, } from "../runstore/pipelineDagSnapshot.js";
2
+ import { definitionIdForInstance } from "../runstore/stageInstanceId.js";
3
+ function definitionInstances(dag, catalogId) {
4
+ const snapshot = dag;
5
+ if (Array.isArray(snapshot.stage_ids)) {
6
+ return instancesOfDefinition(snapshot, catalogId);
7
+ }
8
+ return dag.nodes.some((n) => n.id === catalogId) ? [catalogId] : [];
9
+ }
10
+ export async function buildCompletedEnvelopesFromRun(store, runId, stages, dag) {
2
11
  const completedEnvelopes = new Map();
3
12
  const snapshotStages = stages ?? (await store.readRun(runId)).stages;
4
13
  for (const snap of snapshotStages) {
5
- if (snap.status !== "succeeded")
14
+ const isCloneInstance = dag !== undefined &&
15
+ definitionIdForInstance(dag, snap.stage_id) !==
16
+ snap.stage_id;
17
+ if (snap.status !== "succeeded" && !(snap.status === "failed" && isCloneInstance)) {
6
18
  continue;
19
+ }
7
20
  try {
8
21
  const envelope = snap.envelope ?? (await store.readEnvelope(runId, snap.stage_id));
9
22
  completedEnvelopes.set(snap.stage_id, envelope);
10
23
  }
11
24
  catch {
12
- // envelope may be missing for corrupt runs; leave map empty
25
+ if (snap.status === "failed" && isCloneInstance) {
26
+ completedEnvelopes.set(snap.stage_id, {
27
+ status: "failure",
28
+ summary: "stage failed",
29
+ artifacts: [],
30
+ });
31
+ }
13
32
  }
14
33
  }
15
34
  return completedEnvelopes;
@@ -35,15 +54,41 @@ export async function resolvePriorEnvelope(options) {
35
54
  if (node.needs === null) {
36
55
  return { ok: true, prior: null };
37
56
  }
57
+ const joinInstances = definitionInstances(options.dag, node.needs);
58
+ if (joinInstances.length > 1) {
59
+ const joinPriors = [];
60
+ for (const id of joinInstances) {
61
+ const fromMap = options.completedEnvelopes.get(id);
62
+ if (fromMap !== undefined) {
63
+ joinPriors.push(structuredClone(fromMap));
64
+ continue;
65
+ }
66
+ if (options.store !== undefined && options.runId !== undefined) {
67
+ try {
68
+ const envelope = await options.store.readEnvelope(options.runId, id);
69
+ joinPriors.push(structuredClone(envelope));
70
+ continue;
71
+ }
72
+ catch {
73
+ return {
74
+ ok: false,
75
+ reason: `missing envelope for clone instance "${id}"`,
76
+ };
77
+ }
78
+ }
79
+ return {
80
+ ok: false,
81
+ reason: `missing envelope for clone instance "${id}"`,
82
+ };
83
+ }
84
+ return { ok: true, prior: null, joinPriors };
85
+ }
38
86
  const parentId = node.needs;
39
87
  const fromMap = options.completedEnvelopes.get(parentId);
40
- if (fromMap !== undefined) {
41
- return { ok: true, prior: structuredClone(fromMap) };
42
- }
43
- if (options.store !== undefined && options.runId !== undefined) {
88
+ let parent = fromMap;
89
+ if (parent === undefined && options.store !== undefined && options.runId !== undefined) {
44
90
  try {
45
- const envelope = await options.store.readEnvelope(options.runId, parentId);
46
- return { ok: true, prior: structuredClone(envelope) };
91
+ parent = await options.store.readEnvelope(options.runId, parentId);
47
92
  }
48
93
  catch {
49
94
  return {
@@ -52,8 +97,30 @@ export async function resolvePriorEnvelope(options) {
52
97
  };
53
98
  }
54
99
  }
55
- return {
56
- ok: false,
57
- reason: `missing envelope for upstream stage "${parentId}"`,
58
- };
100
+ if (parent === undefined) {
101
+ return {
102
+ ok: false,
103
+ reason: `missing envelope for upstream stage "${parentId}"`,
104
+ };
105
+ }
106
+ for (const item of parent.clone_forks ?? []) {
107
+ if (item.action === "once" && item.successor_id === options.stageId) {
108
+ return { ok: true, prior: structuredClone(item.envelope) };
109
+ }
110
+ if (item.action === "fanout") {
111
+ const instanceIds = definitionInstances(options.dag, item.successor_id);
112
+ const index = instanceIds.indexOf(options.stageId);
113
+ if (index >= 0) {
114
+ const clone = item.clones[index];
115
+ if (clone === undefined) {
116
+ return {
117
+ ok: false,
118
+ reason: `missing clone envelope for instance "${options.stageId}"`,
119
+ };
120
+ }
121
+ return { ok: true, prior: structuredClone(clone.envelope) };
122
+ }
123
+ }
124
+ }
125
+ return { ok: true, prior: structuredClone(parent) };
59
126
  }
@@ -1,8 +1,9 @@
1
1
  import type { AgentPort } from "../agent/port.js";
2
- import type { RunStore, StageSnapshot } from "../runstore/port.js";
2
+ import type { RunPipelineDagSnapshot, RunStore, StageSnapshot } from "../runstore/port.js";
3
3
  import type { StageEnvelope } from "../types/envelope.js";
4
4
  import type { LoadedPipeline, ResolvedPipelineDag } from "../types/pipeline.js";
5
5
  import type { TaskFile } from "../types/task.js";
6
+ import { applyCloneForksFromEnvelopes, applyCloneForksToSchedule } from "./cloneSchedule.js";
6
7
  import type { PipelineRunResult } from "./pipelineRunner.js";
7
8
  import type { StageProcessLauncher } from "./stageProcessLauncher.js";
8
9
  import type { StageExecutionMode } from "./stageConcurrency.js";
@@ -39,13 +40,16 @@ type HasWorkOutsideBranchContext = {
39
40
  retryRoots?: ReadonlyMap<string, number>;
40
41
  pendingDeltaStageIds?: ReadonlySet<string>;
41
42
  };
42
- type StageScheduleState = "pending" | "active" | "waiting" | "succeeded" | "failed" | "skipped";
43
+ export type StageScheduleState = "pending" | "active" | "waiting" | "succeeded" | "failed" | "skipped";
43
44
  export type HydratedSchedule = {
44
45
  states: Map<string, StageScheduleState>;
45
46
  completedEnvelopes: Map<string, StageEnvelope>;
46
47
  schedulingHalted: boolean;
47
48
  firstFailureReason: string | undefined;
49
+ dag?: RunPipelineDagSnapshot;
48
50
  };
51
+ export { applyCloneForksFromEnvelopes, applyCloneForksToSchedule, };
52
+ export type { ApplyCloneForksResult } from "./cloneSchedule.js";
49
53
  export declare function snapshotToScheduleState(snap: {
50
54
  status: StageSnapshot["status"];
51
55
  }, overrides: ReadonlyMap<string, StageScheduleState>, stageId: string, executionMode?: StageExecutionMode): StageScheduleState;