fix: Stabilize subagent lifecycle and polish subagent live display [LET-7764] (#1391)

This commit is contained in:
Devansh Jain
2026-03-13 20:48:17 -07:00
committed by GitHub
parent 6f999fac25
commit 3015450120
9 changed files with 256 additions and 92 deletions

View File

@@ -680,6 +680,12 @@ async function executeSubagent(
}, },
}); });
// Consider execution "running" once the child process has successfully spawned.
// This avoids waiting on subagent init events (e.g. agentURL) to reflect progress.
proc.once("spawn", () => {
updateSubagent(subagentId, { status: "running" });
});
// Set up abort handler to kill the child process // Set up abort handler to kill the child process
let wasAborted = false; let wasAborted = false;
const abortHandler = () => { const abortHandler = () => {
@@ -708,6 +714,14 @@ async function executeSubagent(
crlfDelay: Number.POSITIVE_INFINITY, crlfDelay: Number.POSITIVE_INFINITY,
}); });
let rlClosed = false;
const rlClosedPromise = new Promise<void>((resolve) => {
rl.once("close", () => {
rlClosed = true;
resolve();
});
});
rl.on("line", (line: string) => { rl.on("line", (line: string) => {
stdoutChunks.push(Buffer.from(`${line}\n`)); stdoutChunks.push(Buffer.from(`${line}\n`));
processStreamEvent(line, state, subagentId); processStreamEvent(line, state, subagentId);
@@ -723,6 +737,13 @@ async function executeSubagent(
proc.on("error", () => resolve(null)); proc.on("error", () => resolve(null));
}); });
// Ensure all stdout lines have been processed before completing.
// Without this, late tool events can be dropped before Task marks completion.
if (!rlClosed) {
rl.close();
}
await rlClosedPromise;
// Clean up abort listener // Clean up abort listener
signal?.removeEventListener("abort", abortHandler); signal?.removeEventListener("abort", abortHandler);

View File

@@ -25,6 +25,7 @@ import {
} from "../helpers/subagentDisplay.js"; } from "../helpers/subagentDisplay.js";
import { import {
getSnapshot, getSnapshot,
getSubagentToolCount,
type SubagentState, type SubagentState,
subscribe, subscribe,
toggleExpanded, toggleExpanded,
@@ -71,20 +72,22 @@ interface AgentRowProps {
const AgentRow = memo( const AgentRow = memo(
({ agent, isLast, expanded, condensed = false }: AgentRowProps) => { ({ agent, isLast, expanded, condensed = false }: AgentRowProps) => {
const { treeChar, continueChar } = getTreeChars(isLast); const { treeChar, continueChar } = getTreeChars(isLast);
const rowIndent = " ";
const statusIndent = " ";
const expandedToolIndent = " ";
const columns = useTerminalWidth(); const columns = useTerminalWidth();
const gutterWidth = 8; // indent (3) + continueChar (2) + status indent (3) const gutterWidth =
rowIndent.length + continueChar.length + statusIndent.length;
const contentWidth = Math.max(0, columns - gutterWidth); const contentWidth = Math.max(0, columns - gutterWidth);
const isRunning = agent.status === "pending" || agent.status === "running"; const isRunning = agent.status === "pending" || agent.status === "running";
const toolCount = getSubagentToolCount(agent);
const shouldDim = isRunning && !agent.isBackground; const shouldDim = isRunning && !agent.isBackground;
const showStats = !(agent.isBackground && isRunning); const showStats =
!(agent.isBackground && isRunning) && !(isRunning && toolCount === 0);
const hideBackgroundStatusLine = const hideBackgroundStatusLine =
agent.isBackground && isRunning && !agent.agentURL; agent.isBackground && isRunning && !agent.agentURL;
const stats = formatStats( const stats = formatStats(toolCount, agent.totalTokens);
agent.toolCalls.length,
agent.totalTokens,
isRunning,
);
const modelDisplay = getSubagentModelDisplay(agent.model); const modelDisplay = getSubagentModelDisplay(agent.model);
const lastTool = agent.toolCalls[agent.toolCalls.length - 1]; const lastTool = agent.toolCalls[agent.toolCalls.length - 1];
@@ -98,9 +101,9 @@ const AgentRow = memo(
<Box flexDirection="column"> <Box flexDirection="column">
{/* Main row: tree char + description + type + model (no stats) */} {/* Main row: tree char + description + type + model (no stats) */}
<Box flexDirection="row"> <Box flexDirection="row">
<Text> <Text wrap="truncate-end">
<Text color={colors.subagent.treeChar}> <Text color={colors.subagent.treeChar}>
{" "} {rowIndent}
{treeChar}{" "} {treeChar}{" "}
</Text> </Text>
<Text bold={!shouldDim} dimColor={shouldDim}> <Text bold={!shouldDim} dimColor={shouldDim}>
@@ -131,19 +134,37 @@ const AgentRow = memo(
{/* Simple status line */} {/* Simple status line */}
{!hideBackgroundStatusLine && ( {!hideBackgroundStatusLine && (
<Box flexDirection="row"> <Box flexDirection="row">
<Text color={colors.subagent.treeChar}> {!agent.agentURL &&
{" "} !lastTool &&
{continueChar} !isComplete &&
</Text> agent.status !== "error" &&
<Text dimColor>{" "}</Text> !agent.isBackground ? (
{agent.status === "error" ? ( <>
<Text color={colors.subagent.error}>Error</Text> <Text color={colors.subagent.treeChar}>
) : isComplete ? ( {rowIndent}
<Text dimColor>Done</Text> {continueChar} {" "}
) : agent.isBackground ? ( </Text>
<Text dimColor>Running in the background</Text> <Text dimColor>Launching...</Text>
</>
) : ( ) : (
<Text dimColor>Running...</Text> <>
<Text color={colors.subagent.treeChar}>
{rowIndent}
{continueChar}
</Text>
<Text dimColor>{statusIndent}</Text>
{agent.status === "error" ? (
<Text color={colors.subagent.error}>Error</Text>
) : isComplete ? (
<Text dimColor>Done</Text>
) : agent.isBackground ? (
<Text dimColor>Running in the background</Text>
) : lastTool ? (
<Text dimColor>Running...</Text>
) : (
<Text dimColor>Thinking</Text>
)}
</>
)} )}
</Box> </Box>
)} )}
@@ -156,9 +177,9 @@ const AgentRow = memo(
<Box flexDirection="column"> <Box flexDirection="column">
{/* Main row: tree char + description + type + model + stats */} {/* Main row: tree char + description + type + model + stats */}
<Box flexDirection="row"> <Box flexDirection="row">
<Text> <Text wrap="truncate-end">
<Text color={colors.subagent.treeChar}> <Text color={colors.subagent.treeChar}>
{" "} {rowIndent}
{treeChar}{" "} {treeChar}{" "}
</Text> </Text>
<Text bold={!shouldDim} dimColor={shouldDim}> <Text bold={!shouldDim} dimColor={shouldDim}>
@@ -195,7 +216,7 @@ const AgentRow = memo(
{agent.agentURL && ( {agent.agentURL && (
<Box flexDirection="row"> <Box flexDirection="row">
<Text color={colors.subagent.treeChar}> <Text color={colors.subagent.treeChar}>
{" "} {rowIndent}
{continueChar} {" "} {continueChar} {" "}
</Text> </Text>
<Text dimColor>{"Subagent: "}</Text> <Text dimColor>{"Subagent: "}</Text>
@@ -210,11 +231,11 @@ const AgentRow = memo(
return ( return (
<Box key={tc.id} flexDirection="row"> <Box key={tc.id} flexDirection="row">
<Text color={colors.subagent.treeChar}> <Text color={colors.subagent.treeChar}>
{" "} {rowIndent}
{continueChar} {continueChar}
</Text> </Text>
<Text dimColor> <Text dimColor>
{" "} {expandedToolIndent}
{tc.name}({formattedArgs}) {tc.name}({formattedArgs})
</Text> </Text>
</Box> </Box>
@@ -224,23 +245,15 @@ const AgentRow = memo(
{/* Status line */} {/* Status line */}
{!hideBackgroundStatusLine && ( {!hideBackgroundStatusLine && (
<Box flexDirection="row"> <Box flexDirection="row">
{agent.status === "completed" ? ( {agent.status === "error" ? (
<>
<Text color={colors.subagent.treeChar}>
{" "}
{continueChar}
</Text>
<Text dimColor>{" Done"}</Text>
</>
) : agent.status === "error" ? (
<> <>
<Box width={gutterWidth} flexShrink={0}> <Box width={gutterWidth} flexShrink={0}>
<Text> <Text>
<Text color={colors.subagent.treeChar}> <Text color={colors.subagent.treeChar}>
{" "} {rowIndent}
{continueChar} {continueChar}
</Text> </Text>
<Text dimColor>{" "}</Text> <Text dimColor>{statusIndent}</Text>
</Text> </Text>
</Box> </Box>
<Box flexGrow={1} width={contentWidth}> <Box flexGrow={1} width={contentWidth}>
@@ -249,32 +262,33 @@ const AgentRow = memo(
</Text> </Text>
</Box> </Box>
</> </>
) : agent.isBackground ? ( ) : !agent.agentURL &&
<Text> !lastTool &&
<Text color={colors.subagent.treeChar}> agent.status !== "completed" &&
{" "} !agent.isBackground ? (
{continueChar}
</Text>
<Text dimColor>{" Running in the background"}</Text>
</Text>
) : lastTool ? (
<> <>
<Text color={colors.subagent.treeChar}> <Text color={colors.subagent.treeChar}>
{" "} {rowIndent}
{continueChar} {continueChar} {" "}
</Text>
<Text dimColor>
{" "}
{lastTool.name}
</Text> </Text>
<Text dimColor>Launching...</Text>
</> </>
) : ( ) : (
<> <>
<Text color={colors.subagent.treeChar}> <Text color={colors.subagent.treeChar}>
{" "} {rowIndent}
{continueChar} {continueChar}
</Text> </Text>
<Text dimColor>{" Starting..."}</Text> <Text dimColor>
{statusIndent}
{agent.status === "completed"
? "Done"
: agent.isBackground
? "Running in the background"
: lastTool
? lastTool.name
: "Thinking"}
</Text>
</> </>
)} )}
</Box> </Box>

View File

@@ -56,8 +56,11 @@ interface AgentRowProps {
const AgentRow = memo(({ agent, isLast }: AgentRowProps) => { const AgentRow = memo(({ agent, isLast }: AgentRowProps) => {
const { treeChar, continueChar } = getTreeChars(isLast); const { treeChar, continueChar } = getTreeChars(isLast);
const rowIndent = " ";
const statusIndent = " ";
const columns = useTerminalWidth(); const columns = useTerminalWidth();
const gutterWidth = 8; // indent (3) + continueChar (2) + status indent (3) const gutterWidth =
rowIndent.length + continueChar.length + statusIndent.length;
const contentWidth = Math.max(0, columns - gutterWidth); const contentWidth = Math.max(0, columns - gutterWidth);
const isRunning = agent.status === "running"; const isRunning = agent.status === "running";
@@ -65,16 +68,16 @@ const AgentRow = memo(({ agent, isLast }: AgentRowProps) => {
const showStats = !(agent.isBackground && isRunning); const showStats = !(agent.isBackground && isRunning);
const hideBackgroundStatusLine = const hideBackgroundStatusLine =
agent.isBackground && isRunning && !agent.agentURL; agent.isBackground && isRunning && !agent.agentURL;
const stats = formatStats(agent.toolCount, agent.totalTokens, isRunning); const stats = formatStats(agent.toolCount, agent.totalTokens);
const modelDisplay = getSubagentModelDisplay(agent.model); const modelDisplay = getSubagentModelDisplay(agent.model);
return ( return (
<Box flexDirection="column"> <Box flexDirection="column">
{/* Main row: tree char + description + type + model + stats */} {/* Main row: tree char + description + type + model + stats */}
<Box flexDirection="row"> <Box flexDirection="row">
<Text> <Text wrap="truncate-end">
<Text color={colors.subagent.treeChar}> <Text color={colors.subagent.treeChar}>
{" "} {rowIndent}
{treeChar}{" "} {treeChar}{" "}
</Text> </Text>
<Text bold={!shouldDim} dimColor={shouldDim}> <Text bold={!shouldDim} dimColor={shouldDim}>
@@ -111,7 +114,7 @@ const AgentRow = memo(({ agent, isLast }: AgentRowProps) => {
{agent.agentURL && ( {agent.agentURL && (
<Box flexDirection="row"> <Box flexDirection="row">
<Text color={colors.subagent.treeChar}> <Text color={colors.subagent.treeChar}>
{" "} {rowIndent}
{continueChar} {" "} {continueChar} {" "}
</Text> </Text>
<Text dimColor>{"Subagent: "}</Text> <Text dimColor>{"Subagent: "}</Text>
@@ -122,23 +125,15 @@ const AgentRow = memo(({ agent, isLast }: AgentRowProps) => {
{/* Status line */} {/* Status line */}
{!hideBackgroundStatusLine && ( {!hideBackgroundStatusLine && (
<Box flexDirection="row"> <Box flexDirection="row">
{agent.status === "completed" && !agent.isBackground ? ( {agent.status === "error" ? (
<>
<Text color={colors.subagent.treeChar}>
{" "}
{continueChar}
</Text>
<Text dimColor>{" Done"}</Text>
</>
) : agent.status === "error" ? (
<> <>
<Box width={gutterWidth} flexShrink={0}> <Box width={gutterWidth} flexShrink={0}>
<Text> <Text>
<Text color={colors.subagent.treeChar}> <Text color={colors.subagent.treeChar}>
{" "} {rowIndent}
{continueChar} {continueChar}
</Text> </Text>
<Text dimColor>{" "}</Text> <Text dimColor>{statusIndent}</Text>
</Text> </Text>
</Box> </Box>
<Box flexGrow={1} width={contentWidth}> <Box flexGrow={1} width={contentWidth}>
@@ -150,10 +145,15 @@ const AgentRow = memo(({ agent, isLast }: AgentRowProps) => {
) : ( ) : (
<> <>
<Text color={colors.subagent.treeChar}> <Text color={colors.subagent.treeChar}>
{" "} {rowIndent}
{continueChar} {continueChar}
</Text> </Text>
<Text dimColor>{" Running in the background"}</Text> <Text dimColor>
{statusIndent}
{agent.status === "completed" && !agent.isBackground
? "Done"
: "Running in the background"}
</Text>
</> </>
)} )}
</Box> </Box>

View File

@@ -5,7 +5,11 @@
import type { StaticSubagent } from "../components/SubagentGroupStatic.js"; import type { StaticSubagent } from "../components/SubagentGroupStatic.js";
import type { Line } from "./accumulator.js"; import type { Line } from "./accumulator.js";
import { getSubagentByToolCallId, getSubagents } from "./subagentState.js"; import {
getSubagentByToolCallId,
getSubagents,
getSubagentToolCount,
} from "./subagentState.js";
import { isTaskTool } from "./toolNameMapping.js"; import { isTaskTool } from "./toolNameMapping.js";
/** /**
@@ -133,7 +137,7 @@ export function createSubagentGroupItem(
status: subagent.isBackground status: subagent.isBackground
? "running" ? "running"
: (subagent.status as "completed" | "error"), : (subagent.status as "completed" | "error"),
toolCount: subagent.toolCalls.length, toolCount: getSubagentToolCount(subagent),
totalTokens: subagent.totalTokens, totalTokens: subagent.totalTokens,
agentURL: subagent.agentURL, agentURL: subagent.agentURL,
error: subagent.error, error: subagent.error,

View File

@@ -12,22 +12,15 @@ import { formatCompact } from "./format";
* *
* @param toolCount - Number of tool calls * @param toolCount - Number of tool calls
* @param totalTokens - Total tokens used (0 or undefined means no data available) * @param totalTokens - Total tokens used (0 or undefined means no data available)
* @param isRunning - If true, shows "—" for tokens (since usage is only available at end)
*/ */
export function formatStats( export function formatStats(toolCount: number, totalTokens: number): string {
toolCount: number,
totalTokens: number,
isRunning = false,
): string {
const toolStr = `${toolCount} tool use${toolCount !== 1 ? "s" : ""}`; const toolStr = `${toolCount} tool use${toolCount !== 1 ? "s" : ""}`;
// Only show token count if we have actual data (not running and totalTokens > 0) if (totalTokens > 0) {
const hasTokenData = !isRunning && totalTokens > 0; return `${toolStr} · ${formatCompact(totalTokens)} tokens`;
if (!hasTokenData) {
return toolStr;
} }
return `${toolStr} · ${formatCompact(totalTokens)} tokens`; return toolStr;
} }
/** /**

View File

@@ -23,6 +23,8 @@ export interface SubagentState {
status: "pending" | "running" | "completed" | "error"; status: "pending" | "running" | "completed" | "error";
agentURL: string | null; agentURL: string | null;
toolCalls: ToolCall[]; toolCalls: ToolCall[];
// Monotonic counter to avoid transient regressions in rendered tool usage.
maxToolCallsSeen: number;
totalTokens: number; totalTokens: number;
durationMs: number; durationMs: number;
error?: string; error?: string;
@@ -121,6 +123,7 @@ export function registerSubagent(
status: "pending", status: "pending",
agentURL: null, agentURL: null,
toolCalls: [], toolCalls: [],
maxToolCallsSeen: 0,
totalTokens: 0, totalTokens: 0,
durationMs: 0, durationMs: 0,
startTime: Date.now(), startTime: Date.now(),
@@ -148,8 +151,22 @@ export function updateSubagent(
updates.status = "running"; updates.status = "running";
} }
const nextToolCalls = updates.toolCalls ?? agent.toolCalls;
const nextMax = Math.max(
agent.maxToolCallsSeen,
nextToolCalls.length,
updates.maxToolCallsSeen ?? 0,
);
// Skip no-op updates to avoid unnecessary re-renders
const keys = Object.keys(updates) as (keyof typeof updates)[];
const isNoop =
keys.every((k) => agent[k] === updates[k]) &&
nextMax === agent.maxToolCallsSeen;
if (isNoop) return;
// Create a new object to ensure React.memo detects the change // Create a new object to ensure React.memo detects the change
const updatedAgent = { ...agent, ...updates }; const updatedAgent = { ...agent, ...updates, maxToolCallsSeen: nextMax };
store.agents.set(id, updatedAgent); store.agents.set(id, updatedAgent);
notifyListeners(); notifyListeners();
} }
@@ -176,6 +193,10 @@ export function addToolCall(
...agent.toolCalls, ...agent.toolCalls,
{ id: toolCallId, name: toolName, args: toolArgs }, { id: toolCallId, name: toolName, args: toolArgs },
], ],
maxToolCallsSeen: Math.max(
agent.maxToolCallsSeen,
agent.toolCalls.length + 1,
),
}; };
store.agents.set(subagentId, updatedAgent); store.agents.set(subagentId, updatedAgent);
notifyListeners(); notifyListeners();
@@ -198,11 +219,18 @@ export function completeSubagent(
error: result.error, error: result.error,
durationMs: Date.now() - agent.startTime, durationMs: Date.now() - agent.startTime,
totalTokens: result.totalTokens ?? agent.totalTokens, totalTokens: result.totalTokens ?? agent.totalTokens,
maxToolCallsSeen: Math.max(agent.maxToolCallsSeen, agent.toolCalls.length),
} as SubagentState; } as SubagentState;
store.agents.set(id, updatedAgent); store.agents.set(id, updatedAgent);
notifyListeners(); notifyListeners();
} }
export function getSubagentToolCount(
agent: Pick<SubagentState, "toolCalls" | "maxToolCallsSeen">,
): number {
return Math.max(agent.toolCalls.length, agent.maxToolCallsSeen);
}
/** /**
* Toggle expanded/collapsed state * Toggle expanded/collapsed state
*/ */

View File

@@ -0,0 +1,96 @@
import { beforeEach, describe, expect, test } from "bun:test";
import type { Line } from "../../cli/helpers/accumulator";
import {
collectFinishedTaskToolCalls,
createSubagentGroupItem,
} from "../../cli/helpers/subagentAggregation";
import {
addToolCall,
clearAllSubagents,
completeSubagent,
getSubagentByToolCallId,
getSubagentToolCount,
registerSubagent,
updateSubagent,
} from "../../cli/helpers/subagentState";
describe("subagent tool count stability", () => {
beforeEach(() => {
clearAllSubagents();
});
test("tool count remains monotonic even if toolCalls array is overwritten with fewer entries", () => {
registerSubagent("sub-1", "explore", "Find symbols", "tc-task", false);
addToolCall("sub-1", "tc-read", "Read", "{}");
addToolCall("sub-1", "tc-grep", "Grep", "{}");
const before = getSubagentByToolCallId("tc-task");
if (!before) {
throw new Error("Expected subagent for tc-task");
}
expect(getSubagentToolCount(before)).toBe(2);
// Simulate a stale overwrite (should not reduce displayed count).
updateSubagent("sub-1", {
toolCalls: before.toolCalls.slice(0, 1),
});
const after = getSubagentByToolCallId("tc-task");
if (!after) {
throw new Error("Expected updated subagent for tc-task");
}
expect(after.toolCalls.length).toBe(1);
expect(getSubagentToolCount(after)).toBe(2);
completeSubagent("sub-1", { success: true });
const completed = getSubagentByToolCallId("tc-task");
if (!completed) {
throw new Error("Expected completed subagent for tc-task");
}
expect(getSubagentToolCount(completed)).toBe(2);
});
test("static subagent grouping uses monotonic tool count", () => {
registerSubagent("sub-1", "explore", "Find symbols", "tc-task", false);
addToolCall("sub-1", "tc-read", "Read", "{}");
addToolCall("sub-1", "tc-grep", "Grep", "{}");
completeSubagent("sub-1", { success: true, totalTokens: 42 });
const subagent = getSubagentByToolCallId("tc-task");
if (!subagent) {
throw new Error("Expected subagent for tc-task before grouping");
}
// Simulate stale reduction right before grouping.
updateSubagent("sub-1", {
toolCalls: subagent.toolCalls.slice(0, 1),
});
const order = ["line-task"];
const byId = new Map<string, Line>([
[
"line-task",
{
kind: "tool_call",
id: "line-task",
name: "Task",
phase: "finished",
toolCallId: "tc-task",
resultOk: true,
},
],
]);
const finished = collectFinishedTaskToolCalls(
order,
byId,
new Set<string>(),
false,
);
expect(finished.length).toBe(1);
const group = createSubagentGroupItem(finished);
expect(group.agents.length).toBe(1);
expect(group.agents[0]?.toolCount).toBe(2);
});
});

View File

@@ -46,6 +46,7 @@ describe("spawnBackgroundSubagentTask", () => {
{ id: "tc-1", name: "Read", args: "{}" }, { id: "tc-1", name: "Read", args: "{}" },
{ id: "tc-2", name: "Edit", args: "{}" }, { id: "tc-2", name: "Edit", args: "{}" },
], ],
maxToolCallsSeen: 2,
totalTokens: 0, totalTokens: 0,
durationMs: 0, durationMs: 0,
startTime: Date.now(), startTime: Date.now(),

View File

@@ -16,6 +16,7 @@ import {
completeSubagent, completeSubagent,
generateSubagentId, generateSubagentId,
getSnapshot as getSubagentSnapshot, getSnapshot as getSubagentSnapshot,
getSubagentToolCount,
registerSubagent, registerSubagent,
} from "../../cli/helpers/subagentState.js"; } from "../../cli/helpers/subagentState.js";
import { formatTaskNotification } from "../../cli/helpers/taskNotifications.js"; import { formatTaskNotification } from "../../cli/helpers/taskNotifications.js";
@@ -293,9 +294,9 @@ export function spawnBackgroundSubagentTask(
if (!silentCompletion) { if (!silentCompletion) {
const subagentSnapshot = getSubagentSnapshotFn(); const subagentSnapshot = getSubagentSnapshotFn();
const toolUses = subagentSnapshot.agents.find( const subagentEntry = subagentSnapshot.agents.find(
(agent) => agent.id === subagentId, (agent) => agent.id === subagentId,
)?.toolCalls.length; );
const durationMs = Math.max(0, Date.now() - bgTask.startTime.getTime()); const durationMs = Math.max(0, Date.now() - bgTask.startTime.getTime());
const fullResult = result.success const fullResult = result.success
@@ -317,7 +318,10 @@ export function spawnBackgroundSubagentTask(
outputFile, outputFile,
usage: { usage: {
totalTokens: result.totalTokens, totalTokens: result.totalTokens,
toolUses, toolUses:
subagentEntry === undefined
? undefined
: getSubagentToolCount(subagentEntry),
durationMs, durationMs,
}, },
}); });
@@ -361,9 +365,9 @@ export function spawnBackgroundSubagentTask(
if (!silentCompletion) { if (!silentCompletion) {
const subagentSnapshot = getSubagentSnapshotFn(); const subagentSnapshot = getSubagentSnapshotFn();
const toolUses = subagentSnapshot.agents.find( const subagentEntry = subagentSnapshot.agents.find(
(agent) => agent.id === subagentId, (agent) => agent.id === subagentId,
)?.toolCalls.length; );
const durationMs = Math.max(0, Date.now() - bgTask.startTime.getTime()); const durationMs = Math.max(0, Date.now() - bgTask.startTime.getTime());
const notificationXml = formatTaskNotificationFn({ const notificationXml = formatTaskNotificationFn({
taskId, taskId,
@@ -372,7 +376,10 @@ export function spawnBackgroundSubagentTask(
result: errorMessage, result: errorMessage,
outputFile, outputFile,
usage: { usage: {
toolUses, toolUses:
subagentEntry === undefined
? undefined
: getSubagentToolCount(subagentEntry),
durationMs, durationMs,
}, },
}); });