stageflow 0.18.0 → 0.23.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 +2 -1
- package/dist/agent/activity.d.ts +9 -0
- package/dist/agent/activityObserver.d.ts +3 -0
- package/dist/agent/activityObserver.js +6 -1
- package/dist/agent/claudeAdapter.js +38 -0
- package/dist/agent/fakeAgent.js +3 -1
- package/dist/agent/piAdapter.js +40 -4
- package/dist/agent/port.d.ts +3 -1
- package/dist/agent/providerInspect.d.ts +19 -0
- package/dist/agent/providerInspect.js +35 -0
- package/dist/agent/stageTimeout.d.ts +9 -0
- package/dist/agent/stageTimeout.js +24 -0
- package/dist/agent/streamLogRedact.d.ts +2 -0
- package/dist/agent/streamLogRedact.js +13 -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/graphCommand.d.ts +15 -0
- package/dist/cli/graphCommand.js +79 -0
- package/dist/cli/graphRender.d.ts +2 -0
- package/dist/cli/graphRender.js +85 -0
- 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/runsCommand.d.ts +4 -8
- package/dist/cli/runsCommand.js +65 -89
- package/dist/cli.js +13 -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 +146 -52
- package/dist/mcp/controlTools.js +94 -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/port.d.ts +3 -1
- package/dist/runstore/port.js +2 -0
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +2 -1
- package/dist/runstore/sqlite/SqliteRunStore.js +15 -1
- 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/forkGeneration.d.ts +4 -0
- package/dist/runtime/forkGeneration.js +16 -0
- package/dist/runtime/pipelineRunner.d.ts +3 -3
- package/dist/runtime/pipelineRunner.js +3 -1
- package/dist/runtime/pipelineScheduler.js +23 -1
- package/dist/runtime/reopenSkipped.d.ts +13 -0
- package/dist/runtime/reopenSkipped.js +56 -0
- package/dist/runtime/resumeReconstruct.js +2 -2
- package/dist/runtime/resumeTimedOut.d.ts +33 -0
- package/dist/runtime/resumeTimedOut.js +165 -0
- package/dist/runtime/runManager.d.ts +3 -1
- package/dist/runtime/runManager.js +110 -14
- 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/stageRecovery.js +2 -2
- package/dist/runtime/stageRunner.js +6 -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.js +9 -2
- 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 +452 -386
- 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/tools/askOperator.d.ts +1 -0
- package/dist/tools/askOperator.js +14 -11
- package/dist/tools/emitStageEnvelope.d.ts +1 -0
- package/dist/tools/emitStageEnvelope.js +3 -1
- package/dist/tools/prepareToolArguments.d.ts +4 -0
- package/dist/tools/prepareToolArguments.js +79 -0
- package/dist/types/pipeline.d.ts +8 -0
- package/dist/ui/assets/{index-DsgVjLUS.css → index-CSsDued1.css} +1 -1
- package/dist/ui/assets/index-Da5EHU6M.js +119 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -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
- package/dist/ui/assets/index-DwKGaQvQ.js +0 -119
package/dist/cli/runsCommand.js
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { resolveGlobalOnlyAgentPort } from "../agent/resolveAgentPort.js";
|
|
4
3
|
import { projectRun } from "../projection/projectRun.js";
|
|
5
4
|
import { waitRun } from "../mcp/waitRun.js";
|
|
6
5
|
import { projectWaitingGates } from "../mcp/waitingGates.js";
|
|
7
|
-
import { resolveOperatorCatalog } from "./operatorCatalog.js";
|
|
8
6
|
import { completeCliRun } from "./runCommand.js";
|
|
9
7
|
import { reportCliRun } from "./runOutput.js";
|
|
8
|
+
import { globalStageflowHome } from "../project/globalHome.js";
|
|
10
9
|
import { createRunStore } from "../runstore/createStore.js";
|
|
11
10
|
import { readStageVerificationHistory } from "../runstore/verificationHistory.js";
|
|
12
|
-
import {
|
|
13
|
-
import { readStageExecutionMode } from "../runtime/stageConcurrency.js";
|
|
14
|
-
import { DEFAULT_PORT } from "../server/createHttpHost.js";
|
|
11
|
+
import { ensureGlobalService, hostBaseUrl, } from "../server/ensureGlobalService.js";
|
|
15
12
|
import { mapRetryStageFailure } from "../server/operatorResults.js";
|
|
13
|
+
import { httpAbandonStage, httpDecideFeedbackLoop, httpDeliverAnswer, httpRecoverManualStageUntilStop, httpRerun, httpResumeTimedOutStage, httpRetryStageUntilStop, httpStopManualRecovery, } from "./hostClient.js";
|
|
16
14
|
import { AskOperatorError, parseAskOperatorAnswer, } from "../tools/askOperator.js";
|
|
17
15
|
export const RUNS_USAGE = `Usage:
|
|
18
16
|
sf runs list [--status created|running|succeeded|failed] [--since <iso>] [--pipeline <id-or-path>] [--json]
|
|
@@ -24,6 +22,7 @@ export const RUNS_USAGE = `Usage:
|
|
|
24
22
|
sf runs answer --run <runId> --stage <stageId> [--answer '<json>'] [--json]
|
|
25
23
|
sf runs feedback-decide --run <runId> --stage <sourceStageId> [--loop <loopId>] --decision extend|continue|abandon [--reason <text>] [--json]
|
|
26
24
|
sf runs retry --run <runId> --stage <stageId> [--json]
|
|
25
|
+
sf runs resume --run <runId> --stage <stageId> [--json]
|
|
27
26
|
sf runs abandon --run <runId> --stage <stageId> [--json]
|
|
28
27
|
sf runs rerun --run <runId> [--json]`;
|
|
29
28
|
const defaultIo = {
|
|
@@ -122,6 +121,8 @@ function flagsFor(subcommand) {
|
|
|
122
121
|
return FEEDBACK_DECIDE_FLAGS;
|
|
123
122
|
case "retry":
|
|
124
123
|
return RETRY_FLAGS;
|
|
124
|
+
case "resume":
|
|
125
|
+
return RETRY_FLAGS;
|
|
125
126
|
case "abandon":
|
|
126
127
|
return ABANDON_FLAGS;
|
|
127
128
|
case "rerun":
|
|
@@ -252,28 +253,6 @@ function resolveRunIdFromFile(fromPath, cwd) {
|
|
|
252
253
|
}
|
|
253
254
|
return runId;
|
|
254
255
|
}
|
|
255
|
-
function hostBaseUrl() {
|
|
256
|
-
return `http://127.0.0.1:${DEFAULT_PORT}`;
|
|
257
|
-
}
|
|
258
|
-
export async function defaultProbeHost() {
|
|
259
|
-
const ac = new AbortController();
|
|
260
|
-
const timer = setTimeout(() => ac.abort(), 1500);
|
|
261
|
-
try {
|
|
262
|
-
const res = await fetch(`${hostBaseUrl()}/api/health`, {
|
|
263
|
-
signal: ac.signal,
|
|
264
|
-
});
|
|
265
|
-
if (res.status !== 200)
|
|
266
|
-
return "down";
|
|
267
|
-
JSON.parse(await res.text());
|
|
268
|
-
return "up";
|
|
269
|
-
}
|
|
270
|
-
catch {
|
|
271
|
-
return "down";
|
|
272
|
-
}
|
|
273
|
-
finally {
|
|
274
|
-
clearTimeout(timer);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
256
|
function printJson(io, payload) {
|
|
278
257
|
io.log(JSON.stringify(payload, null, 2));
|
|
279
258
|
}
|
|
@@ -301,15 +280,22 @@ async function resolveShowWaitRunId(parsed, cwd, io) {
|
|
|
301
280
|
}
|
|
302
281
|
return parsed.runId;
|
|
303
282
|
}
|
|
283
|
+
const MUTATING_SUBCOMMANDS = new Set([
|
|
284
|
+
"answer",
|
|
285
|
+
"feedback-decide",
|
|
286
|
+
"retry",
|
|
287
|
+
"resume",
|
|
288
|
+
"recover",
|
|
289
|
+
"abandon",
|
|
290
|
+
"rerun",
|
|
291
|
+
]);
|
|
304
292
|
export async function runRunsCommand(args, options = {}) {
|
|
305
293
|
const cwd = options.cwd ?? process.cwd();
|
|
306
|
-
const projectRoot = options.projectRoot ?? cwd;
|
|
307
|
-
const isGitProject = options.isGitProject ?? false;
|
|
308
294
|
const out = { ...defaultIo, ...options.io };
|
|
309
|
-
const env = options.env ?? process.env;
|
|
310
295
|
const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY === true;
|
|
311
296
|
const readStdin = options.readStdin ?? (() => readFileSync(0, "utf8"));
|
|
312
|
-
const
|
|
297
|
+
const base = options.hostBaseUrl ?? hostBaseUrl();
|
|
298
|
+
const ensureService = options.ensureService ?? (() => ensureGlobalService());
|
|
313
299
|
let parsed;
|
|
314
300
|
try {
|
|
315
301
|
parsed = parseRunsArgs(args);
|
|
@@ -328,39 +314,23 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
328
314
|
let resolvedStore = options.store;
|
|
329
315
|
const getStore = () => {
|
|
330
316
|
if (resolvedStore === undefined) {
|
|
331
|
-
resolvedStore = createRunStore({ rootDir:
|
|
317
|
+
resolvedStore = createRunStore({ rootDir: globalStageflowHome() });
|
|
332
318
|
}
|
|
333
319
|
return resolvedStore;
|
|
334
320
|
};
|
|
335
321
|
const mutatingIo = out;
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
});
|
|
346
|
-
return new RunManager({
|
|
347
|
-
agent,
|
|
348
|
-
store,
|
|
349
|
-
cwd,
|
|
350
|
-
projectRoot,
|
|
351
|
-
isGitProject,
|
|
352
|
-
operatorCatalog,
|
|
353
|
-
executionMode: readStageExecutionMode(env, "process"),
|
|
354
|
-
});
|
|
355
|
-
};
|
|
356
|
-
const guardHost = async () => {
|
|
357
|
-
const status = await probeHost();
|
|
358
|
-
if (status === "up") {
|
|
359
|
-
out.error(`A Stageflow host is already running at ${hostBaseUrl()}. Use the operator console or MCP instead of mutating the store from the CLI.`);
|
|
322
|
+
if (MUTATING_SUBCOMMANDS.has(parsed.subcommand)) {
|
|
323
|
+
const ensured = await ensureService();
|
|
324
|
+
if (!ensured.ok) {
|
|
325
|
+
if (parsed.json) {
|
|
326
|
+
printJson(out, { error: ensured.message, reason: ensured.reason });
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
out.error(ensured.message);
|
|
330
|
+
}
|
|
360
331
|
return 1;
|
|
361
332
|
}
|
|
362
|
-
|
|
363
|
-
};
|
|
333
|
+
}
|
|
364
334
|
switch (parsed.subcommand) {
|
|
365
335
|
case "list": {
|
|
366
336
|
if (parsed.status === "waiting") {
|
|
@@ -524,9 +494,6 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
524
494
|
}
|
|
525
495
|
}
|
|
526
496
|
case "answer": {
|
|
527
|
-
const blocked = await guardHost();
|
|
528
|
-
if (blocked !== undefined)
|
|
529
|
-
return blocked;
|
|
530
497
|
if (!parsed.runId) {
|
|
531
498
|
return usageError(out, "Missing --run");
|
|
532
499
|
}
|
|
@@ -575,8 +542,7 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
575
542
|
out.error(message);
|
|
576
543
|
return 1;
|
|
577
544
|
}
|
|
578
|
-
const
|
|
579
|
-
const result = await manager.deliverAnswer(parsed.runId, parsed.stageId, answer);
|
|
545
|
+
const result = await httpDeliverAnswer(base, parsed.runId, parsed.stageId, parsedJson);
|
|
580
546
|
if (!result.ok) {
|
|
581
547
|
const payload = { error: result.reason };
|
|
582
548
|
if (result.status !== undefined)
|
|
@@ -598,9 +564,6 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
598
564
|
return 0;
|
|
599
565
|
}
|
|
600
566
|
case "feedback-decide": {
|
|
601
|
-
const blocked = await guardHost();
|
|
602
|
-
if (blocked !== undefined)
|
|
603
|
-
return blocked;
|
|
604
567
|
if (!parsed.runId) {
|
|
605
568
|
return usageError(out, "Missing --run");
|
|
606
569
|
}
|
|
@@ -622,8 +585,7 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
622
585
|
}
|
|
623
586
|
return 1;
|
|
624
587
|
}
|
|
625
|
-
const
|
|
626
|
-
const result = await manager.decideFeedbackLoop(parsed.runId, parsed.stageId, {
|
|
588
|
+
const result = await httpDecideFeedbackLoop(base, parsed.runId, parsed.stageId, {
|
|
627
589
|
decision: parsed.decision,
|
|
628
590
|
...(parsed.loopId !== undefined ? { loopId: parsed.loopId } : {}),
|
|
629
591
|
...(parsed.reason !== undefined ? { reason: parsed.reason } : {}),
|
|
@@ -653,14 +615,10 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
653
615
|
return 0;
|
|
654
616
|
}
|
|
655
617
|
case "retry": {
|
|
656
|
-
const blocked = await guardHost();
|
|
657
|
-
if (blocked !== undefined)
|
|
658
|
-
return blocked;
|
|
659
618
|
if (!parsed.runId || !parsed.stageId) {
|
|
660
619
|
return usageError(out, "Missing --run and/or --stage");
|
|
661
620
|
}
|
|
662
|
-
const
|
|
663
|
-
const result = await manager.retryStageUntilStop(parsed.runId, parsed.stageId);
|
|
621
|
+
const result = await httpRetryStageUntilStop(base, parsed.runId, parsed.stageId);
|
|
664
622
|
if (!result.ok) {
|
|
665
623
|
if (parsed.json) {
|
|
666
624
|
printJson(out, {
|
|
@@ -675,19 +633,45 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
675
633
|
}
|
|
676
634
|
return reportCliRun({ kind: "completion", result: result.pipeline }, { json: parsed.json, io: mutatingIo });
|
|
677
635
|
}
|
|
636
|
+
case "resume": {
|
|
637
|
+
if (!parsed.runId || !parsed.stageId) {
|
|
638
|
+
return usageError(out, "Missing --run and/or --stage");
|
|
639
|
+
}
|
|
640
|
+
const result = await httpResumeTimedOutStage(base, parsed.runId, parsed.stageId);
|
|
641
|
+
if (!result.ok) {
|
|
642
|
+
if (parsed.json) {
|
|
643
|
+
printJson(out, {
|
|
644
|
+
...mapRetryStageFailure(result),
|
|
645
|
+
status: result.status,
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
else {
|
|
649
|
+
out.error(result.reason);
|
|
650
|
+
}
|
|
651
|
+
return 1;
|
|
652
|
+
}
|
|
653
|
+
if (parsed.json) {
|
|
654
|
+
printJson(out, {
|
|
655
|
+
ok: true,
|
|
656
|
+
runId: result.runId,
|
|
657
|
+
stageId: result.stageId,
|
|
658
|
+
attemptIndex: result.attemptIndex,
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
out.log(`ok\t${result.runId}\t${result.stageId}\t${result.attemptIndex}`);
|
|
663
|
+
}
|
|
664
|
+
return 0;
|
|
665
|
+
}
|
|
678
666
|
case "recover": {
|
|
679
|
-
const blocked = await guardHost();
|
|
680
|
-
if (blocked !== undefined)
|
|
681
|
-
return blocked;
|
|
682
667
|
if (!parsed.runId || !parsed.stageId) {
|
|
683
668
|
return usageError(out, "Missing --run and/or --stage");
|
|
684
669
|
}
|
|
685
|
-
const manager = buildManager();
|
|
686
670
|
if (parsed.stop) {
|
|
687
671
|
if (parsed.guidance !== undefined) {
|
|
688
672
|
return usageError(out, "--guidance cannot be used with --stop");
|
|
689
673
|
}
|
|
690
|
-
const result = await
|
|
674
|
+
const result = await httpStopManualRecovery(base, parsed.runId, parsed.stageId);
|
|
691
675
|
if (!result.ok) {
|
|
692
676
|
if (parsed.json) {
|
|
693
677
|
printJson(out, { error: result.reason, status: result.status });
|
|
@@ -705,7 +689,7 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
705
689
|
}
|
|
706
690
|
return 0;
|
|
707
691
|
}
|
|
708
|
-
const result = await
|
|
692
|
+
const result = await httpRecoverManualStageUntilStop(base, parsed.runId, parsed.stageId, parsed.guidance);
|
|
709
693
|
if (!result.ok) {
|
|
710
694
|
if (parsed.json) {
|
|
711
695
|
printJson(out, { ...mapRetryStageFailure(result), status: result.status });
|
|
@@ -718,14 +702,10 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
718
702
|
return reportCliRun({ kind: "completion", result: result.pipeline }, { json: parsed.json, io: mutatingIo });
|
|
719
703
|
}
|
|
720
704
|
case "abandon": {
|
|
721
|
-
const blocked = await guardHost();
|
|
722
|
-
if (blocked !== undefined)
|
|
723
|
-
return blocked;
|
|
724
705
|
if (!parsed.runId || !parsed.stageId) {
|
|
725
706
|
return usageError(out, "Missing --run and/or --stage");
|
|
726
707
|
}
|
|
727
|
-
const
|
|
728
|
-
const result = await manager.abandonStage(parsed.runId, parsed.stageId);
|
|
708
|
+
const result = await httpAbandonStage(base, parsed.runId, parsed.stageId);
|
|
729
709
|
if (!result.ok) {
|
|
730
710
|
const payload = { error: result.reason };
|
|
731
711
|
if (result.status !== undefined)
|
|
@@ -751,14 +731,10 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
751
731
|
return 0;
|
|
752
732
|
}
|
|
753
733
|
case "rerun": {
|
|
754
|
-
const blocked = await guardHost();
|
|
755
|
-
if (blocked !== undefined)
|
|
756
|
-
return blocked;
|
|
757
734
|
if (!parsed.runId) {
|
|
758
735
|
return usageError(out, "Missing --run");
|
|
759
736
|
}
|
|
760
|
-
const
|
|
761
|
-
const started = await manager.rerun(parsed.runId);
|
|
737
|
+
const started = await httpRerun(base, parsed.runId);
|
|
762
738
|
if (!started.ok) {
|
|
763
739
|
return reportCliRun({ kind: "start-failure", started }, { json: parsed.json, io: mutatingIo });
|
|
764
740
|
}
|
package/dist/cli.js
CHANGED
|
@@ -14,6 +14,7 @@ import { RUNS_USAGE, runRunsCommand } from "./cli/runsCommand.js";
|
|
|
14
14
|
import { resolveOperatorCatalog } from "./cli/operatorCatalog.js";
|
|
15
15
|
import { SKILLS_USAGE, runSkillsCommand } from "./cli/skillsCommand.js";
|
|
16
16
|
import { VALIDATE_USAGE, runValidateCommand } from "./cli/validateCommand.js";
|
|
17
|
+
import { GRAPH_USAGE, runGraphCommand } from "./cli/graphCommand.js";
|
|
17
18
|
import { MIGRATE_YAML_USAGE, runMigrateYamlCommand } from "./cli/migrateYamlCommand.js";
|
|
18
19
|
import { createRunStore } from "./runstore/createStore.js";
|
|
19
20
|
import { resolveStageflowContext } from "./project/resolveStageflowContext.js";
|
|
@@ -27,6 +28,7 @@ const USAGE = `Usage:
|
|
|
27
28
|
sf init
|
|
28
29
|
sf run --task <path> --pipeline <path> [--checkout <path>] [--json] [--include stages] [--skip-gates] [--git-sha <sha>] [--ci-pr-url <url>] [--ci-job-url <url>] [--operator-cwd <path>] [--operator-agent-dir <path>]
|
|
29
30
|
sf validate [--pipeline <path>] [--task <path>] [--strict] [--json]
|
|
31
|
+
sf graph --pipeline <path> [--json]
|
|
30
32
|
sf migrate-yaml [path] [--root <path>] [--write] [--json] [--force]
|
|
31
33
|
sf artifact read --run <runId> --path <relPath> [--out <file>]
|
|
32
34
|
sf envelope get --run <runId> --stage <stageId> [--json] [--from <sf-run.json>] [--detect-stage <id>] [--format envelope|handoff]
|
|
@@ -39,6 +41,7 @@ const USAGE = `Usage:
|
|
|
39
41
|
sf runs wait --run <runId> [--from <sf-run.json>] [--until any|waiting|terminal] [--timeout-ms <n>] [--json]
|
|
40
42
|
sf runs answer --run <runId> --stage <stageId> [--answer '<json>'] [--json]
|
|
41
43
|
sf runs retry --run <runId> --stage <stageId> [--json]
|
|
44
|
+
sf runs resume --run <runId> --stage <stageId> [--json]
|
|
42
45
|
sf runs abandon --run <runId> --stage <stageId> [--json]
|
|
43
46
|
sf runs rerun --run <runId> [--json]
|
|
44
47
|
sf ui [--port ${DEFAULT_PORT}] [--mcp-stateless]
|
|
@@ -66,6 +69,8 @@ ${RUN_USAGE}
|
|
|
66
69
|
|
|
67
70
|
${VALIDATE_USAGE}
|
|
68
71
|
|
|
72
|
+
${GRAPH_USAGE}
|
|
73
|
+
|
|
69
74
|
${MIGRATE_YAML_USAGE}
|
|
70
75
|
|
|
71
76
|
${ARTIFACT_USAGE}
|
|
@@ -97,6 +102,7 @@ function parseArgs(argv) {
|
|
|
97
102
|
const command = args[0];
|
|
98
103
|
if (command === "providers" ||
|
|
99
104
|
command === "validate" ||
|
|
105
|
+
command === "graph" ||
|
|
100
106
|
command === "migrate-yaml" ||
|
|
101
107
|
command === "run" ||
|
|
102
108
|
command === "init" ||
|
|
@@ -285,6 +291,12 @@ async function main(argv) {
|
|
|
285
291
|
projectRoot: ctx.projectRoot,
|
|
286
292
|
});
|
|
287
293
|
}
|
|
294
|
+
if (parsed.command === "graph") {
|
|
295
|
+
return runGraphCommand(argv.slice(3), {
|
|
296
|
+
cwd: ctx.invocationCwd,
|
|
297
|
+
projectRoot: ctx.projectRoot,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
288
300
|
if (parsed.command === "migrate-yaml") {
|
|
289
301
|
return runMigrateYamlCommand(argv.slice(3), {
|
|
290
302
|
cwd: ctx.invocationCwd,
|
|
@@ -294,8 +306,6 @@ async function main(argv) {
|
|
|
294
306
|
if (parsed.command === "run") {
|
|
295
307
|
return runRunCommand(argv.slice(3), {
|
|
296
308
|
cwd: ctx.invocationCwd,
|
|
297
|
-
projectRoot: ctx.projectRoot,
|
|
298
|
-
isGitProject: ctx.isGitProject,
|
|
299
309
|
});
|
|
300
310
|
}
|
|
301
311
|
if (parsed.command === "artifact") {
|
|
@@ -319,8 +329,6 @@ async function main(argv) {
|
|
|
319
329
|
if (parsed.command === "runs") {
|
|
320
330
|
return runRunsCommand(argv.slice(3), {
|
|
321
331
|
cwd: ctx.invocationCwd,
|
|
322
|
-
projectRoot: ctx.projectRoot,
|
|
323
|
-
isGitProject: ctx.isGitProject,
|
|
324
332
|
});
|
|
325
333
|
}
|
|
326
334
|
if (parsed.command === "providers") {
|
|
@@ -332,7 +340,7 @@ async function main(argv) {
|
|
|
332
340
|
projectRoot: ctx.projectRoot,
|
|
333
341
|
});
|
|
334
342
|
}
|
|
335
|
-
const store = createRunStore({ rootDir: ctx.
|
|
343
|
+
const store = createRunStore({ rootDir: ctx.globalHome });
|
|
336
344
|
const globalAgent = globalAgentBackendFromManifest(ctx.manifest);
|
|
337
345
|
if (parsed.command === "ui") {
|
|
338
346
|
const mcpStateless = resolveMcpStateless({
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CloneMode, FeedbackLoopConfig, LoadedPipeline, PipelineForkConfig, PipelineNeedEdge, ResolvedPipelineStageNode, RouteIfPredicate } from "../types/pipeline.js";
|
|
2
|
+
import type { StageGateKind } from "../types/stage.js";
|
|
3
|
+
export type PipelineDescribeNeedEdge = {
|
|
4
|
+
id: string;
|
|
5
|
+
on: PipelineNeedEdge["on"];
|
|
6
|
+
if?: RouteIfPredicate;
|
|
7
|
+
};
|
|
8
|
+
export type PipelineDescribeNeeds = string | null | PipelineDescribeNeedEdge[];
|
|
9
|
+
export type PipelineDescribeStage = {
|
|
10
|
+
id: string;
|
|
11
|
+
needs: PipelineDescribeNeeds;
|
|
12
|
+
fork?: PipelineForkConfig;
|
|
13
|
+
gate_kinds?: StageGateKind[];
|
|
14
|
+
clone_cap?: number;
|
|
15
|
+
clone_mode?: CloneMode;
|
|
16
|
+
feedback_loop?: FeedbackLoopConfig;
|
|
17
|
+
entry?: boolean;
|
|
18
|
+
replay_safe?: boolean;
|
|
19
|
+
};
|
|
20
|
+
export type PipelineDescribe = {
|
|
21
|
+
id: string;
|
|
22
|
+
path: string;
|
|
23
|
+
stages: PipelineDescribeStage[];
|
|
24
|
+
};
|
|
25
|
+
export declare function describePipelineNeeds(node: ResolvedPipelineStageNode): PipelineDescribeNeeds;
|
|
26
|
+
export declare function describePipeline(loaded: LoadedPipeline): PipelineDescribe;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { toPipelineNeeds } from "./pipelineNeeds.js";
|
|
2
|
+
export function describePipelineNeeds(node) {
|
|
3
|
+
if (node.needsEdges.length === 0)
|
|
4
|
+
return node.needs;
|
|
5
|
+
const collapsed = toPipelineNeeds(node.needsEdges);
|
|
6
|
+
if (typeof collapsed === "string")
|
|
7
|
+
return collapsed;
|
|
8
|
+
return collapsed.map((edge) => ({
|
|
9
|
+
id: edge.id,
|
|
10
|
+
on: [...edge.on],
|
|
11
|
+
...(edge.if !== undefined ? { if: edge.if } : {}),
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
export function describePipeline(loaded) {
|
|
15
|
+
const gateById = new Map(loaded.stages.map((stage) => [stage.id, stage.gate_kinds]));
|
|
16
|
+
const stages = loaded.dag.nodes.map((node) => {
|
|
17
|
+
const gateKinds = gateById.get(node.id);
|
|
18
|
+
return {
|
|
19
|
+
id: node.id,
|
|
20
|
+
needs: describePipelineNeeds(node),
|
|
21
|
+
...(node.fork !== undefined ? { fork: { ...node.fork } } : {}),
|
|
22
|
+
...(node.clone_cap !== undefined ? { clone_cap: node.clone_cap } : {}),
|
|
23
|
+
...(node.clone_mode !== undefined ? { clone_mode: node.clone_mode } : {}),
|
|
24
|
+
...(node.feedback_loop !== undefined
|
|
25
|
+
? { feedback_loop: { ...node.feedback_loop } }
|
|
26
|
+
: {}),
|
|
27
|
+
...(node.entry !== undefined ? { entry: node.entry } : {}),
|
|
28
|
+
...(node.replay_safe !== undefined ? { replay_safe: node.replay_safe } : {}),
|
|
29
|
+
...(gateKinds !== undefined ? { gate_kinds: [...gateKinds] } : {}),
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
id: loaded.pipeline.id,
|
|
34
|
+
path: loaded.pipelinePath,
|
|
35
|
+
stages,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -1,8 +1,20 @@
|
|
|
1
|
-
import type { LoadedPipeline } from "../types/pipeline.js";
|
|
1
|
+
import type { InlinePipelineDefinition, LoadedPipeline } from "../types/pipeline.js";
|
|
2
2
|
import { type LoadOutcome } from "./loadOutcome.js";
|
|
3
3
|
export type { LoadedPipeline } from "../types/pipeline.js";
|
|
4
4
|
export type { LoadIssue, LoadOutcome } from "./loadOutcome.js";
|
|
5
5
|
export declare function resolvePipelinePath(pipelinePath: string, cwd: string): Promise<string>;
|
|
6
|
+
/** Synthetic `pipelinePath` for a `LoadedPipeline` built from an inline object with no backing file. */
|
|
7
|
+
export declare const INLINE_PIPELINE_PATH = "<inline>";
|
|
8
|
+
export declare function loadPipelineFromObjectOutcome(raw: InlinePipelineDefinition, options?: {
|
|
9
|
+
cwd?: string;
|
|
10
|
+
projectRoot?: string;
|
|
11
|
+
requireIo?: boolean;
|
|
12
|
+
}): Promise<LoadOutcome<LoadedPipeline>>;
|
|
13
|
+
export declare function loadPipelineFromObject(raw: InlinePipelineDefinition, options?: {
|
|
14
|
+
cwd?: string;
|
|
15
|
+
projectRoot?: string;
|
|
16
|
+
requireIo?: boolean;
|
|
17
|
+
}): Promise<LoadedPipeline>;
|
|
6
18
|
export declare function loadPipelineOutcome(nameOrPath: string, options?: {
|
|
7
19
|
cwd?: string;
|
|
8
20
|
projectRoot?: string;
|
|
@@ -2,7 +2,7 @@ import { access } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { compilePayloadSchema, expandPayloadSchemaRefs, isPayloadSchemaSubset, UnresolvedSchemaRefError, } from "../envelope/payloadSchema.js";
|
|
4
4
|
import { loadFailure, loadSuccess } from "./loadOutcome.js";
|
|
5
|
-
import { mergePipelineStages } from "./mergePipelineIncludes.js";
|
|
5
|
+
import { mergeInlinePipelineStages, mergePipelineStages, } from "./mergePipelineIncludes.js";
|
|
6
6
|
import { normalizePipelineStageEntries, toWiringRefs, } from "./normalizePipelineStageEntry.js";
|
|
7
7
|
import { loadStageFromObjectOutcome, loadStageOutcome, afterCompletionForStage } from "./loadStage.js";
|
|
8
8
|
import { materializeStageModels } from "./materializeStageModels.js";
|
|
@@ -92,14 +92,44 @@ export async function resolvePipelinePath(pipelinePath, cwd) {
|
|
|
92
92
|
throw new Error(`Pipeline file not found: ${resolved}. Pass a filesystem path to --pipeline (e.g. pipelines/hello.pipeline.yaml), not a bare pipeline id.`);
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
|
+
/** Synthetic `pipelinePath` for a `LoadedPipeline` built from an inline object with no backing file. */
|
|
96
|
+
export const INLINE_PIPELINE_PATH = "<inline>";
|
|
95
97
|
async function loadPipelineFromPath(pipelinePath, cwd, projectRoot = cwd, requireIo = true) {
|
|
96
98
|
const normalizedPipelinePath = path.normalize(path.resolve(pipelinePath));
|
|
97
99
|
const mergeOutcome = await mergePipelineStages(normalizedPipelinePath);
|
|
98
100
|
if (!mergeOutcome.ok) {
|
|
99
101
|
return loadFailure(mergeOutcome.issues);
|
|
100
102
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
return buildLoadedPipelineFromMerge(mergeOutcome.value, {
|
|
104
|
+
projectRoot,
|
|
105
|
+
requireIo,
|
|
106
|
+
pipelinePath: normalizedPipelinePath,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
export async function loadPipelineFromObjectOutcome(raw, options = {}) {
|
|
110
|
+
const projectRoot = options.projectRoot ?? options.cwd ?? process.cwd();
|
|
111
|
+
const requireIo = options.requireIo !== false;
|
|
112
|
+
const mergeOutcome = await mergeInlinePipelineStages(raw);
|
|
113
|
+
if (!mergeOutcome.ok) {
|
|
114
|
+
return loadFailure(mergeOutcome.issues);
|
|
115
|
+
}
|
|
116
|
+
return buildLoadedPipelineFromMerge(mergeOutcome.value, {
|
|
117
|
+
projectRoot,
|
|
118
|
+
requireIo,
|
|
119
|
+
pipelinePath: INLINE_PIPELINE_PATH,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
export async function loadPipelineFromObject(raw, options = {}) {
|
|
123
|
+
const outcome = await loadPipelineFromObjectOutcome(raw, options);
|
|
124
|
+
if (!outcome.ok) {
|
|
125
|
+
throw new Error(outcome.issues[0].message);
|
|
126
|
+
}
|
|
127
|
+
return outcome.value;
|
|
128
|
+
}
|
|
129
|
+
async function buildLoadedPipelineFromMerge(merge, options) {
|
|
130
|
+
const { projectRoot, requireIo, pipelinePath } = options;
|
|
131
|
+
const { entries: rawEntries, pipelineId, agent: pipelineAgent, model: pipelineModel, schemas: pipelineSchemas, warnings: mergeWarnings, } = merge;
|
|
132
|
+
const ctx = { pipelineId, path: pipelinePath };
|
|
103
133
|
const warnings = [...mergeWarnings];
|
|
104
134
|
const normalizeOutcome = normalizePipelineStageEntries(rawEntries, ctx);
|
|
105
135
|
if (!normalizeOutcome.ok) {
|
|
@@ -274,7 +304,7 @@ async function loadPipelineFromPath(pipelinePath, cwd, projectRoot = cwd, requir
|
|
|
274
304
|
pipeline,
|
|
275
305
|
stages: loadedStages,
|
|
276
306
|
dag,
|
|
277
|
-
pipelinePath
|
|
307
|
+
pipelinePath,
|
|
278
308
|
stageSources,
|
|
279
309
|
}, warnings);
|
|
280
310
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PayloadSchemaMap } from "../envelope/payloadSchema.js";
|
|
2
|
+
import type { InlinePipelineDefinition } from "../types/pipeline.js";
|
|
2
3
|
import { type LoadIssue, type LoadOutcome } from "./loadOutcome.js";
|
|
3
4
|
export type RawMergedEntry = {
|
|
4
5
|
raw: unknown;
|
|
@@ -12,3 +13,13 @@ export declare function mergePipelineStages(rootPath: string): Promise<LoadOutco
|
|
|
12
13
|
schemas?: PayloadSchemaMap;
|
|
13
14
|
warnings: LoadIssue[];
|
|
14
15
|
}>>;
|
|
16
|
+
/** Synthetic `declaringPath` for stage entries with no backing file. */
|
|
17
|
+
export declare const INLINE_DECLARING_PATH = "<inline pipeline>";
|
|
18
|
+
export declare function mergeInlinePipelineStages(raw: InlinePipelineDefinition): Promise<LoadOutcome<{
|
|
19
|
+
entries: RawMergedEntry[];
|
|
20
|
+
pipelineId: string;
|
|
21
|
+
agent?: string;
|
|
22
|
+
model?: string;
|
|
23
|
+
schemas?: PayloadSchemaMap;
|
|
24
|
+
warnings: LoadIssue[];
|
|
25
|
+
}>>;
|
|
@@ -201,6 +201,81 @@ export async function mergePipelineStages(rootPath) {
|
|
|
201
201
|
warnings,
|
|
202
202
|
});
|
|
203
203
|
}
|
|
204
|
+
/** Synthetic `declaringPath` for stage entries with no backing file. */
|
|
205
|
+
export const INLINE_DECLARING_PATH = "<inline pipeline>";
|
|
206
|
+
export async function mergeInlinePipelineStages(raw) {
|
|
207
|
+
if (typeof raw.id !== "string" || !raw.id) {
|
|
208
|
+
return loadFailure([
|
|
209
|
+
{
|
|
210
|
+
code: "pipeline.invalid_shape",
|
|
211
|
+
message: `Invalid inline pipeline: id is required`,
|
|
212
|
+
category: "pipeline",
|
|
213
|
+
},
|
|
214
|
+
]);
|
|
215
|
+
}
|
|
216
|
+
if (!Array.isArray(raw.stages) || raw.stages.length === 0) {
|
|
217
|
+
return loadFailure([
|
|
218
|
+
{
|
|
219
|
+
code: "pipeline.invalid_shape",
|
|
220
|
+
message: `Invalid inline pipeline ${raw.id}: stages must be non-empty`,
|
|
221
|
+
category: "pipeline",
|
|
222
|
+
pipelineId: raw.id,
|
|
223
|
+
},
|
|
224
|
+
]);
|
|
225
|
+
}
|
|
226
|
+
const pipelineId = raw.id;
|
|
227
|
+
const agentField = parseAgentField(raw.agent);
|
|
228
|
+
if (!agentField.ok) {
|
|
229
|
+
return loadFailure([
|
|
230
|
+
{
|
|
231
|
+
code: "pipeline.invalid_agent",
|
|
232
|
+
message: `Invalid inline pipeline ${pipelineId}: ${agentField.message}`,
|
|
233
|
+
category: "pipeline",
|
|
234
|
+
pipelineId,
|
|
235
|
+
},
|
|
236
|
+
]);
|
|
237
|
+
}
|
|
238
|
+
const agent = agentField.value;
|
|
239
|
+
const modelField = parseModelField(raw.model);
|
|
240
|
+
if (!modelField.ok) {
|
|
241
|
+
return loadFailure([
|
|
242
|
+
{
|
|
243
|
+
code: "pipeline.invalid_model",
|
|
244
|
+
message: `Invalid inline pipeline ${pipelineId}: ${modelField.message}`,
|
|
245
|
+
category: "pipeline",
|
|
246
|
+
pipelineId,
|
|
247
|
+
},
|
|
248
|
+
]);
|
|
249
|
+
}
|
|
250
|
+
const model = modelField.value;
|
|
251
|
+
const schemasOutcome = parsePipelineSchemas(raw.schemas, "<inline pipeline>", pipelineId);
|
|
252
|
+
if (!schemasOutcome.ok)
|
|
253
|
+
return schemasOutcome;
|
|
254
|
+
const schemas = schemasOutcome.value;
|
|
255
|
+
const entries = [];
|
|
256
|
+
for (let index = 0; index < raw.stages.length; index++) {
|
|
257
|
+
const entry = raw.stages[index];
|
|
258
|
+
if (isPlainObject(entry) && typeof entry.uses === "string") {
|
|
259
|
+
return loadFailure([
|
|
260
|
+
{
|
|
261
|
+
code: "pipeline.invalid_shape",
|
|
262
|
+
message: `Invalid inline pipeline ${pipelineId}: stage entry at index ${index} has "uses" — inline pipelines cannot reference external stage files, all stage bodies must be inline`,
|
|
263
|
+
category: "pipeline",
|
|
264
|
+
pipelineId,
|
|
265
|
+
},
|
|
266
|
+
]);
|
|
267
|
+
}
|
|
268
|
+
entries.push({ raw: entry, declaringPath: INLINE_DECLARING_PATH });
|
|
269
|
+
}
|
|
270
|
+
return loadSuccess({
|
|
271
|
+
entries,
|
|
272
|
+
pipelineId,
|
|
273
|
+
...(agent !== undefined ? { agent } : {}),
|
|
274
|
+
...(model !== undefined ? { model } : {}),
|
|
275
|
+
...(schemas !== undefined ? { schemas } : {}),
|
|
276
|
+
warnings: [],
|
|
277
|
+
});
|
|
278
|
+
}
|
|
204
279
|
function parsePipelineSchemas(raw, absPath, pipelineId) {
|
|
205
280
|
if (raw === undefined)
|
|
206
281
|
return loadSuccess(undefined);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LoadedPipeline } from "../types/pipeline.js";
|
|
1
|
+
import type { InlinePipelineDefinition, LoadedPipeline } from "../types/pipeline.js";
|
|
2
2
|
import type { LoadIssue } from "./loadOutcome.js";
|
|
3
3
|
export type ValidationSeverity = "error" | "warning";
|
|
4
4
|
export type ValidationCategory = "pipeline" | "stage" | "catalog" | "task";
|
|
@@ -50,7 +50,7 @@ export type LoadPipelineValidatedResult = {
|
|
|
50
50
|
ok: false;
|
|
51
51
|
findings: ValidationFinding[];
|
|
52
52
|
};
|
|
53
|
-
export declare function loadPipelineValidated(nameOrPath: string, options?: {
|
|
53
|
+
export declare function loadPipelineValidated(nameOrPath: string | InlinePipelineDefinition, options?: {
|
|
54
54
|
cwd?: string;
|
|
55
55
|
projectRoot?: string;
|
|
56
56
|
validateStages?: boolean;
|