@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.
- package/README.md +30 -0
- package/dist/index.js +258 -23
- package/examples/starter-kit/agents/planner.md +4 -0
- package/examples/starter-kit/agents/reviewer.md +4 -0
- package/examples/starter-kit/agents/scout.md +4 -0
- package/examples/starter-kit/agents/worker.md +4 -0
- package/examples/starter-kit/agents/workspace-preparer.md +4 -0
- package/examples/starter-kit/investigate.workflow.yaml +34 -64
- package/examples/starter-kit/jira.workflow.yaml +75 -0
- package/examples/starter-kit/mr-comment.workflow.yaml +48 -115
- package/examples/starter-kit/mr-review.workflow.yaml +36 -93
- package/examples/starter-kit/settings.yaml +2 -1
- package/examples/starter-kit/steps/investigate/investigate.md +18 -55
- package/examples/starter-kit/steps/investigate/retrieve.md +15 -50
- package/examples/starter-kit/steps/investigate/validate.md +10 -36
- package/examples/starter-kit/steps/jira/create.md +25 -0
- package/examples/starter-kit/steps/jira/draft.md +18 -0
- package/examples/starter-kit/steps/jira/plan.md +30 -0
- package/examples/starter-kit/steps/mr-comment/checkout-source.md +7 -60
- package/examples/starter-kit/steps/mr-comment/fetch.md +11 -36
- package/examples/starter-kit/steps/mr-comment/implement.md +10 -34
- package/examples/starter-kit/steps/mr-comment/plan.md +47 -70
- package/examples/starter-kit/steps/mr-comment/publish.md +11 -32
- package/examples/starter-kit/steps/mr-comment/verify.md +8 -42
- package/examples/starter-kit/steps/mr-review/fetch.md +8 -46
- package/examples/starter-kit/steps/mr-review/publish-approved.md +7 -38
- package/examples/starter-kit/steps/mr-review/review-for-approval.md +24 -113
- package/examples/starter-kit/steps/mr-review/verify-published.md +9 -30
- package/examples/starter-kit/steps/shared/prepare-workspace.md +9 -105
- package/examples/starter-kit/steps/shared/publish-remote.md +8 -37
- package/examples/starter-kit/steps/ticket/implement.md +11 -58
- package/examples/starter-kit/steps/ticket/plan.md +49 -171
- package/examples/starter-kit/steps/ticket/verify.md +12 -98
- package/examples/starter-kit/steps/work/implement.md +11 -58
- package/examples/starter-kit/steps/work/plan.md +42 -130
- package/examples/starter-kit/steps/work/verify.md +11 -58
- package/examples/starter-kit/ticket.workflow.yaml +38 -81
- package/examples/starter-kit/work.workflow.yaml +34 -72
- package/package.json +7 -4
- package/schemas/workflow.schema.json +61 -0
- package/scripts/patch-herdr-agent-state.mjs +36 -0
- package/src/config/types.ts +9 -0
- package/src/config/validation/step.ts +117 -0
- package/src/harness/artifact-contract.ts +46 -0
- package/src/harness/delegation-recovery.ts +15 -0
- package/src/harness/delegation-response-actions.ts +21 -1
- package/src/harness/gate-submission-action.ts +28 -0
- package/src/harness/status-actions.ts +19 -0
- package/src/herdr-workflow-state.ts +65 -0
- package/src/integrations/subagents/child-runtime-repair.ts +23 -0
- package/src/integrations/subagents/child-runtime.ts +25 -0
- package/src/integrations/subagents/client.ts +84 -1
- package/src/integrations/subagents/diagnostics.ts +43 -0
- 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
|
-
|
|
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
|
-
|
|
7700
|
-
|
|
7934
|
+
createPiWorkflowsExtension,
|
|
7935
|
+
src_default as default
|
|
7701
7936
|
};
|
|
@@ -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:
|
|
17
|
-
|
|
18
|
-
|
|
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:
|
|
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,
|
|
69
|
-
mcp:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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:
|
|
97
|
-
extensions: [
|
|
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
|