pi-subagents 0.14.0 → 0.15.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 +30 -4
- package/README.md +88 -7
- package/agent-management.ts +28 -7
- package/agent-manager-detail.ts +5 -2
- package/agent-manager-edit.ts +46 -6
- package/agent-manager-list.ts +3 -3
- package/agent-manager.ts +28 -2
- package/agent-serializer.ts +6 -0
- package/agents/context-builder.md +24 -26
- package/agents/delegate.md +4 -1
- package/agents/planner.md +21 -15
- package/agents/researcher.md +23 -25
- package/agents/reviewer.md +22 -14
- package/agents/scout.md +24 -19
- package/agents/worker.md +20 -8
- package/agents.ts +121 -13
- package/async-execution.ts +18 -12
- package/async-job-tracker.ts +3 -3
- package/async-status.ts +3 -3
- package/chain-clarify.ts +4 -4
- package/chain-execution.ts +19 -17
- package/execution.ts +3 -3
- package/formatters.ts +14 -12
- package/index.ts +1 -1
- package/install.mjs +3 -3
- package/package.json +1 -1
- package/parallel-utils.ts +9 -20
- package/pi-args.ts +13 -6
- package/prompt-template-bridge.ts +19 -8
- package/render.ts +23 -50
- package/schemas.ts +1 -1
- package/settings.ts +2 -2
- package/single-output.ts +2 -2
- package/slash-commands.ts +8 -8
- package/subagent-executor.ts +40 -27
- package/subagent-prompt-runtime.ts +67 -0
- package/subagent-runner.ts +7 -12
- package/types.ts +1 -1
- package/utils.ts +53 -12
- package/worktree.ts +2 -1
package/async-execution.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { isParallelStep, resolveStepBehavior, type ChainStep, type SequentialSte
|
|
|
16
16
|
import type { RunnerStep } from "./parallel-utils.ts";
|
|
17
17
|
import { resolvePiPackageRoot } from "./pi-spawn.ts";
|
|
18
18
|
import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "./skills.ts";
|
|
19
|
+
import { resolveChildCwd } from "./utils.ts";
|
|
19
20
|
import { buildModelCandidates, resolveModelCandidate, type AvailableModelInfo } from "./model-fallback.ts";
|
|
20
21
|
import {
|
|
21
22
|
type ArtifactConfig,
|
|
@@ -163,6 +164,7 @@ export function executeAsyncChain(
|
|
|
163
164
|
} = params;
|
|
164
165
|
const chainSkills = params.chainSkills ?? [];
|
|
165
166
|
const availableModels = params.availableModels;
|
|
167
|
+
const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
|
|
166
168
|
|
|
167
169
|
for (const s of chain) {
|
|
168
170
|
const stepAgents = isParallelStep(s)
|
|
@@ -193,27 +195,27 @@ export function executeAsyncChain(
|
|
|
193
195
|
|
|
194
196
|
const buildSeqStep = (s: SequentialStep, sessionFile?: string) => {
|
|
195
197
|
const a = agents.find((x) => x.name === s.agent)!;
|
|
198
|
+
const stepCwd = resolveChildCwd(runnerCwd, s.cwd);
|
|
196
199
|
const stepSkillInput = normalizeSkillInput(s.skill);
|
|
197
200
|
const stepOverrides: StepOverrides = { skills: stepSkillInput };
|
|
198
201
|
const behavior = resolveStepBehavior(a, stepOverrides, chainSkills);
|
|
199
202
|
const skillNames = behavior.skills === false ? [] : behavior.skills;
|
|
200
|
-
const
|
|
201
|
-
const { resolved: resolvedSkills } = resolveSkillsWithFallback(skillNames, skillCwd, ctx.cwd);
|
|
203
|
+
const { resolved: resolvedSkills } = resolveSkillsWithFallback(skillNames, stepCwd, ctx.cwd);
|
|
202
204
|
|
|
203
|
-
let systemPrompt = a.systemPrompt?.trim()
|
|
205
|
+
let systemPrompt = a.systemPrompt?.trim() ?? "";
|
|
204
206
|
if (resolvedSkills.length > 0) {
|
|
205
207
|
const injection = buildSkillInjection(resolvedSkills);
|
|
206
208
|
systemPrompt = systemPrompt ? `${systemPrompt}\n\n${injection}` : injection;
|
|
207
209
|
}
|
|
208
210
|
|
|
209
|
-
const outputPath = resolveSingleOutputPath(s.output, ctx.cwd,
|
|
211
|
+
const outputPath = resolveSingleOutputPath(s.output, ctx.cwd, stepCwd);
|
|
210
212
|
const task = injectSingleOutputInstruction(s.task ?? "{previous}", outputPath);
|
|
211
213
|
|
|
212
214
|
const primaryModel = resolveModelCandidate(s.model ?? a.model, availableModels, ctx.currentModelProvider);
|
|
213
215
|
return {
|
|
214
216
|
agent: s.agent,
|
|
215
217
|
task,
|
|
216
|
-
cwd:
|
|
218
|
+
cwd: stepCwd,
|
|
217
219
|
model: applyThinkingSuffix(primaryModel, a.thinking),
|
|
218
220
|
modelCandidates: buildModelCandidates(s.model ?? a.model, a.fallbackModels, availableModels, ctx.currentModelProvider).map((candidate) =>
|
|
219
221
|
applyThinkingSuffix(candidate, a.thinking),
|
|
@@ -222,6 +224,9 @@ export function executeAsyncChain(
|
|
|
222
224
|
extensions: a.extensions,
|
|
223
225
|
mcpDirectTools: a.mcpDirectTools,
|
|
224
226
|
systemPrompt,
|
|
227
|
+
systemPromptMode: a.systemPromptMode,
|
|
228
|
+
inheritProjectContext: a.inheritProjectContext,
|
|
229
|
+
inheritSkills: a.inheritSkills,
|
|
225
230
|
skills: resolvedSkills.map((r) => r.name),
|
|
226
231
|
outputPath,
|
|
227
232
|
sessionFile,
|
|
@@ -255,7 +260,6 @@ export function executeAsyncChain(
|
|
|
255
260
|
return buildSeqStep(s as SequentialStep, nextSessionFile());
|
|
256
261
|
});
|
|
257
262
|
|
|
258
|
-
const runnerCwd = cwd ?? ctx.cwd;
|
|
259
263
|
let pid: number | undefined;
|
|
260
264
|
try {
|
|
261
265
|
pid = spawnRunner(
|
|
@@ -340,11 +344,11 @@ export function executeAsyncSingle(
|
|
|
340
344
|
worktreeSetupHook,
|
|
341
345
|
worktreeSetupHookTimeoutMs,
|
|
342
346
|
} = params;
|
|
347
|
+
const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
|
|
343
348
|
const skillNames = params.skills ?? agentConfig.skills ?? [];
|
|
344
349
|
const availableModels = params.availableModels;
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
let systemPrompt = agentConfig.systemPrompt?.trim() || null;
|
|
350
|
+
const { resolved: resolvedSkills } = resolveSkillsWithFallback(skillNames, runnerCwd, ctx.cwd);
|
|
351
|
+
let systemPrompt = agentConfig.systemPrompt?.trim() ?? "";
|
|
348
352
|
if (resolvedSkills.length > 0) {
|
|
349
353
|
const injection = buildSkillInjection(resolvedSkills);
|
|
350
354
|
systemPrompt = systemPrompt ? `${systemPrompt}\n\n${injection}` : injection;
|
|
@@ -362,8 +366,7 @@ export function executeAsyncSingle(
|
|
|
362
366
|
};
|
|
363
367
|
}
|
|
364
368
|
|
|
365
|
-
const
|
|
366
|
-
const outputPath = resolveSingleOutputPath(params.output, ctx.cwd, cwd);
|
|
369
|
+
const outputPath = resolveSingleOutputPath(params.output, ctx.cwd, runnerCwd);
|
|
367
370
|
const taskWithOutputInstruction = injectSingleOutputInstruction(task, outputPath);
|
|
368
371
|
let pid: number | undefined;
|
|
369
372
|
try {
|
|
@@ -374,7 +377,7 @@ export function executeAsyncSingle(
|
|
|
374
377
|
{
|
|
375
378
|
agent,
|
|
376
379
|
task: taskWithOutputInstruction,
|
|
377
|
-
cwd,
|
|
380
|
+
cwd: runnerCwd,
|
|
378
381
|
model: applyThinkingSuffix(resolveModelCandidate(params.modelOverride ?? agentConfig.model, availableModels, ctx.currentModelProvider), agentConfig.thinking),
|
|
379
382
|
modelCandidates: buildModelCandidates(params.modelOverride ?? agentConfig.model, agentConfig.fallbackModels, availableModels, ctx.currentModelProvider).map((candidate) =>
|
|
380
383
|
applyThinkingSuffix(candidate, agentConfig.thinking),
|
|
@@ -383,6 +386,9 @@ export function executeAsyncSingle(
|
|
|
383
386
|
extensions: agentConfig.extensions,
|
|
384
387
|
mcpDirectTools: agentConfig.mcpDirectTools,
|
|
385
388
|
systemPrompt,
|
|
389
|
+
systemPromptMode: agentConfig.systemPromptMode,
|
|
390
|
+
inheritProjectContext: agentConfig.inheritProjectContext,
|
|
391
|
+
inheritSkills: agentConfig.inheritSkills,
|
|
386
392
|
skills: resolvedSkills.map((r) => r.name),
|
|
387
393
|
outputPath,
|
|
388
394
|
sessionFile,
|
package/async-job-tracker.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { renderWidget } from "./render.
|
|
3
|
+
import { renderWidget } from "./render.ts";
|
|
4
4
|
import {
|
|
5
5
|
type SubagentState,
|
|
6
6
|
POLL_INTERVAL_MS,
|
|
7
|
-
} from "./types.
|
|
8
|
-
import { readStatus } from "./utils.
|
|
7
|
+
} from "./types.ts";
|
|
8
|
+
import { readStatus } from "./utils.ts";
|
|
9
9
|
|
|
10
10
|
export function createAsyncJobTracker(state: SubagentState, asyncDirRoot: string): {
|
|
11
11
|
ensurePoller: () => void;
|
package/async-status.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { formatDuration, formatTokens, shortenPath } from "./formatters.
|
|
4
|
-
import { type AsyncStatus, type TokenUsage } from "./types.
|
|
5
|
-
import { readStatus } from "./utils.
|
|
3
|
+
import { formatDuration, formatTokens, shortenPath } from "./formatters.ts";
|
|
4
|
+
import { type AsyncStatus, type TokenUsage } from "./types.ts";
|
|
5
|
+
import { readStatus } from "./utils.ts";
|
|
6
6
|
|
|
7
7
|
export interface AsyncRunStepSummary {
|
|
8
8
|
index: number;
|
package/chain-clarify.ts
CHANGED
|
@@ -987,7 +987,7 @@ export class ChainClarifyComponent implements Component {
|
|
|
987
987
|
const prefix = isSelected ? th.fg("accent", "→ ") : " ";
|
|
988
988
|
const modelText = isSelected ? th.fg("accent", model.id) : model.id;
|
|
989
989
|
const providerBadge = th.fg("dim", ` [${model.provider}]`);
|
|
990
|
-
const currentBadge = isCurrent ? th.fg("success", "
|
|
990
|
+
const currentBadge = isCurrent ? th.fg("success", " current") : "";
|
|
991
991
|
|
|
992
992
|
lines.push(this.row(` ${prefix}${modelText}${providerBadge}${currentBadge}`));
|
|
993
993
|
}
|
|
@@ -1269,7 +1269,7 @@ export class ChainClarifyComponent implements Component {
|
|
|
1269
1269
|
lines.push(this.row(` Chain Dir: ${th.fg("dim", chainDirPreview)}`));
|
|
1270
1270
|
|
|
1271
1271
|
const progressEnabled = this.agentConfigs.some((_, i) => this.getEffectiveBehavior(i).progress);
|
|
1272
|
-
const progressValue = progressEnabled ? th.fg("success", "
|
|
1272
|
+
const progressValue = progressEnabled ? th.fg("success", "enabled") : th.fg("dim", "disabled");
|
|
1273
1273
|
lines.push(this.row(` Progress: ${progressValue} ${th.fg("dim", "(press [p] to toggle)")}`));
|
|
1274
1274
|
lines.push(this.row(""));
|
|
1275
1275
|
|
|
@@ -1331,8 +1331,8 @@ export class ChainClarifyComponent implements Component {
|
|
|
1331
1331
|
if (progressEnabled) {
|
|
1332
1332
|
const isFirstStep = i === 0;
|
|
1333
1333
|
const progressAction = isFirstStep
|
|
1334
|
-
? th.fg("success", "
|
|
1335
|
-
: th.fg("accent", "
|
|
1334
|
+
? th.fg("success", "writes progress.md")
|
|
1335
|
+
: th.fg("accent", "reads progress.md");
|
|
1336
1336
|
const progressLabel = th.fg("dim", "progress: ");
|
|
1337
1337
|
lines.push(this.row(` ${progressLabel}${progressAction}`));
|
|
1338
1338
|
}
|
package/chain-execution.ts
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
import { discoverAvailableSkills, normalizeSkillInput } from "./skills.ts";
|
|
29
29
|
import { runSync } from "./execution.ts";
|
|
30
30
|
import { buildChainSummary } from "./formatters.ts";
|
|
31
|
-
import { getSingleResultOutput, mapConcurrent } from "./utils.ts";
|
|
31
|
+
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, resolveChildCwd } from "./utils.ts";
|
|
32
32
|
import { recordRun } from "./run-history.ts";
|
|
33
33
|
import {
|
|
34
34
|
cleanupWorktrees,
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
formatWorktreeDiffSummary,
|
|
39
39
|
formatWorktreeTaskCwdConflict,
|
|
40
40
|
type WorktreeSetup,
|
|
41
|
-
} from "./worktree.
|
|
41
|
+
} from "./worktree.ts";
|
|
42
42
|
import {
|
|
43
43
|
type AgentProgress,
|
|
44
44
|
type ArtifactConfig,
|
|
@@ -91,7 +91,7 @@ interface ParallelChainRunInput {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
function buildChainExecutionDetails(input: ChainExecutionDetailsInput): Details {
|
|
94
|
-
return {
|
|
94
|
+
return compactForegroundDetails({
|
|
95
95
|
mode: "chain",
|
|
96
96
|
results: input.results,
|
|
97
97
|
progress: input.includeProgress ? input.allProgress : undefined,
|
|
@@ -99,7 +99,7 @@ function buildChainExecutionDetails(input: ChainExecutionDetailsInput): Details
|
|
|
99
99
|
chainAgents: input.chainAgents,
|
|
100
100
|
totalSteps: input.totalSteps,
|
|
101
101
|
currentStepIndex: input.currentStepIndex,
|
|
102
|
-
};
|
|
102
|
+
});
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
function buildChainExecutionErrorResult(message: string, input: ChainExecutionDetailsInput): ChainExecutionResult {
|
|
@@ -181,7 +181,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
181
181
|
|
|
182
182
|
const taskCwd = input.worktreeSetup
|
|
183
183
|
? input.worktreeSetup.worktrees[taskIndex]!.agentCwd
|
|
184
|
-
: (
|
|
184
|
+
: resolveChildCwd(input.cwd ?? input.ctx.cwd, task.cwd);
|
|
185
185
|
|
|
186
186
|
const outputPath = typeof behavior.output === "string"
|
|
187
187
|
? (path.isAbsolute(behavior.output) ? behavior.output : path.join(input.chainDir, behavior.output))
|
|
@@ -412,7 +412,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
412
412
|
|
|
413
413
|
if (isParallelStep(step)) {
|
|
414
414
|
const parallelTemplates = stepTemplates as string[];
|
|
415
|
-
const parallelCwd =
|
|
415
|
+
const parallelCwd = resolveChildCwd(cwd ?? ctx.cwd, step.cwd);
|
|
416
416
|
let worktreeSetup: WorktreeSetup | undefined;
|
|
417
417
|
if (step.worktree) {
|
|
418
418
|
const worktreeTaskCwdConflict = findWorktreeTaskCwdConflict(step.parallel, parallelCwd);
|
|
@@ -605,7 +605,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
605
605
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(params.maxSubagentDepth, agentConfig.maxSubagentDepth);
|
|
606
606
|
|
|
607
607
|
const r = await runSync(ctx.cwd, agents, seqStep.agent, stepTask, {
|
|
608
|
-
cwd:
|
|
608
|
+
cwd: resolveChildCwd(cwd ?? ctx.cwd, seqStep.cwd),
|
|
609
609
|
signal,
|
|
610
610
|
runId,
|
|
611
611
|
index: globalTaskIndex,
|
|
@@ -670,15 +670,16 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
670
670
|
});
|
|
671
671
|
return {
|
|
672
672
|
content: [{ type: "text", text: summary }],
|
|
673
|
-
details: {
|
|
674
|
-
mode: "chain",
|
|
673
|
+
details: buildChainExecutionDetails({
|
|
675
674
|
results,
|
|
676
|
-
|
|
677
|
-
|
|
675
|
+
includeProgress,
|
|
676
|
+
allProgress,
|
|
677
|
+
allArtifactPaths,
|
|
678
|
+
artifactsDir,
|
|
678
679
|
chainAgents,
|
|
679
680
|
totalSteps,
|
|
680
681
|
currentStepIndex: stepIndex,
|
|
681
|
-
},
|
|
682
|
+
}),
|
|
682
683
|
isError: true,
|
|
683
684
|
};
|
|
684
685
|
}
|
|
@@ -691,13 +692,14 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
691
692
|
|
|
692
693
|
return {
|
|
693
694
|
content: [{ type: "text", text: summary }],
|
|
694
|
-
details: {
|
|
695
|
-
mode: "chain",
|
|
695
|
+
details: buildChainExecutionDetails({
|
|
696
696
|
results,
|
|
697
|
-
|
|
698
|
-
|
|
697
|
+
includeProgress,
|
|
698
|
+
allProgress,
|
|
699
|
+
allArtifactPaths,
|
|
700
|
+
artifactsDir,
|
|
699
701
|
chainAgents,
|
|
700
702
|
totalSteps,
|
|
701
|
-
},
|
|
703
|
+
}),
|
|
702
704
|
};
|
|
703
705
|
}
|
package/execution.ts
CHANGED
|
@@ -72,7 +72,6 @@ async function runSingleAttempt(
|
|
|
72
72
|
shared: {
|
|
73
73
|
sessionEnabled: boolean;
|
|
74
74
|
systemPrompt: string;
|
|
75
|
-
skillNames: string[];
|
|
76
75
|
resolvedSkillNames?: string[];
|
|
77
76
|
skillsWarning?: string;
|
|
78
77
|
jsonlPath?: string;
|
|
@@ -89,9 +88,11 @@ async function runSingleAttempt(
|
|
|
89
88
|
sessionFile: options.sessionFile,
|
|
90
89
|
model,
|
|
91
90
|
thinking: agent.thinking,
|
|
91
|
+
systemPromptMode: agent.systemPromptMode,
|
|
92
|
+
inheritProjectContext: agent.inheritProjectContext,
|
|
93
|
+
inheritSkills: agent.inheritSkills,
|
|
92
94
|
tools: agent.tools,
|
|
93
95
|
extensions: agent.extensions,
|
|
94
|
-
skills: shared.skillNames,
|
|
95
96
|
systemPrompt: shared.systemPrompt,
|
|
96
97
|
mcpDirectTools: agent.mcpDirectTools,
|
|
97
98
|
promptFileStem: agent.name,
|
|
@@ -413,7 +414,6 @@ export async function runSync(
|
|
|
413
414
|
const result = await runSingleAttempt(runtimeCwd, agent, task, candidate, options, {
|
|
414
415
|
sessionEnabled,
|
|
415
416
|
systemPrompt,
|
|
416
|
-
skillNames,
|
|
417
417
|
resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
|
|
418
418
|
skillsWarning: missingSkills.length > 0 ? `Skills not found: ${missingSkills.join(", ")}` : undefined,
|
|
419
419
|
jsonlPath,
|
package/formatters.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import type { Usage, SingleResult } from "./types.ts";
|
|
8
|
-
import type { ChainStep
|
|
8
|
+
import type { ChainStep } from "./settings.ts";
|
|
9
9
|
import { isParallelStep } from "./settings.ts";
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -49,16 +49,13 @@ export function buildChainSummary(
|
|
|
49
49
|
status: "completed" | "failed",
|
|
50
50
|
failedStep?: { index: number; error: string },
|
|
51
51
|
): string {
|
|
52
|
-
// Build step names for display
|
|
53
52
|
const stepNames = steps
|
|
54
|
-
.map((
|
|
53
|
+
.map((step) => (isParallelStep(step) ? `parallel[${step.parallel.length}]` : step.agent))
|
|
55
54
|
.join(" → ");
|
|
56
55
|
|
|
57
|
-
// Calculate total duration from results
|
|
58
56
|
const totalDuration = results.reduce((sum, r) => sum + (r.progress?.durationMs || 0), 0);
|
|
59
57
|
const durationStr = formatDuration(totalDuration);
|
|
60
58
|
|
|
61
|
-
// Check for progress.md
|
|
62
59
|
const progressPath = path.join(chainDir, "progress.md");
|
|
63
60
|
const hasProgress = fs.existsSync(progressPath);
|
|
64
61
|
const allSkills = new Set<string>();
|
|
@@ -88,14 +85,20 @@ export function buildChainSummary(
|
|
|
88
85
|
*/
|
|
89
86
|
export function formatToolCall(name: string, args: Record<string, unknown>): string {
|
|
90
87
|
switch (name) {
|
|
91
|
-
case "bash":
|
|
92
|
-
|
|
88
|
+
case "bash": {
|
|
89
|
+
const command = typeof args.command === "string" ? args.command : "";
|
|
90
|
+
return `$ ${command.slice(0, 60)}${command.length > 60 ? "..." : ""}`;
|
|
91
|
+
}
|
|
93
92
|
case "read":
|
|
94
|
-
return `read ${shortenPath((args.path || args.file_path || "") as string)}`;
|
|
95
93
|
case "write":
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
case "edit": {
|
|
95
|
+
const target = typeof args.path === "string"
|
|
96
|
+
? args.path
|
|
97
|
+
: typeof args.file_path === "string"
|
|
98
|
+
? args.file_path
|
|
99
|
+
: "";
|
|
100
|
+
return `${name} ${shortenPath(target)}`;
|
|
101
|
+
}
|
|
99
102
|
default: {
|
|
100
103
|
const s = JSON.stringify(args);
|
|
101
104
|
return `${name} ${s.slice(0, 40)}${s.length > 40 ? "..." : ""}`;
|
|
@@ -108,7 +111,6 @@ export function formatToolCall(name: string, args: Record<string, unknown>): str
|
|
|
108
111
|
*/
|
|
109
112
|
export function shortenPath(p: string): string {
|
|
110
113
|
const home = process.env.HOME;
|
|
111
|
-
// Only shorten if HOME is defined and non-empty, and path starts with it
|
|
112
114
|
if (home && p.startsWith(home)) {
|
|
113
115
|
return `~${p.slice(home.length)}`;
|
|
114
116
|
}
|
package/index.ts
CHANGED
|
@@ -266,7 +266,7 @@ Example: { chain: [{agent:"scout", task:"Analyze {task}"}, {agent:"planner", tas
|
|
|
266
266
|
MANAGEMENT (use action field, omit agent/task/chain/tasks):
|
|
267
267
|
• { action: "list" } - discover agents/chains
|
|
268
268
|
• { action: "get", agent: "name" } - full agent detail
|
|
269
|
-
• { action: "create", config: { name, systemPrompt, ... } }
|
|
269
|
+
• { action: "create", config: { name, systemPrompt, systemPromptMode, inheritProjectContext, inheritSkills, ... } }
|
|
270
270
|
• { action: "update", agent: "name", config: { ... } } - merge
|
|
271
271
|
• { action: "delete", agent: "name" }
|
|
272
272
|
• Use chainName for chain operations`,
|
package/install.mjs
CHANGED
|
@@ -38,7 +38,7 @@ if (isRemove) {
|
|
|
38
38
|
if (fs.existsSync(EXTENSION_DIR)) {
|
|
39
39
|
console.log(`Removing ${EXTENSION_DIR}...`);
|
|
40
40
|
fs.rmSync(EXTENSION_DIR, { recursive: true });
|
|
41
|
-
console.log("
|
|
41
|
+
console.log("pi-subagents removed");
|
|
42
42
|
} else {
|
|
43
43
|
console.log("pi-subagents is not installed");
|
|
44
44
|
}
|
|
@@ -61,7 +61,7 @@ if (fs.existsSync(EXTENSION_DIR)) {
|
|
|
61
61
|
console.log("Updating existing installation...");
|
|
62
62
|
try {
|
|
63
63
|
execSync("git pull", { cwd: EXTENSION_DIR, stdio: "inherit" });
|
|
64
|
-
console.log("\
|
|
64
|
+
console.log("\npi-subagents updated");
|
|
65
65
|
} catch (err) {
|
|
66
66
|
console.error("Failed to update. Try removing and reinstalling:");
|
|
67
67
|
console.error(" npx pi-subagents --remove && npx pi-subagents");
|
|
@@ -77,7 +77,7 @@ if (fs.existsSync(EXTENSION_DIR)) {
|
|
|
77
77
|
console.log(`Cloning to ${EXTENSION_DIR}...`);
|
|
78
78
|
try {
|
|
79
79
|
execSync(`git clone ${REPO_URL} "${EXTENSION_DIR}"`, { stdio: "inherit" });
|
|
80
|
-
console.log("\
|
|
80
|
+
console.log("\npi-subagents installed");
|
|
81
81
|
} catch (err) {
|
|
82
82
|
console.error("Failed to clone repository");
|
|
83
83
|
process.exit(1);
|
package/package.json
CHANGED
package/parallel-utils.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parallel execution utilities for the async runner.
|
|
3
|
-
* Kept minimal and self-contained so the standalone runner can use them
|
|
4
|
-
* without pulling in the full extension dependency tree.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/** A single agent step in the runner config */
|
|
8
1
|
export interface RunnerSubagentStep {
|
|
9
2
|
agent: string;
|
|
10
3
|
task: string;
|
|
@@ -15,13 +8,15 @@ export interface RunnerSubagentStep {
|
|
|
15
8
|
extensions?: string[];
|
|
16
9
|
mcpDirectTools?: string[];
|
|
17
10
|
systemPrompt?: string | null;
|
|
11
|
+
systemPromptMode?: "append" | "replace";
|
|
12
|
+
inheritProjectContext: boolean;
|
|
13
|
+
inheritSkills: boolean;
|
|
18
14
|
skills?: string[];
|
|
19
15
|
outputPath?: string;
|
|
20
16
|
sessionFile?: string;
|
|
21
17
|
maxSubagentDepth?: number;
|
|
22
18
|
}
|
|
23
19
|
|
|
24
|
-
/** Parallel step group — multiple agents running concurrently */
|
|
25
20
|
export interface ParallelStepGroup {
|
|
26
21
|
parallel: RunnerSubagentStep[];
|
|
27
22
|
concurrency?: number;
|
|
@@ -32,10 +27,9 @@ export interface ParallelStepGroup {
|
|
|
32
27
|
export type RunnerStep = RunnerSubagentStep | ParallelStepGroup;
|
|
33
28
|
|
|
34
29
|
export function isParallelGroup(step: RunnerStep): step is ParallelStepGroup {
|
|
35
|
-
return "parallel" in step && Array.isArray(
|
|
30
|
+
return "parallel" in step && Array.isArray(step.parallel);
|
|
36
31
|
}
|
|
37
32
|
|
|
38
|
-
/** Flatten runner steps into individual SubagentSteps for status tracking */
|
|
39
33
|
export function flattenSteps(steps: RunnerStep[]): RunnerSubagentStep[] {
|
|
40
34
|
const flat: RunnerSubagentStep[] = [];
|
|
41
35
|
for (const step of steps) {
|
|
@@ -48,16 +42,12 @@ export function flattenSteps(steps: RunnerStep[]): RunnerSubagentStep[] {
|
|
|
48
42
|
return flat;
|
|
49
43
|
}
|
|
50
44
|
|
|
51
|
-
/** Run async tasks with bounded concurrency, preserving result order.
|
|
52
|
-
* `staggerMs` adds a small delay between each worker's start to avoid
|
|
53
|
-
* file-lock contention when multiple subagents read shared config. */
|
|
54
45
|
export async function mapConcurrent<T, R>(
|
|
55
46
|
items: T[],
|
|
56
47
|
limit: number,
|
|
57
48
|
fn: (item: T, i: number) => Promise<R>,
|
|
58
49
|
staggerMs = 150,
|
|
59
50
|
): Promise<R[]> {
|
|
60
|
-
// Clamp to at least 1; NaN/undefined/0/negative all become 1
|
|
61
51
|
const safeLimit = Math.max(1, Math.floor(limit) || 1);
|
|
62
52
|
const results: R[] = new Array(items.length);
|
|
63
53
|
let next = 0;
|
|
@@ -90,7 +80,6 @@ export interface ParallelTaskResult {
|
|
|
90
80
|
outputTargetExists?: boolean;
|
|
91
81
|
}
|
|
92
82
|
|
|
93
|
-
/** Aggregate outputs from parallel tasks into a single string for {previous} */
|
|
94
83
|
export function aggregateParallelOutputs(
|
|
95
84
|
results: ParallelTaskResult[],
|
|
96
85
|
headerFormat: (index: number, agent: string) => string = (i, agent) =>
|
|
@@ -102,15 +91,15 @@ export function aggregateParallelOutputs(
|
|
|
102
91
|
const hasOutput = Boolean(r.output?.trim());
|
|
103
92
|
const status =
|
|
104
93
|
r.exitCode === -1
|
|
105
|
-
? "
|
|
94
|
+
? "SKIPPED"
|
|
106
95
|
: r.exitCode !== 0 && r.exitCode !== null
|
|
107
|
-
?
|
|
96
|
+
? `FAILED (exit code ${r.exitCode})${r.error ? `: ${r.error}` : ""}`
|
|
108
97
|
: r.error
|
|
109
|
-
?
|
|
98
|
+
? `WARNING: ${r.error}`
|
|
110
99
|
: !hasOutput && r.outputTargetPath && r.outputTargetExists === false
|
|
111
|
-
?
|
|
100
|
+
? `EMPTY OUTPUT (expected output file missing: ${r.outputTargetPath})`
|
|
112
101
|
: !hasOutput && !r.outputTargetPath
|
|
113
|
-
? "
|
|
102
|
+
? "EMPTY OUTPUT (no textual response returned)"
|
|
114
103
|
: "";
|
|
115
104
|
const body = status ? (hasOutput ? `${status}\n${r.output}` : status) : r.output;
|
|
116
105
|
return `${header}\n${body}`;
|
package/pi-args.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
|
|
5
6
|
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"];
|
|
6
7
|
const TASK_ARG_LIMIT = 8000;
|
|
8
|
+
const PROMPT_RUNTIME_EXTENSION_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-prompt-runtime.ts");
|
|
7
9
|
|
|
8
10
|
export interface BuildPiArgsInput {
|
|
9
11
|
baseArgs: string[];
|
|
@@ -13,9 +15,11 @@ export interface BuildPiArgsInput {
|
|
|
13
15
|
sessionFile?: string;
|
|
14
16
|
model?: string;
|
|
15
17
|
thinking?: string;
|
|
18
|
+
systemPromptMode?: "append" | "replace";
|
|
19
|
+
inheritProjectContext: boolean;
|
|
20
|
+
inheritSkills: boolean;
|
|
16
21
|
tools?: string[];
|
|
17
22
|
extensions?: string[];
|
|
18
|
-
skills?: string[];
|
|
19
23
|
systemPrompt?: string | null;
|
|
20
24
|
mcpDirectTools?: string[];
|
|
21
25
|
promptFileStem?: string;
|
|
@@ -69,28 +73,29 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
75
|
|
|
76
|
+
const runtimeExtensions = [PROMPT_RUNTIME_EXTENSION_PATH];
|
|
72
77
|
if (input.extensions !== undefined) {
|
|
73
78
|
args.push("--no-extensions");
|
|
74
|
-
for (const extPath of input.extensions) {
|
|
79
|
+
for (const extPath of [...new Set([...runtimeExtensions, ...toolExtensionPaths, ...input.extensions])]) {
|
|
75
80
|
args.push("--extension", extPath);
|
|
76
81
|
}
|
|
77
82
|
} else {
|
|
78
|
-
for (const extPath of toolExtensionPaths) {
|
|
83
|
+
for (const extPath of [...new Set([...runtimeExtensions, ...toolExtensionPaths])]) {
|
|
79
84
|
args.push("--extension", extPath);
|
|
80
85
|
}
|
|
81
86
|
}
|
|
82
87
|
|
|
83
|
-
if (
|
|
88
|
+
if (!input.inheritSkills) {
|
|
84
89
|
args.push("--no-skills");
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
let tempDir: string | undefined;
|
|
88
|
-
if (input.systemPrompt) {
|
|
93
|
+
if (input.systemPrompt !== undefined && input.systemPrompt !== null) {
|
|
89
94
|
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
|
|
90
95
|
const stem = (input.promptFileStem ?? "prompt").replace(/[^\w.-]/g, "_");
|
|
91
96
|
const promptPath = path.join(tempDir, `${stem}.md`);
|
|
92
97
|
fs.writeFileSync(promptPath, input.systemPrompt, { mode: 0o600 });
|
|
93
|
-
args.push("--append-system-prompt", promptPath);
|
|
98
|
+
args.push(input.systemPromptMode === "replace" ? "--system-prompt" : "--append-system-prompt", promptPath);
|
|
94
99
|
}
|
|
95
100
|
|
|
96
101
|
if (input.task.length > TASK_ARG_LIMIT) {
|
|
@@ -105,6 +110,8 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
const env: Record<string, string | undefined> = {};
|
|
113
|
+
env.PI_SUBAGENT_INHERIT_PROJECT_CONTEXT = input.inheritProjectContext ? "1" : "0";
|
|
114
|
+
env.PI_SUBAGENT_INHERIT_SKILLS = input.inheritSkills ? "1" : "0";
|
|
108
115
|
if (input.mcpDirectTools?.length) {
|
|
109
116
|
env.MCP_DIRECT_TOOLS = input.mcpDirectTools.join(",");
|
|
110
117
|
} else {
|
|
@@ -78,6 +78,7 @@ interface PromptTemplateBridgeResult {
|
|
|
78
78
|
results?: Array<{
|
|
79
79
|
agent?: string;
|
|
80
80
|
messages?: unknown[];
|
|
81
|
+
finalOutput?: string;
|
|
81
82
|
exitCode?: number;
|
|
82
83
|
error?: string;
|
|
83
84
|
model?: string;
|
|
@@ -180,12 +181,13 @@ function sanitizeRecentTools(
|
|
|
180
181
|
tools: Array<{ tool?: string; args?: string }> | undefined,
|
|
181
182
|
): Array<{ tool: string; args: string }> | undefined {
|
|
182
183
|
if (!tools || tools.length === 0) return undefined;
|
|
183
|
-
const sanitized = tools
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
tool: entry.tool
|
|
184
|
+
const sanitized = tools.flatMap((entry) => {
|
|
185
|
+
if (typeof entry.tool !== "string" || entry.tool.trim().length === 0) return [];
|
|
186
|
+
return [{
|
|
187
|
+
tool: entry.tool,
|
|
187
188
|
args: typeof entry.args === "string" ? entry.args : String(entry.args ?? ""),
|
|
188
|
-
}
|
|
189
|
+
}];
|
|
190
|
+
});
|
|
189
191
|
return sanitized.length > 0 ? sanitized : undefined;
|
|
190
192
|
}
|
|
191
193
|
|
|
@@ -207,6 +209,15 @@ function resolveProgressModel(
|
|
|
207
209
|
return firstWithModel?.model;
|
|
208
210
|
}
|
|
209
211
|
|
|
212
|
+
function buildDelegationMessages(result: { messages?: unknown[]; finalOutput?: string }, fallbackText?: string): unknown[] {
|
|
213
|
+
if (Array.isArray(result.messages) && result.messages.length > 0) return result.messages;
|
|
214
|
+
const text = typeof result.finalOutput === "string" && result.finalOutput.trim().length > 0
|
|
215
|
+
? result.finalOutput.trim()
|
|
216
|
+
: fallbackText;
|
|
217
|
+
if (!text) return [];
|
|
218
|
+
return [{ role: "assistant", content: [{ type: "text", text }] }];
|
|
219
|
+
}
|
|
220
|
+
|
|
210
221
|
function toDelegationUpdate(requestId: string, update: PromptTemplateBridgeResult): PromptTemplateDelegationUpdate | undefined {
|
|
211
222
|
const progress = update.details?.progress?.[0];
|
|
212
223
|
const taskProgress = update.details?.progress?.map((entry) => {
|
|
@@ -324,7 +335,8 @@ export function registerPromptTemplateDelegationBridge<Ctx extends { cwd?: strin
|
|
|
324
335
|
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_UPDATE_EVENT, payload);
|
|
325
336
|
},
|
|
326
337
|
);
|
|
327
|
-
const
|
|
338
|
+
const contentText = firstTextContent(result.content);
|
|
339
|
+
const messages = buildDelegationMessages(result.details?.results?.[0] ?? {}, contentText);
|
|
328
340
|
const parallelResults = request.tasks
|
|
329
341
|
? request.tasks.map<PromptTemplateDelegationParallelResult>((task, index) => {
|
|
330
342
|
const step = result.details?.results?.[index];
|
|
@@ -340,13 +352,12 @@ export function registerPromptTemplateDelegationBridge<Ctx extends { cwd?: strin
|
|
|
340
352
|
const errorText = step.error;
|
|
341
353
|
return {
|
|
342
354
|
agent: step.agent ?? task.agent,
|
|
343
|
-
messages: step
|
|
355
|
+
messages: buildDelegationMessages(step),
|
|
344
356
|
isError: (exitCode !== undefined && exitCode !== 0) || !!errorText,
|
|
345
357
|
errorText: errorText || undefined,
|
|
346
358
|
};
|
|
347
359
|
})
|
|
348
360
|
: undefined;
|
|
349
|
-
const contentText = firstTextContent(result.content);
|
|
350
361
|
const response: PromptTemplateDelegationResponse = {
|
|
351
362
|
...request,
|
|
352
363
|
messages,
|