stageflow 0.15.0 → 0.18.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 -4
- package/dist/agent/activity.d.ts +3 -0
- package/dist/agent/claudeAdapter.js +84 -7
- package/dist/agent/claudeSession.d.ts +3 -0
- package/dist/agent/claudeTools.d.ts +1 -4
- package/dist/agent/claudeTools.js +1 -6
- package/dist/agent/fakeAgent.js +4 -5
- package/dist/agent/piAdapter.d.ts +2 -0
- package/dist/agent/piAdapter.js +30 -19
- package/dist/agent/port.d.ts +3 -0
- package/dist/cli/runOutput.js +32 -8
- package/dist/config/cloneChain.d.ts +12 -0
- package/dist/config/cloneChain.js +322 -0
- package/dist/config/createPipeline.js +101 -21
- package/dist/config/createStage.js +7 -0
- package/dist/config/loadOutcome.d.ts +1 -0
- package/dist/config/loadPipeline.d.ts +2 -0
- package/dist/config/loadPipeline.js +34 -22
- package/dist/config/loadStage.d.ts +2 -0
- package/dist/config/loadStage.js +135 -125
- package/dist/config/migrateYaml.js +14 -3
- package/dist/config/normalizePipelineStageEntry.d.ts +5 -9
- package/dist/config/normalizePipelineStageEntry.js +133 -60
- package/dist/config/parsePreEmitChecks.js +2 -2
- package/dist/config/pipelineNeeds.d.ts +15 -2
- package/dist/config/pipelineNeeds.js +86 -4
- package/dist/config/pipelineRoute.d.ts +46 -0
- package/dist/config/pipelineRoute.js +215 -0
- package/dist/config/pipelineStageKeys.d.ts +2 -0
- package/dist/config/pipelineStageKeys.js +15 -4
- package/dist/config/printTargetYaml.js +18 -12
- package/dist/config/resolveForkEmitContext.d.ts +1 -1
- package/dist/config/resolveForkEmitContext.js +3 -26
- package/dist/config/resolvePipelineDag.d.ts +1 -3
- package/dist/config/resolvePipelineDag.js +143 -186
- package/dist/config/routeIf.d.ts +24 -0
- package/dist/config/routeIf.js +247 -0
- package/dist/config/validateCatalog.d.ts +1 -1
- package/dist/config/validateCatalog.js +5 -3
- package/dist/config/yamlDialect.d.ts +5 -8
- package/dist/config/yamlDialect.js +37 -29
- package/dist/envelope/check.d.ts +0 -2
- package/dist/envelope/check.js +1 -57
- package/dist/envelope/feedbackLoop.js +0 -3
- package/dist/envelope/forkChoice.js +1 -1
- package/dist/envelope/payloadSchema.d.ts +14 -0
- package/dist/envelope/payloadSchema.js +87 -2
- package/dist/mcp/catalogTools.js +1 -2
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/projection/projectRun.d.ts +0 -2
- package/dist/projection/projectRun.js +0 -3
- package/dist/prompt/priorEnvelope.js +1 -1
- package/dist/runstore/port.d.ts +10 -0
- package/dist/runstore/runProjection.js +9 -0
- package/dist/runstore/sqlite/SqliteRunStore.js +27 -4
- package/dist/runstore/sqlite/schema.d.ts +1 -1
- package/dist/runstore/sqlite/schema.js +2 -0
- package/dist/runstore/stageExecution.d.ts +3 -0
- package/dist/runstore/stageExecution.js +6 -2
- package/dist/runstore/stageInstanceId.d.ts +1 -0
- package/dist/runstore/stageInstanceId.js +9 -0
- package/dist/runstore/stageSnapshot.js +11 -0
- package/dist/runtime/cloneSchedule.d.ts +16 -33
- package/dist/runtime/cloneSchedule.js +155 -236
- package/dist/runtime/envelopeRouting.js +43 -94
- package/dist/runtime/forkGeneration.d.ts +2 -2
- package/dist/runtime/forkGeneration.js +29 -15
- package/dist/runtime/joinReadiness.d.ts +16 -0
- package/dist/runtime/joinReadiness.js +156 -0
- package/dist/runtime/pipelineScheduler.d.ts +0 -3
- package/dist/runtime/pipelineScheduler.js +81 -65
- package/dist/runtime/routeIfEval.d.ts +3 -0
- package/dist/runtime/routeIfEval.js +94 -0
- package/dist/runtime/runRetryCoordinator.js +0 -4
- package/dist/runtime/stageAttemptBootstrap.js +60 -11
- package/dist/runtime/stageRunner.js +2 -2
- package/dist/runtime/taskInput.js +1 -13
- package/dist/tools/emitStageEnvelope.d.ts +2 -3
- package/dist/tools/emitStageEnvelope.js +1 -65
- package/dist/types/envelope.d.ts +0 -2
- package/dist/types/forkChoice.d.ts +0 -16
- package/dist/types/pipeline.d.ts +86 -27
- package/dist/types/stage.d.ts +11 -7
- package/dist/types/usage.d.ts +16 -0
- package/dist/types/usage.js +24 -0
- package/dist/ui/assets/{index-uVHiae8s.css → index-DsgVjLUS.css} +1 -1
- package/dist/ui/assets/index-DwKGaQvQ.js +119 -0
- package/dist/ui/index.html +2 -2
- package/package.json +2 -1
- package/skills/stageflow-author/SKILL.md +4 -4
- package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +10 -2
- package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +12 -4
- package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +6 -4
- package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +9 -1
- package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +3 -0
- package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +8 -0
- package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +5 -2
- package/skills/stageflow-author/assets/examples/linear-review/review.yaml +8 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +3 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +5 -2
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +8 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +8 -0
- package/skills/stageflow-author/references/catalog-mapping.md +159 -50
- package/skills/stageflow-author/references/catalog-write-conventions.md +3 -3
- package/skills/stageflow-author/references/stage-prompt-template.md +57 -17
- package/skills/stageflow-session-capture/SKILL.md +2 -2
- package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +3 -1
- package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +15 -0
- package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +3 -0
- package/skills/stageflow-session-capture/references/catalog-authoring.md +4 -2
- package/dist/envelope/cloneForks.d.ts +0 -2
- package/dist/envelope/cloneForks.js +0 -77
- package/dist/ui/assets/index-GtyiWiZg.js +0 -118
|
@@ -4,7 +4,9 @@ import { MCP_CATALOG_FILENAME, STAGEFLOW_STAGE_ARTIFACTS_DIR_ENV, StageMcpError,
|
|
|
4
4
|
import { attemptArtifactsDir } from "../runstore/workspaceLayout.js";
|
|
5
5
|
import { resolveCloneEmitContext, resolveForkEmitContext } from "../config/resolveForkEmitContext.js";
|
|
6
6
|
import { createAttemptQaTrailReader } from "../hitl/qaTrail.js";
|
|
7
|
+
import { assertPriorInputPayload, payloadInstanceMismatch, } from "../envelope/payloadSchema.js";
|
|
7
8
|
import { buildCompletedEnvelopesFromRun, resolvePriorEnvelope, } from "./envelopeRouting.js";
|
|
9
|
+
import { assignment } from "./cloneSchedule.js";
|
|
8
10
|
import { resumeSessionFilePath } from "./stageAttemptContext.js";
|
|
9
11
|
import { buildStageRoots, rootsForStageWorker, withResolvedAuthPath, } from "./stageRoots.js";
|
|
10
12
|
async function resolveStageSkillForRun(stage, catalog) {
|
|
@@ -76,6 +78,51 @@ async function openStageWithOperatorCatalog(agent, input, catalog, factoryCwd, a
|
|
|
76
78
|
};
|
|
77
79
|
}
|
|
78
80
|
}
|
|
81
|
+
function assertPriorsMatchCloneInput(cloneInputSchema, childId, priorResult, task, skipCloneAssignment) {
|
|
82
|
+
if (cloneInputSchema === undefined)
|
|
83
|
+
return { ok: true };
|
|
84
|
+
if (skipCloneAssignment)
|
|
85
|
+
return { ok: true };
|
|
86
|
+
try {
|
|
87
|
+
if (priorResult.priorEnvelopesByStage !== undefined) {
|
|
88
|
+
for (const value of Object.values(priorResult.priorEnvelopesByStage)) {
|
|
89
|
+
const envelopes = Array.isArray(value) ? value : [value];
|
|
90
|
+
for (const envelope of envelopes) {
|
|
91
|
+
if (envelope.status !== "success")
|
|
92
|
+
continue;
|
|
93
|
+
assertPriorInputPayload(envelope, cloneInputSchema, childId);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return { ok: true };
|
|
97
|
+
}
|
|
98
|
+
if (priorResult.joinPriors !== undefined) {
|
|
99
|
+
for (const envelope of priorResult.joinPriors) {
|
|
100
|
+
if (envelope.status !== "success")
|
|
101
|
+
continue;
|
|
102
|
+
assertPriorInputPayload(envelope, cloneInputSchema, childId);
|
|
103
|
+
}
|
|
104
|
+
return { ok: true };
|
|
105
|
+
}
|
|
106
|
+
if (priorResult.prior !== null) {
|
|
107
|
+
assertPriorInputPayload(priorResult.prior, cloneInputSchema, childId);
|
|
108
|
+
return { ok: true };
|
|
109
|
+
}
|
|
110
|
+
const details = payloadInstanceMismatch(task.input ?? {}, cloneInputSchema);
|
|
111
|
+
if (details !== undefined) {
|
|
112
|
+
return {
|
|
113
|
+
ok: false,
|
|
114
|
+
reason: `task input does not match io.input.schema for ${childId}: ${details}`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return { ok: true };
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
return {
|
|
121
|
+
ok: false,
|
|
122
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
79
126
|
function resolveAttemptRoots(input, stageId) {
|
|
80
127
|
const baseRoots = input.roots ??
|
|
81
128
|
(input.workerRoots
|
|
@@ -136,15 +183,20 @@ export async function openStageAttempt(input) {
|
|
|
136
183
|
const definitionId = input.stage.id;
|
|
137
184
|
const completedEnvelopes = input.completedEnvelopes ??
|
|
138
185
|
(await buildCompletedEnvelopesFromRun(input.store, input.runId, undefined, input.dag));
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
186
|
+
const assigned = assignment(input.dag, stageId, definitionId, completedEnvelopes);
|
|
187
|
+
const priorResult = assigned ??
|
|
188
|
+
(await resolvePriorEnvelope({
|
|
189
|
+
dag: input.dag,
|
|
190
|
+
stageId,
|
|
191
|
+
completedEnvelopes,
|
|
192
|
+
store: input.store,
|
|
193
|
+
runId: input.runId,
|
|
194
|
+
}));
|
|
146
195
|
if (!priorResult.ok)
|
|
147
196
|
return priorResult;
|
|
197
|
+
const priorInput = assertPriorsMatchCloneInput(input.stage.clone_input_schema, stageId, priorResult, input.task, false);
|
|
198
|
+
if (!priorInput.ok)
|
|
199
|
+
return priorInput;
|
|
148
200
|
const roots = resolveAttemptRoots(input, stageId);
|
|
149
201
|
const attempt = input.attemptCtx?.attempt ?? 1;
|
|
150
202
|
const resumeToken = input.resumeToken ??
|
|
@@ -152,9 +204,6 @@ export async function openStageAttempt(input) {
|
|
|
152
204
|
const forkEmitContext = resolveForkEmitContext(input.dag, definitionId);
|
|
153
205
|
const snapshot = input.dag;
|
|
154
206
|
const cloneEmitContext = resolveCloneEmitContext(input.dag, definitionId, {
|
|
155
|
-
...(input.stage.clone_actions !== undefined
|
|
156
|
-
? { allowedActions: input.stage.clone_actions }
|
|
157
|
-
: {}),
|
|
158
207
|
...(snapshot.clone_input_schema !== undefined
|
|
159
208
|
? { successorCloneInputSchemas: snapshot.clone_input_schema }
|
|
160
209
|
: {}),
|
|
@@ -163,7 +212,7 @@ export async function openStageAttempt(input) {
|
|
|
163
212
|
const runtimeNode = input.dag.nodes.find((node) => node.id === stageId);
|
|
164
213
|
const isDynamicCloneInstance = runtimeNode?.definition_id !== undefined &&
|
|
165
214
|
runtimeNode.definition_id !== runtimeNode.id;
|
|
166
|
-
const feedbackLoopEmitContext = isDynamicCloneInstance
|
|
215
|
+
const feedbackLoopEmitContext = isDynamicCloneInstance
|
|
167
216
|
? undefined
|
|
168
217
|
: runtimeNode?.feedback_loop;
|
|
169
218
|
const repairContext = await repairContextForAttempt(input, stageId, attempt);
|
|
@@ -54,7 +54,7 @@ async function finalizeStageResult(options) {
|
|
|
54
54
|
store,
|
|
55
55
|
runId,
|
|
56
56
|
stageId,
|
|
57
|
-
event: { event: "failed", reason: result.reason },
|
|
57
|
+
event: { event: "failed", reason: result.reason, usage: result.usage },
|
|
58
58
|
attemptCtx,
|
|
59
59
|
});
|
|
60
60
|
console.error(`Stage ${stageId} failed: ${result.reason}`);
|
|
@@ -64,7 +64,7 @@ async function finalizeStageResult(options) {
|
|
|
64
64
|
store,
|
|
65
65
|
runId,
|
|
66
66
|
stageId,
|
|
67
|
-
event: { event: "succeeded" },
|
|
67
|
+
event: { event: "succeeded", usage: result.usage },
|
|
68
68
|
attemptCtx,
|
|
69
69
|
});
|
|
70
70
|
console.error(`Stage ${stageId} succeeded.`);
|
|
@@ -34,21 +34,9 @@ export function checkTaskEntryInput(task, loaded, options) {
|
|
|
34
34
|
const stage = stageById.get(rootId);
|
|
35
35
|
if (stage?.clone_input_schema === undefined)
|
|
36
36
|
continue;
|
|
37
|
-
if (task.input === undefined) {
|
|
38
|
-
findings.push({
|
|
39
|
-
severity: "warning",
|
|
40
|
-
code: "task.entry_input_unmet",
|
|
41
|
-
path: findingPath,
|
|
42
|
-
message: `Task has no input; entry stage "${rootId}" requires io.input`,
|
|
43
|
-
category: "task",
|
|
44
|
-
pipelineId: loaded.pipeline.id,
|
|
45
|
-
stageId: rootId,
|
|
46
|
-
});
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
37
|
let details;
|
|
50
38
|
try {
|
|
51
|
-
details = payloadInstanceMismatch(task.input, stage.clone_input_schema);
|
|
39
|
+
details = payloadInstanceMismatch(task.input ?? {}, stage.clone_input_schema);
|
|
52
40
|
}
|
|
53
41
|
catch (err) {
|
|
54
42
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -14,12 +14,12 @@ import type { TSchema } from "typebox/type";
|
|
|
14
14
|
import { type PreEmitCheckOptions } from "../envelope/preEmitChecks.js";
|
|
15
15
|
import type { StageEnvelope } from "../types/envelope.js";
|
|
16
16
|
import type { FeedbackLoopConfig } from "../types/pipeline.js";
|
|
17
|
-
import {
|
|
17
|
+
import type { CloneEmitContext, ForkEmitContext } from "../types/forkChoice.js";
|
|
18
18
|
export type EmitCapture = {
|
|
19
19
|
envelope?: StageEnvelope;
|
|
20
20
|
error?: string;
|
|
21
21
|
};
|
|
22
|
-
export declare function createEmitStageEnvelopeTool(capture: EmitCapture, payloadSchema?: unknown, forkEmitContext?: ForkEmitContext,
|
|
22
|
+
export declare function createEmitStageEnvelopeTool(capture: EmitCapture, payloadSchema?: unknown, forkEmitContext?: ForkEmitContext, _cloneEmitContext?: CloneEmitContext, preEmitCheckOptions?: PreEmitCheckOptions, feedbackLoopEmitContext?: FeedbackLoopConfig): {
|
|
23
23
|
name: string;
|
|
24
24
|
label: string;
|
|
25
25
|
description: string;
|
|
@@ -29,7 +29,6 @@ export declare function createEmitStageEnvelopeTool(capture: EmitCapture, payloa
|
|
|
29
29
|
artifacts: Type.TArray<Type.TString>;
|
|
30
30
|
payload: Type.TOptional<TSchema>;
|
|
31
31
|
fork_choice: Type.TArray<Type.TString>;
|
|
32
|
-
clone_forks?: Type.TArray<TSchema> | undefined;
|
|
33
32
|
feedback_loop?: Type.TOptional<TSchema> | undefined;
|
|
34
33
|
checklist_attestations: Type.TOptional<Type.TArray<Type.TObject<{
|
|
35
34
|
check_id: Type.TString;
|
|
@@ -12,11 +12,9 @@
|
|
|
12
12
|
import { Type } from "typebox";
|
|
13
13
|
import { assertRequiredEnvelope, isAdvancingEnvelope, } from "../envelope/check.js";
|
|
14
14
|
import { assertFeedbackLoopAction } from "../envelope/feedbackLoop.js";
|
|
15
|
-
import { assertCloneForks } from "../envelope/cloneForks.js";
|
|
16
15
|
import { normalizeForkChoice } from "../envelope/forkChoice.js";
|
|
17
16
|
import { assertEnvelopePayload, compilePayloadSchema, } from "../envelope/payloadSchema.js";
|
|
18
17
|
import { assertPreEmitChecks, } from "../envelope/preEmitChecks.js";
|
|
19
|
-
import { CLONE_ACTIONS, } from "../types/forkChoice.js";
|
|
20
18
|
function stringLiteralsSchema(values) {
|
|
21
19
|
if (values.length === 1) {
|
|
22
20
|
return Type.Literal(values[0]);
|
|
@@ -42,56 +40,6 @@ function feedbackLoopActionSchema(context) {
|
|
|
42
40
|
}, { additionalProperties: false }),
|
|
43
41
|
]);
|
|
44
42
|
}
|
|
45
|
-
function cloneForksItemSchema(cloneEmitContext) {
|
|
46
|
-
const allowedActions = cloneEmitContext.allowedActions ?? [...CLONE_ACTIONS];
|
|
47
|
-
const allowSkip = allowedActions.includes("skip");
|
|
48
|
-
const allowOnce = allowedActions.includes("once");
|
|
49
|
-
const allowFanout = allowedActions.includes("fanout");
|
|
50
|
-
const variants = [];
|
|
51
|
-
for (const successor of cloneEmitContext.clonableSuccessors) {
|
|
52
|
-
const successorId = Type.Literal(successor.successorId);
|
|
53
|
-
const nestedPayload = successor.cloneInputSchema !== undefined
|
|
54
|
-
? compilePayloadSchema(successor.cloneInputSchema)
|
|
55
|
-
: undefined;
|
|
56
|
-
const nestedEnvelope = stageEnvelopeSchema(nestedPayload);
|
|
57
|
-
if (allowSkip) {
|
|
58
|
-
variants.push(Type.Object({
|
|
59
|
-
successor_id: successorId,
|
|
60
|
-
action: Type.Literal("skip"),
|
|
61
|
-
}, { additionalProperties: false }));
|
|
62
|
-
}
|
|
63
|
-
if (allowOnce) {
|
|
64
|
-
variants.push(Type.Object({
|
|
65
|
-
successor_id: successorId,
|
|
66
|
-
action: Type.Literal("once"),
|
|
67
|
-
envelope: nestedEnvelope,
|
|
68
|
-
}));
|
|
69
|
-
}
|
|
70
|
-
if (allowFanout) {
|
|
71
|
-
variants.push(Type.Object({
|
|
72
|
-
successor_id: successorId,
|
|
73
|
-
action: Type.Literal("fanout"),
|
|
74
|
-
mode: Type.Union([
|
|
75
|
-
Type.Literal("parallel"),
|
|
76
|
-
Type.Literal("sequential"),
|
|
77
|
-
]),
|
|
78
|
-
clones: Type.Array(Type.Object({
|
|
79
|
-
envelope: nestedEnvelope,
|
|
80
|
-
}), { minItems: 2, maxItems: successor.cloneCap }),
|
|
81
|
-
}));
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (variants.length === 0) {
|
|
85
|
-
return Type.Object({
|
|
86
|
-
successor_id: Type.String(),
|
|
87
|
-
action: stringLiteralsSchema(allowedActions),
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
if (variants.length === 1) {
|
|
91
|
-
return variants[0];
|
|
92
|
-
}
|
|
93
|
-
return Type.Union(variants);
|
|
94
|
-
}
|
|
95
43
|
function toolResult(text, details, options) {
|
|
96
44
|
return {
|
|
97
45
|
content: [{ type: "text", text }],
|
|
@@ -100,7 +48,7 @@ function toolResult(text, details, options) {
|
|
|
100
48
|
...(options?.terminate ? { terminate: true } : {}),
|
|
101
49
|
};
|
|
102
50
|
}
|
|
103
|
-
export function createEmitStageEnvelopeTool(capture, payloadSchema, forkEmitContext,
|
|
51
|
+
export function createEmitStageEnvelopeTool(capture, payloadSchema, forkEmitContext, _cloneEmitContext, preEmitCheckOptions, feedbackLoopEmitContext) {
|
|
104
52
|
const compiledPayload = payloadSchema !== undefined
|
|
105
53
|
? compilePayloadSchema(payloadSchema)
|
|
106
54
|
: undefined;
|
|
@@ -118,13 +66,6 @@ export function createEmitStageEnvelopeTool(capture, payloadSchema, forkEmitCont
|
|
|
118
66
|
fork_choice: forkEmitContext !== undefined && feedbackLoopEmitContext === undefined
|
|
119
67
|
? Type.Array(Type.String())
|
|
120
68
|
: Type.Optional(Type.Array(Type.String())),
|
|
121
|
-
...(cloneEmitContext !== undefined
|
|
122
|
-
? {
|
|
123
|
-
clone_forks: feedbackLoopEmitContext === undefined
|
|
124
|
-
? Type.Array(cloneForksItemSchema(cloneEmitContext))
|
|
125
|
-
: Type.Optional(Type.Array(cloneForksItemSchema(cloneEmitContext))),
|
|
126
|
-
}
|
|
127
|
-
: {}),
|
|
128
69
|
...(feedbackLoopEmitContext !== undefined
|
|
129
70
|
? {
|
|
130
71
|
feedback_loop: Type.Optional(feedbackLoopActionSchema(feedbackLoopEmitContext)),
|
|
@@ -150,11 +91,6 @@ export function createEmitStageEnvelopeTool(capture, payloadSchema, forkEmitCont
|
|
|
150
91
|
forkEmitContext !== undefined) {
|
|
151
92
|
normalizeForkChoice(envelope.fork_choice, "emit", forkEmitContext);
|
|
152
93
|
}
|
|
153
|
-
if (envelope.status !== "failure" &&
|
|
154
|
-
!isSendBack &&
|
|
155
|
-
cloneEmitContext !== undefined) {
|
|
156
|
-
assertCloneForks(envelope, cloneEmitContext);
|
|
157
|
-
}
|
|
158
94
|
assertEnvelopePayload(envelope, payloadSchema);
|
|
159
95
|
if (envelope.status !== "failure") {
|
|
160
96
|
await assertPreEmitChecks(envelope, preEmitCheckOptions);
|
package/dist/types/envelope.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { CloneForkItem } from "./forkChoice.js";
|
|
2
1
|
export type EnvelopeStatus = "success" | "failure";
|
|
3
2
|
export type FeedbackLoopAction = {
|
|
4
3
|
action: "continue";
|
|
@@ -12,7 +11,6 @@ export type StageEnvelope = {
|
|
|
12
11
|
artifacts: string[];
|
|
13
12
|
payload?: Record<string, unknown>;
|
|
14
13
|
fork_choice?: string[];
|
|
15
|
-
clone_forks?: CloneForkItem[];
|
|
16
14
|
feedback_loop?: FeedbackLoopAction;
|
|
17
15
|
/** Agent's explicit acknowledgement of pipeline-owned checklist items. */
|
|
18
16
|
checklist_attestations?: Array<{
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { StageEnvelope } from "./envelope.js";
|
|
2
1
|
export type ForkChoiceShape = {
|
|
3
2
|
cardinality: "one" | "subset";
|
|
4
3
|
allowNone: boolean;
|
|
@@ -9,21 +8,6 @@ export type ForkEmitContext = {
|
|
|
9
8
|
};
|
|
10
9
|
export declare const CLONE_ACTIONS: readonly ["skip", "once", "fanout"];
|
|
11
10
|
export type CloneAction = (typeof CLONE_ACTIONS)[number];
|
|
12
|
-
export type CloneForkItem = {
|
|
13
|
-
successor_id: string;
|
|
14
|
-
action: "skip";
|
|
15
|
-
} | {
|
|
16
|
-
successor_id: string;
|
|
17
|
-
action: "once";
|
|
18
|
-
envelope: StageEnvelope;
|
|
19
|
-
} | {
|
|
20
|
-
successor_id: string;
|
|
21
|
-
action: "fanout";
|
|
22
|
-
mode: "parallel" | "sequential";
|
|
23
|
-
clones: Array<{
|
|
24
|
-
envelope: StageEnvelope;
|
|
25
|
-
}>;
|
|
26
|
-
};
|
|
27
11
|
export type CloneSuccessorEmit = {
|
|
28
12
|
successorId: string;
|
|
29
13
|
cloneCap: number;
|
package/dist/types/pipeline.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { CloneAction } from "./forkChoice.js";
|
|
2
1
|
import type { LoadedStageConfig, StageGateKind, StageIoYaml } from "./stage.js";
|
|
3
2
|
import type { CompletionContract, RecoveryPolicy } from "./completion.js";
|
|
4
3
|
export type PipelineConfig = {
|
|
@@ -16,15 +15,75 @@ export type PipelineForkConfig = {
|
|
|
16
15
|
allow_none: boolean;
|
|
17
16
|
};
|
|
18
17
|
export type NeedTerminalState = "succeeded" | "failed" | "skipped";
|
|
18
|
+
export type RouteIfOp = "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "in" | "not_in";
|
|
19
|
+
export type RouteIfLeafPredicate = {
|
|
20
|
+
field: string;
|
|
21
|
+
op: RouteIfOp;
|
|
22
|
+
value: unknown;
|
|
23
|
+
};
|
|
24
|
+
export type RouteIfAllPredicate = {
|
|
25
|
+
all: RouteIfPredicate[];
|
|
26
|
+
field?: never;
|
|
27
|
+
};
|
|
28
|
+
export type RouteIfAnyPredicate = {
|
|
29
|
+
any: RouteIfPredicate[];
|
|
30
|
+
field?: never;
|
|
31
|
+
};
|
|
32
|
+
export type RouteIfNotPredicate = {
|
|
33
|
+
not: RouteIfPredicate;
|
|
34
|
+
field?: never;
|
|
35
|
+
};
|
|
36
|
+
export type RouteIfPredicate = RouteIfLeafPredicate | RouteIfAllPredicate | RouteIfAnyPredicate | RouteIfNotPredicate;
|
|
19
37
|
export type PipelineNeedEdge = {
|
|
20
38
|
id: string;
|
|
21
39
|
on: NeedTerminalState[];
|
|
40
|
+
if?: RouteIfPredicate;
|
|
22
41
|
};
|
|
23
42
|
export type PipelineNeedItem = string | {
|
|
24
43
|
id: string;
|
|
25
44
|
on?: NeedTerminalState[];
|
|
26
45
|
};
|
|
27
46
|
export type PipelineNeeds = string | PipelineNeedEdge[];
|
|
47
|
+
/**
|
|
48
|
+
* Terminal states a route entry (or its inverted predecessor edge) can gate on.
|
|
49
|
+
* Same domain as `NeedTerminalState` — kept as a distinct alias since `route`
|
|
50
|
+
* is a separate vocabulary from `needs` (see docs/yaml-catalog.md).
|
|
51
|
+
*/
|
|
52
|
+
export type RouteTerminalState = NeedTerminalState;
|
|
53
|
+
/**
|
|
54
|
+
* A forward route entry: hands off from the declaring stage to `to`, gated on
|
|
55
|
+
* the declaring stage's own terminal state (`on`, defaulting to succeeded-only).
|
|
56
|
+
*/
|
|
57
|
+
export type PipelineRouteForwardEntry = {
|
|
58
|
+
to: string;
|
|
59
|
+
on?: RouteTerminalState | RouteTerminalState[];
|
|
60
|
+
if?: RouteIfPredicate;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* A loop route entry (ticket 03, route-based-pipeline-wiring spec): sends
|
|
64
|
+
* execution back to a declared ancestor of the source stage instead of
|
|
65
|
+
* continuing forward, carrying the same replay policy `FeedbackLoopConfig`
|
|
66
|
+
* carries today — `target` renamed to `to` for naming consistency with
|
|
67
|
+
* forward entries. Contributes no forward DAG edge (`toRouteEdges` skips
|
|
68
|
+
* it), so it is excluded from cycle detection; it is a runtime-only replay
|
|
69
|
+
* schedule over the already-resolved forward graph.
|
|
70
|
+
*/
|
|
71
|
+
export type PipelineRouteLoopEntry = {
|
|
72
|
+
type: "loop";
|
|
73
|
+
to: string;
|
|
74
|
+
max_replays: number;
|
|
75
|
+
on_max_replays: "require_continue" | "wait_for_human";
|
|
76
|
+
replay_session: "resume" | "new_session";
|
|
77
|
+
};
|
|
78
|
+
export type PipelineRouteEntry = PipelineRouteForwardEntry | PipelineRouteLoopEntry;
|
|
79
|
+
/** A stage's outbound `route` field: where execution goes next. */
|
|
80
|
+
export type PipelineRoute = PipelineRouteEntry[];
|
|
81
|
+
/** Normalized forward route edge (post-parse): `on` is always populated. */
|
|
82
|
+
export type PipelineRouteEdge = {
|
|
83
|
+
to: string;
|
|
84
|
+
on: RouteTerminalState[];
|
|
85
|
+
if?: RouteIfPredicate;
|
|
86
|
+
};
|
|
28
87
|
/** Runtime feedback-loop policy declared by the stage that can send work back. */
|
|
29
88
|
export type FeedbackLoopConfig = {
|
|
30
89
|
target: string;
|
|
@@ -32,43 +91,44 @@ export type FeedbackLoopConfig = {
|
|
|
32
91
|
on_max_replays: "require_continue" | "wait_for_human";
|
|
33
92
|
replay_session: "resume" | "new_session";
|
|
34
93
|
};
|
|
94
|
+
export type CloneMode = "parallel" | "sequential";
|
|
35
95
|
export type PipelineStageRef = {
|
|
36
96
|
id: string;
|
|
37
|
-
needs?: string | PipelineNeedItem[];
|
|
38
|
-
fork?: {
|
|
39
|
-
select: "one" | "subset";
|
|
40
|
-
allow_none?: boolean;
|
|
41
|
-
};
|
|
42
|
-
clonable?: boolean;
|
|
43
|
-
clone_cap?: number;
|
|
44
97
|
/** IR: after-phase checks. YAML: `verify` items whose `when` includes `after`. */
|
|
45
98
|
completion?: CompletionContract;
|
|
46
99
|
/** IR: after-phase failure policy. YAML: `on_verify_fail`. */
|
|
47
100
|
recovery?: RecoveryPolicy;
|
|
48
|
-
feedback_loop?: FeedbackLoopConfig;
|
|
49
101
|
/** Omitted means this stage is safe to include in a feedback replay. */
|
|
50
102
|
replay_safe?: boolean;
|
|
103
|
+
/** Pipeline wiring vocabulary (see docs/yaml-catalog.md). */
|
|
104
|
+
route?: PipelineRouteEntry[];
|
|
105
|
+
/** Marks this stage as a pipeline entry point. */
|
|
106
|
+
entry?: boolean;
|
|
107
|
+
clone_cap?: number;
|
|
108
|
+
clone_mode?: CloneMode;
|
|
51
109
|
};
|
|
52
110
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* New catalog fields: add target YAML keys here and compile them in yamlDialect.ts.
|
|
111
|
+
* Catalog YAML stage entry (`io` / `verify` / `on_verify_fail`). Not IR.
|
|
112
|
+
* Dual-read of `payload_schema` / `pre_emit_checks` / `completion` / `recovery`
|
|
113
|
+
* stays in `legacyYaml.ts` on the raw record.
|
|
57
114
|
*/
|
|
58
|
-
export type PipelineStageYamlEntry =
|
|
115
|
+
export type PipelineStageYamlEntry = {
|
|
116
|
+
id?: string;
|
|
59
117
|
uses?: string;
|
|
60
118
|
system_prompt?: string;
|
|
61
119
|
model?: string;
|
|
62
|
-
payload_schema?: unknown;
|
|
63
120
|
gate_kinds?: StageGateKind[];
|
|
64
|
-
|
|
65
|
-
clone_actions?: CloneAction[];
|
|
121
|
+
timeout_ms?: number;
|
|
66
122
|
skill?: string;
|
|
67
123
|
mcp?: string[];
|
|
124
|
+
replay_safe?: boolean;
|
|
125
|
+
route?: PipelineRouteEntry[];
|
|
126
|
+
entry?: boolean;
|
|
127
|
+
clone_cap?: number;
|
|
128
|
+
clone_mode?: CloneMode;
|
|
68
129
|
io?: StageIoYaml;
|
|
69
130
|
verify?: unknown;
|
|
70
131
|
on_verify_fail?: RecoveryPolicy;
|
|
71
|
-
pre_emit_checks?: unknown;
|
|
72
132
|
};
|
|
73
133
|
export type PipelineIncludeEntry = {
|
|
74
134
|
local: string;
|
|
@@ -79,20 +139,16 @@ export type PipelineFragmentConfig = {
|
|
|
79
139
|
};
|
|
80
140
|
export type NormalizedPipelineStageEntry = {
|
|
81
141
|
id: string;
|
|
82
|
-
needs?: PipelineNeeds;
|
|
83
|
-
fork?: {
|
|
84
|
-
select: "one" | "subset";
|
|
85
|
-
allow_none?: boolean;
|
|
86
|
-
};
|
|
87
|
-
clonable?: boolean;
|
|
88
|
-
clone_cap?: number;
|
|
89
142
|
/** IR after compile. YAML `verify` after-phase / `on_verify_fail`. */
|
|
90
143
|
completion?: CompletionContract;
|
|
91
144
|
recovery?: RecoveryPolicy;
|
|
92
|
-
feedback_loop?: FeedbackLoopConfig;
|
|
93
145
|
replay_safe?: boolean;
|
|
146
|
+
route?: PipelineRouteEntry[];
|
|
147
|
+
entry?: boolean;
|
|
94
148
|
skill?: string;
|
|
95
149
|
mcp?: string[];
|
|
150
|
+
clone_cap?: number;
|
|
151
|
+
clone_mode?: CloneMode;
|
|
96
152
|
body: {
|
|
97
153
|
kind: "inline";
|
|
98
154
|
raw: Record<string, unknown>;
|
|
@@ -116,8 +172,9 @@ export type ResolvedPipelineStageNode = {
|
|
|
116
172
|
ancestors: string[];
|
|
117
173
|
stageIndex: number;
|
|
118
174
|
fork?: PipelineForkConfig;
|
|
119
|
-
clonable?: boolean;
|
|
120
175
|
clone_cap?: number;
|
|
176
|
+
clone_mode?: CloneMode;
|
|
177
|
+
clone_array_field?: string;
|
|
121
178
|
definition_id?: string;
|
|
122
179
|
/** Persisted on pipeline_dag. YAML `verify` after-phase. */
|
|
123
180
|
completion?: CompletionContract;
|
|
@@ -125,6 +182,8 @@ export type ResolvedPipelineStageNode = {
|
|
|
125
182
|
recovery?: RecoveryPolicy;
|
|
126
183
|
feedback_loop?: FeedbackLoopConfig;
|
|
127
184
|
replay_safe?: boolean;
|
|
185
|
+
/** Present (true) only when this stage was declared `entry: true`. */
|
|
186
|
+
entry?: boolean;
|
|
128
187
|
};
|
|
129
188
|
export type ResolvedPipelineDag = {
|
|
130
189
|
nodes: ResolvedPipelineStageNode[];
|
package/dist/types/stage.d.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import type { PreEmitCheck } from "./preEmitCheck.js";
|
|
2
|
-
|
|
2
|
+
/** Typed emit/schema fields from `compileTargetContract`. Not catalog YAML keys. */
|
|
3
|
+
export type CompiledStageEmitBody = {
|
|
4
|
+
payload_schema?: unknown;
|
|
5
|
+
clone_input_schema?: unknown;
|
|
6
|
+
pre_emit_checks?: PreEmitCheck[];
|
|
7
|
+
};
|
|
3
8
|
export declare const STAGE_GATE_KINDS: readonly ["free_text", "confirm", "multi_question", "artifact_backed"];
|
|
4
9
|
export type StageGateKind = (typeof STAGE_GATE_KINDS)[number];
|
|
5
10
|
/** Target YAML `io:` block. Compiles onto StageConfig.payload_schema / clone_input_schema. */
|
|
6
11
|
export type StageIoYaml = {
|
|
7
|
-
input
|
|
8
|
-
schema
|
|
12
|
+
input: {
|
|
13
|
+
schema: unknown;
|
|
9
14
|
};
|
|
10
|
-
output
|
|
11
|
-
schema
|
|
15
|
+
output: {
|
|
16
|
+
schema: unknown;
|
|
12
17
|
};
|
|
13
18
|
};
|
|
14
19
|
/**
|
|
@@ -27,9 +32,8 @@ export type StageConfig = {
|
|
|
27
32
|
gate_kinds?: StageGateKind[];
|
|
28
33
|
/** IR: emit-phase checks. YAML: `verify` items whose `when` includes `emit`. */
|
|
29
34
|
pre_emit_checks?: PreEmitCheck[];
|
|
30
|
-
/** IR:
|
|
35
|
+
/** IR: inbound assignment schema. YAML: `io.input.schema`. */
|
|
31
36
|
clone_input_schema?: unknown;
|
|
32
|
-
clone_actions?: CloneAction[];
|
|
33
37
|
/** Optional stage wall-clock timeout in milliseconds (default 60 minutes). */
|
|
34
38
|
timeout_ms?: number;
|
|
35
39
|
skill?: string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Per-model token/cost breakdown, as computed by the backend SDK itself. */
|
|
2
|
+
export type ModelUsageBreakdown = {
|
|
3
|
+
inputTokens: number;
|
|
4
|
+
outputTokens: number;
|
|
5
|
+
cacheReadInputTokens: number;
|
|
6
|
+
cacheCreationInputTokens: number;
|
|
7
|
+
costUsd: number;
|
|
8
|
+
};
|
|
9
|
+
/** Accumulated LLM usage/cost for one stage attempt, across every turn/call it made. */
|
|
10
|
+
export type StageUsage = {
|
|
11
|
+
costUsd: number;
|
|
12
|
+
models: Record<string, ModelUsageBreakdown>;
|
|
13
|
+
};
|
|
14
|
+
/** Merge one model's usage from a single call into an accumulator, in place. */
|
|
15
|
+
export declare function addModelUsage(usage: StageUsage, model: string, delta: Partial<ModelUsageBreakdown>): void;
|
|
16
|
+
export declare function emptyStageUsage(): StageUsage;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
function emptyBreakdown() {
|
|
2
|
+
return {
|
|
3
|
+
inputTokens: 0,
|
|
4
|
+
outputTokens: 0,
|
|
5
|
+
cacheReadInputTokens: 0,
|
|
6
|
+
cacheCreationInputTokens: 0,
|
|
7
|
+
costUsd: 0,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/** Merge one model's usage from a single call into an accumulator, in place. */
|
|
11
|
+
export function addModelUsage(usage, model, delta) {
|
|
12
|
+
const current = usage.models[model] ?? emptyBreakdown();
|
|
13
|
+
usage.models[model] = {
|
|
14
|
+
inputTokens: current.inputTokens + (delta.inputTokens ?? 0),
|
|
15
|
+
outputTokens: current.outputTokens + (delta.outputTokens ?? 0),
|
|
16
|
+
cacheReadInputTokens: current.cacheReadInputTokens + (delta.cacheReadInputTokens ?? 0),
|
|
17
|
+
cacheCreationInputTokens: current.cacheCreationInputTokens + (delta.cacheCreationInputTokens ?? 0),
|
|
18
|
+
costUsd: current.costUsd + (delta.costUsd ?? 0),
|
|
19
|
+
};
|
|
20
|
+
usage.costUsd += delta.costUsd ?? 0;
|
|
21
|
+
}
|
|
22
|
+
export function emptyStageUsage() {
|
|
23
|
+
return { costUsd: 0, models: {} };
|
|
24
|
+
}
|