oira666_pi-subagent 0.3.10 → 0.3.12
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 +14 -3
- package/index.ts +45 -38
- package/package.json +1 -1
- package/render.ts +114 -25
- package/runner.ts +203 -95
- package/shims.d.ts +0 -1
- package/tree.ts +163 -76
- package/types.ts +90 -26
package/README.md
CHANGED
|
@@ -130,7 +130,7 @@ The Markdown body becomes the agent's system prompt (appended to Pi's default, n
|
|
|
130
130
|
|
|
131
131
|
## Delegation Guards
|
|
132
132
|
|
|
133
|
-
Depth and cycle guards prevent runaway recursive delegation. Layer availability is evaluated for the child being launched: depth 1 is the first layer, and `PI_SUBAGENT_MAX_DEPTH` is the last layer. The bundled `team-lead` agent sets `last-layer: disabled` so it cannot consume the final delegation layer.
|
|
133
|
+
Depth and cycle guards prevent runaway recursive delegation. Layer availability is evaluated for the child being launched: depth 1 is the first layer, and `PI_SUBAGENT_MAX_DEPTH` is the last layer. The bundled `team-lead` agent sets `last-layer: disabled` so it cannot consume the final delegation layer. Cycle checks are applied per task: in a mixed parallel call, cyclic tasks fail while legal siblings still run.
|
|
134
134
|
|
|
135
135
|
| Config | Default | Description |
|
|
136
136
|
| ------------------------------ | ------- | ------------------------------------------------ |
|
|
@@ -160,6 +160,12 @@ cancellation, the runner terminates the child process tree and bounds cleanup;
|
|
|
160
160
|
even if a wedged OS process never reports `close`, the tool returns an error
|
|
161
161
|
result so every waiting parent can settle.
|
|
162
162
|
|
|
163
|
+
RPC completion is based on Pi's `agent_settled` event—not `agent_end`.
|
|
164
|
+
`agent_end` is only a low-level run boundary and may be followed by Pi's normal
|
|
165
|
+
provider retry, overflow compaction, or queued continuation. Rejected prompt
|
|
166
|
+
commands, signal exits, and processes that exit before `agent_settled` are
|
|
167
|
+
reported immediately as failures.
|
|
168
|
+
|
|
163
169
|
| Env Var | Default | Description |
|
|
164
170
|
| --- | --- | --- |
|
|
165
171
|
| `PI_SUBAGENT_STARTUP_TIMEOUT` | `120000` | Milliseconds allowed to reach the first model turn; `0` disables |
|
|
@@ -169,7 +175,10 @@ result so every waiting parent can settle.
|
|
|
169
175
|
## Timestamps & Status Footer
|
|
170
176
|
|
|
171
177
|
Subagent tool calls and live activity lines render a dim `hh:mm:ss` timestamp
|
|
172
|
-
(call start, per-subagent run start, and each live log entry).
|
|
178
|
+
(call start, per-subagent run start, and each live log entry). The collapsed tool
|
|
179
|
+
row keeps the normal compact progress text. Press `Ctrl+O` to expand it into the
|
|
180
|
+
subagent tree immediately; every running node shows its latest six activity
|
|
181
|
+
lines (thinking, tool starts/completions, and completed turns).
|
|
173
182
|
|
|
174
183
|
In the interactive TUI the extension publishes the combined `total` usage line
|
|
175
184
|
(parent + all subagents, recursively) via Pi's normal `ctx.ui.setStatus()`
|
|
@@ -190,6 +199,7 @@ The same detection also runs after navigating the session tree in the TUI (Esc n
|
|
|
190
199
|
- TUI mode asks: **Resume subagents?**
|
|
191
200
|
- Non-UI modes (`pi -p`, JSON/RPC) resume automatically.
|
|
192
201
|
- Already-finished subagents are reused as completed; unfinished ones continue from their own saved sessions.
|
|
202
|
+
- Durable child refs retain final output, own usage, model, and tool counts, so completed siblings survive a JSON/session restart without becoming `(no output)` or losing accounting.
|
|
193
203
|
- Nested subagents use the same mechanism recursively.
|
|
194
204
|
- Provider fallback goes through the selected model's effective Pi provider, so custom providers, custom APIs, auth-derived endpoints, headers, and provider-scoped environment are preserved.
|
|
195
205
|
- Pending resume state and delayed callbacks are discarded on `/resume`, `/new`, `/fork`, and `/reload`, preventing stale work from an old runtime from leaking into the replacement session.
|
|
@@ -220,6 +230,7 @@ Naming is deliberately unambiguous: `agent` (in `subagents`) selects an agent
|
|
|
220
230
|
subagent *instance* by its unique name.
|
|
221
231
|
|
|
222
232
|
- All resumes in one call run **in parallel**.
|
|
233
|
+
- The preferred shape is `{"resumes":[...]}`. For compatibility, the common single-item shorthand `{"subagent":"name","task":"..."}` is normalized automatically before validation.
|
|
223
234
|
- Names are unique within one delegation tree (everything spawned from one
|
|
224
235
|
top-level session) and are persisted in a registry file under the subagent
|
|
225
236
|
session root, so they survive restarts: you can resume a subagent in a later
|
|
@@ -336,7 +347,7 @@ interface SingleResult {
|
|
|
336
347
|
agent: string; // agent name
|
|
337
348
|
agentSource: "user" | "project" | "builtin" | "unknown";
|
|
338
349
|
task: string; // task string passed to this agent
|
|
339
|
-
exitCode: number; // 0 = success, >0 = error, -1 = still running
|
|
350
|
+
exitCode: number; // 0 = process success, >0 = error, -1 = still running
|
|
340
351
|
messages: Message[]; // full conversation history of the subagent
|
|
341
352
|
stderr: string;
|
|
342
353
|
usage: UsageStats; // this agent's OWN token usage only
|
package/index.ts
CHANGED
|
@@ -76,6 +76,8 @@ import {
|
|
|
76
76
|
getNestedSubagentResults,
|
|
77
77
|
isResultError,
|
|
78
78
|
isSubagentDetails,
|
|
79
|
+
prepareResumeArguments,
|
|
80
|
+
subagentDetailsHaveErrors,
|
|
79
81
|
isSubagentToolName,
|
|
80
82
|
RESUME_SUBAGENTS_TOOL_NAME,
|
|
81
83
|
SUBAGENT_TOOL_NAME,
|
|
@@ -442,7 +444,14 @@ function liveDetailsSignature(details: SubagentDetails): string {
|
|
|
442
444
|
function collectLiveUsageSummary(details: SubagentDetails): SubagentUsageSummary {
|
|
443
445
|
const summary = emptyUsageSummary();
|
|
444
446
|
for (const result of details.results) {
|
|
447
|
+
if (result.subtreeUsageSummary) {
|
|
448
|
+
addUsageSummary(summary, result.subtreeUsageSummary);
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
445
451
|
addUsageSummary(summary, usageSummaryFromUsage(result.usage));
|
|
452
|
+
if (result.priorDescendantUsageSummary) {
|
|
453
|
+
addUsageSummary(summary, result.priorDescendantUsageSummary);
|
|
454
|
+
}
|
|
446
455
|
|
|
447
456
|
const completedNested = getNestedSubagentResults(result.messages ?? []);
|
|
448
457
|
const completedNestedIds = new Set<string>();
|
|
@@ -519,15 +528,6 @@ function collectCombinedUsageStatusLine(
|
|
|
519
528
|
return formatCombinedUsageStatusLine(parentUsage, subagents.subagentCount);
|
|
520
529
|
}
|
|
521
530
|
|
|
522
|
-
function getCycleViolations(
|
|
523
|
-
requestedNames: Set<string>,
|
|
524
|
-
ancestorAgentStack: string[],
|
|
525
|
-
): string[] {
|
|
526
|
-
if (requestedNames.size === 0 || ancestorAgentStack.length === 0) return [];
|
|
527
|
-
const stackSet = new Set(ancestorAgentStack);
|
|
528
|
-
return Array.from(requestedNames).filter((name) => stackSet.has(name));
|
|
529
|
-
}
|
|
530
|
-
|
|
531
531
|
/** Get project-local agents referenced by the current request. */
|
|
532
532
|
function getRequestedProjectAgents(
|
|
533
533
|
agents: AgentConfig[],
|
|
@@ -983,6 +983,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
983
983
|
const activeSubagents = new Map<number, { agent: string; task: string; taskIndex: number; handle: RunningSubagentHandle; name?: string }>();
|
|
984
984
|
/** Names with a resume in flight in this process (same-process race guard). */
|
|
985
985
|
const activeResumeNames = new Set<string>();
|
|
986
|
+
/** Tool calls whose execute result requested an actual Pi error result. */
|
|
987
|
+
const forcedErrorToolCallIds = new Set<string>();
|
|
986
988
|
const activeSubagentUsageSummaries = new Map<string, SubagentUsageSummary>();
|
|
987
989
|
const latestBroadcastTargets = {
|
|
988
990
|
all: [] as BroadcastTarget[],
|
|
@@ -1515,6 +1517,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1515
1517
|
latestSessionCtx = undefined;
|
|
1516
1518
|
resumeModelRegistry = undefined;
|
|
1517
1519
|
activeSubagentUsageSummaries.clear();
|
|
1520
|
+
forcedErrorToolCallIds.clear();
|
|
1518
1521
|
activeSubagents.clear();
|
|
1519
1522
|
});
|
|
1520
1523
|
|
|
@@ -1655,6 +1658,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
1655
1658
|
scheduleSessionTask(() => updateCombinedUsageStatus(ctx), 0);
|
|
1656
1659
|
});
|
|
1657
1660
|
|
|
1661
|
+
// A custom tool must throw to make Pi set isError, but throwing would discard
|
|
1662
|
+
// the durable subagent details needed for crash-resume. Convert our internal
|
|
1663
|
+
// outcome marker through Pi's supported tool_result middleware instead.
|
|
1664
|
+
pi.on("tool_result", (event: any) => {
|
|
1665
|
+
if (!isSubagentToolName(event?.toolName)) return;
|
|
1666
|
+
const detailsFailed = subagentDetailsHaveErrors(event.details);
|
|
1667
|
+
const forced = forcedErrorToolCallIds.delete(event.toolCallId);
|
|
1668
|
+
if (forced || detailsFailed) return { isError: true };
|
|
1669
|
+
});
|
|
1670
|
+
|
|
1658
1671
|
pi.on("tool_execution_end", (event, ctx) => {
|
|
1659
1672
|
latestSessionCtx = ctx;
|
|
1660
1673
|
if (isSubagentToolName(event.toolName)) {
|
|
@@ -1776,6 +1789,7 @@ ${agentList}\n\n${subagentsGuidance}${resumeGuidance ? `\n\n${resumeGuidance}` :
|
|
|
1776
1789
|
parameters: SubagentParams,
|
|
1777
1790
|
|
|
1778
1791
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
1792
|
+
const toolResult = await (async () => {
|
|
1779
1793
|
try {
|
|
1780
1794
|
recordToolCallStart(toolCallId);
|
|
1781
1795
|
updateLatestBroadcastTargets(undefined);
|
|
@@ -1797,6 +1811,7 @@ ${agentList}\n\n${subagentsGuidance}${resumeGuidance ? `\n\n${resumeGuidance}` :
|
|
|
1797
1811
|
},
|
|
1798
1812
|
],
|
|
1799
1813
|
details: makeDetails("single")([]),
|
|
1814
|
+
isError: true,
|
|
1800
1815
|
};
|
|
1801
1816
|
}
|
|
1802
1817
|
|
|
@@ -1813,35 +1828,20 @@ ${agentList}\n\n${subagentsGuidance}${resumeGuidance ? `\n\n${resumeGuidance}` :
|
|
|
1813
1828
|
onUpdate?.(partial);
|
|
1814
1829
|
};
|
|
1815
1830
|
|
|
1816
|
-
//
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1831
|
+
// Apply whole-call security preflight only to tasks that can actually
|
|
1832
|
+
// run. Cyclic tasks become aligned structured failures in the runner;
|
|
1833
|
+
// they must not block legal siblings or consume durable names.
|
|
1834
|
+
const cyclicTaskIndexes = new Set<number>();
|
|
1820
1835
|
if (preventCycles) {
|
|
1821
|
-
const
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
);
|
|
1825
|
-
if (cycleViolations.length > 0) {
|
|
1826
|
-
const stackText =
|
|
1827
|
-
ancestorAgentStack.length > 0
|
|
1828
|
-
? ancestorAgentStack.join(" -> ")
|
|
1829
|
-
: "(root)";
|
|
1830
|
-
return {
|
|
1831
|
-
content: [
|
|
1832
|
-
{
|
|
1833
|
-
type: "text",
|
|
1834
|
-
text: `Blocked: delegation cycle detected. Requested agent(s) already in the delegation stack: ${cycleViolations.join(", ")}.
|
|
1835
|
-
Current stack: ${stackText}
|
|
1836
|
-
|
|
1837
|
-
This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A).`,
|
|
1838
|
-
},
|
|
1839
|
-
],
|
|
1840
|
-
details: makeDetails(executionMode)([]),
|
|
1841
|
-
isError: true,
|
|
1842
|
-
};
|
|
1843
|
-
}
|
|
1836
|
+
const stack = new Set(ancestorAgentStack);
|
|
1837
|
+
tasks.forEach((task, index) => {
|
|
1838
|
+
if (stack.has(task.agent)) cyclicTaskIndexes.add(index);
|
|
1839
|
+
});
|
|
1844
1840
|
}
|
|
1841
|
+
const requested = new Set<string>();
|
|
1842
|
+
tasks.forEach((task, index) => {
|
|
1843
|
+
if (!cyclicTaskIndexes.has(index)) requested.add(task.agent);
|
|
1844
|
+
});
|
|
1845
1845
|
|
|
1846
1846
|
const requestedProjectAgents = getRequestedProjectAgents(
|
|
1847
1847
|
agents,
|
|
@@ -1907,7 +1907,7 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
1907
1907
|
if (currentNamesFile) {
|
|
1908
1908
|
const pendingAllocation = tasks
|
|
1909
1909
|
.map((task, index) => ({ task, index }))
|
|
1910
|
-
.filter(({ index }) => !names[index]);
|
|
1910
|
+
.filter(({ index }) => !cyclicTaskIndexes.has(index) && !names[index]);
|
|
1911
1911
|
if (pendingAllocation.length > 0) {
|
|
1912
1912
|
try {
|
|
1913
1913
|
const allocated = await allocateSubagentNames(
|
|
@@ -1980,7 +1980,9 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
1980
1980
|
isError: true,
|
|
1981
1981
|
};
|
|
1982
1982
|
}
|
|
1983
|
-
|
|
1983
|
+
})();
|
|
1984
|
+
if ((toolResult as any)?.isError === true) forcedErrorToolCallIds.add(toolCallId);
|
|
1985
|
+
return toolResult;
|
|
1984
1986
|
},
|
|
1985
1987
|
|
|
1986
1988
|
renderCall: (args, theme, context) => renderCall(args, theme, context),
|
|
@@ -2003,8 +2005,10 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
2003
2005
|
'Example: { resumes: [{ subagent: "code-writer-01", task: "Also update the tests." }] }',
|
|
2004
2006
|
].join("\n"),
|
|
2005
2007
|
parameters: ResumeSubagentsParams,
|
|
2008
|
+
prepareArguments: prepareResumeArguments,
|
|
2006
2009
|
|
|
2007
2010
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
2011
|
+
const toolResult = await (async () => {
|
|
2008
2012
|
const markedNames: string[] = [];
|
|
2009
2013
|
try {
|
|
2010
2014
|
recordToolCallStart(toolCallId);
|
|
@@ -2192,6 +2196,9 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
2192
2196
|
}
|
|
2193
2197
|
}
|
|
2194
2198
|
}
|
|
2199
|
+
})();
|
|
2200
|
+
if ((toolResult as any)?.isError === true) forcedErrorToolCallIds.add(toolCallId);
|
|
2201
|
+
return toolResult;
|
|
2195
2202
|
},
|
|
2196
2203
|
|
|
2197
2204
|
renderCall: (args, theme, context) => renderResumeCall(args, theme, context),
|
package/package.json
CHANGED
package/render.ts
CHANGED
|
@@ -7,12 +7,19 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { Container, Spacer, Text } from "@mariozechner/pi-tui";
|
|
10
|
-
import
|
|
10
|
+
import {
|
|
11
|
+
MAX_LIVE_LOG_ENTRIES,
|
|
12
|
+
isResultError,
|
|
13
|
+
isSubagentDetails,
|
|
14
|
+
type SingleResult,
|
|
15
|
+
type SubagentDetails,
|
|
16
|
+
} from "./types.js";
|
|
11
17
|
import {
|
|
12
18
|
type ThemeFg,
|
|
13
19
|
buildTopLevelNodes,
|
|
14
20
|
countNodes,
|
|
15
21
|
formatClockTime,
|
|
22
|
+
formatLiveLogEntry,
|
|
16
23
|
hasNestedChildren,
|
|
17
24
|
renderTreeLines,
|
|
18
25
|
setBroadcastNumberingActive,
|
|
@@ -122,37 +129,119 @@ export function renderResumeCall(
|
|
|
122
129
|
// renderResult — shown after the tool completes / streams updates
|
|
123
130
|
// ---------------------------------------------------------------------------
|
|
124
131
|
|
|
132
|
+
function getResultText(
|
|
133
|
+
result: { content?: Array<{ type: string; text?: string }> },
|
|
134
|
+
): string {
|
|
135
|
+
const first = Array.isArray(result.content) ? result.content[0] : undefined;
|
|
136
|
+
return first?.type === "text" && typeof first.text === "string" && first.text
|
|
137
|
+
? first.text
|
|
138
|
+
: "(no output)";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function compactText(text: string, maxLength = 240): string {
|
|
142
|
+
const firstLine = text.replace(/\r\n?/g, "\n").split("\n").find((line) => line.trim()) ?? "(no output)";
|
|
143
|
+
return truncate(firstLine, maxLength);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function isRenderableResult(value: unknown): value is SingleResult {
|
|
147
|
+
return value !== null &&
|
|
148
|
+
typeof value === "object" &&
|
|
149
|
+
!Array.isArray(value) &&
|
|
150
|
+
typeof (value as Partial<SingleResult>).agent === "string" &&
|
|
151
|
+
typeof (value as Partial<SingleResult>).exitCode === "number";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getCollapsedResultText(details: SubagentDetails, fallbackText: string): string {
|
|
155
|
+
const results = details.results.filter(isRenderableResult);
|
|
156
|
+
if (results.length === 0) return compactText(fallbackText);
|
|
157
|
+
|
|
158
|
+
const running = results.filter((item) => item.exitCode === -1).length;
|
|
159
|
+
const finished = results.length - running;
|
|
160
|
+
const succeeded = results.filter((item) => item.exitCode === 0 && !isResultError(item)).length;
|
|
161
|
+
const failed = finished - succeeded;
|
|
162
|
+
|
|
163
|
+
if (details.mode === "parallel") {
|
|
164
|
+
return running > 0
|
|
165
|
+
? `Parallel: ${finished}/${results.length} done, ${running} running...`
|
|
166
|
+
: `Parallel: ${succeeded}/${results.length} succeeded${failed > 0 ? `, ${failed} failed` : ""}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const item = results[0];
|
|
170
|
+
if (item.exitCode === -1) return `Agent ${item.agent}: running...`;
|
|
171
|
+
if (isResultError(item)) {
|
|
172
|
+
const reason = [item.errorMessage, item.stderr, item.stopReason]
|
|
173
|
+
.find((value): value is string => typeof value === "string" && value.length > 0);
|
|
174
|
+
return `Agent ${item.agent}: failed${reason ? ` — ${compactText(reason, 180)}` : ""}`;
|
|
175
|
+
}
|
|
176
|
+
return `Agent ${item.agent}: completed`;
|
|
177
|
+
}
|
|
178
|
+
|
|
125
179
|
export function renderResult(
|
|
126
180
|
result: { content: Array<{ type: string; text?: string }>; details?: unknown },
|
|
127
|
-
|
|
181
|
+
expanded: boolean,
|
|
128
182
|
theme: { fg: ThemeFg; bold: (s: string) => string },
|
|
129
183
|
): Container | Text {
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
184
|
+
const fallbackText = getResultText(result);
|
|
185
|
+
|
|
186
|
+
// Keep the normal row compact. Ctrl+O switches to the live tree below.
|
|
187
|
+
if (!expanded) {
|
|
188
|
+
const text = isSubagentDetails(result.details)
|
|
189
|
+
? getCollapsedResultText(result.details, fallbackText)
|
|
190
|
+
: compactText(fallbackText);
|
|
191
|
+
return new Text(text, 0, 0);
|
|
134
192
|
}
|
|
135
193
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
? theme.fg("warning", "⏳")
|
|
141
|
-
: counts.error > 0
|
|
142
|
-
? theme.fg("error", "❌")
|
|
143
|
-
: theme.fg("success", "✅");
|
|
194
|
+
if (!isSubagentDetails(result.details) || result.details.results.length === 0) {
|
|
195
|
+
return new Text(fallbackText, 0, 0);
|
|
196
|
+
}
|
|
197
|
+
const details: SubagentDetails = result.details;
|
|
144
198
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
199
|
+
try {
|
|
200
|
+
const nodes = buildTopLevelNodes(details);
|
|
201
|
+
const counts = countNodes(nodes);
|
|
202
|
+
const showOutputPreview = !hasNestedChildren(nodes);
|
|
203
|
+
const icon = counts.running > 0
|
|
204
|
+
? theme.fg("warning", "⏳")
|
|
205
|
+
: counts.error > 0
|
|
206
|
+
? theme.fg("error", "❌")
|
|
207
|
+
: theme.fg("success", "✅");
|
|
153
208
|
|
|
154
|
-
|
|
155
|
-
|
|
209
|
+
const container = new Container();
|
|
210
|
+
container.addChild(
|
|
211
|
+
new Text(
|
|
212
|
+
`${icon} ${theme.fg("toolTitle", theme.bold("subagent tree "))}${theme.fg("dim", topLevelSummary(details, counts))}`,
|
|
213
|
+
0,
|
|
214
|
+
0,
|
|
215
|
+
),
|
|
216
|
+
);
|
|
156
217
|
|
|
157
|
-
|
|
218
|
+
container.addChild(new Spacer(1));
|
|
219
|
+
container.addChild(new Text(renderTreeLines(nodes, theme, showOutputPreview).join("\n"), 0, 0));
|
|
220
|
+
return container;
|
|
221
|
+
} catch {
|
|
222
|
+
// Pi falls back to raw result.content when a custom renderer throws, which
|
|
223
|
+
// made Ctrl+O appear broken until the offending rolling log entry expired.
|
|
224
|
+
// Keep an expanded tree visible even for malformed third-party/RPC data.
|
|
225
|
+
const lines: string[] = [];
|
|
226
|
+
for (const item of details.results) {
|
|
227
|
+
const agent = typeof item?.agent === "string" ? item.agent : "unknown agent";
|
|
228
|
+
const icon = item?.exitCode === -1
|
|
229
|
+
? theme.fg("warning", "⏳")
|
|
230
|
+
: item?.exitCode === 0
|
|
231
|
+
? theme.fg("success", "✅")
|
|
232
|
+
: theme.fg("error", "❌");
|
|
233
|
+
lines.push(`${icon} ${theme.fg("accent", agent)}`);
|
|
234
|
+
const liveLog = Array.isArray(item?.liveLog)
|
|
235
|
+
? item.liveLog.slice(-MAX_LIVE_LOG_ENTRIES)
|
|
236
|
+
: [];
|
|
237
|
+
for (const entry of liveLog) {
|
|
238
|
+
lines.push(` ${formatLiveLogEntry(entry, theme)}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return new Text(
|
|
242
|
+
`${theme.fg("toolTitle", theme.bold("subagent tree"))}\n${lines.join("\n") || fallbackText}`,
|
|
243
|
+
0,
|
|
244
|
+
0,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
158
247
|
}
|