stageflow 0.19.0 → 0.25.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.
- package/README.md +16 -3
- package/dist/a2a/configDiscovery.d.ts +9 -0
- package/dist/a2a/configDiscovery.js +18 -0
- package/dist/a2a/contracts.d.ts +93 -0
- package/dist/a2a/contracts.js +163 -0
- package/dist/a2a/limits.d.ts +14 -0
- package/dist/a2a/limits.js +29 -0
- package/dist/a2a/registry.d.ts +44 -0
- package/dist/a2a/registry.js +159 -0
- package/dist/a2a/server.d.ts +29 -0
- package/dist/a2a/server.js +341 -0
- package/dist/a2a/service.d.ts +97 -0
- package/dist/a2a/service.js +496 -0
- package/dist/a2a/store.d.ts +106 -0
- package/dist/a2a/store.js +245 -0
- package/dist/agent/activity.d.ts +5 -0
- package/dist/agent/activityObserver.d.ts +3 -0
- package/dist/agent/activityObserver.js +6 -1
- package/dist/agent/piAdapter.js +7 -3
- package/dist/agent/port.d.ts +2 -0
- package/dist/agent/providerInspect.d.ts +19 -0
- package/dist/agent/providerInspect.js +35 -0
- package/dist/agent/streamLogRedact.d.ts +2 -0
- package/dist/agent/streamLogRedact.js +13 -0
- package/dist/cli/a2aCommand.d.ts +8 -0
- package/dist/cli/a2aCommand.js +131 -0
- package/dist/cli/artifactCommand.js +2 -2
- package/dist/cli/envelopeCommand.js +2 -2
- package/dist/cli/exportRunCommand.js +2 -2
- package/dist/cli/hostClient.d.ts +43 -0
- package/dist/cli/hostClient.js +182 -0
- package/dist/cli/providersCommand.js +5 -3
- package/dist/cli/runCommand.d.ts +6 -5
- package/dist/cli/runCommand.js +23 -31
- package/dist/cli/runOutput.d.ts +1 -1
- package/dist/cli/runStageCommand.d.ts +33 -0
- package/dist/cli/runStageCommand.js +407 -0
- package/dist/cli/runsCommand.d.ts +3 -7
- package/dist/cli/runsCommand.js +33 -94
- package/dist/cli.js +20 -5
- package/dist/config/describePipeline.d.ts +26 -0
- package/dist/config/describePipeline.js +37 -0
- package/dist/config/loadPipeline.d.ts +13 -1
- package/dist/config/loadPipeline.js +34 -4
- package/dist/config/mergePipelineIncludes.d.ts +11 -0
- package/dist/config/mergePipelineIncludes.js +75 -0
- package/dist/config/validateCatalog.d.ts +2 -2
- package/dist/config/validateCatalog.js +24 -17
- package/dist/mcp/catalogTools.js +285 -52
- package/dist/mcp/controlTools.js +77 -19
- package/dist/mcp/deps.d.ts +3 -0
- package/dist/mcp/projectMcpTools.d.ts +3 -0
- package/dist/mcp/projectMcpTools.js +25 -0
- package/dist/mcp/providerTools.d.ts +3 -0
- package/dist/mcp/providerTools.js +17 -0
- package/dist/mcp/readArtifact.d.ts +9 -0
- package/dist/mcp/readArtifact.js +11 -0
- package/dist/mcp/server.js +9 -1
- package/dist/mcp/tailStreamLog.d.ts +8 -0
- package/dist/mcp/tailStreamLog.js +28 -0
- package/dist/mcp/toolResults.d.ts +17 -0
- package/dist/mcp/toolResults.js +15 -0
- package/dist/mcp/tools.js +4 -0
- package/dist/mcp/waitRun.js +4 -4
- package/dist/mcp/waitingGates.d.ts +1 -0
- package/dist/mcp/waitingGates.js +3 -2
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/projection/projectRun.d.ts +3 -0
- package/dist/projection/projectRun.js +7 -0
- package/dist/runstore/createStore.d.ts +10 -0
- package/dist/runstore/createStore.js +13 -4
- package/dist/runstore/port.d.ts +17 -2
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +11 -1
- package/dist/runstore/sqlite/SqliteRunStore.js +81 -26
- package/dist/runstore/sqlite/schema.d.ts +1 -1
- package/dist/runstore/sqlite/schema.js +6 -0
- package/dist/runstore/submission.d.ts +11 -0
- package/dist/runstore/submission.js +7 -0
- package/dist/runstore/workspaceLayout.d.ts +1 -0
- package/dist/runstore/workspaceLayout.js +3 -0
- package/dist/runtime/feedbackLoopCoordinator.d.ts +9 -1
- package/dist/runtime/feedbackLoopCoordinator.js +50 -20
- package/dist/runtime/feedbackLoopDecision.js +33 -1
- package/dist/runtime/pipelineRunner.d.ts +5 -3
- package/dist/runtime/pipelineRunner.js +6 -1
- package/dist/runtime/pipelineScheduler.js +32 -15
- package/dist/runtime/reloadRunCatalog.js +7 -1
- package/dist/runtime/resolveEnvelopeRef.d.ts +30 -0
- package/dist/runtime/resolveEnvelopeRef.js +79 -0
- package/dist/runtime/resumeReconstruct.js +2 -2
- package/dist/runtime/runManager.d.ts +24 -2
- package/dist/runtime/runManager.js +83 -19
- package/dist/runtime/settingsFile.d.ts +2 -0
- package/dist/runtime/settingsFile.js +19 -0
- package/dist/runtime/stageAttemptBootstrap.d.ts +3 -0
- package/dist/runtime/stageAttemptBootstrap.js +8 -2
- package/dist/runtime/stageStreamLog.d.ts +17 -0
- package/dist/runtime/stageStreamLog.js +89 -0
- package/dist/runtime/stageWorker.js +2 -1
- package/dist/server/bootstrap.d.ts +2 -0
- package/dist/server/bootstrap.js +22 -4
- package/dist/server/createHttpHost.js +6 -0
- package/dist/server/ensureGlobalService.d.ts +35 -0
- package/dist/server/ensureGlobalService.js +138 -0
- package/dist/server/http.d.ts +20 -1
- package/dist/server/http.js +450 -402
- package/dist/server/mcpHost.js +7 -8
- package/dist/server/operatorResults.d.ts +10 -0
- package/dist/server/operatorResults.js +24 -0
- package/dist/server/providerRoutes.d.ts +0 -6
- package/dist/server/providerRoutes.js +10 -18
- package/dist/types/pipeline.d.ts +8 -0
- package/dist/ui/assets/{index-DMVdl1qB.js → index-Da5EHU6M.js} +5 -5
- package/dist/ui/index.html +1 -1
- package/package.json +2 -1
- package/skills/stageflow/references/control-surface.md +2 -2
- package/skills/stageflow-run/SKILL.md +27 -9
- package/skills/stageflow-run/references/mcp-call.md +1 -1
- package/skills/stageflow-run/scripts/mcp-call.mjs +9 -0
|
@@ -2,6 +2,7 @@ export { stageDir } from "./paths.js";
|
|
|
2
2
|
export declare function guardStageId(stageId: string): void;
|
|
3
3
|
export declare function attemptWorkspaceDir(workspaceDir: string, stageId: string, attempt: number): string;
|
|
4
4
|
export declare function attemptLogPath(workspaceDir: string, stageId: string, attempt: number): string;
|
|
5
|
+
export declare function attemptStreamLogPath(workspaceDir: string, stageId: string, attempt: number): string;
|
|
5
6
|
export declare function attemptEnvelopePath(workspaceDir: string, stageId: string, attempt: number): string;
|
|
6
7
|
export declare function attemptArtifactsDir(workspaceDir: string, stageId: string, attempt: number): string;
|
|
7
8
|
export declare function attemptAgentDir(workspaceDir: string, stageId: string, attempt: number): string;
|
|
@@ -21,6 +21,9 @@ export function attemptWorkspaceDir(workspaceDir, stageId, attempt) {
|
|
|
21
21
|
export function attemptLogPath(workspaceDir, stageId, attempt) {
|
|
22
22
|
return path.join(attemptWorkspaceDir(workspaceDir, stageId, attempt), "log.jsonl");
|
|
23
23
|
}
|
|
24
|
+
export function attemptStreamLogPath(workspaceDir, stageId, attempt) {
|
|
25
|
+
return path.join(attemptWorkspaceDir(workspaceDir, stageId, attempt), "stream.log");
|
|
26
|
+
}
|
|
24
27
|
export function attemptEnvelopePath(workspaceDir, stageId, attempt) {
|
|
25
28
|
return path.join(attemptWorkspaceDir(workspaceDir, stageId, attempt), "envelope.json");
|
|
26
29
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FeedbackLoopContext, StageSessionMode } from "../agent/port.js";
|
|
2
|
-
import type { DeferredFeedbackSendBack, FeedbackLoopRecord, FeedbackReplayRecord, RunStore } from "../runstore/port.js";
|
|
2
|
+
import type { DeferredFeedbackSendBack, FeedbackLoopRecord, FeedbackReplayRecord, FeedbackReplayStagePassStatus, RunStore } from "../runstore/port.js";
|
|
3
3
|
import type { StageEnvelope } from "../types/envelope.js";
|
|
4
4
|
import type { ResolvedPipelineDag, ResolvedPipelineStageNode } from "../types/pipeline.js";
|
|
5
5
|
import type { FeedbackScheduleState } from "./feedbackLoopSchedule.js";
|
|
@@ -38,6 +38,14 @@ export type FeedbackSendBackRejected = {
|
|
|
38
38
|
};
|
|
39
39
|
export type FeedbackSendBackResult = FeedbackSendBackAccepted | FeedbackSendBackIdempotent | FeedbackSendBackWaitingForHuman | FeedbackSendBackRejected;
|
|
40
40
|
export declare function terminalizeCurrentFeedbackReplay(store: RunStore, runId: string, loop: FeedbackLoopRecord, status: Extract<FeedbackReplayRecord["status"], "completed" | "failed" | "waiting_for_human">): Promise<void>;
|
|
41
|
+
export declare function updateFeedbackReplaySourcePass(options: {
|
|
42
|
+
store: RunStore;
|
|
43
|
+
runId: string;
|
|
44
|
+
loop: FeedbackLoopRecord;
|
|
45
|
+
sourceStageId: string;
|
|
46
|
+
status: Extract<FeedbackReplayStagePassStatus, "waiting" | "succeeded" | "failed">;
|
|
47
|
+
envelope?: StageEnvelope;
|
|
48
|
+
}): Promise<void>;
|
|
41
49
|
export declare function acceptFeedbackSendBack(options: {
|
|
42
50
|
store: RunStore;
|
|
43
51
|
runId: string;
|
|
@@ -10,6 +10,33 @@ export async function terminalizeCurrentFeedbackReplay(store, runId, loop, statu
|
|
|
10
10
|
return;
|
|
11
11
|
await store.updateFeedbackReplay(runId, loop.current_replay_id, { status });
|
|
12
12
|
}
|
|
13
|
+
export async function updateFeedbackReplaySourcePass(options) {
|
|
14
|
+
const { store, runId, loop, sourceStageId, status, envelope } = options;
|
|
15
|
+
if (loop.current_replay_id === undefined)
|
|
16
|
+
return;
|
|
17
|
+
const passes = await store.listFeedbackReplayStagePasses(runId, loop.current_replay_id);
|
|
18
|
+
const sourcePass = passes.find((pass) => pass.stage_id === sourceStageId);
|
|
19
|
+
if (sourcePass === undefined)
|
|
20
|
+
return;
|
|
21
|
+
if (sourcePass.status === "succeeded")
|
|
22
|
+
return;
|
|
23
|
+
if (status === "waiting") {
|
|
24
|
+
await store.updateFeedbackReplayStagePass(runId, loop.current_replay_id, sourceStageId, { status: "waiting" });
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (status === "succeeded") {
|
|
28
|
+
await store.updateFeedbackReplayStagePass(runId, loop.current_replay_id, sourceStageId, {
|
|
29
|
+
status: "succeeded",
|
|
30
|
+
finished_at: new Date().toISOString(),
|
|
31
|
+
...(envelope !== undefined ? { emitted_envelope: envelope } : {}),
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
await store.updateFeedbackReplayStagePass(runId, loop.current_replay_id, sourceStageId, {
|
|
36
|
+
status: "failed",
|
|
37
|
+
finished_at: new Date().toISOString(),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
13
40
|
async function resolveResumeSessionAttempt(store, runId, loopId, stageId, fallbackAttempt) {
|
|
14
41
|
const replays = await store.listFeedbackReplays(runId, loopId);
|
|
15
42
|
for (const replay of replays) {
|
|
@@ -182,6 +209,13 @@ export async function acceptFeedbackSendBack(options) {
|
|
|
182
209
|
source_attempt: sourceAttempt,
|
|
183
210
|
};
|
|
184
211
|
await terminalizeCurrentFeedbackReplay(store, runId, loop, "waiting_for_human");
|
|
212
|
+
await updateFeedbackReplaySourcePass({
|
|
213
|
+
store,
|
|
214
|
+
runId,
|
|
215
|
+
loop,
|
|
216
|
+
sourceStageId: sourceNode.id,
|
|
217
|
+
status: "waiting",
|
|
218
|
+
});
|
|
185
219
|
await store.updateFeedbackLoop(runId, loop.loop_id, {
|
|
186
220
|
state: "waiting_for_human",
|
|
187
221
|
deferred_send_back: deferred,
|
|
@@ -205,15 +239,14 @@ export async function acceptFeedbackSendBack(options) {
|
|
|
205
239
|
};
|
|
206
240
|
}
|
|
207
241
|
if (loop.current_replay_id !== undefined) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
242
|
+
await updateFeedbackReplaySourcePass({
|
|
243
|
+
store,
|
|
244
|
+
runId,
|
|
245
|
+
loop,
|
|
246
|
+
sourceStageId: sourceNode.id,
|
|
247
|
+
status: "succeeded",
|
|
248
|
+
envelope,
|
|
249
|
+
});
|
|
217
250
|
await store.updateFeedbackReplay(runId, loop.current_replay_id, {
|
|
218
251
|
status: "completed",
|
|
219
252
|
});
|
|
@@ -301,17 +334,14 @@ export async function markFeedbackLoopContinued(options) {
|
|
|
301
334
|
(entry.state === "active" || entry.state === "waiting_for_human"));
|
|
302
335
|
if (loop === undefined)
|
|
303
336
|
return;
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
}
|
|
337
|
+
await updateFeedbackReplaySourcePass({
|
|
338
|
+
store,
|
|
339
|
+
runId,
|
|
340
|
+
loop,
|
|
341
|
+
sourceStageId,
|
|
342
|
+
status: "succeeded",
|
|
343
|
+
envelope,
|
|
344
|
+
});
|
|
315
345
|
await terminalizeCurrentFeedbackReplay(store, runId, loop, "completed");
|
|
316
346
|
await store.updateFeedbackLoop(runId, loop.loop_id, {
|
|
317
347
|
state: "continued",
|
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
import { terminalizeCurrentFeedbackReplay } from "./feedbackLoopCoordinator.js";
|
|
1
|
+
import { terminalizeCurrentFeedbackReplay, updateFeedbackReplaySourcePass, } from "./feedbackLoopCoordinator.js";
|
|
2
2
|
import { createFeedbackScheduleState } from "./feedbackLoopSchedule.js";
|
|
3
3
|
import { applySendBack, releaseHold, } from "./replayLifecycle.js";
|
|
4
4
|
const DECISION_CONFLICT = "feedback loop decision conflict: no longer waiting_for_human";
|
|
5
|
+
async function appendFeedbackLoopDecided(store, runId, loop, decision, reason) {
|
|
6
|
+
const latest = await store.getLatestStageExecution(runId, loop.source_stage_id);
|
|
7
|
+
const attempt = latest?.attempt;
|
|
8
|
+
const eventOptions = attempt !== undefined ? { attempt } : undefined;
|
|
9
|
+
const trimmed = reason?.trim();
|
|
10
|
+
await store.appendStageEvent(runId, loop.source_stage_id, {
|
|
11
|
+
event: "feedback_loop_decided",
|
|
12
|
+
decision,
|
|
13
|
+
loopId: loop.loop_id,
|
|
14
|
+
...(trimmed ? { reason: trimmed } : {}),
|
|
15
|
+
}, eventOptions);
|
|
16
|
+
}
|
|
5
17
|
async function clearSourceWaiting(store, runId, sourceStageId, nextStatus, reason) {
|
|
6
18
|
const latest = await store.getLatestStageExecution(runId, sourceStageId);
|
|
7
19
|
const attempt = latest?.attempt;
|
|
@@ -96,6 +108,7 @@ export async function resolveFeedbackLoopDecision(options) {
|
|
|
96
108
|
reason: `${DECISION_CONFLICT} (loop=${loop.loop_id})`,
|
|
97
109
|
};
|
|
98
110
|
}
|
|
111
|
+
await appendFeedbackLoopDecided(store, runId, loop, "extend", options.reason);
|
|
99
112
|
await clearSourceWaiting(store, runId, loop.source_stage_id, "succeeded");
|
|
100
113
|
if (schedule === undefined) {
|
|
101
114
|
const applied = await applySendBack({
|
|
@@ -168,8 +181,19 @@ export async function resolveFeedbackLoopDecision(options) {
|
|
|
168
181
|
reason: `${DECISION_CONFLICT} (loop=${loop.loop_id})`,
|
|
169
182
|
};
|
|
170
183
|
}
|
|
184
|
+
await appendFeedbackLoopDecided(store, runId, loop, "continue", options.reason);
|
|
171
185
|
await terminalizeCurrentFeedbackReplay(store, runId, loop, "completed");
|
|
172
186
|
await clearSourceWaiting(store, runId, loop.source_stage_id, "succeeded");
|
|
187
|
+
await updateFeedbackReplaySourcePass({
|
|
188
|
+
store,
|
|
189
|
+
runId,
|
|
190
|
+
loop,
|
|
191
|
+
sourceStageId: loop.source_stage_id,
|
|
192
|
+
status: "succeeded",
|
|
193
|
+
...(deferred !== undefined
|
|
194
|
+
? { envelope: deferred.feedback_envelope }
|
|
195
|
+
: {}),
|
|
196
|
+
});
|
|
173
197
|
if (schedule !== undefined) {
|
|
174
198
|
schedule.states.set(loop.source_stage_id, "succeeded");
|
|
175
199
|
if (schedule.completedEnvelopes.get(loop.source_stage_id) === undefined &&
|
|
@@ -190,8 +214,16 @@ export async function resolveFeedbackLoopDecision(options) {
|
|
|
190
214
|
reason: `${DECISION_CONFLICT} (loop=${loop.loop_id})`,
|
|
191
215
|
};
|
|
192
216
|
}
|
|
217
|
+
await appendFeedbackLoopDecided(store, runId, loop, "abandon", abandonReason);
|
|
193
218
|
await terminalizeCurrentFeedbackReplay(store, runId, loop, "failed");
|
|
194
219
|
await clearSourceWaiting(store, runId, loop.source_stage_id, "failed", abandonReason);
|
|
220
|
+
await updateFeedbackReplaySourcePass({
|
|
221
|
+
store,
|
|
222
|
+
runId,
|
|
223
|
+
loop,
|
|
224
|
+
sourceStageId: loop.source_stage_id,
|
|
225
|
+
status: "failed",
|
|
226
|
+
});
|
|
195
227
|
if (schedule !== undefined) {
|
|
196
228
|
schedule.states.set(loop.source_stage_id, "failed");
|
|
197
229
|
releaseHold({ feedback: schedule.feedback });
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import type { RunSubmission } from "../runstore/submission.js";
|
|
1
2
|
import type { AgentPort } from "../agent/port.js";
|
|
2
3
|
import { type ValidationFinding } from "../config/validateCatalog.js";
|
|
3
4
|
import type { RunStore } from "../runstore/port.js";
|
|
4
5
|
import type { StageEnvelope } from "../types/envelope.js";
|
|
5
6
|
import type { StageHitlController } from "./stageHitl.js";
|
|
6
|
-
import type { LoadedPipeline } from "../types/pipeline.js";
|
|
7
|
+
import type { InlinePipelineDefinition, LoadedPipeline } from "../types/pipeline.js";
|
|
7
8
|
import type { TaskFile } from "../types/task.js";
|
|
8
9
|
import { type StageExecutionMode } from "./stageConcurrency.js";
|
|
9
10
|
import { StageProcessLauncher } from "./stageProcessLauncher.js";
|
|
@@ -57,7 +58,7 @@ export declare function runPipeline(options: {
|
|
|
57
58
|
store: RunStore;
|
|
58
59
|
taskPath?: string;
|
|
59
60
|
taskYaml?: string;
|
|
60
|
-
pipeline: string;
|
|
61
|
+
pipeline: string | InlinePipelineDefinition;
|
|
61
62
|
cwd?: string;
|
|
62
63
|
projectRoot?: string;
|
|
63
64
|
checkoutOverride?: string;
|
|
@@ -70,11 +71,12 @@ export declare function runPipeline(options: {
|
|
|
70
71
|
}): Promise<PipelineRunResult>;
|
|
71
72
|
/** Create the run immediately, then execute stages in the returned promise. */
|
|
72
73
|
export declare function startPipeline(options: {
|
|
74
|
+
submission?: RunSubmission;
|
|
73
75
|
agent: AgentPort;
|
|
74
76
|
store: RunStore;
|
|
75
77
|
taskPath?: string;
|
|
76
78
|
taskYaml?: string;
|
|
77
|
-
pipeline: string;
|
|
79
|
+
pipeline: string | InlinePipelineDefinition;
|
|
78
80
|
cwd?: string;
|
|
79
81
|
projectRoot?: string;
|
|
80
82
|
checkoutOverride?: string;
|
|
@@ -57,12 +57,15 @@ async function preparePipeline(options) {
|
|
|
57
57
|
console.error(`${finding.code}: ${finding.message}`);
|
|
58
58
|
}
|
|
59
59
|
const checkoutRoot = await resolveAndValidateCheckout(task, options.checkoutOverride, options.cwd);
|
|
60
|
-
const pipelinePath =
|
|
60
|
+
const pipelinePath = typeof options.pipeline === "string"
|
|
61
|
+
? normalizeCatalogPath(loaded.pipelinePath)
|
|
62
|
+
: undefined;
|
|
61
63
|
const taskPath = options.taskPath
|
|
62
64
|
? normalizeCatalogPath(path.resolve(options.cwd, options.taskPath))
|
|
63
65
|
: undefined;
|
|
64
66
|
const projectRoot = normalizeCatalogPath(options.projectRoot ?? options.cwd);
|
|
65
67
|
const run = await options.store.createRun({
|
|
68
|
+
submission: options.submission,
|
|
66
69
|
pipelineId: loaded.pipeline.id,
|
|
67
70
|
taskYaml,
|
|
68
71
|
taskId: task.id,
|
|
@@ -74,6 +77,7 @@ async function preparePipeline(options) {
|
|
|
74
77
|
pipelinePath,
|
|
75
78
|
taskPath,
|
|
76
79
|
projectRoot,
|
|
80
|
+
inlinePipeline: typeof options.pipeline === "string" ? undefined : options.pipeline,
|
|
77
81
|
});
|
|
78
82
|
const executionMode = readStageExecutionMode(process.env, options.executionMode);
|
|
79
83
|
const stageProcessLauncher = resolveStageProcessLauncher(executionMode, options.stageProcessLauncher);
|
|
@@ -143,6 +147,7 @@ export async function startPipeline(options) {
|
|
|
143
147
|
const cwd = options.cwd ?? process.cwd();
|
|
144
148
|
const projectRoot = options.projectRoot ?? cwd;
|
|
145
149
|
const prepared = await preparePipeline({
|
|
150
|
+
submission: options.submission,
|
|
146
151
|
agent: options.agent,
|
|
147
152
|
store: options.store,
|
|
148
153
|
taskPath: options.taskPath,
|
|
@@ -220,6 +220,25 @@ export function applyForkSkipsFromEnvelopes(dag, states, completedEnvelopes) {
|
|
|
220
220
|
applyForkChoiceToSchedule(dag, node.id, chosen, states);
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
|
+
async function applyEagerRouteIfSkipsFromEnvelopes(dag, states, completedEnvelopes, persistSkip) {
|
|
224
|
+
for (const [parentId, envelope] of completedEnvelopes) {
|
|
225
|
+
if (states.get(parentId) !== "succeeded")
|
|
226
|
+
continue;
|
|
227
|
+
for (const childId of dag.childrenOf[parentId] ?? []) {
|
|
228
|
+
const child = dag.nodes.find((n) => n.id === childId);
|
|
229
|
+
if (!child)
|
|
230
|
+
continue;
|
|
231
|
+
const result = classifyInboundAfterSuccess(child, parentId, envelope.payload);
|
|
232
|
+
if (result === "missing_field") {
|
|
233
|
+
return parentId;
|
|
234
|
+
}
|
|
235
|
+
if (isEagerSingleParentIfSkip(child, parentId, envelope.payload)) {
|
|
236
|
+
await persistSkip(childId);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
223
242
|
export function applyRetryRootDelta(dag, states, completedEnvelopes, retryRoots, stageId, attempt, clearSchedulingHalt, alreadyLaunchedIds) {
|
|
224
243
|
retryRoots.set(stageId, attempt);
|
|
225
244
|
const downstream = cloneRetryDownstream(dag, [stageId]);
|
|
@@ -472,6 +491,13 @@ export async function runPipelineDag(options) {
|
|
|
472
491
|
await persistSkipPending(id);
|
|
473
492
|
}
|
|
474
493
|
}
|
|
494
|
+
const missingRouteIfParent = await applyEagerRouteIfSkipsFromEnvelopes(dag, states, completedEnvelopes, persistSkipPending);
|
|
495
|
+
if (missingRouteIfParent !== undefined) {
|
|
496
|
+
schedulingHalted = true;
|
|
497
|
+
if (firstFailureReason === undefined) {
|
|
498
|
+
firstFailureReason = `stage "${missingRouteIfParent}": route if field missing from payload`;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
475
501
|
const markSkippedPending = () => {
|
|
476
502
|
for (const node of dag.nodes) {
|
|
477
503
|
if (states.get(node.id) === "pending") {
|
|
@@ -756,22 +782,13 @@ export async function runPipelineDag(options) {
|
|
|
756
782
|
await store.appendStageEvent(run.runId, id, { event: "skipped" });
|
|
757
783
|
}
|
|
758
784
|
}
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
if (
|
|
763
|
-
|
|
764
|
-
const result = classifyInboundAfterSuccess(child, stageId, envelope.payload);
|
|
765
|
-
if (result === "missing_field") {
|
|
766
|
-
schedulingHalted = true;
|
|
767
|
-
if (firstFailureReason === undefined) {
|
|
768
|
-
firstFailureReason = `stage "${stageId}": route if field missing from payload`;
|
|
769
|
-
}
|
|
770
|
-
return;
|
|
771
|
-
}
|
|
772
|
-
if (isEagerSingleParentIfSkip(child, stageId, envelope.payload)) {
|
|
773
|
-
await persistSkipPending(childId);
|
|
785
|
+
const missingParent = await applyEagerRouteIfSkipsFromEnvelopes(dag, states, new Map([[stageId, envelope]]), persistSkipPending);
|
|
786
|
+
if (missingParent !== undefined) {
|
|
787
|
+
schedulingHalted = true;
|
|
788
|
+
if (firstFailureReason === undefined) {
|
|
789
|
+
firstFailureReason = `stage "${missingParent}": route if field missing from payload`;
|
|
774
790
|
}
|
|
791
|
+
return;
|
|
775
792
|
}
|
|
776
793
|
await reopenRunnableSkippedStages(store, run.runId, dag, states, completedEnvelopes);
|
|
777
794
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { access } from "node:fs/promises";
|
|
2
|
-
import { loadPipeline } from "../config/loadPipeline.js";
|
|
2
|
+
import { loadPipeline, loadPipelineFromObject } from "../config/loadPipeline.js";
|
|
3
3
|
import { loadTask } from "../config/loadTask.js";
|
|
4
4
|
import { normalizeCatalogPath } from "../runstore/normalizeCatalogPath.js";
|
|
5
5
|
function missingLocator(field, runId) {
|
|
@@ -33,6 +33,12 @@ async function assertCatalogFileExists(filePath, label) {
|
|
|
33
33
|
}
|
|
34
34
|
export async function reloadPipelineForRun(meta) {
|
|
35
35
|
const projectRoot = requireProjectRoot(meta);
|
|
36
|
+
// An inline-pipeline run (no pipeline_path — nothing was ever written to a
|
|
37
|
+
// catalog file) carries its own pipeline body in meta.inline_pipeline
|
|
38
|
+
// instead, the same way a task without task_path reloads from task_yaml.
|
|
39
|
+
if (meta.inline_pipeline) {
|
|
40
|
+
return loadPipelineFromObject(meta.inline_pipeline, { projectRoot });
|
|
41
|
+
}
|
|
36
42
|
const pipelinePath = requirePipelinePath(meta);
|
|
37
43
|
await assertCatalogFileExists(pipelinePath, "Pipeline");
|
|
38
44
|
return loadPipeline(pipelinePath, { cwd: projectRoot });
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type StoreLookupKind } from "../server/operatorResults.js";
|
|
2
|
+
import type { RunStore } from "../runstore/port.js";
|
|
3
|
+
import type { TaskFile } from "../types/task.js";
|
|
4
|
+
export type EnvelopeRefInput = {
|
|
5
|
+
runId: string;
|
|
6
|
+
stageId: string;
|
|
7
|
+
attempt?: number;
|
|
8
|
+
};
|
|
9
|
+
type EnvelopeRefStore = Pick<RunStore, "readRunMeta" | "readRun" | "readEnvelope">;
|
|
10
|
+
/** Carries enough to map onto either MCP's textResult shape or an A2aApplicationError. */
|
|
11
|
+
export declare class EnvelopeRefError extends Error {
|
|
12
|
+
readonly status: number;
|
|
13
|
+
readonly kind: StoreLookupKind;
|
|
14
|
+
constructor(message: string, status: number, kind: StoreLookupKind);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolves one or more Envelope References (ADR-0002) into a TaskFile for
|
|
18
|
+
* the next standalone stage call — shared by the MCP run_stage tool and the
|
|
19
|
+
* A2A run_stage operation so this resolution logic exists in exactly one
|
|
20
|
+
* place (it was duplicated between them once already; see the reuse finding
|
|
21
|
+
* from this feature's code review).
|
|
22
|
+
*
|
|
23
|
+
* A single ref keeps today's exact shape: goal = its summary, input = its
|
|
24
|
+
* payload verbatim. Multiple refs namespace each resolved payload under its
|
|
25
|
+
* stageId (disambiguated by runId only if two refs share a stageId), so a
|
|
26
|
+
* stage author addresses each source by name in their own io.input.schema
|
|
27
|
+
* instead of one silently overwriting another on a key collision.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveEnvelopeRefsToTask(store: EnvelopeRefStore, refs: EnvelopeRefInput | EnvelopeRefInput[], checkout?: string): Promise<TaskFile>;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { mapStoreLookupError } from "../server/operatorResults.js";
|
|
2
|
+
/** Carries enough to map onto either MCP's textResult shape or an A2aApplicationError. */
|
|
3
|
+
export class EnvelopeRefError extends Error {
|
|
4
|
+
status;
|
|
5
|
+
kind;
|
|
6
|
+
constructor(message, status, kind) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.kind = kind;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
async function resolveOne(store, ref) {
|
|
13
|
+
try {
|
|
14
|
+
await store.readRunMeta(ref.runId);
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
const mapped = mapStoreLookupError(err, { policy: "run" });
|
|
18
|
+
throw new EnvelopeRefError(mapped.error, mapped.status, mapped.kind);
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const detail = await store.readRun(ref.runId);
|
|
22
|
+
if (!detail.stages.some((s) => s.stage_id === ref.stageId)) {
|
|
23
|
+
throw new EnvelopeRefError(`Stage not found: ${ref.stageId}`, 404, "not_found");
|
|
24
|
+
}
|
|
25
|
+
return await store.readEnvelope(ref.runId, ref.stageId, ref.attempt);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
if (err instanceof EnvelopeRefError)
|
|
29
|
+
throw err;
|
|
30
|
+
const mapped = mapStoreLookupError(err, { policy: "envelope" });
|
|
31
|
+
throw new EnvelopeRefError(mapped.error, mapped.status, mapped.kind);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Resolves one or more Envelope References (ADR-0002) into a TaskFile for
|
|
36
|
+
* the next standalone stage call — shared by the MCP run_stage tool and the
|
|
37
|
+
* A2A run_stage operation so this resolution logic exists in exactly one
|
|
38
|
+
* place (it was duplicated between them once already; see the reuse finding
|
|
39
|
+
* from this feature's code review).
|
|
40
|
+
*
|
|
41
|
+
* A single ref keeps today's exact shape: goal = its summary, input = its
|
|
42
|
+
* payload verbatim. Multiple refs namespace each resolved payload under its
|
|
43
|
+
* stageId (disambiguated by runId only if two refs share a stageId), so a
|
|
44
|
+
* stage author addresses each source by name in their own io.input.schema
|
|
45
|
+
* instead of one silently overwriting another on a key collision.
|
|
46
|
+
*/
|
|
47
|
+
export async function resolveEnvelopeRefsToTask(store, refs, checkout) {
|
|
48
|
+
const list = Array.isArray(refs) ? refs : [refs];
|
|
49
|
+
const resolved = [];
|
|
50
|
+
for (const ref of list) {
|
|
51
|
+
resolved.push({ ref, envelope: await resolveOne(store, ref) });
|
|
52
|
+
}
|
|
53
|
+
if (resolved.length === 1) {
|
|
54
|
+
const { ref, envelope } = resolved[0];
|
|
55
|
+
return {
|
|
56
|
+
id: `ref-${ref.runId}-${ref.stageId}`,
|
|
57
|
+
goal: envelope.summary,
|
|
58
|
+
input: envelope.payload ?? {},
|
|
59
|
+
...(checkout ? { checkout } : {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const usedKeys = new Set();
|
|
63
|
+
const input = {};
|
|
64
|
+
const summaryLines = [];
|
|
65
|
+
for (const { ref, envelope } of resolved) {
|
|
66
|
+
let key = ref.stageId;
|
|
67
|
+
if (usedKeys.has(key))
|
|
68
|
+
key = `${ref.stageId}:${ref.runId}`;
|
|
69
|
+
usedKeys.add(key);
|
|
70
|
+
input[key] = envelope.payload ?? {};
|
|
71
|
+
summaryLines.push(`${key}: ${envelope.summary}`);
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
id: `ref-${list.map((r) => r.stageId).join("+")}`,
|
|
75
|
+
goal: summaryLines.join("\n"),
|
|
76
|
+
input,
|
|
77
|
+
...(checkout ? { checkout } : {}),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -33,12 +33,12 @@ export async function loadRunContext(store, runId, cwd) {
|
|
|
33
33
|
}
|
|
34
34
|
export async function reconstructAndContinue(ctx) {
|
|
35
35
|
const { runId, stageId, opaqueAnswer, agent, store, cwd } = ctx;
|
|
36
|
-
const factoryCwd = ctx.factoryCwd ?? cwd;
|
|
37
36
|
try {
|
|
38
37
|
const latestExecution = await store.getLatestStageExecution(runId, stageId);
|
|
39
38
|
const attemptCtx = attemptContext(latestExecution?.attempt ?? 1);
|
|
40
39
|
const attemptOpt = attemptCtx.eventOptions();
|
|
41
40
|
const { meta, task, loaded } = await loadRunContext(store, runId, cwd);
|
|
41
|
+
const factoryCwd = meta.project_root ?? ctx.factoryCwd ?? cwd;
|
|
42
42
|
const definitionId = definitionIdForInstance(meta.pipeline_dag, stageId);
|
|
43
43
|
const stageIndex = loaded.stages.findIndex((s) => s.id === definitionId);
|
|
44
44
|
if (stageIndex < 0) {
|
|
@@ -127,7 +127,7 @@ export async function reconstructAndContinue(ctx) {
|
|
|
127
127
|
run: { runId, workspaceDir },
|
|
128
128
|
agent,
|
|
129
129
|
store,
|
|
130
|
-
cwd,
|
|
130
|
+
cwd: factoryCwd,
|
|
131
131
|
projectRoot: factoryCwd,
|
|
132
132
|
checkoutRoot,
|
|
133
133
|
hitl: ctx.hitl,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { type RunSubmission } from "../runstore/submission.js";
|
|
1
2
|
import type { AgentPort, OpaqueAnswer } from "../agent/port.js";
|
|
3
|
+
import type { InlinePipelineDefinition } from "../types/pipeline.js";
|
|
2
4
|
import { type RunStore } from "../runstore/port.js";
|
|
3
5
|
import type { TaskFile } from "../types/task.js";
|
|
4
6
|
import { type PipelineRunResult } from "./pipelineRunner.js";
|
|
@@ -34,6 +36,23 @@ export type CapacityHealth = {
|
|
|
34
36
|
activeStageProcesses: number;
|
|
35
37
|
maxActiveStageProcesses: number | null;
|
|
36
38
|
};
|
|
39
|
+
export type StartRunOnceResult = {
|
|
40
|
+
ok: true;
|
|
41
|
+
runId: string;
|
|
42
|
+
/** False only for the caller whose submission durably created this run for the first time. */
|
|
43
|
+
reused: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Present when this call observed the run being launched in this process — either it launched
|
|
46
|
+
* the run itself, or it piggybacked on another in-flight call to the same submission key while
|
|
47
|
+
* that launch was still starting. Absent when the submission was already durably committed by
|
|
48
|
+
* an earlier, separate call: `reused: true` does not by itself imply `done` is absent, since a
|
|
49
|
+
* piggybacked call is also `reused: true`. Callers that need completion regardless of which case
|
|
50
|
+
* they hit must still fall back to polling run status by `runId`.
|
|
51
|
+
*/
|
|
52
|
+
done?: Promise<PipelineRunResult>;
|
|
53
|
+
} | Extract<StartRunResult, {
|
|
54
|
+
ok: false;
|
|
55
|
+
}>;
|
|
37
56
|
export type { DeliverAnswerResult };
|
|
38
57
|
export type { RetryStageResult };
|
|
39
58
|
export type AbandonStageResult = {
|
|
@@ -65,6 +84,7 @@ export type StopManualRecoveryResult = {
|
|
|
65
84
|
};
|
|
66
85
|
export declare class RunManager {
|
|
67
86
|
private readonly options;
|
|
87
|
+
private readonly submissionsInFlight;
|
|
68
88
|
private readonly active;
|
|
69
89
|
private readonly checkoutLeases;
|
|
70
90
|
private readonly provisionalIds;
|
|
@@ -124,15 +144,17 @@ export declare class RunManager {
|
|
|
124
144
|
runId: string;
|
|
125
145
|
}>>;
|
|
126
146
|
abandonStage(runId: string, stageId: string): Promise<AbandonStageResult>;
|
|
147
|
+
startRunOnce(input: Parameters<RunManager["startRun"]>[0], submission: RunSubmission): Promise<StartRunOnceResult>;
|
|
148
|
+
private startSubmittedRun;
|
|
127
149
|
startRun(input: StartTaskInput & {
|
|
128
|
-
pipeline: string;
|
|
150
|
+
pipeline: string | InlinePipelineDefinition;
|
|
129
151
|
task?: string | TaskFile;
|
|
130
152
|
checkoutOverride?: string;
|
|
131
153
|
skipGates?: boolean;
|
|
132
154
|
gitSha?: string;
|
|
133
155
|
ciPrUrl?: string;
|
|
134
156
|
ciJobUrl?: string;
|
|
135
|
-
}): Promise<StartRunResult>;
|
|
157
|
+
}, submission?: RunSubmission): Promise<StartRunResult>;
|
|
136
158
|
rerun(runId: string): Promise<StartRunResult>;
|
|
137
159
|
retryStage(runId: string, stageId: string): Promise<RetryStageResult>;
|
|
138
160
|
resumeTimedOutStage(runId: string, stageId: string): Promise<RetryStageResult>;
|