@ian-pascoe/pi-minimal-subagents 0.1.1 → 0.2.1
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 +58 -5
- package/package.json +1 -1
- package/src/minimal-subagents-capabilities.ts +8 -12
- package/src/minimal-subagents-config.ts +169 -63
- package/src/minimal-subagents-context.ts +7 -1
- package/src/minimal-subagents-coordinator.ts +932 -130
- package/src/minimal-subagents-delivery-ledger.ts +521 -0
- package/src/minimal-subagents-extension.ts +382 -64
- package/src/minimal-subagents-fork-lifecycle.ts +22 -12
- package/src/minimal-subagents-message-envelope.ts +20 -0
- package/src/minimal-subagents-registry-wire.ts +269 -0
- package/src/minimal-subagents-registry.ts +1487 -137
- package/src/minimal-subagents-render-contract.ts +279 -0
- package/src/minimal-subagents-rendering.ts +445 -399
- package/src/minimal-subagents-session-wire.ts +42 -0
- package/src/minimal-subagents-sessions.ts +428 -120
- package/src/minimal-subagents-shutdown.ts +1 -1
- package/src/minimal-subagents-tool-schemas.ts +21 -7
- package/src/minimal-subagents-tools.ts +46 -18
- package/src/minimal-subagents-types.ts +67 -27
- package/src/minimal-subagents-ui.ts +8 -5
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
getMarkdownTheme,
|
|
4
4
|
keyHint,
|
|
5
5
|
type AgentToolResult,
|
|
6
|
+
type MessageRenderer,
|
|
7
|
+
type MessageRenderOptions,
|
|
6
8
|
type Theme,
|
|
7
9
|
type ThemeColor,
|
|
8
10
|
type ToolRenderResultOptions,
|
|
@@ -17,25 +19,54 @@ import {
|
|
|
17
19
|
visibleWidth,
|
|
18
20
|
type Component,
|
|
19
21
|
} from "@earendil-works/pi-tui";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
22
|
+
import {
|
|
23
|
+
parseCoordinatorMessageDetails,
|
|
24
|
+
parseCoordinatorToolCall,
|
|
25
|
+
parseCoordinatorToolResult,
|
|
26
|
+
type CancelRenderDetails,
|
|
27
|
+
type CoordinatorMessageRenderDetails,
|
|
28
|
+
type CoordinatorToolCallInput,
|
|
29
|
+
type CoordinatorToolName,
|
|
30
|
+
type DeleteRenderDetails,
|
|
31
|
+
type ManagementCallArguments,
|
|
32
|
+
type MessageCallArguments,
|
|
33
|
+
type MessageRenderDetails,
|
|
34
|
+
type RenderStatusAgent,
|
|
35
|
+
type SpawnCallArguments,
|
|
36
|
+
type SpawnRenderDetails,
|
|
37
|
+
type StatusRenderDetails,
|
|
38
|
+
type WaitCallArguments,
|
|
39
|
+
type WaitRenderDetails,
|
|
40
|
+
} from "./minimal-subagents-render-contract.js";
|
|
41
|
+
import { stripCoordinatorMessageEnvelope } from "./minimal-subagents-message-envelope.js";
|
|
42
|
+
|
|
43
|
+
export type { CoordinatorToolName } from "./minimal-subagents-render-contract.js";
|
|
44
|
+
|
|
45
|
+
/** Theme operations used by Minimal Subagents transcript renderers. */
|
|
46
|
+
export type MinimalSubagentsRenderTheme = Pick<Theme, "fg" | "bg" | "bold">;
|
|
47
|
+
|
|
48
|
+
/** Theme operation shared by transcript and widget status renderers. */
|
|
49
|
+
export type MinimalSubagentsStatusTheme = Pick<Theme, "fg">;
|
|
50
|
+
|
|
51
|
+
type RenderableCoordinatorMessage = Pick<Parameters<MessageRenderer>[0], "content" | "details">;
|
|
52
|
+
|
|
53
|
+
type SubagentPresentationStatus =
|
|
54
|
+
| "running"
|
|
55
|
+
| "waiting"
|
|
56
|
+
| "completed"
|
|
57
|
+
| "failed"
|
|
58
|
+
| "cancelled"
|
|
59
|
+
| "interrupted"
|
|
60
|
+
| "unavailable"
|
|
61
|
+
| "idle"
|
|
62
|
+
| "delivered"
|
|
63
|
+
| "delivered-via-wait"
|
|
64
|
+
| "queued"
|
|
65
|
+
| "message";
|
|
66
|
+
|
|
67
|
+
type SubagentStatusPresentation = { readonly symbol: string; readonly color: ThemeColor };
|
|
68
|
+
|
|
69
|
+
const SUBAGENT_STATUS_PRESENTATION = {
|
|
39
70
|
running: { symbol: "◉", color: "accent" },
|
|
40
71
|
waiting: { symbol: "◌", color: "accent" },
|
|
41
72
|
completed: { symbol: "✓", color: "success" },
|
|
@@ -45,38 +76,19 @@ const SUBAGENT_STATUS_PRESENTATION: Record<string, { symbol: string; color: Them
|
|
|
45
76
|
unavailable: { symbol: "!", color: "warning" },
|
|
46
77
|
idle: { symbol: "○", color: "dim" },
|
|
47
78
|
delivered: { symbol: "→", color: "accent" },
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return Array.isArray(value)
|
|
62
|
-
? value.filter((item): item is string => typeof item === "string")
|
|
63
|
-
: [];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function asNumber(value: unknown): number | undefined {
|
|
67
|
-
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function coordinatorMessageText(content: unknown): string {
|
|
71
|
-
if (typeof content === "string") return content;
|
|
72
|
-
if (!Array.isArray(content)) return "";
|
|
73
|
-
return content
|
|
74
|
-
.map((item) => {
|
|
75
|
-
const block = asRecord(item);
|
|
76
|
-
return block?.type === "text" ? (asString(block.text) ?? "") : "";
|
|
77
|
-
})
|
|
78
|
-
.filter(Boolean)
|
|
79
|
-
.join("\n");
|
|
79
|
+
"delivered-via-wait": { symbol: "→", color: "accent" },
|
|
80
|
+
queued: { symbol: "↗", color: "accent" },
|
|
81
|
+
message: { symbol: "→", color: "accent" },
|
|
82
|
+
} satisfies { readonly [Status in SubagentPresentationStatus]: SubagentStatusPresentation };
|
|
83
|
+
|
|
84
|
+
function coordinatorMessageText(content: RenderableCoordinatorMessage["content"]): string {
|
|
85
|
+
if (!Array.isArray(content)) return stripCoordinatorMessageEnvelope(content);
|
|
86
|
+
return stripCoordinatorMessageEnvelope(
|
|
87
|
+
content
|
|
88
|
+
.map((item) => (item.type === "text" ? item.text : ""))
|
|
89
|
+
.filter(Boolean)
|
|
90
|
+
.join("\n"),
|
|
91
|
+
);
|
|
80
92
|
}
|
|
81
93
|
|
|
82
94
|
function toolResultText(result: AgentToolResult<unknown>): string {
|
|
@@ -84,24 +96,37 @@ function toolResultText(result: AgentToolResult<unknown>): string {
|
|
|
84
96
|
return text?.type === "text" ? text.text : "";
|
|
85
97
|
}
|
|
86
98
|
|
|
99
|
+
function subagentStatusPresentation(status: string): SubagentStatusPresentation {
|
|
100
|
+
for (const [knownStatus, presentation] of Object.entries(SUBAGENT_STATUS_PRESENTATION)) {
|
|
101
|
+
if (knownStatus === status) return presentation;
|
|
102
|
+
}
|
|
103
|
+
return SUBAGENT_STATUS_PRESENTATION.idle;
|
|
104
|
+
}
|
|
105
|
+
|
|
87
106
|
/** Render the shared semantic symbol and color for one subagent status. */
|
|
88
|
-
export function renderSubagentStatusSymbol(
|
|
89
|
-
|
|
107
|
+
export function renderSubagentStatusSymbol(
|
|
108
|
+
theme: MinimalSubagentsStatusTheme,
|
|
109
|
+
status: string,
|
|
110
|
+
): string {
|
|
111
|
+
const presentation = subagentStatusPresentation(status);
|
|
90
112
|
return theme.fg(presentation.color, presentation.symbol);
|
|
91
113
|
}
|
|
92
114
|
|
|
93
115
|
/** Render a subagent status label with the same semantic color as its symbol. */
|
|
94
|
-
export function renderSubagentStatusLabel(
|
|
95
|
-
|
|
116
|
+
export function renderSubagentStatusLabel(
|
|
117
|
+
theme: MinimalSubagentsStatusTheme,
|
|
118
|
+
status: string,
|
|
119
|
+
): string {
|
|
120
|
+
const presentation = subagentStatusPresentation(status);
|
|
96
121
|
return theme.fg(presentation.color, status);
|
|
97
122
|
}
|
|
98
123
|
|
|
99
|
-
function renderSubagentSeparator(theme:
|
|
124
|
+
function renderSubagentSeparator(theme: MinimalSubagentsRenderTheme): string {
|
|
100
125
|
return theme.fg("dim", " · ");
|
|
101
126
|
}
|
|
102
127
|
|
|
103
128
|
function renderSubagentSummary(
|
|
104
|
-
theme:
|
|
129
|
+
theme: MinimalSubagentsRenderTheme,
|
|
105
130
|
status: string,
|
|
106
131
|
agentId: string,
|
|
107
132
|
metrics: readonly string[] = [],
|
|
@@ -114,32 +139,49 @@ function renderSubagentSummary(
|
|
|
114
139
|
].join(renderSubagentSeparator(theme));
|
|
115
140
|
}
|
|
116
141
|
|
|
117
|
-
function renderLabelValue(theme:
|
|
118
|
-
|
|
119
|
-
return new Text(`${theme.fg("muted", `${label}:`)} ${text ?? ""}`, 0, 0);
|
|
142
|
+
function renderLabelValue(theme: MinimalSubagentsRenderTheme, label: string, value: string): Text {
|
|
143
|
+
return new Text(`${theme.fg("muted", `${label}:`)} ${value}`, 0, 0);
|
|
120
144
|
}
|
|
121
145
|
|
|
122
|
-
function
|
|
146
|
+
function appendSectionHeading(
|
|
123
147
|
container: Container,
|
|
124
|
-
theme:
|
|
148
|
+
theme: MinimalSubagentsRenderTheme,
|
|
125
149
|
label: string,
|
|
126
|
-
content: string | Component,
|
|
127
150
|
): void {
|
|
128
151
|
container.addChild(new Spacer(1));
|
|
129
152
|
container.addChild(new Text(theme.fg("muted", theme.bold(label)), 0, 0));
|
|
130
|
-
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function appendTextSection(
|
|
156
|
+
container: Container,
|
|
157
|
+
theme: MinimalSubagentsRenderTheme,
|
|
158
|
+
label: string,
|
|
159
|
+
content: string,
|
|
160
|
+
): void {
|
|
161
|
+
appendSectionHeading(container, theme, label);
|
|
162
|
+
container.addChild(new Text(content, 0, 0));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function appendComponentSection(
|
|
166
|
+
container: Container,
|
|
167
|
+
theme: MinimalSubagentsRenderTheme,
|
|
168
|
+
label: string,
|
|
169
|
+
content: Component,
|
|
170
|
+
): void {
|
|
171
|
+
appendSectionHeading(container, theme, label);
|
|
172
|
+
container.addChild(content);
|
|
131
173
|
}
|
|
132
174
|
|
|
133
175
|
function renderFallbackToolResult(
|
|
134
176
|
result: AgentToolResult<unknown>,
|
|
135
|
-
theme:
|
|
177
|
+
theme: MinimalSubagentsRenderTheme,
|
|
136
178
|
isError: boolean,
|
|
137
179
|
): Component {
|
|
138
180
|
const content = toolResultText(result) || "(no output)";
|
|
139
181
|
return new Text(isError ? theme.fg("error", content) : content, 0, 0);
|
|
140
182
|
}
|
|
141
183
|
|
|
142
|
-
function collapsedExpansionHint(theme:
|
|
184
|
+
function collapsedExpansionHint(theme: MinimalSubagentsRenderTheme): string {
|
|
143
185
|
return theme.fg("dim", ` · ${keyHint("app.tools.expand", "to expand")}`);
|
|
144
186
|
}
|
|
145
187
|
|
|
@@ -165,8 +207,8 @@ export function formatSubagentTokenCount(tokens: number | undefined): string | u
|
|
|
165
207
|
}
|
|
166
208
|
|
|
167
209
|
/** Collapse multiline task or message text into one terminal-friendly preview. */
|
|
168
|
-
export function formatSubagentPreview(content: string, maxWidth = 72): string {
|
|
169
|
-
const singleLine = content.replace(/\s+/g, " ").trim();
|
|
210
|
+
export function formatSubagentPreview(content: string | undefined, maxWidth = 72): string {
|
|
211
|
+
const singleLine = (content ?? "").replace(/\s+/g, " ").trim();
|
|
170
212
|
const boundedWidth = Math.max(1, maxWidth);
|
|
171
213
|
if (visibleWidth(singleLine) <= boundedWidth) return singleLine;
|
|
172
214
|
if (boundedWidth === 1) return "…";
|
|
@@ -179,83 +221,53 @@ function currentSubagentPreviewWidth(reservedWidth: number): number {
|
|
|
179
221
|
|
|
180
222
|
/** Format complete Pi usage metrics for expanded subagent output. */
|
|
181
223
|
export function formatSubagentUsage(usage: Usage | undefined): string | undefined {
|
|
182
|
-
|
|
183
|
-
if (!usageRecord) return undefined;
|
|
224
|
+
if (usage === undefined) return undefined;
|
|
184
225
|
const values = [
|
|
185
|
-
`input ${formatSubagentTokenCount(
|
|
186
|
-
`output ${formatSubagentTokenCount(
|
|
187
|
-
`cache read ${formatSubagentTokenCount(
|
|
188
|
-
`cache write ${formatSubagentTokenCount(
|
|
189
|
-
`total ${formatSubagentTokenCount(
|
|
226
|
+
`input ${formatSubagentTokenCount(usage.input) ?? "0"}`,
|
|
227
|
+
`output ${formatSubagentTokenCount(usage.output) ?? "0"}`,
|
|
228
|
+
`cache read ${formatSubagentTokenCount(usage.cacheRead) ?? "0"}`,
|
|
229
|
+
`cache write ${formatSubagentTokenCount(usage.cacheWrite) ?? "0"}`,
|
|
230
|
+
`total ${formatSubagentTokenCount(usage.totalTokens) ?? "0"}`,
|
|
190
231
|
];
|
|
191
|
-
|
|
192
|
-
if (totalCost !== undefined && totalCost > 0) values.push(`cost $${totalCost.toFixed(4)}`);
|
|
232
|
+
if (usage.cost.total > 0) values.push(`cost $${usage.cost.total.toFixed(4)}`);
|
|
193
233
|
return values.join(" · ");
|
|
194
234
|
}
|
|
195
235
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
function coordinatorToolCallTitle(theme: Theme, label: string): string {
|
|
236
|
+
function coordinatorToolCallTitle(theme: MinimalSubagentsRenderTheme, label: string): string {
|
|
199
237
|
return theme.fg("toolTitle", theme.bold(label));
|
|
200
238
|
}
|
|
201
239
|
|
|
202
|
-
function coordinatorToolCallPreview(
|
|
203
|
-
|
|
240
|
+
function coordinatorToolCallPreview(
|
|
241
|
+
theme: MinimalSubagentsRenderTheme,
|
|
242
|
+
value: string | undefined,
|
|
243
|
+
): string {
|
|
244
|
+
return value && value.length > 0
|
|
204
245
|
? ` · ${theme.fg("dim", `“${formatSubagentPreview(value, currentSubagentPreviewWidth(36))}”`)}`
|
|
205
246
|
: "";
|
|
206
247
|
}
|
|
207
248
|
|
|
208
249
|
function renderManagementToolCall(
|
|
209
250
|
label: string,
|
|
210
|
-
args:
|
|
211
|
-
theme:
|
|
251
|
+
args: ManagementCallArguments,
|
|
252
|
+
theme: MinimalSubagentsRenderTheme,
|
|
212
253
|
): Component {
|
|
213
254
|
return new Text(
|
|
214
|
-
`${coordinatorToolCallTitle(theme, label)} ${theme.fg("accent",
|
|
255
|
+
`${coordinatorToolCallTitle(theme, label)} ${theme.fg("accent", args.agent_id ?? "agent")} ${theme.fg("dim", args.recursive === false ? "· target only" : "· recursive")}`,
|
|
215
256
|
0,
|
|
216
257
|
0,
|
|
217
258
|
);
|
|
218
259
|
}
|
|
219
260
|
|
|
220
|
-
const COORDINATOR_TOOL_CALL_RENDERERS: Record<CoordinatorToolName, CoordinatorToolCallRenderer> = {
|
|
221
|
-
subagent: (args, theme) =>
|
|
222
|
-
new Text(
|
|
223
|
-
`${coordinatorToolCallTitle(theme, "Subagent")} ${theme.fg("accent", asString(args.agent_id) ?? "generated")}${coordinatorToolCallPreview(theme, args.task)}`,
|
|
224
|
-
0,
|
|
225
|
-
0,
|
|
226
|
-
),
|
|
227
|
-
agent_message: (args, theme) =>
|
|
228
|
-
new Text(
|
|
229
|
-
`${coordinatorToolCallTitle(theme, "Message")} ${theme.fg("accent", asString(args.agent_id) ?? "parent")}${coordinatorToolCallPreview(theme, args.message)}`,
|
|
230
|
-
0,
|
|
231
|
-
0,
|
|
232
|
-
),
|
|
233
|
-
subagent_wait: (args, theme) =>
|
|
234
|
-
new Text(
|
|
235
|
-
`${coordinatorToolCallTitle(theme, "Wait")} ${theme.fg("accent", asString(args.agent_id) ?? "agent")}`,
|
|
236
|
-
0,
|
|
237
|
-
0,
|
|
238
|
-
),
|
|
239
|
-
subagent_status: (args, theme) =>
|
|
240
|
-
new Text(
|
|
241
|
-
`${coordinatorToolCallTitle(theme, "Status")} ${theme.fg("accent", asString(args.agent_id) ?? "children")}`,
|
|
242
|
-
0,
|
|
243
|
-
0,
|
|
244
|
-
),
|
|
245
|
-
subagent_cancel: (args, theme) => renderManagementToolCall("Cancel", args, theme),
|
|
246
|
-
subagent_delete: (args, theme) => renderManagementToolCall("Delete", args, theme),
|
|
247
|
-
};
|
|
248
|
-
|
|
249
261
|
function renderSpawnResult(
|
|
250
|
-
details:
|
|
262
|
+
details: SpawnRenderDetails,
|
|
251
263
|
options: ToolRenderResultOptions,
|
|
252
|
-
theme:
|
|
253
|
-
args:
|
|
264
|
+
theme: MinimalSubagentsRenderTheme,
|
|
265
|
+
args: SpawnCallArguments,
|
|
254
266
|
): Component {
|
|
255
|
-
const agentId =
|
|
256
|
-
const status =
|
|
257
|
-
const agent =
|
|
258
|
-
const launchContract =
|
|
267
|
+
const agentId = details.agent_id;
|
|
268
|
+
const status = details.status;
|
|
269
|
+
const agent = details.agent;
|
|
270
|
+
const launchContract = agent?.launch_contract;
|
|
259
271
|
if (!options.expanded) {
|
|
260
272
|
return new Text(
|
|
261
273
|
`${renderSubagentSummary(theme, status, agentId)}${collapsedExpansionHint(theme)}`,
|
|
@@ -265,64 +277,72 @@ function renderSpawnResult(
|
|
|
265
277
|
}
|
|
266
278
|
const container = new Container();
|
|
267
279
|
container.addChild(new Text(renderSubagentSummary(theme, status, agentId), 0, 0));
|
|
268
|
-
container.addChild(renderLabelValue(theme, "Turn",
|
|
269
|
-
|
|
270
|
-
const resolvedModel =
|
|
271
|
-
const resolvedThinking =
|
|
272
|
-
asString(launchContract?.thinking_level) ?? asString(args.thinking_level);
|
|
280
|
+
container.addChild(renderLabelValue(theme, "Turn", details.turn_id));
|
|
281
|
+
appendTextSection(container, theme, "Task", args.task ?? "(task unavailable)");
|
|
282
|
+
const resolvedModel = launchContract?.model ?? args.model;
|
|
283
|
+
const resolvedThinking = launchContract?.thinking_level ?? args.thinking_level;
|
|
273
284
|
const launch = [
|
|
274
|
-
`delegation ${
|
|
275
|
-
`session context ${
|
|
276
|
-
`project context ${
|
|
285
|
+
`delegation ${launchContract?.delegation ?? args.delegation ?? "none"}`,
|
|
286
|
+
`session context ${launchContract?.session_context ?? args.session_context ?? "inherit"}`,
|
|
287
|
+
`project context ${launchContract?.project_context ?? args.project_context ?? "inherit"}`,
|
|
277
288
|
resolvedModel ? `model ${resolvedModel}` : undefined,
|
|
278
289
|
resolvedThinking ? `thinking ${resolvedThinking}` : undefined,
|
|
279
|
-
].filter(
|
|
280
|
-
|
|
281
|
-
|
|
290
|
+
].filter((value): value is string => value !== undefined);
|
|
291
|
+
appendTextSection(container, theme, "Launch", launch.join(" · "));
|
|
292
|
+
appendTextSection(
|
|
282
293
|
container,
|
|
283
294
|
theme,
|
|
284
295
|
"Resolved tools",
|
|
285
|
-
|
|
296
|
+
(launchContract?.ordinary_tools ?? agent?.tools ?? []).join(", ") || "none",
|
|
286
297
|
);
|
|
287
298
|
return container;
|
|
288
299
|
}
|
|
289
300
|
|
|
301
|
+
function messageDisposition(details: MessageRenderDetails): string {
|
|
302
|
+
return "disposition" in details
|
|
303
|
+
? details.disposition
|
|
304
|
+
: details.delivered
|
|
305
|
+
? "delivered"
|
|
306
|
+
: "failed";
|
|
307
|
+
}
|
|
308
|
+
|
|
290
309
|
function renderMessageResult(
|
|
291
|
-
details:
|
|
310
|
+
details: MessageRenderDetails,
|
|
292
311
|
options: ToolRenderResultOptions,
|
|
293
|
-
theme:
|
|
294
|
-
args:
|
|
312
|
+
theme: MinimalSubagentsRenderTheme,
|
|
313
|
+
args: MessageCallArguments,
|
|
295
314
|
): Component {
|
|
296
|
-
const agentId =
|
|
297
|
-
const historicalBehavior =
|
|
315
|
+
const agentId = details.agent_id ?? args.agent_id ?? "parent";
|
|
316
|
+
const historicalBehavior = details.behavior ?? args.behavior;
|
|
298
317
|
const metrics = historicalBehavior ? [historicalBehavior] : [];
|
|
299
|
-
const
|
|
300
|
-
const summary =
|
|
301
|
-
? renderSubagentSummary(theme, "delivered", agentId, metrics)
|
|
302
|
-
: renderSubagentSummary(theme, "failed", agentId, metrics);
|
|
318
|
+
const disposition = messageDisposition(details);
|
|
319
|
+
const summary = renderSubagentSummary(theme, disposition, agentId, metrics);
|
|
303
320
|
if (!options.expanded) return new Text(`${summary}${collapsedExpansionHint(theme)}`, 0, 0);
|
|
304
321
|
const container = new Container();
|
|
305
322
|
container.addChild(new Text(summary, 0, 0));
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (error)
|
|
323
|
+
appendTextSection(container, theme, "Message", args.message ?? "(message unavailable)");
|
|
324
|
+
appendTextSection(container, theme, "Recipient", agentId);
|
|
325
|
+
appendTextSection(container, theme, "Disposition", disposition);
|
|
326
|
+
if (details.error) appendTextSection(container, theme, "Error", details.error);
|
|
310
327
|
return container;
|
|
311
328
|
}
|
|
312
329
|
|
|
313
330
|
function renderWaitResult(
|
|
314
|
-
details:
|
|
331
|
+
details: WaitRenderDetails,
|
|
315
332
|
options: ToolRenderResultOptions,
|
|
316
|
-
theme:
|
|
317
|
-
args:
|
|
333
|
+
theme: MinimalSubagentsRenderTheme,
|
|
334
|
+
args: WaitCallArguments,
|
|
318
335
|
): Component {
|
|
319
|
-
const agentId =
|
|
320
|
-
const status = options.isPartial
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
336
|
+
const agentId = details.agent_id ?? args.agent_id ?? "agent";
|
|
337
|
+
const status = options.isPartial
|
|
338
|
+
? "waiting"
|
|
339
|
+
: details.event === "message"
|
|
340
|
+
? "message"
|
|
341
|
+
: details.status;
|
|
342
|
+
const duration = formatSubagentDuration(details.elapsed_ms);
|
|
343
|
+
const tokens = formatSubagentTokenCount(details.usage?.totalTokens);
|
|
324
344
|
const metrics = [duration, tokens ? `${tokens} tokens` : undefined].filter(
|
|
325
|
-
(metric): metric is string =>
|
|
345
|
+
(metric): metric is string => metric !== undefined,
|
|
326
346
|
);
|
|
327
347
|
const summary = renderSubagentSummary(theme, status, agentId, metrics);
|
|
328
348
|
if (options.isPartial || !options.expanded) {
|
|
@@ -330,310 +350,300 @@ function renderWaitResult(
|
|
|
330
350
|
}
|
|
331
351
|
const container = new Container();
|
|
332
352
|
container.addChild(new Text(summary, 0, 0));
|
|
333
|
-
container.addChild(renderLabelValue(theme, "Turn",
|
|
334
|
-
|
|
353
|
+
container.addChild(renderLabelValue(theme, "Turn", details.turn_id ?? "unknown"));
|
|
354
|
+
if (details.event === "message") {
|
|
355
|
+
appendTextSection(container, theme, "Message", details.message);
|
|
356
|
+
container.addChild(renderLabelValue(theme, "Message ID", details.message_id));
|
|
357
|
+
return container;
|
|
358
|
+
}
|
|
359
|
+
const output = details.output ?? "";
|
|
335
360
|
if (status === "completed") {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
361
|
+
if (output.length > 0) {
|
|
362
|
+
appendComponentSection(
|
|
363
|
+
container,
|
|
364
|
+
theme,
|
|
365
|
+
"Output",
|
|
366
|
+
new Markdown(output, 0, 0, getMarkdownTheme()),
|
|
367
|
+
);
|
|
368
|
+
} else {
|
|
369
|
+
appendTextSection(container, theme, "Output", "(no output)");
|
|
370
|
+
}
|
|
342
371
|
} else {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
theme,
|
|
346
|
-
"Error",
|
|
347
|
-
(asString(details.error) ?? output) || "(no error detail)",
|
|
348
|
-
);
|
|
349
|
-
appendSection(container, theme, "Diagnostics", JSON.stringify(details, null, 2));
|
|
372
|
+
appendTextSection(container, theme, "Error", details.error ?? (output || "(no error detail)"));
|
|
373
|
+
appendTextSection(container, theme, "Diagnostics", JSON.stringify(details, null, 2));
|
|
350
374
|
}
|
|
351
|
-
const usageText = formatSubagentUsage(usage);
|
|
352
|
-
if (usageText)
|
|
375
|
+
const usageText = formatSubagentUsage(details.usage);
|
|
376
|
+
if (usageText) appendTextSection(container, theme, "Usage", usageText);
|
|
353
377
|
return container;
|
|
354
378
|
}
|
|
355
379
|
|
|
356
|
-
|
|
357
|
-
|
|
380
|
+
interface DirectStatusCounts {
|
|
381
|
+
children: number;
|
|
382
|
+
running: number;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function countDirectStatusAgents(agents: readonly RenderStatusAgent[]): DirectStatusCounts {
|
|
358
386
|
let running = 0;
|
|
359
|
-
for (const
|
|
360
|
-
const agent = asRecord(item);
|
|
361
|
-
if (!agent) continue;
|
|
362
|
-
children++;
|
|
387
|
+
for (const agent of agents) {
|
|
363
388
|
if (agent.state === "running") running++;
|
|
364
389
|
}
|
|
365
|
-
return { children, running };
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const duration = formatSubagentDuration(asNumber(agent.elapsed_ms));
|
|
382
|
-
const childCount = asNumber(agent.child_count) ?? 0;
|
|
390
|
+
return { children: agents.length, running };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function statusAgentPresentation(agent: RenderStatusAgent): string {
|
|
394
|
+
if (agent.availability === "unavailable") return "unavailable";
|
|
395
|
+
if (agent.state === "running") return "running";
|
|
396
|
+
return agent.latest_turn?.status ?? "idle";
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function renderDirectStatusRows(
|
|
400
|
+
agents: readonly RenderStatusAgent[],
|
|
401
|
+
theme: MinimalSubagentsRenderTheme,
|
|
402
|
+
): string[] {
|
|
403
|
+
return agents.map((agent) => {
|
|
404
|
+
const duration = formatSubagentDuration(agent.elapsed_ms);
|
|
405
|
+
const childCount = agent.child_count ?? 0;
|
|
383
406
|
const metrics = [duration, childCount > 0 ? `${childCount} children` : undefined].filter(
|
|
384
|
-
(metric): metric is string =>
|
|
407
|
+
(metric): metric is string => metric !== undefined,
|
|
385
408
|
);
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
409
|
+
return renderSubagentSummary(
|
|
410
|
+
theme,
|
|
411
|
+
statusAgentPresentation(agent),
|
|
412
|
+
agent.agent_id ?? "unknown",
|
|
413
|
+
metrics,
|
|
414
|
+
);
|
|
415
|
+
});
|
|
389
416
|
}
|
|
390
417
|
|
|
391
|
-
|
|
392
|
-
|
|
418
|
+
type StatusLabelValue = { readonly label: string; readonly value: string | undefined };
|
|
419
|
+
|
|
420
|
+
function renderDetailedStatusAgent(
|
|
421
|
+
agent: RenderStatusAgent,
|
|
393
422
|
options: ToolRenderResultOptions,
|
|
394
|
-
theme:
|
|
423
|
+
theme: MinimalSubagentsRenderTheme,
|
|
395
424
|
): Component {
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if (!options.expanded) return new Text(`${summary}${collapsedExpansionHint(theme)}`, 0, 0);
|
|
404
|
-
return new Text(
|
|
405
|
-
`${summary}\n${renderDirectStatusRows(agents, theme).join("\n") || theme.fg("dim", "(no agents)")}`,
|
|
406
|
-
0,
|
|
407
|
-
0,
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
const agent = asRecord(details.agent);
|
|
411
|
-
if (!agent) {
|
|
412
|
-
return new Text(
|
|
413
|
-
[theme.fg("muted", "0 children"), theme.fg("dim", "0 running")].join(
|
|
414
|
-
renderSubagentSeparator(theme),
|
|
415
|
-
),
|
|
416
|
-
0,
|
|
417
|
-
0,
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
const availability = asString(agent.availability) ?? "available";
|
|
421
|
-
const latestTurn = asRecord(agent.latest_turn);
|
|
422
|
-
const status =
|
|
423
|
-
availability === "unavailable"
|
|
424
|
-
? "unavailable"
|
|
425
|
-
: asString(agent.state) === "running"
|
|
426
|
-
? "running"
|
|
427
|
-
: (asString(latestTurn?.status) ?? "idle");
|
|
428
|
-
const id = asString(agent.agent_id) ?? "agent";
|
|
429
|
-
const childCount = asNumber(agent.child_count) ?? 0;
|
|
430
|
-
const duration = formatSubagentDuration(asNumber(agent.elapsed_ms));
|
|
431
|
-
const summary = renderSubagentSummary(
|
|
432
|
-
theme,
|
|
433
|
-
status,
|
|
434
|
-
id,
|
|
435
|
-
[duration, `${childCount} children`].filter((metric): metric is string => Boolean(metric)),
|
|
425
|
+
const availability = agent.availability ?? "available";
|
|
426
|
+
const status = statusAgentPresentation(agent);
|
|
427
|
+
const id = agent.agent_id ?? "agent";
|
|
428
|
+
const childCount = agent.child_count ?? 0;
|
|
429
|
+
const duration = formatSubagentDuration(agent.elapsed_ms);
|
|
430
|
+
const metrics = [duration, `${childCount} children`].filter(
|
|
431
|
+
(metric): metric is string => metric !== undefined,
|
|
436
432
|
);
|
|
433
|
+
const summary = renderSubagentSummary(theme, status, id, metrics);
|
|
437
434
|
if (!options.expanded) return new Text(`${summary}${collapsedExpansionHint(theme)}`, 0, 0);
|
|
438
435
|
const container = new Container();
|
|
439
436
|
container.addChild(new Text(summary, 0, 0));
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
]
|
|
437
|
+
const labels = [
|
|
438
|
+
{ label: "Parent", value: agent.parent_id },
|
|
439
|
+
{ label: "Availability", value: availability },
|
|
440
|
+
{ label: "Turn", value: agent.active_turn_id ?? agent.latest_turn?.turn_id },
|
|
441
|
+
{ label: "Duration", value: duration },
|
|
442
|
+
{ label: "Model", value: agent.model },
|
|
443
|
+
{ label: "Thinking", value: agent.thinking_level },
|
|
444
|
+
{ label: "Session", value: agent.session_file },
|
|
445
|
+
{ label: "Spawn entry", value: agent.spawn_entry_id },
|
|
446
|
+
] satisfies readonly StatusLabelValue[];
|
|
447
|
+
for (const { label, value } of labels) {
|
|
450
448
|
if (value !== undefined) container.addChild(renderLabelValue(theme, label, value));
|
|
451
449
|
}
|
|
452
|
-
if (
|
|
453
|
-
const launchContract =
|
|
450
|
+
if (agent.task) appendTextSection(container, theme, "Task", agent.task);
|
|
451
|
+
const launchContract = agent.launch_contract;
|
|
454
452
|
if (launchContract) {
|
|
455
453
|
const launchValues = [
|
|
456
|
-
`session context ${
|
|
457
|
-
`project context ${
|
|
458
|
-
`model ${
|
|
459
|
-
`thinking ${
|
|
460
|
-
`delegation ${
|
|
454
|
+
`session context ${launchContract.session_context ?? "inherit"}`,
|
|
455
|
+
`project context ${launchContract.project_context ?? "inherit"}`,
|
|
456
|
+
`model ${launchContract.model ?? agent.model ?? "unknown"}`,
|
|
457
|
+
`thinking ${launchContract.thinking_level ?? agent.thinking_level ?? "unknown"}`,
|
|
458
|
+
`delegation ${launchContract.delegation ?? "none"}`,
|
|
461
459
|
];
|
|
462
|
-
|
|
460
|
+
appendTextSection(container, theme, "Launch contract", launchValues.join(" · "));
|
|
463
461
|
}
|
|
464
|
-
|
|
465
|
-
|
|
462
|
+
appendTextSection(container, theme, "Tools", (agent.tools ?? []).join(", ") || "none");
|
|
463
|
+
appendTextSection(
|
|
466
464
|
container,
|
|
467
465
|
theme,
|
|
468
466
|
"Capability ceiling",
|
|
469
|
-
|
|
467
|
+
(agent.capability_ceiling ?? []).join(", ") || "none",
|
|
470
468
|
);
|
|
471
|
-
const missing =
|
|
472
|
-
if (missing.length > 0)
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
469
|
+
const missing = agent.missing_dependencies ?? [];
|
|
470
|
+
if (missing.length > 0) {
|
|
471
|
+
appendTextSection(container, theme, "Missing dependencies", missing.join("\n"));
|
|
472
|
+
}
|
|
473
|
+
if (agent.unavailable_reason) {
|
|
474
|
+
appendTextSection(container, theme, "Unavailable reason", agent.unavailable_reason);
|
|
476
475
|
}
|
|
477
|
-
const recentMessages =
|
|
478
|
-
? agent.recent_messages
|
|
479
|
-
.map(asRecord)
|
|
480
|
-
.filter((item): item is Record<string, unknown> => Boolean(item))
|
|
481
|
-
: [];
|
|
476
|
+
const recentMessages = agent.recent_messages ?? [];
|
|
482
477
|
if (recentMessages.length > 0) {
|
|
483
|
-
|
|
478
|
+
appendTextSection(
|
|
484
479
|
container,
|
|
485
480
|
theme,
|
|
486
481
|
"Recent messages",
|
|
487
482
|
recentMessages
|
|
488
|
-
.map(
|
|
489
|
-
(message) =>
|
|
490
|
-
`${asString(message.source_agent_id) ?? "unknown"}: ${asString(message.content) ?? ""}`,
|
|
491
|
-
)
|
|
483
|
+
.map((message) => `${message.source_agent_id ?? "unknown"}: ${message.content ?? ""}`)
|
|
492
484
|
.join("\n"),
|
|
493
485
|
);
|
|
494
486
|
}
|
|
495
|
-
const latestResult =
|
|
487
|
+
const latestResult = agent.latest_result;
|
|
496
488
|
if (latestResult) {
|
|
497
|
-
const output =
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
489
|
+
const output = latestResult.output ?? "";
|
|
490
|
+
if (latestResult.status === "completed" && output) {
|
|
491
|
+
appendComponentSection(
|
|
492
|
+
container,
|
|
493
|
+
theme,
|
|
494
|
+
"Latest result",
|
|
495
|
+
new Markdown(output, 0, 0, getMarkdownTheme()),
|
|
496
|
+
);
|
|
497
|
+
} else {
|
|
498
|
+
appendTextSection(
|
|
499
|
+
container,
|
|
500
|
+
theme,
|
|
501
|
+
"Latest result",
|
|
502
|
+
output || JSON.stringify(latestResult, null, 2),
|
|
503
|
+
);
|
|
504
|
+
}
|
|
506
505
|
}
|
|
507
|
-
const usageText = formatSubagentUsage(
|
|
508
|
-
if (usageText)
|
|
506
|
+
const usageText = formatSubagentUsage(agent.usage);
|
|
507
|
+
if (usageText) appendTextSection(container, theme, "Usage", usageText);
|
|
509
508
|
return container;
|
|
510
509
|
}
|
|
511
510
|
|
|
511
|
+
function renderStatusResult(
|
|
512
|
+
details: StatusRenderDetails,
|
|
513
|
+
options: ToolRenderResultOptions,
|
|
514
|
+
theme: MinimalSubagentsRenderTheme,
|
|
515
|
+
): Component {
|
|
516
|
+
if ("agents" in details) {
|
|
517
|
+
const counts = countDirectStatusAgents(details.agents);
|
|
518
|
+
const summary = [
|
|
519
|
+
theme.fg("muted", `${counts.children} children`),
|
|
520
|
+
theme.fg(counts.running > 0 ? "accent" : "dim", `${counts.running} running`),
|
|
521
|
+
].join(renderSubagentSeparator(theme));
|
|
522
|
+
if (!options.expanded) return new Text(`${summary}${collapsedExpansionHint(theme)}`, 0, 0);
|
|
523
|
+
return new Text(
|
|
524
|
+
`${summary}\n${renderDirectStatusRows(details.agents, theme).join("\n") || theme.fg("dim", "(no agents)")}`,
|
|
525
|
+
0,
|
|
526
|
+
0,
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
return renderDetailedStatusAgent(details.agent, options, theme);
|
|
530
|
+
}
|
|
531
|
+
|
|
512
532
|
function renderCancelResult(
|
|
513
|
-
details:
|
|
533
|
+
details: CancelRenderDetails,
|
|
514
534
|
options: ToolRenderResultOptions,
|
|
515
|
-
theme:
|
|
535
|
+
theme: MinimalSubagentsRenderTheme,
|
|
516
536
|
): Component {
|
|
517
|
-
const
|
|
518
|
-
const turns = asStringArray(details.cancelled_turn_ids);
|
|
537
|
+
const turns = details.cancelled_turn_ids;
|
|
519
538
|
const summary =
|
|
520
539
|
turns.length > 0
|
|
521
|
-
? renderSubagentSummary(theme, "cancelled",
|
|
522
|
-
|
|
540
|
+
? renderSubagentSummary(theme, "cancelled", details.agent_id, [
|
|
541
|
+
`${turns.length} turns cancelled`,
|
|
542
|
+
])
|
|
543
|
+
: renderSubagentSummary(theme, "completed", details.agent_id, ["no active turns"]);
|
|
523
544
|
if (!options.expanded) return new Text(`${summary}${collapsedExpansionHint(theme)}`, 0, 0);
|
|
524
545
|
const container = new Container();
|
|
525
546
|
container.addChild(new Text(summary, 0, 0));
|
|
526
|
-
container.addChild(renderLabelValue(theme, "Requested target",
|
|
547
|
+
container.addChild(renderLabelValue(theme, "Requested target", details.agent_id));
|
|
527
548
|
container.addChild(
|
|
528
|
-
renderLabelValue(theme, "Mode", details.recursive
|
|
549
|
+
renderLabelValue(theme, "Mode", details.recursive ? "recursive" : "target only"),
|
|
529
550
|
);
|
|
530
|
-
|
|
551
|
+
appendTextSection(
|
|
531
552
|
container,
|
|
532
553
|
theme,
|
|
533
554
|
"Affected agents",
|
|
534
|
-
|
|
555
|
+
details.affected_agent_ids.join("\n") || "(none)",
|
|
535
556
|
);
|
|
536
|
-
|
|
557
|
+
appendTextSection(container, theme, "Cancelled turns", turns.join("\n") || "(none)");
|
|
537
558
|
return container;
|
|
538
559
|
}
|
|
539
560
|
|
|
540
561
|
function renderDeleteResult(
|
|
541
|
-
details:
|
|
562
|
+
details: DeleteRenderDetails,
|
|
542
563
|
options: ToolRenderResultOptions,
|
|
543
|
-
theme:
|
|
564
|
+
theme: MinimalSubagentsRenderTheme,
|
|
544
565
|
): Component {
|
|
545
|
-
const
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
status,
|
|
553
|
-
id,
|
|
554
|
-
[
|
|
555
|
-
`${deleted.length} agents deleted`,
|
|
556
|
-
`${tombstoned.length} tombstoned`,
|
|
557
|
-
failures.length > 0 ? `${failures.length} failed` : undefined,
|
|
558
|
-
].filter((metric): metric is string => Boolean(metric)),
|
|
559
|
-
);
|
|
566
|
+
const status = details.failures.length > 0 ? "failed" : "completed";
|
|
567
|
+
const metrics = [
|
|
568
|
+
`${details.deleted_agent_ids.length} agents deleted`,
|
|
569
|
+
`${details.tombstoned_agent_ids.length} tombstoned`,
|
|
570
|
+
details.failures.length > 0 ? `${details.failures.length} failed` : undefined,
|
|
571
|
+
].filter((metric): metric is string => metric !== undefined);
|
|
572
|
+
const summary = renderSubagentSummary(theme, status, details.agent_id, metrics);
|
|
560
573
|
if (!options.expanded) return new Text(`${summary}${collapsedExpansionHint(theme)}`, 0, 0);
|
|
561
574
|
const container = new Container();
|
|
562
575
|
container.addChild(new Text(summary, 0, 0));
|
|
563
|
-
container.addChild(renderLabelValue(theme, "Requested target",
|
|
576
|
+
container.addChild(renderLabelValue(theme, "Requested target", details.agent_id));
|
|
564
577
|
container.addChild(
|
|
565
|
-
renderLabelValue(theme, "Mode", details.recursive
|
|
578
|
+
renderLabelValue(theme, "Mode", details.recursive ? "recursive" : "target only"),
|
|
579
|
+
);
|
|
580
|
+
appendTextSection(
|
|
581
|
+
container,
|
|
582
|
+
theme,
|
|
583
|
+
"Deleted agents",
|
|
584
|
+
details.deleted_agent_ids.join("\n") || "(none)",
|
|
566
585
|
);
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
586
|
+
appendTextSection(
|
|
587
|
+
container,
|
|
588
|
+
theme,
|
|
589
|
+
"Tombstones",
|
|
590
|
+
details.tombstoned_agent_ids.join("\n") || "(none)",
|
|
591
|
+
);
|
|
592
|
+
appendTextSection(
|
|
570
593
|
container,
|
|
571
594
|
theme,
|
|
572
595
|
"Trashed sessions",
|
|
573
|
-
|
|
596
|
+
details.trashed_session_files.join("\n") || "(none)",
|
|
574
597
|
);
|
|
575
|
-
if (failures.length > 0) {
|
|
576
|
-
|
|
598
|
+
if (details.failures.length > 0) {
|
|
599
|
+
appendTextSection(
|
|
577
600
|
container,
|
|
578
601
|
theme,
|
|
579
602
|
"Failures",
|
|
580
|
-
theme.fg("error", JSON.stringify(failures, null, 2)),
|
|
603
|
+
theme.fg("error", JSON.stringify(details.failures, null, 2)),
|
|
581
604
|
);
|
|
582
605
|
}
|
|
583
606
|
return container;
|
|
584
607
|
}
|
|
585
608
|
|
|
586
|
-
type CoordinatorToolResultRenderer = (
|
|
587
|
-
details: Record<string, unknown>,
|
|
588
|
-
options: ToolRenderResultOptions,
|
|
589
|
-
theme: Theme,
|
|
590
|
-
args: Record<string, unknown>,
|
|
591
|
-
) => Component;
|
|
592
|
-
|
|
593
|
-
const COORDINATOR_TOOL_RESULT_RENDERERS: Record<
|
|
594
|
-
CoordinatorToolName,
|
|
595
|
-
CoordinatorToolResultRenderer
|
|
596
|
-
> = {
|
|
597
|
-
subagent: renderSpawnResult,
|
|
598
|
-
agent_message: renderMessageResult,
|
|
599
|
-
subagent_wait: renderWaitResult,
|
|
600
|
-
subagent_status: (details, options, theme) => renderStatusResult(details, options, theme),
|
|
601
|
-
subagent_cancel: (details, options, theme) => renderCancelResult(details, options, theme),
|
|
602
|
-
subagent_delete: (details, options, theme) => renderDeleteResult(details, options, theme),
|
|
603
|
-
};
|
|
604
|
-
|
|
605
|
-
const COORDINATOR_DETAIL_VALIDATORS: Record<
|
|
606
|
-
CoordinatorToolName,
|
|
607
|
-
(details: Record<string, unknown>) => boolean
|
|
608
|
-
> = {
|
|
609
|
-
subagent: (details) =>
|
|
610
|
-
asString(details.agent_id) !== undefined &&
|
|
611
|
-
asString(details.turn_id) !== undefined &&
|
|
612
|
-
asString(details.status) !== undefined,
|
|
613
|
-
agent_message: (details) =>
|
|
614
|
-
asString(details.agent_id) !== undefined && typeof details.delivered === "boolean",
|
|
615
|
-
subagent_wait: (details) =>
|
|
616
|
-
asString(details.agent_id) !== undefined && asString(details.status) !== undefined,
|
|
617
|
-
subagent_status: (details) =>
|
|
618
|
-
Array.isArray(details.agents) || asRecord(details.agent) !== undefined,
|
|
619
|
-
subagent_cancel: (details) =>
|
|
620
|
-
asString(details.agent_id) !== undefined &&
|
|
621
|
-
Array.isArray(details.affected_agent_ids) &&
|
|
622
|
-
Array.isArray(details.cancelled_turn_ids),
|
|
623
|
-
subagent_delete: (details) =>
|
|
624
|
-
asString(details.agent_id) !== undefined &&
|
|
625
|
-
Array.isArray(details.deleted_agent_ids) &&
|
|
626
|
-
Array.isArray(details.tombstoned_agent_ids) &&
|
|
627
|
-
Array.isArray(details.failures),
|
|
628
|
-
};
|
|
629
|
-
|
|
630
609
|
/** Render one of the six coordinator tool calls with a shared native Pi grammar. */
|
|
631
610
|
export function renderCoordinatorToolCall(
|
|
632
611
|
toolName: CoordinatorToolName,
|
|
633
|
-
args:
|
|
634
|
-
theme:
|
|
612
|
+
args: CoordinatorToolCallInput,
|
|
613
|
+
theme: MinimalSubagentsRenderTheme,
|
|
635
614
|
): Component {
|
|
636
|
-
|
|
615
|
+
const parsed = parseCoordinatorToolCall(toolName, args);
|
|
616
|
+
if (parsed === undefined) return new Text(coordinatorToolCallTitle(theme, toolName), 0, 0);
|
|
617
|
+
switch (parsed.toolName) {
|
|
618
|
+
case "subagent":
|
|
619
|
+
return new Text(
|
|
620
|
+
`${coordinatorToolCallTitle(theme, "Subagent")} ${theme.fg("accent", parsed.args.agent_id ?? "generated")}${coordinatorToolCallPreview(theme, parsed.args.task)}`,
|
|
621
|
+
0,
|
|
622
|
+
0,
|
|
623
|
+
);
|
|
624
|
+
case "agent_message":
|
|
625
|
+
return new Text(
|
|
626
|
+
`${coordinatorToolCallTitle(theme, "Message")} ${theme.fg("accent", parsed.args.agent_id ?? "parent")}${coordinatorToolCallPreview(theme, parsed.args.message)}`,
|
|
627
|
+
0,
|
|
628
|
+
0,
|
|
629
|
+
);
|
|
630
|
+
case "subagent_wait":
|
|
631
|
+
return new Text(
|
|
632
|
+
`${coordinatorToolCallTitle(theme, "Wait")} ${theme.fg("accent", parsed.args.agent_id ?? "agent")}`,
|
|
633
|
+
0,
|
|
634
|
+
0,
|
|
635
|
+
);
|
|
636
|
+
case "subagent_status":
|
|
637
|
+
return new Text(
|
|
638
|
+
`${coordinatorToolCallTitle(theme, "Status")} ${theme.fg("accent", parsed.args.agent_id ?? "children")}`,
|
|
639
|
+
0,
|
|
640
|
+
0,
|
|
641
|
+
);
|
|
642
|
+
case "subagent_cancel":
|
|
643
|
+
return renderManagementToolCall("Cancel", parsed.args, theme);
|
|
644
|
+
case "subagent_delete":
|
|
645
|
+
return renderManagementToolCall("Delete", parsed.args, theme);
|
|
646
|
+
}
|
|
637
647
|
}
|
|
638
648
|
|
|
639
649
|
/** Render one coordinator tool result in native collapsed, expanded, or partial mode. */
|
|
@@ -641,22 +651,49 @@ export function renderCoordinatorToolResult(
|
|
|
641
651
|
toolName: CoordinatorToolName,
|
|
642
652
|
result: AgentToolResult<unknown>,
|
|
643
653
|
options: ToolRenderResultOptions,
|
|
644
|
-
theme:
|
|
645
|
-
args:
|
|
654
|
+
theme: MinimalSubagentsRenderTheme,
|
|
655
|
+
args: CoordinatorToolCallInput,
|
|
646
656
|
isError = false,
|
|
647
657
|
): Component {
|
|
648
|
-
const
|
|
649
|
-
if (
|
|
650
|
-
|
|
658
|
+
const parsedResult = parseCoordinatorToolResult(toolName, result.details);
|
|
659
|
+
if (parsedResult === undefined) return renderFallbackToolResult(result, theme, isError);
|
|
660
|
+
const parsedCall = parseCoordinatorToolCall(toolName, args);
|
|
661
|
+
switch (parsedResult.toolName) {
|
|
662
|
+
case "subagent":
|
|
663
|
+
return renderSpawnResult(
|
|
664
|
+
parsedResult.details,
|
|
665
|
+
options,
|
|
666
|
+
theme,
|
|
667
|
+
parsedCall?.toolName === "subagent" ? parsedCall.args : {},
|
|
668
|
+
);
|
|
669
|
+
case "agent_message":
|
|
670
|
+
return renderMessageResult(
|
|
671
|
+
parsedResult.details,
|
|
672
|
+
options,
|
|
673
|
+
theme,
|
|
674
|
+
parsedCall?.toolName === "agent_message" ? parsedCall.args : {},
|
|
675
|
+
);
|
|
676
|
+
case "subagent_wait":
|
|
677
|
+
return renderWaitResult(
|
|
678
|
+
parsedResult.details,
|
|
679
|
+
options,
|
|
680
|
+
theme,
|
|
681
|
+
parsedCall?.toolName === "subagent_wait" ? parsedCall.args : {},
|
|
682
|
+
);
|
|
683
|
+
case "subagent_status":
|
|
684
|
+
return renderStatusResult(parsedResult.details, options, theme);
|
|
685
|
+
case "subagent_cancel":
|
|
686
|
+
return renderCancelResult(parsedResult.details, options, theme);
|
|
687
|
+
case "subagent_delete":
|
|
688
|
+
return renderDeleteResult(parsedResult.details, options, theme);
|
|
651
689
|
}
|
|
652
|
-
return COORDINATOR_TOOL_RESULT_RENDERERS[toolName](details, options, theme, args);
|
|
653
690
|
}
|
|
654
691
|
|
|
655
692
|
/** Render explicit agent messages with compact source/destination metadata. */
|
|
656
693
|
export function renderMinimalSubagentsMessage(
|
|
657
694
|
message: RenderableCoordinatorMessage,
|
|
658
|
-
options:
|
|
659
|
-
theme:
|
|
695
|
+
options: MessageRenderOptions,
|
|
696
|
+
theme: MinimalSubagentsRenderTheme,
|
|
660
697
|
): Component {
|
|
661
698
|
return renderCoordinatorMessage("Agent message", "→", message, options, theme);
|
|
662
699
|
}
|
|
@@ -664,31 +701,42 @@ export function renderMinimalSubagentsMessage(
|
|
|
664
701
|
/** Render automatic successful agent results with expandable Markdown output. */
|
|
665
702
|
export function renderMinimalSubagentsResult(
|
|
666
703
|
message: RenderableCoordinatorMessage,
|
|
667
|
-
options:
|
|
668
|
-
theme:
|
|
704
|
+
options: MessageRenderOptions,
|
|
705
|
+
theme: MinimalSubagentsRenderTheme,
|
|
669
706
|
): Component {
|
|
670
707
|
return renderCoordinatorMessage("Agent result", "✓", message, options, theme);
|
|
671
708
|
}
|
|
672
709
|
|
|
710
|
+
function messageSource(details: CoordinatorMessageRenderDetails | undefined): string {
|
|
711
|
+
return details?.source_agent_id ?? details?.agent_id ?? "unknown";
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function messageSourceTurn(
|
|
715
|
+
details: CoordinatorMessageRenderDetails | undefined,
|
|
716
|
+
): string | undefined {
|
|
717
|
+
return details?.source_turn_id ?? details?.turn_id;
|
|
718
|
+
}
|
|
719
|
+
|
|
673
720
|
function renderCoordinatorMessage(
|
|
674
721
|
label: string,
|
|
675
722
|
symbol: string,
|
|
676
723
|
message: RenderableCoordinatorMessage,
|
|
677
|
-
options:
|
|
678
|
-
theme:
|
|
724
|
+
options: MessageRenderOptions,
|
|
725
|
+
theme: MinimalSubagentsRenderTheme,
|
|
679
726
|
): Component {
|
|
680
|
-
const details =
|
|
727
|
+
const details = parseCoordinatorMessageDetails(message.details);
|
|
681
728
|
const content = coordinatorMessageText(message.content);
|
|
682
|
-
const source =
|
|
683
|
-
const destination =
|
|
684
|
-
const
|
|
729
|
+
const source = messageSource(details);
|
|
730
|
+
const destination = details?.destination_agent_id ?? "recipient";
|
|
731
|
+
const sourceTurn = messageSourceTurn(details);
|
|
685
732
|
const route = `${theme.fg("accent", theme.bold(source))} ${theme.fg("dim", "→")} ${theme.fg("accent", theme.bold(destination))}`;
|
|
686
733
|
const heading = [
|
|
687
734
|
`${theme.fg(symbol === "✓" ? "success" : "accent", symbol)} ${theme.bold(label)}`,
|
|
688
735
|
route,
|
|
689
|
-
|
|
736
|
+
sourceTurn ? theme.fg("dim", `turn ${sourceTurn}`) : undefined,
|
|
737
|
+
details?.status ? renderSubagentStatusLabel(theme, details.status) : undefined,
|
|
690
738
|
]
|
|
691
|
-
.filter((part): part is string =>
|
|
739
|
+
.filter((part): part is string => part !== undefined)
|
|
692
740
|
.join(renderSubagentSeparator(theme));
|
|
693
741
|
const box = new Box(options.outputPad, 1, (text) => theme.bg("customMessageBg", text));
|
|
694
742
|
if (!options.expanded) {
|
|
@@ -703,15 +751,13 @@ function renderCoordinatorMessage(
|
|
|
703
751
|
}
|
|
704
752
|
const container = new Container();
|
|
705
753
|
container.addChild(new Text(heading, 0, 0));
|
|
706
|
-
if (
|
|
707
|
-
|
|
708
|
-
}
|
|
709
|
-
const duration = formatSubagentDuration(asNumber(details?.elapsed_ms));
|
|
754
|
+
if (sourceTurn) container.addChild(renderLabelValue(theme, "Source turn", sourceTurn));
|
|
755
|
+
const duration = formatSubagentDuration(details?.elapsed_ms);
|
|
710
756
|
if (duration) container.addChild(renderLabelValue(theme, "Duration", duration));
|
|
711
757
|
container.addChild(new Spacer(1));
|
|
712
758
|
container.addChild(new Markdown(content, 0, 0, getMarkdownTheme()));
|
|
713
|
-
const usageText = formatSubagentUsage(
|
|
714
|
-
if (usageText)
|
|
759
|
+
const usageText = formatSubagentUsage(details?.usage);
|
|
760
|
+
if (usageText) appendTextSection(container, theme, "Usage", usageText);
|
|
715
761
|
box.addChild(container);
|
|
716
762
|
return box;
|
|
717
763
|
}
|