stageflow 0.13.0 → 0.16.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/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/piAdapter.d.ts +2 -0
- package/dist/agent/piAdapter.js +34 -7
- package/dist/agent/port.d.ts +5 -2
- package/dist/cli/initTemplates.d.ts +1 -1
- package/dist/cli/initTemplates.js +1 -1
- package/dist/cli/migrateYamlCommand.d.ts +18 -0
- package/dist/cli/migrateYamlCommand.js +180 -0
- package/dist/cli/runOutput.js +58 -26
- package/dist/cli/validateCommand.d.ts +1 -0
- package/dist/cli/validateCommand.js +2 -0
- package/dist/cli.js +15 -1
- package/dist/config/browseCatalog.js +1 -1
- package/dist/config/createPipeline.d.ts +1 -1
- package/dist/config/createPipeline.js +23 -11
- package/dist/config/createStage.d.ts +1 -1
- package/dist/config/createStage.js +10 -5
- package/dist/config/legacyYaml.d.ts +28 -0
- package/dist/config/legacyYaml.js +98 -0
- package/dist/config/loadOutcome.d.ts +2 -1
- package/dist/config/loadOutcome.js +4 -1
- package/dist/config/loadPipeline.d.ts +2 -0
- package/dist/config/loadPipeline.js +150 -8
- package/dist/config/loadStage.d.ts +8 -1
- package/dist/config/loadStage.js +138 -29
- package/dist/config/loadStageflowManifest.js +10 -0
- package/dist/config/loadTask.js +14 -0
- package/dist/config/materializeStageModels.d.ts +7 -0
- package/dist/config/materializeStageModels.js +35 -0
- package/dist/config/mergePipelineIncludes.d.ts +5 -1
- package/dist/config/mergePipelineIncludes.js +82 -4
- package/dist/config/migrateYaml.d.ts +19 -0
- package/dist/config/migrateYaml.js +627 -0
- package/dist/config/modelField.d.ts +13 -0
- package/dist/config/modelField.js +16 -0
- package/dist/config/normalizePipelineStageEntry.js +41 -2
- package/dist/config/parseCompletionContract.d.ts +5 -2
- package/dist/config/parseCompletionContract.js +16 -3
- package/dist/config/parsePreEmitChecks.js +4 -0
- package/dist/config/pipelineNeeds.js +3 -3
- package/dist/config/pipelineStageKeys.d.ts +11 -0
- package/dist/config/pipelineStageKeys.js +14 -0
- package/dist/config/printTargetYaml.d.ts +28 -0
- package/dist/config/printTargetYaml.js +243 -0
- package/dist/config/resolveModel.d.ts +20 -0
- package/dist/config/resolveModel.js +35 -0
- package/dist/config/validateCatalog.d.ts +5 -1
- package/dist/config/validateCatalog.js +36 -6
- package/dist/config/validateCompletionContract.js +5 -0
- package/dist/config/yamlDialect.d.ts +29 -0
- package/dist/config/yamlDialect.js +327 -0
- package/dist/envelope/payloadSchema.d.ts +37 -5
- package/dist/envelope/payloadSchema.js +272 -22
- package/dist/mcp/catalogTools.js +1 -0
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/runstore/pipelineDagSnapshot.js +2 -0
- package/dist/runstore/port.d.ts +11 -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/stageSnapshot.js +11 -0
- package/dist/runtime/completionCheckRunner.d.ts +3 -2
- package/dist/runtime/completionCheckRunner.js +3 -2
- package/dist/runtime/envelopeRouting.d.ts +2 -2
- package/dist/runtime/pipelineRunner.d.ts +3 -0
- package/dist/runtime/pipelineRunner.js +23 -3
- package/dist/runtime/runManager.js +3 -2
- package/dist/runtime/stageAttemptBootstrap.d.ts +2 -2
- package/dist/runtime/stageRunner.d.ts +2 -2
- package/dist/runtime/stageRunner.js +2 -2
- package/dist/runtime/taskInput.d.ts +7 -0
- package/dist/runtime/taskInput.js +60 -0
- package/dist/runtime/verifiedStageExecution.js +3 -0
- package/dist/types/completion.d.ts +6 -0
- package/dist/types/pipeline.d.ts +21 -2
- package/dist/types/preEmitCheck.d.ts +4 -6
- package/dist/types/stage.d.ts +22 -3
- package/dist/types/stageflowManifest.d.ts +2 -0
- package/dist/types/task.d.ts +1 -0
- package/dist/types/usage.d.ts +16 -0
- package/dist/types/usage.js +24 -0
- package/dist/ui/assets/{index-uVHiae8s.css → index-D2Wt8hwH.css} +1 -1
- package/dist/ui/assets/index-XWJovnp7.js +118 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/skills/stageflow-author/SKILL.md +7 -7
- package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +9 -1
- package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +6 -2
- package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +9 -1
- package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +16 -1
- package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +12 -2
- package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +16 -1
- package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +11 -2
- package/skills/stageflow-author/assets/examples/linear-review/review.yaml +19 -1
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +9 -1
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +6 -2
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +16 -1
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +9 -1
- package/skills/stageflow-author/references/catalog-mapping.md +30 -28
- package/skills/stageflow-author/references/catalog-write-conventions.md +2 -2
- package/skills/stageflow-author/references/stage-prompt-template.md +5 -5
- package/skills/stageflow-session-capture/SKILL.md +1 -1
- package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +2 -1
- package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +13 -2
- package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +18 -2
- package/skills/stageflow-session-capture/references/catalog-authoring.md +2 -2
- package/dist/ui/assets/index-Yzh42vhf.js +0 -118
|
@@ -1,6 +1,49 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { Value } from "typebox/value";
|
|
3
3
|
import { EnvelopeError } from "../types/envelope.js";
|
|
4
|
+
export class UnresolvedSchemaRefError extends Error {
|
|
5
|
+
ref;
|
|
6
|
+
constructor(path, ref) {
|
|
7
|
+
super(`${path}: unresolved $ref "${ref}"`);
|
|
8
|
+
this.name = "UnresolvedSchemaRefError";
|
|
9
|
+
this.ref = ref;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const SCHEMA_REF_PREFIX = "#/schemas/";
|
|
13
|
+
function readSchemaRefName(ref, path) {
|
|
14
|
+
if (typeof ref !== "string" || !ref.startsWith(SCHEMA_REF_PREFIX)) {
|
|
15
|
+
throw new Error(`${path}: $ref must be a JSON Pointer of the form #/schemas/<name>`);
|
|
16
|
+
}
|
|
17
|
+
const name = ref.slice(SCHEMA_REF_PREFIX.length);
|
|
18
|
+
if (!name || name.includes("/")) {
|
|
19
|
+
throw new Error(`${path}: $ref must be a JSON Pointer of the form #/schemas/<name>`);
|
|
20
|
+
}
|
|
21
|
+
return name;
|
|
22
|
+
}
|
|
23
|
+
function derefSchemaNode(node, path, ctx) {
|
|
24
|
+
if (node === null || typeof node !== "object" || Array.isArray(node)) {
|
|
25
|
+
throw new Error(`${path}: schema must be an object`);
|
|
26
|
+
}
|
|
27
|
+
const schema = node;
|
|
28
|
+
if (schema.$ref === undefined) {
|
|
29
|
+
return { schema, stack: ctx.stack };
|
|
30
|
+
}
|
|
31
|
+
const name = readSchemaRefName(schema.$ref, path);
|
|
32
|
+
if (ctx.stack.includes(name)) {
|
|
33
|
+
const cycle = [...ctx.stack, name]
|
|
34
|
+
.map((item) => `${SCHEMA_REF_PREFIX}${item}`)
|
|
35
|
+
.join(" → ");
|
|
36
|
+
throw new Error(`${path}: $ref cycle involving ${cycle}`);
|
|
37
|
+
}
|
|
38
|
+
const target = ctx.schemas?.[name];
|
|
39
|
+
if (target === undefined) {
|
|
40
|
+
throw new UnresolvedSchemaRefError(path, `${SCHEMA_REF_PREFIX}${name}`);
|
|
41
|
+
}
|
|
42
|
+
return derefSchemaNode(target, path, {
|
|
43
|
+
schemas: ctx.schemas,
|
|
44
|
+
stack: [...ctx.stack, name],
|
|
45
|
+
});
|
|
46
|
+
}
|
|
4
47
|
function readEnum(schema, path, valueType) {
|
|
5
48
|
if (schema.enum === undefined) {
|
|
6
49
|
return undefined;
|
|
@@ -84,11 +127,9 @@ function stringOptions(schema, path) {
|
|
|
84
127
|
...(maxLength !== undefined ? { maxLength } : {}),
|
|
85
128
|
};
|
|
86
129
|
}
|
|
87
|
-
function compileNode(node, path) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
const schema = node;
|
|
130
|
+
function compileNode(node, path, ctx) {
|
|
131
|
+
const { schema, stack } = derefSchemaNode(node, path, ctx);
|
|
132
|
+
const childCtx = { schemas: ctx.schemas, stack };
|
|
92
133
|
if (typeof schema.type !== "string") {
|
|
93
134
|
throw new Error(`${path}: type is required`);
|
|
94
135
|
}
|
|
@@ -138,7 +179,7 @@ function compileNode(node, path) {
|
|
|
138
179
|
schema.minItems < 0)) {
|
|
139
180
|
throw new Error(`${path}: minItems must be a non-negative integer when present`);
|
|
140
181
|
}
|
|
141
|
-
const items = compileNode(schema.items, `${path}.items
|
|
182
|
+
const items = compileNode(schema.items, `${path}.items`, childCtx);
|
|
142
183
|
base =
|
|
143
184
|
schema.minItems !== undefined
|
|
144
185
|
? Type.Array(items, { minItems: schema.minItems })
|
|
@@ -164,7 +205,7 @@ function compileNode(node, path) {
|
|
|
164
205
|
}
|
|
165
206
|
const compiledProps = {};
|
|
166
207
|
for (const [key, value] of Object.entries(properties)) {
|
|
167
|
-
const propSchema = compileNode(value, `${path}.properties.${key}
|
|
208
|
+
const propSchema = compileNode(value, `${path}.properties.${key}`, childCtx);
|
|
168
209
|
compiledProps[key] = required.has(key)
|
|
169
210
|
? propSchema
|
|
170
211
|
: Type.Optional(propSchema);
|
|
@@ -181,28 +222,227 @@ function compileNode(node, path) {
|
|
|
181
222
|
return nullable ? Type.Union([base, Type.Null()]) : base;
|
|
182
223
|
}
|
|
183
224
|
/**
|
|
184
|
-
* Compile a JSON Schema subset
|
|
225
|
+
* Compile a JSON Schema subset for IR `payload_schema` / `clone_input_schema`
|
|
226
|
+
* (YAML: `io.output.schema` / `io.input.schema`).
|
|
185
227
|
* Supported: type object/string/number/integer/boolean/array,
|
|
186
228
|
* properties, required, items, additionalProperties (boolean),
|
|
187
229
|
* minItems, enum, minimum, maximum. String nodes also accept
|
|
188
230
|
* pattern (a JS RegExp validated with the unicode flag), minLength,
|
|
189
231
|
* and maxLength (non-negative integers). Nested nodes accept
|
|
190
232
|
* nullable (boolean), compiling to a union of that type with null;
|
|
191
|
-
* the root object cannot be nullable.
|
|
233
|
+
* the root object cannot be nullable. `$ref` is resolved against
|
|
234
|
+
* `options.schemas` using JSON Pointer `#/schemas/<name>`. Unknown
|
|
235
|
+
* keywords other than `$ref` are ignored.
|
|
192
236
|
*/
|
|
193
|
-
export function compilePayloadSchema(raw) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
raw.nullable === true) {
|
|
237
|
+
export function compilePayloadSchema(raw, options) {
|
|
238
|
+
const ctx = { schemas: options?.schemas, stack: [] };
|
|
239
|
+
const { schema: resolved } = derefSchemaNode(raw, "payload_schema", ctx);
|
|
240
|
+
if (resolved.nullable === true) {
|
|
198
241
|
throw new Error("payload_schema: root cannot be nullable");
|
|
199
242
|
}
|
|
200
|
-
const compiled = compileNode(raw, "payload_schema");
|
|
243
|
+
const compiled = compileNode(raw, "payload_schema", ctx);
|
|
201
244
|
if (!Type.IsObject(compiled)) {
|
|
202
245
|
throw new Error("payload_schema: root type must be object");
|
|
203
246
|
}
|
|
204
247
|
return compiled;
|
|
205
248
|
}
|
|
249
|
+
function isPlainSchemaObject(value) {
|
|
250
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
251
|
+
}
|
|
252
|
+
function readRequiredKeys(schema) {
|
|
253
|
+
if (schema.required === undefined)
|
|
254
|
+
return [];
|
|
255
|
+
if (!Array.isArray(schema.required) ||
|
|
256
|
+
!schema.required.every((key) => typeof key === "string")) {
|
|
257
|
+
return [];
|
|
258
|
+
}
|
|
259
|
+
return schema.required;
|
|
260
|
+
}
|
|
261
|
+
function readProperties(schema) {
|
|
262
|
+
if (!isPlainSchemaObject(schema.properties))
|
|
263
|
+
return {};
|
|
264
|
+
return schema.properties;
|
|
265
|
+
}
|
|
266
|
+
function typesCompatible(consumerType, producerType) {
|
|
267
|
+
if (consumerType === producerType)
|
|
268
|
+
return true;
|
|
269
|
+
return consumerType === "number" && producerType === "integer";
|
|
270
|
+
}
|
|
271
|
+
function readEnumValues(schema) {
|
|
272
|
+
if (!Array.isArray(schema.enum) || schema.enum.length === 0)
|
|
273
|
+
return undefined;
|
|
274
|
+
return schema.enum;
|
|
275
|
+
}
|
|
276
|
+
function enumCompatible(consumer, producer, type) {
|
|
277
|
+
if (type !== "string" && type !== "integer")
|
|
278
|
+
return true;
|
|
279
|
+
const consumerEnum = readEnumValues(consumer);
|
|
280
|
+
const producerEnum = readEnumValues(producer);
|
|
281
|
+
if (consumerEnum === undefined)
|
|
282
|
+
return true;
|
|
283
|
+
if (producerEnum === undefined)
|
|
284
|
+
return false;
|
|
285
|
+
const allowed = new Set(consumerEnum);
|
|
286
|
+
return producerEnum.every((value) => allowed.has(value));
|
|
287
|
+
}
|
|
288
|
+
function patternCompatible(consumer, producer) {
|
|
289
|
+
const consumerPattern = typeof consumer.pattern === "string" ? consumer.pattern : undefined;
|
|
290
|
+
const producerPattern = typeof producer.pattern === "string" ? producer.pattern : undefined;
|
|
291
|
+
if (consumerPattern === undefined)
|
|
292
|
+
return true;
|
|
293
|
+
if (producerPattern === undefined)
|
|
294
|
+
return false;
|
|
295
|
+
return consumerPattern === producerPattern;
|
|
296
|
+
}
|
|
297
|
+
function numericBound(value) {
|
|
298
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
299
|
+
return undefined;
|
|
300
|
+
return value;
|
|
301
|
+
}
|
|
302
|
+
function minBoundFits(consumerBound, producerBound) {
|
|
303
|
+
const consumer = numericBound(consumerBound);
|
|
304
|
+
if (consumer === undefined)
|
|
305
|
+
return true;
|
|
306
|
+
const producer = numericBound(producerBound);
|
|
307
|
+
if (producer === undefined)
|
|
308
|
+
return false;
|
|
309
|
+
return producer >= consumer;
|
|
310
|
+
}
|
|
311
|
+
function maxBoundFits(consumerBound, producerBound) {
|
|
312
|
+
const consumer = numericBound(consumerBound);
|
|
313
|
+
if (consumer === undefined)
|
|
314
|
+
return true;
|
|
315
|
+
const producer = numericBound(producerBound);
|
|
316
|
+
if (producer === undefined)
|
|
317
|
+
return false;
|
|
318
|
+
return producer <= consumer;
|
|
319
|
+
}
|
|
320
|
+
function isSubsetNode(consumer, producer, path, ctx) {
|
|
321
|
+
const consumerDeref = derefSchemaNode(consumer, path, ctx);
|
|
322
|
+
const producerDeref = derefSchemaNode(producer, path, ctx);
|
|
323
|
+
const consumerNode = consumerDeref.schema;
|
|
324
|
+
const producerNode = producerDeref.schema;
|
|
325
|
+
if (typeof consumerNode.type !== "string" || typeof producerNode.type !== "string") {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
if (!typesCompatible(consumerNode.type, producerNode.type)) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
if (producerNode.nullable === true && consumerNode.nullable !== true) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
if (consumerNode.type === "array") {
|
|
335
|
+
if (consumerNode.items === undefined || producerNode.items === undefined) {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
if (!isSubsetNode(consumerNode.items, producerNode.items, `${path}.items`, {
|
|
339
|
+
schemas: ctx.schemas,
|
|
340
|
+
stack: consumerDeref.stack,
|
|
341
|
+
})) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
return minBoundFits(consumerNode.minItems, producerNode.minItems);
|
|
345
|
+
}
|
|
346
|
+
if (consumerNode.type === "object") {
|
|
347
|
+
const consumerRequired = readRequiredKeys(consumerNode);
|
|
348
|
+
const producerRequired = new Set(readRequiredKeys(producerNode));
|
|
349
|
+
const producerProps = readProperties(producerNode);
|
|
350
|
+
const consumerProps = readProperties(consumerNode);
|
|
351
|
+
for (const key of consumerRequired) {
|
|
352
|
+
if (!(key in producerProps) || !producerRequired.has(key)) {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
for (const [key, consumerProp] of Object.entries(consumerProps)) {
|
|
357
|
+
if (producerProps[key] === undefined)
|
|
358
|
+
continue;
|
|
359
|
+
if (!isSubsetNode(consumerProp, producerProps[key], `${path}.properties.${key}`, {
|
|
360
|
+
schemas: ctx.schemas,
|
|
361
|
+
stack: consumerDeref.stack,
|
|
362
|
+
})) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (consumerNode.additionalProperties === false) {
|
|
367
|
+
if (producerNode.additionalProperties !== false) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
for (const key of Object.keys(producerProps)) {
|
|
371
|
+
if (!(key in consumerProps)) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
if (!enumCompatible(consumerNode, producerNode, consumerNode.type)) {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
if (consumerNode.type === "string") {
|
|
382
|
+
if (!patternCompatible(consumerNode, producerNode))
|
|
383
|
+
return false;
|
|
384
|
+
if (!minBoundFits(consumerNode.minLength, producerNode.minLength))
|
|
385
|
+
return false;
|
|
386
|
+
if (!maxBoundFits(consumerNode.maxLength, producerNode.maxLength))
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
if (consumerNode.type === "number" || consumerNode.type === "integer") {
|
|
390
|
+
if (!minBoundFits(consumerNode.minimum, producerNode.minimum))
|
|
391
|
+
return false;
|
|
392
|
+
if (!maxBoundFits(consumerNode.maximum, producerNode.maximum))
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* After `$ref` resolve, true when every success payload that satisfies the
|
|
399
|
+
* producer schema also satisfies the consumer schema (producer output
|
|
400
|
+
* assignable to consumer input). Extra producer fields are allowed unless
|
|
401
|
+
* the consumer sets `additionalProperties: false`, which also requires the
|
|
402
|
+
* producer to close extras. A number consumer accepts an integer producer;
|
|
403
|
+
* the reverse is incompatible. Producer enums must be subsets of consumer
|
|
404
|
+
* enums; a consumer enum with an unconstrained producer is incompatible.
|
|
405
|
+
* String `pattern` values must be identical when the consumer sets one.
|
|
406
|
+
* Producer `minimum`/`maximum`, `minLength`/`maxLength`, and array `minItems`
|
|
407
|
+
* must fit inside the consumer bounds (a consumer bound with an omitted
|
|
408
|
+
* producer bound is incompatible). A nullable producer is incompatible unless
|
|
409
|
+
* the consumer is also nullable.
|
|
410
|
+
*/
|
|
411
|
+
export function isPayloadSchemaSubset(consumer, producer, options) {
|
|
412
|
+
return isSubsetNode(consumer, producer, "payload_schema", {
|
|
413
|
+
schemas: options?.schemas,
|
|
414
|
+
stack: [],
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
export function expandPayloadSchemaRefs(raw, options) {
|
|
418
|
+
return expandSchemaNode(raw, "payload_schema", {
|
|
419
|
+
schemas: options?.schemas,
|
|
420
|
+
stack: [],
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
function expandSchemaNode(node, path, ctx) {
|
|
424
|
+
const { schema, stack } = derefSchemaNode(node, path, ctx);
|
|
425
|
+
const childCtx = { schemas: ctx.schemas, stack };
|
|
426
|
+
const clone = {};
|
|
427
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
428
|
+
if (key === "$ref")
|
|
429
|
+
continue;
|
|
430
|
+
if (key === "properties" && isPlainSchemaObject(value)) {
|
|
431
|
+
const props = {};
|
|
432
|
+
for (const [pk, pv] of Object.entries(value)) {
|
|
433
|
+
props[pk] = expandSchemaNode(pv, `${path}.properties.${pk}`, childCtx);
|
|
434
|
+
}
|
|
435
|
+
clone.properties = props;
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
if (key === "items") {
|
|
439
|
+
clone.items = expandSchemaNode(value, `${path}.items`, childCtx);
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
clone[key] = value;
|
|
443
|
+
}
|
|
444
|
+
return clone;
|
|
445
|
+
}
|
|
206
446
|
function formatPayloadInstancePath(instancePath) {
|
|
207
447
|
const translated = instancePath.replace(/\//g, ".").replace(/^\./, "");
|
|
208
448
|
return translated ? `payload.${translated}` : "payload";
|
|
@@ -212,14 +452,20 @@ function payloadSchemaMismatchDetails(schema, payload) {
|
|
|
212
452
|
.map((err) => `${formatPayloadInstancePath(err.instancePath)} ${err.message}`)
|
|
213
453
|
.join("; ");
|
|
214
454
|
}
|
|
215
|
-
export function
|
|
455
|
+
export function payloadInstanceMismatch(instance, payloadSchema, options) {
|
|
456
|
+
const schema = compilePayloadSchema(payloadSchema, options);
|
|
457
|
+
if (Value.Check(schema, instance))
|
|
458
|
+
return undefined;
|
|
459
|
+
return payloadSchemaMismatchDetails(schema, instance);
|
|
460
|
+
}
|
|
461
|
+
export function assertEnvelopePayload(envelope, payloadSchema, options) {
|
|
216
462
|
if (payloadSchema === undefined) {
|
|
217
463
|
return;
|
|
218
464
|
}
|
|
219
465
|
if (envelope.status !== "success") {
|
|
220
466
|
return;
|
|
221
467
|
}
|
|
222
|
-
const schema = compilePayloadSchema(payloadSchema);
|
|
468
|
+
const schema = compilePayloadSchema(payloadSchema, options);
|
|
223
469
|
if (envelope.payload === undefined) {
|
|
224
470
|
throw new EnvelopeError("payload is required by stage payload_schema");
|
|
225
471
|
}
|
|
@@ -230,19 +476,23 @@ export function assertEnvelopePayload(envelope, payloadSchema) {
|
|
|
230
476
|
: "payload does not match payload_schema");
|
|
231
477
|
}
|
|
232
478
|
}
|
|
233
|
-
|
|
479
|
+
/**
|
|
480
|
+
* Validate clone/fan-out assignment against the successor's IR
|
|
481
|
+
* `clone_input_schema` (YAML: `io.input.schema`).
|
|
482
|
+
*/
|
|
483
|
+
export function assertCloneAssignmentPayload(envelope, cloneInputSchema, successorId, itemPath = "", options) {
|
|
234
484
|
if (envelope.status !== "success") {
|
|
235
485
|
return;
|
|
236
486
|
}
|
|
237
|
-
const schema = compilePayloadSchema(cloneInputSchema);
|
|
487
|
+
const schema = compilePayloadSchema(cloneInputSchema, options);
|
|
238
488
|
const pathLead = itemPath ? `${itemPath}: ` : "";
|
|
239
489
|
if (envelope.payload === undefined) {
|
|
240
|
-
throw new EnvelopeError(`${pathLead}clone assignment payload is required by
|
|
490
|
+
throw new EnvelopeError(`${pathLead}clone assignment payload is required by io.input.schema for ${successorId}`);
|
|
241
491
|
}
|
|
242
492
|
if (!Value.Check(schema, envelope.payload)) {
|
|
243
493
|
const details = payloadSchemaMismatchDetails(schema, envelope.payload);
|
|
244
494
|
throw new EnvelopeError(details
|
|
245
|
-
? `${pathLead}clone assignment payload does not match
|
|
246
|
-
: `${pathLead}clone assignment payload does not match
|
|
495
|
+
? `${pathLead}clone assignment payload does not match io.input.schema for ${successorId}: ${details}`
|
|
496
|
+
: `${pathLead}clone assignment payload does not match io.input.schema for ${successorId}`);
|
|
247
497
|
}
|
|
248
498
|
}
|
package/dist/mcp/catalogTools.js
CHANGED
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.16.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.16.0";
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { hydrateResolvedNeeds } from "../config/pipelineNeeds.js";
|
|
2
2
|
import { mintCloneInstanceIds } from "./stageInstanceId.js";
|
|
3
3
|
export function buildPipelineDagSnapshotFromLoaded(loaded) {
|
|
4
|
+
// Snapshot JSON keeps IR keys (`completion`, `recovery`, `clone_input_schema`).
|
|
5
|
+
// Do not rewrite these from target YAML names. Resume hydrates this shape.
|
|
4
6
|
const gate_kinds = {};
|
|
5
7
|
const clone_input_schema = {};
|
|
6
8
|
for (const stage of loaded.stages) {
|
package/dist/runstore/port.d.ts
CHANGED
|
@@ -4,10 +4,12 @@ import type { StageEnvelope } from "../types/envelope.js";
|
|
|
4
4
|
import type { FeedbackLoopConfig, ResolvedPipelineDag } from "../types/pipeline.js";
|
|
5
5
|
import type { CompletionCheck } from "../types/completion.js";
|
|
6
6
|
import type { StageGateKind } from "../types/stage.js";
|
|
7
|
+
import type { StageUsage } from "../types/usage.js";
|
|
7
8
|
export type RunStatus = "created" | "running" | "succeeded" | "failed";
|
|
8
9
|
export type RunPipelineDagSnapshot = ResolvedPipelineDag & {
|
|
9
10
|
stage_ids: string[];
|
|
10
11
|
gate_kinds?: Record<string, StageGateKind[]>;
|
|
12
|
+
/** IR: per-stage clone assignment schema. YAML: `io.input.schema`. */
|
|
11
13
|
clone_input_schema?: Record<string, unknown>;
|
|
12
14
|
};
|
|
13
15
|
export type StageReadiness = "blocked" | "ready" | "running" | "waiting" | "succeeded" | "failed" | "skipped";
|
|
@@ -69,6 +71,8 @@ export type StageSnapshot = {
|
|
|
69
71
|
last_at?: string;
|
|
70
72
|
pending_prompt?: AskOperatorPrompt;
|
|
71
73
|
attempt_count: number;
|
|
74
|
+
/** Total $ spent on this stage across every attempt; omitted when no attempt reported usage. */
|
|
75
|
+
cost_usd?: number;
|
|
72
76
|
};
|
|
73
77
|
export type StageExecution = {
|
|
74
78
|
run_id: string;
|
|
@@ -79,6 +83,8 @@ export type StageExecution = {
|
|
|
79
83
|
started_at?: string;
|
|
80
84
|
finished_at?: string;
|
|
81
85
|
envelope: StageEnvelope | null;
|
|
86
|
+
cost_usd?: number;
|
|
87
|
+
usage?: StageUsage;
|
|
82
88
|
};
|
|
83
89
|
export type StageExecutionPatch = {
|
|
84
90
|
status?: StageSnapshot["status"];
|
|
@@ -86,6 +92,8 @@ export type StageExecutionPatch = {
|
|
|
86
92
|
started_at?: string;
|
|
87
93
|
finished_at?: string;
|
|
88
94
|
envelope?: StageEnvelope | null;
|
|
95
|
+
cost_usd?: number;
|
|
96
|
+
usage?: StageUsage;
|
|
89
97
|
};
|
|
90
98
|
/** The durable disposition of completion verification for one stage attempt. */
|
|
91
99
|
export type VerificationOutcome = "not_run" | "passed" | "failed" | "error";
|
|
@@ -219,6 +227,7 @@ export type CompactStage = {
|
|
|
219
227
|
status: StageSnapshot["status"];
|
|
220
228
|
attempt_count: number;
|
|
221
229
|
definition_id?: string;
|
|
230
|
+
cost_usd?: number;
|
|
222
231
|
};
|
|
223
232
|
export type RunSummary = {
|
|
224
233
|
run_id: string;
|
|
@@ -245,6 +254,8 @@ export type RunSummary = {
|
|
|
245
254
|
failed_reason?: string;
|
|
246
255
|
/** The active loop, when a run is currently replaying or awaiting a decision. */
|
|
247
256
|
active_feedback_loop?: FeedbackLoopRecord;
|
|
257
|
+
/** Sum of every stage's cost_usd; omitted when no stage reported usage. */
|
|
258
|
+
total_cost_usd?: number;
|
|
248
259
|
};
|
|
249
260
|
export type RunDetail = Omit<RunSummary, "stages"> & {
|
|
250
261
|
task_yaml: string;
|
|
@@ -26,8 +26,15 @@ function compactStages(stages) {
|
|
|
26
26
|
status: s.status,
|
|
27
27
|
attempt_count: s.attempt_count,
|
|
28
28
|
...(s.definition_id !== undefined ? { definition_id: s.definition_id } : {}),
|
|
29
|
+
...(s.cost_usd !== undefined ? { cost_usd: s.cost_usd } : {}),
|
|
29
30
|
}));
|
|
30
31
|
}
|
|
32
|
+
function totalCostUsd(stages) {
|
|
33
|
+
const costs = stages.map((s) => s.cost_usd).filter((c) => c !== undefined);
|
|
34
|
+
if (costs.length === 0)
|
|
35
|
+
return undefined;
|
|
36
|
+
return costs.reduce((sum, c) => sum + c, 0);
|
|
37
|
+
}
|
|
31
38
|
function waitingSummaryFromPrompt(prompt) {
|
|
32
39
|
if (!prompt)
|
|
33
40
|
return "Waiting for input";
|
|
@@ -89,6 +96,7 @@ function resolveListedStatus(stages, meta, dag) {
|
|
|
89
96
|
export function projectRunSummary(meta, stages, feedback) {
|
|
90
97
|
const dag = meta.pipeline_dag;
|
|
91
98
|
const waiting = waitingFieldsFromStages(stages);
|
|
99
|
+
const cost = totalCostUsd(stages);
|
|
92
100
|
if (feedback?.active_feedback_loop?.state === "waiting_for_human" &&
|
|
93
101
|
waiting.waiting_stage_id !== undefined &&
|
|
94
102
|
waiting.waiting_kind === undefined) {
|
|
@@ -114,6 +122,7 @@ export function projectRunSummary(meta, stages, feedback) {
|
|
|
114
122
|
...(feedback?.active_feedback_loop !== undefined
|
|
115
123
|
? { active_feedback_loop: feedback.active_feedback_loop }
|
|
116
124
|
: {}),
|
|
125
|
+
...(cost !== undefined ? { total_cost_usd: cost } : {}),
|
|
117
126
|
};
|
|
118
127
|
}
|
|
119
128
|
export function projectRunDetail(meta, stages, task_yaml, dagSnapshot, feedback) {
|
|
@@ -78,6 +78,18 @@ function ensureStageExecutionVerificationOutcomeColumn(db) {
|
|
|
78
78
|
db.exec(`ALTER TABLE stage_executions ADD COLUMN verification_outcome TEXT NOT NULL DEFAULT 'not_run'`);
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
+
function ensureStageExecutionCostColumns(db) {
|
|
82
|
+
const cols = db
|
|
83
|
+
.prepare(`PRAGMA table_info(stage_executions)`)
|
|
84
|
+
.all();
|
|
85
|
+
const names = new Set(cols.map((c) => c.name));
|
|
86
|
+
if (!names.has("cost_usd")) {
|
|
87
|
+
db.exec(`ALTER TABLE stage_executions ADD COLUMN cost_usd REAL`);
|
|
88
|
+
}
|
|
89
|
+
if (!names.has("usage_json")) {
|
|
90
|
+
db.exec(`ALTER TABLE stage_executions ADD COLUMN usage_json TEXT`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
81
93
|
function backfillVerificationOutcomes(db) {
|
|
82
94
|
db.exec(`
|
|
83
95
|
UPDATE stage_executions
|
|
@@ -184,6 +196,8 @@ function executionFromRow(row) {
|
|
|
184
196
|
envelope: row.envelope_json
|
|
185
197
|
? JSON.parse(row.envelope_json)
|
|
186
198
|
: null,
|
|
199
|
+
...(row.cost_usd != null ? { cost_usd: row.cost_usd } : {}),
|
|
200
|
+
...(row.usage_json != null ? { usage: JSON.parse(row.usage_json) } : {}),
|
|
187
201
|
};
|
|
188
202
|
}
|
|
189
203
|
function verificationCheckResultFromRow(row) {
|
|
@@ -292,6 +306,7 @@ export class SqliteRunStore {
|
|
|
292
306
|
ensureRunLocatorColumns(this.db);
|
|
293
307
|
ensureStageExecutionsTable(this.db);
|
|
294
308
|
ensureStageExecutionVerificationOutcomeColumn(this.db);
|
|
309
|
+
ensureStageExecutionCostColumns(this.db);
|
|
295
310
|
ensureStageEventsAttemptColumn(this.db);
|
|
296
311
|
ensureVerificationCheckResultsTable(this.db);
|
|
297
312
|
ensureFeedbackReplayStagePassEnvelopeColumn(this.db);
|
|
@@ -432,7 +447,7 @@ export class SqliteRunStore {
|
|
|
432
447
|
verification_outcome: "not_run",
|
|
433
448
|
});
|
|
434
449
|
const row = this.db
|
|
435
|
-
.prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json
|
|
450
|
+
.prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json, cost_usd, usage_json
|
|
436
451
|
FROM stage_executions
|
|
437
452
|
WHERE run_id = ? AND stage_id = ? AND attempt = ?`)
|
|
438
453
|
.get(runId, stageId, attempt);
|
|
@@ -442,7 +457,7 @@ export class SqliteRunStore {
|
|
|
442
457
|
async listStageExecutions(runId, stageId) {
|
|
443
458
|
await this.ready();
|
|
444
459
|
const rows = this.db
|
|
445
|
-
.prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json
|
|
460
|
+
.prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json, cost_usd, usage_json
|
|
446
461
|
FROM stage_executions
|
|
447
462
|
WHERE run_id = ? AND stage_id = ?
|
|
448
463
|
ORDER BY attempt ASC`)
|
|
@@ -452,7 +467,7 @@ export class SqliteRunStore {
|
|
|
452
467
|
async getLatestStageExecution(runId, stageId) {
|
|
453
468
|
await this.ready();
|
|
454
469
|
const row = this.db
|
|
455
|
-
.prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json
|
|
470
|
+
.prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json, cost_usd, usage_json
|
|
456
471
|
FROM stage_executions
|
|
457
472
|
WHERE run_id = ? AND stage_id = ?
|
|
458
473
|
ORDER BY attempt DESC
|
|
@@ -471,7 +486,7 @@ export class SqliteRunStore {
|
|
|
471
486
|
async getStageExecution(runId, stageId, attempt) {
|
|
472
487
|
await this.ready();
|
|
473
488
|
const row = this.db
|
|
474
|
-
.prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json
|
|
489
|
+
.prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json, cost_usd, usage_json
|
|
475
490
|
FROM stage_executions
|
|
476
491
|
WHERE run_id = ? AND stage_id = ? AND attempt = ?`)
|
|
477
492
|
.get(runId, stageId, attempt);
|
|
@@ -509,6 +524,14 @@ export class SqliteRunStore {
|
|
|
509
524
|
params.envelope_json =
|
|
510
525
|
patch.envelope != null ? JSON.stringify(patch.envelope) : null;
|
|
511
526
|
}
|
|
527
|
+
if (patch.cost_usd !== undefined) {
|
|
528
|
+
sets.push("cost_usd = @cost_usd");
|
|
529
|
+
params.cost_usd = patch.cost_usd;
|
|
530
|
+
}
|
|
531
|
+
if (patch.usage !== undefined) {
|
|
532
|
+
sets.push("usage_json = @usage_json");
|
|
533
|
+
params.usage_json = JSON.stringify(patch.usage);
|
|
534
|
+
}
|
|
512
535
|
if (sets.length === 0)
|
|
513
536
|
return;
|
|
514
537
|
const result = this.db
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS runs (\n run_id TEXT PRIMARY KEY,\n pipeline_id TEXT NOT NULL,\n task_id TEXT,\n task_yaml TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n checkout_root TEXT,\n pipeline_dag_json TEXT,\n git_sha TEXT,\n ci_pr_url TEXT,\n ci_job_url TEXT,\n pipeline_path TEXT,\n task_path TEXT,\n project_root TEXT\n);\n\nCREATE TABLE IF NOT EXISTS stages (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n status TEXT,\n summary TEXT,\n envelope_json TEXT,\n started_at TEXT,\n finished_at TEXT,\n PRIMARY KEY (run_id, stage_id),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE TABLE IF NOT EXISTS stage_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n at TEXT NOT NULL,\n event TEXT NOT NULL,\n payload_json TEXT,\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_runs_status_created\n ON runs (status, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_runs_pipeline_created\n ON runs (pipeline_id, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_stage_events_run_stage_at\n ON stage_events (run_id, stage_id, at);\n\nCREATE TABLE IF NOT EXISTS stage_executions (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n attempt INTEGER NOT NULL,\n status TEXT NOT NULL,\n verification_outcome TEXT NOT NULL DEFAULT 'not_run',\n started_at TEXT,\n finished_at TEXT,\n envelope_json TEXT,\n PRIMARY KEY (run_id, stage_id, attempt),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_stage_executions_run_stage\n ON stage_executions (run_id, stage_id, attempt);\n\nCREATE TABLE IF NOT EXISTS verification_check_results (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n attempt INTEGER NOT NULL,\n check_id TEXT NOT NULL,\n check_type TEXT NOT NULL,\n status TEXT NOT NULL,\n started_at TEXT,\n finished_at TEXT,\n evidence_json TEXT,\n PRIMARY KEY (run_id, stage_id, attempt, check_id),\n FOREIGN KEY (run_id, stage_id, attempt)\n REFERENCES stage_executions(run_id, stage_id, attempt)\n);\n\nCREATE INDEX IF NOT EXISTS idx_verification_check_results_execution\n ON verification_check_results (run_id, stage_id, attempt, check_id);\n\nCREATE TABLE IF NOT EXISTS feedback_loops (\n run_id TEXT NOT NULL,\n loop_id TEXT NOT NULL,\n source_stage_id TEXT NOT NULL,\n source_attempt INTEGER NOT NULL,\n policy_json TEXT NOT NULL,\n state TEXT NOT NULL,\n current_replay_id TEXT,\n current_replay_number INTEGER,\n deferred_send_back_json TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, loop_id),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_feedback_loops_run_state\n ON feedback_loops (run_id, state, created_at);\n\nCREATE TABLE IF NOT EXISTS feedback_replays (\n run_id TEXT NOT NULL,\n replay_id TEXT NOT NULL,\n loop_id TEXT NOT NULL,\n source_stage_id TEXT NOT NULL,\n source_attempt INTEGER NOT NULL,\n target_stage_id TEXT NOT NULL,\n replay_number INTEGER NOT NULL,\n max_replays INTEGER NOT NULL,\n replay_session TEXT NOT NULL,\n route_stage_ids_json TEXT NOT NULL,\n feedback_envelope_json TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, replay_id),\n FOREIGN KEY (run_id, loop_id) REFERENCES feedback_loops(run_id, loop_id)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_feedback_replays_loop_number\n ON feedback_replays (run_id, loop_id, replay_number);\nCREATE INDEX IF NOT EXISTS idx_feedback_replays_run_loop\n ON feedback_replays (run_id, loop_id, created_at);\n\nCREATE TABLE IF NOT EXISTS feedback_replay_stage_passes (\n run_id TEXT NOT NULL,\n replay_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n stage_attempt INTEGER NOT NULL,\n session_origin_attempt INTEGER,\n session_mode TEXT NOT NULL,\n status TEXT NOT NULL,\n started_at TEXT,\n finished_at TEXT,\n emitted_envelope_json TEXT,\n PRIMARY KEY (run_id, replay_id, stage_id),\n FOREIGN KEY (run_id, replay_id) REFERENCES feedback_replays(run_id, replay_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_feedback_replay_stage_passes_replay\n ON feedback_replay_stage_passes (run_id, replay_id, stage_id);\n\nCREATE TABLE IF NOT EXISTS fork_generations (\n run_id TEXT NOT NULL,\n generation_id TEXT NOT NULL,\n replay_id TEXT,\n fork_parent_stage_id TEXT NOT NULL,\n generation_number INTEGER NOT NULL,\n clone_stage_ids_json TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, generation_id),\n FOREIGN KEY (run_id, replay_id) REFERENCES feedback_replays(run_id, replay_id)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_fork_generations_parent_number\n ON fork_generations (run_id, fork_parent_stage_id, generation_number);\nCREATE INDEX IF NOT EXISTS idx_fork_generations_run_replay\n ON fork_generations (run_id, replay_id, created_at);\n";
|
|
1
|
+
export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS runs (\n run_id TEXT PRIMARY KEY,\n pipeline_id TEXT NOT NULL,\n task_id TEXT,\n task_yaml TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n checkout_root TEXT,\n pipeline_dag_json TEXT,\n git_sha TEXT,\n ci_pr_url TEXT,\n ci_job_url TEXT,\n pipeline_path TEXT,\n task_path TEXT,\n project_root TEXT\n);\n\nCREATE TABLE IF NOT EXISTS stages (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n status TEXT,\n summary TEXT,\n envelope_json TEXT,\n started_at TEXT,\n finished_at TEXT,\n PRIMARY KEY (run_id, stage_id),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE TABLE IF NOT EXISTS stage_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n at TEXT NOT NULL,\n event TEXT NOT NULL,\n payload_json TEXT,\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_runs_status_created\n ON runs (status, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_runs_pipeline_created\n ON runs (pipeline_id, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_stage_events_run_stage_at\n ON stage_events (run_id, stage_id, at);\n\nCREATE TABLE IF NOT EXISTS stage_executions (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n attempt INTEGER NOT NULL,\n status TEXT NOT NULL,\n verification_outcome TEXT NOT NULL DEFAULT 'not_run',\n started_at TEXT,\n finished_at TEXT,\n envelope_json TEXT,\n cost_usd REAL,\n usage_json TEXT,\n PRIMARY KEY (run_id, stage_id, attempt),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_stage_executions_run_stage\n ON stage_executions (run_id, stage_id, attempt);\n\nCREATE TABLE IF NOT EXISTS verification_check_results (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n attempt INTEGER NOT NULL,\n check_id TEXT NOT NULL,\n check_type TEXT NOT NULL,\n status TEXT NOT NULL,\n started_at TEXT,\n finished_at TEXT,\n evidence_json TEXT,\n PRIMARY KEY (run_id, stage_id, attempt, check_id),\n FOREIGN KEY (run_id, stage_id, attempt)\n REFERENCES stage_executions(run_id, stage_id, attempt)\n);\n\nCREATE INDEX IF NOT EXISTS idx_verification_check_results_execution\n ON verification_check_results (run_id, stage_id, attempt, check_id);\n\nCREATE TABLE IF NOT EXISTS feedback_loops (\n run_id TEXT NOT NULL,\n loop_id TEXT NOT NULL,\n source_stage_id TEXT NOT NULL,\n source_attempt INTEGER NOT NULL,\n policy_json TEXT NOT NULL,\n state TEXT NOT NULL,\n current_replay_id TEXT,\n current_replay_number INTEGER,\n deferred_send_back_json TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, loop_id),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_feedback_loops_run_state\n ON feedback_loops (run_id, state, created_at);\n\nCREATE TABLE IF NOT EXISTS feedback_replays (\n run_id TEXT NOT NULL,\n replay_id TEXT NOT NULL,\n loop_id TEXT NOT NULL,\n source_stage_id TEXT NOT NULL,\n source_attempt INTEGER NOT NULL,\n target_stage_id TEXT NOT NULL,\n replay_number INTEGER NOT NULL,\n max_replays INTEGER NOT NULL,\n replay_session TEXT NOT NULL,\n route_stage_ids_json TEXT NOT NULL,\n feedback_envelope_json TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, replay_id),\n FOREIGN KEY (run_id, loop_id) REFERENCES feedback_loops(run_id, loop_id)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_feedback_replays_loop_number\n ON feedback_replays (run_id, loop_id, replay_number);\nCREATE INDEX IF NOT EXISTS idx_feedback_replays_run_loop\n ON feedback_replays (run_id, loop_id, created_at);\n\nCREATE TABLE IF NOT EXISTS feedback_replay_stage_passes (\n run_id TEXT NOT NULL,\n replay_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n stage_attempt INTEGER NOT NULL,\n session_origin_attempt INTEGER,\n session_mode TEXT NOT NULL,\n status TEXT NOT NULL,\n started_at TEXT,\n finished_at TEXT,\n emitted_envelope_json TEXT,\n PRIMARY KEY (run_id, replay_id, stage_id),\n FOREIGN KEY (run_id, replay_id) REFERENCES feedback_replays(run_id, replay_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_feedback_replay_stage_passes_replay\n ON feedback_replay_stage_passes (run_id, replay_id, stage_id);\n\nCREATE TABLE IF NOT EXISTS fork_generations (\n run_id TEXT NOT NULL,\n generation_id TEXT NOT NULL,\n replay_id TEXT,\n fork_parent_stage_id TEXT NOT NULL,\n generation_number INTEGER NOT NULL,\n clone_stage_ids_json TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, generation_id),\n FOREIGN KEY (run_id, replay_id) REFERENCES feedback_replays(run_id, replay_id)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_fork_generations_parent_number\n ON fork_generations (run_id, fork_parent_stage_id, generation_number);\nCREATE INDEX IF NOT EXISTS idx_fork_generations_run_replay\n ON fork_generations (run_id, replay_id, created_at);\n";
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { StageEnvelope } from "../types/envelope.js";
|
|
2
|
+
import type { StageUsage } from "../types/usage.js";
|
|
2
3
|
import type { StageLogEvent, StageSnapshot } from "./port.js";
|
|
3
4
|
export type StageExecutionPatch = {
|
|
4
5
|
status?: StageSnapshot["status"];
|
|
5
6
|
started_at?: string;
|
|
6
7
|
finished_at?: string;
|
|
7
8
|
envelope?: StageEnvelope | null;
|
|
9
|
+
cost_usd?: number;
|
|
10
|
+
usage?: StageUsage;
|
|
8
11
|
};
|
|
9
12
|
export declare function executionStatusFromEvents(events: StageLogEvent[]): StageSnapshot["status"];
|
|
10
13
|
export declare function deriveExecutionPatchFromEvent(event: StageLogEvent, current: Pick<StageSnapshot, "status"> & {
|
|
@@ -14,9 +14,13 @@ export function deriveExecutionPatchFromEvent(event, current) {
|
|
|
14
14
|
else if (eventName === "waiting_for_input") {
|
|
15
15
|
patch.status = "waiting_for_input";
|
|
16
16
|
}
|
|
17
|
-
else if (
|
|
18
|
-
patch.status =
|
|
17
|
+
else if (event.event === "succeeded" || event.event === "failed") {
|
|
18
|
+
patch.status = event.event;
|
|
19
19
|
patch.finished_at = at;
|
|
20
|
+
if (event.usage) {
|
|
21
|
+
patch.cost_usd = event.usage.costUsd;
|
|
22
|
+
patch.usage = event.usage;
|
|
23
|
+
}
|
|
20
24
|
}
|
|
21
25
|
return patch;
|
|
22
26
|
}
|
|
@@ -18,11 +18,14 @@ export async function buildStageSnapshotFromStore(store, runId, stageId, workspa
|
|
|
18
18
|
let status;
|
|
19
19
|
let envelope;
|
|
20
20
|
let attempt_count;
|
|
21
|
+
let cost_usd;
|
|
21
22
|
if (latest !== null) {
|
|
22
23
|
events = await store.listStageEvents(runId, stageId, latest.attempt);
|
|
23
24
|
status = stageStatusFromEvents(events);
|
|
24
25
|
envelope = await tryReadEnvelope(store, runId, stageId, latest);
|
|
25
26
|
attempt_count = await store.countStageAttempts(runId, stageId);
|
|
27
|
+
const executions = await store.listStageExecutions(runId, stageId);
|
|
28
|
+
cost_usd = sumStageExecutionCost(executions);
|
|
26
29
|
}
|
|
27
30
|
else {
|
|
28
31
|
events = await store.listStageEvents(runId, stageId);
|
|
@@ -42,5 +45,13 @@ export async function buildStageSnapshotFromStore(store, runId, stageId, workspa
|
|
|
42
45
|
last_at,
|
|
43
46
|
attempt_count,
|
|
44
47
|
...(pending ? { pending_prompt: pending } : {}),
|
|
48
|
+
...(cost_usd !== undefined ? { cost_usd } : {}),
|
|
45
49
|
};
|
|
46
50
|
}
|
|
51
|
+
/** Sum cost across every attempt — retries are real spend too, so a stage's total isn't just its latest attempt. */
|
|
52
|
+
function sumStageExecutionCost(executions) {
|
|
53
|
+
const costs = executions.map((e) => e.cost_usd).filter((c) => c !== undefined);
|
|
54
|
+
if (costs.length === 0)
|
|
55
|
+
return undefined;
|
|
56
|
+
return costs.reduce((sum, c) => sum + c, 0);
|
|
57
|
+
}
|