@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.
@@ -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,
@@ -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";
@@ -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>;
@@ -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 selfRemediation = await remediateWritableNodeFailure({
305
+ const remediation = await remediateFailedNode({
306
306
  attempt,
307
307
  context,
308
308
  node,
309
309
  retry
310
310
  });
311
- if (selfRemediation) {
311
+ if (remediation?.result) {
312
312
  recordNodeEvent(context, node.id, {
313
313
  at: now(),
314
- result: selfRemediation,
314
+ result: remediation.result,
315
315
  type: "PASSED"
316
316
  });
317
- emitNodeFinish(context, selfRemediation);
318
- return selfRemediation;
317
+ emitNodeFinish(context, remediation.result);
318
+ return remediation.result;
319
319
  }
320
- if (await remediateCoverageFailure({
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
- if (isCancelled(input.context)) return false;
428
- const beforeSnapshot = await snapshotChangedFiles(input.context.worktreePath);
429
- const beforeOutput = input.context.lastOutputByNode.get(implementationNode.id);
430
- const result = await executeImplementationRemediation({
431
- attempt: input.attempt,
432
- context: input.context,
433
- coverageNode: input.node,
434
- implementationNode,
435
- retry: input.retry
436
- });
437
- if (result.status !== "passed") return false;
438
- if (diffChangedFiles(beforeSnapshot, await snapshotChangedFiles(input.context.worktreePath), input.context.worktreePath).files.size === 0 && result.output === beforeOutput) return false;
439
- input.context.lastOutputByNode.set(implementationNode.id, result.output);
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 = runnerTextCandidates(plan, stdout);
67
- for (const candidate of [...candidates].reverse()) if (validateJsonSchemaSource(candidate.output, output.schema_path, context.worktreePath).passed) return {
68
- evidence: [candidate.evidence, `selected valid structured output for ${node.id}`],
69
- output: candidate.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 repairedValidation = validateJsonSchemaSource(repaired.output, repairContext.schemaPath, context.worktreePath);
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: repaired.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: repaired.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.11",
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",