stageflow 0.18.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 +9 -0
- package/dist/agent/activityObserver.d.ts +3 -0
- package/dist/agent/activityObserver.js +6 -1
- package/dist/agent/claudeAdapter.js +38 -0
- package/dist/agent/fakeAgent.js +3 -1
- package/dist/agent/piAdapter.js +40 -4
- package/dist/agent/port.d.ts +3 -1
- package/dist/agent/providerInspect.d.ts +19 -0
- package/dist/agent/providerInspect.js +35 -0
- package/dist/agent/stageTimeout.d.ts +9 -0
- package/dist/agent/stageTimeout.js +24 -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/graphCommand.d.ts +15 -0
- package/dist/cli/graphCommand.js +79 -0
- package/dist/cli/graphRender.d.ts +2 -0
- package/dist/cli/graphRender.js +85 -0
- 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 +4 -8
- package/dist/cli/runsCommand.js +65 -89
- package/dist/cli.js +13 -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 +94 -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/port.js +2 -0
- 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/forkGeneration.d.ts +4 -0
- package/dist/runtime/forkGeneration.js +16 -0
- package/dist/runtime/pipelineRunner.d.ts +3 -3
- package/dist/runtime/pipelineRunner.js +3 -1
- package/dist/runtime/pipelineScheduler.js +23 -1
- package/dist/runtime/reopenSkipped.d.ts +13 -0
- package/dist/runtime/reopenSkipped.js +56 -0
- package/dist/runtime/resumeReconstruct.js +2 -2
- package/dist/runtime/resumeTimedOut.d.ts +33 -0
- package/dist/runtime/resumeTimedOut.js +165 -0
- package/dist/runtime/runManager.d.ts +3 -1
- package/dist/runtime/runManager.js +110 -14
- 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/stageRecovery.js +2 -2
- package/dist/runtime/stageRunner.js +6 -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 +452 -386
- 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/tools/askOperator.d.ts +1 -0
- package/dist/tools/askOperator.js +14 -11
- package/dist/tools/emitStageEnvelope.d.ts +1 -0
- package/dist/tools/emitStageEnvelope.js +3 -1
- package/dist/tools/prepareToolArguments.d.ts +4 -0
- package/dist/tools/prepareToolArguments.js +79 -0
- package/dist/types/pipeline.d.ts +8 -0
- package/dist/ui/assets/{index-DsgVjLUS.css → index-CSsDued1.css} +1 -1
- package/dist/ui/assets/index-Da5EHU6M.js +119 -0
- package/dist/ui/index.html +2 -2
- 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/ui/assets/index-DwKGaQvQ.js +0 -119
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { parseStreamLogHeader } from "../runtime/stageStreamLog.js";
|
|
3
|
+
/** Read new content from a stage's stream.log since a caller's last cursor. */
|
|
4
|
+
export async function readStreamLogTail(streamLogPath, sinceOffset) {
|
|
5
|
+
let raw;
|
|
6
|
+
try {
|
|
7
|
+
raw = await readFile(streamLogPath);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return { text: "", nextOffset: 0 };
|
|
11
|
+
}
|
|
12
|
+
const { base, headerLength } = parseStreamLogHeader(raw);
|
|
13
|
+
const contentBytes = raw.subarray(headerLength);
|
|
14
|
+
const eof = base + contentBytes.length;
|
|
15
|
+
if (sinceOffset === undefined || sinceOffset < base) {
|
|
16
|
+
const truncated = sinceOffset !== undefined && sinceOffset < base;
|
|
17
|
+
return {
|
|
18
|
+
text: contentBytes.toString("utf8"),
|
|
19
|
+
nextOffset: eof,
|
|
20
|
+
...(truncated ? { truncated: true, earliestOffset: base } : {}),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (sinceOffset >= eof) {
|
|
24
|
+
return { text: "", nextOffset: eof };
|
|
25
|
+
}
|
|
26
|
+
const slice = contentBytes.subarray(sinceOffset - base);
|
|
27
|
+
return { text: slice.toString("utf8"), nextOffset: eof };
|
|
28
|
+
}
|
|
@@ -5,3 +5,20 @@ export declare function textResult(data: unknown, isError?: boolean): {
|
|
|
5
5
|
}[];
|
|
6
6
|
isError?: boolean | undefined;
|
|
7
7
|
};
|
|
8
|
+
export declare function imageResult(mimeType: string, bytes: Buffer, identity: {
|
|
9
|
+
runId: string;
|
|
10
|
+
path: string;
|
|
11
|
+
mimeType: string;
|
|
12
|
+
}): {
|
|
13
|
+
content: ({
|
|
14
|
+
type: "image";
|
|
15
|
+
mimeType: string;
|
|
16
|
+
data: string;
|
|
17
|
+
text?: undefined;
|
|
18
|
+
} | {
|
|
19
|
+
mimeType?: undefined;
|
|
20
|
+
data?: undefined;
|
|
21
|
+
type: "text";
|
|
22
|
+
text: string;
|
|
23
|
+
})[];
|
|
24
|
+
};
|
package/dist/mcp/toolResults.js
CHANGED
|
@@ -4,3 +4,18 @@ export function textResult(data, isError = false) {
|
|
|
4
4
|
...(isError ? { isError: true } : {}),
|
|
5
5
|
};
|
|
6
6
|
}
|
|
7
|
+
export function imageResult(mimeType, bytes, identity) {
|
|
8
|
+
return {
|
|
9
|
+
content: [
|
|
10
|
+
{
|
|
11
|
+
type: "image",
|
|
12
|
+
mimeType,
|
|
13
|
+
data: bytes.toString("base64"),
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
type: "text",
|
|
17
|
+
text: JSON.stringify(identity, null, 2),
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
};
|
|
21
|
+
}
|
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>;
|
package/dist/runstore/port.js
CHANGED
|
@@ -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 });
|
|
@@ -63,6 +63,10 @@ export declare function selectActiveInput(options: {
|
|
|
63
63
|
needsStageId: string;
|
|
64
64
|
scheduleOverride?: ReadonlyMap<string, ReadonlySet<string>>;
|
|
65
65
|
}): Promise<ActiveCohort>;
|
|
66
|
+
export declare function forkCohortMapsFromStore(store: Pick<RunStore, "listForkGenerations">, runId: string): Promise<{
|
|
67
|
+
supersededCloneIds: Set<string>;
|
|
68
|
+
cohortByForkParent: Map<string, Set<string>>;
|
|
69
|
+
}>;
|
|
66
70
|
export declare function filterJoinInputs(allInstanceIds: readonly string[], cohort: ActiveCohort): string[];
|
|
67
71
|
export declare function filterJoinInstanceIds(allInstanceIds: readonly string[], activeCloneIds: Set<string> | null): string[];
|
|
68
72
|
/**
|
|
@@ -251,6 +251,22 @@ export async function selectActiveInput(options) {
|
|
|
251
251
|
}
|
|
252
252
|
return activeCohortForParent(options.store, options.runId, forkParentId);
|
|
253
253
|
}
|
|
254
|
+
export async function forkCohortMapsFromStore(store, runId) {
|
|
255
|
+
const gens = await store.listForkGenerations(runId);
|
|
256
|
+
const supersededCloneIds = new Set();
|
|
257
|
+
const cohortByForkParent = new Map();
|
|
258
|
+
for (const gen of gens) {
|
|
259
|
+
if (gen.status === "superseded") {
|
|
260
|
+
for (const id of gen.clone_stage_ids) {
|
|
261
|
+
supersededCloneIds.add(id);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
else if (gen.status === "active") {
|
|
265
|
+
cohortByForkParent.set(gen.fork_parent_stage_id, new Set(gen.clone_stage_ids));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return { supersededCloneIds, cohortByForkParent };
|
|
269
|
+
}
|
|
254
270
|
export function filterJoinInputs(allInstanceIds, cohort) {
|
|
255
271
|
if (cohort.kind === "untracked")
|
|
256
272
|
return [...allInstanceIds];
|
|
@@ -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;
|
|
@@ -4,7 +4,8 @@ import { definitionIdForInstance } from "../runstore/stageInstanceId.js";
|
|
|
4
4
|
import { cloneFailureContinuesSchedule, cloneRetryDownstream, cloneFailFastSkipIds, cloneScheduleAllowsRun, failureIsAccepted, isCloneInstance, mint, protectedClonableChildIds, sequentialLaterCloneIds, skipRejectedNeedDependents, } from "./cloneSchedule.js";
|
|
5
5
|
import { buildCompletedEnvelopesFromRun, buildStageConfigById, } from "./envelopeRouting.js";
|
|
6
6
|
import { classifyInboundAfterSuccess, isEagerSingleParentIfSkip, joinAllowsRun, pickStalledJoinSkips, } from "./joinReadiness.js";
|
|
7
|
-
import {
|
|
7
|
+
import { reopenRunnableSkippedStages, persistReopenedStages } from "./reopenSkipped.js";
|
|
8
|
+
import { forkChoicePreservesReplaySource, forkCohortMapsFromStore, mintCohortForFanout, } from "./forkGeneration.js";
|
|
8
9
|
import { activeReplayId, activeSourceStageId, applySendBack, createReplaySchedule, hydrateFromStore, isHeld, launchAttemptFor, launchFor, noteActiveCohort, onContinued, onRouteStageFailed, onRouteStageRunning, onRouteStageSucceeded, rebindRouteAttempts, resolveDecision, } from "./replayLifecycle.js";
|
|
9
10
|
import { WAIT_WITHOUT_WORKER_DISPATCH } from "./answerResume.js";
|
|
10
11
|
import { runStage, isRunStageWaiting } from "./stageRunner.js";
|
|
@@ -65,6 +66,7 @@ export async function hydrateScheduleFromStore(store, runId, dag, executionMode
|
|
|
65
66
|
continue;
|
|
66
67
|
schedulingHalted = true;
|
|
67
68
|
}
|
|
69
|
+
await reopenRunnableSkippedStages(store, runId, resolvedDag, states, completedEnvelopes);
|
|
68
70
|
return {
|
|
69
71
|
states,
|
|
70
72
|
completedEnvelopes,
|
|
@@ -85,15 +87,20 @@ export async function hydrateScheduleForRetryRoots(store, runId, dag, retryStage
|
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
const retryRootSet = new Set(retryStageIds);
|
|
90
|
+
const { supersededCloneIds } = await forkCohortMapsFromStore(store, runId);
|
|
88
91
|
const states = new Map();
|
|
89
92
|
for (const node of resolvedDag.nodes) {
|
|
90
93
|
states.set(node.id, "pending");
|
|
91
94
|
}
|
|
92
95
|
const overrides = new Map();
|
|
93
96
|
for (const rootId of retryStageIds) {
|
|
97
|
+
if (supersededCloneIds.has(rootId))
|
|
98
|
+
continue;
|
|
94
99
|
overrides.set(rootId, "pending");
|
|
95
100
|
}
|
|
96
101
|
for (const id of downstream) {
|
|
102
|
+
if (supersededCloneIds.has(id))
|
|
103
|
+
continue;
|
|
97
104
|
overrides.set(id, "pending");
|
|
98
105
|
}
|
|
99
106
|
for (const snap of runDetail.stages) {
|
|
@@ -104,6 +111,20 @@ export async function hydrateScheduleForRetryRoots(store, runId, dag, retryStage
|
|
|
104
111
|
completedEnvelopes.delete(id);
|
|
105
112
|
}
|
|
106
113
|
}
|
|
114
|
+
const skippedToReopen = [];
|
|
115
|
+
const skipStates = new Map();
|
|
116
|
+
for (const snap of runDetail.stages) {
|
|
117
|
+
const id = snap.stage_id;
|
|
118
|
+
if (supersededCloneIds.has(id))
|
|
119
|
+
continue;
|
|
120
|
+
if (snap.status !== "skipped")
|
|
121
|
+
continue;
|
|
122
|
+
if (!retryRootSet.has(id) && !downstream.has(id))
|
|
123
|
+
continue;
|
|
124
|
+
skippedToReopen.push(id);
|
|
125
|
+
skipStates.set(id, "skipped");
|
|
126
|
+
}
|
|
127
|
+
await persistReopenedStages(store, runId, skippedToReopen, skipStates);
|
|
107
128
|
return {
|
|
108
129
|
states,
|
|
109
130
|
completedEnvelopes,
|
|
@@ -752,6 +773,7 @@ export async function runPipelineDag(options) {
|
|
|
752
773
|
await persistSkipPending(childId);
|
|
753
774
|
}
|
|
754
775
|
}
|
|
776
|
+
await reopenRunnableSkippedStages(store, run.runId, dag, states, completedEnvelopes);
|
|
755
777
|
};
|
|
756
778
|
const handlePostSuccessError = async (stageId, reason) => {
|
|
757
779
|
if (states.get(stageId) === "succeeded") {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { RunStore } from "../runstore/port.js";
|
|
2
|
+
import type { StageEnvelope } from "../types/envelope.js";
|
|
3
|
+
import type { ResolvedPipelineDag } from "../types/pipeline.js";
|
|
4
|
+
import type { StageScheduleState } from "./pipelineScheduler.js";
|
|
5
|
+
export declare function isUnchosenForkChild(dag: ResolvedPipelineDag, stageId: string, envelopes: Map<string, StageEnvelope>): boolean;
|
|
6
|
+
export declare function eventsShowStarted(events: ReadonlyArray<{
|
|
7
|
+
event: string;
|
|
8
|
+
}>): boolean;
|
|
9
|
+
export declare function shouldReopenSkippedStage(dag: ResolvedPipelineDag, stageId: string, states: Map<string, StageScheduleState>, envelopes: Map<string, StageEnvelope>, hasStarted: boolean, options?: {
|
|
10
|
+
supersededCloneIds?: ReadonlySet<string>;
|
|
11
|
+
}): boolean;
|
|
12
|
+
export declare function persistReopenedStages(store: Pick<RunStore, "appendStageEvent">, runId: string, stageIds: Iterable<string>, states: Map<string, StageScheduleState>): Promise<void>;
|
|
13
|
+
export declare function reopenRunnableSkippedStages(store: Pick<RunStore, "appendStageEvent" | "listStageEvents" | "listForkGenerations">, runId: string, dag: ResolvedPipelineDag, states: Map<string, StageScheduleState>, envelopes: Map<string, StageEnvelope>): Promise<void>;
|