stageflow 0.8.0 → 0.12.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 (159) hide show
  1. package/README.md +24 -14
  2. package/dist/agent/activity.d.ts +9 -0
  3. package/dist/agent/agentBackend.d.ts +32 -0
  4. package/dist/agent/agentBackend.js +38 -0
  5. package/dist/agent/claudeActivity.d.ts +15 -0
  6. package/dist/agent/claudeActivity.js +58 -0
  7. package/dist/agent/claudeAdapter.d.ts +5 -0
  8. package/dist/agent/claudeAdapter.js +301 -0
  9. package/dist/agent/claudeSession.d.ts +18 -0
  10. package/dist/agent/claudeSession.js +54 -0
  11. package/dist/agent/claudeTools.d.ts +89 -0
  12. package/dist/agent/claudeTools.js +135 -0
  13. package/dist/agent/fakeAgent.js +24 -4
  14. package/dist/agent/piAdapter.d.ts +6 -3
  15. package/dist/agent/piAdapter.js +129 -21
  16. package/dist/agent/port.d.ts +50 -1
  17. package/dist/agent/resolveAgentPort.d.ts +26 -0
  18. package/dist/agent/resolveAgentPort.js +39 -0
  19. package/dist/cli/runCommand.js +2 -2
  20. package/dist/cli/runsCommand.d.ts +22 -0
  21. package/dist/cli/runsCommand.js +773 -0
  22. package/dist/cli/validateOutput.d.ts +2 -2
  23. package/dist/cli/validateOutput.js +2 -2
  24. package/dist/cli.d.ts +1 -1
  25. package/dist/cli.js +43 -5
  26. package/dist/config/browseCatalog.js +1 -1
  27. package/dist/config/createPipeline.d.ts +2 -1
  28. package/dist/config/createPipeline.js +34 -9
  29. package/dist/config/createStage.js +14 -9
  30. package/dist/config/loadPipeline.js +10 -1
  31. package/dist/config/loadStage.js +138 -2
  32. package/dist/config/loadStageflowManifest.js +14 -1
  33. package/dist/config/mergePipelineIncludes.d.ts +1 -0
  34. package/dist/config/mergePipelineIncludes.js +14 -1
  35. package/dist/config/normalizePipelineStageEntry.d.ts +7 -2
  36. package/dist/config/normalizePipelineStageEntry.js +49 -6
  37. package/dist/config/parseCompletionContract.d.ts +8 -0
  38. package/dist/config/parseCompletionContract.js +218 -0
  39. package/dist/config/parsePreEmitChecks.d.ts +3 -0
  40. package/dist/config/parsePreEmitChecks.js +65 -0
  41. package/dist/config/pipelineNeeds.d.ts +19 -0
  42. package/dist/config/pipelineNeeds.js +151 -0
  43. package/dist/config/pipelineStageKeys.js +16 -1
  44. package/dist/config/resolveForkEmitContext.d.ts +6 -2
  45. package/dist/config/resolveForkEmitContext.js +9 -2
  46. package/dist/config/resolvePipelineDag.d.ts +2 -1
  47. package/dist/config/resolvePipelineDag.js +182 -39
  48. package/dist/config/validateCatalog.d.ts +1 -1
  49. package/dist/config/validateCompletionContract.d.ts +4 -0
  50. package/dist/config/validateCompletionContract.js +52 -0
  51. package/dist/envelope/check.d.ts +1 -1
  52. package/dist/envelope/check.js +86 -13
  53. package/dist/envelope/cloneForks.d.ts +1 -1
  54. package/dist/envelope/cloneForks.js +24 -9
  55. package/dist/envelope/feedbackLoop.d.ts +4 -0
  56. package/dist/envelope/feedbackLoop.js +31 -0
  57. package/dist/envelope/payloadSchema.d.ts +7 -1
  58. package/dist/envelope/payloadSchema.js +170 -13
  59. package/dist/envelope/preEmitChecks.d.ts +29 -0
  60. package/dist/envelope/preEmitChecks.js +57 -0
  61. package/dist/hitl/qaTrail.d.ts +25 -0
  62. package/dist/hitl/qaTrail.js +51 -0
  63. package/dist/index.d.ts +1 -1
  64. package/dist/index.js +1 -1
  65. package/dist/mcp/catalogTools.js +4 -2
  66. package/dist/mcp/controlTools.js +72 -0
  67. package/dist/mcp/waitingGates.d.ts +2 -1
  68. package/dist/mcp/waitingGates.js +17 -1
  69. package/dist/package-meta.d.ts +1 -0
  70. package/dist/package-meta.js +1 -0
  71. package/dist/projection/projectRun.d.ts +4 -1
  72. package/dist/projection/projectRun.js +7 -0
  73. package/dist/prompt/feedbackLoopContext.d.ts +2 -0
  74. package/dist/prompt/feedbackLoopContext.js +17 -0
  75. package/dist/prompt/priorEnvelope.d.ts +2 -2
  76. package/dist/prompt/priorEnvelope.js +4 -1
  77. package/dist/runstore/pipelineDagSnapshot.d.ts +1 -0
  78. package/dist/runstore/pipelineDagSnapshot.js +51 -11
  79. package/dist/runstore/port.d.ts +185 -3
  80. package/dist/runstore/port.js +42 -3
  81. package/dist/runstore/runProjection.d.ts +7 -3
  82. package/dist/runstore/runProjection.js +30 -15
  83. package/dist/runstore/sqlite/SqliteRunStore.d.ts +25 -1
  84. package/dist/runstore/sqlite/SqliteRunStore.js +626 -8
  85. package/dist/runstore/sqlite/schema.d.ts +1 -1
  86. package/dist/runstore/sqlite/schema.js +99 -0
  87. package/dist/runstore/stageInstanceId.d.ts +1 -1
  88. package/dist/runstore/stageInstanceId.js +3 -2
  89. package/dist/runstore/trackProjection.js +59 -27
  90. package/dist/runstore/verificationHistory.d.ts +19 -0
  91. package/dist/runstore/verificationHistory.js +30 -0
  92. package/dist/runtime/cloneSchedule.d.ts +40 -4
  93. package/dist/runtime/cloneSchedule.js +212 -28
  94. package/dist/runtime/completionCheckRunner.d.ts +137 -0
  95. package/dist/runtime/completionCheckRunner.js +365 -0
  96. package/dist/runtime/completionCheckoutBaseline.d.ts +3 -0
  97. package/dist/runtime/completionCheckoutBaseline.js +44 -0
  98. package/dist/runtime/envelopeRouting.d.ts +10 -3
  99. package/dist/runtime/envelopeRouting.js +146 -5
  100. package/dist/runtime/feedbackLoopCoordinator.d.ts +74 -0
  101. package/dist/runtime/feedbackLoopCoordinator.js +446 -0
  102. package/dist/runtime/feedbackLoopDecision.d.ts +44 -0
  103. package/dist/runtime/feedbackLoopDecision.js +207 -0
  104. package/dist/runtime/feedbackLoopRoute.d.ts +5 -0
  105. package/dist/runtime/feedbackLoopRoute.js +13 -0
  106. package/dist/runtime/feedbackLoopSchedule.d.ts +38 -0
  107. package/dist/runtime/feedbackLoopSchedule.js +72 -0
  108. package/dist/runtime/forkGeneration.d.ts +72 -0
  109. package/dist/runtime/forkGeneration.js +295 -0
  110. package/dist/runtime/gitCheckoutCapability.d.ts +7 -0
  111. package/dist/runtime/gitCheckoutCapability.js +97 -0
  112. package/dist/runtime/manualRecoveryState.d.ts +40 -0
  113. package/dist/runtime/manualRecoveryState.js +73 -0
  114. package/dist/runtime/pipelineScheduler.d.ts +13 -1
  115. package/dist/runtime/pipelineScheduler.js +328 -45
  116. package/dist/runtime/replayLifecycle.d.ts +107 -0
  117. package/dist/runtime/replayLifecycle.js +168 -0
  118. package/dist/runtime/runManager.d.ts +61 -1
  119. package/dist/runtime/runManager.js +501 -7
  120. package/dist/runtime/runRetryCoordinator.d.ts +8 -1
  121. package/dist/runtime/runRetryCoordinator.js +47 -10
  122. package/dist/runtime/stageAttemptBootstrap.d.ts +3 -1
  123. package/dist/runtime/stageAttemptBootstrap.js +79 -1
  124. package/dist/runtime/stageProcessLauncher.d.ts +1 -1
  125. package/dist/runtime/stageRecovery.js +1 -1
  126. package/dist/runtime/stageRunner.d.ts +4 -1
  127. package/dist/runtime/stageRunner.js +29 -3
  128. package/dist/runtime/stageWorker.js +69 -2
  129. package/dist/runtime/stageWorkerProtocol.d.ts +1 -1
  130. package/dist/runtime/verifiedStageExecution.d.ts +29 -0
  131. package/dist/runtime/verifiedStageExecution.js +119 -0
  132. package/dist/server/bootstrap.js +1 -0
  133. package/dist/server/http.js +114 -0
  134. package/dist/server/operatorResults.js +2 -0
  135. package/dist/tools/askOperator.d.ts +161 -5
  136. package/dist/tools/askOperator.js +58 -25
  137. package/dist/tools/emitStageEnvelope.d.ts +12 -12
  138. package/dist/tools/emitStageEnvelope.js +112 -24
  139. package/dist/types/completion.d.ts +43 -0
  140. package/dist/types/completion.js +1 -0
  141. package/dist/types/envelope.d.ts +18 -0
  142. package/dist/types/forkChoice.d.ts +9 -4
  143. package/dist/types/forkChoice.js +1 -1
  144. package/dist/types/pipeline.d.ts +39 -2
  145. package/dist/types/preEmitCheck.d.ts +18 -0
  146. package/dist/types/preEmitCheck.js +1 -0
  147. package/dist/types/stage.d.ts +13 -0
  148. package/dist/types/stageflowManifest.d.ts +2 -0
  149. package/dist/ui/assets/index-CXGz4nL7.js +118 -0
  150. package/dist/ui/assets/{index-C3N7MXAC.css → index-uVHiae8s.css} +1 -1
  151. package/dist/ui/index.html +2 -2
  152. package/package.json +2 -1
  153. package/skills/stageflow/references/control-surface.md +4 -2
  154. package/skills/stageflow-author/references/catalog-mapping.md +26 -2
  155. package/skills/stageflow-author/references/catalog-write-conventions.md +1 -1
  156. package/skills/stageflow-run/SKILL.md +56 -25
  157. package/skills/stageflow-run/references/mcp-call.md +2 -2
  158. package/skills/stageflow-run/references/native-question-ui.md +78 -0
  159. package/dist/ui/assets/index-CSoGDA3A.js +0 -118
