stageflow 0.19.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 +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/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/runsCommand.d.ts +3 -7
- package/dist/cli/runsCommand.js +33 -94
- package/dist/cli.js +1 -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 +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/port.d.ts +3 -1
- 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/pipelineRunner.d.ts +3 -3
- package/dist/runtime/pipelineRunner.js +3 -1
- package/dist/runtime/resumeReconstruct.js +2 -2
- package/dist/runtime/runManager.d.ts +2 -1
- package/dist/runtime/runManager.js +36 -16
- 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.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 +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 +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/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]
|
|
@@ -255,28 +253,6 @@ function resolveRunIdFromFile(fromPath, cwd) {
|
|
|
255
253
|
}
|
|
256
254
|
return runId;
|
|
257
255
|
}
|
|
258
|
-
function hostBaseUrl() {
|
|
259
|
-
return `http://127.0.0.1:${DEFAULT_PORT}`;
|
|
260
|
-
}
|
|
261
|
-
export async function defaultProbeHost() {
|
|
262
|
-
const ac = new AbortController();
|
|
263
|
-
const timer = setTimeout(() => ac.abort(), 1500);
|
|
264
|
-
try {
|
|
265
|
-
const res = await fetch(`${hostBaseUrl()}/api/health`, {
|
|
266
|
-
signal: ac.signal,
|
|
267
|
-
});
|
|
268
|
-
if (res.status !== 200)
|
|
269
|
-
return "down";
|
|
270
|
-
JSON.parse(await res.text());
|
|
271
|
-
return "up";
|
|
272
|
-
}
|
|
273
|
-
catch {
|
|
274
|
-
return "down";
|
|
275
|
-
}
|
|
276
|
-
finally {
|
|
277
|
-
clearTimeout(timer);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
256
|
function printJson(io, payload) {
|
|
281
257
|
io.log(JSON.stringify(payload, null, 2));
|
|
282
258
|
}
|
|
@@ -304,15 +280,22 @@ async function resolveShowWaitRunId(parsed, cwd, io) {
|
|
|
304
280
|
}
|
|
305
281
|
return parsed.runId;
|
|
306
282
|
}
|
|
283
|
+
const MUTATING_SUBCOMMANDS = new Set([
|
|
284
|
+
"answer",
|
|
285
|
+
"feedback-decide",
|
|
286
|
+
"retry",
|
|
287
|
+
"resume",
|
|
288
|
+
"recover",
|
|
289
|
+
"abandon",
|
|
290
|
+
"rerun",
|
|
291
|
+
]);
|
|
307
292
|
export async function runRunsCommand(args, options = {}) {
|
|
308
293
|
const cwd = options.cwd ?? process.cwd();
|
|
309
|
-
const projectRoot = options.projectRoot ?? cwd;
|
|
310
|
-
const isGitProject = options.isGitProject ?? false;
|
|
311
294
|
const out = { ...defaultIo, ...options.io };
|
|
312
|
-
const env = options.env ?? process.env;
|
|
313
295
|
const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY === true;
|
|
314
296
|
const readStdin = options.readStdin ?? (() => readFileSync(0, "utf8"));
|
|
315
|
-
const
|
|
297
|
+
const base = options.hostBaseUrl ?? hostBaseUrl();
|
|
298
|
+
const ensureService = options.ensureService ?? (() => ensureGlobalService());
|
|
316
299
|
let parsed;
|
|
317
300
|
try {
|
|
318
301
|
parsed = parseRunsArgs(args);
|
|
@@ -331,39 +314,23 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
331
314
|
let resolvedStore = options.store;
|
|
332
315
|
const getStore = () => {
|
|
333
316
|
if (resolvedStore === undefined) {
|
|
334
|
-
resolvedStore = createRunStore({ rootDir:
|
|
317
|
+
resolvedStore = createRunStore({ rootDir: globalStageflowHome() });
|
|
335
318
|
}
|
|
336
319
|
return resolvedStore;
|
|
337
320
|
};
|
|
338
321
|
const mutatingIo = out;
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
});
|
|
349
|
-
return new RunManager({
|
|
350
|
-
agent,
|
|
351
|
-
store,
|
|
352
|
-
cwd,
|
|
353
|
-
projectRoot,
|
|
354
|
-
isGitProject,
|
|
355
|
-
operatorCatalog,
|
|
356
|
-
executionMode: readStageExecutionMode(env, "process"),
|
|
357
|
-
});
|
|
358
|
-
};
|
|
359
|
-
const guardHost = async () => {
|
|
360
|
-
const status = await probeHost();
|
|
361
|
-
if (status === "up") {
|
|
362
|
-
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
|
+
}
|
|
363
331
|
return 1;
|
|
364
332
|
}
|
|
365
|
-
|
|
366
|
-
};
|
|
333
|
+
}
|
|
367
334
|
switch (parsed.subcommand) {
|
|
368
335
|
case "list": {
|
|
369
336
|
if (parsed.status === "waiting") {
|
|
@@ -527,9 +494,6 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
527
494
|
}
|
|
528
495
|
}
|
|
529
496
|
case "answer": {
|
|
530
|
-
const blocked = await guardHost();
|
|
531
|
-
if (blocked !== undefined)
|
|
532
|
-
return blocked;
|
|
533
497
|
if (!parsed.runId) {
|
|
534
498
|
return usageError(out, "Missing --run");
|
|
535
499
|
}
|
|
@@ -578,8 +542,7 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
578
542
|
out.error(message);
|
|
579
543
|
return 1;
|
|
580
544
|
}
|
|
581
|
-
const
|
|
582
|
-
const result = await manager.deliverAnswer(parsed.runId, parsed.stageId, answer);
|
|
545
|
+
const result = await httpDeliverAnswer(base, parsed.runId, parsed.stageId, parsedJson);
|
|
583
546
|
if (!result.ok) {
|
|
584
547
|
const payload = { error: result.reason };
|
|
585
548
|
if (result.status !== undefined)
|
|
@@ -601,9 +564,6 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
601
564
|
return 0;
|
|
602
565
|
}
|
|
603
566
|
case "feedback-decide": {
|
|
604
|
-
const blocked = await guardHost();
|
|
605
|
-
if (blocked !== undefined)
|
|
606
|
-
return blocked;
|
|
607
567
|
if (!parsed.runId) {
|
|
608
568
|
return usageError(out, "Missing --run");
|
|
609
569
|
}
|
|
@@ -625,8 +585,7 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
625
585
|
}
|
|
626
586
|
return 1;
|
|
627
587
|
}
|
|
628
|
-
const
|
|
629
|
-
const result = await manager.decideFeedbackLoop(parsed.runId, parsed.stageId, {
|
|
588
|
+
const result = await httpDecideFeedbackLoop(base, parsed.runId, parsed.stageId, {
|
|
630
589
|
decision: parsed.decision,
|
|
631
590
|
...(parsed.loopId !== undefined ? { loopId: parsed.loopId } : {}),
|
|
632
591
|
...(parsed.reason !== undefined ? { reason: parsed.reason } : {}),
|
|
@@ -656,14 +615,10 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
656
615
|
return 0;
|
|
657
616
|
}
|
|
658
617
|
case "retry": {
|
|
659
|
-
const blocked = await guardHost();
|
|
660
|
-
if (blocked !== undefined)
|
|
661
|
-
return blocked;
|
|
662
618
|
if (!parsed.runId || !parsed.stageId) {
|
|
663
619
|
return usageError(out, "Missing --run and/or --stage");
|
|
664
620
|
}
|
|
665
|
-
const
|
|
666
|
-
const result = await manager.retryStageUntilStop(parsed.runId, parsed.stageId);
|
|
621
|
+
const result = await httpRetryStageUntilStop(base, parsed.runId, parsed.stageId);
|
|
667
622
|
if (!result.ok) {
|
|
668
623
|
if (parsed.json) {
|
|
669
624
|
printJson(out, {
|
|
@@ -679,14 +634,10 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
679
634
|
return reportCliRun({ kind: "completion", result: result.pipeline }, { json: parsed.json, io: mutatingIo });
|
|
680
635
|
}
|
|
681
636
|
case "resume": {
|
|
682
|
-
const blocked = await guardHost();
|
|
683
|
-
if (blocked !== undefined)
|
|
684
|
-
return blocked;
|
|
685
637
|
if (!parsed.runId || !parsed.stageId) {
|
|
686
638
|
return usageError(out, "Missing --run and/or --stage");
|
|
687
639
|
}
|
|
688
|
-
const
|
|
689
|
-
const result = await manager.resumeTimedOutStage(parsed.runId, parsed.stageId);
|
|
640
|
+
const result = await httpResumeTimedOutStage(base, parsed.runId, parsed.stageId);
|
|
690
641
|
if (!result.ok) {
|
|
691
642
|
if (parsed.json) {
|
|
692
643
|
printJson(out, {
|
|
@@ -713,18 +664,14 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
713
664
|
return 0;
|
|
714
665
|
}
|
|
715
666
|
case "recover": {
|
|
716
|
-
const blocked = await guardHost();
|
|
717
|
-
if (blocked !== undefined)
|
|
718
|
-
return blocked;
|
|
719
667
|
if (!parsed.runId || !parsed.stageId) {
|
|
720
668
|
return usageError(out, "Missing --run and/or --stage");
|
|
721
669
|
}
|
|
722
|
-
const manager = buildManager();
|
|
723
670
|
if (parsed.stop) {
|
|
724
671
|
if (parsed.guidance !== undefined) {
|
|
725
672
|
return usageError(out, "--guidance cannot be used with --stop");
|
|
726
673
|
}
|
|
727
|
-
const result = await
|
|
674
|
+
const result = await httpStopManualRecovery(base, parsed.runId, parsed.stageId);
|
|
728
675
|
if (!result.ok) {
|
|
729
676
|
if (parsed.json) {
|
|
730
677
|
printJson(out, { error: result.reason, status: result.status });
|
|
@@ -742,7 +689,7 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
742
689
|
}
|
|
743
690
|
return 0;
|
|
744
691
|
}
|
|
745
|
-
const result = await
|
|
692
|
+
const result = await httpRecoverManualStageUntilStop(base, parsed.runId, parsed.stageId, parsed.guidance);
|
|
746
693
|
if (!result.ok) {
|
|
747
694
|
if (parsed.json) {
|
|
748
695
|
printJson(out, { ...mapRetryStageFailure(result), status: result.status });
|
|
@@ -755,14 +702,10 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
755
702
|
return reportCliRun({ kind: "completion", result: result.pipeline }, { json: parsed.json, io: mutatingIo });
|
|
756
703
|
}
|
|
757
704
|
case "abandon": {
|
|
758
|
-
const blocked = await guardHost();
|
|
759
|
-
if (blocked !== undefined)
|
|
760
|
-
return blocked;
|
|
761
705
|
if (!parsed.runId || !parsed.stageId) {
|
|
762
706
|
return usageError(out, "Missing --run and/or --stage");
|
|
763
707
|
}
|
|
764
|
-
const
|
|
765
|
-
const result = await manager.abandonStage(parsed.runId, parsed.stageId);
|
|
708
|
+
const result = await httpAbandonStage(base, parsed.runId, parsed.stageId);
|
|
766
709
|
if (!result.ok) {
|
|
767
710
|
const payload = { error: result.reason };
|
|
768
711
|
if (result.status !== undefined)
|
|
@@ -788,14 +731,10 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
788
731
|
return 0;
|
|
789
732
|
}
|
|
790
733
|
case "rerun": {
|
|
791
|
-
const blocked = await guardHost();
|
|
792
|
-
if (blocked !== undefined)
|
|
793
|
-
return blocked;
|
|
794
734
|
if (!parsed.runId) {
|
|
795
735
|
return usageError(out, "Missing --run");
|
|
796
736
|
}
|
|
797
|
-
const
|
|
798
|
-
const started = await manager.rerun(parsed.runId);
|
|
737
|
+
const started = await httpRerun(base, parsed.runId);
|
|
799
738
|
if (!started.ok) {
|
|
800
739
|
return reportCliRun({ kind: "start-failure", started }, { json: parsed.json, io: mutatingIo });
|
|
801
740
|
}
|
package/dist/cli.js
CHANGED
|
@@ -306,8 +306,6 @@ async function main(argv) {
|
|
|
306
306
|
if (parsed.command === "run") {
|
|
307
307
|
return runRunCommand(argv.slice(3), {
|
|
308
308
|
cwd: ctx.invocationCwd,
|
|
309
|
-
projectRoot: ctx.projectRoot,
|
|
310
|
-
isGitProject: ctx.isGitProject,
|
|
311
309
|
});
|
|
312
310
|
}
|
|
313
311
|
if (parsed.command === "artifact") {
|
|
@@ -331,8 +329,6 @@ async function main(argv) {
|
|
|
331
329
|
if (parsed.command === "runs") {
|
|
332
330
|
return runRunsCommand(argv.slice(3), {
|
|
333
331
|
cwd: ctx.invocationCwd,
|
|
334
|
-
projectRoot: ctx.projectRoot,
|
|
335
|
-
isGitProject: ctx.isGitProject,
|
|
336
332
|
});
|
|
337
333
|
}
|
|
338
334
|
if (parsed.command === "providers") {
|
|
@@ -344,7 +340,7 @@ async function main(argv) {
|
|
|
344
340
|
projectRoot: ctx.projectRoot,
|
|
345
341
|
});
|
|
346
342
|
}
|
|
347
|
-
const store = createRunStore({ rootDir: ctx.
|
|
343
|
+
const store = createRunStore({ rootDir: ctx.globalHome });
|
|
348
344
|
const globalAgent = globalAgentBackendFromManifest(ctx.manifest);
|
|
349
345
|
if (parsed.command === "ui") {
|
|
350
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;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { loadPipelineOutcome, resolvePipelinePath } from "./loadPipeline.js";
|
|
2
|
+
import { INLINE_PIPELINE_PATH, loadPipelineFromObjectOutcome, loadPipelineOutcome, resolvePipelinePath, } from "./loadPipeline.js";
|
|
3
3
|
import { loadStageOutcome } from "./loadStage.js";
|
|
4
4
|
import { loadTaskOutcome } from "./loadTask.js";
|
|
5
5
|
import { readYamlObject } from "./readYamlObject.js";
|
|
@@ -241,23 +241,30 @@ async function runPipelineValidation(nameOrPath, options) {
|
|
|
241
241
|
const { cwd, validateStages } = options;
|
|
242
242
|
const projectRoot = options.projectRoot ?? cwd;
|
|
243
243
|
let pipelinePath;
|
|
244
|
-
|
|
245
|
-
|
|
244
|
+
let outcome;
|
|
245
|
+
if (typeof nameOrPath === "string") {
|
|
246
|
+
try {
|
|
247
|
+
pipelinePath = await resolvePipelinePath(nameOrPath, cwd);
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
251
|
+
return {
|
|
252
|
+
ok: false,
|
|
253
|
+
findings: findingsFromLoadIssues(cwd, path.resolve(cwd, nameOrPath), [
|
|
254
|
+
{
|
|
255
|
+
code: "pipeline.load_error",
|
|
256
|
+
message,
|
|
257
|
+
category: "pipeline",
|
|
258
|
+
},
|
|
259
|
+
]),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
outcome = await loadPipelineOutcome(pipelinePath, { cwd, projectRoot });
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
pipelinePath = path.resolve(cwd, INLINE_PIPELINE_PATH);
|
|
266
|
+
outcome = await loadPipelineFromObjectOutcome(nameOrPath, { cwd, projectRoot });
|
|
246
267
|
}
|
|
247
|
-
catch (err) {
|
|
248
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
249
|
-
return {
|
|
250
|
-
ok: false,
|
|
251
|
-
findings: findingsFromLoadIssues(cwd, path.resolve(cwd, nameOrPath), [
|
|
252
|
-
{
|
|
253
|
-
code: "pipeline.load_error",
|
|
254
|
-
message,
|
|
255
|
-
category: "pipeline",
|
|
256
|
-
},
|
|
257
|
-
]),
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
const outcome = await loadPipelineOutcome(pipelinePath, { cwd, projectRoot });
|
|
261
268
|
const findings = [];
|
|
262
269
|
if (!outcome.ok) {
|
|
263
270
|
findings.push(...findingsFromLoadIssues(cwd, pipelinePath, outcome.issues));
|