stageflow 0.2.0 → 0.3.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 +158 -14
- package/dist/agent/activity.d.ts +10 -1
- package/dist/agent/activity.js +37 -2
- package/dist/agent/activityObserver.d.ts +5 -1
- package/dist/agent/activityObserver.js +174 -11
- package/dist/agent/fakeAgent.js +4 -0
- package/dist/agent/piAdapter.d.ts +16 -0
- package/dist/agent/piAdapter.js +88 -21
- package/dist/agent/port.d.ts +2 -0
- package/dist/agent/providerAuth.js +12 -7
- package/dist/catalog/displayCatalogPath.d.ts +12 -0
- package/dist/catalog/displayCatalogPath.js +28 -0
- package/dist/cli/initCommand.d.ts +9 -0
- package/dist/cli/initCommand.js +71 -0
- package/dist/cli/initTemplates.d.ts +3 -0
- package/dist/cli/initTemplates.js +19 -0
- package/dist/cli/operatorCatalog.d.ts +10 -0
- package/dist/cli/operatorCatalog.js +16 -0
- package/dist/cli/runCommand.d.ts +3 -1
- package/dist/cli/runCommand.js +48 -7
- package/dist/cli/validateCommand.d.ts +1 -1
- package/dist/cli/validateCommand.js +20 -3
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +52 -19
- package/dist/config/browseCatalog.d.ts +45 -0
- package/dist/config/browseCatalog.js +170 -0
- package/dist/config/createPipeline.d.ts +12 -3
- package/dist/config/createPipeline.js +292 -100
- package/dist/config/createStage.d.ts +10 -4
- package/dist/config/createStage.js +44 -14
- package/dist/config/listConfig.d.ts +14 -19
- package/dist/config/listConfig.js +26 -191
- package/dist/config/listModelsFromManifest.d.ts +2 -0
- package/dist/config/listModelsFromManifest.js +4 -0
- package/dist/config/loadOutcome.d.ts +4 -0
- package/dist/config/loadPipeline.d.ts +1 -15
- package/dist/config/loadPipeline.js +84 -142
- package/dist/config/loadStage.d.ts +4 -0
- package/dist/config/loadStage.js +60 -32
- package/dist/config/loadStageflowManifest.d.ts +5 -0
- package/dist/config/loadStageflowManifest.js +157 -0
- package/dist/config/mergePipelineIncludes.d.ts +9 -0
- package/dist/config/mergePipelineIncludes.js +141 -0
- package/dist/config/normalizePipelineStageEntry.d.ts +16 -0
- package/dist/config/normalizePipelineStageEntry.js +186 -0
- package/dist/config/pipelineStageKeys.d.ts +4 -0
- package/dist/config/pipelineStageKeys.js +14 -0
- package/dist/config/resolveCatalogContext.d.ts +13 -0
- package/dist/config/resolveCatalogContext.js +13 -0
- package/dist/config/resolveForkEmitContext.d.ts +3 -0
- package/dist/config/resolveForkEmitContext.js +12 -0
- package/dist/config/resolvePipelineDag.d.ts +5 -1
- package/dist/config/resolvePipelineDag.js +54 -36
- package/dist/config/scanCatalogPaths.d.ts +4 -0
- package/dist/config/scanCatalogPaths.js +83 -0
- package/dist/config/validateCatalog.d.ts +22 -4
- package/dist/config/validateCatalog.js +169 -132
- package/dist/envelope/check.js +7 -0
- package/dist/envelope/forkChoice.d.ts +3 -0
- package/dist/envelope/forkChoice.js +47 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/mcp/projectRun.d.ts +3 -0
- package/dist/mcp/projectRun.js +7 -0
- package/dist/mcp/tools.js +28 -11
- package/dist/project/findProjectRoot.d.ts +2 -0
- package/dist/project/findProjectRoot.js +51 -0
- package/dist/project/globalHome.d.ts +2 -0
- package/dist/project/globalHome.js +19 -0
- package/dist/project/resolveProjectContext.d.ts +9 -0
- package/dist/project/resolveProjectContext.js +38 -0
- package/dist/project/resolveStageflowContext.d.ts +11 -0
- package/dist/project/resolveStageflowContext.js +66 -0
- package/dist/runstore/normalizeCatalogPath.d.ts +1 -0
- package/dist/runstore/normalizeCatalogPath.js +4 -0
- package/dist/runstore/port.d.ts +9 -0
- package/dist/runstore/runProjection.js +11 -1
- package/dist/runstore/sqlite/SqliteRunStore.js +30 -4
- package/dist/runstore/sqlite/schema.d.ts +1 -1
- package/dist/runstore/sqlite/schema.js +4 -1
- package/dist/runtime/credentialBinding.d.ts +8 -6
- package/dist/runtime/credentialBinding.js +18 -24
- package/dist/runtime/pipelineRunner.d.ts +3 -0
- package/dist/runtime/pipelineRunner.js +15 -1
- package/dist/runtime/pipelineScheduler.d.ts +2 -0
- package/dist/runtime/pipelineScheduler.js +51 -11
- package/dist/runtime/reloadRunCatalog.d.ts +5 -0
- package/dist/runtime/reloadRunCatalog.js +44 -0
- package/dist/runtime/resumeReconstruct.d.ts +1 -0
- package/dist/runtime/resumeReconstruct.js +23 -7
- package/dist/runtime/runManager.d.ts +4 -0
- package/dist/runtime/runManager.js +36 -16
- package/dist/runtime/settingsFile.d.ts +12 -0
- package/dist/runtime/settingsFile.js +87 -6
- package/dist/runtime/stageAttemptBootstrap.js +3 -0
- package/dist/runtime/stageProcessLauncher.js +3 -0
- package/dist/runtime/stageRecovery.js +3 -1
- package/dist/runtime/stageWorker.js +2 -6
- package/dist/server/http.js +33 -9
- package/dist/tools/emitStageEnvelope.d.ts +3 -1
- package/dist/tools/emitStageEnvelope.js +11 -1
- package/dist/types/envelope.d.ts +1 -0
- package/dist/types/forkChoice.d.ts +8 -0
- package/dist/types/forkChoice.js +1 -0
- package/dist/types/pipeline.d.ts +51 -2
- package/dist/types/stageflowManifest.d.ts +23 -0
- package/dist/types/stageflowManifest.js +1 -0
- package/dist/ui/assets/index-CFSzDZje.js +118 -0
- package/dist/ui/assets/{index-DCsDopak.css → index-DefBlEvN.css} +1 -1
- package/dist/ui/index.html +3 -2
- package/dist/ui/stageflow-icon.svg +12 -0
- package/package.json +7 -5
- package/dist/ui/assets/index-Cry0Tpfx.js +0 -118
package/dist/agent/piAdapter.js
CHANGED
|
@@ -41,8 +41,8 @@ import { createAskOperatorTool, } from "../tools/askOperator.js";
|
|
|
41
41
|
import { createWriteStageArtifactTool } from "../tools/writeStageArtifact.js";
|
|
42
42
|
import "./cursorProvider.js";
|
|
43
43
|
import { findProviderSupport } from "./providerSupport.js";
|
|
44
|
-
import { mapSessionEventToActivity } from "./activity.js";
|
|
45
|
-
import { createStageActivityObserver } from "./activityObserver.js";
|
|
44
|
+
import { mapSessionEventToActivity, readActivityVerbose } from "./activity.js";
|
|
45
|
+
import { createStageActivityObserver, } from "./activityObserver.js";
|
|
46
46
|
import { DEFAULT_STAGE_TIMEOUT_MS, runStageViaOpen } from "./port.js";
|
|
47
47
|
/**
|
|
48
48
|
* Stage tool allowlist for sealed Pi sessions.
|
|
@@ -260,31 +260,98 @@ export function createConnectedAskWaitStageHandle(options) {
|
|
|
260
260
|
},
|
|
261
261
|
};
|
|
262
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* Extract display text from a Pi tool_execution_update partialResult.
|
|
265
|
+
* Prefers content[].text blocks; otherwise JSON-stringifies.
|
|
266
|
+
*/
|
|
267
|
+
export function extractPartialResultText(partialResult) {
|
|
268
|
+
if (partialResult === undefined || partialResult === null) {
|
|
269
|
+
return "";
|
|
270
|
+
}
|
|
271
|
+
if (typeof partialResult === "string") {
|
|
272
|
+
return partialResult;
|
|
273
|
+
}
|
|
274
|
+
if (typeof partialResult === "object") {
|
|
275
|
+
const content = partialResult.content;
|
|
276
|
+
if (Array.isArray(content)) {
|
|
277
|
+
const parts = [];
|
|
278
|
+
for (const block of content) {
|
|
279
|
+
if (!block || typeof block !== "object")
|
|
280
|
+
continue;
|
|
281
|
+
const b = block;
|
|
282
|
+
if (b.type === "text" && typeof b.text === "string") {
|
|
283
|
+
parts.push(b.text);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (parts.length > 0) {
|
|
287
|
+
return parts.join("");
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
return JSON.stringify(partialResult);
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
return String(partialResult);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return String(partialResult);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Adapter-edge routing: stream deltas to the observer, map milestones to
|
|
301
|
+
* StageActivityEvent. Verbose thinking / live tool partials go to observer
|
|
302
|
+
* hooks (stderr streams + coalesced/throttled persist via onActivity).
|
|
303
|
+
*/
|
|
304
|
+
export function routeSessionEventToProgress(event, options) {
|
|
305
|
+
const { observer, verbose } = options;
|
|
306
|
+
if (event.type === "message_update") {
|
|
307
|
+
const ame = event.assistantMessageEvent;
|
|
308
|
+
if (ame && typeof ame === "object") {
|
|
309
|
+
const update = ame;
|
|
310
|
+
if (update.type === "text_delta") {
|
|
311
|
+
observer.onAssistantTextDelta(update.delta ?? "");
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (verbose && update.type === "thinking_delta") {
|
|
315
|
+
observer.onThinkingDelta(update.delta ?? "");
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (verbose &&
|
|
322
|
+
event.type === "tool_execution_update") {
|
|
323
|
+
const text = extractPartialResultText(event.partialResult);
|
|
324
|
+
if (text) {
|
|
325
|
+
observer.onToolPartialResult(text);
|
|
326
|
+
}
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (event.type === "message_end" || event.type === "agent_end") {
|
|
330
|
+
observer.onStreamBoundary();
|
|
331
|
+
}
|
|
332
|
+
if (event.type === "agent_start" ||
|
|
333
|
+
event.type === "turn_start" ||
|
|
334
|
+
event.type === "tool_execution_start") {
|
|
335
|
+
observer.onStreamBoundary();
|
|
336
|
+
}
|
|
337
|
+
const activity = mapSessionEventToActivity(event);
|
|
338
|
+
if (!activity) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
observer.onActivity(activity);
|
|
342
|
+
}
|
|
263
343
|
/**
|
|
264
344
|
* Map Pi session events to StageActivityEvent at the adapter edge, then
|
|
265
345
|
* forward to the activity observer (stderr + onActivity).
|
|
266
346
|
*/
|
|
267
347
|
function attachStageProgress(session, onActivity) {
|
|
268
348
|
const observer = createStageActivityObserver({ onActivity, writeStderr: true });
|
|
349
|
+
const verbose = readActivityVerbose();
|
|
269
350
|
const unsubscribe = session.subscribe((event) => {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
if (event.type === "message_end" || event.type === "agent_end") {
|
|
276
|
-
observer.onStreamBoundary();
|
|
277
|
-
}
|
|
278
|
-
if (event.type === "agent_start" ||
|
|
279
|
-
event.type === "turn_start" ||
|
|
280
|
-
event.type === "tool_execution_start") {
|
|
281
|
-
observer.onStreamBoundary();
|
|
282
|
-
}
|
|
283
|
-
const activity = mapSessionEventToActivity(event);
|
|
284
|
-
if (!activity) {
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
observer.onActivity(activity);
|
|
351
|
+
routeSessionEventToProgress(event, {
|
|
352
|
+
observer,
|
|
353
|
+
verbose,
|
|
354
|
+
});
|
|
288
355
|
});
|
|
289
356
|
return () => {
|
|
290
357
|
unsubscribe();
|
|
@@ -616,7 +683,7 @@ async function prepareStageSessionWiring(input, sessionManager, existingAskWaitC
|
|
|
616
683
|
const provider = findProviderSupport(input.stage.model);
|
|
617
684
|
const capture = {};
|
|
618
685
|
const askWaitChannel = existingAskWaitChannel ?? new AskOperatorWaitChannel();
|
|
619
|
-
const emitDef = createEmitStageEnvelopeTool(capture, input.stage.payload_schema);
|
|
686
|
+
const emitDef = createEmitStageEnvelopeTool(capture, input.stage.payload_schema, input.forkEmitContext);
|
|
620
687
|
const emitTool = defineTool(emitDef);
|
|
621
688
|
const askDef = createAskOperatorTool({
|
|
622
689
|
requestWait: (prompt) => askWaitChannel.requestWait(prompt),
|
package/dist/agent/port.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { StageEnvelope } from "../types/envelope.js";
|
|
2
|
+
import type { ForkEmitContext } from "../types/forkChoice.js";
|
|
2
3
|
import type { StageConfig } from "../types/stage.js";
|
|
3
4
|
import type { TaskFile } from "../types/task.js";
|
|
4
5
|
import type { StageActivityEvent } from "./activity.js";
|
|
@@ -15,6 +16,7 @@ export type StageRunInput = {
|
|
|
15
16
|
/** Optional observe hook; HITL wait/answer uses openStage beside this. */
|
|
16
17
|
onActivity?: (event: StageActivityEvent) => void;
|
|
17
18
|
skillFilePath?: string;
|
|
19
|
+
forkEmitContext?: ForkEmitContext;
|
|
18
20
|
};
|
|
19
21
|
export type StageRunResult = {
|
|
20
22
|
ok: true;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { CredentialSynchronizationError, ModelRuntime, } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { isUsableAuthFile, parseCredentialSource, piHomeAuthPath,
|
|
2
|
+
import { isUsableAuthFile, parseCredentialSource, piHomeAuthPath, resolveCredentialBinding, writeCredentialSourceToContext, } from "../runtime/credentialBinding.js";
|
|
3
|
+
import { readPersistedCredentialSourceFromContext, } from "../runtime/settingsFile.js";
|
|
4
|
+
import { resolveProjectContext } from "../project/resolveProjectContext.js";
|
|
3
5
|
export function makeMutationLock() {
|
|
4
6
|
let tail = Promise.resolve();
|
|
5
7
|
return {
|
|
@@ -245,8 +247,9 @@ export async function logoutProvider(cwd, providerId, ctx = defaultContext) {
|
|
|
245
247
|
});
|
|
246
248
|
}
|
|
247
249
|
export function detectPiHome(cwd) {
|
|
248
|
-
const
|
|
249
|
-
const
|
|
250
|
+
const projectCtx = resolveProjectContext(cwd);
|
|
251
|
+
const binding = resolveCredentialBinding(projectCtx);
|
|
252
|
+
const persisted = readPersistedCredentialSourceFromContext(projectCtx);
|
|
250
253
|
return {
|
|
251
254
|
piHomeUsable: isUsableAuthFile(piHomeAuthPath()),
|
|
252
255
|
...(persisted !== undefined ? { credentialSource: persisted } : {}),
|
|
@@ -255,8 +258,9 @@ export function detectPiHome(cwd) {
|
|
|
255
258
|
};
|
|
256
259
|
}
|
|
257
260
|
export function getCredentialSourceSettings(cwd) {
|
|
258
|
-
const
|
|
259
|
-
const
|
|
261
|
+
const projectCtx = resolveProjectContext(cwd);
|
|
262
|
+
const persisted = readPersistedCredentialSourceFromContext(projectCtx);
|
|
263
|
+
const binding = resolveCredentialBinding(projectCtx);
|
|
260
264
|
return {
|
|
261
265
|
...(persisted !== undefined ? { credentialSource: persisted } : {}),
|
|
262
266
|
binding: {
|
|
@@ -270,8 +274,9 @@ export function setCredentialSource(cwd, credentialSource) {
|
|
|
270
274
|
if (parsed === undefined) {
|
|
271
275
|
throw new ProviderAuthError('credentialSource must be "pi_home" or "sf_owned"', 400);
|
|
272
276
|
}
|
|
273
|
-
|
|
274
|
-
|
|
277
|
+
const projectCtx = resolveProjectContext(cwd);
|
|
278
|
+
writeCredentialSourceToContext(projectCtx, parsed);
|
|
279
|
+
const binding = resolveCredentialBinding(projectCtx);
|
|
275
280
|
return {
|
|
276
281
|
credentialSource: parsed,
|
|
277
282
|
binding: {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function displayCatalogPath(absPath: string, projectRoot?: string): string;
|
|
2
|
+
export declare function normalizeCatalogSlashes(pathValue: string): string;
|
|
3
|
+
export type PipelinePathMatchInput = {
|
|
4
|
+
pipeline_id: string;
|
|
5
|
+
pipeline_path?: string;
|
|
6
|
+
project_root?: string;
|
|
7
|
+
};
|
|
8
|
+
export type PipelineListingPath = {
|
|
9
|
+
id: string;
|
|
10
|
+
path: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function matchPipelineRun(run: PipelinePathMatchInput, pipeline: PipelineListingPath): boolean;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function displayCatalogPath(absPath, projectRoot) {
|
|
2
|
+
if (projectRoot) {
|
|
3
|
+
const root = projectRoot.replace(/\\/g, "/").replace(/\/$/, "");
|
|
4
|
+
const normalized = absPath.replace(/\\/g, "/");
|
|
5
|
+
if (normalized === root)
|
|
6
|
+
return ".";
|
|
7
|
+
const prefix = `${root}/`;
|
|
8
|
+
if (normalized.startsWith(prefix)) {
|
|
9
|
+
return normalized.slice(prefix.length);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const parts = absPath.replace(/\\/g, "/").split("/");
|
|
13
|
+
return parts[parts.length - 1] ?? absPath;
|
|
14
|
+
}
|
|
15
|
+
export function normalizeCatalogSlashes(pathValue) {
|
|
16
|
+
return pathValue.replace(/\\/g, "/");
|
|
17
|
+
}
|
|
18
|
+
export function matchPipelineRun(run, pipeline) {
|
|
19
|
+
if (run.pipeline_id === pipeline.id)
|
|
20
|
+
return true;
|
|
21
|
+
if (run.pipeline_path && run.project_root) {
|
|
22
|
+
const rel = displayCatalogPath(run.pipeline_path, run.project_root);
|
|
23
|
+
if (normalizeCatalogSlashes(rel) === normalizeCatalogSlashes(pipeline.path)) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const INIT_USAGE = "Usage:\n sf init";
|
|
2
|
+
export type InitCommandIo = {
|
|
3
|
+
log: (line: string) => void;
|
|
4
|
+
error: (line: string) => void;
|
|
5
|
+
};
|
|
6
|
+
export declare function runInitCommand(args: string[], options?: {
|
|
7
|
+
cwd?: string;
|
|
8
|
+
io?: Partial<InitCommandIo>;
|
|
9
|
+
}): Promise<number>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { access, mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { findProjectRoot } from "../project/findProjectRoot.js";
|
|
4
|
+
import { ensureGlobalHome } from "../project/globalHome.js";
|
|
5
|
+
import { HELLO_PIPELINE_YAML, HELLO_TASK_YAML, STAGEFLOW_YAML, } from "./initTemplates.js";
|
|
6
|
+
export const INIT_USAGE = `Usage:
|
|
7
|
+
sf init`;
|
|
8
|
+
const defaultIo = {
|
|
9
|
+
log: (line) => console.log(line),
|
|
10
|
+
error: (line) => console.error(line),
|
|
11
|
+
};
|
|
12
|
+
const INIT_TARGETS = [
|
|
13
|
+
{ relPath: "stageflow.yaml", content: STAGEFLOW_YAML },
|
|
14
|
+
{ relPath: "pipelines/hello.pipeline.yaml", content: HELLO_PIPELINE_YAML },
|
|
15
|
+
{ relPath: "tasks/hello.task.yaml", content: HELLO_TASK_YAML },
|
|
16
|
+
];
|
|
17
|
+
async function fileExists(absPath) {
|
|
18
|
+
try {
|
|
19
|
+
await access(absPath);
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function parseInitArgs(args) {
|
|
27
|
+
if (args.length === 0) {
|
|
28
|
+
return { help: false };
|
|
29
|
+
}
|
|
30
|
+
if (args[0] === "--help" || args[0] === "-h") {
|
|
31
|
+
return { help: true };
|
|
32
|
+
}
|
|
33
|
+
if (args[0].startsWith("-")) {
|
|
34
|
+
throw new Error(`Unknown flag: ${args[0]}`);
|
|
35
|
+
}
|
|
36
|
+
throw new Error(`Unexpected argument: ${args[0]}`);
|
|
37
|
+
}
|
|
38
|
+
export async function runInitCommand(args, options = {}) {
|
|
39
|
+
const cwd = options.cwd ?? process.cwd();
|
|
40
|
+
const out = { ...defaultIo, ...options.io };
|
|
41
|
+
try {
|
|
42
|
+
const parsed = parseInitArgs(args);
|
|
43
|
+
if (parsed.help) {
|
|
44
|
+
out.error(INIT_USAGE);
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
const initRoot = findProjectRoot(cwd) ?? path.resolve(cwd);
|
|
48
|
+
ensureGlobalHome();
|
|
49
|
+
for (const target of INIT_TARGETS) {
|
|
50
|
+
const absPath = path.join(initRoot, target.relPath);
|
|
51
|
+
if (await fileExists(absPath)) {
|
|
52
|
+
out.log(`Skipped ${target.relPath} (exists)`);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
await mkdir(path.dirname(absPath), { recursive: true });
|
|
56
|
+
await writeFile(absPath, target.content, "utf8");
|
|
57
|
+
out.log(`Created ${target.relPath}`);
|
|
58
|
+
}
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
const message = err instanceof Error
|
|
63
|
+
? err.message
|
|
64
|
+
: typeof err === "string"
|
|
65
|
+
? err
|
|
66
|
+
: "Init command failed";
|
|
67
|
+
out.error(message);
|
|
68
|
+
out.error(INIT_USAGE);
|
|
69
|
+
return 1;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const STAGEFLOW_YAML = "version: 1\ncatalog:\n pipelines:\n - pipelines\n tasks:\n - tasks\n patterns:\n pipeline: \"*.pipeline.yaml\"\n task: \"*.task.yaml\"\n";
|
|
2
|
+
export declare const HELLO_PIPELINE_YAML = "id: hello\nstages:\n - id: hello\n system_prompt: Say hello and emit a success envelope.\n model: anthropic/claude-sonnet-4-5\n";
|
|
3
|
+
export declare const HELLO_TASK_YAML = "id: hello\ngoal: Run the hello pipeline scaffold.\n";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const STAGEFLOW_YAML = `version: 1
|
|
2
|
+
catalog:
|
|
3
|
+
pipelines:
|
|
4
|
+
- pipelines
|
|
5
|
+
tasks:
|
|
6
|
+
- tasks
|
|
7
|
+
patterns:
|
|
8
|
+
pipeline: "*.pipeline.yaml"
|
|
9
|
+
task: "*.task.yaml"
|
|
10
|
+
`;
|
|
11
|
+
export const HELLO_PIPELINE_YAML = `id: hello
|
|
12
|
+
stages:
|
|
13
|
+
- id: hello
|
|
14
|
+
system_prompt: Say hello and emit a success envelope.
|
|
15
|
+
model: anthropic/claude-sonnet-4-5
|
|
16
|
+
`;
|
|
17
|
+
export const HELLO_TASK_YAML = `id: hello
|
|
18
|
+
goal: Run the hello pipeline scaffold.
|
|
19
|
+
`;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { OperatorCatalog } from "../runtime/stageAttemptBootstrap.js";
|
|
2
|
+
export type OperatorCatalogFlags = {
|
|
3
|
+
operatorCwd?: string;
|
|
4
|
+
operatorAgentDir?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function resolveOperatorCatalog(input: {
|
|
7
|
+
flags: OperatorCatalogFlags;
|
|
8
|
+
env: Record<string, string | undefined>;
|
|
9
|
+
defaultCwd: string;
|
|
10
|
+
}): OperatorCatalog;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
function nonempty(value) {
|
|
3
|
+
if (value === undefined)
|
|
4
|
+
return undefined;
|
|
5
|
+
const trimmed = value.trim();
|
|
6
|
+
return trimmed.length === 0 ? undefined : trimmed;
|
|
7
|
+
}
|
|
8
|
+
export function resolveOperatorCatalog(input) {
|
|
9
|
+
const cwd = nonempty(input.flags.operatorCwd) ??
|
|
10
|
+
nonempty(input.env.STAGEFLOW_OPERATOR_CWD) ??
|
|
11
|
+
input.defaultCwd;
|
|
12
|
+
const agentDir = nonempty(input.flags.operatorAgentDir) ??
|
|
13
|
+
nonempty(input.env.STAGEFLOW_OPERATOR_AGENT_DIR) ??
|
|
14
|
+
getAgentDir();
|
|
15
|
+
return { cwd, agentDir };
|
|
16
|
+
}
|
package/dist/cli/runCommand.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type StartRunResult } from "../runtime/runManager.js";
|
|
2
2
|
import { type CliRunReportIo } from "./runOutput.js";
|
|
3
|
-
export declare const RUN_USAGE = "Usage:\n sf run --task <path> --pipeline <
|
|
3
|
+
export declare const RUN_USAGE = "Usage:\n sf run --task <path> --pipeline <path> [--checkout <path>] [--json] [--skip-gates] [--git-sha <sha>] [--ci-pr-url <url>] [--ci-job-url <url>] [--operator-cwd <path>] [--operator-agent-dir <path>]";
|
|
4
4
|
export type RunCommandIo = CliRunReportIo;
|
|
5
5
|
export type StartRunFn = (input: {
|
|
6
6
|
task: string;
|
|
@@ -18,6 +18,8 @@ export declare function completeCliRun(started: Extract<StartRunResult, {
|
|
|
18
18
|
}): Promise<number>;
|
|
19
19
|
export declare function runRunCommand(args: string[], options?: {
|
|
20
20
|
cwd?: string;
|
|
21
|
+
projectRoot?: string;
|
|
22
|
+
isGitProject?: boolean;
|
|
21
23
|
io?: Partial<RunCommandIo>;
|
|
22
24
|
startRun?: StartRunFn;
|
|
23
25
|
env?: Record<string, string | undefined>;
|
package/dist/cli/runCommand.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
2
1
|
import { PiAgentAdapter } from "../agent/piAdapter.js";
|
|
3
2
|
import { createRunStore } from "../runstore/createStore.js";
|
|
4
3
|
import { PipelineValidationError } from "../runtime/pipelineRunner.js";
|
|
@@ -6,9 +5,10 @@ import { RunManager } from "../runtime/runManager.js";
|
|
|
6
5
|
import { readStageExecutionMode } from "../runtime/stageConcurrency.js";
|
|
7
6
|
import { reportCliRun, } from "./runOutput.js";
|
|
8
7
|
import { resolveCiIdentity } from "./ciIdentity.js";
|
|
8
|
+
import { resolveOperatorCatalog } from "./operatorCatalog.js";
|
|
9
9
|
import { exitCodeForValidation, formatValidationHuman, formatValidationJson, } from "./validateOutput.js";
|
|
10
10
|
export const RUN_USAGE = `Usage:
|
|
11
|
-
sf run --task <path> --pipeline <
|
|
11
|
+
sf run --task <path> --pipeline <path> [--checkout <path>] [--json] [--skip-gates] [--git-sha <sha>] [--ci-pr-url <url>] [--ci-job-url <url>] [--operator-cwd <path>] [--operator-agent-dir <path>]`;
|
|
12
12
|
const defaultIo = {
|
|
13
13
|
log: (line) => console.log(line),
|
|
14
14
|
error: (line) => console.error(line),
|
|
@@ -26,6 +26,8 @@ function parseRunArgs(args) {
|
|
|
26
26
|
let gitSha;
|
|
27
27
|
let ciPrUrl;
|
|
28
28
|
let ciJobUrl;
|
|
29
|
+
let operatorCwd;
|
|
30
|
+
let operatorAgentDir;
|
|
29
31
|
let json = false;
|
|
30
32
|
let skipGates = false;
|
|
31
33
|
let help = false;
|
|
@@ -76,6 +78,20 @@ function parseRunArgs(args) {
|
|
|
76
78
|
}
|
|
77
79
|
ciJobUrl = value;
|
|
78
80
|
}
|
|
81
|
+
else if (arg === "--operator-cwd") {
|
|
82
|
+
const value = args[++i];
|
|
83
|
+
if (value === undefined || value.length === 0) {
|
|
84
|
+
throw new Error("Missing value for --operator-cwd");
|
|
85
|
+
}
|
|
86
|
+
operatorCwd = value;
|
|
87
|
+
}
|
|
88
|
+
else if (arg === "--operator-agent-dir") {
|
|
89
|
+
const value = args[++i];
|
|
90
|
+
if (value === undefined || value.length === 0) {
|
|
91
|
+
throw new Error("Missing value for --operator-agent-dir");
|
|
92
|
+
}
|
|
93
|
+
operatorAgentDir = value;
|
|
94
|
+
}
|
|
79
95
|
else if (arg === "--json") {
|
|
80
96
|
json = true;
|
|
81
97
|
}
|
|
@@ -89,16 +105,30 @@ function parseRunArgs(args) {
|
|
|
89
105
|
throw new Error(`Unexpected argument: ${arg}`);
|
|
90
106
|
}
|
|
91
107
|
}
|
|
92
|
-
return {
|
|
108
|
+
return {
|
|
109
|
+
help,
|
|
110
|
+
json,
|
|
111
|
+
skipGates,
|
|
112
|
+
task,
|
|
113
|
+
pipeline,
|
|
114
|
+
checkout,
|
|
115
|
+
gitSha,
|
|
116
|
+
ciPrUrl,
|
|
117
|
+
ciJobUrl,
|
|
118
|
+
operatorCwd,
|
|
119
|
+
operatorAgentDir,
|
|
120
|
+
};
|
|
93
121
|
}
|
|
94
|
-
function defaultStartRun(cwd) {
|
|
122
|
+
function defaultStartRun(cwd, projectRoot, isGitProject, operatorCatalog) {
|
|
95
123
|
return async (input) => {
|
|
96
|
-
const store = createRunStore({ rootDir:
|
|
124
|
+
const store = createRunStore({ rootDir: projectRoot });
|
|
97
125
|
const manager = new RunManager({
|
|
98
126
|
agent: new PiAgentAdapter(),
|
|
99
127
|
store,
|
|
100
128
|
cwd,
|
|
101
|
-
|
|
129
|
+
projectRoot,
|
|
130
|
+
isGitProject,
|
|
131
|
+
operatorCatalog,
|
|
102
132
|
executionMode: readStageExecutionMode(process.env, "process"),
|
|
103
133
|
});
|
|
104
134
|
return manager.startRun(input);
|
|
@@ -110,6 +140,8 @@ export async function completeCliRun(started, io = defaultIo, options = {}) {
|
|
|
110
140
|
}
|
|
111
141
|
export async function runRunCommand(args, options = {}) {
|
|
112
142
|
const cwd = options.cwd ?? process.cwd();
|
|
143
|
+
const projectRoot = options.projectRoot ?? cwd;
|
|
144
|
+
const isGitProject = options.isGitProject ?? false;
|
|
113
145
|
const out = { ...defaultIo, ...options.io };
|
|
114
146
|
let parsed;
|
|
115
147
|
try {
|
|
@@ -130,7 +162,16 @@ export async function runRunCommand(args, options = {}) {
|
|
|
130
162
|
out.error(RUN_USAGE);
|
|
131
163
|
return 1;
|
|
132
164
|
}
|
|
133
|
-
const
|
|
165
|
+
const operatorCatalog = resolveOperatorCatalog({
|
|
166
|
+
flags: {
|
|
167
|
+
operatorCwd: parsed.operatorCwd,
|
|
168
|
+
operatorAgentDir: parsed.operatorAgentDir,
|
|
169
|
+
},
|
|
170
|
+
env: options.env ?? process.env,
|
|
171
|
+
defaultCwd: cwd,
|
|
172
|
+
});
|
|
173
|
+
const startRun = options.startRun ??
|
|
174
|
+
defaultStartRun(cwd, projectRoot, isGitProject, operatorCatalog);
|
|
134
175
|
const identity = resolveCiIdentity({
|
|
135
176
|
flags: {
|
|
136
177
|
gitSha: parsed.gitSha,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { validateCatalog as defaultValidateCatalog, type ValidationResult } from "../config/validateCatalog.js";
|
|
2
|
-
export declare const VALIDATE_USAGE = "Usage:\n sf validate [--pipeline <
|
|
2
|
+
export declare const VALIDATE_USAGE = "Usage:\n sf validate [--pipeline <path>] [--task <path>] [--strict] [--json]\n\n With no flags, validates all pipelines and tasks declared in stageflow.yaml (manifest-all).\n --pipeline validates one pipeline file (includes uses:/include: transitively).\n --task validates one task file.";
|
|
3
3
|
export type ValidateCommandIo = {
|
|
4
4
|
log: (line: string) => void;
|
|
5
5
|
error: (line: string) => void;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { validateCatalog as defaultValidateCatalog, } from "../config/validateCatalog.js";
|
|
2
2
|
import { exitCodeForValidation, formatValidationHuman, formatValidationJson, } from "./validateOutput.js";
|
|
3
3
|
export const VALIDATE_USAGE = `Usage:
|
|
4
|
-
sf validate [--pipeline <
|
|
4
|
+
sf validate [--pipeline <path>] [--task <path>] [--strict] [--json]
|
|
5
|
+
|
|
6
|
+
With no flags, validates all pipelines and tasks declared in stageflow.yaml (manifest-all).
|
|
7
|
+
--pipeline validates one pipeline file (includes uses:/include: transitively).
|
|
8
|
+
--task validates one task file.`;
|
|
5
9
|
const defaultIo = {
|
|
6
10
|
log: (line) => console.log(line),
|
|
7
11
|
error: (line) => console.error(line),
|
|
@@ -14,6 +18,7 @@ function parseValidateArgs(args) {
|
|
|
14
18
|
return { help: true, strict: false, json: false };
|
|
15
19
|
}
|
|
16
20
|
let pipeline;
|
|
21
|
+
let task;
|
|
17
22
|
let strict = false;
|
|
18
23
|
let json = false;
|
|
19
24
|
let help = false;
|
|
@@ -29,6 +34,13 @@ function parseValidateArgs(args) {
|
|
|
29
34
|
}
|
|
30
35
|
pipeline = value;
|
|
31
36
|
}
|
|
37
|
+
else if (arg === "--task") {
|
|
38
|
+
const value = args[++i];
|
|
39
|
+
if (value === undefined || value.length === 0) {
|
|
40
|
+
throw new Error("Missing value for --task");
|
|
41
|
+
}
|
|
42
|
+
task = value;
|
|
43
|
+
}
|
|
32
44
|
else if (arg === "--strict") {
|
|
33
45
|
strict = true;
|
|
34
46
|
}
|
|
@@ -42,7 +54,10 @@ function parseValidateArgs(args) {
|
|
|
42
54
|
throw new Error(`Unexpected argument: ${arg}`);
|
|
43
55
|
}
|
|
44
56
|
}
|
|
45
|
-
|
|
57
|
+
if (pipeline !== undefined && task !== undefined) {
|
|
58
|
+
throw new Error("Use at most one of --pipeline or --task");
|
|
59
|
+
}
|
|
60
|
+
return { help, pipeline, task, strict, json };
|
|
46
61
|
}
|
|
47
62
|
export async function runValidateCommand(args, options = {}) {
|
|
48
63
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -54,10 +69,12 @@ export async function runValidateCommand(args, options = {}) {
|
|
|
54
69
|
out.error(VALIDATE_USAGE);
|
|
55
70
|
return 0;
|
|
56
71
|
}
|
|
72
|
+
const scope = parsed.pipeline ? "pipeline" : parsed.task ? "task" : "full";
|
|
57
73
|
const result = await validateCatalogFn({
|
|
58
|
-
scope
|
|
74
|
+
scope,
|
|
59
75
|
cwd,
|
|
60
76
|
pipeline: parsed.pipeline,
|
|
77
|
+
task: parsed.task,
|
|
61
78
|
strict: parsed.strict,
|
|
62
79
|
});
|
|
63
80
|
if (parsed.json) {
|
package/dist/cli.d.ts
CHANGED