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
package/src/extension/schemas.ts
CHANGED
|
@@ -78,30 +78,42 @@ const AcceptanceReviewGateSchema = Type.Object({
|
|
|
78
78
|
}, { additionalProperties: false });
|
|
79
79
|
|
|
80
80
|
const AcceptanceOverride = Type.Unsafe({
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
81
|
+
anyOf: [
|
|
82
|
+
{ type: "string", enum: ["auto", "none", "attested", "checked", "verified", "reviewed"] },
|
|
83
|
+
{ const: false },
|
|
84
|
+
{
|
|
85
|
+
type: "object",
|
|
86
|
+
properties: {
|
|
87
|
+
level: { type: "string", enum: ["auto", "none", "attested", "checked", "verified", "reviewed"] },
|
|
88
|
+
criteria: {
|
|
89
|
+
type: "array",
|
|
90
|
+
items: {
|
|
91
|
+
anyOf: [
|
|
92
|
+
{ type: "string" },
|
|
93
|
+
AcceptanceGateSchema,
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
evidence: { type: "array", items: AcceptanceEvidenceKind },
|
|
98
|
+
verify: { type: "array", items: AcceptanceVerifyCommandSchema },
|
|
99
|
+
review: {
|
|
100
|
+
anyOf: [
|
|
101
|
+
{ const: false },
|
|
102
|
+
AcceptanceReviewGateSchema,
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
stopRules: { type: "array", items: { type: "string" } },
|
|
106
|
+
reason: { type: "string" },
|
|
90
107
|
},
|
|
108
|
+
additionalProperties: false,
|
|
91
109
|
},
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
review: AcceptanceReviewGateSchema,
|
|
95
|
-
stopRules: { type: "array", items: { type: "string" } },
|
|
96
|
-
maxFinalizationTurns: { type: "integer", minimum: 1, maximum: 10 },
|
|
97
|
-
},
|
|
98
|
-
additionalProperties: false,
|
|
99
|
-
description: "Optional acceptance contract. Use this for goal-style requests and for implementation handoffs from plans, PRDs, specs, issues, or broad fixes. Put implementation instructions and plan paths in task; put the definition of done in criteria, proof in evidence/verify, constraints in stopRules, and the bounded loop budget in maxFinalizationTurns. Runtime validation still requires at least one of criteria, evidence, verify, review, or stopRules. When present, the child must complete a same-session self-review/repair loop before acceptance is evaluated.",
|
|
110
|
+
],
|
|
111
|
+
description: "Optional acceptance policy. Omitted means auto-inferred; verified requires configured runtime commands.",
|
|
100
112
|
});
|
|
101
113
|
|
|
102
114
|
const TaskItem = Type.Object({
|
|
103
|
-
agent: Type.String(),
|
|
104
|
-
task: Type.String(),
|
|
115
|
+
agent: Type.String(),
|
|
116
|
+
task: Type.String(),
|
|
105
117
|
cwd: Type.Optional(Type.String()),
|
|
106
118
|
count: Type.Optional(Type.Integer({ minimum: 1, description: "Repeat this parallel task N times with the same settings." })),
|
|
107
119
|
output: Type.Optional(OutputOverride),
|
|
@@ -250,12 +262,10 @@ export const SubagentParams = Type.Object({
|
|
|
250
262
|
{ type: "object", additionalProperties: true },
|
|
251
263
|
{ type: "string" },
|
|
252
264
|
],
|
|
253
|
-
description: "Agent or chain config for create/update. Agent: name, package (optional namespace; runtime name becomes package.name), description, scope ('user'|'project', default 'user'), systemPrompt, systemPromptMode, inheritProjectContext, inheritSkills, defaultContext ('fresh'|'fork'), model, tools (comma-separated), extensions (comma-separated), skills (comma-separated), thinking, output, reads, progress, maxSubagentDepth
|
|
265
|
+
description: "Agent or chain config for create/update. Agent: name, package (optional namespace; runtime name becomes package.name), description, scope ('user'|'project', default 'user'), systemPrompt, systemPromptMode, inheritProjectContext, inheritSkills, defaultContext ('fresh'|'fork'), model, tools (comma-separated), extensions (comma-separated), skills (comma-separated), thinking, output, reads, progress, maxSubagentDepth. Chain: name, package, description, scope, steps (array of {agent, task?, output?, outputMode?, reads?, model?, skill?, progress?}). Presence of 'steps' creates a chain instead of an agent. String values must be valid JSON."
|
|
254
266
|
})),
|
|
255
267
|
tasks: Type.Optional(Type.Array(TaskItem, { description: "PARALLEL mode: [{agent, task, count?, output?, outputMode?, reads?, progress?}, ...]" })),
|
|
256
268
|
concurrency: Type.Optional(Type.Integer({ minimum: 1, description: "Top-level PARALLEL mode only: max concurrent tasks. Defaults to config.parallel.concurrency or 4." })),
|
|
257
|
-
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Foreground execution wall-clock timeout in milliseconds. When it expires, running children are soft-interrupted and timed-out results are returned. Foreground only; async/background runs ignore this field." })),
|
|
258
|
-
maxRuntimeMs: Type.Optional(Type.Integer({ minimum: 1, description: "Alias for timeoutMs. Use only one unless both values are identical." })),
|
|
259
269
|
worktree: Type.Optional(Type.Boolean({
|
|
260
270
|
description: "Create isolated git worktrees for each parallel task. " +
|
|
261
271
|
"Prevents filesystem conflicts. Requires clean git state. " +
|
|
@@ -17,6 +17,16 @@ function defaultIntercomExtensionDir(agentDir = defaultAgentDir()): string {
|
|
|
17
17
|
return path.join(agentDir, "extensions", PI_INTERCOM_PACKAGE_NAME);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export const INTERCOM_EXTENSION_DIR_ENV = "PI_INTERCOM_EXTENSION_DIR";
|
|
21
|
+
|
|
22
|
+
// Launcher-provided override for the pi-intercom package directory. Lets a hermetic
|
|
23
|
+
// wrapper point the subagent intercom bridge at a read-only install (e.g. a Nix-store
|
|
24
|
+
// path) instead of seeding the package into the writable agent dir.
|
|
25
|
+
function envIntercomExtensionDir(): string | undefined {
|
|
26
|
+
const dir = process.env[INTERCOM_EXTENSION_DIR_ENV]?.trim();
|
|
27
|
+
return dir ? dir : undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
20
30
|
function defaultIntercomConfigPath(agentDir = defaultAgentDir()): string {
|
|
21
31
|
return path.join(agentDir, "intercom", "config.json");
|
|
22
32
|
}
|
|
@@ -224,7 +234,7 @@ function configuredPiIntercomPackageDir(input: ResolveIntercomBridgeInput, agent
|
|
|
224
234
|
}
|
|
225
235
|
|
|
226
236
|
function resolveIntercomExtensionDir(input: ResolveIntercomBridgeInput, agentDir: string): string {
|
|
227
|
-
const legacyDir = path.resolve(input.extensionDir ?? defaultIntercomExtensionDir(agentDir));
|
|
237
|
+
const legacyDir = path.resolve(input.extensionDir ?? envIntercomExtensionDir() ?? defaultIntercomExtensionDir(agentDir));
|
|
228
238
|
if (fs.existsSync(legacyDir)) return legacyDir;
|
|
229
239
|
return configuredPiIntercomPackageDir(input, agentDir) ?? legacyDir;
|
|
230
240
|
}
|
|
@@ -20,10 +20,8 @@ export function resolveSubagentResultStatus(input: {
|
|
|
20
20
|
state?: string;
|
|
21
21
|
interrupted?: boolean;
|
|
22
22
|
detached?: boolean;
|
|
23
|
-
timedOut?: boolean;
|
|
24
23
|
}): SubagentResultStatus {
|
|
25
24
|
if (input.detached) return "detached";
|
|
26
|
-
if (input.timedOut || input.state === "timed-out") return "timed-out";
|
|
27
25
|
if (input.interrupted || input.state === "paused") return "paused";
|
|
28
26
|
if (typeof input.success === "boolean") return input.success ? "completed" : "failed";
|
|
29
27
|
if (input.state === "complete") return "completed";
|
|
@@ -38,7 +36,6 @@ function countStatuses(children: SubagentResultIntercomChild[]): Record<Subagent
|
|
|
38
36
|
failed: 0,
|
|
39
37
|
paused: 0,
|
|
40
38
|
detached: 0,
|
|
41
|
-
"timed-out": 0,
|
|
42
39
|
};
|
|
43
40
|
for (const child of children) {
|
|
44
41
|
counts[child.status] += 1;
|
|
@@ -52,7 +49,6 @@ function formatStatusCounts(counts: Record<SubagentResultStatus, number>): strin
|
|
|
52
49
|
counts.failed ? `${counts.failed} failed` : undefined,
|
|
53
50
|
counts.paused ? `${counts.paused} paused` : undefined,
|
|
54
51
|
counts.detached ? `${counts.detached} detached` : undefined,
|
|
55
|
-
counts["timed-out"] ? `${counts["timed-out"]} timed out` : undefined,
|
|
56
52
|
].filter((part): part is string => Boolean(part));
|
|
57
53
|
return parts.length ? parts.join(", ") : "0 results";
|
|
58
54
|
}
|
|
@@ -60,7 +56,6 @@ function formatStatusCounts(counts: Record<SubagentResultStatus, number>): strin
|
|
|
60
56
|
function resolveGroupedStatus(children: SubagentResultIntercomChild[]): SubagentResultStatus {
|
|
61
57
|
const counts = countStatuses(children);
|
|
62
58
|
if (counts.failed > 0) return "failed";
|
|
63
|
-
if (counts["timed-out"] > 0) return "timed-out";
|
|
64
59
|
if (counts.paused > 0) return "paused";
|
|
65
60
|
if (counts.completed > 0) return "completed";
|
|
66
61
|
if (counts.detached > 0) return "detached";
|
|
@@ -17,7 +17,7 @@ import type { RunnerStep } from "../shared/parallel-utils.ts";
|
|
|
17
17
|
import { resolvePiPackageRoot } from "../shared/pi-spawn.ts";
|
|
18
18
|
import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "../../agents/skills.ts";
|
|
19
19
|
import { resolveChildCwd } from "../../shared/utils.ts";
|
|
20
|
-
import { buildModelCandidates, resolveModelCandidate, type AvailableModelInfo } from "../shared/model-fallback.ts";
|
|
20
|
+
import { buildModelCandidates, resolveModelCandidate, resolveSubagentModelOverride, type AvailableModelInfo, type ParentModel } from "../shared/model-fallback.ts";
|
|
21
21
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
22
22
|
import { resolveExpectedWorktreeAgentCwd } from "../shared/worktree.ts";
|
|
23
23
|
import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
|
|
@@ -95,6 +95,7 @@ interface AsyncExecutionContext {
|
|
|
95
95
|
cwd: string;
|
|
96
96
|
currentSessionId: string;
|
|
97
97
|
currentModelProvider?: string;
|
|
98
|
+
currentModel?: ParentModel;
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
interface AsyncChainParams {
|
|
@@ -342,7 +343,8 @@ export function executeAsyncChain(
|
|
|
342
343
|
taskTemplate = taskTemplate.replace(/\{chain_dir\}/g, runnerCwd);
|
|
343
344
|
const task = injectSingleOutputInstruction(`${readInstructions.prefix}${taskTemplate}${progressInstructions.suffix}`, outputPath);
|
|
344
345
|
|
|
345
|
-
const
|
|
346
|
+
const requestedModel = behavior.model ?? a.model;
|
|
347
|
+
const primaryModel = resolveSubagentModelOverride(requestedModel, ctx.currentModel, availableModels, ctx.currentModelProvider);
|
|
346
348
|
const model = applyThinkingSuffix(primaryModel, a.thinking);
|
|
347
349
|
return {
|
|
348
350
|
agent: s.agent,
|
|
@@ -354,7 +356,7 @@ export function executeAsyncChain(
|
|
|
354
356
|
cwd: stepCwd,
|
|
355
357
|
model,
|
|
356
358
|
thinking: resolveEffectiveThinking(model, a.thinking),
|
|
357
|
-
modelCandidates: buildModelCandidates(
|
|
359
|
+
modelCandidates: buildModelCandidates(primaryModel, a.fallbackModels, availableModels, ctx.currentModelProvider).map((candidate) =>
|
|
358
360
|
applyThinkingSuffix(candidate, a.thinking),
|
|
359
361
|
),
|
|
360
362
|
tools: a.tools,
|
|
@@ -370,8 +372,6 @@ export function executeAsyncChain(
|
|
|
370
372
|
outputMode: behavior.outputMode,
|
|
371
373
|
sessionFile,
|
|
372
374
|
maxSubagentDepth: resolveChildMaxSubagentDepth(maxSubagentDepth, a.maxSubagentDepth),
|
|
373
|
-
maxExecutionTimeMs: a.maxExecutionTimeMs,
|
|
374
|
-
maxTokens: a.maxTokens,
|
|
375
375
|
effectiveAcceptance: resolveEffectiveAcceptance({
|
|
376
376
|
explicit: s.acceptance,
|
|
377
377
|
agentName: s.agent,
|
|
@@ -438,6 +438,14 @@ export function executeAsyncChain(
|
|
|
438
438
|
failFast: s.failFast,
|
|
439
439
|
phase: s.phase,
|
|
440
440
|
label: s.label,
|
|
441
|
+
effectiveAcceptance: resolveEffectiveAcceptance({
|
|
442
|
+
explicit: s.acceptance,
|
|
443
|
+
agentName: s.parallel.agent,
|
|
444
|
+
task: s.parallel.task,
|
|
445
|
+
mode: resultMode,
|
|
446
|
+
async: true,
|
|
447
|
+
dynamicGroup: true,
|
|
448
|
+
}),
|
|
441
449
|
};
|
|
442
450
|
}
|
|
443
451
|
return buildSeqStep(s as SequentialStep, nextSessionFile());
|
|
@@ -659,10 +667,13 @@ export function executeAsyncSingle(
|
|
|
659
667
|
const validationError = validateFileOnlyOutputMode(outputMode, outputPath, `Async single run (${agent})`);
|
|
660
668
|
if (validationError) return formatAsyncStartError("single", validationError);
|
|
661
669
|
const taskWithOutputInstruction = injectSingleOutputInstruction(task, outputPath);
|
|
662
|
-
const
|
|
663
|
-
|
|
664
|
-
|
|
670
|
+
const primaryModel = resolveSubagentModelOverride(
|
|
671
|
+
params.modelOverride ?? agentConfig.model,
|
|
672
|
+
ctx.currentModel,
|
|
673
|
+
availableModels,
|
|
674
|
+
ctx.currentModelProvider,
|
|
665
675
|
);
|
|
676
|
+
const model = applyThinkingSuffix(primaryModel, agentConfig.thinking);
|
|
666
677
|
let spawnResult: { pid?: number; error?: string } = {};
|
|
667
678
|
try {
|
|
668
679
|
spawnResult = spawnRunner(
|
|
@@ -675,7 +686,7 @@ export function executeAsyncSingle(
|
|
|
675
686
|
cwd: runnerCwd,
|
|
676
687
|
model,
|
|
677
688
|
thinking: resolveEffectiveThinking(model, agentConfig.thinking),
|
|
678
|
-
modelCandidates: buildModelCandidates(
|
|
689
|
+
modelCandidates: buildModelCandidates(primaryModel, agentConfig.fallbackModels, availableModels, ctx.currentModelProvider).map((candidate) =>
|
|
679
690
|
applyThinkingSuffix(candidate, agentConfig.thinking),
|
|
680
691
|
),
|
|
681
692
|
tools: agentConfig.tools,
|
|
@@ -691,8 +702,6 @@ export function executeAsyncSingle(
|
|
|
691
702
|
outputMode,
|
|
692
703
|
sessionFile,
|
|
693
704
|
maxSubagentDepth: resolveChildMaxSubagentDepth(maxSubagentDepth, agentConfig.maxSubagentDepth),
|
|
694
|
-
maxExecutionTimeMs: agentConfig.maxExecutionTimeMs,
|
|
695
|
-
maxTokens: agentConfig.maxTokens,
|
|
696
705
|
effectiveAcceptance: resolveEffectiveAcceptance({
|
|
697
706
|
explicit: params.acceptance,
|
|
698
707
|
agentName: agent,
|
|
@@ -49,11 +49,6 @@ function formatResumeGuidance(runId: string | undefined, children: Array<{ agent
|
|
|
49
49
|
return "Resume: unavailable; no child session file was persisted.";
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
function formatAcceptanceFinalizationSummary(finalization: NonNullable<NonNullable<AsyncStatus["steps"]>[number]["acceptance"]>["finalization"] | undefined): string {
|
|
53
|
-
if (!finalization) return "";
|
|
54
|
-
return `, finalization: ${finalization.status} after ${finalization.turns.length}/${finalization.maxTurns} turns`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
52
|
function stepLineLabel(status: AsyncStatus, index: number): string {
|
|
58
53
|
const steps = status.steps ?? [];
|
|
59
54
|
if (status.mode === "parallel") return `Agent ${index + 1}/${steps.length || 1}`;
|
|
@@ -222,8 +217,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
222
217
|
const modelThinking = formatModelThinking(step.model, step.thinking);
|
|
223
218
|
const modelText = modelThinking ? ` (${modelThinking})` : "";
|
|
224
219
|
const errorText = step.error ? `, error: ${step.error}` : "";
|
|
225
|
-
const
|
|
226
|
-
const acceptanceText = step.acceptance?.status ? `, acceptance: ${step.acceptance.status}${finalizationText}` : "";
|
|
220
|
+
const acceptanceText = step.acceptance?.status ? `, acceptance: ${step.acceptance.status}` : "";
|
|
227
221
|
const display = step.label ? `${step.label} (${step.agent})` : step.agent;
|
|
228
222
|
const phase = step.phase ? `[${step.phase}] ` : "";
|
|
229
223
|
lines.push(`${stepLineLabel(status, index)}: ${phase}${display} ${step.status}${modelText}${stepActivityText ? `, ${stepActivityText}` : ""}${acceptanceText}${errorText}`);
|