@wichayutdew/pi-workflows 2.6.0 → 2.7.1

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.
Files changed (54) hide show
  1. package/README.md +30 -0
  2. package/dist/index.js +258 -23
  3. package/examples/starter-kit/agents/planner.md +4 -0
  4. package/examples/starter-kit/agents/reviewer.md +4 -0
  5. package/examples/starter-kit/agents/scout.md +4 -0
  6. package/examples/starter-kit/agents/worker.md +4 -0
  7. package/examples/starter-kit/agents/workspace-preparer.md +4 -0
  8. package/examples/starter-kit/investigate.workflow.yaml +34 -64
  9. package/examples/starter-kit/jira.workflow.yaml +75 -0
  10. package/examples/starter-kit/mr-comment.workflow.yaml +48 -115
  11. package/examples/starter-kit/mr-review.workflow.yaml +36 -93
  12. package/examples/starter-kit/settings.yaml +2 -1
  13. package/examples/starter-kit/steps/investigate/investigate.md +18 -55
  14. package/examples/starter-kit/steps/investigate/retrieve.md +15 -50
  15. package/examples/starter-kit/steps/investigate/validate.md +10 -36
  16. package/examples/starter-kit/steps/jira/create.md +25 -0
  17. package/examples/starter-kit/steps/jira/draft.md +18 -0
  18. package/examples/starter-kit/steps/jira/plan.md +30 -0
  19. package/examples/starter-kit/steps/mr-comment/checkout-source.md +7 -60
  20. package/examples/starter-kit/steps/mr-comment/fetch.md +11 -36
  21. package/examples/starter-kit/steps/mr-comment/implement.md +10 -34
  22. package/examples/starter-kit/steps/mr-comment/plan.md +47 -70
  23. package/examples/starter-kit/steps/mr-comment/publish.md +11 -32
  24. package/examples/starter-kit/steps/mr-comment/verify.md +8 -42
  25. package/examples/starter-kit/steps/mr-review/fetch.md +8 -46
  26. package/examples/starter-kit/steps/mr-review/publish-approved.md +7 -38
  27. package/examples/starter-kit/steps/mr-review/review-for-approval.md +24 -113
  28. package/examples/starter-kit/steps/mr-review/verify-published.md +9 -30
  29. package/examples/starter-kit/steps/shared/prepare-workspace.md +9 -105
  30. package/examples/starter-kit/steps/shared/publish-remote.md +8 -37
  31. package/examples/starter-kit/steps/ticket/implement.md +11 -58
  32. package/examples/starter-kit/steps/ticket/plan.md +49 -171
  33. package/examples/starter-kit/steps/ticket/verify.md +12 -98
  34. package/examples/starter-kit/steps/work/implement.md +11 -58
  35. package/examples/starter-kit/steps/work/plan.md +42 -130
  36. package/examples/starter-kit/steps/work/verify.md +11 -58
  37. package/examples/starter-kit/ticket.workflow.yaml +38 -81
  38. package/examples/starter-kit/work.workflow.yaml +34 -72
  39. package/package.json +7 -4
  40. package/schemas/workflow.schema.json +61 -0
  41. package/scripts/patch-herdr-agent-state.mjs +36 -0
  42. package/src/config/types.ts +9 -0
  43. package/src/config/validation/step.ts +117 -0
  44. package/src/harness/artifact-contract.ts +46 -0
  45. package/src/harness/delegation-recovery.ts +15 -0
  46. package/src/harness/delegation-response-actions.ts +21 -1
  47. package/src/harness/gate-submission-action.ts +28 -0
  48. package/src/harness/status-actions.ts +19 -0
  49. package/src/herdr-workflow-state.ts +65 -0
  50. package/src/integrations/subagents/child-runtime-repair.ts +23 -0
  51. package/src/integrations/subagents/child-runtime.ts +25 -0
  52. package/src/integrations/subagents/client.ts +84 -1
  53. package/src/integrations/subagents/diagnostics.ts +43 -0
  54. package/src/integrations/subagents/protocol-events.ts +3 -0
package/README.md CHANGED
@@ -36,6 +36,36 @@ dictating your language, framework, or delivery process.
36
36
  `reviewer`, or `scout`; customize profiles under your user workflow
37
37
  directory's `agents/` folder.
38
38
 
