@oisincoveney/pipeline 1.27.11 → 1.27.13
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/argo-submit.js +5 -0
- package/dist/argo-workflow.d.ts +2 -0
- package/dist/argo-workflow.js +3 -0
- package/dist/config.d.ts +2 -2
- package/dist/hooks.d.ts +1 -1
- package/dist/pipeline-runtime.js +46 -26
- package/dist/runtime/agent-node/agent-node.js +23 -11
- package/dist/runtime/json-validation/json-validation.js +8 -3
- package/package.json +1 -1
package/dist/argo-submit.js
CHANGED
|
@@ -64,6 +64,11 @@ async function submitRunnerArgoWorkflow(rawOptions, dependencies = {}) {
|
|
|
64
64
|
"pipeline.oisin.dev/workflow": compiled.workflowId
|
|
65
65
|
};
|
|
66
66
|
const workflow = buildRunnerArgoWorkflowManifest({
|
|
67
|
+
annotations: payload.task.kind === "ticket" ? {
|
|
68
|
+
"pipeline.oisin.dev/ticket-id": payload.task.id,
|
|
69
|
+
"pipeline.oisin.dev/ticket-project": payload.run.project,
|
|
70
|
+
"pipeline.oisin.dev/ticket-title": payload.task.title
|
|
71
|
+
} : {},
|
|
67
72
|
eventAuthSecretKey: options.eventAuthSecretKey,
|
|
68
73
|
eventAuthSecretName: options.eventAuthSecretName,
|
|
69
74
|
generateName: options.generateName,
|
package/dist/argo-workflow.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ declare const runnerArgoWorkflowManifestSchema: z.ZodObject<{
|
|
|
6
6
|
apiVersion: z.ZodLiteral<"argoproj.io/v1alpha1">;
|
|
7
7
|
kind: z.ZodLiteral<"Workflow">;
|
|
8
8
|
metadata: z.ZodObject<{
|
|
9
|
+
annotations: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
9
10
|
generateName: z.ZodOptional<z.ZodString>;
|
|
10
11
|
labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
11
12
|
name: z.ZodOptional<z.ZodString>;
|
|
@@ -112,6 +113,7 @@ declare const buildRunnerArgoWorkflowOptionsSchema: z.ZodObject<{
|
|
|
112
113
|
eventAuthSecretName: z.ZodOptional<z.ZodString>;
|
|
113
114
|
generateName: z.ZodOptional<z.ZodString>;
|
|
114
115
|
githubAuthSecretName: z.ZodOptional<z.ZodString>;
|
|
116
|
+
annotations: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodString>>>;
|
|
115
117
|
image: z.ZodDefault<z.ZodString>;
|
|
116
118
|
imagePullPolicy: z.ZodDefault<z.ZodEnum<{
|
|
117
119
|
Always: "Always";
|
package/dist/argo-workflow.js
CHANGED
|
@@ -83,6 +83,7 @@ const runnerArgoWorkflowManifestSchema = z.object({
|
|
|
83
83
|
apiVersion: z.literal(ARGO_WORKFLOW_API_VERSION),
|
|
84
84
|
kind: z.literal(ARGO_WORKFLOW_KIND),
|
|
85
85
|
metadata: z.object({
|
|
86
|
+
annotations: z.record(z.string().min(1), z.string().min(1)).optional(),
|
|
86
87
|
generateName: z.string().min(1).optional(),
|
|
87
88
|
labels: z.record(z.string().min(1), labelValueSchema).optional(),
|
|
88
89
|
name: z.string().min(1).optional(),
|
|
@@ -110,6 +111,7 @@ const buildRunnerArgoWorkflowOptionsSchema = z.object({
|
|
|
110
111
|
eventAuthSecretName: kubernetesNameSchema.optional(),
|
|
111
112
|
generateName: z.string().min(1).optional(),
|
|
112
113
|
githubAuthSecretName: kubernetesNameSchema.optional(),
|
|
114
|
+
annotations: z.record(z.string().min(1), z.string().min(1).optional()).default({}),
|
|
113
115
|
image: z.string().min(1).default(RUNNER_WORKFLOW_IMAGE),
|
|
114
116
|
imagePullPolicy: z.enum([
|
|
115
117
|
"Always",
|
|
@@ -151,6 +153,7 @@ function buildRunnerArgoWorkflowManifest(rawOptions) {
|
|
|
151
153
|
apiVersion: ARGO_WORKFLOW_API_VERSION,
|
|
152
154
|
kind: ARGO_WORKFLOW_KIND,
|
|
153
155
|
metadata: {
|
|
156
|
+
annotations: compactRecord(options.annotations),
|
|
154
157
|
...options.name ? { name: options.name } : {},
|
|
155
158
|
...options.generateName ? { generateName: options.generateName } : {},
|
|
156
159
|
labels: compactRecord({
|
package/dist/config.d.ts
CHANGED
|
@@ -452,6 +452,7 @@ declare const configSchema: z.ZodObject<{
|
|
|
452
452
|
skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
453
453
|
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
454
454
|
tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
455
|
+
task: "task";
|
|
455
456
|
read: "read";
|
|
456
457
|
list: "list";
|
|
457
458
|
grep: "grep";
|
|
@@ -459,7 +460,6 @@ declare const configSchema: z.ZodObject<{
|
|
|
459
460
|
bash: "bash";
|
|
460
461
|
edit: "edit";
|
|
461
462
|
write: "write";
|
|
462
|
-
task: "task";
|
|
463
463
|
}>>>;
|
|
464
464
|
}, z.core.$strict>>>;
|
|
465
465
|
runner_command: z.ZodDefault<z.ZodObject<{
|
|
@@ -511,6 +511,7 @@ declare const configSchema: z.ZodObject<{
|
|
|
511
511
|
rules: z.ZodOptional<z.ZodBoolean>;
|
|
512
512
|
skills: z.ZodOptional<z.ZodBoolean>;
|
|
513
513
|
tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
514
|
+
task: "task";
|
|
514
515
|
read: "read";
|
|
515
516
|
list: "list";
|
|
516
517
|
grep: "grep";
|
|
@@ -518,7 +519,6 @@ declare const configSchema: z.ZodObject<{
|
|
|
518
519
|
bash: "bash";
|
|
519
520
|
edit: "edit";
|
|
520
521
|
write: "write";
|
|
521
|
-
task: "task";
|
|
522
522
|
}>>>;
|
|
523
523
|
}, z.core.$strict>;
|
|
524
524
|
command: z.ZodOptional<z.ZodString>;
|
package/dist/hooks.d.ts
CHANGED
|
@@ -13,8 +13,8 @@ declare const hookResultSchema: z.ZodObject<{
|
|
|
13
13
|
taskContext: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
14
14
|
}, z.core.$strict>>;
|
|
15
15
|
status: z.ZodEnum<{
|
|
16
|
-
pass: "pass";
|
|
17
16
|
fail: "fail";
|
|
17
|
+
pass: "pass";
|
|
18
18
|
skip: "skip";
|
|
19
19
|
}>;
|
|
20
20
|
summary: z.ZodOptional<z.ZodString>;
|
package/dist/pipeline-runtime.js
CHANGED
|
@@ -302,27 +302,22 @@ async function executeNode(node, context) {
|
|
|
302
302
|
return cycle.result;
|
|
303
303
|
}
|
|
304
304
|
retry = retryCandidateForCycle(node, cycle, last, attempt);
|
|
305
|
-
const
|
|
305
|
+
const remediation = await remediateFailedNode({
|
|
306
306
|
attempt,
|
|
307
307
|
context,
|
|
308
308
|
node,
|
|
309
309
|
retry
|
|
310
310
|
});
|
|
311
|
-
if (
|
|
311
|
+
if (remediation?.result) {
|
|
312
312
|
recordNodeEvent(context, node.id, {
|
|
313
313
|
at: now(),
|
|
314
|
-
result:
|
|
314
|
+
result: remediation.result,
|
|
315
315
|
type: "PASSED"
|
|
316
316
|
});
|
|
317
|
-
emitNodeFinish(context,
|
|
318
|
-
return
|
|
317
|
+
emitNodeFinish(context, remediation.result);
|
|
318
|
+
return remediation.result;
|
|
319
319
|
}
|
|
320
|
-
if (
|
|
321
|
-
attempt,
|
|
322
|
-
context,
|
|
323
|
-
node,
|
|
324
|
-
retry
|
|
325
|
-
})) continue;
|
|
320
|
+
if (remediation?.retryNode) continue;
|
|
326
321
|
recordNodeEvent(context, node.id, {
|
|
327
322
|
at: now(),
|
|
328
323
|
attempt,
|
|
@@ -379,6 +374,13 @@ async function executeNode(node, context) {
|
|
|
379
374
|
emitNodeFinish(context, result);
|
|
380
375
|
return result;
|
|
381
376
|
}
|
|
377
|
+
async function remediateFailedNode(input) {
|
|
378
|
+
const selfRemediation = await remediateWritableNodeFailure(input);
|
|
379
|
+
if (selfRemediation) return { result: selfRemediation };
|
|
380
|
+
if (await remediateCoverageFailure(input)) return { retryNode: true };
|
|
381
|
+
if (await remediateUpstreamImplementationFailure(input)) return { retryNode: true };
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
382
384
|
async function remediateWritableNodeFailure(input) {
|
|
383
385
|
if (input.retry.retryReason !== "gate_failure" || isRemediationNode(input.node) || !nodeCanWrite(input.context, input.node)) return null;
|
|
384
386
|
const beforeSnapshot = await snapshotChangedFiles(input.context.worktreePath);
|
|
@@ -421,23 +423,41 @@ async function executeSelfRemediation(input) {
|
|
|
421
423
|
}
|
|
422
424
|
async function remediateCoverageFailure(input) {
|
|
423
425
|
if (input.retry.retryReason !== "gate_failure" || !hasSchedulingRole(input.context, input.node, "coverage")) return false;
|
|
426
|
+
return await remediatePassedImplementationAncestors(input);
|
|
427
|
+
}
|
|
428
|
+
async function remediateUpstreamImplementationFailure(input) {
|
|
429
|
+
if (isRemediationNode(input.node) || nodeCanWrite(input.context, input.node) || hasSchedulingRole(input.context, input.node, "coverage")) return false;
|
|
430
|
+
return await remediatePassedImplementationAncestors(input);
|
|
431
|
+
}
|
|
432
|
+
async function remediatePassedImplementationAncestors(input) {
|
|
424
433
|
const implementationNodes = upstreamImplementationNodes(input.context, input.node).filter((candidate) => input.context.nodeStates.get(candidate.id)?.status === "passed");
|
|
425
434
|
if (implementationNodes.length === 0) return false;
|
|
426
|
-
for (const implementationNode of implementationNodes)
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
435
|
+
for (const implementationNode of implementationNodes) if (!await remediateImplementationAncestor(input, implementationNode)) return false;
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
async function remediateImplementationAncestor(input, implementationNode) {
|
|
439
|
+
if (isCancelled(input.context)) return false;
|
|
440
|
+
const beforeSnapshot = await snapshotChangedFiles(input.context.worktreePath);
|
|
441
|
+
const beforeOutput = input.context.lastOutputByNode.get(implementationNode.id);
|
|
442
|
+
const result = await executeImplementationRemediation({
|
|
443
|
+
attempt: input.attempt,
|
|
444
|
+
context: input.context,
|
|
445
|
+
coverageNode: input.node,
|
|
446
|
+
implementationNode,
|
|
447
|
+
retry: input.retry
|
|
448
|
+
});
|
|
449
|
+
if (result.status !== "passed") return false;
|
|
450
|
+
return await recordImplementationRemediationEffect({
|
|
451
|
+
beforeOutput,
|
|
452
|
+
beforeSnapshot,
|
|
453
|
+
context: input.context,
|
|
454
|
+
implementationNode,
|
|
455
|
+
result
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
async function recordImplementationRemediationEffect(input) {
|
|
459
|
+
if (diffChangedFiles(input.beforeSnapshot, await snapshotChangedFiles(input.context.worktreePath), input.context.worktreePath).files.size === 0 && input.result.output === input.beforeOutput) return false;
|
|
460
|
+
input.context.lastOutputByNode.set(input.implementationNode.id, input.result.output);
|
|
441
461
|
return true;
|
|
442
462
|
}
|
|
443
463
|
async function executeImplementationRemediation(input) {
|
|
@@ -4,7 +4,7 @@ import { normalizeRunnerOutput, runnerTextCandidates } from "../../runner-output
|
|
|
4
4
|
import { gatewayServerForProfile } from "../../mcp/gateway.js";
|
|
5
5
|
import { selectNodeModel } from "../../model-resolver.js";
|
|
6
6
|
import { resolvePackageAssetPath } from "../../package-assets.js";
|
|
7
|
-
import { readJsonSchemaSource, validateJsonSchemaSource } from "../json-validation/json-validation.js";
|
|
7
|
+
import { normalizeJsonSource, readJsonSchemaSource, validateJsonSchemaSource } from "../json-validation/json-validation.js";
|
|
8
8
|
import "../json-validation/index.js";
|
|
9
9
|
import { emit, emitAgentFinish, emitAgentStart } from "../events/events.js";
|
|
10
10
|
import "../events/index.js";
|
|
@@ -54,22 +54,33 @@ async function executeAgentNode(node, context, attempt) {
|
|
|
54
54
|
}
|
|
55
55
|
async function finalizeAgentOutput(inputs) {
|
|
56
56
|
const { attempt, context, node, normalized, plan, result } = inputs;
|
|
57
|
-
const validStructuredOutput = selectValidStructuredOutput(context, node, plan, result.stdout);
|
|
57
|
+
const validStructuredOutput = selectValidStructuredOutput(context, node, normalized, plan, result.stdout);
|
|
58
58
|
if (validStructuredOutput) return validStructuredOutput;
|
|
59
59
|
const repairContext = outputRepairContext(context, node, normalized, result);
|
|
60
60
|
if (!repairContext) return normalized;
|
|
61
61
|
return await runOutputRepair(context, node, normalized, repairContext, attempt);
|
|
62
62
|
}
|
|
63
|
-
function selectValidStructuredOutput(context, node, plan, stdout) {
|
|
63
|
+
function selectValidStructuredOutput(context, node, normalized, plan, stdout) {
|
|
64
64
|
const output = (node.profile ? context.config.profiles[node.profile] : void 0)?.output;
|
|
65
65
|
if (output?.format !== "json_schema" || !output.schema_path) return null;
|
|
66
|
-
const candidates =
|
|
67
|
-
for (const candidate of
|
|
68
|
-
|
|
69
|
-
output
|
|
70
|
-
|
|
66
|
+
const candidates = structuredOutputCandidates(plan, stdout, normalized);
|
|
67
|
+
for (const candidate of candidates) {
|
|
68
|
+
const candidateOutput = normalizeJsonSource(candidate.output);
|
|
69
|
+
if (validateJsonSchemaSource(candidateOutput, output.schema_path, context.worktreePath).passed) return {
|
|
70
|
+
evidence: [candidate.evidence, `selected valid structured output for ${node.id}`],
|
|
71
|
+
output: candidateOutput
|
|
72
|
+
};
|
|
73
|
+
}
|
|
71
74
|
return null;
|
|
72
75
|
}
|
|
76
|
+
function structuredOutputCandidates(plan, stdout, normalized) {
|
|
77
|
+
const candidates = runnerTextCandidates(plan, stdout);
|
|
78
|
+
if (candidates.length > 0) return [...candidates].reverse();
|
|
79
|
+
return [{
|
|
80
|
+
evidence: normalized.evidence.join("; ") || "selected runner stdout",
|
|
81
|
+
output: normalized.output
|
|
82
|
+
}];
|
|
83
|
+
}
|
|
73
84
|
function outputRepairContext(context, node, normalized, result) {
|
|
74
85
|
if (result.exitCode !== 0 || result.timedOut) return null;
|
|
75
86
|
const profile = node.profile ? context.config.profiles[node.profile] : void 0;
|
|
@@ -110,14 +121,15 @@ async function runOutputRepair(context, node, normalized, repairContext, nodeAtt
|
|
|
110
121
|
const repairResult = await context.executor(repairPlan, { signal: context.signal });
|
|
111
122
|
emitAgentFinish(context, repairPlan, nodeAttempt, repairResult);
|
|
112
123
|
const repaired = normalizeAgentOutput(repairPlan, repairResult.stdout);
|
|
113
|
-
const
|
|
124
|
+
const repairedOutput = normalizeJsonSource(repaired.output);
|
|
125
|
+
const repairedValidation = validateJsonSchemaSource(repairedOutput, repairContext.schemaPath, context.worktreePath);
|
|
114
126
|
latest = {
|
|
115
127
|
evidence: [
|
|
116
128
|
...repaired.evidence,
|
|
117
129
|
...repairResult.stderr ? [`repair stderr: ${repairResult.stderr}`] : [],
|
|
118
130
|
...repairResult.timedOut ? ["output repair timed out"] : []
|
|
119
131
|
],
|
|
120
|
-
output:
|
|
132
|
+
output: repairedOutput
|
|
121
133
|
};
|
|
122
134
|
latestValidation = repairedValidation;
|
|
123
135
|
const passed = repairResult.exitCode === 0 && repairedValidation.passed;
|
|
@@ -131,7 +143,7 @@ async function runOutputRepair(context, node, normalized, repairContext, nodeAtt
|
|
|
131
143
|
});
|
|
132
144
|
if (passed) return {
|
|
133
145
|
evidence,
|
|
134
|
-
output:
|
|
146
|
+
output: repairedOutput
|
|
135
147
|
};
|
|
136
148
|
}
|
|
137
149
|
return {
|
|
@@ -6,6 +6,7 @@ import Ajv from "ajv";
|
|
|
6
6
|
import addFormats from "ajv-formats";
|
|
7
7
|
//#region src/runtime/json-validation/json-validation.ts
|
|
8
8
|
const LINE_RE = /\r?\n/;
|
|
9
|
+
const MARKDOWN_JSON_FENCE_RE = /^\s*```(?:json)?\s*\r?\n([\s\S]*?)\r?\n```\s*$/i;
|
|
9
10
|
const jsonSchemaValidator = addFormats(new Ajv({
|
|
10
11
|
allErrors: true,
|
|
11
12
|
strict: false
|
|
@@ -15,7 +16,7 @@ function parseRuntimeOutput(format, output) {
|
|
|
15
16
|
if (!(format === "json" || format === "json_schema" || format === "jsonl")) return { output };
|
|
16
17
|
try {
|
|
17
18
|
if (format === "jsonl") return { output: output.split(LINE_RE).filter((line) => line.trim().length > 0).map((line) => parseJson(line, "runtime JSONL line")) };
|
|
18
|
-
return { output: parseJson(output, "runtime JSON output") };
|
|
19
|
+
return { output: parseJson(normalizeJsonSource(output), "runtime JSON output") };
|
|
19
20
|
} catch (err) {
|
|
20
21
|
return {
|
|
21
22
|
error: err instanceof Error ? err.message : "failed to parse output",
|
|
@@ -26,7 +27,7 @@ function parseRuntimeOutput(format, output) {
|
|
|
26
27
|
function validateJsonSchemaSource(source, schemaPath, worktreePath) {
|
|
27
28
|
try {
|
|
28
29
|
const schemaSource = readJsonSchemaSource(schemaPath, worktreePath);
|
|
29
|
-
const value = parseJson(source, "JSON schema gate value");
|
|
30
|
+
const value = parseJson(normalizeJsonSource(source), "JSON schema gate value");
|
|
30
31
|
const validate = compiledJsonSchemaValidator(schemaPath, schemaSource);
|
|
31
32
|
const errors = validate(value) ? [] : formatJsonSchemaErrors(validate.errors ?? []);
|
|
32
33
|
return {
|
|
@@ -42,6 +43,10 @@ function validateJsonSchemaSource(source, schemaPath, worktreePath) {
|
|
|
42
43
|
};
|
|
43
44
|
}
|
|
44
45
|
}
|
|
46
|
+
function normalizeJsonSource(source) {
|
|
47
|
+
const trimmed = source.trim();
|
|
48
|
+
return MARKDOWN_JSON_FENCE_RE.exec(trimmed)?.[1].trim() ?? trimmed;
|
|
49
|
+
}
|
|
45
50
|
function readJsonSchemaSource(schemaPath, worktreePath) {
|
|
46
51
|
const standardName = standardOutputSchemaNameFromPath(schemaPath);
|
|
47
52
|
if (standardName) return standardOutputSchemaJson(standardName);
|
|
@@ -82,4 +87,4 @@ function parseJsonObject(value) {
|
|
|
82
87
|
}
|
|
83
88
|
}
|
|
84
89
|
//#endregion
|
|
85
|
-
export { parseJsonObject, parseRuntimeOutput, readJsonSchemaSource, readOptionalFile, validateJsonSchemaSource };
|
|
90
|
+
export { normalizeJsonSource, parseJsonObject, parseRuntimeOutput, readJsonSchemaSource, readOptionalFile, validateJsonSchemaSource };
|
package/package.json
CHANGED
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"prepack": "bun run build:cli"
|
|
121
121
|
},
|
|
122
122
|
"type": "module",
|
|
123
|
-
"version": "1.27.
|
|
123
|
+
"version": "1.27.13",
|
|
124
124
|
"description": "Config-driven multi-agent pipeline runner for repository work",
|
|
125
125
|
"main": "./dist/index.js",
|
|
126
126
|
"types": "./dist/index.d.ts",
|