pi-subagents 0.28.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +18 -61
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +4 -35
- package/src/agents/agent-management.ts +10 -20
- package/src/agents/agent-selection.ts +2 -0
- package/src/agents/agent-serializer.ts +0 -10
- package/src/agents/agents.ts +304 -47
- package/src/agents/chain-serializer.ts +4 -9
- package/src/extension/doctor.ts +4 -3
- package/src/extension/fanout-child.ts +0 -2
- package/src/extension/index.ts +3 -8
- package/src/extension/schemas.ts +32 -22
- package/src/intercom/intercom-bridge.ts +11 -1
- package/src/intercom/result-intercom.ts +0 -5
- package/src/runs/background/async-execution.ts +20 -11
- package/src/runs/background/run-status.ts +1 -7
- package/src/runs/background/subagent-runner.ts +81 -211
- package/src/runs/foreground/chain-execution.ts +62 -58
- package/src/runs/foreground/execution.ts +38 -343
- package/src/runs/foreground/subagent-executor.ts +28 -99
- package/src/runs/shared/acceptance.ts +605 -22
- package/src/runs/shared/completion-guard.ts +3 -26
- package/src/runs/shared/model-fallback.ts +38 -0
- package/src/runs/shared/parallel-utils.ts +6 -10
- package/src/runs/shared/subagent-prompt-runtime.ts +3 -2
- package/src/runs/shared/workflow-graph.ts +2 -6
- package/src/shared/atomic-json.ts +68 -11
- package/src/shared/settings.ts +1 -0
- package/src/shared/types.ts +10 -48
- package/src/shared/utils.ts +2 -8
- package/src/tui/render.ts +14 -29
- package/src/runs/shared/acceptance-contract.ts +0 -318
- package/src/runs/shared/acceptance-evaluation.ts +0 -221
- package/src/runs/shared/acceptance-finalization.ts +0 -173
- package/src/runs/shared/acceptance-reports.ts +0 -127
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AcceptanceFinalizationTurn,
|
|
3
|
-
AcceptanceLedger,
|
|
4
|
-
ResolvedAcceptanceConfig,
|
|
5
|
-
} from "../../shared/types.ts";
|
|
6
|
-
import { acceptanceFailureMessage } from "./acceptance-evaluation.ts";
|
|
7
|
-
import { formatEvidenceReportFieldMapping } from "./acceptance-contract.ts";
|
|
8
|
-
import { stripAcceptanceReport } from "./acceptance-reports.ts";
|
|
9
|
-
|
|
10
|
-
const INITIAL_OUTPUT_LIMIT = 8_000;
|
|
11
|
-
|
|
12
|
-
function truncateForPrompt(value: string): string {
|
|
13
|
-
const trimmed = stripAcceptanceReport(value).trim();
|
|
14
|
-
if (trimmed.length <= INITIAL_OUTPUT_LIMIT) return trimmed || "(initial output was empty after removing acceptance-report)";
|
|
15
|
-
return `${trimmed.slice(0, INITIAL_OUTPUT_LIMIT)}\n...[truncated]`;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function formatReportForPrompt(ledger: AcceptanceLedger): string {
|
|
19
|
-
if (ledger.childReport) return JSON.stringify(ledger.childReport, null, 2);
|
|
20
|
-
return `Missing or malformed acceptance report: ${ledger.childReportParseError ?? "no parse detail"}`;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function formatAcceptanceFinalizationPrompt(input: {
|
|
24
|
-
acceptance: ResolvedAcceptanceConfig;
|
|
25
|
-
initialOutput: string;
|
|
26
|
-
initialLedger: AcceptanceLedger;
|
|
27
|
-
turn: number;
|
|
28
|
-
maxTurns: number;
|
|
29
|
-
previousFailure?: string;
|
|
30
|
-
}): string {
|
|
31
|
-
const lines = [
|
|
32
|
-
"## Acceptance Finalization",
|
|
33
|
-
"You are continuing the same subagent session. Before this run can be accepted, compare the current work to the acceptance contract and the evidence below.",
|
|
34
|
-
`This is finalization turn ${input.turn} of ${input.maxTurns}. The run will be rejected if the contract is still not satisfied after turn ${input.maxTurns}.`,
|
|
35
|
-
"",
|
|
36
|
-
"If a criterion is incomplete and fixable in this session, keep working now before returning the final report.",
|
|
37
|
-
"If a criterion cannot be satisfied in this session, report it as not-satisfied, explain the blocker in residualRisks, and say what input would unblock progress.",
|
|
38
|
-
"Do not claim a criterion is satisfied unless the current work has concrete evidence from files, commands, validation output, or other inspectable artifacts.",
|
|
39
|
-
"",
|
|
40
|
-
"## Acceptance Contract",
|
|
41
|
-
"Criteria:",
|
|
42
|
-
...(input.acceptance.criteria.length ? input.acceptance.criteria.map((criterion) => `- ${criterion.id}: ${criterion.must}`) : ["- No explicit criteria were configured; satisfy the requested task and required evidence/checks."]),
|
|
43
|
-
"",
|
|
44
|
-
`Required evidence: ${input.acceptance.evidence.join(", ") || "none explicitly requested"}`,
|
|
45
|
-
];
|
|
46
|
-
if (input.acceptance.evidence.length > 0) {
|
|
47
|
-
lines.push(
|
|
48
|
-
"",
|
|
49
|
-
"Structured evidence must be present in the final `acceptance-report` JSON fields. Markdown sections in the visible answer do not satisfy required evidence by themselves. If the previous visible output already included the evidence, copy or summarize it into the matching JSON field.",
|
|
50
|
-
"Evidence field mapping:",
|
|
51
|
-
...formatEvidenceReportFieldMapping(input.acceptance.evidence),
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
if (input.acceptance.verify.length > 0) {
|
|
55
|
-
lines.push("", "Runtime verification commands that must pass:", ...input.acceptance.verify.map((command) => `- ${command.id}: ${command.command}`));
|
|
56
|
-
}
|
|
57
|
-
if (input.acceptance.review) {
|
|
58
|
-
lines.push("", `Independent review gate after self-review: ${input.acceptance.review.required === false ? "optional" : "required"}${input.acceptance.review.agent ? ` by ${input.acceptance.review.agent}` : ""}.`);
|
|
59
|
-
}
|
|
60
|
-
if (input.acceptance.stopRules.length > 0) {
|
|
61
|
-
lines.push("", "Stop rules are hard constraints while deciding whether to continue, stop as blocked, or report success:", ...input.acceptance.stopRules.map((rule) => `- ${rule}`));
|
|
62
|
-
}
|
|
63
|
-
lines.push(
|
|
64
|
-
"",
|
|
65
|
-
"Initial visible output:",
|
|
66
|
-
truncateForPrompt(input.initialOutput),
|
|
67
|
-
"",
|
|
68
|
-
"Initial acceptance report:",
|
|
69
|
-
formatReportForPrompt(input.initialLedger),
|
|
70
|
-
);
|
|
71
|
-
if (input.previousFailure) {
|
|
72
|
-
lines.push("", "Previous finalization failure to address:", input.previousFailure);
|
|
73
|
-
}
|
|
74
|
-
lines.push(
|
|
75
|
-
"",
|
|
76
|
-
"Now do the self-check. If work was missing and you repaired it, report the repaired final state. Finish with exactly one fenced JSON block tagged `acceptance-report`.",
|
|
77
|
-
"```acceptance-report",
|
|
78
|
-
JSON.stringify({
|
|
79
|
-
criteriaSatisfied: [{ id: "criterion-1", status: "satisfied", evidence: "specific proof from the final state" }],
|
|
80
|
-
changedFiles: [],
|
|
81
|
-
testsAddedOrUpdated: [],
|
|
82
|
-
commandsRun: [{ command: "command", result: "passed", summary: "short result" }],
|
|
83
|
-
validationOutput: [],
|
|
84
|
-
residualRisks: [],
|
|
85
|
-
noStagedFiles: true,
|
|
86
|
-
diffSummary: "concise summary of changed behavior and important files",
|
|
87
|
-
reviewFindings: [],
|
|
88
|
-
manualNotes: "manual notes or external evidence, if any",
|
|
89
|
-
notes: "final self-review summary",
|
|
90
|
-
}, null, 2),
|
|
91
|
-
"```",
|
|
92
|
-
);
|
|
93
|
-
return lines.join("\n");
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function createFinalizationTurn(input: {
|
|
97
|
-
turn: number;
|
|
98
|
-
prompt: string;
|
|
99
|
-
rawOutput: string;
|
|
100
|
-
ledger: AcceptanceLedger;
|
|
101
|
-
}): AcceptanceFinalizationTurn {
|
|
102
|
-
const failureMessage = acceptanceFailureMessage(input.ledger);
|
|
103
|
-
return {
|
|
104
|
-
turn: input.turn,
|
|
105
|
-
prompt: input.prompt,
|
|
106
|
-
status: input.ledger.status,
|
|
107
|
-
rawOutput: input.rawOutput,
|
|
108
|
-
...(input.ledger.childReport ? { report: input.ledger.childReport } : {}),
|
|
109
|
-
...(input.ledger.childReportParseError ? { parseError: input.ledger.childReportParseError } : {}),
|
|
110
|
-
runtimeChecks: input.ledger.runtimeChecks,
|
|
111
|
-
verifyRuns: input.ledger.verifyRuns,
|
|
112
|
-
...(failureMessage ? { failureMessage } : {}),
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export function createFinalizationProcessFailureTurn(input: {
|
|
117
|
-
turn: number;
|
|
118
|
-
prompt: string;
|
|
119
|
-
rawOutput?: string;
|
|
120
|
-
message: string;
|
|
121
|
-
}): AcceptanceFinalizationTurn {
|
|
122
|
-
return {
|
|
123
|
-
turn: input.turn,
|
|
124
|
-
prompt: input.prompt,
|
|
125
|
-
status: "rejected",
|
|
126
|
-
...(input.rawOutput ? { rawOutput: input.rawOutput } : {}),
|
|
127
|
-
runtimeChecks: [{ id: "finalization-process", status: "failed", message: input.message }],
|
|
128
|
-
verifyRuns: [],
|
|
129
|
-
failureMessage: `Acceptance rejected: ${input.message}`,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export function attachFinalizationToLedger(input: {
|
|
134
|
-
initialLedger: AcceptanceLedger;
|
|
135
|
-
authoritativeLedger: AcceptanceLedger;
|
|
136
|
-
turns: AcceptanceFinalizationTurn[];
|
|
137
|
-
status: "completed" | "failed";
|
|
138
|
-
maxTurns: number;
|
|
139
|
-
}): AcceptanceLedger {
|
|
140
|
-
return {
|
|
141
|
-
...input.authoritativeLedger,
|
|
142
|
-
...(input.initialLedger.childReport ? { initialChildReport: input.initialLedger.childReport } : {}),
|
|
143
|
-
...(input.initialLedger.childReportParseError ? { initialChildReportParseError: input.initialLedger.childReportParseError } : {}),
|
|
144
|
-
finalization: {
|
|
145
|
-
mode: "self-review-loop",
|
|
146
|
-
status: input.status,
|
|
147
|
-
maxTurns: input.maxTurns,
|
|
148
|
-
turns: input.turns,
|
|
149
|
-
},
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export function buildFinalizationProcessFailureLedger(input: {
|
|
154
|
-
initialLedger: AcceptanceLedger;
|
|
155
|
-
turns: AcceptanceFinalizationTurn[];
|
|
156
|
-
maxTurns: number;
|
|
157
|
-
message: string;
|
|
158
|
-
}): AcceptanceLedger {
|
|
159
|
-
return attachFinalizationToLedger({
|
|
160
|
-
initialLedger: input.initialLedger,
|
|
161
|
-
authoritativeLedger: {
|
|
162
|
-
...input.initialLedger,
|
|
163
|
-
status: "rejected",
|
|
164
|
-
runtimeChecks: [
|
|
165
|
-
...input.initialLedger.runtimeChecks,
|
|
166
|
-
{ id: "finalization-process", status: "failed", message: input.message },
|
|
167
|
-
],
|
|
168
|
-
},
|
|
169
|
-
turns: input.turns,
|
|
170
|
-
status: "failed",
|
|
171
|
-
maxTurns: input.maxTurns,
|
|
172
|
-
});
|
|
173
|
-
}
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AcceptanceReport,
|
|
3
|
-
} from "../../shared/types.ts";
|
|
4
|
-
|
|
5
|
-
function extractBalancedJson(text: string, start: number): string | undefined {
|
|
6
|
-
let depth = 0;
|
|
7
|
-
let inString = false;
|
|
8
|
-
let escaped = false;
|
|
9
|
-
for (let i = start; i < text.length; i++) {
|
|
10
|
-
const char = text[i]!;
|
|
11
|
-
if (inString) {
|
|
12
|
-
if (escaped) escaped = false;
|
|
13
|
-
else if (char === "\\") escaped = true;
|
|
14
|
-
else if (char === "\"") inString = false;
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
17
|
-
if (char === "\"") {
|
|
18
|
-
inString = true;
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
if (char === "{") depth++;
|
|
22
|
-
if (char === "}") {
|
|
23
|
-
depth--;
|
|
24
|
-
if (depth === 0) return text.slice(start, i + 1);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function parseAcceptanceReport(output: string): { report?: AcceptanceReport; error?: string } {
|
|
31
|
-
const fenced = [...output.matchAll(/```acceptance-report\s*\n([\s\S]*?)```/gi)]
|
|
32
|
-
.map((match) => match[1]?.trim())
|
|
33
|
-
.filter((value): value is string => Boolean(value));
|
|
34
|
-
const parseErrors: string[] = [];
|
|
35
|
-
for (const body of fenced) {
|
|
36
|
-
try {
|
|
37
|
-
const parsed = JSON.parse(body) as unknown;
|
|
38
|
-
const report = (parsed && typeof parsed === "object" && "acceptance" in parsed)
|
|
39
|
-
? (parsed as { acceptance?: unknown }).acceptance
|
|
40
|
-
: parsed;
|
|
41
|
-
if (isAcceptanceReport(report)) return { report };
|
|
42
|
-
parseErrors.push("acceptance-report block does not contain a valid acceptance report");
|
|
43
|
-
} catch (error) {
|
|
44
|
-
parseErrors.push(error instanceof Error ? error.message : String(error));
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
if (parseErrors.length > 0) return { error: `Failed to parse acceptance-report: ${parseErrors.join("; ")}` };
|
|
48
|
-
const markerIndex = output.search(/ACCEPTANCE_REPORT\s*:/i);
|
|
49
|
-
if (markerIndex !== -1) {
|
|
50
|
-
const jsonStart = output.indexOf("{", markerIndex);
|
|
51
|
-
if (jsonStart !== -1) {
|
|
52
|
-
const json = extractBalancedJson(output, jsonStart);
|
|
53
|
-
if (json) {
|
|
54
|
-
try {
|
|
55
|
-
const parsed = JSON.parse(json) as unknown;
|
|
56
|
-
if (isAcceptanceReport(parsed)) return { report: parsed };
|
|
57
|
-
} catch (error) {
|
|
58
|
-
return { error: error instanceof Error ? error.message : String(error) };
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return { error: "Structured acceptance report not found." };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function stripAcceptanceReport(output: string): string {
|
|
67
|
-
return output
|
|
68
|
-
.replace(/\n?```acceptance-report\s*\n[\s\S]*?```\s*$/i, "")
|
|
69
|
-
.replace(/\n?ACCEPTANCE_REPORT\s*:\s*\{[\s\S]*\}\s*$/i, "")
|
|
70
|
-
.trimEnd();
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function isStringArray(value: unknown): value is string[] {
|
|
74
|
-
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function isCriterionReport(value: unknown): value is NonNullable<AcceptanceReport["criteriaSatisfied"]>[number] {
|
|
78
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
79
|
-
const criterion = value as { id?: unknown; status?: unknown; evidence?: unknown };
|
|
80
|
-
if (criterion.id !== undefined && typeof criterion.id !== "string") return false;
|
|
81
|
-
if (criterion.status !== "satisfied" && criterion.status !== "not-satisfied" && criterion.status !== "not-applicable") return false;
|
|
82
|
-
return typeof criterion.evidence === "string" && criterion.evidence.trim().length > 0;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function isCommandReport(value: unknown): value is NonNullable<AcceptanceReport["commandsRun"]>[number] {
|
|
86
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
87
|
-
const command = value as { command?: unknown; result?: unknown; summary?: unknown };
|
|
88
|
-
return typeof command.command === "string"
|
|
89
|
-
&& (command.result === "passed" || command.result === "failed" || command.result === "not-run")
|
|
90
|
-
&& typeof command.summary === "string";
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function isAcceptanceReport(value: unknown): value is AcceptanceReport {
|
|
94
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
95
|
-
const report = value as {
|
|
96
|
-
criteriaSatisfied?: unknown;
|
|
97
|
-
changedFiles?: unknown;
|
|
98
|
-
testsAddedOrUpdated?: unknown;
|
|
99
|
-
commandsRun?: unknown;
|
|
100
|
-
validationOutput?: unknown;
|
|
101
|
-
residualRisks?: unknown;
|
|
102
|
-
noStagedFiles?: unknown;
|
|
103
|
-
diffSummary?: unknown;
|
|
104
|
-
reviewFindings?: unknown;
|
|
105
|
-
manualNotes?: unknown;
|
|
106
|
-
notes?: unknown;
|
|
107
|
-
};
|
|
108
|
-
if (report.criteriaSatisfied !== undefined && (!Array.isArray(report.criteriaSatisfied) || !report.criteriaSatisfied.every(isCriterionReport))) return false;
|
|
109
|
-
if (report.changedFiles !== undefined && !isStringArray(report.changedFiles)) return false;
|
|
110
|
-
if (report.testsAddedOrUpdated !== undefined && !isStringArray(report.testsAddedOrUpdated)) return false;
|
|
111
|
-
if (report.commandsRun !== undefined && (!Array.isArray(report.commandsRun) || !report.commandsRun.every(isCommandReport))) return false;
|
|
112
|
-
if (report.validationOutput !== undefined && !isStringArray(report.validationOutput)) return false;
|
|
113
|
-
if (report.residualRisks !== undefined && !isStringArray(report.residualRisks)) return false;
|
|
114
|
-
if (report.noStagedFiles !== undefined && typeof report.noStagedFiles !== "boolean") return false;
|
|
115
|
-
if (report.diffSummary !== undefined && typeof report.diffSummary !== "string") return false;
|
|
116
|
-
if (report.reviewFindings !== undefined && !isStringArray(report.reviewFindings)) return false;
|
|
117
|
-
if (report.manualNotes !== undefined && typeof report.manualNotes !== "string") return false;
|
|
118
|
-
if (report.notes !== undefined && typeof report.notes !== "string") return false;
|
|
119
|
-
return report.criteriaSatisfied !== undefined
|
|
120
|
-
|| report.changedFiles !== undefined
|
|
121
|
-
|| report.testsAddedOrUpdated !== undefined
|
|
122
|
-
|| report.commandsRun !== undefined
|
|
123
|
-
|| report.residualRisks !== undefined
|
|
124
|
-
|| report.manualNotes !== undefined
|
|
125
|
-
|| report.reviewFindings !== undefined;
|
|
126
|
-
}
|
|
127
|
-
|