39
+ ## Herdr workflow status
40
+
41
+ When Pi runs delegated workflow steps, the parent agent may be idle while child work continues. Pi Workflows includes a Herdr companion extension that reports workflow lifecycle state so the pane remains **working** until the workflow completes, pauses, or is interrupted.
42
+
43
+ The companion is inactive unless Herdr provides `HERDR_ENV=1`, `HERDR_SOCKET_PATH`, and `HERDR_PANE_ID`. It uses Herdr's existing managed Pi reporter, so it does not create a competing pane agent.
44
+
45
+ ### Install from npm
46
+
47
+ ```bash
48
+ pi install npm:@wichayutdew/pi-workflows
49
+ ```
50
+
51
+ Start a new Pi session in Herdr, then run a workflow normally. Herdr should show the pane as working while the workflow is active and display the terminal workflow message after completion or interruption.
52
+
53
+ ### Reapply after a Herdr update
54
+
55
+ Herdr manages `~/.pi/agent/extensions/herdr-agent-state.ts`; an integration update can replace the workflow lifecycle patch. Locate the installed package with `pi list`, change into that package directory, and run:
56
+
57
+ ```bash
58
+ npm run patch:herdr
59
+ ```
60
+
61
+ The script patches the managed integration at its default location. It is idempotent, so it is safe to run after every Herdr update. To patch a non-default integration path, set `HERDR_PI_EXTENSION_PATH` before running the script:
62
+
63
+ ```bash
64
+ HERDR_PI_EXTENSION_PATH=/path/to/herdr-agent-state.ts npm run patch:herdr
65
+ ```
66
+
67
+ Restart Pi or run `/reload` after patching. If Herdr's managed extension changes shape and the script reports an unsupported layout, do not edit it manually; update Pi Workflows or report the integration change.
68
+
39
69
  ## Explore
40
70
 
41
71
  - [Getting started guide](./GETTING_STARTED.md) — install Pi Workflows and
package/dist/index.js CHANGED
@@ -518,6 +518,75 @@ function parseTransitions(value, path, errors) {
518
518
  }
519
519
  return transitions;
520
520
  }
521
+ function parseArtifactContract(value, path, errors) {
522
+ if (value === undefined)
523
+ return;
524
+ if (!isJsonObject(value)) {
525
+ errors.push(`${path}: expected an object`);
526
+ return;
527
+ }
528
+ rejectUnknownKeys(value, [
529
+ "maxChars",
530
+ "requiredSubstrings",
531
+ "forbiddenSubstrings",
532
+ "equalOccurrenceGroups",
533
+ "onValidationFailure"
534
+ ], path, errors);
535
+ if (value.maxChars === undefined) {
536
+ errors.push(`${path}.maxChars: expected an integer from 1 to 200000`);
537
+ }
538
+ const maxChars = readInteger(value.maxChars, 200000, `${path}.maxChars`, errors, { min: 1, max: 200000 });
539
+ const parseSubstrings = (field) => {
540
+ const values = readStringList(value[field], `${path}.${field}`, errors, /.+/);
541
+ if (values.length > 32) {
542
+ errors.push(`${path}.${field}: at most 32 values are allowed`);
543
+ }
544
+ values.forEach((substring, index) => {
545
+ if (substring.length > 1024) {
546
+ errors.push(`${path}.${field}[${index}]: exceeds 1024 characters`);
547
+ }
548
+ });
549
+ return values;
550
+ };
551
+ const equalOccurrenceGroups = (() => {
552
+ if (value.equalOccurrenceGroups === undefined)
553
+ return [];
554
+ if (!Array.isArray(value.equalOccurrenceGroups)) {
555
+ errors.push(`${path}.equalOccurrenceGroups: expected an array`);
556
+ return [];
557
+ }
558
+ if (value.equalOccurrenceGroups.length > 32) {
559
+ errors.push(`${path}.equalOccurrenceGroups: at most 32 groups are allowed`);
560
+ }
561
+ return value.equalOccurrenceGroups.reduce((groups, group, index) => {
562
+ const groupPath = `${path}.equalOccurrenceGroups[${index}]`;
563
+ const values = readStringList(group, groupPath, errors, /.+/);
564
+ if (values.length < 2) {
565
+ errors.push(`${groupPath}: at least two values are required`);
566
+ }
567
+ if (values.length > 32) {
568
+ errors.push(`${groupPath}: at most 32 values are allowed`);
569
+ }
570
+ values.forEach((substring, valueIndex) => {
571
+ if (substring.length > 1024) {
572
+ errors.push(`${groupPath}[${valueIndex}]: exceeds 1024 characters`);
573
+ }
574
+ });
575
+ return [...groups, values];
576
+ }, []);
577
+ })();
578
+ const onValidationFailure = value.onValidationFailure === undefined ? undefined : readString(value.onValidationFailure, `${path}.onValidationFailure`, errors);
579
+ if (onValidationFailure !== undefined && onValidationFailure !== "retry") {
580
+ errors.push(`${path}.onValidationFailure: expected retry`);
581
+ }
582
+ return {
583
+ maxChars,
584
+ requiredSubstrings: parseSubstrings("requiredSubstrings"),
585
+ forbiddenSubstrings: parseSubstrings("forbiddenSubstrings"),
586
+ equalOccurrenceGroups,
587
+ ...onValidationFailure === "retry" ? { onValidationFailure } : {}
588
+ };
589
+ }
521
590
  function parseGate(value, path, errors) {
522
591
  if (value === undefined)
523
592
  return;
@@ -530,7 +599,8 @@ function parseGate(value, path, errors) {
530
599
  "submitOutcome",
531
600
  "approvedOutcome",
532
601
  "rejectedOutcome",
533
- "timeoutMs"
602
+ "timeoutMs",
603
+ "artifactContract"
534
604
  ], path, errors);