@@ -0,0 +1,207 @@
1
+ import { terminalizeCurrentFeedbackReplay } from "./feedbackLoopCoordinator.js";
2
+ import { createFeedbackScheduleState } from "./feedbackLoopSchedule.js";
3
+ import { applySendBack, releaseHold, } from "./replayLifecycle.js";
4
+ const DECISION_CONFLICT = "feedback loop decision conflict: no longer waiting_for_human";
5
+ async function clearSourceWaiting(store, runId, sourceStageId, nextStatus, reason) {
6
+ const latest = await store.getLatestStageExecution(runId, sourceStageId);
7
+ const attempt = latest?.attempt;
8
+ const eventOptions = attempt !== undefined ? { attempt } : undefined;
9
+ if (nextStatus === "failed") {
10
+ await store.appendStageEvent(runId, sourceStageId, {
11
+ event: "failed",
12
+ reason: reason ?? "feedback loop abandoned after max_replays",
13
+ }, eventOptions);
14
+ }
15
+ else {
16
+ await store.appendStageEvent(runId, sourceStageId, { event: "succeeded" }, eventOptions);
17
+ }
18
+ if (latest !== null && latest !== undefined) {
19
+ await store.updateStageExecution(runId, sourceStageId, latest.attempt, {
20
+ status: nextStatus,
21
+ ...(nextStatus === "failed"
22
+ ? { finished_at: new Date().toISOString() }
23
+ : {}),
24
+ });
25
+ }
26
+ }
27
+ async function resolveSourceNode(store, runId, sourceStageId, schedule) {
28
+ if (schedule !== undefined) {
29
+ return {
30
+ ok: true,
31
+ dag: schedule.dag,
32
+ sourceNode: schedule.sourceNode,
33
+ };
34
+ }
35
+ const meta = await store.readRunMeta(runId);
36
+ const dag = meta.pipeline_dag;
37
+ if (dag === undefined || dag === null) {
38
+ return {
39
+ ok: false,
40
+ reason: "cannot resolve feedback loop decision without pipeline_dag on run",
41
+ };
42
+ }
43
+ const sourceNode = dag.nodes.find((n) => n.id === sourceStageId);
44
+ if (sourceNode === undefined || sourceNode.feedback_loop === undefined) {
45
+ return {
46
+ ok: false,
47
+ reason: `source stage "${sourceStageId}" missing feedback_loop policy`,
48
+ };
49
+ }
50
+ return { ok: true, dag, sourceNode };
51
+ }
52
+ export async function resolveFeedbackLoopDecision(options) {
53
+ const { store, runId, decision, schedule } = options;
54
+ const loops = await store.listFeedbackLoops(runId);
55
+ const loop = options.loopId !== undefined
56
+ ? loops.find((entry) => entry.loop_id === options.loopId)
57
+ : loops
58
+ .filter((entry) => entry.state === "waiting_for_human")
59
+ .sort((a, b) => b.updated_at.localeCompare(a.updated_at))[0];
60
+ if (loop === undefined) {
61
+ return {
62
+ ok: false,
63
+ reason: options.loopId !== undefined
64
+ ? `feedback loop not found: ${options.loopId}`
65
+ : "no feedback loop is waiting_for_human",
66
+ };
67
+ }
68
+ if (loop.state !== "waiting_for_human") {
69
+ return {
70
+ ok: false,
71
+ reason: `feedback loop "${loop.loop_id}" is not waiting_for_human (state=${loop.state})`,
72
+ };
73
+ }
74
+ if (decision === "extend") {
75
+ const deferred = loop.deferred_send_back;
76
+ if (deferred === undefined) {
77
+ return {
78
+ ok: false,
79
+ reason: `feedback loop "${loop.loop_id}" has no deferred send_back to extend`,
80
+ };
81
+ }
82
+ const resolved = await resolveSourceNode(store, runId, loop.source_stage_id, schedule);
83
+ if (!resolved.ok)
84
+ return resolved;
85
+ const bumpedPolicy = {
86
+ ...loop.policy,
87
+ max_replays: loop.policy.max_replays + 1,
88
+ };
89
+ const casOk = await store.updateFeedbackLoop(runId, loop.loop_id, {
90
+ policy: bumpedPolicy,
91
+ state: "active",
92
+ }, { expectedState: "waiting_for_human" });
93
+ if (!casOk) {
94
+ return {
95
+ ok: false,
96
+ reason: `${DECISION_CONFLICT} (loop=${loop.loop_id})`,
97
+ };
98
+ }
99
+ await clearSourceWaiting(store, runId, loop.source_stage_id, "succeeded");
100
+ if (schedule === undefined) {
101
+ const applied = await applySendBack({
102
+ store,
103
+ runId,
104
+ sourceNode: resolved.sourceNode,
105
+ sourceAttempt: deferred.source_attempt,
106
+ envelope: deferred.feedback_envelope,
107
+ schedule: { feedback: createFeedbackScheduleState() },
108
+ projection: {
109
+ dag: resolved.dag,
110
+ states: new Map(),
111
+ completedEnvelopes: new Map(),
112
+ },
113
+ });
114
+ if (applied.kind === "rejected") {
115
+ return { ok: false, reason: applied.reason };
116
+ }
117
+ if (applied.kind === "waiting_for_human") {
118
+ return {
119
+ ok: false,
120
+ reason: "extend still exceeded max_replays after bump",
121
+ };
122
+ }
123
+ const updated = await store.getFeedbackLoop(runId, loop.loop_id);
124
+ return {
125
+ ok: true,
126
+ effect: "extended",
127
+ loop: updated,
128
+ replayId: applied.replayId,
129
+ };
130
+ }
131
+ const handle = { feedback: schedule.feedback };
132
+ const applied = await applySendBack({
133
+ store,
134
+ runId,
135
+ sourceNode: resolved.sourceNode,
136
+ sourceAttempt: deferred.source_attempt,
137
+ envelope: deferred.feedback_envelope,
138
+ schedule: handle,
139
+ projection: {
140
+ dag: schedule.dag,
141
+ states: schedule.states,
142
+ completedEnvelopes: schedule.completedEnvelopes,
143
+ },
144
+ });
145
+ if (applied.kind === "rejected") {
146
+ return { ok: false, reason: applied.reason };
147
+ }
148
+ if (applied.kind === "waiting_for_human") {
149
+ return {
150
+ ok: false,
151
+ reason: "extend still exceeded max_replays after bump",
152
+ };
153
+ }
154
+ const updated = await store.getFeedbackLoop(runId, loop.loop_id);
155
+ return {
156
+ ok: true,
157
+ effect: "extended",
158
+ loop: updated,
159
+ replayId: applied.replayId,
160
+ };
161
+ }
162
+ if (decision === "continue") {
163
+ const deferred = loop.deferred_send_back;
164
+ const casOk = await store.updateFeedbackLoop(runId, loop.loop_id, { state: "continued" }, { expectedState: "waiting_for_human" });
165
+ if (!casOk) {
166
+ return {
167
+ ok: false,
168
+ reason: `${DECISION_CONFLICT} (loop=${loop.loop_id})`,
169
+ };
170
+ }
171
+ await terminalizeCurrentFeedbackReplay(store, runId, loop, "completed");
172
+ await clearSourceWaiting(store, runId, loop.source_stage_id, "succeeded");
173
+ if (schedule !== undefined) {
174
+ schedule.states.set(loop.source_stage_id, "succeeded");
175
+ if (schedule.completedEnvelopes.get(loop.source_stage_id) === undefined &&
176
+ deferred !== undefined) {
177
+ schedule.completedEnvelopes.set(loop.source_stage_id, deferred.feedback_envelope);
178
+ }
179
+ releaseHold({ feedback: schedule.feedback });
180
+ }
181
+ const updated = await store.getFeedbackLoop(runId, loop.loop_id);
182
+ return { ok: true, effect: "continued", loop: updated };
183
+ }
184
+ const abandonReason = options.reason?.trim() ||
185
+ `feedback loop abandoned after max_replays (${loop.policy.max_replays})`;
186
+ const casOk = await store.updateFeedbackLoop(runId, loop.loop_id, { state: "abandoned" }, { expectedState: "waiting_for_human" });
187
+ if (!casOk) {
188
+ return {
189
+ ok: false,
190
+ reason: `${DECISION_CONFLICT} (loop=${loop.loop_id})`,
191
+ };
192
+ }
193
+ await terminalizeCurrentFeedbackReplay(store, runId, loop, "failed");
194
+ await clearSourceWaiting(store, runId, loop.source_stage_id, "failed", abandonReason);
195
+ if (schedule !== undefined) {
196
+ schedule.states.set(loop.source_stage_id, "failed");
197
+ releaseHold({ feedback: schedule.feedback });
198
+ }
199
+ await store.updateRunStatus(runId, "failed");
200
+ const updated = await store.getFeedbackLoop(runId, loop.loop_id);
201
+ return {
202
+ ok: true,
203
+ effect: "abandoned",
204
+ loop: updated,
205
+ reason: abandonReason,
206
+ };
207
+ }
@@ -0,0 +1,5 @@
1
+ import type { ResolvedPipelineDag, ResolvedPipelineStageNode } from "../types/pipeline.js";
2
+ /** Persistent stages on target→source inclusive, forward order (ancestors slice + source). */
3
+ export declare function feedbackRouteStageIds(sourceNode: ResolvedPipelineStageNode, targetId: string): string[];
4
+ /** All DAG descendants of the feedback-loop source (exclusive of source). */
5
+ export declare function postSourceStageIds(dag: ResolvedPipelineDag, sourceId: string): string[];
@@ -0,0 +1,13 @@
1
+ import { collectDownstreamStageIds } from "./dagTraversal.js";
2
+ /** Persistent stages on target→source inclusive, forward order (ancestors slice + source). */
3
+ export function feedbackRouteStageIds(sourceNode, targetId) {
4
+ const targetIndex = sourceNode.ancestors.indexOf(targetId);
5
+ if (targetIndex < 0) {
6
+ throw new Error(`feedback-loop target "${targetId}" is not an ancestor of "${sourceNode.id}"`);
7
+ }
8
+ return [...sourceNode.ancestors.slice(targetIndex), sourceNode.id];
9
+ }
10
+ /** All DAG descendants of the feedback-loop source (exclusive of source). */
11
+ export function postSourceStageIds(dag, sourceId) {
12
+ return [...collectDownstreamStageIds(dag, sourceId)];
13
+ }
@@ -0,0 +1,38 @@
1
+ import type { FeedbackLoopContext, StageSessionMode } from "../agent/port.js";
2
+ import type { StageEnvelope } from "../types/envelope.js";
3
+ import type { ResolvedPipelineDag } from "../types/pipeline.js";
4
+ type StageScheduleState = "pending" | "active" | "waiting" | "succeeded" | "failed" | "skipped";
5
+ export type FeedbackScheduleState = {
6
+ activeHoldStageIds: Set<string>;
7
+ contextsByStageId: Map<string, FeedbackLoopContext>;
8
+ sessionModeByStageId: Map<string, StageSessionMode>;
9
+ priorAttemptByStageId: Map<string, number>;
10
+ launchAttemptByStageId: Map<string, number>;
11
+ activeLoopId?: string;
12
+ activeReplayId?: string;
13
+ sourceStageId?: string;
14
+ /**
15
+ * Fork-parent → active cohort clone ids. An empty set means the prior
16
+ * cohort was superseded and a fresh fan-out has not minted yet (joins wait).
17
+ */
18
+ activeCloneIdsByForkParent: Map<string, Set<string>>;
19
+ };
20
+ export declare function createFeedbackScheduleState(): FeedbackScheduleState;
21
+ export declare function applyFeedbackSendBackToSchedule(options: {
22
+ dag: ResolvedPipelineDag;
23
+ sourceId: string;
24
+ routeStageIds: string[];
25
+ states: Map<string, StageScheduleState>;
26
+ completedEnvelopes: Map<string, StageEnvelope>;
27
+ feedback: FeedbackScheduleState;
28
+ loopId: string;
29
+ replayId: string;
30
+ contextsByStageId: Map<string, FeedbackLoopContext>;
31
+ sessionModeByStageId: Map<string, StageSessionMode>;
32
+ priorAttemptByStageId: Map<string, number>;
33
+ launchAttemptByStageId: Map<string, number>;
34
+ supersededCloneIds?: ReadonlySet<string>;
35
+ }): string[];
36
+ export declare function releaseFeedbackHold(feedback: FeedbackScheduleState): void;
37
+ export declare function isFeedbackHeld(feedback: FeedbackScheduleState, stageId: string): boolean;
38
+ export {};
@@ -0,0 +1,72 @@
1
+ import { postSourceStageIds } from "./feedbackLoopRoute.js";
2
+ export function createFeedbackScheduleState() {
3
+ return {
4
+ activeHoldStageIds: new Set(),
5
+ contextsByStageId: new Map(),
6
+ sessionModeByStageId: new Map(),
7
+ priorAttemptByStageId: new Map(),
8
+ launchAttemptByStageId: new Map(),
9
+ activeCloneIdsByForkParent: new Map(),
10
+ };
11
+ }
12
+ export function applyFeedbackSendBackToSchedule(options) {
13
+ const { dag, sourceId, routeStageIds, states, completedEnvelopes, feedback, } = options;
14
+ const routeSet = new Set(routeStageIds);
15
+ const retiredSkippedIds = [];
16
+ for (const cloneId of options.supersededCloneIds ?? []) {
17
+ if (routeSet.has(cloneId))
18
+ continue;
19
+ const state = states.get(cloneId);
20
+ if (state !== "skipped") {
21
+ states.set(cloneId, "skipped");
22
+ retiredSkippedIds.push(cloneId);
23
+ }
24
+ completedEnvelopes.delete(cloneId);
25
+ for (const desc of postSourceStageIds(dag, cloneId)) {
26
+ if (routeSet.has(desc))
27
+ continue;
28
+ if (feedback.activeHoldStageIds?.has(desc))
29
+ continue;
30
+ const descState = states.get(desc);
31
+ if (descState === "pending" || descState === "succeeded" || descState === "active") {
32
+ states.set(desc, "skipped");
33
+ retiredSkippedIds.push(desc);
34
+ completedEnvelopes.delete(desc);
35
+ }
36
+ }
37
+ }
38
+ for (const stageId of routeStageIds) {
39
+ states.set(stageId, "pending");
40
+ completedEnvelopes.delete(stageId);
41
+ }
42
+ feedback.activeHoldStageIds = new Set(postSourceStageIds(dag, sourceId));
43
+ feedback.contextsByStageId = new Map(options.contextsByStageId);
44
+ feedback.sessionModeByStageId = new Map(options.sessionModeByStageId);
45
+ feedback.priorAttemptByStageId = new Map(options.priorAttemptByStageId);
46
+ feedback.launchAttemptByStageId = new Map(options.launchAttemptByStageId);
47
+ feedback.activeLoopId = options.loopId;
48
+ feedback.activeReplayId = options.replayId;
49
+ feedback.sourceStageId = sourceId;
50
+ for (const cloneId of options.supersededCloneIds ?? []) {
51
+ const node = dag.nodes.find((n) => n.id === cloneId);
52
+ const forkParentId = node?.needs;
53
+ if (forkParentId !== undefined && forkParentId !== null) {
54
+ feedback.activeCloneIdsByForkParent.set(forkParentId, new Set());
55
+ }
56
+ }
57
+ return retiredSkippedIds;
58
+ }
59
+ export function releaseFeedbackHold(feedback) {
60
+ feedback.activeHoldStageIds.clear();
61
+ feedback.contextsByStageId.clear();
62
+ feedback.sessionModeByStageId.clear();
63
+ feedback.priorAttemptByStageId.clear();
64
+ feedback.launchAttemptByStageId.clear();
65
+ feedback.activeLoopId = undefined;
66
+ feedback.activeReplayId = undefined;
67
+ feedback.sourceStageId = undefined;
68
+ feedback.activeCloneIdsByForkParent.clear();
69
+ }
70
+ export function isFeedbackHeld(feedback, stageId) {
71
+ return feedback.activeHoldStageIds.has(stageId);
72
+ }
@@ -0,0 +1,72 @@
1
+ import type { ForkGenerationRecord, RunStore } from "../runstore/port.js";
2
+ import type { ResolvedPipelineDag } from "../types/pipeline.js";
3
+ /** Persistent route stages that fan out to clonable successors (or already have instances). */
4
+ export declare function routeRelatedForkParentIds(dag: ResolvedPipelineDag, routeStageIds: readonly string[]): string[];
5
+ /** Clone instance ids currently under clonable successors of a fork parent. */
6
+ export declare function cloneInstanceIdsForForkParent(dag: ResolvedPipelineDag, forkParentStageId: string): string[];
7
+ export declare function backfillActiveGenerationIfNeeded(store: RunStore, runId: string, options: {
8
+ dag: ResolvedPipelineDag;
9
+ forkParentStageId: string;
10
+ }): Promise<ForkGenerationRecord | undefined>;
11
+ export type ActiveCohort = {
12
+ kind: "untracked";
13
+ } | {
14
+ kind: "awaiting_mint";
15
+ } | {
16
+ kind: "active";
17
+ cloneIds: ReadonlySet<string>;
18
+ };
19
+ export declare function activeCohortFromCloneIds(activeCloneIds: Set<string> | ReadonlySet<string> | null | undefined): ActiveCohort;
20
+ export declare function cloneIdsFromActiveCohort(cohort: ActiveCohort): Set<string> | null;
21
+ /** Resolve the fork parent that fans out into instances of `needsStageId`. */
22
+ export declare function forkParentForNeedsStage(dag: ResolvedPipelineDag, needsStageId: string): string | null;
23
+ export declare function supersedeActiveGenerations(store: RunStore, runId: string, options: {
24
+ dag: ResolvedPipelineDag;
25
+ forkParentStageIds: readonly string[];
26
+ }): Promise<{
27
+ supersededCloneIds: Set<string>;
28
+ supersededGenerations: ForkGenerationRecord[];
29
+ }>;
30
+ export declare function retireCohortsForRoute(options: {
31
+ store: RunStore;
32
+ runId: string;
33
+ dag: ResolvedPipelineDag;
34
+ routeStageIds: readonly string[];
35
+ }): Promise<{
36
+ supersededCloneIds: ReadonlySet<string>;
37
+ forkParentIds: string[];
38
+ }>;
39
+ export declare function collectSupersededCloneIdsForRoute(options: {
40
+ store: RunStore;
41
+ runId: string;
42
+ dag: ResolvedPipelineDag;
43
+ routeStageIds: readonly string[];
44
+ }): Promise<ReadonlySet<string>>;
45
+ export declare function createGenerationForFanout(store: RunStore, runId: string, options: {
46
+ replayId?: string;
47
+ forkParentStageId: string;
48
+ cloneStageIds: string[];
49
+ }): Promise<ForkGenerationRecord>;
50
+ export declare function mintCohortForFanout(options: {
51
+ store: RunStore;
52
+ runId: string;
53
+ replayId?: string;
54
+ forkParent: string;
55
+ cloneStageIds: string[];
56
+ }): Promise<ForkGenerationRecord>;
57
+ export declare function activeCloneIdsForParent(store: RunStore, runId: string, forkParentStageId: string): Promise<Set<string> | null>;
58
+ export declare function activeCohortForParent(store: RunStore, runId: string, forkParentStageId: string): Promise<ActiveCohort>;
59
+ export declare function selectActiveInput(options: {
60
+ store: RunStore;
61
+ runId: string;
62
+ dag: ResolvedPipelineDag;
63
+ needsStageId: string;
64
+ scheduleOverride?: ReadonlyMap<string, ReadonlySet<string>>;
65
+ }): Promise<ActiveCohort>;
66
+ export declare function filterJoinInputs(allInstanceIds: readonly string[], cohort: ActiveCohort): string[];
67
+ export declare function filterJoinInstanceIds(allInstanceIds: readonly string[], activeCloneIds: Set<string> | null): string[];
68
+ /**
69
+ * During an active feedback replay, a fork_choice must keep at least one
70
+ * chosen branch that can still reach the replay source stage.
71
+ */
72
+ export declare function forkChoicePreservesReplaySource(dag: ResolvedPipelineDag, chosen: ReadonlySet<string>, sourceStageId: string): boolean;