@pi-archimedes/subagent 2.10.4 → 2.11.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 +2 -2
- package/package.json +10 -10
- package/src/agent-panel.mcp.test.ts +76 -0
- package/src/agent-panel.tools.test.ts +162 -0
- package/src/agent-panel.ts +37 -8
- package/src/agent-panel.width-audit.test.ts +258 -0
- package/src/agents-parse.test.ts +252 -0
- package/src/agents.ts +72 -5
- package/src/compact.test.ts +136 -57
- package/src/compact.ts +57 -37
- package/src/dispatch.test.ts +73 -1
- package/src/dispatch.ts +20 -2
- package/src/execute.test.ts +9 -1
- package/src/execute.ts +26 -29
- package/src/expanded.test.ts +472 -0
- package/src/expanded.ts +202 -82
- package/src/format.test.ts +71 -29
- package/src/format.ts +71 -28
- package/src/frontmatter-io.test.ts +62 -0
- package/src/frontmatter-io.ts +16 -2
- package/src/handlers.test.ts +21 -3
- package/src/handlers.ts +22 -3
- package/src/index.output-schema.test.ts +317 -0
- package/src/index.test.ts +566 -0
- package/src/index.ts +169 -16
- package/src/render.ts +46 -36
- package/src/row-block.test.ts +244 -0
- package/src/row-block.ts +209 -0
- package/src/run-outcome.test.ts +71 -0
- package/src/run-outcome.ts +84 -0
- package/src/spawn.test.ts +204 -19
- package/src/spawn.ts +49 -17
- package/src/stream.test.ts +215 -17
- package/src/stream.ts +47 -9
- package/src/thinking-validation.test.ts +96 -0
- package/src/thinking-validation.ts +78 -0
- package/src/tool-declarations.test.ts +58 -0
- package/src/tool-row.width-audit.test.ts +720 -0
- package/src/tool-schema.test.ts +154 -0
- package/src/tool-schema.ts +106 -4
- package/src/tool-validation.test.ts +321 -0
- package/src/tool-validation.ts +262 -0
- package/src/types.ts +86 -0
- package/src/usage.test.ts +95 -0
- package/src/usage.ts +87 -0
- package/src/cost.test.ts +0 -62
- package/src/cost.ts +0 -8
package/src/index.ts
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Text, type TUI } from "@earendil-works/pi-tui";
|
|
3
3
|
import { Type } from "typebox";
|
|
4
|
-
import { SUBAGENT_PARAMS_SCHEMA } from "./tool-schema.js";
|
|
4
|
+
import { SUBAGENT_PARAMS_SCHEMA, SUBAGENT_OUTPUT_SCHEMA, subagentStructuredContent, type SubagentParams } from "./tool-schema.js";
|
|
5
5
|
import { executeSubagent, executeParallel } from "./execute.js";
|
|
6
|
-
import { formatParallelResults } from "./format.js";
|
|
6
|
+
import { formatParallelResults, formatSingleResult } from "./format.js";
|
|
7
7
|
// agent-manager.js lazy-loaded below to keep subagent tool registration fast
|
|
8
8
|
import { renderSubagentResult } from "./render.js";
|
|
9
|
+
import { RowBlock } from "./row-block.js";
|
|
9
10
|
import { OVERLAY_CHROME } from "@pi-archimedes/core/overlay";
|
|
10
11
|
import { renderToolHeader } from "@pi-archimedes/core/tool-render";
|
|
12
|
+
import { reportPiSupport } from "@pi-archimedes/core/pi-support";
|
|
13
|
+
import { diag, describeError } from "@pi-archimedes/core/log";
|
|
11
14
|
import { discoverAgents, discoverAgentsAll, findAgent, formatAgentList } from "./agents.js";
|
|
12
15
|
import { validateModel, firstError } from "./model-validation.js";
|
|
16
|
+
import {
|
|
17
|
+
unknownToolEntries,
|
|
18
|
+
formatUnknownToolsWarning,
|
|
19
|
+
} from "./tool-validation.js";
|
|
20
|
+
import { resolveThinkingLevel } from "./thinking-validation.js";
|
|
21
|
+
import { toNativeUsage, sumNativeUsage } from "./usage.js";
|
|
13
22
|
import type {
|
|
14
23
|
SubagentDetails,
|
|
15
24
|
SubagentProgress,
|
|
@@ -27,23 +36,42 @@ interface RenderTheme {
|
|
|
27
36
|
// ── Tool registration ───────────────────────────────────────────────────────
|
|
28
37
|
|
|
29
38
|
export function registerSubagent(pi: ExtensionAPI): void {
|
|
39
|
+
// Floor check before anything that can fail (ADR 0026). This package spawns
|
|
40
|
+
// pi with flags and reads event fields that only behave correctly on 1.x
|
|
41
|
+
// (`--tools` semantics, `agent_settled.aborted`, print-mode exit codes), and a
|
|
42
|
+
// peer range cannot say so at install time. Warn and register anyway — the
|
|
43
|
+
// tool must still show up in /plugins. Entry point for both meta and the
|
|
44
|
+
// standalone default export, so `registerAgentsCommand` needs no second call.
|
|
45
|
+
reportPiSupport("subagent", "1.2.0");
|
|
46
|
+
|
|
30
47
|
pi.registerTool({
|
|
31
48
|
name: "subagent",
|
|
32
49
|
label: "Subagent",
|
|
33
50
|
description:
|
|
34
51
|
"Delegate tasks to subagents. Provide either 'task' (single) or 'tasks' (parallel). Agent is optional — omit for a config-less run with the parent's model and all tools. Model override is rarely needed; the agent config or parent model is used by default.",
|
|
35
52
|
parameters: SUBAGENT_PARAMS_SCHEMA,
|
|
53
|
+
outputSchema: SUBAGENT_OUTPUT_SCHEMA,
|
|
54
|
+
/**
|
|
55
|
+
* Declared to the model, never callable from a codemode script — pi's own
|
|
56
|
+
* wording for this exposure is "orchestrating" tools
|
|
57
|
+
* (`dist/core/extensions/types.d.ts`, `ToolExposure`), and dispatching a
|
|
58
|
+
* child session is that.
|
|
59
|
+
*
|
|
60
|
+
* Measured cost, so it is not discovered later: before this line a codemode
|
|
61
|
+
* script could dispatch a child and read its report — `tools.subagent({ task:
|
|
62
|
+
* "Reply with exactly the single word: child" })` resolved to
|
|
63
|
+
* `{ mode: "single", results: [ { outcome: "completed", finalOutput: "child",
|
|
64
|
+
* usage: {…} } ] }` on a real headless pi. After it the same call reports
|
|
65
|
+
* `tools.subagent does not exist`, and `subagent` is gone from the script's
|
|
66
|
+
* `ALL_TOOLS`. A script that wants a child's work now has to ask the model to
|
|
67
|
+
* dispatch one; `list_agents` stays `direct`, so scripts can still read the
|
|
68
|
+
* catalogue. The model's own dispatches are unaffected.
|
|
69
|
+
*/
|
|
70
|
+
exposure: "model-only",
|
|
36
71
|
|
|
37
72
|
async execute(
|
|
38
73
|
_id: string,
|
|
39
|
-
params:
|
|
40
|
-
agent?: string;
|
|
41
|
-
task?: string;
|
|
42
|
-
tasks?: Array<{ agent?: string; task: string; count?: number; model?: string; cwd?: string }>;
|
|
43
|
-
model?: string;
|
|
44
|
-
cwd?: string;
|
|
45
|
-
async?: boolean;
|
|
46
|
-
},
|
|
74
|
+
params: SubagentParams,
|
|
47
75
|
signal: AbortSignal | undefined,
|
|
48
76
|
onUpdate: ((update: SubagentToolResult) => void) | undefined,
|
|
49
77
|
ctx: ExtensionContext,
|
|
@@ -68,6 +96,9 @@ export function registerSubagent(pi: ExtensionAPI): void {
|
|
|
68
96
|
// Skip model validation for tasks already caught by unknown-agent check
|
|
69
97
|
if (t.agent && unknownAgentSet.has(t.agent)) continue;
|
|
70
98
|
const taskAgentConfig = t.agent ? findAgent(agents, t.agent) : undefined;
|
|
99
|
+
// A `tools:` list the config read rejected is never applied, so the task
|
|
100
|
+
// cannot run as configured — say so before anything spawns.
|
|
101
|
+
if (taskAgentConfig?.toolsError) errors.push(taskAgentConfig.toolsError);
|
|
71
102
|
const me = firstError(
|
|
72
103
|
validateModel(t.model, ctx.modelRegistry, { agentName: t.agent }),
|
|
73
104
|
validateModel(taskAgentConfig?.model, ctx.modelRegistry, {
|
|
@@ -88,6 +119,20 @@ export function registerSubagent(pi: ExtensionAPI): void {
|
|
|
88
119
|
isError: true,
|
|
89
120
|
};
|
|
90
121
|
}
|
|
122
|
+
// Membership only becomes knowable here, with the handle in scope — and
|
|
123
|
+
// it is a warning, so the batch still runs (see warnUnavailableTools).
|
|
124
|
+
const warnedTools = new Set<string>();
|
|
125
|
+
const warnedThinking = new Set<string>();
|
|
126
|
+
for (const t of params.tasks) {
|
|
127
|
+
warnUnavailableTools(
|
|
128
|
+
pi,
|
|
129
|
+
ctx,
|
|
130
|
+
warnedTools,
|
|
131
|
+
t.agent ?? "subagent",
|
|
132
|
+
t.agent ? findAgent(agents, t.agent)?.tools : undefined,
|
|
133
|
+
);
|
|
134
|
+
warnUnknownThinkingLevel(ctx, warnedThinking, t.agent ? findAgent(agents, t.agent)?.thinking : undefined);
|
|
135
|
+
}
|
|
91
136
|
const results: SubagentResult[] = await executeParallel({
|
|
92
137
|
tasks: params.tasks.map((t) => ({
|
|
93
138
|
agent: t.agent ?? undefined,
|
|
@@ -101,6 +146,7 @@ export function registerSubagent(pi: ExtensionAPI): void {
|
|
|
101
146
|
toolCallId: _id,
|
|
102
147
|
})),
|
|
103
148
|
signal: signal ?? undefined,
|
|
149
|
+
events: pi.events,
|
|
104
150
|
onUpdate: (progress: SubagentProgress[]) => {
|
|
105
151
|
onUpdate?.({
|
|
106
152
|
content: [],
|
|
@@ -113,8 +159,11 @@ export function registerSubagent(pi: ExtensionAPI): void {
|
|
|
113
159
|
},
|
|
114
160
|
});
|
|
115
161
|
|
|
162
|
+
const nativeUsage = sumNativeUsage(results);
|
|
116
163
|
return {
|
|
117
164
|
content: [{ type: "text", text: formatParallelResults(results) }],
|
|
165
|
+
...(nativeUsage ? { usage: nativeUsage } : {}),
|
|
166
|
+
structuredContent: subagentStructuredContent("parallel", results),
|
|
118
167
|
details: {
|
|
119
168
|
mode: "parallel",
|
|
120
169
|
results,
|
|
@@ -158,6 +207,18 @@ export function registerSubagent(pi: ExtensionAPI): void {
|
|
|
158
207
|
isError: true,
|
|
159
208
|
};
|
|
160
209
|
}
|
|
210
|
+
// A rejected `tools:` list is a config error, not a runtime one: the child
|
|
211
|
+
// would run with a different tool set than the config asked for, so it
|
|
212
|
+
// does not run at all.
|
|
213
|
+
if (agentConfig?.toolsError) {
|
|
214
|
+
return {
|
|
215
|
+
content: [{ type: "text", text: agentConfig.toolsError }],
|
|
216
|
+
details: { mode: "single", results: [], progress: undefined },
|
|
217
|
+
isError: true,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
warnUnavailableTools(pi, ctx, new Set(), params.agent ?? "subagent", agentConfig?.tools);
|
|
221
|
+
warnUnknownThinkingLevel(ctx, new Set(), agentConfig?.thinking);
|
|
161
222
|
const result: SubagentResult = await executeSubagent({
|
|
162
223
|
agent: params.agent ?? undefined,
|
|
163
224
|
agentConfig,
|
|
@@ -166,6 +227,7 @@ export function registerSubagent(pi: ExtensionAPI): void {
|
|
|
166
227
|
activeModel: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined,
|
|
167
228
|
cwd: params.cwd ?? undefined,
|
|
168
229
|
signal: signal ?? undefined,
|
|
230
|
+
events: pi.events,
|
|
169
231
|
toolCallId: _id,
|
|
170
232
|
onUpdate: (progress: SubagentProgress) => {
|
|
171
233
|
onUpdate?.({
|
|
@@ -180,13 +242,31 @@ export function registerSubagent(pi: ExtensionAPI): void {
|
|
|
180
242
|
});
|
|
181
243
|
|
|
182
244
|
return {
|
|
183
|
-
content: [{ type: "text", text: result
|
|
245
|
+
content: [{ type: "text", text: formatSingleResult(result) }],
|
|
246
|
+
// The cost report — but only for a run this session performed. A
|
|
247
|
+
// bridge-dispatched run is a session of the desktop's own and books
|
|
248
|
+
// its usage there; pi folds a tool result's `usage` into THIS
|
|
249
|
+
// session's totals, so publishing it here would count the money twice
|
|
250
|
+
// (the same reason the `COST_UPDATE` route was deleted).
|
|
251
|
+
...(result.usageAccountedElsewhere ? {} : { usage: toNativeUsage(result.usage) }),
|
|
252
|
+
// Successes only. The predicate is the one two lines below, so a run
|
|
253
|
+
// that reports itself as failed carries no structured content and a
|
|
254
|
+
// codemode script keeps rejecting with the text it rejects with today
|
|
255
|
+
// (see `SUBAGENT_OUTPUT_SCHEMA`).
|
|
256
|
+
...(result.error === undefined ? { structuredContent: subagentStructuredContent("single", [result]) } : {}),
|
|
184
257
|
details: {
|
|
185
258
|
mode: "single",
|
|
186
259
|
results: [result],
|
|
187
260
|
progress: result.progress ? [result.progress] : undefined,
|
|
188
261
|
},
|
|
189
|
-
|
|
262
|
+
// Only a *failed* run is a tool error. A truncated answer and a
|
|
263
|
+
// cancelled one both produced something, and `isError` is how the model
|
|
264
|
+
// and the Client decide to retry or give up (ADR 0028). `error` is the
|
|
265
|
+
// predicate because the exit code is not one — a json child exits 0 for
|
|
266
|
+
// an errored run — and `stream.ts` guarantees a failed run always
|
|
267
|
+
// carries an explanation while a completed/truncated/aborted one never
|
|
268
|
+
// does.
|
|
269
|
+
isError: result.error !== undefined,
|
|
190
270
|
};
|
|
191
271
|
}
|
|
192
272
|
|
|
@@ -231,18 +311,21 @@ export function registerSubagent(pi: ExtensionAPI): void {
|
|
|
231
311
|
false) as boolean;
|
|
232
312
|
|
|
233
313
|
const renderTheme = theme as unknown as RenderTheme;
|
|
234
|
-
|
|
314
|
+
// A RowBlock, not a plain Text: the subagent rows are cut at the width pi
|
|
315
|
+
// renders them at, and only the component is ever handed that width.
|
|
316
|
+
const lastComponent = (context as { lastComponent?: unknown })?.lastComponent;
|
|
317
|
+
const rows = lastComponent instanceof RowBlock ? lastComponent : new RowBlock();
|
|
235
318
|
const renderContext = {
|
|
236
319
|
expanded,
|
|
237
320
|
isError: toolResult.isError ?? false,
|
|
238
|
-
lastComponent:
|
|
321
|
+
lastComponent: rows,
|
|
239
322
|
state: (context as Record<string, unknown>)?.state ?? {},
|
|
240
323
|
invalidate: () => {},
|
|
241
324
|
};
|
|
242
|
-
(context as Record<string, unknown>).lastComponent =
|
|
325
|
+
(context as Record<string, unknown>).lastComponent = rows;
|
|
243
326
|
|
|
244
327
|
try {
|
|
245
|
-
const rendered = renderSubagentResult(
|
|
328
|
+
const rendered = renderSubagentResult(rows, toolResult, { expanded }, renderTheme, renderContext as any);
|
|
246
329
|
return rendered;
|
|
247
330
|
} catch (e) {
|
|
248
331
|
debugText.setText("render error: " + (e instanceof Error ? e.message : String(e)));
|
|
@@ -256,6 +339,76 @@ export function registerSubagent(pi: ExtensionAPI): void {
|
|
|
256
339
|
|
|
257
340
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
258
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Warn (never fail) about allowlist entries no registered tool satisfies right
|
|
344
|
+
* now, naming each one.
|
|
345
|
+
*
|
|
346
|
+
* Membership cannot be checked any earlier: `pi.getAllTools()` needs this
|
|
347
|
+
* handle, and the config read is pure filesystem. And it must not fail the run:
|
|
348
|
+
* a legitimate name is missing whenever its plugin is toggled off or its MCP
|
|
349
|
+
* server is disconnected, so a hard error would make `tools: web_search` unusable
|
|
350
|
+
* for anyone who disabled `web`. One warning per entry per tool call — a parallel
|
|
351
|
+
* batch of eight agents sharing a list warns once, not eight times.
|
|
352
|
+
*
|
|
353
|
+
* Diagnostics go to `diag()` because a `console.*` write is discarded by a
|
|
354
|
+
* subagent child and smears the fullscreen frame (see `core/log.ts`); the
|
|
355
|
+
* on-screen copy only goes out where there is a UI to receive it.
|
|
356
|
+
*/
|
|
357
|
+
function warnUnavailableTools(
|
|
358
|
+
pi: ExtensionAPI,
|
|
359
|
+
ctx: ExtensionContext,
|
|
360
|
+
alreadyWarned: Set<string>,
|
|
361
|
+
agentName: string,
|
|
362
|
+
tools: string[] | undefined,
|
|
363
|
+
): void {
|
|
364
|
+
if (!tools || tools.length === 0) return;
|
|
365
|
+
let unknown: string[];
|
|
366
|
+
try {
|
|
367
|
+
const known = pi.getAllTools().map((t) => t.name);
|
|
368
|
+
unknown = unknownToolEntries(tools, known).filter((entry) => !alreadyWarned.has(entry));
|
|
369
|
+
} catch (e) {
|
|
370
|
+
// A handle that cannot answer is not a reason to lose the dispatch.
|
|
371
|
+
diag("subagent", `tool-membership check skipped for "${agentName}": ${describeError(e)}`);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (unknown.length === 0) return;
|
|
375
|
+
for (const entry of unknown) alreadyWarned.add(entry);
|
|
376
|
+
const message = formatUnknownToolsWarning(agentName, unknown);
|
|
377
|
+
diag("subagent", message);
|
|
378
|
+
if (!ctx.hasUI) return;
|
|
379
|
+
try {
|
|
380
|
+
ctx.ui.notify(message, "warning");
|
|
381
|
+
} catch {
|
|
382
|
+
// A context too stale to notify must not fail the run it is only warning about.
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Warn (never fail) about a `thinking:` level pi's `--thinking` does not take.
|
|
388
|
+
*
|
|
389
|
+
* `spawnSubagent` withholds the flag in this case, which is what pi would have
|
|
390
|
+
* done anyway — the difference is that the child no longer runs at the default
|
|
391
|
+
* level silently: pi's own notice is a stderr warning, and a successful json
|
|
392
|
+
* child's stderr is never read. One warning per distinct value per tool call.
|
|
393
|
+
*/
|
|
394
|
+
function warnUnknownThinkingLevel(
|
|
395
|
+
ctx: ExtensionContext,
|
|
396
|
+
alreadyWarned: Set<string>,
|
|
397
|
+
thinking: string | undefined,
|
|
398
|
+
): void {
|
|
399
|
+
if (thinking === undefined || alreadyWarned.has(thinking)) return;
|
|
400
|
+
const { warning } = resolveThinkingLevel(thinking);
|
|
401
|
+
if (!warning) return;
|
|
402
|
+
alreadyWarned.add(thinking);
|
|
403
|
+
diag("subagent", warning);
|
|
404
|
+
if (!ctx.hasUI) return;
|
|
405
|
+
try {
|
|
406
|
+
ctx.ui.notify(warning, "warning");
|
|
407
|
+
} catch {
|
|
408
|
+
// Same rule as the tool-membership warning: a report must not lose a run.
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
259
412
|
export function registerListAgentsTool(pi: ExtensionAPI): void {
|
|
260
413
|
pi.registerTool({
|
|
261
414
|
name: "list_agents",
|
package/src/render.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RowBlock, row, type RowSegment } from "./row-block.js";
|
|
2
2
|
import type { SubagentDetails, SubagentProgress, SubagentToolResult } from "./types.js";
|
|
3
3
|
import {
|
|
4
4
|
renderCompactSingle,
|
|
@@ -17,107 +17,117 @@ type Theme = { fg: (token: string, text: string) => string; bold: (text: string)
|
|
|
17
17
|
type RenderContext = {
|
|
18
18
|
expanded: boolean;
|
|
19
19
|
isError: boolean;
|
|
20
|
-
lastComponent:
|
|
20
|
+
lastComponent: RowBlock | undefined;
|
|
21
21
|
state: Record<string, unknown>;
|
|
22
22
|
invalidate: () => void;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Draw a subagent result (or its live progress) into `rows`.
|
|
27
|
+
*
|
|
28
|
+
* `rows` is a `RowBlock` rather than a plain `Text` because the renderers below
|
|
29
|
+
* hand back whole content: the component is what knows the width pi is drawing
|
|
30
|
+
* at, and it is what cuts each row to that width (`row-block.ts`).
|
|
31
|
+
*/
|
|
25
32
|
export function renderSubagentResult(
|
|
26
|
-
|
|
33
|
+
rows: RowBlock,
|
|
27
34
|
result: SubagentToolResult,
|
|
28
35
|
options: { expanded: boolean },
|
|
29
36
|
theme: Theme,
|
|
30
37
|
context: RenderContext,
|
|
31
|
-
):
|
|
38
|
+
): RowBlock {
|
|
32
39
|
const details: SubagentDetails | undefined = result.details;
|
|
33
40
|
const expanded = context.expanded ?? options.expanded;
|
|
34
41
|
|
|
35
42
|
if (!details) {
|
|
36
|
-
|
|
37
|
-
return
|
|
43
|
+
rows.setText(theme.fg("dim", " no results"));
|
|
44
|
+
return rows;
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
// ── Streaming progress (results empty but progress has data) ──────────
|
|
41
48
|
if (details.results.length === 0 && details.progress && details.progress.length > 0) {
|
|
42
49
|
if (details.progress.length === 1) {
|
|
43
|
-
return renderProgressUpdate(
|
|
50
|
+
return renderProgressUpdate(rows, details.progress[0]!, expanded, theme, context);
|
|
44
51
|
}
|
|
45
|
-
return renderProgressUpdatesParallel(
|
|
52
|
+
return renderProgressUpdatesParallel(rows, details, expanded, theme, context);
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
if (details.results.length === 0) {
|
|
49
|
-
|
|
50
|
-
return
|
|
56
|
+
rows.setText(theme.fg("dim", " no results"));
|
|
57
|
+
return rows;
|
|
51
58
|
}
|
|
52
59
|
|
|
53
60
|
// ── Single agent ────────────────────────────────────────────────────────
|
|
54
61
|
if (details.mode === "single" || details.results.length === 1) {
|
|
55
|
-
return renderSingleAgent(
|
|
62
|
+
return renderSingleAgent(rows, details.results[0]!, details.progress?.[0], expanded, theme, context);
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
// ── Parallel agents ─────────────────────────────────────────────────────
|
|
59
|
-
return renderParallelAgents(
|
|
66
|
+
return renderParallelAgents(rows, details, expanded, theme, context);
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
function renderSingleAgent(
|
|
63
|
-
|
|
70
|
+
rows: RowBlock,
|
|
64
71
|
result: SubagentDetails["results"][number],
|
|
65
72
|
progress: SubagentDetails["progress"] extends (infer U)[] | undefined ? U | undefined : undefined,
|
|
66
73
|
expanded: boolean,
|
|
67
74
|
theme: Theme,
|
|
68
75
|
context: RenderContext,
|
|
69
|
-
):
|
|
76
|
+
): RowBlock {
|
|
70
77
|
if (expanded) {
|
|
71
|
-
return renderExpanded(
|
|
78
|
+
return renderExpanded(rows, result, progress, theme);
|
|
72
79
|
}
|
|
73
|
-
|
|
80
|
+
renderCompactSingle(rows, result, progress, theme, context);
|
|
81
|
+
return rows;
|
|
74
82
|
}
|
|
75
83
|
|
|
76
84
|
function renderParallelAgents(
|
|
77
|
-
|
|
85
|
+
rows: RowBlock,
|
|
78
86
|
details: SubagentDetails,
|
|
79
87
|
expanded: boolean,
|
|
80
88
|
theme: Theme,
|
|
81
89
|
context: RenderContext,
|
|
82
|
-
):
|
|
90
|
+
): RowBlock {
|
|
83
91
|
if (expanded) {
|
|
84
92
|
const progressArr: SubagentProgress[] = details.progress ?? [];
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
88
|
-
text.setText(lines.join("\n\n"));
|
|
89
|
-
return text;
|
|
93
|
+
rows.setSegments(joinBlocks(details.results.map((result, i) => buildExpandedText(result, progressArr[i], theme))));
|
|
94
|
+
return rows;
|
|
90
95
|
}
|
|
91
|
-
|
|
96
|
+
renderCompactParallel(rows, details, theme, context);
|
|
97
|
+
return rows;
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
function renderProgressUpdate(
|
|
95
|
-
|
|
101
|
+
rows: RowBlock,
|
|
96
102
|
progress: SubagentProgress,
|
|
97
103
|
expanded: boolean,
|
|
98
104
|
theme: Theme,
|
|
99
105
|
context: RenderContext,
|
|
100
|
-
):
|
|
106
|
+
): RowBlock {
|
|
101
107
|
if (expanded) {
|
|
102
|
-
return renderProgressExpanded(
|
|
108
|
+
return renderProgressExpanded(rows, progress, theme);
|
|
103
109
|
}
|
|
104
|
-
|
|
110
|
+
renderCompactProgress(rows, progress, theme, context);
|
|
111
|
+
return rows;
|
|
105
112
|
}
|
|
106
113
|
|
|
107
114
|
function renderProgressUpdatesParallel(
|
|
108
|
-
|
|
115
|
+
rows: RowBlock,
|
|
109
116
|
details: SubagentDetails,
|
|
110
117
|
expanded: boolean,
|
|
111
118
|
theme: Theme,
|
|
112
119
|
context: RenderContext,
|
|
113
|
-
):
|
|
120
|
+
): RowBlock {
|
|
114
121
|
if (expanded) {
|
|
115
122
|
const progressArr: SubagentProgress[] = details.progress ?? [];
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
});
|
|
119
|
-
text.setText(lines.join("\n\n"));
|
|
120
|
-
return text;
|
|
123
|
+
rows.setSegments(joinBlocks(progressArr.map((progress) => buildProgressExpandedText(progress, theme))));
|
|
124
|
+
return rows;
|
|
121
125
|
}
|
|
122
|
-
|
|
126
|
+
renderCompactParallelProgress(rows, details, theme, context);
|
|
127
|
+
return rows;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** One blank row between agents — the spacing the old `join("\n\n")` produced. */
|
|
131
|
+
function joinBlocks(blocks: RowSegment[][]): RowSegment[] {
|
|
132
|
+
return blocks.flatMap((blockLines, i) => (i === 0 ? blockLines : [row(""), ...blockLines]));
|
|
123
133
|
}
|