535
605
  const providerValue = value.provider === undefined ? "prompt" : readString(value.provider, `${path}.provider`, errors);
536
606
  const provider = providerValue === "prompt" || providerValue === "plannotator" ? providerValue : undefined;
@@ -540,6 +610,7 @@ function parseGate(value, path, errors) {
540
610
  const submitOutcome = readString(value.submitOutcome, `${path}.submitOutcome`, errors, { pattern: OUTCOME_PATTERN });
541
611
  const approvedOutcome = readString(value.approvedOutcome, `${path}.approvedOutcome`, errors, { pattern: OUTCOME_PATTERN });
542
612
  const rejectedOutcome = readString(value.rejectedOutcome, `${path}.rejectedOutcome`, errors, { pattern: OUTCOME_PATTERN });
613
+ const artifactContract = parseArtifactContract(value.artifactContract, `${path}.artifactContract`, errors);
543
614
  if (provider === "prompt" && value.timeoutMs !== undefined) {
544
615
  errors.push(`${path}.timeoutMs: only valid with provider "plannotator"`);
545
616
  }
@@ -553,12 +624,14 @@ function parseGate(value, path, errors) {
553
624
  provider,
554
625
  submitOutcome,
555
626
  approvedOutcome,
556
- rejectedOutcome
627
+ rejectedOutcome,
628
+ ...artifactContract ? { artifactContract } : {}
557
629
  } : {
558
630
  provider,
559
631
  submitOutcome,
560
632
  approvedOutcome,
561
633
  rejectedOutcome,
634
+ ...artifactContract ? { artifactContract } : {},
562
635
  timeoutMs: readInteger(value.timeoutMs, 30000, `${path}.timeoutMs`, errors, { min: 1000, max: 30000 })
563
636
  };
564
637
  }
@@ -649,6 +722,9 @@ function parseWorkflowStep(value, stepId, path, errors) {
649
722
  if (Object.hasOwn(transitions, gate.submitOutcome)) {
650
723
  errors.push(`${path}.transitions: submitOutcome is handled by the gate and must not be a transition`);
651
724
  }
725
+ if (gate.artifactContract?.onValidationFailure === "retry" && !Object.hasOwn(transitions, "retry")) {
726
+ errors.push(`${path}.transitions: artifact-contract retry requires a "retry" transition`);
727
+ }
652
728
  }
