pi-subagents 0.25.0 → 0.27.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 +21 -0
- package/README.md +129 -17
- package/package.json +1 -1
- package/prompts/parallel-context-build.md +3 -1
- package/prompts/parallel-handoff-plan.md +3 -1
- package/skills/pi-subagents/SKILL.md +32 -17
- package/src/agents/agent-management.ts +57 -15
- package/src/agents/agent-serializer.ts +3 -2
- package/src/agents/agents.ts +47 -16
- package/src/agents/chain-serializer.ts +120 -0
- package/src/extension/fanout-child.ts +1 -0
- package/src/extension/index.ts +1 -0
- package/src/extension/schemas.ts +138 -5
- package/src/runs/background/async-execution.ts +84 -6
- package/src/runs/background/async-status.ts +11 -1
- package/src/runs/background/run-status.ts +10 -1
- package/src/runs/background/subagent-runner.ts +600 -31
- package/src/runs/foreground/chain-execution.ts +325 -118
- package/src/runs/foreground/execution.ts +222 -10
- package/src/runs/foreground/subagent-executor.ts +67 -0
- package/src/runs/shared/acceptance-contract.ts +291 -0
- package/src/runs/shared/acceptance-evaluation.ts +221 -0
- package/src/runs/shared/acceptance-finalization.ts +161 -0
- package/src/runs/shared/acceptance-reports.ts +127 -0
- package/src/runs/shared/acceptance.ts +22 -0
- package/src/runs/shared/chain-outputs.ts +101 -0
- package/src/runs/shared/completion-guard.ts +26 -3
- package/src/runs/shared/dynamic-fanout.ts +293 -0
- package/src/runs/shared/parallel-utils.ts +31 -1
- package/src/runs/shared/pi-args.ts +11 -0
- package/src/runs/shared/structured-output.ts +77 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +53 -3
- package/src/runs/shared/workflow-graph.ts +206 -0
- package/src/shared/formatters.ts +2 -2
- package/src/shared/settings.ts +53 -4
- package/src/shared/types.ts +250 -0
- package/src/slash/slash-commands.ts +41 -3
- package/src/tui/render.ts +162 -34
package/src/shared/types.ts
CHANGED
|
@@ -19,6 +19,51 @@ export interface MaxOutputConfig {
|
|
|
19
19
|
|
|
20
20
|
export type OutputMode = "inline" | "file-only";
|
|
21
21
|
|
|
22
|
+
export type JsonSchemaObject = Record<string, unknown>;
|
|
23
|
+
|
|
24
|
+
export interface ChainOutputMapEntry {
|
|
25
|
+
text: string;
|
|
26
|
+
structured?: unknown;
|
|
27
|
+
agent: string;
|
|
28
|
+
stepIndex: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type ChainOutputMap = Record<string, ChainOutputMapEntry>;
|
|
32
|
+
|
|
33
|
+
export type WorkflowNodeStatus = "pending" | "running" | "completed" | "failed" | "paused" | "detached";
|
|
34
|
+
|
|
35
|
+
export interface WorkflowGraphNode {
|
|
36
|
+
id: string;
|
|
37
|
+
kind: "step" | "parallel-group" | "dynamic-parallel-group" | "agent";
|
|
38
|
+
agent?: string;
|
|
39
|
+
phase?: string;
|
|
40
|
+
label: string;
|
|
41
|
+
status: WorkflowNodeStatus;
|
|
42
|
+
flatIndex?: number;
|
|
43
|
+
stepIndex?: number;
|
|
44
|
+
children?: WorkflowGraphNode[];
|
|
45
|
+
dynamic?: {
|
|
46
|
+
sourceOutput: string;
|
|
47
|
+
sourcePath: string;
|
|
48
|
+
itemName: string;
|
|
49
|
+
maxItems?: number;
|
|
50
|
+
collectAs?: string;
|
|
51
|
+
};
|
|
52
|
+
itemKey?: string;
|
|
53
|
+
outputName?: string;
|
|
54
|
+
structured?: boolean;
|
|
55
|
+
acceptanceStatus?: AcceptanceLedgerStatus;
|
|
56
|
+
error?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface WorkflowGraphSnapshot {
|
|
60
|
+
runId: string;
|
|
61
|
+
mode: "chain" | "parallel" | "single";
|
|
62
|
+
phases: Array<{ title: string; nodeIds: string[] }>;
|
|
63
|
+
nodes: WorkflowGraphNode[];
|
|
64
|
+
currentNodeId?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
22
67
|
export interface SavedOutputReference {
|
|
23
68
|
path: string;
|
|
24
69
|
bytes: number;
|
|
@@ -194,6 +239,175 @@ export interface ModelAttempt {
|
|
|
194
239
|
usage?: Usage;
|
|
195
240
|
}
|
|
196
241
|
|
|
242
|
+
export type AcceptanceProvenanceLevel = "none" | "attested" | "checked" | "verified" | "reviewed";
|
|
243
|
+
|
|
244
|
+
export type AcceptanceEvidenceKind =
|
|
245
|
+
| "changed-files"
|
|
246
|
+
| "tests-added"
|
|
247
|
+
| "commands-run"
|
|
248
|
+
| "validation-output"
|
|
249
|
+
| "residual-risks"
|
|
250
|
+
| "no-staged-files"
|
|
251
|
+
| "diff-summary"
|
|
252
|
+
| "review-findings"
|
|
253
|
+
| "manual-notes";
|
|
254
|
+
|
|
255
|
+
export interface AcceptanceGate {
|
|
256
|
+
id: string;
|
|
257
|
+
must: string;
|
|
258
|
+
evidence?: AcceptanceEvidenceKind[];
|
|
259
|
+
severity?: "required" | "recommended";
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export interface AcceptanceVerifyCommand {
|
|
263
|
+
id: string;
|
|
264
|
+
command: string;
|
|
265
|
+
timeoutMs?: number;
|
|
266
|
+
cwd?: string;
|
|
267
|
+
env?: Record<string, string>;
|
|
268
|
+
allowFailure?: boolean;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export interface AcceptanceReviewGate {
|
|
272
|
+
agent?: string;
|
|
273
|
+
focus?: string;
|
|
274
|
+
required?: boolean;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export interface AcceptanceConfig {
|
|
278
|
+
criteria?: Array<string | AcceptanceGate>;
|
|
279
|
+
evidence?: AcceptanceEvidenceKind[];
|
|
280
|
+
verify?: AcceptanceVerifyCommand[];
|
|
281
|
+
review?: AcceptanceReviewGate;
|
|
282
|
+
stopRules?: string[];
|
|
283
|
+
maxFinalizationTurns?: number;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export type AcceptanceInput = AcceptanceConfig;
|
|
287
|
+
|
|
288
|
+
export interface ResolvedAcceptanceGate extends AcceptanceGate {
|
|
289
|
+
id: string;
|
|
290
|
+
must: string;
|
|
291
|
+
evidence: AcceptanceEvidenceKind[];
|
|
292
|
+
severity: "required" | "recommended";
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export interface ResolvedAcceptanceConfig {
|
|
296
|
+
level: AcceptanceProvenanceLevel;
|
|
297
|
+
explicit: boolean;
|
|
298
|
+
inferredReason: string[];
|
|
299
|
+
criteria: ResolvedAcceptanceGate[];
|
|
300
|
+
evidence: AcceptanceEvidenceKind[];
|
|
301
|
+
verify: AcceptanceVerifyCommand[];
|
|
302
|
+
review?: AcceptanceReviewGate;
|
|
303
|
+
stopRules: string[];
|
|
304
|
+
finalization: {
|
|
305
|
+
mode: "none" | "self-review-loop";
|
|
306
|
+
maxTurns: number;
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export interface AcceptanceReport {
|
|
311
|
+
criteriaSatisfied?: Array<{
|
|
312
|
+
id?: string;
|
|
313
|
+
status: "satisfied" | "not-satisfied" | "not-applicable";
|
|
314
|
+
evidence: string;
|
|
315
|
+
}>;
|
|
316
|
+
changedFiles?: string[];
|
|
317
|
+
testsAddedOrUpdated?: string[];
|
|
318
|
+
commandsRun?: Array<{
|
|
319
|
+
command: string;
|
|
320
|
+
result: "passed" | "failed" | "not-run";
|
|
321
|
+
summary: string;
|
|
322
|
+
}>;
|
|
323
|
+
validationOutput?: string[];
|
|
324
|
+
residualRisks?: string[];
|
|
325
|
+
noStagedFiles?: boolean;
|
|
326
|
+
diffSummary?: string;
|
|
327
|
+
reviewFindings?: string[];
|
|
328
|
+
manualNotes?: string;
|
|
329
|
+
notes?: string;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export type AcceptanceRuntimeCheckStatus = "passed" | "failed" | "not-applicable";
|
|
333
|
+
|
|
334
|
+
export interface AcceptanceRuntimeCheck {
|
|
335
|
+
id: string;
|
|
336
|
+
status: AcceptanceRuntimeCheckStatus;
|
|
337
|
+
message: string;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export interface AcceptanceVerifyResult {
|
|
341
|
+
id: string;
|
|
342
|
+
command: string;
|
|
343
|
+
cwd?: string;
|
|
344
|
+
exitCode: number | null;
|
|
345
|
+
status: "passed" | "failed" | "timed-out" | "allowed-failure";
|
|
346
|
+
stdout?: string;
|
|
347
|
+
stderr?: string;
|
|
348
|
+
durationMs: number;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export interface AcceptanceReviewResult {
|
|
352
|
+
status: "no-blockers" | "blockers" | "needs-parent-decision";
|
|
353
|
+
findings: Array<{
|
|
354
|
+
severity: "blocker" | "non-blocking";
|
|
355
|
+
file?: string;
|
|
356
|
+
issue: string;
|
|
357
|
+
rationale: string;
|
|
358
|
+
}>;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export type AcceptanceLedgerStatus =
|
|
362
|
+
| "not-required"
|
|
363
|
+
| "claimed"
|
|
364
|
+
| "attested"
|
|
365
|
+
| "checked"
|
|
366
|
+
| "verified"
|
|
367
|
+
| "reviewed"
|
|
368
|
+
| "accepted"
|
|
369
|
+
| "rejected";
|
|
370
|
+
|
|
371
|
+
export interface AcceptanceFinalizationTurn {
|
|
372
|
+
turn: number;
|
|
373
|
+
prompt: string;
|
|
374
|
+
status: AcceptanceLedgerStatus;
|
|
375
|
+
rawOutput?: string;
|
|
376
|
+
report?: AcceptanceReport;
|
|
377
|
+
parseError?: string;
|
|
378
|
+
runtimeChecks: AcceptanceRuntimeCheck[];
|
|
379
|
+
verifyRuns: AcceptanceVerifyResult[];
|
|
380
|
+
failureMessage?: string;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export interface AcceptanceFinalizationLedger {
|
|
384
|
+
mode: "self-review-loop";
|
|
385
|
+
status: "not-run" | "completed" | "failed";
|
|
386
|
+
maxTurns: number;
|
|
387
|
+
turns: AcceptanceFinalizationTurn[];
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export interface AcceptanceLedger {
|
|
391
|
+
status: AcceptanceLedgerStatus;
|
|
392
|
+
explicit: boolean;
|
|
393
|
+
effectiveAcceptance: ResolvedAcceptanceConfig;
|
|
394
|
+
inferredReason: string[];
|
|
395
|
+
criteria: ResolvedAcceptanceGate[];
|
|
396
|
+
childReport?: AcceptanceReport;
|
|
397
|
+
childReportParseError?: string;
|
|
398
|
+
initialChildReport?: AcceptanceReport;
|
|
399
|
+
initialChildReportParseError?: string;
|
|
400
|
+
runtimeChecks: AcceptanceRuntimeCheck[];
|
|
401
|
+
verifyRuns: AcceptanceVerifyResult[];
|
|
402
|
+
reviewResult?: AcceptanceReviewResult;
|
|
403
|
+
finalization?: AcceptanceFinalizationLedger;
|
|
404
|
+
parentDecision?: {
|
|
405
|
+
status: "accepted" | "rejected";
|
|
406
|
+
at: string;
|
|
407
|
+
reason?: string;
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
197
411
|
export interface SingleResult {
|
|
198
412
|
agent: string;
|
|
199
413
|
task: string;
|
|
@@ -221,6 +435,10 @@ export interface SingleResult {
|
|
|
221
435
|
savedOutputPath?: string;
|
|
222
436
|
outputReference?: SavedOutputReference;
|
|
223
437
|
outputSaveError?: string;
|
|
438
|
+
structuredOutput?: unknown;
|
|
439
|
+
structuredOutputPath?: string;
|
|
440
|
+
structuredOutputSchemaPath?: string;
|
|
441
|
+
acceptance?: AcceptanceLedger;
|
|
224
442
|
}
|
|
225
443
|
|
|
226
444
|
export interface Details {
|
|
@@ -247,6 +465,8 @@ export interface Details {
|
|
|
247
465
|
chainAgents?: string[]; // Agent names in order, e.g., ["scout", "planner"]
|
|
248
466
|
totalSteps?: number; // Total steps in chain
|
|
249
467
|
currentStepIndex?: number; // 0-indexed current step (for running chains)
|
|
468
|
+
workflowGraph?: WorkflowGraphSnapshot;
|
|
469
|
+
outputs?: ChainOutputMap;
|
|
250
470
|
}
|
|
251
471
|
|
|
252
472
|
// ============================================================================
|
|
@@ -360,6 +580,7 @@ export interface AsyncStartedEvent {
|
|
|
360
580
|
chain?: string[];
|
|
361
581
|
chainStepCount?: number;
|
|
362
582
|
parallelGroups?: AsyncParallelGroupStatus[];
|
|
583
|
+
workflowGraph?: WorkflowGraphSnapshot;
|
|
363
584
|
nestedRoute?: NestedRouteInfo;
|
|
364
585
|
}
|
|
365
586
|
|
|
@@ -383,8 +604,13 @@ export interface AsyncStatus {
|
|
|
383
604
|
currentStep?: number;
|
|
384
605
|
chainStepCount?: number;
|
|
385
606
|
parallelGroups?: AsyncParallelGroupStatus[];
|
|
607
|
+
workflowGraph?: WorkflowGraphSnapshot;
|
|
386
608
|
steps?: Array<{
|
|
387
609
|
agent: string;
|
|
610
|
+
phase?: string;
|
|
611
|
+
label?: string;
|
|
612
|
+
outputName?: string;
|
|
613
|
+
structured?: boolean;
|
|
388
614
|
status: "pending" | "running" | "complete" | "completed" | "failed" | "paused";
|
|
389
615
|
children?: NestedRunSummary[];
|
|
390
616
|
sessionFile?: string;
|
|
@@ -409,11 +635,16 @@ export interface AsyncStatus {
|
|
|
409
635
|
attemptedModels?: string[];
|
|
410
636
|
modelAttempts?: ModelAttempt[];
|
|
411
637
|
error?: string;
|
|
638
|
+
structuredOutput?: unknown;
|
|
639
|
+
structuredOutputPath?: string;
|
|
640
|
+
structuredOutputSchemaPath?: string;
|
|
641
|
+
acceptance?: AcceptanceLedger;
|
|
412
642
|
}>;
|
|
413
643
|
sessionDir?: string;
|
|
414
644
|
outputFile?: string;
|
|
415
645
|
totalTokens?: TokenUsage;
|
|
416
646
|
sessionFile?: string;
|
|
647
|
+
outputs?: ChainOutputMap;
|
|
417
648
|
}
|
|
418
649
|
|
|
419
650
|
export type AsyncJobStep = NonNullable<AsyncStatus["steps"]>[number] & {
|
|
@@ -576,6 +807,18 @@ export interface RunSyncOptions {
|
|
|
576
807
|
preferredModelProvider?: string;
|
|
577
808
|
/** Skills to inject (overrides agent default if provided) */
|
|
578
809
|
skills?: string[];
|
|
810
|
+
structuredOutput?: {
|
|
811
|
+
schema: JsonSchemaObject;
|
|
812
|
+
schemaPath: string;
|
|
813
|
+
outputPath: string;
|
|
814
|
+
};
|
|
815
|
+
acceptance?: AcceptanceInput;
|
|
816
|
+
acceptanceContext?: {
|
|
817
|
+
mode?: SubagentRunMode;
|
|
818
|
+
async?: boolean;
|
|
819
|
+
dynamic?: boolean;
|
|
820
|
+
dynamicGroup?: boolean;
|
|
821
|
+
};
|
|
579
822
|
}
|
|
580
823
|
|
|
581
824
|
export type IntercomBridgeMode = "off" | "fork-only" | "always";
|
|
@@ -590,6 +833,12 @@ interface TopLevelParallelConfig {
|
|
|
590
833
|
concurrency?: number;
|
|
591
834
|
}
|
|
592
835
|
|
|
836
|
+
interface ExtensionChainConfig {
|
|
837
|
+
dynamicFanout?: {
|
|
838
|
+
maxItems?: number;
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
|
|
593
842
|
export interface ExtensionConfig {
|
|
594
843
|
asyncByDefault?: boolean;
|
|
595
844
|
forceTopLevelAsync?: boolean;
|
|
@@ -597,6 +846,7 @@ export interface ExtensionConfig {
|
|
|
597
846
|
maxSubagentDepth?: number;
|
|
598
847
|
control?: ControlConfig;
|
|
599
848
|
parallel?: TopLevelParallelConfig;
|
|
849
|
+
chain?: ExtensionChainConfig;
|
|
600
850
|
worktreeSetupHook?: string;
|
|
601
851
|
worktreeSetupHookTimeoutMs?: number;
|
|
602
852
|
intercomBridge?: IntercomBridgeConfig;
|
|
@@ -5,7 +5,8 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
|
|
|
5
5
|
import { Key, matchesKey } from "@earendil-works/pi-tui";
|
|
6
6
|
import { discoverAgents, discoverAgentsAll, type ChainConfig } from "../agents/agents.ts";
|
|
7
7
|
import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
8
|
-
import { isParallelStep, type ChainStep } from "../shared/settings.ts";
|
|
8
|
+
import { isDynamicParallelStep, isParallelStep, type ChainStep } from "../shared/settings.ts";
|
|
9
|
+
import { assertJsonSchemaObject } from "../runs/shared/structured-output.ts";
|
|
9
10
|
import type { SlashSubagentResponse, SlashSubagentUpdate } from "./slash-bridge.ts";
|
|
10
11
|
import {
|
|
11
12
|
applySlashUpdate,
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
SLASH_SUBAGENT_RESPONSE_EVENT,
|
|
21
22
|
SLASH_SUBAGENT_STARTED_EVENT,
|
|
22
23
|
SLASH_SUBAGENT_UPDATE_EVENT,
|
|
24
|
+
type JsonSchemaObject,
|
|
23
25
|
type SingleResult,
|
|
24
26
|
type SubagentState,
|
|
25
27
|
} from "../shared/types.ts";
|
|
@@ -123,12 +125,48 @@ const makeChainCompletions = (state: SubagentState) => (prefix: string) => {
|
|
|
123
125
|
.map((chain) => ({ value: chain.name, label: chain.name }));
|
|
124
126
|
};
|
|
125
127
|
|
|
128
|
+
function loadSavedOutputSchema(chain: ChainConfig, stepAgent: string, outputSchema: unknown): JsonSchemaObject | undefined {
|
|
129
|
+
if (outputSchema === undefined) return undefined;
|
|
130
|
+
if (typeof outputSchema === "string") {
|
|
131
|
+
const schemaPath = path.isAbsolute(outputSchema)
|
|
132
|
+
? outputSchema
|
|
133
|
+
: path.join(path.dirname(chain.filePath), outputSchema);
|
|
134
|
+
const parsed = JSON.parse(fs.readFileSync(schemaPath, "utf-8")) as unknown;
|
|
135
|
+
assertJsonSchemaObject(parsed, `outputSchema for chain '${chain.name}' step '${stepAgent}' (${schemaPath})`);
|
|
136
|
+
return parsed;
|
|
137
|
+
}
|
|
138
|
+
assertJsonSchemaObject(outputSchema, `outputSchema for chain '${chain.name}' step '${stepAgent}'`);
|
|
139
|
+
return outputSchema;
|
|
140
|
+
}
|
|
141
|
+
|
|
126
142
|
const mapSavedChainSteps = (chain: ChainConfig, worktree = false): ChainStep[] => {
|
|
127
|
-
return (chain.steps as Array<ChainStep & { skills?: string[] | false }>).map((step) => {
|
|
128
|
-
if (isParallelStep(step))
|
|
143
|
+
return (chain.steps as unknown as Array<ChainStep & { skills?: string[] | false }>).map((step) => {
|
|
144
|
+
if (isParallelStep(step)) {
|
|
145
|
+
const parallel = step.parallel.map((task) => {
|
|
146
|
+
const { outputSchema: rawOutputSchema, ...rest } = task as typeof task & { outputSchema?: unknown };
|
|
147
|
+
const outputSchema = loadSavedOutputSchema(chain, task.agent, rawOutputSchema);
|
|
148
|
+
return { ...rest, ...(outputSchema ? { outputSchema } : {}) };
|
|
149
|
+
});
|
|
150
|
+
return { ...step, parallel, ...(worktree ? { worktree: true } : {}) };
|
|
151
|
+
}
|
|
152
|
+
if (isDynamicParallelStep(step)) {
|
|
153
|
+
const { outputSchema: rawOutputSchema, ...parallelRest } = step.parallel as typeof step.parallel & { outputSchema?: unknown };
|
|
154
|
+
const outputSchema = loadSavedOutputSchema(chain, step.parallel.agent, rawOutputSchema);
|
|
155
|
+
const collectSchema = loadSavedOutputSchema(chain, `${step.collect.as} collection`, step.collect.outputSchema);
|
|
156
|
+
return {
|
|
157
|
+
...step,
|
|
158
|
+
parallel: { ...parallelRest, ...(outputSchema ? { outputSchema } : {}) },
|
|
159
|
+
collect: { ...step.collect, ...(collectSchema ? { outputSchema: collectSchema } : {}) },
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const outputSchema = loadSavedOutputSchema(chain, step.agent, (step as { outputSchema?: unknown }).outputSchema);
|
|
129
163
|
return {
|
|
130
164
|
agent: step.agent,
|
|
131
165
|
task: step.task || undefined,
|
|
166
|
+
...(step.phase ? { phase: step.phase } : {}),
|
|
167
|
+
...(step.label ? { label: step.label } : {}),
|
|
168
|
+
...(step.as ? { as: step.as } : {}),
|
|
169
|
+
...(outputSchema ? { outputSchema } : {}),
|
|
132
170
|
output: step.output,
|
|
133
171
|
outputMode: step.outputMode,
|
|
134
172
|
reads: step.reads,
|