@ian-pascoe/pi-minimal-subagents 0.16.0 → 0.17.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 +2 -2
- package/package.json +6 -6
- package/src/minimal-subagents-config.ts +65 -53
- package/src/minimal-subagents-coordinator.ts +1 -1
- package/src/minimal-subagents-extension.ts +58 -19
- package/src/minimal-subagents-rendering.ts +214 -193
- package/src/minimal-subagents-settings-writer.ts +22 -36
- package/src/minimal-subagents-status-panel.ts +93 -69
- package/src/minimal-subagents-tools.ts +22 -7
- package/src/minimal-subagents-transcript.ts +16 -11
- package/src/minimal-subagents-ui.ts +51 -154
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import type { Usage } from "@earendil-works/pi-ai";
|
|
2
2
|
import {
|
|
3
3
|
getMarkdownTheme,
|
|
4
|
-
keyHint,
|
|
5
4
|
type AgentToolResult,
|
|
6
5
|
type MessageRenderer,
|
|
7
6
|
type MessageRenderOptions,
|
|
8
7
|
type Theme,
|
|
9
|
-
type ThemeColor,
|
|
10
8
|
type ToolRenderResultOptions,
|
|
11
9
|
} from "@earendil-works/pi-coding-agent";
|
|
12
10
|
import {
|
|
13
|
-
|
|
11
|
+
COLLAPSED_LINES,
|
|
12
|
+
CollapsedPreview,
|
|
13
|
+
customMessageBox,
|
|
14
|
+
joinInline,
|
|
15
|
+
previewBody,
|
|
16
|
+
statusMark,
|
|
17
|
+
summaryExpandHint,
|
|
18
|
+
toolHeader,
|
|
19
|
+
treePrefix,
|
|
20
|
+
type StatusKind,
|
|
21
|
+
} from "@ian-pascoe/pi-utils/ui";
|
|
22
|
+
import {
|
|
14
23
|
Container,
|
|
15
24
|
Markdown,
|
|
16
25
|
sliceByColumn,
|
|
@@ -56,40 +65,19 @@ export type MinimalSubagentsStatusTheme = Pick<Theme, "fg">;
|
|
|
56
65
|
|
|
57
66
|
type RenderableCoordinatorMessage = Pick<Parameters<MessageRenderer>[0], "content" | "details">;
|
|
58
67
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
| "message"
|
|
73
|
-
| "timed out";
|
|
74
|
-
|
|
75
|
-
type SubagentStatusPresentation = { readonly symbol: string; readonly color: ThemeColor };
|
|
76
|
-
|
|
77
|
-
const SUBAGENT_STATUS_PRESENTATION = {
|
|
78
|
-
running: { symbol: "◉", color: "accent" },
|
|
79
|
-
waiting: { symbol: "◌", color: "accent" },
|
|
80
|
-
completed: { symbol: "✓", color: "success" },
|
|
81
|
-
failed: { symbol: "×", color: "error" },
|
|
82
|
-
cancelled: { symbol: "■", color: "warning" },
|
|
83
|
-
interrupted: { symbol: "!", color: "warning" },
|
|
84
|
-
unavailable: { symbol: "!", color: "warning" },
|
|
85
|
-
idle: { symbol: "○", color: "dim" },
|
|
86
|
-
delivered: { symbol: "→", color: "accent" },
|
|
87
|
-
"delivered-via-wait": { symbol: "→", color: "accent" },
|
|
88
|
-
queued: { symbol: "↗", color: "accent" },
|
|
89
|
-
"started-turn": { symbol: "◉", color: "accent" },
|
|
90
|
-
message: { symbol: "→", color: "accent" },
|
|
91
|
-
"timed out": { symbol: "!", color: "warning" },
|
|
92
|
-
} satisfies { readonly [Status in SubagentPresentationStatus]: SubagentStatusPresentation };
|
|
68
|
+
/** Status Mark kind of each subagent status that has one; any other status reads as idle. */
|
|
69
|
+
const SUBAGENT_STATUS_KIND = new Map<string, StatusKind>([
|
|
70
|
+
["running", "active"],
|
|
71
|
+
["waiting", "active"],
|
|
72
|
+
["started-turn", "active"],
|
|
73
|
+
["completed", "done"],
|
|
74
|
+
["failed", "failed"],
|
|
75
|
+
["cancelled", "stopped"],
|
|
76
|
+
["interrupted", "warning"],
|
|
77
|
+
["unavailable", "warning"],
|
|
78
|
+
["timed out", "warning"],
|
|
79
|
+
["idle", "idle"],
|
|
80
|
+
]);
|
|
93
81
|
|
|
94
82
|
function coordinatorMessageText(content: RenderableCoordinatorMessage["content"]): string {
|
|
95
83
|
if (!Array.isArray(content)) return stripCoordinatorMessageEnvelope(content);
|
|
@@ -134,46 +122,64 @@ export function subagentStatusLadder(agent: {
|
|
|
134
122
|
return agent.latest_turn?.status ?? "idle";
|
|
135
123
|
}
|
|
136
124
|
|
|
137
|
-
function
|
|
138
|
-
|
|
139
|
-
const known = status as keyof typeof SUBAGENT_STATUS_PRESENTATION;
|
|
140
|
-
return SUBAGENT_STATUS_PRESENTATION[known] ?? SUBAGENT_STATUS_PRESENTATION.idle;
|
|
125
|
+
function subagentStatusKind(status: string): StatusKind {
|
|
126
|
+
return SUBAGENT_STATUS_KIND.get(status) ?? "idle";
|
|
141
127
|
}
|
|
142
128
|
|
|
143
|
-
/** Render the
|
|
144
|
-
export function
|
|
129
|
+
/** Render the Status Mark of one subagent status, for widgets and overlay rows. */
|
|
130
|
+
export function renderSubagentStatusMark(
|
|
145
131
|
theme: MinimalSubagentsStatusTheme,
|
|
146
132
|
status: string,
|
|
147
133
|
): string {
|
|
148
|
-
|
|
149
|
-
return theme.fg(presentation.color, presentation.symbol);
|
|
134
|
+
return statusMark(theme, subagentStatusKind(status));
|
|
150
135
|
}
|
|
151
136
|
|
|
152
|
-
/** Render a subagent status
|
|
137
|
+
/** Render a subagent status word: `error` when failed, `warning` when it needs attention, else `muted`. */
|
|
153
138
|
export function renderSubagentStatusLabel(
|
|
154
139
|
theme: MinimalSubagentsStatusTheme,
|
|
155
140
|
status: string,
|
|
156
141
|
): string {
|
|
157
|
-
const
|
|
158
|
-
return theme.fg(
|
|
142
|
+
const kind = subagentStatusKind(status);
|
|
143
|
+
return theme.fg(kind === "failed" ? "error" : kind === "warning" ? "warning" : "muted", status);
|
|
159
144
|
}
|
|
160
145
|
|
|
161
|
-
|
|
162
|
-
|
|
146
|
+
/**
|
|
147
|
+
* Tree prefixes for rows listed in pre-order with their depth: nothing for a root row, then
|
|
148
|
+
* `├─ `/`└─ ` with `│ ` guides for the ancestors that still have later siblings.
|
|
149
|
+
*/
|
|
150
|
+
export function treeRowPrefixes(depths: readonly number[]): string[] {
|
|
151
|
+
const isLastAt = (index: number): boolean => {
|
|
152
|
+
const depth = depths[index] ?? 0;
|
|
153
|
+
for (let next = index + 1; next < depths.length; next++) {
|
|
154
|
+
const nextDepth = depths[next] ?? 0;
|
|
155
|
+
if (nextDepth < depth) return true;
|
|
156
|
+
if (nextDepth === depth) return false;
|
|
157
|
+
}
|
|
158
|
+
return true;
|
|
159
|
+
};
|
|
160
|
+
return depths.map((depth, index) => {
|
|
161
|
+
if (depth === 0) return "";
|
|
162
|
+
const ancestorsLast: boolean[] = [];
|
|
163
|
+
let cursor = index - 1;
|
|
164
|
+
for (let level = depth - 1; level >= 1; level--) {
|
|
165
|
+
while (cursor >= 0 && depths[cursor] !== level) cursor--;
|
|
166
|
+
ancestorsLast.unshift(cursor < 0 || isLastAt(cursor));
|
|
167
|
+
}
|
|
168
|
+
return treePrefix(ancestorsLast, isLastAt(index));
|
|
169
|
+
});
|
|
163
170
|
}
|
|
164
171
|
|
|
165
172
|
function renderSubagentSummary(
|
|
166
173
|
theme: MinimalSubagentsRenderTheme,
|
|
167
|
-
status: string,
|
|
174
|
+
status: string | undefined,
|
|
168
175
|
agentId: string,
|
|
169
176
|
metrics: readonly string[] = [],
|
|
170
177
|
): string {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
].join(renderSubagentSeparator(theme));
|
|
178
|
+
return joinInline(theme, [
|
|
179
|
+
theme.fg("accent", agentId),
|
|
180
|
+
status === undefined ? undefined : renderSubagentStatusLabel(theme, status),
|
|
181
|
+
...metrics.filter(Boolean).map((metric) => theme.fg("muted", metric)),
|
|
182
|
+
]);
|
|
177
183
|
}
|
|
178
184
|
|
|
179
185
|
function renderLabelValue(theme: MinimalSubagentsRenderTheme, label: string, value: string): Text {
|
|
@@ -213,58 +219,50 @@ function renderFallbackToolResult(
|
|
|
213
219
|
result: AgentToolResult<unknown>,
|
|
214
220
|
theme: MinimalSubagentsRenderTheme,
|
|
215
221
|
isError: boolean,
|
|
222
|
+
expanded: boolean,
|
|
216
223
|
): Component {
|
|
217
224
|
const content = toolResultText(result) || "(no output)";
|
|
218
|
-
return new Text(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
function isBlankRenderedLine(line: string): boolean {
|
|
229
|
-
return visibleWidth(line.trim()) === 0;
|
|
225
|
+
return new Text(
|
|
226
|
+
previewBody(theme, content.split("\n"), {
|
|
227
|
+
limit: COLLAPSED_LINES.fallback,
|
|
228
|
+
expanded,
|
|
229
|
+
color: isError ? "error" : "toolOutput",
|
|
230
|
+
}).join("\n"),
|
|
231
|
+
0,
|
|
232
|
+
0,
|
|
233
|
+
);
|
|
230
234
|
}
|
|
231
235
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
236
|
+
/**
|
|
237
|
+
* A collapsed result: its one-line summary above a body preview, or, with no body, the summary
|
|
238
|
+
* alone ending in Pi's summary Expand Hint.
|
|
239
|
+
*/
|
|
240
|
+
function collapsedWithBody(
|
|
241
|
+
theme: MinimalSubagentsRenderTheme,
|
|
242
|
+
summary: string,
|
|
243
|
+
body: Component | undefined,
|
|
244
|
+
): Component {
|
|
245
|
+
if (!body) return new Text(`${summary}${summaryExpandHint(theme)}`, 0, 0);
|
|
246
|
+
const container = new Container();
|
|
247
|
+
container.addChild(new Text(summary, 0, 0));
|
|
248
|
+
container.addChild(body);
|
|
249
|
+
return container;
|
|
235
250
|
}
|
|
236
251
|
|
|
237
|
-
/**
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
render(width: number): string[] {
|
|
245
|
-
const lines = withoutTrailingBlankLines(this.content.render(width));
|
|
246
|
-
if (lines.length <= COLLAPSED_PREVIEW_LINES) return lines;
|
|
247
|
-
// A cut at a paragraph break would leave a blank line above the hint.
|
|
248
|
-
const shown = withoutTrailingBlankLines(lines.slice(0, COLLAPSED_PREVIEW_LINES));
|
|
249
|
-
const hidden = lines.length - shown.length;
|
|
250
|
-
return [
|
|
251
|
-
...shown,
|
|
252
|
-
// The result's first line already carries the expansion hint.
|
|
253
|
-
this.theme.fg("muted", `... (${hidden} more lines)`),
|
|
254
|
-
];
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
invalidate(): void {
|
|
258
|
-
this.content.invalidate();
|
|
259
|
-
}
|
|
252
|
+
/** Pre-styled text in its Collapsed View: wrapped, clipped to `limit` lines, with Pi's Expand Hint. */
|
|
253
|
+
function collapsedText(
|
|
254
|
+
theme: MinimalSubagentsRenderTheme,
|
|
255
|
+
content: string,
|
|
256
|
+
limit: number = COLLAPSED_LINES.fallback,
|
|
257
|
+
): Component {
|
|
258
|
+
return new CollapsedPreview(theme, new Text(content, 0, 0), { limit, expanded: false });
|
|
260
259
|
}
|
|
261
260
|
|
|
262
261
|
function collapsedMarkdownPreview(content: string, theme: MinimalSubagentsRenderTheme): Component {
|
|
263
|
-
return new CollapsedPreview(new Markdown(content, 0, 0, getMarkdownTheme()),
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
return new CollapsedPreview(new Text(content, 0, 0), theme);
|
|
262
|
+
return new CollapsedPreview(theme, new Markdown(content, 0, 0, getMarkdownTheme()), {
|
|
263
|
+
limit: COLLAPSED_LINES.fallback,
|
|
264
|
+
expanded: false,
|
|
265
|
+
});
|
|
268
266
|
}
|
|
269
267
|
|
|
270
268
|
const ToolArgumentsSchema = Type.Record(Type.String(), Type.Unknown());
|
|
@@ -307,13 +305,13 @@ function formatActivityLine(
|
|
|
307
305
|
formatSubagentPreview(text, Math.max(1, width - visibleWidth(prefix)));
|
|
308
306
|
if (activity.label.startsWith("tool call ")) {
|
|
309
307
|
const name = activity.label.slice("tool call ".length);
|
|
310
|
-
const prefix =
|
|
311
|
-
return `${theme.fg("
|
|
308
|
+
const prefix = `${name} `;
|
|
309
|
+
return `${theme.fg("toolTitle", name)} ${theme.fg("dim", fit(prefix, toolArgumentSummary(activity.content)))}`;
|
|
312
310
|
}
|
|
313
311
|
if (activity.label.startsWith("tool result ")) {
|
|
314
312
|
const failed = activity.label.endsWith(" (error)");
|
|
315
313
|
const name = activity.label.slice("tool result ".length).replace(/ \(error\)$/, "");
|
|
316
|
-
const prefix = `${
|
|
314
|
+
const prefix = `${name}: `;
|
|
317
315
|
return theme.fg(failed ? "error" : "dim", `${prefix}${fit(prefix, activity.content)}`);
|
|
318
316
|
}
|
|
319
317
|
if (activity.label === "reasoning") {
|
|
@@ -338,9 +336,11 @@ export function drawActivityRail(
|
|
|
338
336
|
return items.flatMap((lines, index) => {
|
|
339
337
|
const last = index === items.length - 1;
|
|
340
338
|
const [first = "", ...rest] = lines;
|
|
339
|
+
// The guide for one ancestor is the leading segment of a nested row's tree prefix.
|
|
340
|
+
const guide = treePrefix([last], true).slice(0, ACTIVITY_RAIL_WIDTH);
|
|
341
341
|
return [
|
|
342
|
-
`${theme.fg("dim", last
|
|
343
|
-
...rest.map((line) => `${theme.fg("dim",
|
|
342
|
+
`${theme.fg("dim", treePrefix([], last))}${first}`,
|
|
343
|
+
...rest.map((line) => `${theme.fg("dim", guide)}${line}`),
|
|
344
344
|
];
|
|
345
345
|
});
|
|
346
346
|
}
|
|
@@ -411,8 +411,8 @@ export function formatSubagentPreview(content: string | undefined, maxWidth = 72
|
|
|
411
411
|
const singleLine = (content ?? "").replace(/\s+/g, " ").trim();
|
|
412
412
|
const boundedWidth = Math.max(1, maxWidth);
|
|
413
413
|
if (visibleWidth(singleLine) <= boundedWidth) return singleLine;
|
|
414
|
-
if (boundedWidth
|
|
415
|
-
return `${sliceByColumn(singleLine, 0, boundedWidth -
|
|
414
|
+
if (boundedWidth <= 3) return ".".repeat(boundedWidth);
|
|
415
|
+
return `${sliceByColumn(singleLine, 0, boundedWidth - 3, true).trimEnd()}...`;
|
|
416
416
|
}
|
|
417
417
|
|
|
418
418
|
/** Format complete Pi usage metrics for expanded subagent output. */
|
|
@@ -429,10 +429,6 @@ export function formatSubagentUsage(usage: Usage | undefined): string | undefine
|
|
|
429
429
|
return values.join(" · ");
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
-
function coordinatorToolCallTitle(theme: MinimalSubagentsRenderTheme, label: string): string {
|
|
433
|
-
return theme.fg("toolTitle", theme.bold(label));
|
|
434
|
-
}
|
|
435
|
-
|
|
436
432
|
/** Launch settings the caller chose explicitly; defaults are omitted to keep the header short. */
|
|
437
433
|
function spawnCallLaunchSummary(args: SpawnCallArguments): string[] {
|
|
438
434
|
const profile = args.model
|
|
@@ -464,19 +460,28 @@ function renderCallWithText(
|
|
|
464
460
|
expanded: boolean,
|
|
465
461
|
): Component {
|
|
466
462
|
if (!text) return new Text(header, 0, 0);
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
463
|
+
return new Text(
|
|
464
|
+
[
|
|
465
|
+
header,
|
|
466
|
+
...previewBody(theme, text.split("\n"), { limit: COLLAPSED_LINES.fallback, expanded }),
|
|
467
|
+
].join("\n"),
|
|
468
|
+
0,
|
|
469
|
+
0,
|
|
470
|
+
);
|
|
471
471
|
}
|
|
472
472
|
|
|
473
473
|
function renderManagementToolCall(
|
|
474
|
-
|
|
474
|
+
toolName: "subagent_cancel" | "subagent_delete",
|
|
475
475
|
args: ManagementCallArguments,
|
|
476
476
|
theme: MinimalSubagentsRenderTheme,
|
|
477
477
|
): Component {
|
|
478
478
|
return new Text(
|
|
479
|
-
|
|
479
|
+
toolHeader(
|
|
480
|
+
theme,
|
|
481
|
+
toolName,
|
|
482
|
+
args.agent_id ?? "agent",
|
|
483
|
+
args.recursive === false ? "target only" : "recursive",
|
|
484
|
+
),
|
|
480
485
|
0,
|
|
481
486
|
0,
|
|
482
487
|
);
|
|
@@ -493,11 +498,7 @@ function renderSpawnResult(
|
|
|
493
498
|
const agent = details.agent;
|
|
494
499
|
const launchContract = agent?.launch_contract;
|
|
495
500
|
if (!options.expanded) {
|
|
496
|
-
return
|
|
497
|
-
`${renderSubagentSummary(theme, status, agentId)}${collapsedExpansionHint(theme)}`,
|
|
498
|
-
0,
|
|
499
|
-
0,
|
|
500
|
-
);
|
|
501
|
+
return collapsedWithBody(theme, renderSubagentSummary(theme, status, agentId), undefined);
|
|
501
502
|
}
|
|
502
503
|
const container = new Container();
|
|
503
504
|
container.addChild(new Text(renderSubagentSummary(theme, status, agentId), 0, 0));
|
|
@@ -541,7 +542,13 @@ function renderMessageResult(
|
|
|
541
542
|
const metrics = historicalBehavior ? [historicalBehavior] : [];
|
|
542
543
|
const disposition = messageDisposition(details);
|
|
543
544
|
const summary = renderSubagentSummary(theme, disposition, agentId, metrics);
|
|
544
|
-
if (!options.expanded)
|
|
545
|
+
if (!options.expanded) {
|
|
546
|
+
return collapsedWithBody(
|
|
547
|
+
theme,
|
|
548
|
+
summary,
|
|
549
|
+
details.error ? collapsedText(theme, theme.fg("error", details.error)) : undefined,
|
|
550
|
+
);
|
|
551
|
+
}
|
|
545
552
|
const container = new Container();
|
|
546
553
|
container.addChild(new Text(summary, 0, 0));
|
|
547
554
|
appendTextSection(container, theme, "Recipient", agentId);
|
|
@@ -576,7 +583,7 @@ function renderWaitProgress(
|
|
|
576
583
|
if (!liveTurn) return new Text(summary, 0, 0);
|
|
577
584
|
const container = new Container();
|
|
578
585
|
container.addChild(
|
|
579
|
-
new Text(options.expanded ? summary : `${summary}${
|
|
586
|
+
new Text(options.expanded ? summary : `${summary}${summaryExpandHint(theme)}`, 0, 0),
|
|
580
587
|
);
|
|
581
588
|
container.addChild(liveTurn);
|
|
582
589
|
return container;
|
|
@@ -601,24 +608,21 @@ function collapsedWaitBody(
|
|
|
601
608
|
status: string,
|
|
602
609
|
theme: MinimalSubagentsRenderTheme,
|
|
603
610
|
): Component | undefined {
|
|
604
|
-
if (details.event === "message") return
|
|
611
|
+
if (details.event === "message") return collapsedText(theme, details.message);
|
|
605
612
|
if (details.event === "timeout") {
|
|
606
613
|
const labels = timeoutActivityLabels(details);
|
|
607
614
|
return labels.length > 0
|
|
608
|
-
?
|
|
615
|
+
? collapsedText(theme, theme.fg("dim", labels.join(" \u00b7 ")))
|
|
609
616
|
: undefined;
|
|
610
617
|
}
|
|
611
618
|
if (details.already_delivered) {
|
|
612
|
-
return
|
|
619
|
+
return collapsedText(theme, theme.fg("muted", ALREADY_DELIVERED_TEXT));
|
|
613
620
|
}
|
|
614
621
|
const output = details.output ?? "";
|
|
615
622
|
if (status === "completed") {
|
|
616
623
|
return output ? collapsedMarkdownPreview(output, theme) : undefined;
|
|
617
624
|
}
|
|
618
|
-
return
|
|
619
|
-
theme.fg("error", details.error ?? (output || "(no error detail)")),
|
|
620
|
-
theme,
|
|
621
|
-
);
|
|
625
|
+
return collapsedText(theme, theme.fg("error", details.error ?? (output || "(no error detail)")));
|
|
622
626
|
}
|
|
623
627
|
|
|
624
628
|
function renderWaitResult(
|
|
@@ -655,13 +659,7 @@ function renderWaitResult(
|
|
|
655
659
|
const summary = renderSubagentSummary(theme, status, agentId, metrics);
|
|
656
660
|
if (options.isPartial) return new Text(summary, 0, 0);
|
|
657
661
|
if (!options.expanded) {
|
|
658
|
-
|
|
659
|
-
const heading = `${summary}${collapsedExpansionHint(theme)}`;
|
|
660
|
-
if (!body) return new Text(heading, 0, 0);
|
|
661
|
-
const container = new Container();
|
|
662
|
-
container.addChild(new Text(heading, 0, 0));
|
|
663
|
-
container.addChild(body);
|
|
664
|
-
return container;
|
|
662
|
+
return collapsedWithBody(theme, summary, collapsedWaitBody(details, status, theme));
|
|
665
663
|
}
|
|
666
664
|
const container = new Container();
|
|
667
665
|
container.addChild(new Text(summary, 0, 0));
|
|
@@ -769,12 +767,11 @@ function renderDetailedStatusAgent(
|
|
|
769
767
|
const summary = renderSubagentSummary(theme, status, id, metrics);
|
|
770
768
|
if (!options.expanded) {
|
|
771
769
|
const activity = latestProgressActivity(agent.recent_activity ?? []);
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
return collapsed;
|
|
770
|
+
return collapsedWithBody(
|
|
771
|
+
theme,
|
|
772
|
+
summary,
|
|
773
|
+
activity.length === 0 ? undefined : new ActivityLines(activity, theme),
|
|
774
|
+
);
|
|
778
775
|
}
|
|
779
776
|
const container = new Container();
|
|
780
777
|
container.addChild(new Text(summary, 0, 0));
|
|
@@ -874,17 +871,20 @@ function renderStatusResult(
|
|
|
874
871
|
): Component {
|
|
875
872
|
if ("agents" in details) {
|
|
876
873
|
const counts = countDirectStatusAgents(details.agents);
|
|
877
|
-
const summary = [
|
|
874
|
+
const summary = joinInline(theme, [
|
|
878
875
|
theme.fg("muted", `${counts.children} children`),
|
|
879
876
|
theme.fg(counts.running > 0 ? "accent" : "dim", `${counts.running} running`),
|
|
880
|
-
]
|
|
881
|
-
const rows =
|
|
882
|
-
renderDirectStatusRows(details.agents, theme).join("\n") || theme.fg("dim", "(no agents)");
|
|
877
|
+
]);
|
|
878
|
+
const rows = renderDirectStatusRows(details.agents, theme);
|
|
883
879
|
const container = new Container();
|
|
880
|
+
container.addChild(new Text(summary, 0, 0));
|
|
884
881
|
container.addChild(
|
|
885
|
-
new
|
|
882
|
+
new CollapsedPreview(
|
|
883
|
+
theme,
|
|
884
|
+
new Text(rows.length === 0 ? theme.fg("dim", "(no agents)") : rows.join("\n"), 0, 0),
|
|
885
|
+
{ limit: COLLAPSED_LINES.list, expanded: options.expanded },
|
|
886
|
+
),
|
|
886
887
|
);
|
|
887
|
-
container.addChild(options.expanded ? new Text(rows, 0, 0) : collapsedTextPreview(rows, theme));
|
|
888
888
|
return container;
|
|
889
889
|
}
|
|
890
890
|
return renderDetailedStatusAgent(details.agent, options, theme);
|
|
@@ -898,12 +898,12 @@ function renderCancelResult(
|
|
|
898
898
|
const turns = details.cancelled_turn_ids;
|
|
899
899
|
const summary =
|
|
900
900
|
turns.length > 0
|
|
901
|
-
? renderSubagentSummary(theme,
|
|
901
|
+
? renderSubagentSummary(theme, undefined, details.agent_id, [
|
|
902
902
|
`${turns.length} ${turns.length === 1 ? "turn" : "turns"} cancelled`,
|
|
903
903
|
details.affected_agent_ids.join(", "),
|
|
904
904
|
])
|
|
905
|
-
: renderSubagentSummary(theme,
|
|
906
|
-
if (!options.expanded) return
|
|
905
|
+
: renderSubagentSummary(theme, undefined, details.agent_id, ["no active turns"]);
|
|
906
|
+
if (!options.expanded) return collapsedWithBody(theme, summary, undefined);
|
|
907
907
|
const container = new Container();
|
|
908
908
|
container.addChild(new Text(summary, 0, 0));
|
|
909
909
|
container.addChild(renderLabelValue(theme, "Requested target", details.agent_id));
|
|
@@ -925,7 +925,7 @@ function renderDeleteResult(
|
|
|
925
925
|
options: ToolRenderResultOptions,
|
|
926
926
|
theme: MinimalSubagentsRenderTheme,
|
|
927
927
|
): Component {
|
|
928
|
-
const status = details.failures.length > 0 ? "failed" :
|
|
928
|
+
const status = details.failures.length > 0 ? "failed" : undefined;
|
|
929
929
|
const deletedCount = details.deleted_agent_ids.length;
|
|
930
930
|
const metrics = [
|
|
931
931
|
`${deletedCount} ${deletedCount === 1 ? "agent" : "agents"} deleted`,
|
|
@@ -933,7 +933,21 @@ function renderDeleteResult(
|
|
|
933
933
|
details.failures.length > 0 ? `${details.failures.length} failed` : undefined,
|
|
934
934
|
].filter((metric): metric is string => metric !== undefined);
|
|
935
935
|
const summary = renderSubagentSummary(theme, status, details.agent_id, metrics);
|
|
936
|
-
if (!options.expanded)
|
|
936
|
+
if (!options.expanded) {
|
|
937
|
+
return collapsedWithBody(
|
|
938
|
+
theme,
|
|
939
|
+
summary,
|
|
940
|
+
details.failures.length > 0
|
|
941
|
+
? collapsedText(
|
|
942
|
+
theme,
|
|
943
|
+
theme.fg(
|
|
944
|
+
"error",
|
|
945
|
+
details.failures.map((failure) => `${failure.agent_id}: ${failure.error}`).join("\n"),
|
|
946
|
+
),
|
|
947
|
+
)
|
|
948
|
+
: undefined,
|
|
949
|
+
);
|
|
950
|
+
}
|
|
937
951
|
const container = new Container();
|
|
938
952
|
container.addChild(new Text(summary, 0, 0));
|
|
939
953
|
container.addChild(renderLabelValue(theme, "Requested target", details.agent_id));
|
|
@@ -971,38 +985,41 @@ export function renderCoordinatorToolCall(
|
|
|
971
985
|
expanded = false,
|
|
972
986
|
): Component {
|
|
973
987
|
const parsed = parseCoordinatorToolCall(toolName, args);
|
|
974
|
-
if (parsed === undefined) return new Text(
|
|
988
|
+
if (parsed === undefined) return new Text(toolHeader(theme, toolName), 0, 0);
|
|
975
989
|
switch (parsed.toolName) {
|
|
976
990
|
case "subagent": {
|
|
977
|
-
const header =
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
991
|
+
const header = toolHeader(
|
|
992
|
+
theme,
|
|
993
|
+
"subagent",
|
|
994
|
+
parsed.args.agent_id ?? "generated",
|
|
995
|
+
spawnCallLaunchSummary(parsed.args).join(" · ") || undefined,
|
|
996
|
+
);
|
|
981
997
|
return renderCallWithText(header, parsed.args.task, theme, expanded);
|
|
982
998
|
}
|
|
983
999
|
case "agent_message":
|
|
984
1000
|
return renderCallWithText(
|
|
985
|
-
|
|
1001
|
+
toolHeader(theme, "agent_message", parsed.args.agent_id ?? "parent"),
|
|
986
1002
|
parsed.args.message,
|
|
987
1003
|
theme,
|
|
988
1004
|
expanded,
|
|
989
1005
|
);
|
|
990
1006
|
case "subagent_wait":
|
|
991
|
-
return new Text(
|
|
992
|
-
`${coordinatorToolCallTitle(theme, "Wait")} ${theme.fg("accent", parsed.args.agent_id ?? "agent")}`,
|
|
993
|
-
0,
|
|
994
|
-
0,
|
|
995
|
-
);
|
|
1007
|
+
return new Text(toolHeader(theme, "subagent_wait", parsed.args.agent_id ?? "agent"), 0, 0);
|
|
996
1008
|
case "subagent_status":
|
|
997
1009
|
return new Text(
|
|
998
|
-
|
|
1010
|
+
toolHeader(
|
|
1011
|
+
theme,
|
|
1012
|
+
"subagent_status",
|
|
1013
|
+
parsed.args.agent_id,
|
|
1014
|
+
parsed.args.agent_id === undefined ? "children" : undefined,
|
|
1015
|
+
),
|
|
999
1016
|
0,
|
|
1000
1017
|
0,
|
|
1001
1018
|
);
|
|
1002
1019
|
case "subagent_cancel":
|
|
1003
|
-
return renderManagementToolCall("
|
|
1020
|
+
return renderManagementToolCall("subagent_cancel", parsed.args, theme);
|
|
1004
1021
|
case "subagent_delete":
|
|
1005
|
-
return renderManagementToolCall("
|
|
1022
|
+
return renderManagementToolCall("subagent_delete", parsed.args, theme);
|
|
1006
1023
|
}
|
|
1007
1024
|
}
|
|
1008
1025
|
|
|
@@ -1021,7 +1038,9 @@ export function renderCoordinatorToolResult(
|
|
|
1021
1038
|
if (progress) return renderWaitProgress(progress, options, theme, renderLiveTurn);
|
|
1022
1039
|
}
|
|
1023
1040
|
const parsedResult = parseCoordinatorToolResult(toolName, result.details);
|
|
1024
|
-
if (parsedResult === undefined)
|
|
1041
|
+
if (parsedResult === undefined) {
|
|
1042
|
+
return renderFallbackToolResult(result, theme, isError, options.expanded);
|
|
1043
|
+
}
|
|
1025
1044
|
const parsedCall = parseCoordinatorToolCall(toolName, args);
|
|
1026
1045
|
switch (parsedResult.toolName) {
|
|
1027
1046
|
case "subagent":
|
|
@@ -1060,7 +1079,7 @@ export function renderMinimalSubagentsMessage(
|
|
|
1060
1079
|
options: MessageRenderOptions,
|
|
1061
1080
|
theme: MinimalSubagentsRenderTheme,
|
|
1062
1081
|
): Component {
|
|
1063
|
-
return renderCoordinatorMessage("
|
|
1082
|
+
return renderCoordinatorMessage("message", message, options, theme);
|
|
1064
1083
|
}
|
|
1065
1084
|
|
|
1066
1085
|
/** Render automatic successful agent results with expandable Markdown output. */
|
|
@@ -1069,7 +1088,7 @@ export function renderMinimalSubagentsResult(
|
|
|
1069
1088
|
options: MessageRenderOptions,
|
|
1070
1089
|
theme: MinimalSubagentsRenderTheme,
|
|
1071
1090
|
): Component {
|
|
1072
|
-
return renderCoordinatorMessage("
|
|
1091
|
+
return renderCoordinatorMessage("result", message, options, theme);
|
|
1073
1092
|
}
|
|
1074
1093
|
|
|
1075
1094
|
function messageSource(details: CoordinatorMessageRenderDetails | undefined): string {
|
|
@@ -1082,9 +1101,9 @@ function messageSourceTurn(
|
|
|
1082
1101
|
return details?.source_turn_id ?? details?.turn_id;
|
|
1083
1102
|
}
|
|
1084
1103
|
|
|
1104
|
+
/** `[subagents] result · worker → root · completed · 3s` above the Markdown, like Pi's `[skill] name`. */
|
|
1085
1105
|
function renderCoordinatorMessage(
|
|
1086
|
-
|
|
1087
|
-
symbol: string,
|
|
1106
|
+
kind: "message" | "result",
|
|
1088
1107
|
message: RenderableCoordinatorMessage,
|
|
1089
1108
|
options: MessageRenderOptions,
|
|
1090
1109
|
theme: MinimalSubagentsRenderTheme,
|
|
@@ -1094,29 +1113,31 @@ function renderCoordinatorMessage(
|
|
|
1094
1113
|
const source = messageSource(details);
|
|
1095
1114
|
const destination = details?.destination_agent_id ?? "recipient";
|
|
1096
1115
|
const sourceTurn = messageSourceTurn(details);
|
|
1097
|
-
const route = `${theme.fg("accent", theme.bold(source))} ${theme.fg("dim", "→")} ${theme.fg("accent", theme.bold(destination))}`;
|
|
1098
1116
|
const metrics = [formatSubagentDuration(details?.elapsed_ms), formatSubagentCost(details?.usage)];
|
|
1099
|
-
const heading = [
|
|
1100
|
-
|
|
1101
|
-
|
|
1117
|
+
const heading = joinInline(theme, [
|
|
1118
|
+
theme.fg("customMessageText", kind),
|
|
1119
|
+
theme.fg("customMessageText", `${source} \u2192 ${destination}`),
|
|
1102
1120
|
details?.status ? renderSubagentStatusLabel(theme, details.status) : undefined,
|
|
1103
1121
|
...metrics.map((metric) => (metric ? theme.fg("muted", metric) : undefined)),
|
|
1104
|
-
]
|
|
1105
|
-
|
|
1106
|
-
.
|
|
1107
|
-
|
|
1122
|
+
]);
|
|
1123
|
+
const markdown = new Markdown(content, 0, 0, getMarkdownTheme(), {
|
|
1124
|
+
color: (text) => theme.fg("customMessageText", text),
|
|
1125
|
+
});
|
|
1126
|
+
const box = (body: Component[]) =>
|
|
1127
|
+
customMessageBox(theme, { outputPad: options.outputPad, source: "subagents", heading }, body);
|
|
1108
1128
|
if (!options.expanded) {
|
|
1109
|
-
box
|
|
1110
|
-
|
|
1111
|
-
|
|
1129
|
+
return box([
|
|
1130
|
+
new CollapsedPreview(theme, markdown, { limit: COLLAPSED_LINES.fallback, expanded: false }),
|
|
1131
|
+
]);
|
|
1112
1132
|
}
|
|
1113
|
-
const
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
container.addChild(new Spacer(1));
|
|
1117
|
-
container.addChild(new Markdown(content, 0, 0, getMarkdownTheme()));
|
|
1133
|
+
const body: Component[] = [];
|
|
1134
|
+
if (sourceTurn) body.push(renderLabelValue(theme, "Source turn", sourceTurn), new Spacer(1));
|
|
1135
|
+
body.push(markdown);
|
|
1118
1136
|
const usageText = formatSubagentUsage(details?.usage);
|
|
1119
|
-
if (usageText)
|
|
1120
|
-
|
|
1121
|
-
|
|
1137
|
+
if (usageText) {
|
|
1138
|
+
const usage = new Container();
|
|
1139
|
+
appendTextSection(usage, theme, "Usage", usageText);
|
|
1140
|
+
body.push(usage);
|
|
1141
|
+
}
|
|
1142
|
+
return box(body);
|
|
1122
1143
|
}
|