653
729
  if (workspace) {
654
730
  workspace.bindOn.forEach((outcome) => {
@@ -1429,17 +1505,19 @@ var directWorkerCommand = (request) => [
1429
1505
  "--print",
1430
1506
  request.task
1431
1507
  ];
1432
- function directWorkerResponse(request, code, signal, stderr) {
1508
+ function directWorkerResponse(request, code, signal, stderr, diagnostic) {
1433
1509
  const status = code === 0 ? "completed" : signal ? "cancelled" : "failed";
1434
1510
  return {
1435
1511
  requestId: request.requestId,
1436
1512
  agent: request.agent,
1437
1513
  status,
1438
1514
  ...code === null ? {} : { exitCode: code },
1439
- ...status !== "completed" && stderr.trim() ? { error: stderr.trim().slice(-4000) } : {}
1515
+ ...status !== "completed" && stderr.trim() ? { error: stderr.trim().slice(-4000) } : {},
1516
+ ...diagnostic ? { diagnostic } : {}
1440
1517
  };
1441
1518
  }
1442
1519
  var MAX_PROGRESS_DETAIL_CHARS = 480;
1520
+ var MAX_DIAGNOSTIC_CALLS = 64;
1443
1521
  var SECRET_KEY = /authorization|cookie|password|secret|token|api[-_]?key/i;
1444
1522
  function redactProgressValue(value, key = "") {
1445
1523
  if (SECRET_KEY.test(key))
@@ -1461,6 +1539,52 @@ function formatToolCall(toolName, args) {
1461
1539
  const rendered = JSON.stringify(redactProgressValue(args));
1462
1540
  return `call ${toolName} ${rendered}`.slice(0, MAX_PROGRESS_DETAIL_CHARS);
1463
1541
  }
1542
+ var createDiagnostic2 = () => ({
1543
+ settled: false,
1544
+ truncated: false,
1545
+ calls: new Map
1546
+ });
1547
+ var diagnosticSnapshot = (diagnostic) => ({
1548
+ settled: diagnostic.settled,
1549
+ truncated: diagnostic.truncated,
1550
+ calls: [...diagnostic.calls.values()]
1551
+ });
1552
+ var recordWorkerDiagnostic = (line, diagnostic) => {
1553
+ let event;
1554
+ try {
1555
+ const parsed = JSON.parse(line);
1556
+ if (typeof parsed !== "object" || parsed === null)
1557
+ return;
1558
+ event = parsed;
1559
+ } catch {
1560
+ return;
1561
+ }
1562
+ if (event.type === "agent_settled") {
1563
+ diagnostic.settled = true;
1564
+ return;
1565
+ }
1566
+ if (event.type !== "tool_execution_start" && event.type !== "tool_execution_end" || typeof event.toolName !== "string" || typeof event.toolCallId !== "string") {
1567
+ return;
1568
+ }
1569
+ if (!diagnostic.calls.has(event.toolCallId)) {
1570
+ if (diagnostic.calls.size >= MAX_DIAGNOSTIC_CALLS) {
1571
+ diagnostic.truncated = true;
1572
+ return;
1573
+ }
1574
+ diagnostic.calls.set(event.toolCallId, {
1575
+ id: event.toolCallId,
1576
+ name: event.toolName,
1577
+ state: "started"
1578
+ });
1579
+ }
1580
+ if (event.type === "tool_execution_end") {
1581
+ diagnostic.calls.set(event.toolCallId, {
1582
+ id: event.toolCallId,
1583
+ name: event.toolName,
1584
+ state: event.isError === false ? "completed" : "failed"
1585
+ });
1586
+ }
1587
+ };
1464
1588
  function workerProgressFromJsonLine(line, requestId, toolCount, responseText = "") {
1465
1589
  let event;
1466
1590
  try {
@@ -1533,6 +1657,7 @@ function createSubagentDelegationClient(spawnWorker = spawn) {
1533
1657
  let stdoutBuffer = "";
1534
1658
  let toolCount = 0;
1535
1659
  let responseText = "";
1660
+ const diagnostic = createDiagnostic2();
1536
1661
  const stdoutDecoder = new StringDecoder("utf8");
1537
1662
  const consumeWorkerLines = () => {
1538
1663
  while (true) {
@@ -1542,6 +1667,7 @@ function createSubagentDelegationClient(spawnWorker = spawn) {
1542
1667
  return;
1543
1668
  const line = stdoutBuffer.slice(0, newline);
1544
1669
  stdoutBuffer = stdoutBuffer.slice(newline + 1);
1670
+ recordWorkerDiagnostic(line, diagnostic);
1545
1671
  const progress = workerProgressFromJsonLine(line, request.requestId, toolCount, responseText);
1546
1672
  toolCount = progress.toolCount;
1547
1673
  responseText = progress.responseText;
@@ -1572,7 +1698,7 @@ function createSubagentDelegationClient(spawnWorker = spawn) {
1572
1698
  if (active?.process === child)
1573
1699
  active = undefined;
1574
1700
  options.signal?.removeEventListener("abort", abort);
1575
- resolve3(directWorkerResponse(request, code, signal, stderr));
1701
+ resolve3(directWorkerResponse(request, code, signal, stderr, diagnosticSnapshot(diagnostic)));
1576
1702
  });
1577
1703
  });
1578
1704
  };
@@ -1595,19 +1721,6 @@ function createSubagentDelegationClient(spawnWorker = spawn) {
1595
1721
  };
1596
1722
  }
1597
1723
 
1598
- class SubagentDelegationClient {
1599
- #client = createSubagentDelegationClient();
1600
- get activeRequestId() {
1601
- return this.#client.activeRequestId;
1602
- }
1603
- delegate(request, options) {
1604
- return this.#client.delegate(request, options);
1605
- }
1606
- cancelActiveAndWait(waitMs) {
1607
- return this.#client.cancelActiveAndWait(waitMs);
1608
- }
1609
- }
1610
-
1611
1724
  // src/policy/completion-batch.ts
1612
1725
  var isRecord3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1613
1726
  var toolCalls = (message) => {
@@ -4005,6 +4118,13 @@ function workflowStatusSnapshot() {
4005
4118
  }
4006
4119
  function updateStatus() {
4007
4120
  refreshStatusWhileRunning.call(this);
4121
+ const run = this.run;
4122
+ this.pi.events.emit("pi-workflows:state", {
4123
+ state: run?.status === "running" ? "working" : run?.status === "awaiting-gate" || run?.status === "paused" ? "blocked" : run?.status === "completed" ? "completed" : "interrupted",
4124
+ workflowId: run?.workflowId,
4125
+ stepId: run?.currentStepId,
4126
+ message: run?.status === "completed" ? `Workflow "${run.workflowId}" completed` : run?.status === "paused" ? run.pauseReason : undefined
4127
+ });
4008
4128
  if (!this.latestContext)
4009
4129
  return;
4010
4130
  if (this.legacyProgressWidgetContext !== this.latestContext) {
@@ -6493,6 +6613,26 @@ function createStepExecutionActions() {
6493
6613
  };
6494
6614
  }
6495
6615
 
6616
+ // src/integrations/subagents/diagnostics.ts
6617
+ var READ_ONLY_TOOLS = new Set([
6618
+ "read",
6619
+ "ls",
6620
+ "grep",
6621
+ "structured_output"
6622
+ ]);
6623
+ var classifyRecoverySafety = (diagnostic) => {
6624
+ if (!diagnostic || !diagnostic.settled || diagnostic.truncated) {
6625
+ return "incomplete";
6626
+ }
6627
+ if (diagnostic.calls.some((call) => call.state !== "completed" || !READ_ONLY_TOOLS.has(call.name))) {
6628
+ return "unsafe";
6629
+ }
6630
+ return "read-only";
6631
+ };
6632
+
6633
+ // src/harness/delegation-recovery.ts
6634
+ var shouldRetryMissingCompletion = (diagnostic, subagentAttemptCount) => subagentAttemptCount === 1 && classifyRecoverySafety(diagnostic) === "read-only";
6635
+
6496
6636
  // src/harness/delegation-response-actions.ts
6497
6637
  function hasErrorCode(error, code) {
6498
6638
  return error instanceof Error && "code" in error && error.code === code;
@@ -6568,7 +6708,15 @@ async function finishDelegation(active, response) {
6568
6708
  serializedResult = await this.dependencies.readDelegatedResult(active);
6569
6709
  } catch (error) {
6570
6710
  if (hasErrorCode(error, "ENOENT")) {
6571
- throw new Error(`Subagent "${active.agent}" completed without producing the required correlated structured_output result`, { cause: error });
6711
+ const subagentAttemptCount = this.run.currentStepAttempts?.filter((attempt) => attempt.kind === "subagent").length ?? 0;
6712
+ if (shouldRetryMissingCompletion(response.diagnostic, subagentAttemptCount)) {
6713
+ cleanupAttempted = true;
6714
+ await this.cleanupDelegation(active);
6715
+ this.launchCurrentStep(workflow);
6716
+ return;
6717
+ }
6718
+ const diagnosticState = response.diagnostic ? `settled=${response.diagnostic.settled}, truncated=${response.diagnostic.truncated}, calls=${response.diagnostic.calls.length}` : "unavailable";
6719
+ throw new Error(`Subagent "${active.agent}" completed without producing the required correlated structured_output result (request ${active.requestId}; diagnostic ${diagnosticState})`, { cause: error });
6572
6720
  }
6573
6721
  throw error;
6574
6722
  }
@@ -6700,6 +6848,44 @@ function createDelegationControlActions() {
6700
6848
  };
6701
6849
  }
6702
6850
 
6851
+ // src/harness/artifact-contract.ts
6852
+ function countOccurrences(value, substring) {
6853
+ let count = 0;
6854
+ let offset = 0;
6855
+ while (true) {
6856
+ const match = value.indexOf(substring, offset);
6857
+ if (match === -1)
6858
+ return count;
6859
+ count += 1;
6860
+ offset = match + substring.length;
6861
+ }
6862
+ }
6863
+ function validateArtifactContract(artifact, contract) {
6864
+ if (!contract)
6865
+ return;
6866
+ if (artifact.length > contract.maxChars) {
6867
+ return `gate artifact exceeds ${contract.maxChars} characters`;
6868
+ }
6869
+ const required = contract.requiredSubstrings.find((substring) => !artifact.includes(substring));
6870
+ if (required) {
6871
+ return `gate artifact is missing required text: ${JSON.stringify(required)}`;
6872
+ }
6873
+ const forbidden = contract.forbiddenSubstrings.find((substring) => artifact.includes(substring));
6874
+ if (forbidden) {
6875
+ return `gate artifact contains forbidden text: ${JSON.stringify(forbidden)}`;
6876
+ }
6877
+ for (const group of contract.equalOccurrenceGroups) {
6878
+ const counts = group.map((substring) => countOccurrences(artifact, substring));
6879
+ if (counts.some((count) => count === 0)) {
6880
+ return `gate artifact is missing required repeated text: ${JSON.stringify(group)}`;
6881
+ }
6882
+ if (!counts.every((count) => count === counts[0])) {
6883
+ return `gate artifact has unequal repeated text counts: ${JSON.stringify(group)}`;
6884
+ }
6885
+ }
6886
+ return;
6887
+ }
6888
+
6703
6889
  // src/harness/gate-submission-action.ts
6704
6890
  function isCurrentGateRequest(run, originalRun, requestId) {
6705
6891
  return run !== undefined && run.runId === originalRun.runId && run.currentStepId === originalRun.currentStepId && run.pendingGate?.requestId === requestId && run.pendingGate.reviewId === undefined && (run.status === "awaiting-gate" || run.status === "paused");
@@ -6710,6 +6896,22 @@ async function submitGate(workflow, originalRun, outcome, summary, artifact) {
6710
6896
  const step = workflow.definition.steps[originalRun.currentStepId];
6711
6897
  if (!step?.gate)
6712
6898
  throw new Error("Current step has no gate");
6899
+ const contractError = validateArtifactContract(artifact, step.gate.artifactContract);
6900
+ if (contractError) {
6901
+ if (step.gate.artifactContract?.onValidationFailure !== "retry") {
6902
+ throw new Error(contractError);
6903
+ }
6904
+ const retrySummary = `Artifact contract failed: ${contractError}`;
6905
+ this.run = advanceRun(workflow, originalRun, "retry", retrySummary, this.dependencies.now());
6906
+ this.persist();
6907
+ this.updateStatus();
6908
+ this.settleAfterTransition(workflow, {
6909
+ stepId: originalRun.currentStepId,
6910
+ outcome: "retry",
6911
+ summary: retrySummary
6912
+ });
6913
+ return;
6914
+ }
6713
6915
  this.run = beginGate(workflow, originalRun, outcome, artifact, requestId, this.dependencies.now(), summary);
6714
6916
  this.persist();
6715
6917
  this.restoreBaselineTools();
@@ -7397,6 +7599,24 @@ var DEFAULT_CHILD_RUNTIME_DEPENDENCIES = {
7397
7599
  tokensAreEqual
7398
7600
  };
7399
7601
 
7602
+ // src/integrations/subagents/child-runtime-repair.ts
7603
+ var COMPLETION_REPAIR_PROMPT = [
7604
+ "The delegated step settled without its required correlated result.",
7605
+ "Do not repeat completed work and do not execute work tools.",
7606
+ "Call `structured_output` exactly once, alone, with one configured outcome and the required summary, artifact, and workspace fields."
7607
+ ].join(`
7608
+ `);
7609
+ var needsCompletionRepair = ({
7610
+ policy,
7611
+ dependencies
7612
+ }) => {
7613
+ try {
7614
+ return !dependencies.fileSystem.exists(policy.resultPath);
7615
+ } catch {
7616
+ return false;
7617
+ }
7618
+ };
7619
+
7400
7620
  // src/integrations/subagents/child-runtime-files.ts
7401
7621
  var verifyChildWorkingDirectory = (policy, dependencies) => {
7402
7622
  let expected;
@@ -7494,7 +7714,8 @@ var INITIAL_STATE = {
7494
7714
  activePolicy: undefined,
7495
7715
  policyError: undefined,
7496
7716
  invalidCompletionCalls: new Set,
7497
- effectiveTools: new Set
7717
+ effectiveTools: new Set,
7718
+ repairRequested: false
7498
7719
  };
7499
7720
  var errorMessage2 = (error) => error instanceof Error ? error.message : String(error);
7500
7721
  var invalidPolicyInput = (pi, policyError, images) => {
@@ -7560,7 +7781,8 @@ var registerSubagentChildRuntime = (pi, options = {}) => {
7560
7781
  ...state,
7561
7782
  activePolicy: extracted.policy,
7562
7783
  policyError: undefined,
7563
- effectiveTools
7784
+ effectiveTools,
7785
+ repairRequested: false
7564
7786
  };
7565
7787
  } catch (error) {
7566
7788
  const policyError = errorMessage2(error);
@@ -7593,6 +7815,19 @@ ${childSystemPrompt(state.activePolicy)}`
7593
7815
  pi.on("turn_start", () => {
7594
7816
  state = { ...state, invalidCompletionCalls: new Set };
7595
7817
  });
7818
+ pi.on("agent_settled", () => {
7819
+ const policy = state.activePolicy;
7820
+ if (!policy || state.repairRequested || !needsCompletionRepair({ policy, dependencies })) {
7821
+ return;
7822
+ }
7823
+ state = {
7824
+ ...state,
7825
+ repairRequested: true,
7826
+ effectiveTools: new Set([CHILD_COMPLETION_TOOL])
7827
+ };
7828
+ pi.setActiveTools([CHILD_COMPLETION_TOOL]);
7829
+ pi.sendUserMessage(COMPLETION_REPAIR_PROMPT, { deliverAs: "followUp" });
7830
+ });
7596
7831
  pi.on("message_end", (event) => {
7597
7832
  if (!state.activePolicy)
7598
7833
  return;
@@ -7696,6 +7931,6 @@ function createPiWorkflowsExtension(dependencies) {
7696
7931
  var piWorkflowsExtension = createPiWorkflowsExtension(DEFAULT_DEPENDENCIES4);
7697
7932
  var src_default = piWorkflowsExtension;
7698
7933
  export {
7699
- src_default as default,
7700
- createPiWorkflowsExtension
7934
+ createPiWorkflowsExtension,
7935
+ src_default as default
7701
7936
  };
@@ -1,3 +1,7 @@
1
+ ---
2
+ thinking: high
3
+ ---
4
+
1
5
  You are the planning role for one workflow step.
2
6
 
3
7
  Stay within the step's declared permissions. Establish facts before conclusions,
@@ -1,3 +1,7 @@
1
+ ---
2
+ thinking: xhigh
3
+ ---
4
+
1
5
  You are the independent review role for one workflow step.
2
6
 
3
7
  Remain read-only unless the step explicitly grants a different authority.
@@ -1,3 +1,7 @@
1
+ ---
2
+ thinking: medium
3
+ ---
4
+
1
5
  You are the investigation role for one workflow step.
2
6
 
3
7
  Stay read-only. Gather the smallest set of decisive evidence, distinguish facts
@@ -1,3 +1,7 @@
1
+ ---
2
+ thinking: high
3
+ ---
4
+
1
5
  You are the implementation role for one workflow step.
2
6
 
3
7
  Treat the approved workflow artifact and declared permissions as the complete
@@ -1,3 +1,7 @@
1
+ ---
2
+ thinking: low
3
+ ---
4
+
1
5
  You are the workspace preparation role for one workflow step.
2
6
 
3
7
  Inspect the current repository state before mutation. Create or reuse only the
@@ -1,7 +1,7 @@
1
1
  version: 1
2
2
  id: investigate
3
3
  command: investigate
4
- description: Derive, approve, investigate, and independently validate an evidence-backed finding
4
+ description: 'Derive, approve, investigate, and independently validate an evidence-backed finding. Example: /investigate PROJ-123 Root cause of elevated 500 error rates in checkout service'
5
5
  start: retrieve
6
6
  maxStepVisits: 12
7
7
  summaryMaxChars: 30000
@@ -12,48 +12,30 @@ steps:
12
12
  file: steps/investigate/retrieve.md
13
13
  agent: scout
14
14
  permissions:
15
- tools: [read, ls, bash]
16
- mcp: &jira-read-mcp
17
- - atlassian/atlassian_getAccessibleAtlassianResources
18
- - atlassian/atlassian_getJiraIssue
19
- - atlassian/atlassian_getJiraIssueRemoteIssueLinks
20
- - atlassian/atlassian_searchJiraIssuesUsingJql
21
- extensions: [pi-web-tools]
22
- skills: &retrieve-skills [caveman, coding-standards, brainstorming]
15
+ tools: [read, ls, bash, mcp]
16
+ mcp: [atlassian, context7, sourcegraph, glean, grafana]
17
+ extensions: [/]
18
+ skills: [jira-ticket, start-triage, search-code-sourcegraph, caveman]
23
19
  bash:
24
- mode: allow-list
25
- allow: &inspection-bash
26
- - executable: grep
27
- - executable: head
28
- - executable: ls
29
- - executable: pwd
30
- - executable: rg
31
- - executable: stat
32
- - executable: tail
33
- - executable: wc
34
- - executable: git
35
- argsPrefixes:
36
- [
37
- [status],
38
- [diff],
39
- [grep],
40
- [log],
41
- [ls-files],
42
- [rev-parse],
43
- [show],
44
- [merge-base],
45
- [rev-list],
46
- [worktree, list],
47
- ]
48
- requires:
49
- tools: [read, ls, bash]
50
- skills: *retrieve-skills
20
+ mode: unrestricted
51
21
  gate:
52
22
  provider: plannotator
53
23
  submitOutcome: submit
54
24
  approvedOutcome: approved
55
25
  rejectedOutcome: changes-requested
56
26
  timeoutMs: 30000
27
+ artifactContract:
28
+ maxChars: 6000
29
+ requiredSubstrings:
30
+ - '## Brief description'
31
+ - '## Goals'
32
+ - '## Boundaries'
33
+ - '## Evidence & sources'
34
+ - '## Report destination'
35
+ - '## Open evidence gaps'
36
+ forbiddenSubstrings:
37
+ - 'The complete gate artifact is the exact Markdown saved at'
38
+ onValidationFailure: retry
57
39
  transitions:
58
40
  approved: investigate
59
41
  changes-requested: retrieve
@@ -65,23 +47,20 @@ steps:
65
47
  file: steps/investigate/investigate.md
66
48
  agent: worker
67
49
  permissions:
68
- tools: [read, ls, bash, edit, write]
69
- mcp: &investigation-mcp
70
- - atlassian
71
- - gitlab
72
- - glean
73
- - superset
74
- - sourcegraph
75
- - context7/resolve-library-id
76
- - context7/query-docs
77
- - gh_grep/searchGitHub
78
- extensions: [pi-web-tools]
79
- skills: &investigation-skills [caveman, coding-standards]
50
+ tools: [read, ls, bash, write, mcp]
51
+ mcp: [atlassian, context7, sourcegraph, glean, grafana, gitlab]
52
+ extensions: [/]
53
+ skills:
54
+ [
55
+ coding-standards,
56
+ systematic-debugging,
57
+ start-triage,
58
+ grafana-logs,
59
+ search-code-sourcegraph,
60
+ caveman,
61
+ ]
80
62
  bash:
81
63
  mode: unrestricted
82
- requires:
83
- tools: [read, ls, bash, edit, write]
84
- skills: *investigation-skills
85
64
  transitions:
86
65
  ready: validate
87
66
  retry: investigate
@@ -92,21 +71,12 @@ steps:
92
71
  file: steps/investigate/validate.md
93
72
  agent: reviewer
94
73
  permissions:
95
- tools: [read, ls, bash]
96
- mcp: *investigation-mcp
97
- extensions: [pi-web-tools]
98
- skills:
99
- &validation-skills [
100
- caveman,
101
- coding-standards,
102
- systematic-debugging,
103
- verification-before-completion,
104
- ]
74
+ tools: [read, ls, bash, mcp]
75
+ mcp: [atlassian, sourcegraph, glean, gitlab]
76
+ extensions: [/]
77
+ skills: [verification-before-completion, caveman]
105
78
  bash:
106
79
  mode: unrestricted
107
- requires:
108
- tools: [read, ls, bash]
109
- skills: *validation-skills
110
80
  transitions:
111
81
  approved: $done
112
82
  gaps: investigate