pi-fast-subagent 0.1.2 → 0.3.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/README.md +19 -13
- package/index.ts +449 -215
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -8,8 +8,7 @@ Runs subagents with `createAgentSession()` in same process instead of spawning `
|
|
|
8
8
|
|
|
9
9
|
- Single mode: `{ agent, task }`
|
|
10
10
|
- Parallel mode: `{ tasks: [...] }`
|
|
11
|
-
-
|
|
12
|
-
- Per-call or per-step model override
|
|
11
|
+
- Per-call model override
|
|
13
12
|
- User + project agent discovery
|
|
14
13
|
- Project agents override user agents
|
|
15
14
|
- Max nesting depth guard
|
|
@@ -60,6 +59,24 @@ model: anthropic/claude-haiku-4-5
|
|
|
60
59
|
You are code exploration specialist. Read relevant files, trace data flow, summarize findings clearly.
|
|
61
60
|
```
|
|
62
61
|
|
|
62
|
+
## Slash Commands
|
|
63
|
+
|
|
64
|
+
### `/agent`
|
|
65
|
+
|
|
66
|
+
List all available agents:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
/agent
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Show details for a specific agent (description, file path, model, tools, system prompt):
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
/agent scout
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Tab-completion is supported for agent names.
|
|
79
|
+
|
|
63
80
|
## Usage
|
|
64
81
|
|
|
65
82
|
### List agents
|
|
@@ -108,17 +125,6 @@ subagent({
|
|
|
108
125
|
})
|
|
109
126
|
```
|
|
110
127
|
|
|
111
|
-
### Chain
|
|
112
|
-
|
|
113
|
-
```js
|
|
114
|
-
subagent({
|
|
115
|
-
chain: [
|
|
116
|
-
{ agent: "scout", task: "Explore app structure" },
|
|
117
|
-
{ agent: "scout", task: "Based on this: {previous}\n\nExtract only auth flow." }
|
|
118
|
-
]
|
|
119
|
-
})
|
|
120
|
-
```
|
|
121
|
-
|
|
122
128
|
## Notes
|
|
123
129
|
|
|
124
130
|
- Async/background isolation not supported in-process
|
package/index.ts
CHANGED
|
@@ -4,13 +4,20 @@
|
|
|
4
4
|
* Uses createAgentSession() to run subagents in the same process as pi —
|
|
5
5
|
* no subprocess spawn, no cold-start overhead.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* Supports: single, parallel, chain.
|
|
7
|
+
* Supports: single, parallel.
|
|
9
8
|
* Agent .md files are compatible with pi-subagents frontmatter format.
|
|
10
9
|
*/
|
|
11
10
|
|
|
12
|
-
import type {
|
|
13
|
-
|
|
11
|
+
import type {
|
|
12
|
+
AgentToolResult,
|
|
13
|
+
AgentToolUpdateCallback,
|
|
14
|
+
ExtensionAPI,
|
|
15
|
+
ExtensionContext,
|
|
16
|
+
ToolRenderResultOptions,
|
|
17
|
+
} from "@mariozechner/pi-coding-agent";
|
|
18
|
+
import { Theme } from "@mariozechner/pi-coding-agent";
|
|
19
|
+
import { truncateToWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
|
|
20
|
+
import { truncateToVisualLines, keyHint } from "@mariozechner/pi-coding-agent";
|
|
14
21
|
import {
|
|
15
22
|
AuthStorage,
|
|
16
23
|
createAgentSession,
|
|
@@ -19,9 +26,70 @@ import {
|
|
|
19
26
|
ModelRegistry,
|
|
20
27
|
SessionManager,
|
|
21
28
|
} from "@mariozechner/pi-coding-agent";
|
|
29
|
+
|
|
30
|
+
type DefaultResourceLoaderOptions = ConstructorParameters<typeof DefaultResourceLoader>[0];
|
|
22
31
|
import { Type } from "@sinclair/typebox";
|
|
23
32
|
import { type AgentConfig, discoverAgents } from "./agents.js";
|
|
24
33
|
|
|
34
|
+
// ─── Tool arg summarizer (compact one-liner per tool call) ─────────────────────
|
|
35
|
+
|
|
36
|
+
function shortPath(p: unknown): string {
|
|
37
|
+
if (typeof p !== "string") return "";
|
|
38
|
+
const cwd = process.cwd();
|
|
39
|
+
if (p.startsWith(cwd + "/")) return p.slice(cwd.length + 1);
|
|
40
|
+
return p.replace(/^\/Users\/[^/]+\/[^/]+\//, "");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function summarizeToolArgs(toolName: unknown, toolInput: unknown): string {
|
|
44
|
+
const name = String(toolName ?? "");
|
|
45
|
+
const input =
|
|
46
|
+
toolInput && typeof toolInput === "object" ? (toolInput as Record<string, unknown>) : {};
|
|
47
|
+
const filePath = (): string => shortPath(input.path ?? input.file_path) || "";
|
|
48
|
+
switch (name) {
|
|
49
|
+
case "Read":
|
|
50
|
+
case "read":
|
|
51
|
+
case "Write":
|
|
52
|
+
case "write":
|
|
53
|
+
case "Edit":
|
|
54
|
+
case "edit":
|
|
55
|
+
return filePath();
|
|
56
|
+
case "Bash":
|
|
57
|
+
case "bash": {
|
|
58
|
+
const cmd = String(input.command ?? "");
|
|
59
|
+
return cmd.length > 80 ? cmd.slice(0, 77) + "..." : cmd;
|
|
60
|
+
}
|
|
61
|
+
case "Glob":
|
|
62
|
+
case "glob":
|
|
63
|
+
return String(input.pattern ?? "");
|
|
64
|
+
case "find": {
|
|
65
|
+
const pat = String(input.pattern ?? "");
|
|
66
|
+
const p = shortPath(input.path);
|
|
67
|
+
return p ? `${pat} in ${p}` : pat;
|
|
68
|
+
}
|
|
69
|
+
case "Grep":
|
|
70
|
+
case "grep": {
|
|
71
|
+
const pat = String(input.pattern ?? "");
|
|
72
|
+
const g = input.glob ? ` ${input.glob}` : "";
|
|
73
|
+
return `${pat}${g}`;
|
|
74
|
+
}
|
|
75
|
+
case "ls":
|
|
76
|
+
return shortPath(input.path) || "";
|
|
77
|
+
case "subagent": {
|
|
78
|
+
const agent = String(input.agent ?? "");
|
|
79
|
+
const t = String(input.task ?? "");
|
|
80
|
+
const summary = t.length > 50 ? t.slice(0, 47) + "..." : t;
|
|
81
|
+
return agent ? `${agent}: ${summary}` : summary;
|
|
82
|
+
}
|
|
83
|
+
default: {
|
|
84
|
+
for (const v of Object.values(input)) {
|
|
85
|
+
if (typeof v === "string" && v.length > 0)
|
|
86
|
+
return v.length > 60 ? v.slice(0, 57) + "..." : v;
|
|
87
|
+
}
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
25
93
|
// ─── Shared auth (created once, reused across calls) ─────────────────────────
|
|
26
94
|
|
|
27
95
|
let _authStorage: ReturnType<typeof AuthStorage.create> | null = null;
|
|
@@ -38,14 +106,45 @@ function getAuth() {
|
|
|
38
106
|
const MAX_DEPTH = 2;
|
|
39
107
|
const DEPTH_ENV = "PI_FAST_SUBAGENT_DEPTH";
|
|
40
108
|
|
|
109
|
+
interface ToolCallEntry {
|
|
110
|
+
id: string;
|
|
111
|
+
name: string;
|
|
112
|
+
argSummary: string;
|
|
113
|
+
result?: string;
|
|
114
|
+
isError?: boolean;
|
|
115
|
+
durMs?: number;
|
|
116
|
+
}
|
|
117
|
+
|
|
41
118
|
interface RunResult {
|
|
42
119
|
output: string;
|
|
43
120
|
exitCode: number;
|
|
44
121
|
error?: string;
|
|
45
122
|
model?: string;
|
|
123
|
+
toolCalls: ToolCallEntry[];
|
|
46
124
|
usage: { input: number; output: number; cost: number; turns: number };
|
|
47
125
|
}
|
|
48
126
|
|
|
127
|
+
interface AgentRowStatus {
|
|
128
|
+
name: string;
|
|
129
|
+
taskSummary: string;
|
|
130
|
+
status: "pending" | "running" | "done" | "error";
|
|
131
|
+
durMs?: number;
|
|
132
|
+
toolCalls?: ToolCallEntry[];
|
|
133
|
+
responseText?: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
interface SubagentDetails {
|
|
137
|
+
mode?: "single" | "parallel";
|
|
138
|
+
task?: string;
|
|
139
|
+
// parallel
|
|
140
|
+
parallelAgents?: AgentRowStatus[];
|
|
141
|
+
usage: RunResult["usage"];
|
|
142
|
+
running: boolean;
|
|
143
|
+
elapsedMs?: number;
|
|
144
|
+
model?: string;
|
|
145
|
+
toolCalls: ToolCallEntry[];
|
|
146
|
+
}
|
|
147
|
+
|
|
49
148
|
type OnUpdate = (partial: { content: [{ type: "text"; text: string }]; details: unknown }) => void;
|
|
50
149
|
|
|
51
150
|
function formatDuration(ms: number): string {
|
|
@@ -55,6 +154,9 @@ function formatDuration(ms: number): string {
|
|
|
55
154
|
return m > 0 ? `${m}m ${rem}s` : `${rem}s`;
|
|
56
155
|
}
|
|
57
156
|
|
|
157
|
+
// Module-level depth counter — avoids process.env race conditions in parallel mode
|
|
158
|
+
let _currentDepth = 0;
|
|
159
|
+
|
|
58
160
|
async function runAgent(
|
|
59
161
|
agent: AgentConfig,
|
|
60
162
|
task: string,
|
|
@@ -62,13 +164,15 @@ async function runAgent(
|
|
|
62
164
|
modelOverride: string | undefined,
|
|
63
165
|
signal: AbortSignal | undefined,
|
|
64
166
|
onUpdate: OnUpdate | undefined,
|
|
167
|
+
parentDepth?: number,
|
|
65
168
|
): Promise<RunResult> {
|
|
66
|
-
const depth =
|
|
169
|
+
const depth = parentDepth ?? _currentDepth;
|
|
67
170
|
if (depth >= MAX_DEPTH) {
|
|
68
171
|
return {
|
|
69
172
|
output: "",
|
|
70
173
|
exitCode: 1,
|
|
71
174
|
error: `Max subagent depth (${MAX_DEPTH}) exceeded. Increase PI_FAST_SUBAGENT_DEPTH env to allow deeper nesting.`,
|
|
175
|
+
toolCalls: [],
|
|
72
176
|
usage: { input: 0, output: 0, cost: 0, turns: 0 },
|
|
73
177
|
};
|
|
74
178
|
}
|
|
@@ -77,7 +181,7 @@ async function runAgent(
|
|
|
77
181
|
const agentDir = getAgentDir();
|
|
78
182
|
|
|
79
183
|
// Build resource loader — no extensions/context files to keep subagent lean
|
|
80
|
-
const loaderOptions:
|
|
184
|
+
const loaderOptions: DefaultResourceLoaderOptions = {
|
|
81
185
|
cwd,
|
|
82
186
|
agentDir,
|
|
83
187
|
noExtensions: true,
|
|
@@ -124,54 +228,78 @@ async function runAgent(
|
|
|
124
228
|
let detectedModel: string | undefined;
|
|
125
229
|
const startedAt = Date.now();
|
|
126
230
|
const configuredModel = modelOverride ?? agent.model;
|
|
231
|
+
const toolCalls: ToolCallEntry[] = [];
|
|
232
|
+
const toolStartTimes = new Map<string, number>();
|
|
127
233
|
|
|
128
|
-
|
|
129
|
-
content: [{ type: "text", text: "Starting subagent..." }],
|
|
130
|
-
details: {
|
|
131
|
-
agent: agent.name,
|
|
132
|
-
usage,
|
|
133
|
-
running: true,
|
|
134
|
-
elapsedMs: 0,
|
|
135
|
-
model: configuredModel,
|
|
136
|
-
},
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
const heartbeat = setInterval(() => {
|
|
234
|
+
function emitUpdate(): void {
|
|
140
235
|
onUpdate?.({
|
|
141
|
-
content: [{ type: "text", text: currentDelta || lastOutput || "
|
|
236
|
+
content: [{ type: "text", text: currentDelta || lastOutput || "" }],
|
|
142
237
|
details: {
|
|
143
|
-
|
|
238
|
+
task,
|
|
144
239
|
usage,
|
|
145
240
|
running: true,
|
|
146
241
|
elapsedMs: Date.now() - startedAt,
|
|
147
242
|
model: detectedModel ?? configuredModel,
|
|
148
|
-
|
|
243
|
+
toolCalls: [...toolCalls],
|
|
244
|
+
} satisfies SubagentDetails,
|
|
149
245
|
});
|
|
150
|
-
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
emitUpdate();
|
|
249
|
+
|
|
250
|
+
const heartbeat = setInterval(emitUpdate, 1000);
|
|
151
251
|
|
|
152
252
|
const unsubscribe = session.subscribe((event: any) => {
|
|
253
|
+
// Stream tool execution events
|
|
254
|
+
if (event.type === "tool_execution_start") {
|
|
255
|
+
toolStartTimes.set(event.toolCallId, Date.now());
|
|
256
|
+
toolCalls.push({
|
|
257
|
+
id: event.toolCallId,
|
|
258
|
+
name: event.toolName,
|
|
259
|
+
argSummary: summarizeToolArgs(event.toolName, event.args),
|
|
260
|
+
});
|
|
261
|
+
emitUpdate();
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (event.type === "tool_execution_end") {
|
|
266
|
+
const startedAtTool = toolStartTimes.get(event.toolCallId);
|
|
267
|
+
toolStartTimes.delete(event.toolCallId);
|
|
268
|
+
const resultText: string = (event.result?.content ?? [])
|
|
269
|
+
.filter((p: any) => p.type === "text")
|
|
270
|
+
.map((p: any) => p.text as string)
|
|
271
|
+
.join("\n");
|
|
272
|
+
let entry: ToolCallEntry | undefined;
|
|
273
|
+
for (let i = toolCalls.length - 1; i >= 0; i--) {
|
|
274
|
+
if (toolCalls[i]!.id === event.toolCallId) { entry = toolCalls[i]; break; }
|
|
275
|
+
}
|
|
276
|
+
if (!entry) {
|
|
277
|
+
for (let i = toolCalls.length - 1; i >= 0; i--) {
|
|
278
|
+
if (toolCalls[i]!.name === event.toolName && toolCalls[i]!.result === undefined) { entry = toolCalls[i]; break; }
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (entry) {
|
|
282
|
+
entry.result = resultText;
|
|
283
|
+
entry.isError = event.isError;
|
|
284
|
+
entry.durMs = startedAtTool != null ? Date.now() - startedAtTool : undefined;
|
|
285
|
+
}
|
|
286
|
+
emitUpdate();
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
153
290
|
// Stream text deltas live to the UI
|
|
154
291
|
if (event.type === "message_update") {
|
|
155
292
|
const e = event.assistantMessageEvent;
|
|
156
293
|
if (e?.type === "text_delta" && e.delta) {
|
|
157
294
|
currentDelta += e.delta;
|
|
158
|
-
|
|
159
|
-
content: [{ type: "text", text: currentDelta }],
|
|
160
|
-
details: {
|
|
161
|
-
agent: agent.name,
|
|
162
|
-
usage,
|
|
163
|
-
running: true,
|
|
164
|
-
elapsedMs: Date.now() - startedAt,
|
|
165
|
-
model: detectedModel ?? configuredModel,
|
|
166
|
-
},
|
|
167
|
-
});
|
|
295
|
+
emitUpdate();
|
|
168
296
|
}
|
|
169
297
|
return;
|
|
170
298
|
}
|
|
171
299
|
|
|
172
300
|
if (event.type !== "message_end" || !event.message) return;
|
|
173
301
|
const msg = event.message;
|
|
174
|
-
if (msg.role !== "assistant") return;
|
|
302
|
+
if (msg.role !== "assistant") return; // usage/model only tracked for assistant turns
|
|
175
303
|
|
|
176
304
|
usage.turns++;
|
|
177
305
|
const u = msg.usage;
|
|
@@ -204,9 +332,10 @@ async function runAgent(
|
|
|
204
332
|
});
|
|
205
333
|
});
|
|
206
334
|
|
|
207
|
-
// Propagate depth to
|
|
208
|
-
const
|
|
335
|
+
// Propagate depth to nested calls — use module counter (safe for parallel) + env for subprocess compat
|
|
336
|
+
const prevEnvDepth = process.env[DEPTH_ENV];
|
|
209
337
|
process.env[DEPTH_ENV] = String(depth + 1);
|
|
338
|
+
_currentDepth = depth + 1;
|
|
210
339
|
|
|
211
340
|
let exitCode = 0;
|
|
212
341
|
let error: string | undefined;
|
|
@@ -228,11 +357,12 @@ async function runAgent(
|
|
|
228
357
|
clearInterval(heartbeat);
|
|
229
358
|
unsubscribe();
|
|
230
359
|
session.dispose();
|
|
231
|
-
if (
|
|
232
|
-
else process.env[DEPTH_ENV] =
|
|
360
|
+
if (prevEnvDepth === undefined) delete process.env[DEPTH_ENV];
|
|
361
|
+
else process.env[DEPTH_ENV] = prevEnvDepth;
|
|
362
|
+
_currentDepth = depth;
|
|
233
363
|
}
|
|
234
364
|
|
|
235
|
-
return { output: lastOutput, exitCode, error, model: detectedModel, usage };
|
|
365
|
+
return { output: lastOutput, exitCode, error, model: detectedModel, toolCalls, usage };
|
|
236
366
|
}
|
|
237
367
|
|
|
238
368
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
@@ -289,19 +419,6 @@ const TaskItem = Type.Object({
|
|
|
289
419
|
count: Type.Optional(Type.Number({ description: "Repeat this task N times" })),
|
|
290
420
|
});
|
|
291
421
|
|
|
292
|
-
const ChainItem = Type.Object({
|
|
293
|
-
agent: Type.String({ description: "Agent name" }),
|
|
294
|
-
task: Type.Optional(
|
|
295
|
-
Type.String({
|
|
296
|
-
description:
|
|
297
|
-
"Task template. Supports {previous} (output from prior step) and {task} (first step task). " +
|
|
298
|
-
"Defaults to {previous} for steps 2+.",
|
|
299
|
-
}),
|
|
300
|
-
),
|
|
301
|
-
model: Type.Optional(Type.String({ description: "Model override (provider/model)" })),
|
|
302
|
-
cwd: Type.Optional(Type.String({ description: "Working directory" })),
|
|
303
|
-
});
|
|
304
|
-
|
|
305
422
|
const SubagentParams = Type.Object({
|
|
306
423
|
// Single mode
|
|
307
424
|
agent: Type.Optional(Type.String({ description: "Agent name (single mode)" })),
|
|
@@ -319,13 +436,6 @@ const SubagentParams = Type.Object({
|
|
|
319
436
|
Type.Number({ description: "Max parallel concurrency (default: 4)", default: 4 }),
|
|
320
437
|
),
|
|
321
438
|
|
|
322
|
-
// Chain mode
|
|
323
|
-
chain: Type.Optional(
|
|
324
|
-
Type.Array(ChainItem, {
|
|
325
|
-
description: "Sequential chain. Use {previous} in task to receive prior step output.",
|
|
326
|
-
}),
|
|
327
|
-
),
|
|
328
|
-
|
|
329
439
|
// Management
|
|
330
440
|
action: Type.Optional(
|
|
331
441
|
Type.Union(
|
|
@@ -347,59 +457,257 @@ const SubagentParams = Type.Object({
|
|
|
347
457
|
// ─── Extension entry point ────────────────────────────────────────────────────
|
|
348
458
|
|
|
349
459
|
export default function (pi: ExtensionAPI) {
|
|
460
|
+
// ─── /agent slash command ─────────────────────────────────────────────────
|
|
461
|
+
pi.registerCommand("agent", {
|
|
462
|
+
description: "List available subagents. Usage: /agent [name] — show details for a specific agent.",
|
|
463
|
+
getArgumentCompletions(prefix: string) {
|
|
464
|
+
const agents = discoverAgents(process.cwd());
|
|
465
|
+
return agents
|
|
466
|
+
.filter((a) => a.name.startsWith(prefix))
|
|
467
|
+
.map((a) => ({ value: a.name, label: a.name, description: a.description }));
|
|
468
|
+
},
|
|
469
|
+
async handler(args: string, ctx) {
|
|
470
|
+
const agents = discoverAgents(ctx.cwd);
|
|
471
|
+
const name = args.trim();
|
|
472
|
+
|
|
473
|
+
if (name) {
|
|
474
|
+
const agent = agents.find((a) => a.name === name);
|
|
475
|
+
if (!agent) {
|
|
476
|
+
const list = agents.map((a) => a.name).join(", ") || "none";
|
|
477
|
+
ctx.ui.notify(`Unknown agent "${name}". Available: ${list}`, "warning");
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const lines = [
|
|
481
|
+
`## ${agent.name} [${agent.source}]`,
|
|
482
|
+
`File: ${agent.filePath}`,
|
|
483
|
+
`Description: ${agent.description}`,
|
|
484
|
+
agent.model ? `Model: ${agent.model}` : "",
|
|
485
|
+
agent.tools ? `Tools: ${agent.tools.join(", ")}` : "",
|
|
486
|
+
agent.systemPrompt ? `\nSystem prompt:\n${agent.systemPrompt}` : "",
|
|
487
|
+
].filter(Boolean).join("\n");
|
|
488
|
+
ctx.ui.notify(lines, "info");
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (agents.length === 0) {
|
|
493
|
+
ctx.ui.notify(
|
|
494
|
+
"No agents found.\n" +
|
|
495
|
+
"Add .md files to:\n" +
|
|
496
|
+
" ~/.pi/agent/agents/ (user-level)\n" +
|
|
497
|
+
" .pi/agents/ (project-level)\n" +
|
|
498
|
+
"\nFrontmatter required: name, description. Optional: model, tools.",
|
|
499
|
+
"info"
|
|
500
|
+
);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const userAgents = agents.filter((a) => a.source === "user");
|
|
505
|
+
const projectAgents = agents.filter((a) => a.source === "project");
|
|
506
|
+
|
|
507
|
+
const lines: string[] = [`Agents (${agents.length}):`];
|
|
508
|
+
if (projectAgents.length) {
|
|
509
|
+
lines.push("\nProject (.pi/agents/):");
|
|
510
|
+
for (const a of projectAgents) {
|
|
511
|
+
lines.push(` ${a.name}${a.model ? ` [${a.model}]` : ""} — ${a.description}`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (userAgents.length) {
|
|
515
|
+
lines.push("\nUser (~/.pi/agent/agents/):");
|
|
516
|
+
for (const a of userAgents) {
|
|
517
|
+
lines.push(` ${a.name}${a.model ? ` [${a.model}]` : ""} — ${a.description}`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
lines.push("");
|
|
521
|
+
lines.push("Tip: /agent <name> for details · Add .md files to .pi/agents/ to create new agents");
|
|
522
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
|
|
350
526
|
pi.registerTool({
|
|
351
527
|
name: "subagent",
|
|
352
528
|
label: "Subagent",
|
|
353
529
|
description: [
|
|
354
530
|
"Delegate tasks to specialized subagents. Runs IN-PROCESS — no subprocess cold-start overhead.",
|
|
355
|
-
"Modes: single ({ agent, task }), parallel ({ tasks: [...] })
|
|
356
|
-
"Chain supports {task} (first step task) and {previous} (prior step output) template vars.",
|
|
531
|
+
"Modes: single ({ agent, task }), parallel ({ tasks: [...] }).",
|
|
357
532
|
"Agents defined as .md files in ~/.pi/agent/agents/ (user) or .pi/agents/ (project).",
|
|
358
533
|
"Use { action: 'list' } to discover available agents.",
|
|
359
534
|
].join(" "),
|
|
360
535
|
parameters: SubagentParams,
|
|
361
536
|
|
|
362
|
-
renderResult(result
|
|
363
|
-
const
|
|
364
|
-
const details = (result.details ?? {}) as
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
537
|
+
renderResult(result: AgentToolResult<unknown>, { isPartial, expanded }: ToolRenderResultOptions, theme: Theme) {
|
|
538
|
+
const agentText = result.content?.[0]?.type === "text" ? (result.content[0] as any).text as string : "";
|
|
539
|
+
const details = (result.details ?? {}) as SubagentDetails;
|
|
540
|
+
const toolCalls = details.toolCalls ?? [];
|
|
541
|
+
|
|
542
|
+
// ── Parallel / Chain mode renders ────────────────────────────────
|
|
543
|
+
if (details.mode === "parallel" && details.parallelAgents) {
|
|
544
|
+
const agents = details.parallelAgents;
|
|
545
|
+
const doneCount = agents.filter((a) => a.status === "done" || a.status === "error").length;
|
|
546
|
+
|
|
547
|
+
function agentToolRow(t: ToolCallEntry): string {
|
|
548
|
+
const arg = t.argSummary || "";
|
|
549
|
+
const call = `${t.name}(${arg})`;
|
|
550
|
+
if (t.result === undefined) return theme.fg("dim", call);
|
|
551
|
+
const dur = t.durMs != null ? (t.durMs < 1000 ? ` ${t.durMs}ms` : ` ${(t.durMs / 1000).toFixed(1)}s`) : "";
|
|
552
|
+
return `${call}${t.isError ? " ✗" : ` ✓${dur}`}`;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function wrapL(text: string, w: number): string[] {
|
|
556
|
+
try { return wrapTextWithAnsi(text, w); } catch { return [truncateToWidth(text, w, "...")]; }
|
|
557
|
+
}
|
|
370
558
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
559
|
+
const cache: { width?: number } = {};
|
|
560
|
+
return {
|
|
561
|
+
invalidate() { cache.width = undefined; },
|
|
562
|
+
render(width: number): string[] {
|
|
563
|
+
const out: string[] = [];
|
|
564
|
+
const header = details.running
|
|
565
|
+
? `Parallel (${doneCount}/${agents.length} done)`
|
|
566
|
+
: `Parallel: ${agents.filter((a) => a.status === "done").length}/${agents.length} succeeded`;
|
|
567
|
+
out.push(truncateToWidth(header, width, "..."));
|
|
568
|
+
|
|
569
|
+
for (const a of agents) {
|
|
570
|
+
const dur = a.durMs != null ? (a.durMs < 1000 ? ` ${a.durMs}ms` : ` ${(a.durMs / 1000).toFixed(1)}s`) : "";
|
|
571
|
+
const mark = a.status === "pending" ? theme.fg("dim", "⋅") : a.status === "running" ? theme.fg("dim", "→") : a.status === "done" ? `✓${dur}` : `✗${dur}`;
|
|
572
|
+
|
|
573
|
+
if (expanded) {
|
|
574
|
+
// Full solo-style block per agent
|
|
575
|
+
out.push("");
|
|
576
|
+
out.push(truncateToWidth(`[${a.name}] ${mark}`, width, "..."));
|
|
577
|
+
out.push(truncateToWidth(`Prompt:`, width, "..."));
|
|
578
|
+
out.push(truncateToWidth(` ${a.taskSummary}`, width, "..."));
|
|
579
|
+
for (const t of a.toolCalls ?? []) {
|
|
580
|
+
out.push(truncateToWidth(agentToolRow(t), width, "..."));
|
|
581
|
+
}
|
|
582
|
+
if (a.responseText) {
|
|
583
|
+
out.push("Response:");
|
|
584
|
+
const preview = truncateToVisualLines(a.responseText, 6, width - 2);
|
|
585
|
+
for (const l of preview.visualLines) out.push(truncateToWidth(" " + l, width, "..."));
|
|
586
|
+
if (preview.skippedCount > 0) out.push(truncateToWidth(theme.fg("dim", ` … ${preview.skippedCount} more lines`), width, "..."));
|
|
587
|
+
} else if (a.status === "running") {
|
|
588
|
+
out.push(theme.fg("dim", " running..."));
|
|
589
|
+
}
|
|
590
|
+
} else {
|
|
591
|
+
// Collapsed: compact one-liner
|
|
592
|
+
const row = ` [${a.name}] ${mark} ${a.taskSummary}`;
|
|
593
|
+
out.push(truncateToWidth(row, width, "..."));
|
|
594
|
+
// Show tool call rows compactly
|
|
595
|
+
for (const t of a.toolCalls ?? []) {
|
|
596
|
+
out.push(truncateToWidth(` ${agentToolRow(t)}`, width, "..."));
|
|
597
|
+
}
|
|
598
|
+
if (a.responseText && (a.status === "done" || a.status === "error")) {
|
|
599
|
+
const preview = truncateToVisualLines(a.responseText, 2, width - 4);
|
|
600
|
+
for (const l of preview.visualLines) out.push(truncateToWidth(" " + l, width, "..."));
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
out.push("");
|
|
606
|
+
const status = details.running
|
|
607
|
+
? ["running", details.usage?.turns ? `${details.usage.turns} turn${details.usage.turns > 1 ? "s" : ""}` : ""].filter(Boolean).join(" · ")
|
|
608
|
+
: formatUsage(details.usage ?? { input: 0, output: 0, cost: 0, turns: 0 }, details.model);
|
|
609
|
+
const expandHint = !expanded ? keyHint("app.tools.expand", "expand for full output") : "";
|
|
610
|
+
out.push(truncateToWidth([status, expandHint].filter(Boolean).join(" "), width, "..."));
|
|
611
|
+
return out;
|
|
612
|
+
},
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function statusLine(): string {
|
|
617
|
+
if (details.running) {
|
|
618
|
+
const parts: string[] = ["running"];
|
|
619
|
+
if (details.usage?.turns) parts.push(`${details.usage.turns} turn${details.usage.turns > 1 ? "s" : ""}`);
|
|
620
|
+
if (details.elapsedMs != null) parts.push(formatDuration(details.elapsedMs));
|
|
621
|
+
if (details.model) parts.push(details.model);
|
|
622
|
+
return parts.join(" · ");
|
|
623
|
+
}
|
|
624
|
+
return formatUsage(details.usage ?? { input: 0, output: 0, cost: 0, turns: 0 }, details.model);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Name(arg) ✓ 0.3s or Name(arg) (dim, still running)
|
|
628
|
+
function toolRow(t: ToolCallEntry): string {
|
|
629
|
+
const arg = t.argSummary ? t.argSummary : "";
|
|
630
|
+
const call = `${t.name}(${arg})`;
|
|
631
|
+
if (t.result === undefined) return theme.fg("dim", call);
|
|
632
|
+
const dur = t.durMs != null
|
|
633
|
+
? t.durMs < 1000 ? ` ${t.durMs}ms` : ` ${(t.durMs / 1000).toFixed(1)}s`
|
|
634
|
+
: "";
|
|
635
|
+
return `${call}${t.isError ? " ✗" : ` ✓${dur}`}`;
|
|
381
636
|
}
|
|
382
637
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
// Use a custom component with truncateToWidth per line instead of Text + wrapTextWithAnsi.
|
|
391
|
-
// visibleWidth (used by wrapTextWithAnsi) undercounts wide chars (emoji/CJK), causing the
|
|
392
|
-
// TUI to crash with "Rendered line exceeds terminal width". truncateToWidth uses
|
|
393
|
-
// graphemeWidth internally and correctly measures wide chars.
|
|
638
|
+
function wrapLine(text: string, w: number): string[] {
|
|
639
|
+
try { return wrapTextWithAnsi(text, w); } catch { return [truncateToWidth(text, w, "...")]; }
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const cache: { width?: number; responseLines?: string[]; skipped?: number } = {};
|
|
643
|
+
|
|
394
644
|
return {
|
|
395
|
-
invalidate() {},
|
|
645
|
+
invalidate() { cache.width = undefined; },
|
|
396
646
|
render(width: number): string[] {
|
|
397
|
-
|
|
647
|
+
const out: string[] = [];
|
|
648
|
+
const indent = " ";
|
|
649
|
+
|
|
650
|
+
// ── Prompt ────────────────────────────────────────────────────
|
|
651
|
+
if (details.task) {
|
|
652
|
+
out.push("Prompt:");
|
|
653
|
+
const taskLines = details.task.split("\n");
|
|
654
|
+
if (expanded) {
|
|
655
|
+
for (const line of taskLines) {
|
|
656
|
+
for (const w of wrapLine(indent + line, width)) out.push(w);
|
|
657
|
+
}
|
|
658
|
+
} else {
|
|
659
|
+
// Single truncated line in collapsed
|
|
660
|
+
const oneLiner = taskLines[0] ?? "";
|
|
661
|
+
out.push(truncateToWidth(indent + oneLiner, width, "..."));
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// ── Tool calls ─────────────────────────────────────────────
|
|
666
|
+
for (const t of toolCalls) {
|
|
667
|
+
out.push(truncateToWidth(toolRow(t), width, "..."));
|
|
668
|
+
if (expanded && t.result !== undefined) {
|
|
669
|
+
for (const line of t.result.split("\n")) {
|
|
670
|
+
for (const w of wrapLine(theme.fg("dim", indent + line), width)) out.push(w);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// ── Response ────────────────────────────────────────────
|
|
676
|
+
const responseText = agentText || (isPartial ? "" : "");
|
|
677
|
+
if (responseText || isPartial) {
|
|
678
|
+
out.push("Response:");
|
|
679
|
+
if (expanded) {
|
|
680
|
+
for (const line of responseText.split("\n")) {
|
|
681
|
+
for (const w of wrapLine(indent + line, width)) out.push(w);
|
|
682
|
+
}
|
|
683
|
+
} else {
|
|
684
|
+
const PREVIEW_LINES = 6;
|
|
685
|
+
if (cache.width !== width) {
|
|
686
|
+
const preview = truncateToVisualLines(responseText, PREVIEW_LINES, width - indent.length);
|
|
687
|
+
cache.responseLines = preview.visualLines.map((l) => truncateToWidth(indent + l, width, "..."));
|
|
688
|
+
cache.skipped = preview.skippedCount;
|
|
689
|
+
cache.width = width;
|
|
690
|
+
}
|
|
691
|
+
out.push(...(cache.responseLines ?? []));
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// ── Status ───────────────────────────────────────────────
|
|
696
|
+
const status = statusLine();
|
|
697
|
+
const expandHint = !expanded && (cache.skipped ?? 0) > 0
|
|
698
|
+
? keyHint("app.tools.expand", `expand · ${cache.skipped} lines hidden`)
|
|
699
|
+
: !expanded && toolCalls.some((t) => t.result !== undefined)
|
|
700
|
+
? keyHint("app.tools.expand", "expand for tool outputs")
|
|
701
|
+
: "";
|
|
702
|
+
const statusWithHint = [status, expandHint].filter(Boolean).join(" ");
|
|
703
|
+
if (statusWithHint) out.push(truncateToWidth(statusWithHint, width, "..."));
|
|
704
|
+
|
|
705
|
+
return out;
|
|
398
706
|
},
|
|
399
707
|
};
|
|
400
708
|
},
|
|
401
709
|
|
|
402
|
-
async execute(_id, params, signal, onUpdate, ctx) {
|
|
710
|
+
async execute(_id: string, params: Record<string, any>, signal: AbortSignal | undefined, onUpdate: AgentToolUpdateCallback<unknown> | undefined, ctx: ExtensionContext): Promise<any> {
|
|
403
711
|
const cwd = params.cwd ?? ctx.cwd;
|
|
404
712
|
const agents = discoverAgents(cwd);
|
|
405
713
|
|
|
@@ -413,7 +721,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
413
721
|
};
|
|
414
722
|
|
|
415
723
|
// ── Management: list ──────────────────────────────────────────────────────
|
|
416
|
-
if (params.action === "list" || (!params.agent && !params.tasks
|
|
724
|
+
if (params.action === "list" || (!params.agent && !params.tasks)) {
|
|
417
725
|
if (agents.length === 0) {
|
|
418
726
|
return {
|
|
419
727
|
content: [{
|
|
@@ -459,18 +767,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
459
767
|
return {
|
|
460
768
|
content: [{ type: "text", text: getFinalText(result) }],
|
|
461
769
|
details: {
|
|
770
|
+
task: params.task,
|
|
462
771
|
usage: result.usage,
|
|
463
772
|
running: false,
|
|
464
773
|
elapsedMs: undefined,
|
|
465
774
|
model: result.model,
|
|
466
|
-
|
|
775
|
+
toolCalls: result.toolCalls,
|
|
776
|
+
} satisfies SubagentDetails,
|
|
467
777
|
isError: result.exitCode !== 0,
|
|
468
778
|
};
|
|
469
779
|
}
|
|
470
780
|
|
|
471
|
-
// ── Parallel mode
|
|
781
|
+
// ── Parallel mode ─────────────────────────────────────────────
|
|
472
782
|
if (params.tasks && params.tasks.length > 0) {
|
|
473
|
-
// Expand count shorthand
|
|
474
783
|
const expanded: Array<{ agent: string; task: string; model?: string; cwd?: string }> = [];
|
|
475
784
|
for (const t of params.tasks) {
|
|
476
785
|
const n = t.count ?? 1;
|
|
@@ -478,138 +787,63 @@ export default function (pi: ExtensionAPI) {
|
|
|
478
787
|
}
|
|
479
788
|
|
|
480
789
|
const concurrency = params.concurrency ?? 4;
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
doneCount++;
|
|
493
|
-
onUpdate?.({
|
|
494
|
-
content: [{ type: "text", text: `Parallel: ${doneCount}/${expanded.length} done...` }],
|
|
495
|
-
details: {},
|
|
496
|
-
});
|
|
497
|
-
return { ...result, agentName: t.agent };
|
|
498
|
-
},
|
|
499
|
-
);
|
|
500
|
-
|
|
501
|
-
const successCount = allResults.filter((r) => r.exitCode === 0).length;
|
|
502
|
-
const summaries = allResults.map((r) => {
|
|
503
|
-
const out = getFinalText(r);
|
|
504
|
-
const preview = out.length > 300 ? `${out.slice(0, 300)}...` : out;
|
|
505
|
-
return `**[${r.agentName}]** ${r.exitCode === 0 ? "✓" : "✗"}\n${preview}`;
|
|
790
|
+
const emptyUsage = { input: 0, output: 0, cost: 0, turns: 0 };
|
|
791
|
+
const parallelAgents: AgentRowStatus[] = expanded.map((t) => ({
|
|
792
|
+
name: t.agent,
|
|
793
|
+
taskSummary: t.task.length > 60 ? t.task.slice(0, 57) + "..." : t.task,
|
|
794
|
+
status: "pending" as const,
|
|
795
|
+
}));
|
|
796
|
+
let runningUsage = { ...emptyUsage };
|
|
797
|
+
|
|
798
|
+
const emitParallel = (running: boolean) => onUpdate?.({
|
|
799
|
+
content: [{ type: "text", text: "" }],
|
|
800
|
+
details: { mode: "parallel", parallelAgents: [...parallelAgents], usage: { ...runningUsage }, running, toolCalls: [] } satisfies SubagentDetails,
|
|
506
801
|
});
|
|
507
|
-
const totalUsage = allResults.reduce(
|
|
508
|
-
(acc, r) => ({
|
|
509
|
-
input: acc.input + r.usage.input,
|
|
510
|
-
output: acc.output + r.usage.output,
|
|
511
|
-
cost: acc.cost + r.usage.cost,
|
|
512
|
-
turns: acc.turns + r.usage.turns,
|
|
513
|
-
}),
|
|
514
|
-
{ input: 0, output: 0, cost: 0, turns: 0 },
|
|
515
|
-
);
|
|
516
802
|
|
|
517
|
-
|
|
518
|
-
content: [{
|
|
519
|
-
type: "text",
|
|
520
|
-
text: [
|
|
521
|
-
`Parallel: ${successCount}/${allResults.length} succeeded`,
|
|
522
|
-
"",
|
|
523
|
-
summaries.join("\n\n"),
|
|
524
|
-
"",
|
|
525
|
-
formatUsage(totalUsage),
|
|
526
|
-
].join("\n"),
|
|
527
|
-
}],
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
// ── Chain mode ────────────────────────────────────────────────────────────
|
|
532
|
-
if (params.chain && params.chain.length > 0) {
|
|
533
|
-
const firstTask = params.chain[0]?.task ?? "";
|
|
534
|
-
let previousOutput = "";
|
|
803
|
+
emitParallel(true);
|
|
535
804
|
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
const { agent, error } = findAgent(
|
|
805
|
+
const parentDepth = _currentDepth;
|
|
806
|
+
const allResults = await mapConcurrent(expanded, concurrency, async (t, i) => {
|
|
807
|
+
parallelAgents[i]!.status = "running";
|
|
808
|
+
emitParallel(true);
|
|
809
|
+
const { agent, error } = findAgent(t.agent);
|
|
541
810
|
if (error || !agent) {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// Resolve task template
|
|
549
|
-
let task = step.task ?? (i === 0 ? firstTask : "{previous}");
|
|
550
|
-
task = task
|
|
551
|
-
.replace(/\{previous\}/g, previousOutput)
|
|
552
|
-
.replace(/\{task\}/g, firstTask);
|
|
553
|
-
|
|
554
|
-
if (onUpdate) {
|
|
555
|
-
onUpdate({
|
|
556
|
-
content: [{
|
|
557
|
-
type: "text",
|
|
558
|
-
text: `Chain step ${i + 1}/${params.chain.length}: ${step.agent}...`,
|
|
559
|
-
}],
|
|
560
|
-
details: {},
|
|
561
|
-
});
|
|
811
|
+
parallelAgents[i]!.status = "error";
|
|
812
|
+
emitParallel(true);
|
|
813
|
+
return { agentName: t.agent, output: "", exitCode: 1, error, model: undefined, toolCalls: [] as ToolCallEntry[], usage: emptyUsage };
|
|
562
814
|
}
|
|
815
|
+
const agentStart = Date.now();
|
|
816
|
+
const agentOnUpdate: OnUpdate = (partial) => {
|
|
817
|
+
const d = partial.details as SubagentDetails | undefined;
|
|
818
|
+
parallelAgents[i]!.toolCalls = d?.toolCalls ? [...d.toolCalls] : parallelAgents[i]!.toolCalls;
|
|
819
|
+
parallelAgents[i]!.responseText = (partial.content?.[0] as any)?.text || parallelAgents[i]!.responseText;
|
|
820
|
+
emitParallel(true);
|
|
821
|
+
};
|
|
822
|
+
const result = await runAgent(agent, t.task, t.cwd ?? cwd, t.model, signal, agentOnUpdate, parentDepth);
|
|
823
|
+
parallelAgents[i]!.status = result.exitCode === 0 ? "done" : "error";
|
|
824
|
+
parallelAgents[i]!.durMs = Date.now() - agentStart;
|
|
825
|
+
parallelAgents[i]!.toolCalls = result.toolCalls;
|
|
826
|
+
parallelAgents[i]!.responseText = result.output;
|
|
827
|
+
runningUsage = { input: runningUsage.input + result.usage.input, output: runningUsage.output + result.usage.output, cost: runningUsage.cost + result.usage.cost, turns: runningUsage.turns + result.usage.turns };
|
|
828
|
+
emitParallel(true);
|
|
829
|
+
return { ...result, agentName: t.agent, toolCalls: result.toolCalls ?? [] };
|
|
830
|
+
});
|
|
563
831
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
step.cwd ?? cwd,
|
|
568
|
-
step.model,
|
|
569
|
-
signal,
|
|
570
|
-
onUpdate,
|
|
571
|
-
);
|
|
572
|
-
|
|
573
|
-
stepResults.push({ ...result, agentName: step.agent, step: i + 1 });
|
|
574
|
-
|
|
575
|
-
if (result.exitCode !== 0) {
|
|
576
|
-
return {
|
|
577
|
-
content: [{
|
|
578
|
-
type: "text",
|
|
579
|
-
text: `Chain failed at step ${i + 1} (${step.agent}): ${result.error ?? "(no output)"}`,
|
|
580
|
-
}],
|
|
581
|
-
isError: true,
|
|
582
|
-
};
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
previousOutput = result.output;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
const last = stepResults[stepResults.length - 1];
|
|
589
|
-
const totalUsage = stepResults.reduce(
|
|
590
|
-
(acc, r) => ({
|
|
591
|
-
input: acc.input + r.usage.input,
|
|
592
|
-
output: acc.output + r.usage.output,
|
|
593
|
-
cost: acc.cost + r.usage.cost,
|
|
594
|
-
turns: acc.turns + r.usage.turns,
|
|
595
|
-
}),
|
|
596
|
-
{ input: 0, output: 0, cost: 0, turns: 0 },
|
|
832
|
+
const totalUsage = allResults.reduce(
|
|
833
|
+
(acc, r) => ({ input: acc.input + r.usage.input, output: acc.output + r.usage.output, cost: acc.cost + r.usage.cost, turns: acc.turns + r.usage.turns }),
|
|
834
|
+
emptyUsage,
|
|
597
835
|
);
|
|
836
|
+
const outputs = allResults.map((r) => `[${r.agentName}] ${r.exitCode === 0 ? "✓" : "✗"}\n${getFinalText(r)}`).join("\n\n");
|
|
598
837
|
|
|
599
838
|
return {
|
|
600
|
-
content: [{
|
|
601
|
-
|
|
602
|
-
text: [
|
|
603
|
-
last.output,
|
|
604
|
-
"",
|
|
605
|
-
`Chain: ${stepResults.length} steps · ${formatUsage(totalUsage)}`,
|
|
606
|
-
].join("\n"),
|
|
607
|
-
}],
|
|
839
|
+
content: [{ type: "text", text: outputs }],
|
|
840
|
+
details: { mode: "parallel", parallelAgents, usage: totalUsage, running: false, toolCalls: [] } satisfies SubagentDetails,
|
|
608
841
|
};
|
|
609
842
|
}
|
|
610
843
|
|
|
844
|
+
// ── Chain mode ────────────────────────────────────────────
|
|
611
845
|
// Shouldn't reach here
|
|
612
|
-
return { content: [{ type: "text", text: "Provide agent+task
|
|
846
|
+
return { content: [{ type: "text", text: "Provide agent+task or tasks array." }] };
|
|
613
847
|
},
|
|
614
848
|
});
|
|
615
849
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-fast-subagent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "In-process subagent delegation for pi with single, parallel, and chain modes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -34,5 +34,10 @@
|
|
|
34
34
|
"@mariozechner/pi-tui": "*",
|
|
35
35
|
"@sinclair/typebox": "*"
|
|
36
36
|
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@mariozechner/pi-coding-agent": "^0.68.0",
|
|
39
|
+
"@mariozechner/pi-tui": "^0.68.0",
|
|
40
|
+
"@sinclair/typebox": "^0.34.41"
|
|
41
|
+
},
|
|
37
42
|
"license": "MIT"
|
|
38
43
|
}
|