stageflow 0.9.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/agent/activity.d.ts +9 -0
- package/dist/agent/agentBackend.d.ts +32 -0
- package/dist/agent/agentBackend.js +38 -0
- package/dist/agent/claudeActivity.d.ts +15 -0
- package/dist/agent/claudeActivity.js +58 -0
- package/dist/agent/claudeAdapter.d.ts +5 -0
- package/dist/agent/claudeAdapter.js +301 -0
- package/dist/agent/claudeSession.d.ts +18 -0
- package/dist/agent/claudeSession.js +54 -0
- package/dist/agent/claudeTools.d.ts +89 -0
- package/dist/agent/claudeTools.js +135 -0
- package/dist/agent/fakeAgent.js +24 -4
- package/dist/agent/piAdapter.d.ts +6 -3
- package/dist/agent/piAdapter.js +129 -21
- package/dist/agent/port.d.ts +50 -1
- package/dist/agent/resolveAgentPort.d.ts +26 -0
- package/dist/agent/resolveAgentPort.js +39 -0
- package/dist/cli/runCommand.js +2 -2
- package/dist/cli/runsCommand.d.ts +1 -1
- package/dist/cli/runsCommand.js +184 -2
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +11 -5
- package/dist/config/browseCatalog.js +1 -1
- package/dist/config/createPipeline.d.ts +2 -1
- package/dist/config/createPipeline.js +34 -9
- package/dist/config/createStage.js +14 -9
- package/dist/config/loadPipeline.js +10 -1
- package/dist/config/loadStage.js +138 -2
- package/dist/config/loadStageflowManifest.js +14 -1
- package/dist/config/mergePipelineIncludes.d.ts +1 -0
- package/dist/config/mergePipelineIncludes.js +14 -1
- package/dist/config/normalizePipelineStageEntry.d.ts +7 -2
- package/dist/config/normalizePipelineStageEntry.js +49 -6
- package/dist/config/parseCompletionContract.d.ts +8 -0
- package/dist/config/parseCompletionContract.js +218 -0
- package/dist/config/parsePreEmitChecks.d.ts +3 -0
- package/dist/config/parsePreEmitChecks.js +65 -0
- package/dist/config/pipelineNeeds.d.ts +19 -0
- package/dist/config/pipelineNeeds.js +151 -0
- package/dist/config/pipelineStageKeys.js +16 -1
- package/dist/config/resolveForkEmitContext.d.ts +6 -2
- package/dist/config/resolveForkEmitContext.js +9 -2
- package/dist/config/resolvePipelineDag.d.ts +2 -1
- package/dist/config/resolvePipelineDag.js +182 -39
- package/dist/config/validateCatalog.d.ts +1 -1
- package/dist/config/validateCompletionContract.d.ts +4 -0
- package/dist/config/validateCompletionContract.js +52 -0
- package/dist/envelope/check.d.ts +1 -1
- package/dist/envelope/check.js +86 -13
- package/dist/envelope/cloneForks.d.ts +1 -1
- package/dist/envelope/cloneForks.js +24 -9
- package/dist/envelope/feedbackLoop.d.ts +4 -0
- package/dist/envelope/feedbackLoop.js +31 -0
- package/dist/envelope/payloadSchema.d.ts +7 -1
- package/dist/envelope/payloadSchema.js +170 -13
- package/dist/envelope/preEmitChecks.d.ts +29 -0
- package/dist/envelope/preEmitChecks.js +57 -0
- package/dist/hitl/qaTrail.d.ts +25 -0
- package/dist/hitl/qaTrail.js +51 -0
- package/dist/mcp/catalogTools.js +4 -2
- package/dist/mcp/controlTools.js +72 -0
- package/dist/mcp/waitingGates.d.ts +2 -1
- package/dist/mcp/waitingGates.js +17 -1
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/projection/projectRun.d.ts +4 -1
- package/dist/projection/projectRun.js +7 -0
- package/dist/prompt/feedbackLoopContext.d.ts +2 -0
- package/dist/prompt/feedbackLoopContext.js +17 -0
- package/dist/prompt/priorEnvelope.d.ts +2 -2
- package/dist/prompt/priorEnvelope.js +4 -1
- package/dist/runstore/pipelineDagSnapshot.d.ts +1 -0
- package/dist/runstore/pipelineDagSnapshot.js +51 -11
- package/dist/runstore/port.d.ts +185 -3
- package/dist/runstore/port.js +42 -3
- package/dist/runstore/runProjection.d.ts +7 -3
- package/dist/runstore/runProjection.js +30 -15
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +25 -1
- package/dist/runstore/sqlite/SqliteRunStore.js +626 -8
- package/dist/runstore/sqlite/schema.d.ts +1 -1
- package/dist/runstore/sqlite/schema.js +99 -0
- package/dist/runstore/stageInstanceId.d.ts +1 -1
- package/dist/runstore/stageInstanceId.js +3 -2
- package/dist/runstore/trackProjection.js +59 -27
- package/dist/runstore/verificationHistory.d.ts +19 -0
- package/dist/runstore/verificationHistory.js +30 -0
- package/dist/runtime/cloneSchedule.d.ts +40 -4
- package/dist/runtime/cloneSchedule.js +212 -28
- package/dist/runtime/completionCheckRunner.d.ts +137 -0
- package/dist/runtime/completionCheckRunner.js +365 -0
- package/dist/runtime/completionCheckoutBaseline.d.ts +3 -0
- package/dist/runtime/completionCheckoutBaseline.js +44 -0
- package/dist/runtime/envelopeRouting.d.ts +10 -3
- package/dist/runtime/envelopeRouting.js +146 -5
- package/dist/runtime/feedbackLoopCoordinator.d.ts +74 -0
- package/dist/runtime/feedbackLoopCoordinator.js +446 -0
- package/dist/runtime/feedbackLoopDecision.d.ts +44 -0
- package/dist/runtime/feedbackLoopDecision.js +207 -0
- package/dist/runtime/feedbackLoopRoute.d.ts +5 -0
- package/dist/runtime/feedbackLoopRoute.js +13 -0
- package/dist/runtime/feedbackLoopSchedule.d.ts +38 -0
- package/dist/runtime/feedbackLoopSchedule.js +72 -0
- package/dist/runtime/forkGeneration.d.ts +72 -0
- package/dist/runtime/forkGeneration.js +295 -0
- package/dist/runtime/gitCheckoutCapability.d.ts +7 -0
- package/dist/runtime/gitCheckoutCapability.js +97 -0
- package/dist/runtime/manualRecoveryState.d.ts +40 -0
- package/dist/runtime/manualRecoveryState.js +73 -0
- package/dist/runtime/pipelineScheduler.d.ts +13 -1
- package/dist/runtime/pipelineScheduler.js +328 -45
- package/dist/runtime/replayLifecycle.d.ts +107 -0
- package/dist/runtime/replayLifecycle.js +168 -0
- package/dist/runtime/runManager.d.ts +54 -1
- package/dist/runtime/runManager.js +471 -6
- package/dist/runtime/runRetryCoordinator.d.ts +6 -1
- package/dist/runtime/runRetryCoordinator.js +43 -9
- package/dist/runtime/stageAttemptBootstrap.d.ts +3 -1
- package/dist/runtime/stageAttemptBootstrap.js +79 -1
- package/dist/runtime/stageProcessLauncher.d.ts +1 -1
- package/dist/runtime/stageRecovery.js +1 -1
- package/dist/runtime/stageRunner.d.ts +4 -1
- package/dist/runtime/stageRunner.js +29 -3
- package/dist/runtime/stageWorker.js +69 -2
- package/dist/runtime/stageWorkerProtocol.d.ts +1 -1
- package/dist/runtime/verifiedStageExecution.d.ts +29 -0
- package/dist/runtime/verifiedStageExecution.js +119 -0
- package/dist/server/bootstrap.js +1 -0
- package/dist/server/http.js +114 -0
- package/dist/server/operatorResults.js +2 -0
- package/dist/tools/askOperator.d.ts +161 -5
- package/dist/tools/askOperator.js +58 -25
- package/dist/tools/emitStageEnvelope.d.ts +12 -12
- package/dist/tools/emitStageEnvelope.js +112 -24
- package/dist/types/completion.d.ts +43 -0
- package/dist/types/completion.js +1 -0
- package/dist/types/envelope.d.ts +18 -0
- package/dist/types/forkChoice.d.ts +9 -4
- package/dist/types/forkChoice.js +1 -1
- package/dist/types/pipeline.d.ts +39 -2
- package/dist/types/preEmitCheck.d.ts +18 -0
- package/dist/types/preEmitCheck.js +1 -0
- package/dist/types/stage.d.ts +13 -0
- package/dist/types/stageflowManifest.d.ts +2 -0
- package/dist/ui/assets/index-CXGz4nL7.js +118 -0
- package/dist/ui/assets/{index-C3N7MXAC.css → index-uVHiae8s.css} +1 -1
- package/dist/ui/index.html +2 -2
- package/package.json +2 -1
- package/skills/stageflow-author/references/catalog-mapping.md +26 -2
- package/skills/stageflow-author/references/catalog-write-conventions.md +1 -1
- package/dist/ui/assets/index-CSoGDA3A.js +0 -118
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RunStore } from "../runstore/port.js";
|
|
2
2
|
import { RunManager } from "../runtime/runManager.js";
|
|
3
|
-
export declare const RUNS_USAGE = "Usage:\n sf runs list [--status created|running|succeeded|failed] [--since <iso>] [--pipeline <id-or-path>] [--json]\n sf runs show --run <runId> [--from <sf-run.json>] [--json]\n sf runs waiting [--run <runId>] [--json]\n sf runs wait --run <runId> [--from <sf-run.json>] [--until any|waiting|terminal] [--timeout-ms <n>] [--json]\n sf runs answer --run <runId> --stage <stageId> [--answer '<json>'] [--json]\n sf runs retry --run <runId> --stage <stageId> [--json]\n sf runs abandon --run <runId> --stage <stageId> [--json]\n sf runs rerun --run <runId> [--json]";
|
|
3
|
+
export declare const RUNS_USAGE = "Usage:\n sf runs list [--status created|running|succeeded|failed] [--since <iso>] [--pipeline <id-or-path>] [--json]\n sf runs show --run <runId> [--from <sf-run.json>] [--json]\n sf runs verify --run <runId> --stage <stageId> [--json]\n sf runs recover --run <runId> --stage <stageId> [--guidance <text>] [--stop] [--json]\n sf runs waiting [--run <runId>] [--json]\n sf runs wait --run <runId> [--from <sf-run.json>] [--until any|waiting|terminal] [--timeout-ms <n>] [--json]\n sf runs answer --run <runId> --stage <stageId> [--answer '<json>'] [--json]\n sf runs feedback-decide --run <runId> --stage <sourceStageId> [--loop <loopId>] --decision extend|continue|abandon [--reason <text>] [--json]\n sf runs retry --run <runId> --stage <stageId> [--json]\n sf runs abandon --run <runId> --stage <stageId> [--json]\n sf runs rerun --run <runId> [--json]";
|
|
4
4
|
export type RunsCommandIo = {
|
|
5
5
|
log: (line: string) => void;
|
|
6
6
|
error: (line: string) => void;
|
package/dist/cli/runsCommand.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveGlobalOnlyAgentPort } from "../agent/resolveAgentPort.js";
|
|
4
4
|
import { projectRun } from "../projection/projectRun.js";
|
|
5
5
|
import { waitRun } from "../mcp/waitRun.js";
|
|
6
6
|
import { projectWaitingGates } from "../mcp/waitingGates.js";
|
|
@@ -8,6 +8,7 @@ import { resolveOperatorCatalog } from "./operatorCatalog.js";
|
|
|
8
8
|
import { completeCliRun } from "./runCommand.js";
|
|
9
9
|
import { reportCliRun } from "./runOutput.js";
|
|
10
10
|
import { createRunStore } from "../runstore/createStore.js";
|
|
11
|
+
import { readStageVerificationHistory } from "../runstore/verificationHistory.js";
|
|
11
12
|
import { RunManager } from "../runtime/runManager.js";
|
|
12
13
|
import { readStageExecutionMode } from "../runtime/stageConcurrency.js";
|
|
13
14
|
import { DEFAULT_PORT } from "../server/createHttpHost.js";
|
|
@@ -16,9 +17,12 @@ import { AskOperatorError, parseAskOperatorAnswer, } from "../tools/askOperator.
|
|
|
16
17
|
export const RUNS_USAGE = `Usage:
|
|
17
18
|
sf runs list [--status created|running|succeeded|failed] [--since <iso>] [--pipeline <id-or-path>] [--json]
|
|
18
19
|
sf runs show --run <runId> [--from <sf-run.json>] [--json]
|
|
20
|
+
sf runs verify --run <runId> --stage <stageId> [--json]
|
|
21
|
+
sf runs recover --run <runId> --stage <stageId> [--guidance <text>] [--stop] [--json]
|
|
19
22
|
sf runs waiting [--run <runId>] [--json]
|
|
20
23
|
sf runs wait --run <runId> [--from <sf-run.json>] [--until any|waiting|terminal] [--timeout-ms <n>] [--json]
|
|
21
24
|
sf runs answer --run <runId> --stage <stageId> [--answer '<json>'] [--json]
|
|
25
|
+
sf runs feedback-decide --run <runId> --stage <sourceStageId> [--loop <loopId>] --decision extend|continue|abandon [--reason <text>] [--json]
|
|
22
26
|
sf runs retry --run <runId> --stage <stageId> [--json]
|
|
23
27
|
sf runs abandon --run <runId> --stage <stageId> [--json]
|
|
24
28
|
sf runs rerun --run <runId> [--json]`;
|
|
@@ -41,6 +45,16 @@ const LIST_FLAGS = new Set([
|
|
|
41
45
|
"-h",
|
|
42
46
|
]);
|
|
43
47
|
const SHOW_FLAGS = new Set(["--run", "--from", "--json", "--help", "-h"]);
|
|
48
|
+
const VERIFY_FLAGS = new Set(["--run", "--stage", "--json", "--help", "-h"]);
|
|
49
|
+
const RECOVER_FLAGS = new Set([
|
|
50
|
+
"--run",
|
|
51
|
+
"--stage",
|
|
52
|
+
"--guidance",
|
|
53
|
+
"--stop",
|
|
54
|
+
"--json",
|
|
55
|
+
"--help",
|
|
56
|
+
"-h",
|
|
57
|
+
]);
|
|
44
58
|
const WAITING_FLAGS = new Set(["--run", "--json", "--help", "-h"]);
|
|
45
59
|
const WAIT_FLAGS = new Set([
|
|
46
60
|
"--run",
|
|
@@ -55,6 +69,17 @@ const ANSWER_FLAGS = new Set([
|
|
|
55
69
|
"--run",
|
|
56
70
|
"--stage",
|
|
57
71
|
"--answer",
|
|
72
|
+
"--guidance",
|
|
73
|
+
"--json",
|
|
74
|
+
"--help",
|
|
75
|
+
"-h",
|
|
76
|
+
]);
|
|
77
|
+
const FEEDBACK_DECIDE_FLAGS = new Set([
|
|
78
|
+
"--run",
|
|
79
|
+
"--stage",
|
|
80
|
+
"--loop",
|
|
81
|
+
"--decision",
|
|
82
|
+
"--reason",
|
|
58
83
|
"--json",
|
|
59
84
|
"--help",
|
|
60
85
|
"-h",
|
|
@@ -72,6 +97,10 @@ const VALUE_FLAGS = new Set([
|
|
|
72
97
|
"--timeout-ms",
|
|
73
98
|
"--stage",
|
|
74
99
|
"--answer",
|
|
100
|
+
"--loop",
|
|
101
|
+
"--decision",
|
|
102
|
+
"--reason",
|
|
103
|
+
"--guidance",
|
|
75
104
|
]);
|
|
76
105
|
function flagsFor(subcommand) {
|
|
77
106
|
switch (subcommand) {
|
|
@@ -79,12 +108,18 @@ function flagsFor(subcommand) {
|
|
|
79
108
|
return LIST_FLAGS;
|
|
80
109
|
case "show":
|
|
81
110
|
return SHOW_FLAGS;
|
|
111
|
+
case "verify":
|
|
112
|
+
return VERIFY_FLAGS;
|
|
113
|
+
case "recover":
|
|
114
|
+
return RECOVER_FLAGS;
|
|
82
115
|
case "waiting":
|
|
83
116
|
return WAITING_FLAGS;
|
|
84
117
|
case "wait":
|
|
85
118
|
return WAIT_FLAGS;
|
|
86
119
|
case "answer":
|
|
87
120
|
return ANSWER_FLAGS;
|
|
121
|
+
case "feedback-decide":
|
|
122
|
+
return FEEDBACK_DECIDE_FLAGS;
|
|
88
123
|
case "retry":
|
|
89
124
|
return RETRY_FLAGS;
|
|
90
125
|
case "abandon":
|
|
@@ -118,6 +153,11 @@ function parseRunsArgs(args) {
|
|
|
118
153
|
let timeoutMs;
|
|
119
154
|
let stageId;
|
|
120
155
|
let answer;
|
|
156
|
+
let guidance;
|
|
157
|
+
let stop = false;
|
|
158
|
+
let loopId;
|
|
159
|
+
let decision;
|
|
160
|
+
let reason;
|
|
121
161
|
for (let i = 1; i < args.length; i++) {
|
|
122
162
|
const arg = args[i];
|
|
123
163
|
if (arg === "--help" || arg === "-h") {
|
|
@@ -126,6 +166,11 @@ function parseRunsArgs(args) {
|
|
|
126
166
|
else if (arg === "--json") {
|
|
127
167
|
json = true;
|
|
128
168
|
}
|
|
169
|
+
else if (arg === "--stop") {
|
|
170
|
+
if (!allowed.has(arg))
|
|
171
|
+
throw new Error(`Unknown flag: ${arg}`);
|
|
172
|
+
stop = true;
|
|
173
|
+
}
|
|
129
174
|
else if (VALUE_FLAGS.has(arg)) {
|
|
130
175
|
if (!allowed.has(arg)) {
|
|
131
176
|
throw new Error(`Unknown flag: ${arg}`);
|
|
@@ -152,6 +197,14 @@ function parseRunsArgs(args) {
|
|
|
152
197
|
stageId = value;
|
|
153
198
|
else if (arg === "--answer")
|
|
154
199
|
answer = value;
|
|
200
|
+
else if (arg === "--guidance")
|
|
201
|
+
guidance = value;
|
|
202
|
+
else if (arg === "--loop")
|
|
203
|
+
loopId = value;
|
|
204
|
+
else if (arg === "--decision")
|
|
205
|
+
decision = value;
|
|
206
|
+
else if (arg === "--reason")
|
|
207
|
+
reason = value;
|
|
155
208
|
}
|
|
156
209
|
else if (arg.startsWith("-")) {
|
|
157
210
|
throw new Error(`Unknown flag: ${arg}`);
|
|
@@ -173,6 +226,11 @@ function parseRunsArgs(args) {
|
|
|
173
226
|
timeoutMs,
|
|
174
227
|
stageId,
|
|
175
228
|
answer,
|
|
229
|
+
guidance,
|
|
230
|
+
stop,
|
|
231
|
+
loopId,
|
|
232
|
+
decision,
|
|
233
|
+
reason,
|
|
176
234
|
};
|
|
177
235
|
}
|
|
178
236
|
function resolveRunIdFromFile(fromPath, cwd) {
|
|
@@ -275,6 +333,7 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
275
333
|
return resolvedStore;
|
|
276
334
|
};
|
|
277
335
|
const mutatingIo = out;
|
|
336
|
+
const agent = await resolveGlobalOnlyAgentPort(projectRoot);
|
|
278
337
|
const buildManager = () => {
|
|
279
338
|
const store = getStore();
|
|
280
339
|
if (options.createManager)
|
|
@@ -285,7 +344,7 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
285
344
|
defaultCwd: cwd,
|
|
286
345
|
});
|
|
287
346
|
return new RunManager({
|
|
288
|
-
agent
|
|
347
|
+
agent,
|
|
289
348
|
store,
|
|
290
349
|
cwd,
|
|
291
350
|
projectRoot,
|
|
@@ -359,6 +418,32 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
359
418
|
return 1;
|
|
360
419
|
}
|
|
361
420
|
}
|
|
421
|
+
case "verify": {
|
|
422
|
+
if (!parsed.runId)
|
|
423
|
+
return usageError(out, "Missing --run");
|
|
424
|
+
if (!parsed.stageId)
|
|
425
|
+
return usageError(out, "Missing --stage");
|
|
426
|
+
try {
|
|
427
|
+
const history = await readStageVerificationHistory(getStore(), parsed.runId, parsed.stageId);
|
|
428
|
+
if (parsed.json) {
|
|
429
|
+
printJson(out, history);
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
for (const attempt of history.attempts) {
|
|
433
|
+
const checks = attempt.checks
|
|
434
|
+
.map((check) => `${check.check_id}:${check.status}`)
|
|
435
|
+
.join(", ");
|
|
436
|
+
out.log(`attempt ${attempt.attempt}\t${attempt.status}${checks ? `\t${checks}` : ""}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return 0;
|
|
440
|
+
}
|
|
441
|
+
catch (err) {
|
|
442
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
443
|
+
out.error(message);
|
|
444
|
+
return 1;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
362
447
|
case "waiting": {
|
|
363
448
|
const waiting = await projectWaitingGates(getStore(), {
|
|
364
449
|
runId: parsed.runId,
|
|
@@ -512,6 +597,61 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
512
597
|
}
|
|
513
598
|
return 0;
|
|
514
599
|
}
|
|
600
|
+
case "feedback-decide": {
|
|
601
|
+
const blocked = await guardHost();
|
|
602
|
+
if (blocked !== undefined)
|
|
603
|
+
return blocked;
|
|
604
|
+
if (!parsed.runId) {
|
|
605
|
+
return usageError(out, "Missing --run");
|
|
606
|
+
}
|
|
607
|
+
if (!parsed.stageId) {
|
|
608
|
+
return usageError(out, "Missing --stage");
|
|
609
|
+
}
|
|
610
|
+
if (!parsed.decision) {
|
|
611
|
+
return usageError(out, "Missing --decision");
|
|
612
|
+
}
|
|
613
|
+
if (parsed.decision !== "extend" &&
|
|
614
|
+
parsed.decision !== "continue" &&
|
|
615
|
+
parsed.decision !== "abandon") {
|
|
616
|
+
const message = "--decision must be extend, continue, or abandon";
|
|
617
|
+
if (parsed.json) {
|
|
618
|
+
printJson(out, { error: message, status: 400 });
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
out.error(message);
|
|
622
|
+
}
|
|
623
|
+
return 1;
|
|
624
|
+
}
|
|
625
|
+
const manager = buildManager();
|
|
626
|
+
const result = await manager.decideFeedbackLoop(parsed.runId, parsed.stageId, {
|
|
627
|
+
decision: parsed.decision,
|
|
628
|
+
...(parsed.loopId !== undefined ? { loopId: parsed.loopId } : {}),
|
|
629
|
+
...(parsed.reason !== undefined ? { reason: parsed.reason } : {}),
|
|
630
|
+
});
|
|
631
|
+
if (!result.ok) {
|
|
632
|
+
const payload = { error: result.reason };
|
|
633
|
+
if (result.status !== undefined)
|
|
634
|
+
payload.status = result.status;
|
|
635
|
+
if (parsed.json) {
|
|
636
|
+
printJson(out, payload);
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
out.error(result.reason);
|
|
640
|
+
}
|
|
641
|
+
return 1;
|
|
642
|
+
}
|
|
643
|
+
if (parsed.json) {
|
|
644
|
+
printJson(out, {
|
|
645
|
+
ok: true,
|
|
646
|
+
effect: result.effect,
|
|
647
|
+
loopId: result.loopId,
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
out.log(`ok\t${result.effect}\t${result.loopId}`);
|
|
652
|
+
}
|
|
653
|
+
return 0;
|
|
654
|
+
}
|
|
515
655
|
case "retry": {
|
|
516
656
|
const blocked = await guardHost();
|
|
517
657
|
if (blocked !== undefined)
|
|
@@ -535,6 +675,48 @@ export async function runRunsCommand(args, options = {}) {
|
|
|
535
675
|
}
|
|
536
676
|
return reportCliRun({ kind: "completion", result: result.pipeline }, { json: parsed.json, io: mutatingIo });
|
|
537
677
|
}
|
|
678
|
+
case "recover": {
|
|
679
|
+
const blocked = await guardHost();
|
|
680
|
+
if (blocked !== undefined)
|
|
681
|
+
return blocked;
|
|
682
|
+
if (!parsed.runId || !parsed.stageId) {
|
|
683
|
+
return usageError(out, "Missing --run and/or --stage");
|
|
684
|
+
}
|
|
685
|
+
const manager = buildManager();
|
|
686
|
+
if (parsed.stop) {
|
|
687
|
+
if (parsed.guidance !== undefined) {
|
|
688
|
+
return usageError(out, "--guidance cannot be used with --stop");
|
|
689
|
+
}
|
|
690
|
+
const result = await manager.stopManualRecovery(parsed.runId, parsed.stageId);
|
|
691
|
+
if (!result.ok) {
|
|
692
|
+
if (parsed.json) {
|
|
693
|
+
printJson(out, { error: result.reason, status: result.status });
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
out.error(result.reason);
|
|
697
|
+
}
|
|
698
|
+
return 1;
|
|
699
|
+
}
|
|
700
|
+
if (parsed.json) {
|
|
701
|
+
printJson(out, result);
|
|
702
|
+
}
|
|
703
|
+
else {
|
|
704
|
+
out.log(`${result.runId}\t${result.stageId}\tstopped`);
|
|
705
|
+
}
|
|
706
|
+
return 0;
|
|
707
|
+
}
|
|
708
|
+
const result = await manager.recoverManualStageUntilStop(parsed.runId, parsed.stageId, parsed.guidance);
|
|
709
|
+
if (!result.ok) {
|
|
710
|
+
if (parsed.json) {
|
|
711
|
+
printJson(out, { ...mapRetryStageFailure(result), status: result.status });
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
out.error(result.reason);
|
|
715
|
+
}
|
|
716
|
+
return 1;
|
|
717
|
+
}
|
|
718
|
+
return reportCliRun({ kind: "completion", result: result.pipeline }, { json: parsed.json, io: mutatingIo });
|
|
719
|
+
}
|
|
538
720
|
case "abandon": {
|
|
539
721
|
const blocked = await guardHost();
|
|
540
722
|
if (blocked !== undefined)
|
package/dist/cli.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { OperatorCatalog } from "./runtime/stageAttemptBootstrap.js";
|
|
|
3
3
|
export declare function parseRunStageArgs(argv: string[]): {
|
|
4
4
|
runId: string;
|
|
5
5
|
stageId: string;
|
|
6
|
-
mode?: "run" | "resume";
|
|
6
|
+
mode?: "run" | "resume" | "feedback_resume" | "new_session";
|
|
7
7
|
resumeAnswer?: unknown;
|
|
8
8
|
attempt?: number;
|
|
9
9
|
sessionFilePath?: string;
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import {
|
|
6
|
+
import { globalAgentBackendFromManifest, resolveAgentPort } from "./agent/resolveAgentPort.js";
|
|
7
7
|
import { ARTIFACT_USAGE, runArtifactCommand } from "./cli/artifactCommand.js";
|
|
8
8
|
import { ENVELOPE_USAGE, runEnvelopeCommand, } from "./cli/envelopeCommand.js";
|
|
9
9
|
import { EXPORT_RUN_USAGE, runExportRunCommand, } from "./cli/exportRunCommand.js";
|
|
@@ -31,6 +31,8 @@ const USAGE = `Usage:
|
|
|
31
31
|
sf export-run --run <runId> [--from <sf-run.json>] [--out <file>]
|
|
32
32
|
sf runs list [--status created|running|succeeded|failed] [--since <iso>] [--pipeline <id-or-path>] [--json]
|
|
33
33
|
sf runs show --run <runId> [--from <sf-run.json>] [--json]
|
|
34
|
+
sf runs verify --run <runId> --stage <stageId> [--json]
|
|
35
|
+
sf runs recover --run <runId> --stage <stageId> [--guidance <text>] [--stop] [--json]
|
|
34
36
|
sf runs waiting [--run <runId>] [--json]
|
|
35
37
|
sf runs wait --run <runId> [--from <sf-run.json>] [--until any|waiting|terminal] [--timeout-ms <n>] [--json]
|
|
36
38
|
sf runs answer --run <runId> --stage <stageId> [--answer '<json>'] [--json]
|
|
@@ -154,8 +156,11 @@ export function parseRunStageArgs(argv) {
|
|
|
154
156
|
}
|
|
155
157
|
else if (argv[i] === "--mode") {
|
|
156
158
|
const raw = argv[++i];
|
|
157
|
-
if (raw !== "run" &&
|
|
158
|
-
|
|
159
|
+
if (raw !== "run" &&
|
|
160
|
+
raw !== "resume" &&
|
|
161
|
+
raw !== "feedback_resume" &&
|
|
162
|
+
raw !== "new_session") {
|
|
163
|
+
throw new Error("--mode must be run, resume, feedback_resume, or new_session");
|
|
159
164
|
}
|
|
160
165
|
mode = raw;
|
|
161
166
|
}
|
|
@@ -314,12 +319,13 @@ async function main(argv) {
|
|
|
314
319
|
});
|
|
315
320
|
}
|
|
316
321
|
const store = createRunStore({ rootDir: ctx.projectRoot });
|
|
322
|
+
const globalAgent = globalAgentBackendFromManifest(ctx.manifest);
|
|
317
323
|
if (parsed.command === "ui") {
|
|
318
324
|
const mcpStateless = resolveMcpStateless({
|
|
319
325
|
mcpStateless: parsed.mcpStateless,
|
|
320
326
|
});
|
|
321
327
|
const { url, mcpUrl } = await startUiServer({
|
|
322
|
-
agent:
|
|
328
|
+
agent: resolveAgentPort({ global: globalAgent }),
|
|
323
329
|
store,
|
|
324
330
|
cwd: ctx.invocationCwd,
|
|
325
331
|
rootDir: ctx.projectRoot,
|
|
@@ -337,7 +343,7 @@ async function main(argv) {
|
|
|
337
343
|
mcpStateless: parsed.mcpStateless,
|
|
338
344
|
});
|
|
339
345
|
const { mcpUrl } = await startMcpServer({
|
|
340
|
-
agent:
|
|
346
|
+
agent: resolveAgentPort({ global: globalAgent }),
|
|
341
347
|
store,
|
|
342
348
|
cwd: ctx.invocationCwd,
|
|
343
349
|
rootDir: ctx.projectRoot,
|
|
@@ -14,7 +14,7 @@ function repoRelPath(projectRoot, absPath) {
|
|
|
14
14
|
function mapStageListing(stage, source, projectRoot) {
|
|
15
15
|
const listing = {
|
|
16
16
|
id: stage.id,
|
|
17
|
-
...(stage.gate_kinds ? { gate_kinds: stage.gate_kinds } : {}),
|
|
17
|
+
...(stage.gate_kinds !== undefined ? { gate_kinds: stage.gate_kinds } : {}),
|
|
18
18
|
};
|
|
19
19
|
if (source?.kind === "inline") {
|
|
20
20
|
listing.inline = true;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PipelineNeeds } from "../types/pipeline.js";
|
|
1
2
|
import type { StageGateKind } from "../types/stage.js";
|
|
2
3
|
import type { PipelineListing } from "./listConfig.js";
|
|
3
4
|
export type CreatePipelineStageInline = {
|
|
@@ -7,7 +8,7 @@ export type CreatePipelineStageInline = {
|
|
|
7
8
|
};
|
|
8
9
|
export type CreatePipelineStageRef = {
|
|
9
10
|
id: string;
|
|
10
|
-
needs?:
|
|
11
|
+
needs?: PipelineNeeds;
|
|
11
12
|
uses?: string;
|
|
12
13
|
inline?: CreatePipelineStageInline;
|
|
13
14
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { access, mkdir, readdir, unlink, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { parsePipelineNeeds } from "./pipelineNeeds.js";
|
|
3
4
|
import { STAGE_ID_PATTERN } from "./createStage.js";
|
|
4
5
|
import { inferIdFromUsesPath, normalizePipelineStageEntries, toWiringRefs, } from "./normalizePipelineStageEntry.js";
|
|
5
6
|
import { readYamlObject } from "./readYamlObject.js";
|
|
@@ -149,14 +150,15 @@ function parseStageEntry(entry, index) {
|
|
|
149
150
|
}
|
|
150
151
|
const ref = { id };
|
|
151
152
|
if (entry.needs !== undefined) {
|
|
152
|
-
|
|
153
|
+
const parsedNeeds = parsePipelineNeeds(entry.needs, id);
|
|
154
|
+
if (!parsedNeeds.ok) {
|
|
153
155
|
return {
|
|
154
156
|
ok: false,
|
|
155
157
|
status: 400,
|
|
156
|
-
error: `stages[${index}]
|
|
158
|
+
error: parsedNeeds.message.replace(/^stage "[^"]+": /, `stages[${index}].`),
|
|
157
159
|
};
|
|
158
160
|
}
|
|
159
|
-
ref.needs =
|
|
161
|
+
ref.needs = parsedNeeds.value;
|
|
160
162
|
}
|
|
161
163
|
if (entry.uses !== undefined) {
|
|
162
164
|
if (typeof entry.uses !== "string" || !entry.uses.trim()) {
|
|
@@ -224,6 +226,24 @@ export function parseCreatePipelineBody(body) {
|
|
|
224
226
|
stages: refs,
|
|
225
227
|
};
|
|
226
228
|
}
|
|
229
|
+
function appendNeedsYaml(lines, needs) {
|
|
230
|
+
if (typeof needs === "string") {
|
|
231
|
+
lines.push(` needs: ${needs}`);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
lines.push(" needs:");
|
|
235
|
+
for (const edge of needs) {
|
|
236
|
+
if (edge.on.length === 1 && edge.on[0] === "succeeded") {
|
|
237
|
+
lines.push(` - ${edge.id}`);
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
lines.push(` - id: ${edge.id}`);
|
|
241
|
+
lines.push(" on:");
|
|
242
|
+
for (const state of edge.on) {
|
|
243
|
+
lines.push(` - ${state}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
227
247
|
function formatInlineYamlScalar(value) {
|
|
228
248
|
if (value.includes("\n") ||
|
|
229
249
|
/[:#"'&*!|>@`[\]{}]/.test(value) ||
|
|
@@ -245,13 +265,18 @@ export function pipelineConfigToYaml(pipeline, options) {
|
|
|
245
265
|
}
|
|
246
266
|
lines.push(` - id: ${stage.id}`);
|
|
247
267
|
if (stage.needs) {
|
|
248
|
-
lines
|
|
268
|
+
appendNeedsYaml(lines, stage.needs);
|
|
249
269
|
}
|
|
250
270
|
if (stage.inline) {
|
|
251
|
-
if (stage.inline.gate_kinds
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
271
|
+
if (stage.inline.gate_kinds !== undefined) {
|
|
272
|
+
if (stage.inline.gate_kinds.length === 0) {
|
|
273
|
+
lines.push(" gate_kinds: []");
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
lines.push(" gate_kinds:");
|
|
277
|
+
for (const kind of stage.inline.gate_kinds) {
|
|
278
|
+
lines.push(` - ${kind}`);
|
|
279
|
+
}
|
|
255
280
|
}
|
|
256
281
|
}
|
|
257
282
|
if (stage.inline.system_prompt.includes("\n")) {
|
|
@@ -364,7 +389,7 @@ function buildPipelineListing(projectRoot, relPath, loaded) {
|
|
|
364
389
|
const source = loaded.stageSources?.[stage.id];
|
|
365
390
|
const listing = {
|
|
366
391
|
id: stage.id,
|
|
367
|
-
...(stage.gate_kinds ? { gate_kinds: stage.gate_kinds } : {}),
|
|
392
|
+
...(stage.gate_kinds !== undefined ? { gate_kinds: stage.gate_kinds } : {}),
|
|
368
393
|
};
|
|
369
394
|
if (source?.kind === "inline") {
|
|
370
395
|
return { ...listing, inline: true };
|
|
@@ -22,7 +22,7 @@ function validateStageId(id) {
|
|
|
22
22
|
function parseGateKinds(raw) {
|
|
23
23
|
if (raw === undefined)
|
|
24
24
|
return undefined;
|
|
25
|
-
if (!Array.isArray(raw)
|
|
25
|
+
if (!Array.isArray(raw))
|
|
26
26
|
return "invalid";
|
|
27
27
|
const kinds = [];
|
|
28
28
|
for (const item of raw) {
|
|
@@ -71,7 +71,7 @@ export function parseCreateStageBody(body) {
|
|
|
71
71
|
return {
|
|
72
72
|
ok: false,
|
|
73
73
|
status: 400,
|
|
74
|
-
error: `gate_kinds must be
|
|
74
|
+
error: `gate_kinds must be an array of: ${STAGE_GATE_KINDS.join(", ")}`,
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
77
|
return {
|
|
@@ -80,7 +80,7 @@ export function parseCreateStageBody(body) {
|
|
|
80
80
|
id: body.id,
|
|
81
81
|
system_prompt: body.system_prompt,
|
|
82
82
|
model: body.model,
|
|
83
|
-
...(gateKinds ? { gate_kinds: gateKinds } : {}),
|
|
83
|
+
...(gateKinds !== undefined ? { gate_kinds: gateKinds } : {}),
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
86
|
function formatInlineYamlScalar(value) {
|
|
@@ -96,10 +96,15 @@ function formatInlineYamlScalar(value) {
|
|
|
96
96
|
}
|
|
97
97
|
export function stageConfigToYaml(stage) {
|
|
98
98
|
const lines = [`id: ${stage.id}`];
|
|
99
|
-
if (stage.gate_kinds
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
if (stage.gate_kinds !== undefined) {
|
|
100
|
+
if (stage.gate_kinds.length === 0) {
|
|
101
|
+
lines.push("gate_kinds: []");
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
lines.push("gate_kinds:");
|
|
105
|
+
for (const kind of stage.gate_kinds) {
|
|
106
|
+
lines.push(` - ${kind}`);
|
|
107
|
+
}
|
|
103
108
|
}
|
|
104
109
|
}
|
|
105
110
|
if (stage.system_prompt.includes("\n")) {
|
|
@@ -186,7 +191,7 @@ export async function createStage(projectRoot, input) {
|
|
|
186
191
|
id: input.id,
|
|
187
192
|
system_prompt: input.system_prompt,
|
|
188
193
|
model: input.model,
|
|
189
|
-
...(input.gate_kinds ? { gate_kinds: input.gate_kinds } : {}),
|
|
194
|
+
...(input.gate_kinds !== undefined ? { gate_kinds: input.gate_kinds } : {}),
|
|
190
195
|
}), "utf8");
|
|
191
196
|
}
|
|
192
197
|
catch (err) {
|
|
@@ -200,7 +205,7 @@ export async function createStage(projectRoot, input) {
|
|
|
200
205
|
stage: {
|
|
201
206
|
path: relPath,
|
|
202
207
|
id: stage.id,
|
|
203
|
-
...(stage.gate_kinds ? { gate_kinds: stage.gate_kinds } : {}),
|
|
208
|
+
...(stage.gate_kinds !== undefined ? { gate_kinds: stage.gate_kinds } : {}),
|
|
204
209
|
},
|
|
205
210
|
};
|
|
206
211
|
}
|
|
@@ -5,6 +5,7 @@ import { mergePipelineStages } from "./mergePipelineIncludes.js";
|
|
|
5
5
|
import { normalizePipelineStageEntries, toWiringRefs, } from "./normalizePipelineStageEntry.js";
|
|
6
6
|
import { loadStageFromObjectOutcome, loadStageOutcome } from "./loadStage.js";
|
|
7
7
|
import { resolvePipelineDagFromRefs } from "./resolvePipelineDag.js";
|
|
8
|
+
import { validateCompletionContractForStage } from "./validateCompletionContract.js";
|
|
8
9
|
export async function resolvePipelinePath(pipelinePath, cwd) {
|
|
9
10
|
const resolved = path.normalize(path.resolve(cwd, pipelinePath));
|
|
10
11
|
try {
|
|
@@ -21,7 +22,7 @@ async function loadPipelineFromPath(pipelinePath) {
|
|
|
21
22
|
if (!mergeOutcome.ok) {
|
|
22
23
|
return loadFailure(mergeOutcome.issues);
|
|
23
24
|
}
|
|
24
|
-
const { entries: rawEntries, pipelineId } = mergeOutcome.value;
|
|
25
|
+
const { entries: rawEntries, pipelineId, agent: pipelineAgent } = mergeOutcome.value;
|
|
25
26
|
const ctx = { pipelineId, path: normalizedPipelinePath };
|
|
26
27
|
const normalizeOutcome = normalizePipelineStageEntries(rawEntries, ctx);
|
|
27
28
|
if (!normalizeOutcome.ok) {
|
|
@@ -106,7 +107,15 @@ async function loadPipelineFromPath(pipelinePath) {
|
|
|
106
107
|
const pipeline = {
|
|
107
108
|
id: pipelineId,
|
|
108
109
|
stages: stageIds,
|
|
110
|
+
...(pipelineAgent !== undefined ? { agent: pipelineAgent } : {}),
|
|
109
111
|
};
|
|
112
|
+
const nodeById = new Map(dag.nodes.map((node) => [node.id, node]));
|
|
113
|
+
for (const stage of stages) {
|
|
114
|
+
const completion = nodeById.get(stage.id)?.completion;
|
|
115
|
+
const completionOutcome = validateCompletionContractForStage(stage, completion);
|
|
116
|
+
if (!completionOutcome.ok)
|
|
117
|
+
return loadFailure(completionOutcome.issues);
|
|
118
|
+
}
|
|
110
119
|
return loadSuccess({
|
|
111
120
|
pipeline,
|
|
112
121
|
stages,
|