stageflow 0.1.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 +190 -13
- 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/ciIdentity.d.ts +14 -0
- package/dist/cli/ciIdentity.js +52 -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 +26 -0
- package/dist/cli/runCommand.js +209 -0
- package/dist/cli/runOutput.d.ts +19 -0
- package/dist/cli/runOutput.js +125 -0
- package/dist/cli/validateCommand.d.ts +1 -1
- package/dist/cli/validateCommand.js +20 -3
- package/dist/cli.d.ts +12 -1
- package/dist/cli.js +82 -66
- 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/server.js +1 -1
- 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 +15 -0
- package/dist/runstore/runProjection.js +11 -1
- package/dist/runstore/sqlite/SqliteRunStore.js +46 -4
- package/dist/runstore/sqlite/schema.d.ts +1 -1
- package/dist/runstore/sqlite/schema.js +7 -1
- package/dist/runtime/credentialBinding.d.ts +8 -6
- package/dist/runtime/credentialBinding.js +18 -24
- package/dist/runtime/pipelineRunner.d.ts +11 -0
- package/dist/runtime/pipelineRunner.js +25 -1
- package/dist/runtime/pipelineScheduler.d.ts +3 -0
- package/dist/runtime/pipelineScheduler.js +70 -14
- 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 +27 -10
- package/dist/runtime/runManager.d.ts +8 -0
- package/dist/runtime/runManager.js +49 -18
- 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.d.ts +1 -0
- package/dist/runtime/stageProcessLauncher.js +6 -0
- package/dist/runtime/stageRecovery.js +3 -1
- package/dist/runtime/stageRunner.d.ts +2 -0
- package/dist/runtime/stageRunner.js +9 -2
- package/dist/runtime/stageWorker.js +4 -6
- package/dist/runtime/stageWorkerProtocol.d.ts +1 -0
- 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/agent/cursorExtension.d.ts +0 -12
- package/dist/agent/cursorExtension.js +0 -88
- package/dist/runstore/catalog.d.ts +0 -8
- package/dist/runstore/catalog.js +0 -16
- package/dist/runstore/disk/DiskRunStore.d.ts +0 -30
- package/dist/runstore/disk/DiskRunStore.js +0 -238
- package/dist/runstore/layout.d.ts +0 -22
- package/dist/runstore/layout.js +0 -58
- package/dist/runtime/hitlSeams.d.ts +0 -38
- package/dist/runtime/hitlSeams.js +0 -2
- 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,14 @@
|
|
|
1
|
+
export type CiIdentityFlags = {
|
|
2
|
+
gitSha?: string;
|
|
3
|
+
ciPrUrl?: string;
|
|
4
|
+
ciJobUrl?: string;
|
|
5
|
+
};
|
|
6
|
+
export type CiIdentity = {
|
|
7
|
+
gitSha?: string;
|
|
8
|
+
ciPrUrl?: string;
|
|
9
|
+
ciJobUrl?: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function resolveCiIdentity(input: {
|
|
12
|
+
flags: CiIdentityFlags;
|
|
13
|
+
env: Record<string, string | undefined>;
|
|
14
|
+
}): CiIdentity;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
function nonempty(value) {
|
|
2
|
+
if (value === undefined)
|
|
3
|
+
return undefined;
|
|
4
|
+
const trimmed = value.trim();
|
|
5
|
+
return trimmed.length === 0 ? undefined : trimmed;
|
|
6
|
+
}
|
|
7
|
+
function prNumberFromEnv(env) {
|
|
8
|
+
const ref = nonempty(env.GITHUB_REF);
|
|
9
|
+
if (ref !== undefined) {
|
|
10
|
+
const match = /^refs\/pull\/(\d+)\//.exec(ref);
|
|
11
|
+
if (match?.[1] !== undefined)
|
|
12
|
+
return match[1];
|
|
13
|
+
}
|
|
14
|
+
const refName = nonempty(env.GITHUB_REF_NAME);
|
|
15
|
+
if (refName !== undefined) {
|
|
16
|
+
const match = /^(\d+)\/merge$/.exec(refName);
|
|
17
|
+
if (match?.[1] !== undefined)
|
|
18
|
+
return match[1];
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
export function resolveCiIdentity(input) {
|
|
23
|
+
const result = {};
|
|
24
|
+
const gitSha = nonempty(input.flags.gitSha) ?? nonempty(input.env.GITHUB_SHA);
|
|
25
|
+
if (gitSha !== undefined)
|
|
26
|
+
result.gitSha = gitSha;
|
|
27
|
+
const flagPrUrl = nonempty(input.flags.ciPrUrl);
|
|
28
|
+
if (flagPrUrl !== undefined) {
|
|
29
|
+
result.ciPrUrl = flagPrUrl;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
const prNumber = prNumberFromEnv(input.env);
|
|
33
|
+
const server = nonempty(input.env.GITHUB_SERVER_URL);
|
|
34
|
+
const repo = nonempty(input.env.GITHUB_REPOSITORY);
|
|
35
|
+
if (prNumber !== undefined && server !== undefined && repo !== undefined) {
|
|
36
|
+
result.ciPrUrl = `${server}/${repo}/pull/${prNumber}`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const flagJobUrl = nonempty(input.flags.ciJobUrl);
|
|
40
|
+
if (flagJobUrl !== undefined) {
|
|
41
|
+
result.ciJobUrl = flagJobUrl;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
const server = nonempty(input.env.GITHUB_SERVER_URL);
|
|
45
|
+
const repo = nonempty(input.env.GITHUB_REPOSITORY);
|
|
46
|
+
const runId = nonempty(input.env.GITHUB_RUN_ID);
|
|
47
|
+
if (server !== undefined && repo !== undefined && runId !== undefined) {
|
|
48
|
+
result.ciJobUrl = `${server}/${repo}/actions/runs/${runId}`;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type StartRunResult } from "../runtime/runManager.js";
|
|
2
|
+
import { type CliRunReportIo } from "./runOutput.js";
|
|
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
|
+
export type RunCommandIo = CliRunReportIo;
|
|
5
|
+
export type StartRunFn = (input: {
|
|
6
|
+
task: string;
|
|
7
|
+
pipeline: string;
|
|
8
|
+
checkoutOverride?: string;
|
|
9
|
+
skipGates?: boolean;
|
|
10
|
+
gitSha?: string;
|
|
11
|
+
ciPrUrl?: string;
|
|
12
|
+
ciJobUrl?: string;
|
|
13
|
+
}) => Promise<StartRunResult>;
|
|
14
|
+
export declare function completeCliRun(started: Extract<StartRunResult, {
|
|
15
|
+
ok: true;
|
|
16
|
+
}>, io?: RunCommandIo, options?: {
|
|
17
|
+
json?: boolean;
|
|
18
|
+
}): Promise<number>;
|
|
19
|
+
export declare function runRunCommand(args: string[], options?: {
|
|
20
|
+
cwd?: string;
|
|
21
|
+
projectRoot?: string;
|
|
22
|
+
isGitProject?: boolean;
|
|
23
|
+
io?: Partial<RunCommandIo>;
|
|
24
|
+
startRun?: StartRunFn;
|
|
25
|
+
env?: Record<string, string | undefined>;
|
|
26
|
+
}): Promise<number>;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { PiAgentAdapter } from "../agent/piAdapter.js";
|
|
2
|
+
import { createRunStore } from "../runstore/createStore.js";
|
|
3
|
+
import { PipelineValidationError } from "../runtime/pipelineRunner.js";
|
|
4
|
+
import { RunManager } from "../runtime/runManager.js";
|
|
5
|
+
import { readStageExecutionMode } from "../runtime/stageConcurrency.js";
|
|
6
|
+
import { reportCliRun, } from "./runOutput.js";
|
|
7
|
+
import { resolveCiIdentity } from "./ciIdentity.js";
|
|
8
|
+
import { resolveOperatorCatalog } from "./operatorCatalog.js";
|
|
9
|
+
import { exitCodeForValidation, formatValidationHuman, formatValidationJson, } from "./validateOutput.js";
|
|
10
|
+
export const RUN_USAGE = `Usage:
|
|
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
|
+
const defaultIo = {
|
|
13
|
+
log: (line) => console.log(line),
|
|
14
|
+
error: (line) => console.error(line),
|
|
15
|
+
};
|
|
16
|
+
function parseRunArgs(args) {
|
|
17
|
+
if (args.length === 0) {
|
|
18
|
+
return { help: false, json: false, skipGates: false };
|
|
19
|
+
}
|
|
20
|
+
if (args[0] === "--help" || args[0] === "-h") {
|
|
21
|
+
return { help: true, json: false, skipGates: false };
|
|
22
|
+
}
|
|
23
|
+
let task;
|
|
24
|
+
let pipeline;
|
|
25
|
+
let checkout;
|
|
26
|
+
let gitSha;
|
|
27
|
+
let ciPrUrl;
|
|
28
|
+
let ciJobUrl;
|
|
29
|
+
let operatorCwd;
|
|
30
|
+
let operatorAgentDir;
|
|
31
|
+
let json = false;
|
|
32
|
+
let skipGates = false;
|
|
33
|
+
let help = false;
|
|
34
|
+
for (let i = 0; i < args.length; i++) {
|
|
35
|
+
const arg = args[i];
|
|
36
|
+
if (arg === "--help" || arg === "-h") {
|
|
37
|
+
help = true;
|
|
38
|
+
}
|
|
39
|
+
else if (arg === "--task") {
|
|
40
|
+
const value = args[++i];
|
|
41
|
+
if (value === undefined || value.length === 0) {
|
|
42
|
+
throw new Error("Missing value for --task");
|
|
43
|
+
}
|
|
44
|
+
task = value;
|
|
45
|
+
}
|
|
46
|
+
else if (arg === "--pipeline") {
|
|
47
|
+
const value = args[++i];
|
|
48
|
+
if (value === undefined || value.length === 0) {
|
|
49
|
+
throw new Error("Missing value for --pipeline");
|
|
50
|
+
}
|
|
51
|
+
pipeline = value;
|
|
52
|
+
}
|
|
53
|
+
else if (arg === "--checkout") {
|
|
54
|
+
const value = args[++i];
|
|
55
|
+
if (value === undefined || value.length === 0) {
|
|
56
|
+
throw new Error("Missing value for --checkout");
|
|
57
|
+
}
|
|
58
|
+
checkout = value;
|
|
59
|
+
}
|
|
60
|
+
else if (arg === "--git-sha") {
|
|
61
|
+
const value = args[++i];
|
|
62
|
+
if (value === undefined || value.length === 0) {
|
|
63
|
+
throw new Error("Missing value for --git-sha");
|
|
64
|
+
}
|
|
65
|
+
gitSha = value;
|
|
66
|
+
}
|
|
67
|
+
else if (arg === "--ci-pr-url") {
|
|
68
|
+
const value = args[++i];
|
|
69
|
+
if (value === undefined || value.length === 0) {
|
|
70
|
+
throw new Error("Missing value for --ci-pr-url");
|
|
71
|
+
}
|
|
72
|
+
ciPrUrl = value;
|
|
73
|
+
}
|
|
74
|
+
else if (arg === "--ci-job-url") {
|
|
75
|
+
const value = args[++i];
|
|
76
|
+
if (value === undefined || value.length === 0) {
|
|
77
|
+
throw new Error("Missing value for --ci-job-url");
|
|
78
|
+
}
|
|
79
|
+
ciJobUrl = value;
|
|
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
|
+
}
|
|
95
|
+
else if (arg === "--json") {
|
|
96
|
+
json = true;
|
|
97
|
+
}
|
|
98
|
+
else if (arg === "--skip-gates") {
|
|
99
|
+
skipGates = true;
|
|
100
|
+
}
|
|
101
|
+
else if (arg.startsWith("-")) {
|
|
102
|
+
throw new Error(`Unknown flag: ${arg}`);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
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
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function defaultStartRun(cwd, projectRoot, isGitProject, operatorCatalog) {
|
|
123
|
+
return async (input) => {
|
|
124
|
+
const store = createRunStore({ rootDir: projectRoot });
|
|
125
|
+
const manager = new RunManager({
|
|
126
|
+
agent: new PiAgentAdapter(),
|
|
127
|
+
store,
|
|
128
|
+
cwd,
|
|
129
|
+
projectRoot,
|
|
130
|
+
isGitProject,
|
|
131
|
+
operatorCatalog,
|
|
132
|
+
executionMode: readStageExecutionMode(process.env, "process"),
|
|
133
|
+
});
|
|
134
|
+
return manager.startRun(input);
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export async function completeCliRun(started, io = defaultIo, options = {}) {
|
|
138
|
+
const result = await started.done;
|
|
139
|
+
return reportCliRun({ kind: "completion", result }, { json: options.json, io });
|
|
140
|
+
}
|
|
141
|
+
export async function runRunCommand(args, options = {}) {
|
|
142
|
+
const cwd = options.cwd ?? process.cwd();
|
|
143
|
+
const projectRoot = options.projectRoot ?? cwd;
|
|
144
|
+
const isGitProject = options.isGitProject ?? false;
|
|
145
|
+
const out = { ...defaultIo, ...options.io };
|
|
146
|
+
let parsed;
|
|
147
|
+
try {
|
|
148
|
+
parsed = parseRunArgs(args);
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
152
|
+
out.error(message);
|
|
153
|
+
out.error(RUN_USAGE);
|
|
154
|
+
return 1;
|
|
155
|
+
}
|
|
156
|
+
if (parsed.help) {
|
|
157
|
+
out.error(RUN_USAGE);
|
|
158
|
+
return 0;
|
|
159
|
+
}
|
|
160
|
+
if (!parsed.task || !parsed.pipeline) {
|
|
161
|
+
out.error("Missing --task and/or --pipeline");
|
|
162
|
+
out.error(RUN_USAGE);
|
|
163
|
+
return 1;
|
|
164
|
+
}
|
|
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);
|
|
175
|
+
const identity = resolveCiIdentity({
|
|
176
|
+
flags: {
|
|
177
|
+
gitSha: parsed.gitSha,
|
|
178
|
+
ciPrUrl: parsed.ciPrUrl,
|
|
179
|
+
ciJobUrl: parsed.ciJobUrl,
|
|
180
|
+
},
|
|
181
|
+
env: options.env ?? process.env,
|
|
182
|
+
});
|
|
183
|
+
try {
|
|
184
|
+
const started = await startRun({
|
|
185
|
+
task: parsed.task,
|
|
186
|
+
pipeline: parsed.pipeline,
|
|
187
|
+
checkoutOverride: parsed.checkout,
|
|
188
|
+
...(parsed.skipGates ? { skipGates: true } : {}),
|
|
189
|
+
...identity,
|
|
190
|
+
});
|
|
191
|
+
if (!started.ok) {
|
|
192
|
+
return reportCliRun({ kind: "start-failure", started }, { json: parsed.json, io: out });
|
|
193
|
+
}
|
|
194
|
+
return completeCliRun(started, out, { json: parsed.json });
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
if (err instanceof PipelineValidationError) {
|
|
198
|
+
if (parsed.json) {
|
|
199
|
+
out.log(formatValidationJson(err.result));
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
out.error(formatValidationHuman(err.result));
|
|
203
|
+
}
|
|
204
|
+
return exitCodeForValidation(err.result);
|
|
205
|
+
}
|
|
206
|
+
out.error(err instanceof Error ? err.message : String(err));
|
|
207
|
+
return 1;
|
|
208
|
+
}
|
|
209
|
+
}
|