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/subagent-runner.ts
CHANGED
|
@@ -503,10 +503,12 @@ async function runSingleStep(
|
|
|
503
503
|
sessionDir,
|
|
504
504
|
sessionFile: step.sessionFile,
|
|
505
505
|
model: candidate,
|
|
506
|
+
inheritProjectContext: step.inheritProjectContext,
|
|
507
|
+
inheritSkills: step.inheritSkills,
|
|
506
508
|
tools: step.tools,
|
|
507
509
|
extensions: step.extensions,
|
|
508
|
-
skills: step.skills,
|
|
509
510
|
systemPrompt: step.systemPrompt,
|
|
511
|
+
systemPromptMode: step.systemPromptMode,
|
|
510
512
|
mcpDirectTools: step.mcpDirectTools,
|
|
511
513
|
promptFileStem: step.agent,
|
|
512
514
|
});
|
|
@@ -555,12 +557,12 @@ async function runSingleStep(
|
|
|
555
557
|
}
|
|
556
558
|
if (resolvedOutput.savedPath) {
|
|
557
559
|
outputForSummary = outputForSummary
|
|
558
|
-
? `${outputForSummary}\n\
|
|
559
|
-
:
|
|
560
|
+
? `${outputForSummary}\n\nOutput saved to: ${resolvedOutput.savedPath}`
|
|
561
|
+
: `Output saved to: ${resolvedOutput.savedPath}`;
|
|
560
562
|
} else if (resolvedOutput.saveError && step.outputPath && finalResult?.exitCode === 0) {
|
|
561
563
|
outputForSummary = outputForSummary
|
|
562
|
-
? `${outputForSummary}\n\
|
|
563
|
-
:
|
|
564
|
+
? `${outputForSummary}\n\nFailed to save output to: ${step.outputPath}\n${resolvedOutput.saveError}`
|
|
565
|
+
: `Failed to save output to: ${step.outputPath}\n${resolvedOutput.saveError}`;
|
|
564
566
|
}
|
|
565
567
|
|
|
566
568
|
if (artifactPaths && ctx.artifactConfig?.enabled !== false) {
|
|
@@ -740,7 +742,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
740
742
|
let previousCumulativeTokens: TokenUsage = { input: 0, output: 0, total: 0 };
|
|
741
743
|
let latestSessionFile: string | undefined;
|
|
742
744
|
|
|
743
|
-
// Flatten steps for status tracking (parallel groups expand to individual entries)
|
|
744
745
|
const flatSteps = flattenSteps(steps);
|
|
745
746
|
const sessionEnabled = Boolean(config.sessionDir)
|
|
746
747
|
|| shareEnabled
|
|
@@ -780,14 +781,12 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
780
781
|
}),
|
|
781
782
|
);
|
|
782
783
|
|
|
783
|
-
// Track the flat index into statusPayload.steps across sequential + parallel steps
|
|
784
784
|
let flatIndex = 0;
|
|
785
785
|
|
|
786
786
|
for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
|
|
787
787
|
const step = steps[stepIndex];
|
|
788
788
|
|
|
789
789
|
if (isParallelGroup(step)) {
|
|
790
|
-
// === PARALLEL STEP GROUP ===
|
|
791
790
|
const group = step;
|
|
792
791
|
const concurrency = group.concurrency ?? MAX_PARALLEL_CONCURRENCY;
|
|
793
792
|
const failFast = group.failFast ?? false;
|
|
@@ -915,7 +914,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
915
914
|
|
|
916
915
|
flatIndex += group.parallel.length;
|
|
917
916
|
|
|
918
|
-
// Aggregate token usage from parallel task session dirs
|
|
919
917
|
if (config.sessionDir) {
|
|
920
918
|
for (let t = 0; t < group.parallel.length; t++) {
|
|
921
919
|
const taskSessionDir = path.join(config.sessionDir, `parallel-${t}`);
|
|
@@ -935,7 +933,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
935
933
|
writeJson(statusPath, statusPayload);
|
|
936
934
|
}
|
|
937
935
|
|
|
938
|
-
// Collect results
|
|
939
936
|
for (const pr of parallelResults) {
|
|
940
937
|
results.push({
|
|
941
938
|
agent: pr.agent,
|
|
@@ -969,7 +966,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
969
966
|
success: parallelResults.every((r) => r.exitCode === 0 || r.exitCode === -1),
|
|
970
967
|
}));
|
|
971
968
|
|
|
972
|
-
// If any parallel task failed (not skipped), stop the chain
|
|
973
969
|
if (parallelResults.some((r) => r.exitCode !== 0 && r.exitCode !== -1)) {
|
|
974
970
|
break;
|
|
975
971
|
}
|
|
@@ -977,7 +973,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
977
973
|
if (worktreeSetup) cleanupWorktrees(worktreeSetup);
|
|
978
974
|
}
|
|
979
975
|
} else {
|
|
980
|
-
// === SEQUENTIAL STEP ===
|
|
981
976
|
const seqStep = step as SubagentStep;
|
|
982
977
|
const stepStartTime = Date.now();
|
|
983
978
|
statusPayload.currentStep = flatIndex;
|
package/types.ts
CHANGED
package/utils.ts
CHANGED
|
@@ -6,19 +6,23 @@ import * as fs from "node:fs";
|
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import type { Message } from "@mariozechner/pi-ai";
|
|
9
|
-
import type { AsyncStatus, DisplayItem, ErrorInfo, SingleResult } from "./types.ts";
|
|
9
|
+
import type { AgentProgress, AsyncStatus, Details, DisplayItem, ErrorInfo, SingleResult } from "./types.ts";
|
|
10
10
|
|
|
11
11
|
// ============================================================================
|
|
12
12
|
// File System Utilities
|
|
13
13
|
// ============================================================================
|
|
14
14
|
|
|
15
|
-
// Cache for status file reads - avoid re-reading unchanged files
|
|
16
15
|
const statusCache = new Map<string, { mtime: number; status: AsyncStatus }>();
|
|
17
16
|
|
|
18
17
|
function getErrorMessage(error: unknown): string {
|
|
19
18
|
return error instanceof Error ? error.message : String(error);
|
|
20
19
|
}
|
|
21
20
|
|
|
21
|
+
export function resolveChildCwd(baseCwd: string, childCwd: string | undefined): string {
|
|
22
|
+
if (!childCwd) return baseCwd;
|
|
23
|
+
return path.isAbsolute(childCwd) ? childCwd : path.resolve(baseCwd, childCwd);
|
|
24
|
+
}
|
|
25
|
+
|
|
22
26
|
function isNotFoundError(error: unknown): boolean {
|
|
23
27
|
return typeof error === "object"
|
|
24
28
|
&& error !== null
|
|
@@ -74,7 +78,6 @@ export function readStatus(asyncDir: string): AsyncStatus | null {
|
|
|
74
78
|
return status;
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
// Cache for output tail reads - avoid re-reading unchanged files
|
|
78
81
|
const outputTailCache = new Map<string, { mtime: number; size: number; lines: string[] }>();
|
|
79
82
|
|
|
80
83
|
/**
|
|
@@ -87,7 +90,6 @@ export function getOutputTail(outputFile: string | undefined, maxLines: number =
|
|
|
87
90
|
const stat = fs.statSync(outputFile);
|
|
88
91
|
if (stat.size === 0) return [];
|
|
89
92
|
|
|
90
|
-
// Check cache using both mtime and size (size changes more frequently during writes)
|
|
91
93
|
const cached = outputTailCache.get(outputFile);
|
|
92
94
|
if (cached && cached.mtime === stat.mtimeMs && cached.size === stat.size) {
|
|
93
95
|
return cached.lines;
|
|
@@ -102,9 +104,7 @@ export function getOutputTail(outputFile: string | undefined, maxLines: number =
|
|
|
102
104
|
const allLines = content.split("\n").filter((l) => l.trim());
|
|
103
105
|
const lines = allLines.slice(-maxLines).map((l) => l.slice(0, 120) + (l.length > 120 ? "..." : ""));
|
|
104
106
|
|
|
105
|
-
// Cache the result
|
|
106
107
|
outputTailCache.set(outputFile, { mtime: stat.mtimeMs, size: stat.size, lines });
|
|
107
|
-
// Limit cache size
|
|
108
108
|
if (outputTailCache.size > 20) {
|
|
109
109
|
const firstKey = outputTailCache.keys().next().value;
|
|
110
110
|
if (firstKey) outputTailCache.delete(firstKey);
|
|
@@ -112,12 +112,15 @@ export function getOutputTail(outputFile: string | undefined, maxLines: number =
|
|
|
112
112
|
|
|
113
113
|
return lines;
|
|
114
114
|
} catch {
|
|
115
|
+
// Output tails are UI-only hints; unreadable or missing files should render as no tail.
|
|
115
116
|
return [];
|
|
116
117
|
} finally {
|
|
117
118
|
if (fd !== null) {
|
|
118
119
|
try {
|
|
119
120
|
fs.closeSync(fd);
|
|
120
|
-
} catch {
|
|
121
|
+
} catch {
|
|
122
|
+
// Closing the best-effort tail file handle should not surface over the main status view.
|
|
123
|
+
}
|
|
121
124
|
}
|
|
122
125
|
}
|
|
123
126
|
}
|
|
@@ -125,16 +128,16 @@ export function getOutputTail(outputFile: string | undefined, maxLines: number =
|
|
|
125
128
|
/**
|
|
126
129
|
* Get human-readable last activity time for a file
|
|
127
130
|
*/
|
|
128
|
-
export function getLastActivity(outputFile: string | undefined): string {
|
|
131
|
+
export function getLastActivity(outputFile: string | undefined): string {
|
|
129
132
|
if (!outputFile) return "";
|
|
130
133
|
try {
|
|
131
|
-
// Single stat call - throws if file doesn't exist
|
|
132
134
|
const stat = fs.statSync(outputFile);
|
|
133
135
|
const ago = Date.now() - stat.mtimeMs;
|
|
134
136
|
if (ago < 1000) return "active now";
|
|
135
137
|
if (ago < 60000) return `active ${Math.floor(ago / 1000)}s ago`;
|
|
136
138
|
return `active ${Math.floor(ago / 60000)}m ago`;
|
|
137
139
|
} catch {
|
|
140
|
+
// Last-activity text is best effort; missing files should simply omit the hint.
|
|
138
141
|
return "";
|
|
139
142
|
}
|
|
140
143
|
}
|
|
@@ -201,13 +204,14 @@ export function getFinalOutput(messages: Message[]): string {
|
|
|
201
204
|
}
|
|
202
205
|
|
|
203
206
|
export function getSingleResultOutput(result: Pick<SingleResult, "finalOutput" | "messages">): string {
|
|
204
|
-
return result.finalOutput ?? getFinalOutput(result.messages);
|
|
207
|
+
return result.finalOutput ?? getFinalOutput(result.messages ?? []);
|
|
205
208
|
}
|
|
206
209
|
|
|
207
210
|
/**
|
|
208
211
|
* Extract display items (text and tool calls) from messages
|
|
209
212
|
*/
|
|
210
|
-
export function getDisplayItems(messages: Message[]): DisplayItem[] {
|
|
213
|
+
export function getDisplayItems(messages: Message[] | undefined): DisplayItem[] {
|
|
214
|
+
if (!messages || messages.length === 0) return [];
|
|
211
215
|
const items: DisplayItem[] = [];
|
|
212
216
|
for (const msg of messages) {
|
|
213
217
|
if (msg.role === "assistant") {
|
|
@@ -220,6 +224,43 @@ export function getDisplayItems(messages: Message[]): DisplayItem[] {
|
|
|
220
224
|
return items;
|
|
221
225
|
}
|
|
222
226
|
|
|
227
|
+
function compactCompletedProgress(progress: AgentProgress): AgentProgress {
|
|
228
|
+
if (progress.status === "running") return progress;
|
|
229
|
+
return {
|
|
230
|
+
index: progress.index,
|
|
231
|
+
agent: progress.agent,
|
|
232
|
+
status: progress.status,
|
|
233
|
+
task: progress.task,
|
|
234
|
+
skills: progress.skills,
|
|
235
|
+
toolCount: progress.toolCount,
|
|
236
|
+
tokens: progress.tokens,
|
|
237
|
+
durationMs: progress.durationMs,
|
|
238
|
+
error: progress.error,
|
|
239
|
+
failedTool: progress.failedTool,
|
|
240
|
+
recentTools: [],
|
|
241
|
+
recentOutput: [],
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function compactForegroundResult(result: SingleResult): SingleResult {
|
|
246
|
+
if (result.progress?.status === "running") return result;
|
|
247
|
+
return {
|
|
248
|
+
...result,
|
|
249
|
+
messages: undefined,
|
|
250
|
+
progress: undefined,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function compactForegroundDetails(details: Details): Details {
|
|
255
|
+
return {
|
|
256
|
+
...details,
|
|
257
|
+
results: details.results.map(compactForegroundResult),
|
|
258
|
+
progress: details.progress
|
|
259
|
+
? details.progress.map(compactCompletedProgress)
|
|
260
|
+
: undefined,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
223
264
|
/**
|
|
224
265
|
* Detect errors in subagent execution from messages (only errors with no subsequent success)
|
|
225
266
|
*/
|
|
@@ -358,4 +399,4 @@ export function extractTextFromContent(content: unknown): string {
|
|
|
358
399
|
// Concurrency Utilities
|
|
359
400
|
// ============================================================================
|
|
360
401
|
|
|
361
|
-
export { mapConcurrent } from "./parallel-utils.
|
|
402
|
+
export { mapConcurrent } from "./parallel-utils.ts";
|
package/worktree.ts
CHANGED
|
@@ -139,7 +139,8 @@ export function findWorktreeTaskCwdConflict(
|
|
|
139
139
|
for (let index = 0; index < tasks.length; index++) {
|
|
140
140
|
const task = tasks[index]!;
|
|
141
141
|
if (!task.cwd) continue;
|
|
142
|
-
|
|
142
|
+
const taskCwd = path.isAbsolute(task.cwd) ? task.cwd : path.resolve(sharedCwd, task.cwd);
|
|
143
|
+
if (normalizeComparableCwd(taskCwd) === normalizedSharedCwd) continue;
|
|
143
144
|
return { index, agent: task.agent, cwd: task.cwd };
|
|
144
145
|
}
|
|
145
146
|
return undefined;
|