stageflow 0.3.0 → 0.9.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 +44 -14
- package/dist/agent/activity.d.ts +2 -0
- package/dist/agent/cursorProvider.d.ts +1 -10
- package/dist/agent/cursorProvider.js +18 -10
- package/dist/agent/fakeAgent.js +11 -7
- package/dist/agent/piAdapter.d.ts +2 -1
- package/dist/agent/piAdapter.js +28 -25
- package/dist/agent/port.d.ts +6 -1
- package/dist/agent/port.js +3 -0
- package/dist/cli/artifactCommand.d.ts +10 -0
- package/dist/cli/artifactCommand.js +120 -0
- package/dist/cli/envelopeCommand.d.ts +32 -0
- package/dist/cli/envelopeCommand.js +285 -0
- package/dist/cli/exportRunCommand.d.ts +10 -0
- package/dist/cli/exportRunCommand.js +150 -0
- package/dist/cli/handoffFormat.d.ts +22 -0
- package/dist/cli/handoffFormat.js +61 -0
- package/dist/cli/runCommand.d.ts +5 -1
- package/dist/cli/runCommand.js +36 -5
- package/dist/cli/runOutput.d.ts +4 -1
- package/dist/cli/runOutput.js +36 -8
- package/dist/cli/runsCommand.d.ts +22 -0
- package/dist/cli/runsCommand.js +591 -0
- package/dist/cli/skillsCommand.d.ts +16 -0
- package/dist/cli/skillsCommand.js +459 -0
- package/dist/cli/validateOutput.d.ts +2 -2
- package/dist/cli/validateOutput.js +2 -2
- package/dist/cli.js +110 -5
- package/dist/config/loadPipeline.js +5 -1
- package/dist/config/normalizePipelineStageEntry.d.ts +2 -0
- package/dist/config/normalizePipelineStageEntry.js +25 -1
- package/dist/config/pipelineStageKeys.js +1 -1
- package/dist/config/resolveForkEmitContext.d.ts +2 -1
- package/dist/config/resolveForkEmitContext.js +23 -1
- package/dist/config/resolvePipelineDag.js +36 -4
- package/dist/envelope/check.d.ts +2 -0
- package/dist/envelope/check.js +51 -0
- package/dist/envelope/cloneForks.d.ts +2 -0
- package/dist/envelope/cloneForks.js +62 -0
- package/dist/envelope/forkChoice.js +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/mcp/catalogTools.d.ts +3 -0
- package/dist/mcp/catalogTools.js +187 -0
- package/dist/mcp/controlTools.d.ts +3 -0
- package/dist/mcp/controlTools.js +147 -0
- package/dist/mcp/deps.d.ts +11 -0
- package/dist/mcp/deps.js +1 -0
- package/dist/mcp/projectRun.d.ts +1 -40
- package/dist/mcp/projectRun.js +1 -64
- package/dist/mcp/resources.d.ts +5 -0
- package/dist/mcp/resources.js +45 -0
- package/dist/mcp/server.d.ts +16 -3
- package/dist/mcp/server.js +159 -3
- package/dist/mcp/toolResults.d.ts +7 -0
- package/dist/mcp/toolResults.js +6 -0
- package/dist/mcp/tools.d.ts +2 -7
- package/dist/mcp/tools.js +52 -103
- package/dist/mcp/waitRun.d.ts +46 -0
- package/dist/mcp/waitRun.js +146 -0
- package/dist/mcp/waitingGates.d.ts +12 -0
- package/dist/mcp/waitingGates.js +83 -0
- package/dist/package-meta.d.ts +1 -0
- package/dist/package-meta.js +1 -0
- package/dist/projection/projectRun.d.ts +44 -0
- package/dist/projection/projectRun.js +71 -0
- package/dist/prompt/priorEnvelope.d.ts +1 -1
- package/dist/prompt/priorEnvelope.js +4 -1
- package/dist/runstore/paths.js +2 -0
- package/dist/runstore/pipelineDagSnapshot.d.ts +9 -0
- package/dist/runstore/pipelineDagSnapshot.js +90 -1
- package/dist/runstore/port.d.ts +13 -2
- package/dist/runstore/port.js +4 -1
- package/dist/runstore/runProjection.d.ts +1 -1
- package/dist/runstore/runProjection.js +14 -3
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +3 -2
- package/dist/runstore/sqlite/SqliteRunStore.js +35 -3
- package/dist/runstore/stageInstanceId.d.ts +4 -0
- package/dist/runstore/stageInstanceId.js +23 -0
- package/dist/runstore/syntheticStageSnapshot.d.ts +1 -1
- package/dist/runstore/syntheticStageSnapshot.js +2 -1
- package/dist/runstore/trackProjection.js +28 -12
- package/dist/runstore/workspaceLayout.d.ts +1 -0
- package/dist/runstore/workspaceLayout.js +1 -1
- package/dist/runtime/cloneSchedule.d.ts +18 -0
- package/dist/runtime/cloneSchedule.js +233 -0
- package/dist/runtime/envelopeRouting.d.ts +2 -1
- package/dist/runtime/envelopeRouting.js +80 -13
- package/dist/runtime/pipelineScheduler.d.ts +6 -2
- package/dist/runtime/pipelineScheduler.js +166 -30
- package/dist/runtime/resumeReconstruct.js +8 -3
- package/dist/runtime/runChangeBus.d.ts +18 -0
- package/dist/runtime/runChangeBus.js +83 -0
- package/dist/runtime/runManager.d.ts +7 -0
- package/dist/runtime/runManager.js +31 -2
- package/dist/runtime/runRetryCoordinator.d.ts +2 -0
- package/dist/runtime/runRetryCoordinator.js +4 -1
- package/dist/runtime/stageAttemptBootstrap.d.ts +1 -0
- package/dist/runtime/stageAttemptBootstrap.js +17 -9
- package/dist/runtime/stageRunner.d.ts +1 -0
- package/dist/runtime/stageRunner.js +17 -15
- package/dist/runtime/stageWorker.js +11 -4
- package/dist/server/bootstrap.d.ts +32 -0
- package/dist/server/bootstrap.js +56 -0
- package/dist/server/createHttpHost.d.ts +34 -0
- package/dist/server/createHttpHost.js +70 -0
- package/dist/server/http.d.ts +6 -13
- package/dist/server/http.js +295 -374
- package/dist/server/mcpHost.d.ts +22 -0
- package/dist/server/mcpHost.js +21 -0
- package/dist/server/operatorResults.d.ts +13 -0
- package/dist/server/operatorResults.js +22 -0
- package/dist/tools/emitStageEnvelope.d.ts +11 -2
- package/dist/tools/emitStageEnvelope.js +22 -6
- package/dist/types/envelope.d.ts +2 -0
- package/dist/types/forkChoice.d.ts +22 -0
- package/dist/types/pipeline.d.ts +8 -0
- package/dist/ui/assets/{index-DefBlEvN.css → index-C3N7MXAC.css} +1 -1
- package/dist/ui/assets/index-CSoGDA3A.js +118 -0
- package/dist/ui/index.html +2 -2
- package/package.json +2 -1
- package/skills/install-suite.sh +107 -0
- package/skills/stageflow/SKILL.md +26 -0
- package/skills/stageflow/references/control-surface.md +28 -0
- package/skills/stageflow/scripts/detect-host.mjs +54 -0
- package/skills/stageflow/scripts/detect-host.test.mjs +97 -0
- package/skills/stageflow-author/SKILL.md +58 -0
- package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +10 -0
- package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +12 -0
- package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +13 -0
- package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +10 -0
- package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +9 -0
- package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +10 -0
- package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +10 -0
- package/skills/stageflow-author/assets/examples/linear-review/review.yaml +15 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +9 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +10 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +10 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +9 -0
- package/skills/stageflow-author/references/catalog-mapping.md +131 -0
- package/skills/stageflow-author/references/catalog-write-conventions.md +29 -0
- package/skills/stageflow-author/references/stage-prompt-template.md +55 -0
- package/skills/stageflow-author/references/validate-and-report.md +38 -0
- package/skills/stageflow-delegate/SKILL.md +18 -0
- package/skills/stageflow-delegate/references/authoring-or-run.md +15 -0
- package/skills/stageflow-delegate/references/example-walkthrough.md +30 -0
- package/skills/stageflow-delegate/references/pattern-detection.md +44 -0
- package/skills/stageflow-run/SKILL.md +182 -0
- package/skills/stageflow-run/references/mcp-call.md +17 -0
- package/skills/stageflow-run/references/native-question-ui.md +78 -0
- package/skills/stageflow-run/references/task-and-pipeline-selection.md +52 -0
- package/skills/stageflow-run/scripts/mcp-call.mjs +299 -0
- package/skills/stageflow-run/scripts/mcp-call.test.mjs +271 -0
- package/skills/stageflow-session-capture/SKILL.md +94 -0
- package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +7 -0
- package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +8 -0
- package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +8 -0
- package/skills/stageflow-session-capture/references/catalog-authoring.md +40 -0
- package/skills/stageflow-session-capture/references/transcript-sources.md +25 -0
- package/skills/stageflow-session-capture/scripts/check-provider-gate.mjs +43 -0
- package/skills/stageflow-session-capture/scripts/locate-session-transcript.mjs +141 -0
- package/skills/stageflow-session-capture/scripts/resolve-catalog-id.mjs +119 -0
- package/skills/stageflow-setup/SKILL.md +110 -0
- package/dist/ui/assets/index-CFSzDZje.js +0 -118
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { PiAgentAdapter } from "../agent/piAdapter.js";
|
|
4
|
+
import { projectRun } from "../projection/projectRun.js";
|
|
5
|
+
import { waitRun } from "../mcp/waitRun.js";
|
|
6
|
+
import { projectWaitingGates } from "../mcp/waitingGates.js";
|
|
7
|
+
import { resolveOperatorCatalog } from "./operatorCatalog.js";
|
|
8
|
+
import { completeCliRun } from "./runCommand.js";
|
|
9
|
+
import { reportCliRun } from "./runOutput.js";
|
|
10
|
+
import { createRunStore } from "../runstore/createStore.js";
|
|
11
|
+
import { RunManager } from "../runtime/runManager.js";
|
|
12
|
+
import { readStageExecutionMode } from "../runtime/stageConcurrency.js";
|
|
13
|
+
import { DEFAULT_PORT } from "../server/createHttpHost.js";
|
|
14
|
+
import { mapRetryStageFailure } from "../server/operatorResults.js";
|
|
15
|
+
import { AskOperatorError, parseAskOperatorAnswer, } from "../tools/askOperator.js";
|
|
16
|
+
export const RUNS_USAGE = `Usage:
|
|
17
|
+
sf runs list [--status created|running|succeeded|failed] [--since <iso>] [--pipeline <id-or-path>] [--json]
|
|
18
|
+
sf runs show --run <runId> [--from <sf-run.json>] [--json]
|
|
19
|
+
sf runs waiting [--run <runId>] [--json]
|
|
20
|
+
sf runs wait --run <runId> [--from <sf-run.json>] [--until any|waiting|terminal] [--timeout-ms <n>] [--json]
|
|
21
|
+
sf runs answer --run <runId> --stage <stageId> [--answer '<json>'] [--json]
|
|
22
|
+
sf runs retry --run <runId> --stage <stageId> [--json]
|
|
23
|
+
sf runs abandon --run <runId> --stage <stageId> [--json]
|
|
24
|
+
sf runs rerun --run <runId> [--json]`;
|
|
25
|
+
const defaultIo = {
|
|
26
|
+
log: (line) => console.log(line),
|
|
27
|
+
error: (line) => console.error(line),
|
|
28
|
+
};
|
|
29
|
+
const RUN_STATUSES = [
|
|
30
|
+
"created",
|
|
31
|
+
"running",
|
|
32
|
+
"succeeded",
|
|
33
|
+
"failed",
|
|
34
|
+
];
|
|
35
|
+
const LIST_FLAGS = new Set([
|
|
36
|
+
"--status",
|
|
37
|
+
"--since",
|
|
38
|
+
"--pipeline",
|
|
39
|
+
"--json",
|
|
40
|
+
"--help",
|
|
41
|
+
"-h",
|
|
42
|
+
]);
|
|
43
|
+
const SHOW_FLAGS = new Set(["--run", "--from", "--json", "--help", "-h"]);
|
|
44
|
+
const WAITING_FLAGS = new Set(["--run", "--json", "--help", "-h"]);
|
|
45
|
+
const WAIT_FLAGS = new Set([
|
|
46
|
+
"--run",
|
|
47
|
+
"--from",
|
|
48
|
+
"--until",
|
|
49
|
+
"--timeout-ms",
|
|
50
|
+
"--json",
|
|
51
|
+
"--help",
|
|
52
|
+
"-h",
|
|
53
|
+
]);
|
|
54
|
+
const ANSWER_FLAGS = new Set([
|
|
55
|
+
"--run",
|
|
56
|
+
"--stage",
|
|
57
|
+
"--answer",
|
|
58
|
+
"--json",
|
|
59
|
+
"--help",
|
|
60
|
+
"-h",
|
|
61
|
+
]);
|
|
62
|
+
const RETRY_FLAGS = new Set(["--run", "--stage", "--json", "--help", "-h"]);
|
|
63
|
+
const ABANDON_FLAGS = new Set(["--run", "--stage", "--json", "--help", "-h"]);
|
|
64
|
+
const RERUN_FLAGS = new Set(["--run", "--json", "--help", "-h"]);
|
|
65
|
+
const VALUE_FLAGS = new Set([
|
|
66
|
+
"--status",
|
|
67
|
+
"--since",
|
|
68
|
+
"--pipeline",
|
|
69
|
+
"--run",
|
|
70
|
+
"--from",
|
|
71
|
+
"--until",
|
|
72
|
+
"--timeout-ms",
|
|
73
|
+
"--stage",
|
|
74
|
+
"--answer",
|
|
75
|
+
]);
|
|
76
|
+
function flagsFor(subcommand) {
|
|
77
|
+
switch (subcommand) {
|
|
78
|
+
case "list":
|
|
79
|
+
return LIST_FLAGS;
|
|
80
|
+
case "show":
|
|
81
|
+
return SHOW_FLAGS;
|
|
82
|
+
case "waiting":
|
|
83
|
+
return WAITING_FLAGS;
|
|
84
|
+
case "wait":
|
|
85
|
+
return WAIT_FLAGS;
|
|
86
|
+
case "answer":
|
|
87
|
+
return ANSWER_FLAGS;
|
|
88
|
+
case "retry":
|
|
89
|
+
return RETRY_FLAGS;
|
|
90
|
+
case "abandon":
|
|
91
|
+
return ABANDON_FLAGS;
|
|
92
|
+
case "rerun":
|
|
93
|
+
return RERUN_FLAGS;
|
|
94
|
+
default:
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function parseRunsArgs(args) {
|
|
99
|
+
if (args.length === 0) {
|
|
100
|
+
return { help: false, json: false };
|
|
101
|
+
}
|
|
102
|
+
if (args[0] === "--help" || args[0] === "-h") {
|
|
103
|
+
return { help: true, json: false };
|
|
104
|
+
}
|
|
105
|
+
const subcommand = args[0];
|
|
106
|
+
const allowed = flagsFor(subcommand);
|
|
107
|
+
if (allowed === undefined) {
|
|
108
|
+
return { subcommand, help: false, json: false };
|
|
109
|
+
}
|
|
110
|
+
let help = false;
|
|
111
|
+
let json = false;
|
|
112
|
+
let status;
|
|
113
|
+
let since;
|
|
114
|
+
let pipeline;
|
|
115
|
+
let runId;
|
|
116
|
+
let fromPath;
|
|
117
|
+
let until;
|
|
118
|
+
let timeoutMs;
|
|
119
|
+
let stageId;
|
|
120
|
+
let answer;
|
|
121
|
+
for (let i = 1; i < args.length; i++) {
|
|
122
|
+
const arg = args[i];
|
|
123
|
+
if (arg === "--help" || arg === "-h") {
|
|
124
|
+
help = true;
|
|
125
|
+
}
|
|
126
|
+
else if (arg === "--json") {
|
|
127
|
+
json = true;
|
|
128
|
+
}
|
|
129
|
+
else if (VALUE_FLAGS.has(arg)) {
|
|
130
|
+
if (!allowed.has(arg)) {
|
|
131
|
+
throw new Error(`Unknown flag: ${arg}`);
|
|
132
|
+
}
|
|
133
|
+
const value = args[++i];
|
|
134
|
+
if (value === undefined || value.length === 0) {
|
|
135
|
+
throw new Error(`Missing value for ${arg}`);
|
|
136
|
+
}
|
|
137
|
+
if (arg === "--status")
|
|
138
|
+
status = value;
|
|
139
|
+
else if (arg === "--since")
|
|
140
|
+
since = value;
|
|
141
|
+
else if (arg === "--pipeline")
|
|
142
|
+
pipeline = value;
|
|
143
|
+
else if (arg === "--run")
|
|
144
|
+
runId = value;
|
|
145
|
+
else if (arg === "--from")
|
|
146
|
+
fromPath = value;
|
|
147
|
+
else if (arg === "--until")
|
|
148
|
+
until = value;
|
|
149
|
+
else if (arg === "--timeout-ms")
|
|
150
|
+
timeoutMs = Number(value);
|
|
151
|
+
else if (arg === "--stage")
|
|
152
|
+
stageId = value;
|
|
153
|
+
else if (arg === "--answer")
|
|
154
|
+
answer = value;
|
|
155
|
+
}
|
|
156
|
+
else if (arg.startsWith("-")) {
|
|
157
|
+
throw new Error(`Unknown flag: ${arg}`);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
help,
|
|
165
|
+
json,
|
|
166
|
+
subcommand,
|
|
167
|
+
status,
|
|
168
|
+
since,
|
|
169
|
+
pipeline,
|
|
170
|
+
runId,
|
|
171
|
+
fromPath,
|
|
172
|
+
until,
|
|
173
|
+
timeoutMs,
|
|
174
|
+
stageId,
|
|
175
|
+
answer,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function resolveRunIdFromFile(fromPath, cwd) {
|
|
179
|
+
const resolved = path.resolve(cwd, fromPath);
|
|
180
|
+
let parsed;
|
|
181
|
+
try {
|
|
182
|
+
parsed = JSON.parse(readFileSync(resolved, "utf8"));
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
186
|
+
throw new Error(`Failed to read --from file: ${message}`);
|
|
187
|
+
}
|
|
188
|
+
if (!parsed || typeof parsed !== "object") {
|
|
189
|
+
throw new Error("--from file must contain a JSON object with runId");
|
|
190
|
+
}
|
|
191
|
+
const runId = parsed.runId;
|
|
192
|
+
if (typeof runId !== "string" || runId.length === 0) {
|
|
193
|
+
throw new Error("--from file must contain a JSON object with runId");
|
|
194
|
+
}
|
|
195
|
+
return runId;
|
|
196
|
+
}
|
|
197
|
+
function hostBaseUrl() {
|
|
198
|
+
return `http://127.0.0.1:${DEFAULT_PORT}`;
|
|
199
|
+
}
|
|
200
|
+
export async function defaultProbeHost() {
|
|
201
|
+
const ac = new AbortController();
|
|
202
|
+
const timer = setTimeout(() => ac.abort(), 1500);
|
|
203
|
+
try {
|
|
204
|
+
const res = await fetch(`${hostBaseUrl()}/api/health`, {
|
|
205
|
+
signal: ac.signal,
|
|
206
|
+
});
|
|
207
|
+
if (res.status !== 200)
|
|
208
|
+
return "down";
|
|
209
|
+
JSON.parse(await res.text());
|
|
210
|
+
return "up";
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return "down";
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
clearTimeout(timer);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function printJson(io, payload) {
|
|
220
|
+
io.log(JSON.stringify(payload, null, 2));
|
|
221
|
+
}
|
|
222
|
+
function isRunStatus(value) {
|
|
223
|
+
return RUN_STATUSES.includes(value);
|
|
224
|
+
}
|
|
225
|
+
function isWaitUntil(value) {
|
|
226
|
+
return value === "any" || value === "waiting" || value === "terminal";
|
|
227
|
+
}
|
|
228
|
+
function usageError(io, message) {
|
|
229
|
+
if (message !== undefined)
|
|
230
|
+
io.error(message);
|
|
231
|
+
io.error(RUNS_USAGE);
|
|
232
|
+
return 1;
|
|
233
|
+
}
|
|
234
|
+
async function resolveShowWaitRunId(parsed, cwd, io) {
|
|
235
|
+
if (parsed.fromPath !== undefined) {
|
|
236
|
+
try {
|
|
237
|
+
return resolveRunIdFromFile(parsed.fromPath, cwd);
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
io.error(err instanceof Error ? err.message : String(err));
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return parsed.runId;
|
|
245
|
+
}
|
|
246
|
+
export async function runRunsCommand(args, options = {}) {
|
|
247
|
+
const cwd = options.cwd ?? process.cwd();
|
|
248
|
+
const projectRoot = options.projectRoot ?? cwd;
|
|
249
|
+
const isGitProject = options.isGitProject ?? false;
|
|
250
|
+
const out = { ...defaultIo, ...options.io };
|
|
251
|
+
const env = options.env ?? process.env;
|
|
252
|
+
const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY === true;
|
|
253
|
+
const readStdin = options.readStdin ?? (() => readFileSync(0, "utf8"));
|
|
254
|
+
const probeHost = options.probeHost ?? defaultProbeHost;
|
|
255
|
+
let parsed;
|
|
256
|
+
try {
|
|
257
|
+
parsed = parseRunsArgs(args);
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
261
|
+
return usageError(out, message);
|
|
262
|
+
}
|
|
263
|
+
if (parsed.help) {
|
|
264
|
+
out.error(RUNS_USAGE);
|
|
265
|
+
return 0;
|
|
266
|
+
}
|
|
267
|
+
if (!parsed.subcommand) {
|
|
268
|
+
return usageError(out);
|
|
269
|
+
}
|
|
270
|
+
let resolvedStore = options.store;
|
|
271
|
+
const getStore = () => {
|
|
272
|
+
if (resolvedStore === undefined) {
|
|
273
|
+
resolvedStore = createRunStore({ rootDir: projectRoot });
|
|
274
|
+
}
|
|
275
|
+
return resolvedStore;
|
|
276
|
+
};
|
|
277
|
+
const mutatingIo = out;
|
|
278
|
+
const buildManager = () => {
|
|
279
|
+
const store = getStore();
|
|
280
|
+
if (options.createManager)
|
|
281
|
+
return options.createManager(store);
|
|
282
|
+
const operatorCatalog = resolveOperatorCatalog({
|
|
283
|
+
flags: {},
|
|
284
|
+
env,
|
|
285
|
+
defaultCwd: cwd,
|
|
286
|
+
});
|
|
287
|
+
return new RunManager({
|
|
288
|
+
agent: new PiAgentAdapter(),
|
|
289
|
+
store,
|
|
290
|
+
cwd,
|
|
291
|
+
projectRoot,
|
|
292
|
+
isGitProject,
|
|
293
|
+
operatorCatalog,
|
|
294
|
+
executionMode: readStageExecutionMode(env, "process"),
|
|
295
|
+
});
|
|
296
|
+
};
|
|
297
|
+
const guardHost = async () => {
|
|
298
|
+
const status = await probeHost();
|
|
299
|
+
if (status === "up") {
|
|
300
|
+
out.error(`A Stageflow host is already running at ${hostBaseUrl()}. Use the operator console or MCP instead of mutating the store from the CLI.`);
|
|
301
|
+
return 1;
|
|
302
|
+
}
|
|
303
|
+
return undefined;
|
|
304
|
+
};
|
|
305
|
+
switch (parsed.subcommand) {
|
|
306
|
+
case "list": {
|
|
307
|
+
if (parsed.status === "waiting") {
|
|
308
|
+
out.error("waiting is not a run status; parked HITL runs stay running. Use sf runs waiting.");
|
|
309
|
+
return 1;
|
|
310
|
+
}
|
|
311
|
+
if (parsed.status !== undefined && !isRunStatus(parsed.status)) {
|
|
312
|
+
out.error("--status must be created, running, succeeded, or failed");
|
|
313
|
+
return 1;
|
|
314
|
+
}
|
|
315
|
+
if (parsed.since !== undefined && !Number.isFinite(Date.parse(parsed.since))) {
|
|
316
|
+
out.error("since must be a valid date");
|
|
317
|
+
return 1;
|
|
318
|
+
}
|
|
319
|
+
const filter = {};
|
|
320
|
+
if (parsed.status !== undefined)
|
|
321
|
+
filter.status = parsed.status;
|
|
322
|
+
if (parsed.since !== undefined)
|
|
323
|
+
filter.since = parsed.since;
|
|
324
|
+
if (parsed.pipeline !== undefined)
|
|
325
|
+
filter.pipeline = parsed.pipeline;
|
|
326
|
+
const runs = await getStore().listRuns(Object.keys(filter).length > 0 ? filter : undefined);
|
|
327
|
+
if (parsed.json) {
|
|
328
|
+
printJson(out, { runs });
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
for (const run of runs) {
|
|
332
|
+
out.log(`${run.run_id}\t${run.status}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return 0;
|
|
336
|
+
}
|
|
337
|
+
case "show": {
|
|
338
|
+
const runId = await resolveShowWaitRunId(parsed, cwd, out);
|
|
339
|
+
if (parsed.fromPath !== undefined && runId === undefined) {
|
|
340
|
+
return 1;
|
|
341
|
+
}
|
|
342
|
+
if (!runId) {
|
|
343
|
+
return usageError(out, "Missing --run (or --from <sf-run.json>)");
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
const detail = await getStore().readRun(runId);
|
|
347
|
+
const projection = projectRun(detail);
|
|
348
|
+
if (parsed.json) {
|
|
349
|
+
printJson(out, projection);
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
out.log(`${projection.run_id}\t${projection.status}`);
|
|
353
|
+
}
|
|
354
|
+
return 0;
|
|
355
|
+
}
|
|
356
|
+
catch (err) {
|
|
357
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
358
|
+
out.error(message);
|
|
359
|
+
return 1;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
case "waiting": {
|
|
363
|
+
const waiting = await projectWaitingGates(getStore(), {
|
|
364
|
+
runId: parsed.runId,
|
|
365
|
+
});
|
|
366
|
+
if (parsed.json) {
|
|
367
|
+
printJson(out, { waiting });
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
for (const item of waiting) {
|
|
371
|
+
const runId = typeof item.runId === "string" ? item.runId : "";
|
|
372
|
+
const stageId = typeof item.stageId === "string" ? item.stageId : "";
|
|
373
|
+
out.log(`${runId}\t${stageId}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return 0;
|
|
377
|
+
}
|
|
378
|
+
case "wait": {
|
|
379
|
+
const runId = await resolveShowWaitRunId(parsed, cwd, out);
|
|
380
|
+
if (parsed.fromPath !== undefined && runId === undefined) {
|
|
381
|
+
return 1;
|
|
382
|
+
}
|
|
383
|
+
if (!runId) {
|
|
384
|
+
return usageError(out, "Missing --run (or --from <sf-run.json>)");
|
|
385
|
+
}
|
|
386
|
+
const untilRaw = parsed.until ?? "any";
|
|
387
|
+
if (!isWaitUntil(untilRaw)) {
|
|
388
|
+
return usageError(out, "--until must be any, waiting, or terminal");
|
|
389
|
+
}
|
|
390
|
+
let signal = options.waitSignal;
|
|
391
|
+
let onSigInt;
|
|
392
|
+
if (signal === undefined) {
|
|
393
|
+
const controller = new AbortController();
|
|
394
|
+
signal = controller.signal;
|
|
395
|
+
onSigInt = () => controller.abort();
|
|
396
|
+
process.once("SIGINT", onSigInt);
|
|
397
|
+
}
|
|
398
|
+
try {
|
|
399
|
+
const result = await waitRun({
|
|
400
|
+
store: getStore(),
|
|
401
|
+
runId,
|
|
402
|
+
timeoutMs: parsed.timeoutMs,
|
|
403
|
+
until: untilRaw,
|
|
404
|
+
signal,
|
|
405
|
+
});
|
|
406
|
+
if (!result.ok) {
|
|
407
|
+
if (result.code === "aborted") {
|
|
408
|
+
if (parsed.json) {
|
|
409
|
+
printJson(out, { error: result.error, code: "aborted" });
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
out.error(result.error);
|
|
413
|
+
}
|
|
414
|
+
return 130;
|
|
415
|
+
}
|
|
416
|
+
if (parsed.json) {
|
|
417
|
+
const payload = { error: result.error };
|
|
418
|
+
if (result.status !== undefined)
|
|
419
|
+
payload.status = result.status;
|
|
420
|
+
printJson(out, payload);
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
out.error(result.error);
|
|
424
|
+
}
|
|
425
|
+
return 1;
|
|
426
|
+
}
|
|
427
|
+
if (parsed.json) {
|
|
428
|
+
printJson(out, result);
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
out.log(`${result.reason}\t${result.run.status}`);
|
|
432
|
+
}
|
|
433
|
+
return 0;
|
|
434
|
+
}
|
|
435
|
+
finally {
|
|
436
|
+
if (onSigInt !== undefined) {
|
|
437
|
+
process.removeListener("SIGINT", onSigInt);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
case "answer": {
|
|
442
|
+
const blocked = await guardHost();
|
|
443
|
+
if (blocked !== undefined)
|
|
444
|
+
return blocked;
|
|
445
|
+
if (!parsed.runId) {
|
|
446
|
+
return usageError(out, "Missing --run");
|
|
447
|
+
}
|
|
448
|
+
if (!parsed.stageId) {
|
|
449
|
+
return usageError(out, "Missing --stage");
|
|
450
|
+
}
|
|
451
|
+
let answerRaw = parsed.answer;
|
|
452
|
+
if (answerRaw === undefined) {
|
|
453
|
+
if (stdinIsTTY) {
|
|
454
|
+
return usageError(out, "Missing --answer (pass --answer '<json>' or pipe JSON on stdin)");
|
|
455
|
+
}
|
|
456
|
+
answerRaw = readStdin().trim();
|
|
457
|
+
if (answerRaw.length === 0) {
|
|
458
|
+
return usageError(out, "Missing --answer (pass --answer '<json>' or pipe JSON on stdin)");
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
let parsedJson;
|
|
462
|
+
try {
|
|
463
|
+
parsedJson = JSON.parse(answerRaw);
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
const message = "--answer must be valid JSON";
|
|
467
|
+
if (parsed.json) {
|
|
468
|
+
printJson(out, { error: message, status: 400 });
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
out.error(message);
|
|
472
|
+
}
|
|
473
|
+
return 1;
|
|
474
|
+
}
|
|
475
|
+
let answer;
|
|
476
|
+
try {
|
|
477
|
+
answer = parseAskOperatorAnswer(parsedJson);
|
|
478
|
+
}
|
|
479
|
+
catch (err) {
|
|
480
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
481
|
+
if (err instanceof AskOperatorError) {
|
|
482
|
+
if (parsed.json) {
|
|
483
|
+
printJson(out, { error: message, status: 400 });
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
out.error(message);
|
|
487
|
+
}
|
|
488
|
+
return 1;
|
|
489
|
+
}
|
|
490
|
+
out.error(message);
|
|
491
|
+
return 1;
|
|
492
|
+
}
|
|
493
|
+
const manager = buildManager();
|
|
494
|
+
const result = await manager.deliverAnswer(parsed.runId, parsed.stageId, answer);
|
|
495
|
+
if (!result.ok) {
|
|
496
|
+
const payload = { error: result.reason };
|
|
497
|
+
if (result.status !== undefined)
|
|
498
|
+
payload.status = result.status;
|
|
499
|
+
if (parsed.json) {
|
|
500
|
+
printJson(out, payload);
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
out.error(result.reason);
|
|
504
|
+
}
|
|
505
|
+
return 1;
|
|
506
|
+
}
|
|
507
|
+
if (parsed.json) {
|
|
508
|
+
printJson(out, { ok: true });
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
out.log("ok");
|
|
512
|
+
}
|
|
513
|
+
return 0;
|
|
514
|
+
}
|
|
515
|
+
case "retry": {
|
|
516
|
+
const blocked = await guardHost();
|
|
517
|
+
if (blocked !== undefined)
|
|
518
|
+
return blocked;
|
|
519
|
+
if (!parsed.runId || !parsed.stageId) {
|
|
520
|
+
return usageError(out, "Missing --run and/or --stage");
|
|
521
|
+
}
|
|
522
|
+
const manager = buildManager();
|
|
523
|
+
const result = await manager.retryStageUntilStop(parsed.runId, parsed.stageId);
|
|
524
|
+
if (!result.ok) {
|
|
525
|
+
if (parsed.json) {
|
|
526
|
+
printJson(out, {
|
|
527
|
+
...mapRetryStageFailure(result),
|
|
528
|
+
status: result.status,
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
out.error(result.reason);
|
|
533
|
+
}
|
|
534
|
+
return 1;
|
|
535
|
+
}
|
|
536
|
+
return reportCliRun({ kind: "completion", result: result.pipeline }, { json: parsed.json, io: mutatingIo });
|
|
537
|
+
}
|
|
538
|
+
case "abandon": {
|
|
539
|
+
const blocked = await guardHost();
|
|
540
|
+
if (blocked !== undefined)
|
|
541
|
+
return blocked;
|
|
542
|
+
if (!parsed.runId || !parsed.stageId) {
|
|
543
|
+
return usageError(out, "Missing --run and/or --stage");
|
|
544
|
+
}
|
|
545
|
+
const manager = buildManager();
|
|
546
|
+
const result = await manager.abandonStage(parsed.runId, parsed.stageId);
|
|
547
|
+
if (!result.ok) {
|
|
548
|
+
const payload = { error: result.reason };
|
|
549
|
+
if (result.status !== undefined)
|
|
550
|
+
payload.status = result.status;
|
|
551
|
+
if (parsed.json) {
|
|
552
|
+
printJson(out, payload);
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
out.error(result.reason);
|
|
556
|
+
}
|
|
557
|
+
return 1;
|
|
558
|
+
}
|
|
559
|
+
if (parsed.json) {
|
|
560
|
+
printJson(out, {
|
|
561
|
+
ok: true,
|
|
562
|
+
runId: result.runId,
|
|
563
|
+
stageId: result.stageId,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
out.log(`${result.runId}\t${result.stageId}`);
|
|
568
|
+
}
|
|
569
|
+
return 0;
|
|
570
|
+
}
|
|
571
|
+
case "rerun": {
|
|
572
|
+
const blocked = await guardHost();
|
|
573
|
+
if (blocked !== undefined)
|
|
574
|
+
return blocked;
|
|
575
|
+
if (!parsed.runId) {
|
|
576
|
+
return usageError(out, "Missing --run");
|
|
577
|
+
}
|
|
578
|
+
const manager = buildManager();
|
|
579
|
+
const started = await manager.rerun(parsed.runId);
|
|
580
|
+
if (!started.ok) {
|
|
581
|
+
return reportCliRun({ kind: "start-failure", started }, { json: parsed.json, io: mutatingIo });
|
|
582
|
+
}
|
|
583
|
+
return completeCliRun(started, mutatingIo, {
|
|
584
|
+
json: parsed.json,
|
|
585
|
+
store: getStore(),
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
default:
|
|
589
|
+
return usageError(out, `Unknown runs subcommand: ${parsed.subcommand}`);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const SKILLS_USAGE = "Usage:\n sf skills list\n sf skills install --from-path <dir> [--skill-name <name>]\n sf skills install --from-zip <url-or-path> [--skill-name <name>] [--checksum sha256:<hex>]";
|
|
2
|
+
export type SkillsCommandIo = {
|
|
3
|
+
log: (line: string) => void;
|
|
4
|
+
error: (line: string) => void;
|
|
5
|
+
fetch: typeof globalThis.fetch;
|
|
6
|
+
runDoctor: (scriptPath: string, cwd: string) => number;
|
|
7
|
+
};
|
|
8
|
+
export declare function runSkillsCommand(args: string[], options?: {
|
|
9
|
+
cwd?: string;
|
|
10
|
+
projectRoot?: string;
|
|
11
|
+
io?: Partial<SkillsCommandIo>;
|
|
12
|
+
}): Promise<number>;
|
|
13
|
+
export declare function createStoredZip(files: {
|
|
14
|
+
name: string;
|
|
15
|
+
content: string | Buffer;
|
|
16
|
+
}[]): Buffer;
|