stageflow 0.19.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/agent/activity.d.ts +5 -0
- package/dist/agent/activityObserver.d.ts +3 -0
- package/dist/agent/activityObserver.js +6 -1
- package/dist/agent/piAdapter.js +7 -3
- package/dist/agent/port.d.ts +2 -0
- package/dist/agent/providerInspect.d.ts +19 -0
- package/dist/agent/providerInspect.js +35 -0
- package/dist/agent/streamLogRedact.d.ts +2 -0
- package/dist/agent/streamLogRedact.js +13 -0
- package/dist/cli/artifactCommand.js +2 -2
- package/dist/cli/envelopeCommand.js +2 -2
- package/dist/cli/exportRunCommand.js +2 -2
- package/dist/cli/hostClient.d.ts +43 -0
- package/dist/cli/hostClient.js +182 -0
- package/dist/cli/providersCommand.js +5 -3
- package/dist/cli/runCommand.d.ts +6 -5
- package/dist/cli/runCommand.js +23 -31
- package/dist/cli/runOutput.d.ts +1 -1
- package/dist/cli/runsCommand.d.ts +3 -7
- package/dist/cli/runsCommand.js +33 -94
- package/dist/cli.js +1 -5
- package/dist/config/describePipeline.d.ts +26 -0
- package/dist/config/describePipeline.js +37 -0
- package/dist/config/loadPipeline.d.ts +13 -1
- package/dist/config/loadPipeline.js +34 -4
- package/dist/config/mergePipelineIncludes.d.ts +11 -0
- package/dist/config/mergePipelineIncludes.js +75 -0
- package/dist/config/validateCatalog.d.ts +2 -2
- package/dist/config/validateCatalog.js +24 -17
- package/dist/mcp/catalogTools.js +146 -52
- package/dist/mcp/controlTools.js +77 -19
- package/dist/mcp/deps.d.ts +3 -0
- package/dist/mcp/projectMcpTools.d.ts +3 -0
- package/dist/mcp/projectMcpTools.js +25 -0
- package/dist/mcp/providerTools.d.ts +3 -0
- package/dist/mcp/providerTools.js +17 -0
- package/dist/mcp/readArtifact.d.ts +9 -0
- package/dist/mcp/readArtifact.js +11 -0
- package/dist/mcp/server.js +9 -1
- package/dist/mcp/tailStreamLog.d.ts +8 -0
- package/dist/mcp/tailStreamLog.js +28 -0
- package/dist/mcp/toolResults.d.ts +17 -0
- package/dist/mcp/toolResults.js +15 -0
- package/dist/mcp/tools.js +4 -0
- package/dist/mcp/waitRun.js +4 -4
- package/dist/mcp/waitingGates.d.ts +1 -0
- package/dist/mcp/waitingGates.js +3 -2
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/projection/projectRun.d.ts +3 -0
- package/dist/projection/projectRun.js +7 -0
- package/dist/runstore/port.d.ts +3 -1
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +2 -1
- package/dist/runstore/sqlite/SqliteRunStore.js +15 -1
- package/dist/runstore/workspaceLayout.d.ts +1 -0
- package/dist/runstore/workspaceLayout.js +3 -0
- package/dist/runtime/feedbackLoopCoordinator.d.ts +9 -1
- package/dist/runtime/feedbackLoopCoordinator.js +50 -20
- package/dist/runtime/feedbackLoopDecision.js +33 -1
- package/dist/runtime/pipelineRunner.d.ts +3 -3
- package/dist/runtime/pipelineRunner.js +3 -1
- package/dist/runtime/resumeReconstruct.js +2 -2
- package/dist/runtime/runManager.d.ts +2 -1
- package/dist/runtime/runManager.js +36 -16
- package/dist/runtime/settingsFile.d.ts +2 -0
- package/dist/runtime/settingsFile.js +19 -0
- package/dist/runtime/stageAttemptBootstrap.d.ts +3 -0
- package/dist/runtime/stageAttemptBootstrap.js +8 -2
- package/dist/runtime/stageStreamLog.d.ts +17 -0
- package/dist/runtime/stageStreamLog.js +89 -0
- package/dist/runtime/stageWorker.js +2 -1
- package/dist/server/bootstrap.js +9 -2
- package/dist/server/ensureGlobalService.d.ts +35 -0
- package/dist/server/ensureGlobalService.js +138 -0
- package/dist/server/http.d.ts +20 -1
- package/dist/server/http.js +450 -402
- package/dist/server/mcpHost.js +7 -8
- package/dist/server/operatorResults.d.ts +10 -0
- package/dist/server/operatorResults.js +24 -0
- package/dist/server/providerRoutes.d.ts +0 -6
- package/dist/server/providerRoutes.js +10 -18
- package/dist/types/pipeline.d.ts +8 -0
- package/dist/ui/assets/{index-DMVdl1qB.js → index-Da5EHU6M.js} +5 -5
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
- package/skills/stageflow/references/control-surface.md +2 -2
- package/skills/stageflow-run/SKILL.md +27 -9
- package/skills/stageflow-run/references/mcp-call.md +1 -1
- package/skills/stageflow-run/scripts/mcp-call.mjs +9 -0
package/dist/mcp/tools.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { registerCatalogTools } from "./catalogTools.js";
|
|
3
3
|
import { registerControlTools } from "./controlTools.js";
|
|
4
|
+
import { registerProjectMcpTools } from "./projectMcpTools.js";
|
|
5
|
+
import { registerProviderTools } from "./providerTools.js";
|
|
4
6
|
import { textResult } from "./toolResults.js";
|
|
5
7
|
import { DEFAULT_TIMEOUT_MS, waitRun } from "./waitRun.js";
|
|
6
8
|
export function registerMcpTools(server, deps) {
|
|
7
9
|
const { store } = deps;
|
|
8
10
|
registerCatalogTools(server, deps);
|
|
9
11
|
registerControlTools(server, deps);
|
|
12
|
+
registerProviderTools(server, deps);
|
|
13
|
+
registerProjectMcpTools(server, deps);
|
|
10
14
|
server.registerTool("wait_run", {
|
|
11
15
|
description: "Long-poll until a run is waiting for HITL input and/or terminal (succeeded/failed), or until timeout_ms. Default until=any wakes on waiting OR terminal. Abort cancels only this wait (not the run). Optional notifications/progress when the client supplies _meta.progressToken. Compose: wait_run → answer_gate → wait_run.",
|
|
12
16
|
inputSchema: z.object({
|
package/dist/mcp/waitRun.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mapStoreLookupError } from "../server/operatorResults.js";
|
|
1
2
|
import { projectRunForMcp } from "./projectRun.js";
|
|
2
3
|
export const DEFAULT_TIMEOUT_MS = 60_000;
|
|
3
4
|
export const MAX_TIMEOUT_MS = 240_000;
|
|
@@ -90,12 +91,11 @@ export async function waitRun(opts) {
|
|
|
90
91
|
detail = await opts.store.readRun(opts.runId);
|
|
91
92
|
}
|
|
92
93
|
catch (err) {
|
|
93
|
-
const
|
|
94
|
-
const notFound = /not found|no such|unknown run/i.test(message);
|
|
94
|
+
const mapped = mapStoreLookupError(err, { policy: "run" });
|
|
95
95
|
return {
|
|
96
96
|
ok: false,
|
|
97
|
-
error:
|
|
98
|
-
status:
|
|
97
|
+
error: mapped.error,
|
|
98
|
+
status: mapped.status,
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
101
|
const run = projectRunForMcp(detail);
|
package/dist/mcp/waitingGates.js
CHANGED
|
@@ -83,8 +83,9 @@ export async function projectWaitingGates(store, opts) {
|
|
|
83
83
|
return projectGatesForRun(detail, detail);
|
|
84
84
|
}
|
|
85
85
|
const summaries = await store.listRuns();
|
|
86
|
-
const waiting = summaries.filter((r) => (
|
|
87
|
-
r.
|
|
86
|
+
const waiting = summaries.filter((r) => (opts?.projectRoot === undefined || r.project_root === opts.projectRoot) &&
|
|
87
|
+
((r.waiting_stage_ids !== undefined && r.waiting_stage_ids.length > 0) ||
|
|
88
|
+
r.waiting_stage_id !== undefined));
|
|
88
89
|
const perRun = await Promise.all(waiting.map(async (summary) => {
|
|
89
90
|
let detail;
|
|
90
91
|
try {
|
package/dist/package-meta.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare const PACKAGE_NAME = "stageflow";
|
|
2
|
-
export declare const PACKAGE_VERSION = "0.
|
|
2
|
+
export declare const PACKAGE_VERSION = "0.23.0";
|
package/dist/package-meta.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export const PACKAGE_NAME = "stageflow";
|
|
2
|
-
export const PACKAGE_VERSION = "0.
|
|
2
|
+
export const PACKAGE_VERSION = "0.23.0";
|
|
@@ -16,6 +16,8 @@ export type StageProjection = {
|
|
|
16
16
|
artifacts: string[];
|
|
17
17
|
last_at?: string;
|
|
18
18
|
pending_prompt?: AskOperatorPrompt;
|
|
19
|
+
definition_id?: string;
|
|
20
|
+
cost_usd?: number;
|
|
19
21
|
};
|
|
20
22
|
export type RunProjection = {
|
|
21
23
|
run_id: string;
|
|
@@ -40,6 +42,7 @@ export type RunProjection = {
|
|
|
40
42
|
failed_reason?: RunSummary["failed_reason"];
|
|
41
43
|
active_feedback_loop?: FeedbackLoopRecord;
|
|
42
44
|
feedback_loops: FeedbackLoopHistory[];
|
|
45
|
+
total_cost_usd?: number;
|
|
43
46
|
};
|
|
44
47
|
export declare function projectRun(detail: RunDetail): RunProjection;
|
|
45
48
|
export { projectRun as projectRunForMcp };
|
|
@@ -44,6 +44,9 @@ export function projectRun(detail) {
|
|
|
44
44
|
...(detail.active_feedback_loop !== undefined
|
|
45
45
|
? { active_feedback_loop: detail.active_feedback_loop }
|
|
46
46
|
: {}),
|
|
47
|
+
...(detail.total_cost_usd !== undefined
|
|
48
|
+
? { total_cost_usd: detail.total_cost_usd }
|
|
49
|
+
: {}),
|
|
47
50
|
feedback_loops: detail.feedback_loops ?? [],
|
|
48
51
|
stages: detail.stages.map((stage) => ({
|
|
49
52
|
stage_id: stage.stage_id,
|
|
@@ -69,6 +72,10 @@ export function projectRun(detail) {
|
|
|
69
72
|
...(stage.pending_prompt
|
|
70
73
|
? { pending_prompt: stage.pending_prompt }
|
|
71
74
|
: {}),
|
|
75
|
+
...(stage.definition_id !== undefined
|
|
76
|
+
? { definition_id: stage.definition_id }
|
|
77
|
+
: {}),
|
|
78
|
+
...(stage.cost_usd !== undefined ? { cost_usd: stage.cost_usd } : {}),
|
|
72
79
|
})),
|
|
73
80
|
};
|
|
74
81
|
}
|
package/dist/runstore/port.d.ts
CHANGED
|
@@ -327,12 +327,14 @@ export interface RunStore {
|
|
|
327
327
|
writeEnvelope(runId: string, stageId: string, envelope: StageEnvelope, options?: {
|
|
328
328
|
attempt?: number;
|
|
329
329
|
}): Promise<void>;
|
|
330
|
-
readEnvelope(runId: string, stageId: string): Promise<StageEnvelope>;
|
|
330
|
+
readEnvelope(runId: string, stageId: string, attempt?: number): Promise<StageEnvelope>;
|
|
331
331
|
appendStageEvent(runId: string, stageId: string, event: StageLogLine, options?: {
|
|
332
332
|
attempt?: number;
|
|
333
333
|
}): Promise<void>;
|
|
334
334
|
listStageEvents(runId: string, stageId: string, attempt?: number): Promise<StageLogEvent[]>;
|
|
335
335
|
listRuns(filter?: ListRunsFilter): Promise<RunSummary[]>;
|
|
336
|
+
/** Every distinct non-empty project_root recorded across all runs. */
|
|
337
|
+
listProjectRoots(): Promise<string[]>;
|
|
336
338
|
readRun(runId: string): Promise<RunDetail>;
|
|
337
339
|
updatePipelineDag(runId: string, dag: RunPipelineDagSnapshot): Promise<void>;
|
|
338
340
|
createFeedbackLoop(runId: string, input: CreateFeedbackLoopInput): Promise<FeedbackLoopRecord>;
|
|
@@ -29,7 +29,7 @@ export declare class SqliteRunStore implements RunStore {
|
|
|
29
29
|
writeEnvelope(runId: string, stageId: string, envelope: StageEnvelope, options?: {
|
|
30
30
|
attempt?: number;
|
|
31
31
|
}): Promise<void>;
|
|
32
|
-
readEnvelope(runId: string, stageId: string): Promise<StageEnvelope>;
|
|
32
|
+
readEnvelope(runId: string, stageId: string, attempt?: number): Promise<StageEnvelope>;
|
|
33
33
|
private readEnvelopeFromDb;
|
|
34
34
|
appendStageEvent(runId: string, stageId: string, event: StageLogLine, options?: {
|
|
35
35
|
attempt?: number;
|
|
@@ -43,6 +43,7 @@ export declare class SqliteRunStore implements RunStore {
|
|
|
43
43
|
private updateStageStatusDenorm;
|
|
44
44
|
listStageEvents(runId: string, stageId: string, attempt?: number): Promise<StageLogEvent[]>;
|
|
45
45
|
listRuns(filter?: ListRunsFilter): Promise<RunSummary[]>;
|
|
46
|
+
listProjectRoots(): Promise<string[]>;
|
|
46
47
|
readRun(runId: string): Promise<RunDetail>;
|
|
47
48
|
createFeedbackLoop(runId: string, input: CreateFeedbackLoopInput): Promise<FeedbackLoopRecord>;
|
|
48
49
|
getFeedbackLoop(runId: string, loopId: string): Promise<FeedbackLoopRecord>;
|
|
@@ -606,8 +606,15 @@ export class SqliteRunStore {
|
|
|
606
606
|
});
|
|
607
607
|
await this.updateStageExecution(runId, stageId, attempt, { envelope });
|
|
608
608
|
}
|
|
609
|
-
async readEnvelope(runId, stageId) {
|
|
609
|
+
async readEnvelope(runId, stageId, attempt) {
|
|
610
610
|
await this.ready();
|
|
611
|
+
if (attempt !== undefined) {
|
|
612
|
+
const execution = await this.getStageExecution(runId, stageId, attempt);
|
|
613
|
+
if (execution.envelope != null) {
|
|
614
|
+
return execution.envelope;
|
|
615
|
+
}
|
|
616
|
+
throw new Error(`Envelope not found: ${runId}/${stageId}`);
|
|
617
|
+
}
|
|
611
618
|
const execution = await this.getLatestStageExecution(runId, stageId);
|
|
612
619
|
if (execution?.envelope != null) {
|
|
613
620
|
return execution.envelope;
|
|
@@ -717,6 +724,13 @@ export class SqliteRunStore {
|
|
|
717
724
|
}
|
|
718
725
|
return summaries;
|
|
719
726
|
}
|
|
727
|
+
async listProjectRoots() {
|
|
728
|
+
await this.ready();
|
|
729
|
+
const rows = this.db
|
|
730
|
+
.prepare(`SELECT DISTINCT project_root FROM runs WHERE project_root IS NOT NULL AND project_root != ''`)
|
|
731
|
+
.all();
|
|
732
|
+
return rows.map((row) => row.project_root);
|
|
733
|
+
}
|
|
720
734
|
async readRun(runId) {
|
|
721
735
|
await this.ready();
|
|
722
736
|
const row = this.getRunRow(runId);
|
|
@@ -2,6 +2,7 @@ export { stageDir } from "./paths.js";
|
|
|
2
2
|
export declare function guardStageId(stageId: string): void;
|
|
3
3
|
export declare function attemptWorkspaceDir(workspaceDir: string, stageId: string, attempt: number): string;
|
|
4
4
|
export declare function attemptLogPath(workspaceDir: string, stageId: string, attempt: number): string;
|
|
5
|
+
export declare function attemptStreamLogPath(workspaceDir: string, stageId: string, attempt: number): string;
|
|
5
6
|
export declare function attemptEnvelopePath(workspaceDir: string, stageId: string, attempt: number): string;
|
|
6
7
|
export declare function attemptArtifactsDir(workspaceDir: string, stageId: string, attempt: number): string;
|
|
7
8
|
export declare function attemptAgentDir(workspaceDir: string, stageId: string, attempt: number): string;
|
|
@@ -21,6 +21,9 @@ export function attemptWorkspaceDir(workspaceDir, stageId, attempt) {
|
|
|
21
21
|
export function attemptLogPath(workspaceDir, stageId, attempt) {
|
|
22
22
|
return path.join(attemptWorkspaceDir(workspaceDir, stageId, attempt), "log.jsonl");
|
|
23
23
|
}
|
|
24
|
+
export function attemptStreamLogPath(workspaceDir, stageId, attempt) {
|
|
25
|
+
return path.join(attemptWorkspaceDir(workspaceDir, stageId, attempt), "stream.log");
|
|
26
|
+
}
|
|
24
27
|
export function attemptEnvelopePath(workspaceDir, stageId, attempt) {
|
|
25
28
|
return path.join(attemptWorkspaceDir(workspaceDir, stageId, attempt), "envelope.json");
|
|
26
29
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FeedbackLoopContext, StageSessionMode } from "../agent/port.js";
|
|
2
|
-
import type { DeferredFeedbackSendBack, FeedbackLoopRecord, FeedbackReplayRecord, RunStore } from "../runstore/port.js";
|
|
2
|
+
import type { DeferredFeedbackSendBack, FeedbackLoopRecord, FeedbackReplayRecord, FeedbackReplayStagePassStatus, RunStore } from "../runstore/port.js";
|
|
3
3
|
import type { StageEnvelope } from "../types/envelope.js";
|
|
4
4
|
import type { ResolvedPipelineDag, ResolvedPipelineStageNode } from "../types/pipeline.js";
|
|
5
5
|
import type { FeedbackScheduleState } from "./feedbackLoopSchedule.js";
|
|
@@ -38,6 +38,14 @@ export type FeedbackSendBackRejected = {
|
|
|
38
38
|
};
|
|
39
39
|
export type FeedbackSendBackResult = FeedbackSendBackAccepted | FeedbackSendBackIdempotent | FeedbackSendBackWaitingForHuman | FeedbackSendBackRejected;
|
|
40
40
|
export declare function terminalizeCurrentFeedbackReplay(store: RunStore, runId: string, loop: FeedbackLoopRecord, status: Extract<FeedbackReplayRecord["status"], "completed" | "failed" | "waiting_for_human">): Promise<void>;
|
|
41
|
+
export declare function updateFeedbackReplaySourcePass(options: {
|
|
42
|
+
store: RunStore;
|
|
43
|
+
runId: string;
|
|
44
|
+
loop: FeedbackLoopRecord;
|
|
45
|
+
sourceStageId: string;
|
|
46
|
+
status: Extract<FeedbackReplayStagePassStatus, "waiting" | "succeeded" | "failed">;
|
|
47
|
+
envelope?: StageEnvelope;
|
|
48
|
+
}): Promise<void>;
|
|
41
49
|
export declare function acceptFeedbackSendBack(options: {
|
|
42
50
|
store: RunStore;
|
|
43
51
|
runId: string;
|
|
@@ -10,6 +10,33 @@ export async function terminalizeCurrentFeedbackReplay(store, runId, loop, statu
|
|
|
10
10
|
return;
|
|
11
11
|
await store.updateFeedbackReplay(runId, loop.current_replay_id, { status });
|
|
12
12
|
}
|
|
13
|
+
export async function updateFeedbackReplaySourcePass(options) {
|
|
14
|
+
const { store, runId, loop, sourceStageId, status, envelope } = options;
|
|
15
|
+
if (loop.current_replay_id === undefined)
|
|
16
|
+
return;
|
|
17
|
+
const passes = await store.listFeedbackReplayStagePasses(runId, loop.current_replay_id);
|
|
18
|
+
const sourcePass = passes.find((pass) => pass.stage_id === sourceStageId);
|
|
19
|
+
if (sourcePass === undefined)
|
|
20
|
+
return;
|
|
21
|
+
if (sourcePass.status === "succeeded")
|
|
22
|
+
return;
|
|
23
|
+
if (status === "waiting") {
|
|
24
|
+
await store.updateFeedbackReplayStagePass(runId, loop.current_replay_id, sourceStageId, { status: "waiting" });
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (status === "succeeded") {
|
|
28
|
+
await store.updateFeedbackReplayStagePass(runId, loop.current_replay_id, sourceStageId, {
|
|
29
|
+
status: "succeeded",
|
|
30
|
+
finished_at: new Date().toISOString(),
|
|
31
|
+
...(envelope !== undefined ? { emitted_envelope: envelope } : {}),
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
await store.updateFeedbackReplayStagePass(runId, loop.current_replay_id, sourceStageId, {
|
|
36
|
+
status: "failed",
|
|
37
|
+
finished_at: new Date().toISOString(),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
13
40
|
async function resolveResumeSessionAttempt(store, runId, loopId, stageId, fallbackAttempt) {
|
|
14
41
|
const replays = await store.listFeedbackReplays(runId, loopId);
|
|
15
42
|
for (const replay of replays) {
|
|
@@ -182,6 +209,13 @@ export async function acceptFeedbackSendBack(options) {
|
|
|
182
209
|
source_attempt: sourceAttempt,
|
|
183
210
|
};
|
|
184
211
|
await terminalizeCurrentFeedbackReplay(store, runId, loop, "waiting_for_human");
|
|
212
|
+
await updateFeedbackReplaySourcePass({
|
|
213
|
+
store,
|
|
214
|
+
runId,
|
|
215
|
+
loop,
|
|
216
|
+
sourceStageId: sourceNode.id,
|
|
217
|
+
status: "waiting",
|
|
218
|
+
});
|
|
185
219
|
await store.updateFeedbackLoop(runId, loop.loop_id, {
|
|
186
220
|
state: "waiting_for_human",
|
|
187
221
|
deferred_send_back: deferred,
|
|
@@ -205,15 +239,14 @@ export async function acceptFeedbackSendBack(options) {
|
|
|
205
239
|
};
|
|
206
240
|
}
|
|
207
241
|
if (loop.current_replay_id !== undefined) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
242
|
+
await updateFeedbackReplaySourcePass({
|
|
243
|
+
store,
|
|
244
|
+
runId,
|
|
245
|
+
loop,
|
|
246
|
+
sourceStageId: sourceNode.id,
|
|
247
|
+
status: "succeeded",
|
|
248
|
+
envelope,
|
|
249
|
+
});
|
|
217
250
|
await store.updateFeedbackReplay(runId, loop.current_replay_id, {
|
|
218
251
|
status: "completed",
|
|
219
252
|
});
|
|
@@ -301,17 +334,14 @@ export async function markFeedbackLoopContinued(options) {
|
|
|
301
334
|
(entry.state === "active" || entry.state === "waiting_for_human"));
|
|
302
335
|
if (loop === undefined)
|
|
303
336
|
return;
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
}
|
|
337
|
+
await updateFeedbackReplaySourcePass({
|
|
338
|
+
store,
|
|
339
|
+
runId,
|
|
340
|
+
loop,
|
|
341
|
+
sourceStageId,
|
|
342
|
+
status: "succeeded",
|
|
343
|
+
envelope,
|
|
344
|
+
});
|
|
315
345
|
await terminalizeCurrentFeedbackReplay(store, runId, loop, "completed");
|
|
316
346
|
await store.updateFeedbackLoop(runId, loop.loop_id, {
|
|
317
347
|
state: "continued",
|
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
import { terminalizeCurrentFeedbackReplay } from "./feedbackLoopCoordinator.js";
|
|
1
|
+
import { terminalizeCurrentFeedbackReplay, updateFeedbackReplaySourcePass, } from "./feedbackLoopCoordinator.js";
|
|
2
2
|
import { createFeedbackScheduleState } from "./feedbackLoopSchedule.js";
|
|
3
3
|
import { applySendBack, releaseHold, } from "./replayLifecycle.js";
|
|
4
4
|
const DECISION_CONFLICT = "feedback loop decision conflict: no longer waiting_for_human";
|
|
5
|
+
async function appendFeedbackLoopDecided(store, runId, loop, decision, reason) {
|
|
6
|
+
const latest = await store.getLatestStageExecution(runId, loop.source_stage_id);
|
|
7
|
+
const attempt = latest?.attempt;
|
|
8
|
+
const eventOptions = attempt !== undefined ? { attempt } : undefined;
|
|
9
|
+
const trimmed = reason?.trim();
|
|
10
|
+
await store.appendStageEvent(runId, loop.source_stage_id, {
|
|
11
|
+
event: "feedback_loop_decided",
|
|
12
|
+
decision,
|
|
13
|
+
loopId: loop.loop_id,
|
|
14
|
+
...(trimmed ? { reason: trimmed } : {}),
|
|
15
|
+
}, eventOptions);
|
|
16
|
+
}
|
|
5
17
|
async function clearSourceWaiting(store, runId, sourceStageId, nextStatus, reason) {
|
|
6
18
|
const latest = await store.getLatestStageExecution(runId, sourceStageId);
|
|
7
19
|
const attempt = latest?.attempt;
|
|
@@ -96,6 +108,7 @@ export async function resolveFeedbackLoopDecision(options) {
|
|
|
96
108
|
reason: `${DECISION_CONFLICT} (loop=${loop.loop_id})`,
|
|
97
109
|
};
|
|
98
110
|
}
|
|
111
|
+
await appendFeedbackLoopDecided(store, runId, loop, "extend", options.reason);
|
|
99
112
|
await clearSourceWaiting(store, runId, loop.source_stage_id, "succeeded");
|
|
100
113
|
if (schedule === undefined) {
|
|
101
114
|
const applied = await applySendBack({
|
|
@@ -168,8 +181,19 @@ export async function resolveFeedbackLoopDecision(options) {
|
|
|
168
181
|
reason: `${DECISION_CONFLICT} (loop=${loop.loop_id})`,
|
|
169
182
|
};
|
|
170
183
|
}
|
|
184
|
+
await appendFeedbackLoopDecided(store, runId, loop, "continue", options.reason);
|
|
171
185
|
await terminalizeCurrentFeedbackReplay(store, runId, loop, "completed");
|
|
172
186
|
await clearSourceWaiting(store, runId, loop.source_stage_id, "succeeded");
|
|
187
|
+
await updateFeedbackReplaySourcePass({
|
|
188
|
+
store,
|
|
189
|
+
runId,
|
|
190
|
+
loop,
|
|
191
|
+
sourceStageId: loop.source_stage_id,
|
|
192
|
+
status: "succeeded",
|
|
193
|
+
...(deferred !== undefined
|
|
194
|
+
? { envelope: deferred.feedback_envelope }
|
|
195
|
+
: {}),
|
|
196
|
+
});
|
|
173
197
|
if (schedule !== undefined) {
|
|
174
198
|
schedule.states.set(loop.source_stage_id, "succeeded");
|
|
175
199
|
if (schedule.completedEnvelopes.get(loop.source_stage_id) === undefined &&
|
|
@@ -190,8 +214,16 @@ export async function resolveFeedbackLoopDecision(options) {
|
|
|
190
214
|
reason: `${DECISION_CONFLICT} (loop=${loop.loop_id})`,
|
|
191
215
|
};
|
|
192
216
|
}
|
|
217
|
+
await appendFeedbackLoopDecided(store, runId, loop, "abandon", abandonReason);
|
|
193
218
|
await terminalizeCurrentFeedbackReplay(store, runId, loop, "failed");
|
|
194
219
|
await clearSourceWaiting(store, runId, loop.source_stage_id, "failed", abandonReason);
|
|
220
|
+
await updateFeedbackReplaySourcePass({
|
|
221
|
+
store,
|
|
222
|
+
runId,
|
|
223
|
+
loop,
|
|
224
|
+
sourceStageId: loop.source_stage_id,
|
|
225
|
+
status: "failed",
|
|
226
|
+
});
|
|
195
227
|
if (schedule !== undefined) {
|
|
196
228
|
schedule.states.set(loop.source_stage_id, "failed");
|
|
197
229
|
releaseHold({ feedback: schedule.feedback });
|
|
@@ -3,7 +3,7 @@ import { type ValidationFinding } from "../config/validateCatalog.js";
|
|
|
3
3
|
import type { RunStore } from "../runstore/port.js";
|
|
4
4
|
import type { StageEnvelope } from "../types/envelope.js";
|
|
5
5
|
import type { StageHitlController } from "./stageHitl.js";
|
|
6
|
-
import type { LoadedPipeline } from "../types/pipeline.js";
|
|
6
|
+
import type { InlinePipelineDefinition, LoadedPipeline } from "../types/pipeline.js";
|
|
7
7
|
import type { TaskFile } from "../types/task.js";
|
|
8
8
|
import { type StageExecutionMode } from "./stageConcurrency.js";
|
|
9
9
|
import { StageProcessLauncher } from "./stageProcessLauncher.js";
|
|
@@ -57,7 +57,7 @@ export declare function runPipeline(options: {
|
|
|
57
57
|
store: RunStore;
|
|
58
58
|
taskPath?: string;
|
|
59
59
|
taskYaml?: string;
|
|
60
|
-
pipeline: string;
|
|
60
|
+
pipeline: string | InlinePipelineDefinition;
|
|
61
61
|
cwd?: string;
|
|
62
62
|
projectRoot?: string;
|
|
63
63
|
checkoutOverride?: string;
|
|
@@ -74,7 +74,7 @@ export declare function startPipeline(options: {
|
|
|
74
74
|
store: RunStore;
|
|
75
75
|
taskPath?: string;
|
|
76
76
|
taskYaml?: string;
|
|
77
|
-
pipeline: string;
|
|
77
|
+
pipeline: string | InlinePipelineDefinition;
|
|
78
78
|
cwd?: string;
|
|
79
79
|
projectRoot?: string;
|
|
80
80
|
checkoutOverride?: string;
|
|
@@ -57,7 +57,9 @@ async function preparePipeline(options) {
|
|
|
57
57
|
console.error(`${finding.code}: ${finding.message}`);
|
|
58
58
|
}
|
|
59
59
|
const checkoutRoot = await resolveAndValidateCheckout(task, options.checkoutOverride, options.cwd);
|
|
60
|
-
const pipelinePath =
|
|
60
|
+
const pipelinePath = typeof options.pipeline === "string"
|
|
61
|
+
? normalizeCatalogPath(loaded.pipelinePath)
|
|
62
|
+
: undefined;
|
|
61
63
|
const taskPath = options.taskPath
|
|
62
64
|
? normalizeCatalogPath(path.resolve(options.cwd, options.taskPath))
|
|
63
65
|
: undefined;
|
|
@@ -33,12 +33,12 @@ export async function loadRunContext(store, runId, cwd) {
|
|
|
33
33
|
}
|
|
34
34
|
export async function reconstructAndContinue(ctx) {
|
|
35
35
|
const { runId, stageId, opaqueAnswer, agent, store, cwd } = ctx;
|
|
36
|
-
const factoryCwd = ctx.factoryCwd ?? cwd;
|
|
37
36
|
try {
|
|
38
37
|
const latestExecution = await store.getLatestStageExecution(runId, stageId);
|
|
39
38
|
const attemptCtx = attemptContext(latestExecution?.attempt ?? 1);
|
|
40
39
|
const attemptOpt = attemptCtx.eventOptions();
|
|
41
40
|
const { meta, task, loaded } = await loadRunContext(store, runId, cwd);
|
|
41
|
+
const factoryCwd = meta.project_root ?? ctx.factoryCwd ?? cwd;
|
|
42
42
|
const definitionId = definitionIdForInstance(meta.pipeline_dag, stageId);
|
|
43
43
|
const stageIndex = loaded.stages.findIndex((s) => s.id === definitionId);
|
|
44
44
|
if (stageIndex < 0) {
|
|
@@ -127,7 +127,7 @@ export async function reconstructAndContinue(ctx) {
|
|
|
127
127
|
run: { runId, workspaceDir },
|
|
128
128
|
agent,
|
|
129
129
|
store,
|
|
130
|
-
cwd,
|
|
130
|
+
cwd: factoryCwd,
|
|
131
131
|
projectRoot: factoryCwd,
|
|
132
132
|
checkoutRoot,
|
|
133
133
|
hitl: ctx.hitl,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentPort, OpaqueAnswer } from "../agent/port.js";
|
|
2
|
+
import type { InlinePipelineDefinition } from "../types/pipeline.js";
|
|
2
3
|
import { type RunStore } from "../runstore/port.js";
|
|
3
4
|
import type { TaskFile } from "../types/task.js";
|
|
4
5
|
import { type PipelineRunResult } from "./pipelineRunner.js";
|
|
@@ -125,7 +126,7 @@ export declare class RunManager {
|
|
|
125
126
|
}>>;
|
|
126
127
|
abandonStage(runId: string, stageId: string): Promise<AbandonStageResult>;
|
|
127
128
|
startRun(input: StartTaskInput & {
|
|
128
|
-
pipeline: string;
|
|
129
|
+
pipeline: string | InlinePipelineDefinition;
|
|
129
130
|
task?: string | TaskFile;
|
|
130
131
|
checkoutOverride?: string;
|
|
131
132
|
skipGates?: boolean;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { readFile, realpath } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { findProjectRoot } from "../project/findProjectRoot.js";
|
|
4
5
|
import { normalizeCatalogPath } from "../runstore/normalizeCatalogPath.js";
|
|
5
6
|
import { loadRunContext } from "./resumeReconstruct.js";
|
|
6
7
|
import { assertTimedOutStageEligible, reconstructTimedOutAndContinue, } from "./resumeTimedOut.js";
|
|
@@ -12,7 +13,7 @@ import { resolveFeedbackLoopDecision, } from "./feedbackLoopDecision.js";
|
|
|
12
13
|
import { RunRetryCoordinator, readRetryRootWaitTimeoutMs, } from "./runRetryCoordinator.js";
|
|
13
14
|
import { readMaxActiveStageProcesses, readMaxActiveStagesPerRun, readStageExecutionMode, } from "./stageConcurrency.js";
|
|
14
15
|
import { StageProcessLauncher } from "./stageProcessLauncher.js";
|
|
15
|
-
import { INVALID_SLOT_COUNT_MESSAGE, parseSlotCount,
|
|
16
|
+
import { INVALID_SLOT_COUNT_MESSAGE, parseSlotCount, readMaxConcurrentFromGlobal, writeMaxConcurrentToGlobal, } from "./settingsFile.js";
|
|
16
17
|
import { StageHitlController, waitKey, } from "./stageHitl.js";
|
|
17
18
|
import { resolveAndValidateCheckout, } from "./stageRoots.js";
|
|
18
19
|
import { orchestrateAnswerResume } from "./answerResume.js";
|
|
@@ -94,10 +95,9 @@ export class RunManager {
|
|
|
94
95
|
this.cwd = options.cwd ?? process.cwd();
|
|
95
96
|
this.projectRoot = options.projectRoot ?? this.cwd;
|
|
96
97
|
this.isGitProject = options.isGitProject ?? false;
|
|
97
|
-
const settingsCtx = projectSettingsContext(this.cwd, this.projectRoot, this.isGitProject);
|
|
98
98
|
this.maxConcurrent =
|
|
99
99
|
options.maxConcurrent ??
|
|
100
|
-
|
|
100
|
+
readMaxConcurrentFromGlobal() ??
|
|
101
101
|
parseMaxConcurrent(process.env.STAGEFLOW_MAX_CONCURRENT_RUNS);
|
|
102
102
|
this.maxActiveStagesPerRun = readMaxActiveStagesPerRun(process.env, options.maxActiveStagesPerRun);
|
|
103
103
|
this.executionMode = readStageExecutionMode(process.env, options.executionMode);
|
|
@@ -119,7 +119,7 @@ export class RunManager {
|
|
|
119
119
|
throw new Error(INVALID_SLOT_COUNT_MESSAGE);
|
|
120
120
|
}
|
|
121
121
|
this.maxConcurrent = parsed;
|
|
122
|
-
|
|
122
|
+
writeMaxConcurrentToGlobal(parsed);
|
|
123
123
|
return this.getHealth();
|
|
124
124
|
}
|
|
125
125
|
getActiveCount() {
|
|
@@ -350,6 +350,11 @@ export class RunManager {
|
|
|
350
350
|
}
|
|
351
351
|
async startRun(input) {
|
|
352
352
|
const cwd = this.options.cwd ?? process.cwd();
|
|
353
|
+
// An inline pipeline has no filesystem anchor to derive a project root from.
|
|
354
|
+
const derivedProjectRoot = typeof input.pipeline === "string"
|
|
355
|
+
? (findProjectRoot(path.dirname(path.resolve(cwd, input.pipeline))) ??
|
|
356
|
+
this.projectRoot)
|
|
357
|
+
: this.projectRoot;
|
|
353
358
|
let resolved;
|
|
354
359
|
try {
|
|
355
360
|
resolved = resolveStartTaskInput(input, cwd);
|
|
@@ -384,7 +389,7 @@ export class RunManager {
|
|
|
384
389
|
gitSha: input.gitSha,
|
|
385
390
|
ciPrUrl: input.ciPrUrl,
|
|
386
391
|
ciJobUrl: input.ciJobUrl,
|
|
387
|
-
},
|
|
392
|
+
}, derivedProjectRoot, resolved.kind === "path" ? resolved.taskPath : undefined);
|
|
388
393
|
}
|
|
389
394
|
async rerun(runId) {
|
|
390
395
|
const cwd = this.options.cwd ?? process.cwd();
|
|
@@ -456,6 +461,7 @@ export class RunManager {
|
|
|
456
461
|
}
|
|
457
462
|
const insertedForResume = !wasActive;
|
|
458
463
|
try {
|
|
464
|
+
const runProjectRoot = detail.project_root ?? this.projectRoot;
|
|
459
465
|
const done = reconstructTimedOutAndContinue({
|
|
460
466
|
runId,
|
|
461
467
|
stageId,
|
|
@@ -463,9 +469,9 @@ export class RunManager {
|
|
|
463
469
|
store: this.options.store,
|
|
464
470
|
hitl: this.hitl,
|
|
465
471
|
executionMode: this.executionMode,
|
|
466
|
-
cwd:
|
|
472
|
+
cwd: runProjectRoot,
|
|
467
473
|
maxActiveStagesPerRun: this.maxActiveStagesPerRun,
|
|
468
|
-
factoryCwd:
|
|
474
|
+
factoryCwd: runProjectRoot,
|
|
469
475
|
...(this.stageProcessLauncher !== undefined
|
|
470
476
|
? { stageProcessLauncher: this.stageProcessLauncher }
|
|
471
477
|
: {}),
|
|
@@ -585,6 +591,7 @@ export class RunManager {
|
|
|
585
591
|
};
|
|
586
592
|
}
|
|
587
593
|
try {
|
|
594
|
+
let meta;
|
|
588
595
|
let orchestrationConflict = this.active.has(runId) &&
|
|
589
596
|
!this.attachedWaiting.has(waitKey(runId, stageId));
|
|
590
597
|
if (!this.retryCoordinator.isActive(runId)) {
|
|
@@ -607,7 +614,7 @@ export class RunManager {
|
|
|
607
614
|
let detail;
|
|
608
615
|
try {
|
|
609
616
|
detail = await this.options.store.readRun(runId);
|
|
610
|
-
await this.options.store.readRunMeta(runId);
|
|
617
|
+
meta = await this.options.store.readRunMeta(runId);
|
|
611
618
|
}
|
|
612
619
|
catch {
|
|
613
620
|
this.retryInFlight.delete(retryKey);
|
|
@@ -626,12 +633,21 @@ export class RunManager {
|
|
|
626
633
|
}
|
|
627
634
|
}
|
|
628
635
|
}
|
|
636
|
+
if (meta === undefined) {
|
|
637
|
+
try {
|
|
638
|
+
meta = await this.options.store.readRunMeta(runId);
|
|
639
|
+
}
|
|
640
|
+
catch {
|
|
641
|
+
this.retryInFlight.delete(retryKey);
|
|
642
|
+
return { ok: false, reason: `Run not found: ${runId}`, status: 404 };
|
|
643
|
+
}
|
|
644
|
+
}
|
|
629
645
|
const result = await this.retryCoordinator.retryStage({
|
|
630
646
|
runId,
|
|
631
647
|
stageId,
|
|
632
648
|
store: this.options.store,
|
|
633
649
|
agent: this.options.agent,
|
|
634
|
-
cwd: this.
|
|
650
|
+
cwd: meta.project_root ?? this.projectRoot,
|
|
635
651
|
operatorCatalog: this.options.operatorCatalog,
|
|
636
652
|
maxActiveStagesPerRun: this.maxActiveStagesPerRun,
|
|
637
653
|
executionMode: this.executionMode,
|
|
@@ -980,6 +996,7 @@ export class RunManager {
|
|
|
980
996
|
};
|
|
981
997
|
}
|
|
982
998
|
const { meta, task, loaded } = await loadRunContext(this.options.store, runId, this.cwd);
|
|
999
|
+
const runProjectRoot = meta.project_root ?? this.projectRoot;
|
|
983
1000
|
const done = runPipelineDag({
|
|
984
1001
|
prepared: {
|
|
985
1002
|
task,
|
|
@@ -990,8 +1007,8 @@ export class RunManager {
|
|
|
990
1007
|
},
|
|
991
1008
|
agent: this.options.agent,
|
|
992
1009
|
store: this.options.store,
|
|
993
|
-
cwd:
|
|
994
|
-
projectRoot:
|
|
1010
|
+
cwd: runProjectRoot,
|
|
1011
|
+
projectRoot: runProjectRoot,
|
|
995
1012
|
checkoutRoot: meta.checkout_root,
|
|
996
1013
|
hitl: this.hitl,
|
|
997
1014
|
operatorCatalog: this.options.operatorCatalog,
|
|
@@ -1041,10 +1058,12 @@ export class RunManager {
|
|
|
1041
1058
|
const sessionFilePath = resumeSessionFilePath(workspaceDir, stageId, attempt);
|
|
1042
1059
|
const eventOptions = { attempt };
|
|
1043
1060
|
try {
|
|
1061
|
+
const runMeta = await store.readRunMeta(runId);
|
|
1062
|
+
const runProjectRoot = runMeta.project_root ?? this.projectRoot;
|
|
1044
1063
|
const launchResult = await launcher.launch({
|
|
1045
1064
|
runId,
|
|
1046
1065
|
stageId,
|
|
1047
|
-
rootDir:
|
|
1066
|
+
rootDir: runProjectRoot,
|
|
1048
1067
|
mode: "resume",
|
|
1049
1068
|
resumeAnswer: opaqueAnswer,
|
|
1050
1069
|
attempt,
|
|
@@ -1079,8 +1098,8 @@ export class RunManager {
|
|
|
1079
1098
|
run: { runId, workspaceDir: store.getWorkspaceDir(runId) },
|
|
1080
1099
|
agent: this.options.agent,
|
|
1081
1100
|
store,
|
|
1082
|
-
cwd: this.
|
|
1083
|
-
projectRoot: this.projectRoot,
|
|
1101
|
+
cwd: meta.project_root ?? this.projectRoot,
|
|
1102
|
+
projectRoot: meta.project_root ?? this.projectRoot,
|
|
1084
1103
|
checkoutRoot: meta.checkout_root,
|
|
1085
1104
|
hitl: this.hitl,
|
|
1086
1105
|
operatorCatalog: this.options.operatorCatalog,
|
|
@@ -1277,6 +1296,7 @@ export class RunManager {
|
|
|
1277
1296
|
return false;
|
|
1278
1297
|
}
|
|
1279
1298
|
try {
|
|
1299
|
+
const runProjectRoot = meta.project_root ?? this.projectRoot;
|
|
1280
1300
|
const { meta: loadedMeta, task, loaded, workspaceDir } = await loadRunContext(this.options.store, runId, this.cwd);
|
|
1281
1301
|
const promise = runPipelineDag({
|
|
1282
1302
|
prepared: {
|
|
@@ -1285,8 +1305,8 @@ export class RunManager {
|
|
|
1285
1305
|
run: { runId, workspaceDir },
|
|
1286
1306
|
agent: this.options.agent,
|
|
1287
1307
|
store: this.options.store,
|
|
1288
|
-
cwd:
|
|
1289
|
-
projectRoot:
|
|
1308
|
+
cwd: runProjectRoot,
|
|
1309
|
+
projectRoot: runProjectRoot,
|
|
1290
1310
|
checkoutRoot: loadedMeta.checkout_root,
|
|
1291
1311
|
hitl: this.hitl,
|
|
1292
1312
|
operatorCatalog: this.options.operatorCatalog,
|
|
@@ -23,6 +23,8 @@ export declare function readMaxConcurrentFromContext(ctx: ProjectContext): numbe
|
|
|
23
23
|
export declare function readMaxConcurrentFromFile(cwd: string): number | undefined;
|
|
24
24
|
export declare function writeMaxConcurrentToContext(ctx: ProjectContext, maxConcurrent: number): void;
|
|
25
25
|
export declare function writeMaxConcurrentToFile(cwd: string, maxConcurrent: number): void;
|
|
26
|
+
export declare function readMaxConcurrentFromGlobal(): number | undefined;
|
|
27
|
+
export declare function writeMaxConcurrentToGlobal(maxConcurrent: number): void;
|
|
26
28
|
export declare function readCredentialSourceFromFile(cwd: string): CredentialSource | undefined;
|
|
27
29
|
export declare function writeCredentialSourceToContext(ctx: ProjectContext, credentialSource: CredentialSource): void;
|
|
28
30
|
export declare function writeCredentialSourceToFile(cwd: string, credentialSource: CredentialSource): void;
|