@oisincoveney/pipeline 1.3.1 → 1.4.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/index.js +197 -11
- package/dist/mastra/config.d.ts +5 -0
- package/dist/mastra/runner.js +4 -1
- package/dist/pipeline-runtime.d.ts +6 -0
- package/docs/config-architecture.md +18 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -27948,8 +27948,14 @@ var filesystemSchema = exports_external.object({
|
|
|
27948
27948
|
var networkSchema = exports_external.object({
|
|
27949
27949
|
mode: exports_external.enum(NETWORK_MODES)
|
|
27950
27950
|
}).strict();
|
|
27951
|
+
var outputRepairSchema = exports_external.object({
|
|
27952
|
+
enabled: exports_external.boolean().optional(),
|
|
27953
|
+
max_attempts: exports_external.number().int().positive().optional(),
|
|
27954
|
+
runner: exports_external.string().optional()
|
|
27955
|
+
}).strict();
|
|
27951
27956
|
var outputSchema = exports_external.object({
|
|
27952
27957
|
format: exports_external.enum(OUTPUT_FORMATS),
|
|
27958
|
+
repair: outputRepairSchema.optional(),
|
|
27953
27959
|
schema_path: exports_external.string().min(1).optional()
|
|
27954
27960
|
}).strict();
|
|
27955
27961
|
var artifactSchema = exports_external.object({
|
|
@@ -28184,6 +28190,16 @@ function validateProfile(profileId, profile, runner, config2, issues, projectRoo
|
|
|
28184
28190
|
message: `profile '${profileId}' must declare output.schema_path for json_schema output`
|
|
28185
28191
|
});
|
|
28186
28192
|
}
|
|
28193
|
+
const repairRunnerId = profile.output?.repair?.runner;
|
|
28194
|
+
if (repairRunnerId && !config2.runners[repairRunnerId]) {
|
|
28195
|
+
issues.push({
|
|
28196
|
+
path: `profiles.${profileId}.output.repair.runner`,
|
|
28197
|
+
message: `profile '${profileId}' references missing repair runner '${repairRunnerId}'`
|
|
28198
|
+
});
|
|
28199
|
+
}
|
|
28200
|
+
if (repairRunnerId && config2.runners[repairRunnerId]) {
|
|
28201
|
+
validateListCapability(`profiles.${profileId}.output.repair.runner`, ["text"], config2.runners[repairRunnerId].capabilities.output_formats, "repair output format", issues);
|
|
28202
|
+
}
|
|
28187
28203
|
validatePath(`profiles.${profileId}.output.schema_path`, profile.output?.schema_path, projectRoot, issues);
|
|
28188
28204
|
}
|
|
28189
28205
|
function validateActor(label, path, actor, runner, config2, issues, projectRoot) {
|
|
@@ -35815,7 +35831,7 @@ function harnessArgv(harness, prompt, worktreePath, contextFile, options2 = {})
|
|
|
35815
35831
|
...mcpArgs,
|
|
35816
35832
|
...skillArgs,
|
|
35817
35833
|
"--sandbox",
|
|
35818
|
-
|
|
35834
|
+
codexSandboxFor(options2.actor),
|
|
35819
35835
|
"--config",
|
|
35820
35836
|
'approval_policy="never"',
|
|
35821
35837
|
"--skip-git-repo-check",
|
|
@@ -35864,6 +35880,9 @@ function harnessArgv(harness, prompt, worktreePath, contextFile, options2 = {})
|
|
|
35864
35880
|
}
|
|
35865
35881
|
}
|
|
35866
35882
|
}
|
|
35883
|
+
function codexSandboxFor(actor) {
|
|
35884
|
+
return actor?.filesystem?.mode === "read-only" ? "read-only" : "workspace-write";
|
|
35885
|
+
}
|
|
35867
35886
|
function createRunnerLaunchPlan(config2, input) {
|
|
35868
35887
|
const profile = input.profileId ? config2.profiles[input.profileId] : undefined;
|
|
35869
35888
|
if (input.profileId && !profile) {
|
|
@@ -36267,6 +36286,9 @@ profiles:
|
|
|
36267
36286
|
output:
|
|
36268
36287
|
format: json_schema
|
|
36269
36288
|
schema_path: .pipeline/schemas/research.schema.json
|
|
36289
|
+
repair:
|
|
36290
|
+
enabled: true
|
|
36291
|
+
max_attempts: 1
|
|
36270
36292
|
pipeline-inspector:
|
|
36271
36293
|
runner: codex
|
|
36272
36294
|
description: Inspect the repository without modifying files.
|
|
@@ -36327,6 +36349,9 @@ profiles:
|
|
|
36327
36349
|
output:
|
|
36328
36350
|
format: json_schema
|
|
36329
36351
|
schema_path: .pipeline/schemas/verify.schema.json
|
|
36352
|
+
repair:
|
|
36353
|
+
enabled: true
|
|
36354
|
+
max_attempts: 1
|
|
36330
36355
|
pipeline-learner:
|
|
36331
36356
|
runner: codex
|
|
36332
36357
|
description: Store durable lessons from the completed run.
|
|
@@ -36342,6 +36367,9 @@ profiles:
|
|
|
36342
36367
|
output:
|
|
36343
36368
|
format: json_schema
|
|
36344
36369
|
schema_path: .pipeline/schemas/learn.schema.json
|
|
36370
|
+
repair:
|
|
36371
|
+
enabled: true
|
|
36372
|
+
max_attempts: 1
|
|
36345
36373
|
`;
|
|
36346
36374
|
var RESEARCH_SCHEMA = JSON.stringify({
|
|
36347
36375
|
additionalProperties: false,
|
|
@@ -37052,7 +37080,7 @@ async function executeNode(node, context) {
|
|
|
37052
37080
|
emitNodeFinish(context, result2);
|
|
37053
37081
|
return result2;
|
|
37054
37082
|
}
|
|
37055
|
-
const evidence = failedGate
|
|
37083
|
+
const evidence = failedGate ? [...last.evidence, ...failedGate.evidence] : last.evidence.concat(`node exited with code ${last.exitCode}`);
|
|
37056
37084
|
if (attempt === maxAttempts) {
|
|
37057
37085
|
await dispatchHooks(context, "node.error", {
|
|
37058
37086
|
evidence,
|
|
@@ -37117,17 +37145,167 @@ async function executeAgentNode(node, context) {
|
|
|
37117
37145
|
context.agentInvocations.push(plan);
|
|
37118
37146
|
const result = await context.executor(plan);
|
|
37119
37147
|
const normalized = normalizeAgentOutput(plan, result.stdout);
|
|
37148
|
+
const finalized = await finalizeAgentOutput({
|
|
37149
|
+
context,
|
|
37150
|
+
node,
|
|
37151
|
+
normalized,
|
|
37152
|
+
result
|
|
37153
|
+
});
|
|
37120
37154
|
return {
|
|
37121
37155
|
evidence: [
|
|
37122
37156
|
`agent boundary node=${node.id} profile=${node.profile} runner=${plan.runnerId} strategy=${plan.strategy}`,
|
|
37123
|
-
...
|
|
37157
|
+
...finalized.evidence,
|
|
37124
37158
|
...result.stderr ? [`stderr: ${result.stderr}`] : [],
|
|
37125
37159
|
...result.timedOut ? ["agent timed out"] : []
|
|
37126
37160
|
],
|
|
37127
37161
|
exitCode: result.exitCode,
|
|
37128
|
-
output:
|
|
37162
|
+
output: finalized.output
|
|
37163
|
+
};
|
|
37164
|
+
}
|
|
37165
|
+
async function finalizeAgentOutput(inputs) {
|
|
37166
|
+
const { context, node, normalized, result } = inputs;
|
|
37167
|
+
const repairContext = outputRepairContext(context, node, normalized, result);
|
|
37168
|
+
if (!repairContext) {
|
|
37169
|
+
return normalized;
|
|
37170
|
+
}
|
|
37171
|
+
return await runOutputRepair(context, node, normalized, repairContext);
|
|
37172
|
+
}
|
|
37173
|
+
function outputRepairContext(context, node, normalized, result) {
|
|
37174
|
+
if (result.exitCode !== 0 || result.timedOut) {
|
|
37175
|
+
return null;
|
|
37176
|
+
}
|
|
37177
|
+
const profile = node.profile ? context.config.profiles[node.profile] : undefined;
|
|
37178
|
+
if (!profile) {
|
|
37179
|
+
return null;
|
|
37180
|
+
}
|
|
37181
|
+
const output = profile?.output;
|
|
37182
|
+
if (output?.format !== "json_schema" || !output.schema_path) {
|
|
37183
|
+
return null;
|
|
37184
|
+
}
|
|
37185
|
+
const firstValidation = validateJsonSchemaSource(normalized.output, output.schema_path, context.worktreePath);
|
|
37186
|
+
if (firstValidation.passed) {
|
|
37187
|
+
return null;
|
|
37188
|
+
}
|
|
37189
|
+
const repair = outputRepairOptions(output);
|
|
37190
|
+
if (!repair.enabled) {
|
|
37191
|
+
return null;
|
|
37192
|
+
}
|
|
37193
|
+
return {
|
|
37194
|
+
evidence: [
|
|
37195
|
+
...normalized.evidence,
|
|
37196
|
+
"output repair triggered",
|
|
37197
|
+
...firstValidation.evidence.map((item) => `original output: ${item}`)
|
|
37198
|
+
],
|
|
37199
|
+
maxAttempts: repair.maxAttempts,
|
|
37200
|
+
runner: repair.runner ?? profile.runner,
|
|
37201
|
+
schemaPath: output.schema_path,
|
|
37202
|
+
validation: firstValidation
|
|
37203
|
+
};
|
|
37204
|
+
}
|
|
37205
|
+
async function runOutputRepair(context, node, normalized, repairContext) {
|
|
37206
|
+
let latest = normalized;
|
|
37207
|
+
let latestValidation = repairContext.validation;
|
|
37208
|
+
const evidence = [...repairContext.evidence];
|
|
37209
|
+
for (let attempt = 1;attempt <= repairContext.maxAttempts; attempt += 1) {
|
|
37210
|
+
const repairPlan = createOutputRepairPlan({
|
|
37211
|
+
context,
|
|
37212
|
+
node,
|
|
37213
|
+
originalOutput: latest.output,
|
|
37214
|
+
repairRunner: repairContext.runner,
|
|
37215
|
+
schemaPath: repairContext.schemaPath,
|
|
37216
|
+
validation: latestValidation
|
|
37217
|
+
});
|
|
37218
|
+
context.agentInvocations.push(repairPlan);
|
|
37219
|
+
const repairResult = await context.executor(repairPlan);
|
|
37220
|
+
const repaired = normalizeAgentOutput(repairPlan, repairResult.stdout);
|
|
37221
|
+
const repairedValidation = validateJsonSchemaSource(repaired.output, repairContext.schemaPath, context.worktreePath);
|
|
37222
|
+
latest = {
|
|
37223
|
+
evidence: [
|
|
37224
|
+
...repaired.evidence,
|
|
37225
|
+
...repairResult.stderr ? [`repair stderr: ${repairResult.stderr}`] : [],
|
|
37226
|
+
...repairResult.timedOut ? ["output repair timed out"] : []
|
|
37227
|
+
],
|
|
37228
|
+
output: repaired.output
|
|
37229
|
+
};
|
|
37230
|
+
latestValidation = repairedValidation;
|
|
37231
|
+
const passed = repairResult.exitCode === 0 && repairedValidation.passed;
|
|
37232
|
+
evidence.push(...repaired.evidence, passed ? `output repair passed for ${node.id} after attempt ${attempt}` : `output repair failed for ${node.id} after attempt ${attempt}`, ...repairedValidation.evidence.map((item) => `repaired output: ${item}`));
|
|
37233
|
+
emit(context, {
|
|
37234
|
+
attempt,
|
|
37235
|
+
nodeId: node.id,
|
|
37236
|
+
passed,
|
|
37237
|
+
type: "output.repair",
|
|
37238
|
+
...passed ? {} : { reason: repairedValidation.reason ?? "repair failed" }
|
|
37239
|
+
});
|
|
37240
|
+
if (passed) {
|
|
37241
|
+
return {
|
|
37242
|
+
evidence,
|
|
37243
|
+
output: repaired.output
|
|
37244
|
+
};
|
|
37245
|
+
}
|
|
37246
|
+
}
|
|
37247
|
+
return {
|
|
37248
|
+
evidence,
|
|
37249
|
+
output: latest.output
|
|
37129
37250
|
};
|
|
37130
37251
|
}
|
|
37252
|
+
function outputRepairOptions(output) {
|
|
37253
|
+
const repair = output.repair;
|
|
37254
|
+
return {
|
|
37255
|
+
enabled: repair?.enabled ?? true,
|
|
37256
|
+
maxAttempts: repair?.max_attempts ?? 1,
|
|
37257
|
+
...repair?.runner ? { runner: repair.runner } : {}
|
|
37258
|
+
};
|
|
37259
|
+
}
|
|
37260
|
+
function createOutputRepairPlan(inputs) {
|
|
37261
|
+
const {
|
|
37262
|
+
context,
|
|
37263
|
+
node,
|
|
37264
|
+
originalOutput,
|
|
37265
|
+
repairRunner,
|
|
37266
|
+
schemaPath,
|
|
37267
|
+
validation
|
|
37268
|
+
} = inputs;
|
|
37269
|
+
const schema = readFileSync7(join6(context.worktreePath, schemaPath), "utf8");
|
|
37270
|
+
const repairProfileId = `${node.id}:output-repair`;
|
|
37271
|
+
const repairConfig = {
|
|
37272
|
+
...context.config,
|
|
37273
|
+
profiles: {
|
|
37274
|
+
...context.config.profiles,
|
|
37275
|
+
[repairProfileId]: {
|
|
37276
|
+
filesystem: { mode: "read-only" },
|
|
37277
|
+
instructions: { inline: "Repair invalid structured output." },
|
|
37278
|
+
network: { mode: "disabled" },
|
|
37279
|
+
output: { format: "text" },
|
|
37280
|
+
runner: repairRunner,
|
|
37281
|
+
tools: []
|
|
37282
|
+
}
|
|
37283
|
+
}
|
|
37284
|
+
};
|
|
37285
|
+
const prompt = [
|
|
37286
|
+
"You are an output finalizer for a pipeline agent.",
|
|
37287
|
+
"Return only valid JSON matching the expected schema.",
|
|
37288
|
+
"Do not use Markdown fences or add prose outside the JSON value.",
|
|
37289
|
+
"Preserve facts from the original output. If required information is missing, use empty arrays or nulls only where the schema permits.",
|
|
37290
|
+
"",
|
|
37291
|
+
"Expected schema:",
|
|
37292
|
+
schema,
|
|
37293
|
+
"",
|
|
37294
|
+
"Validation error:",
|
|
37295
|
+
validation.evidence.join(`
|
|
37296
|
+
`),
|
|
37297
|
+
"",
|
|
37298
|
+
"Original output:",
|
|
37299
|
+
originalOutput
|
|
37300
|
+
].join(`
|
|
37301
|
+
`);
|
|
37302
|
+
return createRunnerLaunchPlan(repairConfig, {
|
|
37303
|
+
nodeId: repairProfileId,
|
|
37304
|
+
profileId: repairProfileId,
|
|
37305
|
+
prompt,
|
|
37306
|
+
worktreePath: context.worktreePath
|
|
37307
|
+
});
|
|
37308
|
+
}
|
|
37131
37309
|
function normalizeAgentOutput(plan, stdout) {
|
|
37132
37310
|
if (plan.type === "codex") {
|
|
37133
37311
|
const text = lastJsonLineValue(stdout, (value) => {
|
|
@@ -37442,24 +37620,29 @@ function evaluateJsonSchemaGate(gate, gateId, nodeId, context, attempt) {
|
|
|
37442
37620
|
reason: `missing JSON artifact '${gate.path ?? ""}'`
|
|
37443
37621
|
};
|
|
37444
37622
|
}
|
|
37623
|
+
const result = validateJsonSchemaSource(source, schemaPath, context.worktreePath);
|
|
37624
|
+
return {
|
|
37625
|
+
evidence: result.evidence,
|
|
37626
|
+
gateId,
|
|
37627
|
+
kind: gate.kind,
|
|
37628
|
+
nodeId,
|
|
37629
|
+
passed: result.passed,
|
|
37630
|
+
reason: result.reason
|
|
37631
|
+
};
|
|
37632
|
+
}
|
|
37633
|
+
function validateJsonSchemaSource(source, schemaPath, worktreePath) {
|
|
37445
37634
|
try {
|
|
37446
|
-
const schema = JSON.parse(readFileSync7(join6(
|
|
37635
|
+
const schema = JSON.parse(readFileSync7(join6(worktreePath, schemaPath), "utf8"));
|
|
37447
37636
|
const value = JSON.parse(source);
|
|
37448
37637
|
const errors4 = validateJsonSchema(value, schema);
|
|
37449
37638
|
return {
|
|
37450
37639
|
evidence: errors4.length === 0 ? [`JSON schema passed: ${schemaPath}`] : errors4.map((error51) => `schema: ${error51}`),
|
|
37451
|
-
gateId,
|
|
37452
|
-
kind: gate.kind,
|
|
37453
|
-
nodeId,
|
|
37454
37640
|
passed: errors4.length === 0,
|
|
37455
37641
|
reason: errors4.length === 0 ? undefined : "JSON schema validation failed"
|
|
37456
37642
|
};
|
|
37457
37643
|
} catch (err) {
|
|
37458
37644
|
return {
|
|
37459
37645
|
evidence: [err instanceof Error ? err.message : String(err)],
|
|
37460
|
-
gateId,
|
|
37461
|
-
kind: gate.kind,
|
|
37462
|
-
nodeId,
|
|
37463
37646
|
passed: false,
|
|
37464
37647
|
reason: "JSON schema validation failed"
|
|
37465
37648
|
};
|
|
@@ -37653,6 +37836,9 @@ function formatRuntimeProgress(event) {
|
|
|
37653
37836
|
case "gate.finish":
|
|
37654
37837
|
console.error(`Gate ${event.passed ? "passed" : "failed"}: ${event.nodeId}/${event.gateId}${event.reason ? ` (${event.reason})` : ""}`);
|
|
37655
37838
|
return;
|
|
37839
|
+
case "output.repair":
|
|
37840
|
+
console.error(`Output repair ${event.passed ? "passed" : "failed"}: ${event.nodeId} attempt=${event.attempt}${event.reason ? ` (${event.reason})` : ""}`);
|
|
37841
|
+
return;
|
|
37656
37842
|
case "node.finish":
|
|
37657
37843
|
console.error(`Node finished: ${event.nodeId} ${event.status} exit=${event.exitCode}`);
|
|
37658
37844
|
return;
|
package/dist/mastra/config.d.ts
CHANGED
|
@@ -76,6 +76,11 @@ declare const configSchema: z.ZodObject<{
|
|
|
76
76
|
jsonl: "jsonl";
|
|
77
77
|
json_schema: "json_schema";
|
|
78
78
|
}>;
|
|
79
|
+
repair: z.ZodOptional<z.ZodObject<{
|
|
80
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
81
|
+
max_attempts: z.ZodOptional<z.ZodNumber>;
|
|
82
|
+
runner: z.ZodOptional<z.ZodString>;
|
|
83
|
+
}, z.core.$strict>>;
|
|
79
84
|
schema_path: z.ZodOptional<z.ZodString>;
|
|
80
85
|
}, z.core.$strict>>;
|
|
81
86
|
rules: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
package/dist/mastra/runner.js
CHANGED
|
@@ -7283,7 +7283,7 @@ function harnessArgv(harness, prompt, worktreePath, contextFile, options = {}) {
|
|
|
7283
7283
|
...mcpArgs,
|
|
7284
7284
|
...skillArgs,
|
|
7285
7285
|
"--sandbox",
|
|
7286
|
-
|
|
7286
|
+
codexSandboxFor(options.actor),
|
|
7287
7287
|
"--config",
|
|
7288
7288
|
'approval_policy="never"',
|
|
7289
7289
|
"--skip-git-repo-check",
|
|
@@ -7332,6 +7332,9 @@ function harnessArgv(harness, prompt, worktreePath, contextFile, options = {}) {
|
|
|
7332
7332
|
}
|
|
7333
7333
|
}
|
|
7334
7334
|
}
|
|
7335
|
+
function codexSandboxFor(actor) {
|
|
7336
|
+
return actor?.filesystem?.mode === "read-only" ? "read-only" : "workspace-write";
|
|
7337
|
+
}
|
|
7335
7338
|
async function execaHarness(harness, prompt, contextFile, worktreePath) {
|
|
7336
7339
|
if (harness === "pi") {
|
|
7337
7340
|
return execaHarnessPi(prompt, contextFile, worktreePath);
|
|
@@ -54,6 +54,12 @@ export type PipelineRuntimeEvent = {
|
|
|
54
54
|
passed: boolean;
|
|
55
55
|
reason?: string;
|
|
56
56
|
type: "gate.finish";
|
|
57
|
+
} | {
|
|
58
|
+
attempt: number;
|
|
59
|
+
nodeId: string;
|
|
60
|
+
passed: boolean;
|
|
61
|
+
reason?: string;
|
|
62
|
+
type: "output.repair";
|
|
57
63
|
} | {
|
|
58
64
|
outcome: PipelineRuntimeResult["outcome"];
|
|
59
65
|
type: "workflow.finish";
|
|
@@ -109,6 +109,24 @@ receive explicit grants:
|
|
|
109
109
|
- `network`: inherited or disabled.
|
|
110
110
|
- `output`: text, JSON, JSONL, or JSON Schema output.
|
|
111
111
|
|
|
112
|
+
JSON Schema outputs are hard contracts. The runtime validates normalized agent
|
|
113
|
+
output before the node can pass. Schema outputs also get a bounded repair pass
|
|
114
|
+
by default:
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
output:
|
|
118
|
+
format: json_schema
|
|
119
|
+
schema_path: .pipeline/schemas/research.schema.json
|
|
120
|
+
repair:
|
|
121
|
+
enabled: true
|
|
122
|
+
max_attempts: 1
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The repair pass receives only the schema, invalid output, and validation error.
|
|
126
|
+
It uses a no-tools, read-only profile, then the runtime validates the repaired
|
|
127
|
+
output again. If repair still fails, the node fails with both original and
|
|
128
|
+
repair evidence.
|
|
129
|
+
|
|
112
130
|
Hooks live in `pipeline.yaml` and can be attached to the orchestrator, workflow,
|
|
113
131
|
or workflow nodes.
|
|
114
132
|
|