@pi-archimedes/subagent 1.5.0 → 1.6.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/package.json +2 -2
- package/src/compact.ts +12 -8
- package/src/execute.ts +35 -9
- package/src/expanded.ts +10 -1
- package/src/format.ts +26 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-archimedes/subagent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
],
|
|
12
12
|
"main": "./src/index.ts",
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@pi-archimedes/core": "1.
|
|
14
|
+
"@pi-archimedes/core": "1.6.0"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
17
|
"@earendil-works/pi-ai": ">=0.1.0",
|
package/src/compact.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Text } from "@earendil-works/pi-tui";
|
|
2
2
|
import type { SubagentDetails, SubagentProgress, SubagentResult, SubagentToolResult } from "./types.js";
|
|
3
|
-
import { formatTokens, formatDuration, formatCost, truncLine, buildStatsLine } from "./format.js";
|
|
3
|
+
import { formatTokens, formatDuration, formatCost, truncLine, buildStatsLine, buildAgentLabel } from "./format.js";
|
|
4
4
|
|
|
5
5
|
type Theme = { fg: (token: string, text: string) => string; bold: (text: string) => string };
|
|
6
6
|
type RenderContext = { state: Record<string, unknown>; invalidate: () => void };
|
|
@@ -39,7 +39,7 @@ export function buildActivityLine(
|
|
|
39
39
|
if (data.currentTool) {
|
|
40
40
|
const arrow = theme.fg("muted", "↳ ");
|
|
41
41
|
const argsPreview = data.currentToolArgs
|
|
42
|
-
? truncLine(data.currentToolArgs
|
|
42
|
+
? truncLine(data.currentToolArgs, 60)
|
|
43
43
|
: "";
|
|
44
44
|
const durationPart = data.currentToolStartedAt
|
|
45
45
|
? " | " + formatDuration(Date.now() - data.currentToolStartedAt)
|
|
@@ -90,6 +90,7 @@ export function renderCompactSingle(
|
|
|
90
90
|
theme: Theme,
|
|
91
91
|
context: RenderContext,
|
|
92
92
|
): Text {
|
|
93
|
+
// agentName sourced from result; defaults to "subagent"
|
|
93
94
|
const agentName = result.agent ?? "subagent";
|
|
94
95
|
const summary = result.progressSummary ?? { toolCount: 0, tokens: 0, durationMs: 0 };
|
|
95
96
|
const isRunning = progress?.status === "running";
|
|
@@ -133,7 +134,8 @@ export function renderCompactSingle(
|
|
|
133
134
|
? theme.fg("accent", modelName)
|
|
134
135
|
: "";
|
|
135
136
|
const expandHint = theme.fg("muted", "(ctrl+o)");
|
|
136
|
-
|
|
137
|
+
const agentLabel = buildAgentLabel(agentName, result.task, theme);
|
|
138
|
+
let output = agentLabel + "\n" + [modelLabel, statsPart, expandHint].filter(Boolean).join(" ");
|
|
137
139
|
output += "\n" + activityLine;
|
|
138
140
|
|
|
139
141
|
text.setText(output);
|
|
@@ -150,7 +152,7 @@ export function renderCompactParallel(
|
|
|
150
152
|
): Text {
|
|
151
153
|
const lines = details.results.map((result, i) => {
|
|
152
154
|
const progress = details.progress?.[i];
|
|
153
|
-
const agentName = result.agent ?? "
|
|
155
|
+
const agentName = result.agent ?? "subagent";
|
|
154
156
|
const summary = result.progressSummary ?? { toolCount: 0, tokens: 0, durationMs: 0 };
|
|
155
157
|
const isRunning = progress?.status === "running";
|
|
156
158
|
// Prefer result.exitCode as the source of truth for completion status
|
|
@@ -189,7 +191,7 @@ export function renderCompactParallel(
|
|
|
189
191
|
};
|
|
190
192
|
const activityLine = buildActivityLine(activityData, theme);
|
|
191
193
|
|
|
192
|
-
let line = `${glyphColored} ${agentName}${statsPart}`;
|
|
194
|
+
let line = `${glyphColored} ${buildAgentLabel(agentName, result.task, theme)}${statsPart}`;
|
|
193
195
|
if (activityLine) {
|
|
194
196
|
line += "\n" + activityLine;
|
|
195
197
|
}
|
|
@@ -208,6 +210,7 @@ export function renderCompactProgress(
|
|
|
208
210
|
theme: Theme,
|
|
209
211
|
context: RenderContext,
|
|
210
212
|
): Text {
|
|
213
|
+
// agentName sourced from progress; defaults to "subagent"
|
|
211
214
|
const agentName = progress.agent ?? "subagent";
|
|
212
215
|
const status = progress.status;
|
|
213
216
|
const isRunning = status === "running";
|
|
@@ -253,7 +256,8 @@ export function renderCompactProgress(
|
|
|
253
256
|
: "";
|
|
254
257
|
const statsPart = statsLine;
|
|
255
258
|
const expandHint = theme.fg("muted", "(ctrl+o)");
|
|
256
|
-
|
|
259
|
+
const agentLabel = buildAgentLabel(agentName, progress.task, theme);
|
|
260
|
+
let output = agentLabel + "\n" + [modelLabel, statsPart, expandHint].filter(Boolean).join(" ");
|
|
257
261
|
output += "\n" + activityLine;
|
|
258
262
|
|
|
259
263
|
text.setText(output);
|
|
@@ -269,7 +273,7 @@ export function renderCompactParallelProgress(
|
|
|
269
273
|
context: RenderContext,
|
|
270
274
|
): Text {
|
|
271
275
|
const lines = (details.progress ?? []).map((progress, i) => {
|
|
272
|
-
const agentName = progress.agent ?? "
|
|
276
|
+
const agentName = progress.agent ?? "subagent";
|
|
273
277
|
const status = progress.status;
|
|
274
278
|
const isRunning = status === "running";
|
|
275
279
|
|
|
@@ -301,7 +305,7 @@ export function renderCompactParallelProgress(
|
|
|
301
305
|
};
|
|
302
306
|
const activityLine = buildActivityLine(activityData, theme);
|
|
303
307
|
|
|
304
|
-
let line = `${glyphColored} ${agentName}${statsPart}`;
|
|
308
|
+
let line = `${glyphColored} ${buildAgentLabel(agentName, progress.task, theme)}${statsPart}`;
|
|
305
309
|
if (activityLine) {
|
|
306
310
|
line += "\n" + activityLine;
|
|
307
311
|
}
|
package/src/execute.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface ExecuteOptions {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* Execute a single subagent
|
|
19
|
+
* Execute a single subagent — waits for completion before resolving.
|
|
20
20
|
*/
|
|
21
21
|
export async function executeSubagent(options: ExecuteOptions): Promise<SubagentResult> {
|
|
22
22
|
const agentName = options.agent ?? "subagent";
|
|
@@ -95,7 +95,7 @@ export async function executeSubagent(options: ExecuteOptions): Promise<Subagent
|
|
|
95
95
|
} catch (err) {
|
|
96
96
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
97
97
|
const durationMs = Date.now() - startTime;
|
|
98
|
-
|
|
98
|
+
const result: SubagentResult = {
|
|
99
99
|
agent: agentName,
|
|
100
100
|
task: options.task,
|
|
101
101
|
exitCode: 1,
|
|
@@ -135,6 +135,10 @@ export async function executeSubagent(options: ExecuteOptions): Promise<Subagent
|
|
|
135
135
|
},
|
|
136
136
|
progressSummary: { toolCount: 0, tokens: 0, durationMs },
|
|
137
137
|
};
|
|
138
|
+
// Emit final failure progress so executeParallel's progress slot updates
|
|
139
|
+
// from the pending placeholder to "failed" (prevents stale "Starting..." display).
|
|
140
|
+
options.onUpdate?.(result.progress!);
|
|
141
|
+
return result;
|
|
138
142
|
}
|
|
139
143
|
}
|
|
140
144
|
|
|
@@ -146,19 +150,41 @@ export async function executeParallel(options: {
|
|
|
146
150
|
signal: AbortSignal | undefined;
|
|
147
151
|
onUpdate: ((progress: SubagentProgress[]) => void) | undefined;
|
|
148
152
|
}): Promise<SubagentResult[]> {
|
|
149
|
-
//
|
|
150
|
-
|
|
153
|
+
// Pre-fill one pending slot per task, keyed by task index (NOT agent name).
|
|
154
|
+
// This keeps all N lines stacked from t=0 in stable task order, with no
|
|
155
|
+
// collisions when multiple subagents share an agent name (e.g. 8 x "general").
|
|
156
|
+
const latestProgress: SubagentProgress[] = options.tasks.map((taskDef) => ({
|
|
157
|
+
agent: taskDef.agent ?? "subagent",
|
|
158
|
+
status: "running" as const,
|
|
159
|
+
task: taskDef.task,
|
|
160
|
+
currentTool: undefined,
|
|
161
|
+
currentToolArgs: undefined,
|
|
162
|
+
currentToolStartedAt: undefined,
|
|
163
|
+
toolCount: 0,
|
|
164
|
+
inputTokens: 0,
|
|
165
|
+
outputTokens: 0,
|
|
166
|
+
tokens: 0,
|
|
167
|
+
cost: 0,
|
|
168
|
+
durationMs: 0,
|
|
169
|
+
error: undefined,
|
|
170
|
+
output: undefined,
|
|
171
|
+
recentOutput: undefined,
|
|
172
|
+
toolCalls: undefined,
|
|
173
|
+
// Match the model executeSubagent will report for this task, so the
|
|
174
|
+
// pending placeholder's model label matches the streaming label exactly.
|
|
175
|
+
model: taskDef.model,
|
|
176
|
+
}));
|
|
151
177
|
|
|
152
178
|
const results = await Promise.all(
|
|
153
|
-
options.tasks.map((taskDef) =>
|
|
179
|
+
options.tasks.map((taskDef, index) =>
|
|
154
180
|
executeSubagent({
|
|
155
181
|
...taskDef,
|
|
156
182
|
signal: options.signal,
|
|
157
183
|
onUpdate: (progress: SubagentProgress) => {
|
|
158
|
-
// Store latest progress
|
|
159
|
-
latestProgress
|
|
160
|
-
// Emit
|
|
161
|
-
options.onUpdate?.([...latestProgress
|
|
184
|
+
// Store latest progress in this task's stable slot (by index).
|
|
185
|
+
latestProgress[index] = progress;
|
|
186
|
+
// Emit all N entries in stable task order.
|
|
187
|
+
options.onUpdate?.([...latestProgress]);
|
|
162
188
|
},
|
|
163
189
|
}),
|
|
164
190
|
),
|
package/src/expanded.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Text } from "@earendil-works/pi-tui";
|
|
2
2
|
import type { SubagentDetails, SubagentProgress, SubagentResult } from "./types.js";
|
|
3
|
-
import { formatTokens, formatDuration, truncLine, buildStatsLine } from "./format.js";
|
|
3
|
+
import { formatTokens, formatDuration, truncLine, buildStatsLine, buildAgentLabel } from "./format.js";
|
|
4
4
|
|
|
5
5
|
type Theme = { fg: (token: string, text: string) => string; bold: (text: string) => string };
|
|
6
6
|
|
|
@@ -13,6 +13,9 @@ export function buildExpandedText(
|
|
|
13
13
|
): string {
|
|
14
14
|
const lines: string[] = [];
|
|
15
15
|
|
|
16
|
+
lines.push(buildAgentLabel(result.agent, result.task, theme));
|
|
17
|
+
lines.push("");
|
|
18
|
+
|
|
16
19
|
// Stats line (same as compact view)
|
|
17
20
|
const modelName = progress?.model ?? result.model;
|
|
18
21
|
const modelLabel = modelName ? theme.fg("accent", modelName) : "";
|
|
@@ -85,6 +88,9 @@ export function renderProgressExpanded(
|
|
|
85
88
|
): Text {
|
|
86
89
|
const lines: string[] = [];
|
|
87
90
|
|
|
91
|
+
lines.push(buildAgentLabel(progress.agent, progress.task, theme));
|
|
92
|
+
lines.push("");
|
|
93
|
+
|
|
88
94
|
// Stats line (same as compact view)
|
|
89
95
|
const modelLabel = progress.model ? theme.fg("accent", progress.model) : "";
|
|
90
96
|
const statsLine = buildStatsLine({
|
|
@@ -158,6 +164,9 @@ export function buildProgressExpandedText(
|
|
|
158
164
|
): string {
|
|
159
165
|
const lines: string[] = [];
|
|
160
166
|
|
|
167
|
+
lines.push(buildAgentLabel(progress.agent, progress.task, theme));
|
|
168
|
+
lines.push("");
|
|
169
|
+
|
|
161
170
|
// Stats line (same as compact view)
|
|
162
171
|
const modelLabel = progress.model ? theme.fg("accent", progress.model) : "";
|
|
163
172
|
const statsLine = buildStatsLine({
|
package/src/format.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { clampLine } from "@pi-archimedes/core/text";
|
|
2
|
-
|
|
3
1
|
export function formatTokens(n: number): string {
|
|
4
2
|
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + "M";
|
|
5
3
|
if (n >= 1_000) return Math.round(n / 1_000) + "k";
|
|
@@ -22,8 +20,17 @@ export function formatCost(cost: number): string {
|
|
|
22
20
|
return "$" + cost.toFixed(2);
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Truncate plain text to a max character length, appending "..." if truncated.
|
|
25
|
+
*
|
|
26
|
+
* Unlike truncateToWidth from pi-tui, this does not handle wide characters or
|
|
27
|
+
* measure ANSI display width. The benefit: the "..." is plain text so it
|
|
28
|
+
* inherits any ANSI color wrapper applied around the result (pi-tui's
|
|
29
|
+
* truncateToWidth appends "..." after closing color codes, leaving it unstyled).
|
|
30
|
+
*/
|
|
31
|
+
export function truncLine(text: string, maxLen: number): string {
|
|
32
|
+
if (text.length <= maxLen) return text;
|
|
33
|
+
return text.slice(0, maxLen - 3) + "...";
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
export interface StatsData {
|
|
@@ -57,3 +64,18 @@ export function buildStatsLine(
|
|
|
57
64
|
|
|
58
65
|
return parts.map(p => theme.fg("dim", "· " + p)).join(" ");
|
|
59
66
|
}
|
|
67
|
+
|
|
68
|
+
// Build an agent label for display: "agentName: truncated task preview".
|
|
69
|
+
// Truncation width of 60 keeps compact rows readable on standard terminal widths
|
|
70
|
+
// while still showing meaningful task context.
|
|
71
|
+
export function buildAgentLabel(
|
|
72
|
+
agent: string,
|
|
73
|
+
task: string | undefined,
|
|
74
|
+
theme: { fg: (token: string, text: string) => string; bold: (text: string) => string },
|
|
75
|
+
): string {
|
|
76
|
+
const name = theme.bold(agent);
|
|
77
|
+
if (task) {
|
|
78
|
+
return name + theme.fg("dim", ": " + truncLine(task, 60));
|
|
79
|
+
}
|
|
80
|
+
return name;
|
|
81
|
+
}
|