fix: patch subagent display (#512)

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Charles Packer
2026-01-10 09:59:00 -08:00
committed by GitHub
parent ff9ad0deb0
commit 3fa18f7699
5 changed files with 663 additions and 429 deletions

View File

@@ -17,6 +17,7 @@
import { Box, Text, useInput } from "ink";
import { memo, useSyncExternalStore } from "react";
import { useAnimation } from "../contexts/AnimationContext.js";
import { formatStats, getTreeChars } from "../helpers/subagentDisplay.js";
import {
getSnapshot,
@@ -59,123 +60,167 @@ interface AgentRowProps {
agent: SubagentState;
isLast: boolean;
expanded: boolean;
condensed?: boolean;
}
const AgentRow = memo(({ agent, isLast, expanded }: AgentRowProps) => {
const { treeChar, continueChar } = getTreeChars(isLast);
const columns = useTerminalWidth();
const gutterWidth = 8; // indent (3) + continueChar (2) + status indent (3)
const contentWidth = Math.max(0, columns - gutterWidth);
const AgentRow = memo(
({ agent, isLast, expanded, condensed = false }: AgentRowProps) => {
const { treeChar, continueChar } = getTreeChars(isLast);
const columns = useTerminalWidth();
const gutterWidth = 8; // indent (3) + continueChar (2) + status indent (3)
const contentWidth = Math.max(0, columns - gutterWidth);
const isRunning = agent.status === "pending" || agent.status === "running";
const stats = formatStats(
agent.toolCalls.length,
agent.totalTokens,
isRunning,
);
const lastTool = agent.toolCalls[agent.toolCalls.length - 1];
const isRunning = agent.status === "pending" || agent.status === "running";
const stats = formatStats(
agent.toolCalls.length,
agent.totalTokens,
isRunning,
);
const lastTool = agent.toolCalls[agent.toolCalls.length - 1];
return (
<Box flexDirection="column">
{/* Main row: tree char + description + type + model + stats */}
<Box flexDirection="row">
<Text>
<Text color={colors.subagent.treeChar}>
{" "}
{treeChar}{" "}
</Text>
<Text bold>{agent.description}</Text>
<Text dimColor>
{" · "}
{agent.type.toLowerCase()}
{agent.model ? ` · ${agent.model}` : ""}
{" · "}
{stats}
</Text>
</Text>
</Box>
{/* Subagent URL */}
{agent.agentURL && (
<Box flexDirection="row">
<Text color={colors.subagent.treeChar}>
{" "}
{continueChar} {" "}
</Text>
<Text dimColor>{"Subagent: "}</Text>
<Text dimColor>{agent.agentURL}</Text>
// Condensed mode: simplified view to reduce re-renders when overflowing
// Shows: "Description · type · model" + "Running..." or "Done"
// Full details are shown in SubagentGroupStatic when flushed to static area
if (condensed) {
const isComplete =
agent.status === "completed" || agent.status === "error";
return (
<Box flexDirection="column">
{/* Main row: tree char + description + type + model (no stats) */}
<Box flexDirection="row">
<Text>
<Text color={colors.subagent.treeChar}>
{" "}
{treeChar}{" "}
</Text>
<Text bold>{agent.description}</Text>
<Text dimColor>
{" · "}
{agent.type.toLowerCase()}
{agent.model ? ` · ${agent.model}` : ""}
</Text>
</Text>
</Box>
{/* Simple status line */}
<Box flexDirection="row">
<Text color={colors.subagent.treeChar}>
{" "}
{continueChar}
</Text>
<Text dimColor>{" "}</Text>
{agent.status === "error" ? (
<Text color={colors.subagent.error}>Error</Text>
) : (
<Text dimColor>{isComplete ? "Done" : "Running..."}</Text>
)}
</Box>
</Box>
)}
);
}
{/* Expanded: show all tool calls */}
{expanded &&
agent.toolCalls.map((tc) => {
const formattedArgs = formatToolArgs(tc.args);
return (
<Box key={tc.id} flexDirection="row">
// Full mode: all details including live tool calls
return (
<Box flexDirection="column">
{/* Main row: tree char + description + type + model + stats */}
<Box flexDirection="row">
<Text>
<Text color={colors.subagent.treeChar}>
{" "}
{treeChar}{" "}
</Text>
<Text bold>{agent.description}</Text>
<Text dimColor>
{" · "}
{agent.type.toLowerCase()}
{agent.model ? ` · ${agent.model}` : ""}
{" · "}
{stats}
</Text>
</Text>
</Box>
{/* Subagent URL */}
{agent.agentURL && (
<Box flexDirection="row">
<Text color={colors.subagent.treeChar}>
{" "}
{continueChar} {" "}
</Text>
<Text dimColor>{"Subagent: "}</Text>
<Text dimColor>{agent.agentURL}</Text>
</Box>
)}
{/* Expanded: show all tool calls */}
{expanded &&
agent.toolCalls.map((tc) => {
const formattedArgs = formatToolArgs(tc.args);
return (
<Box key={tc.id} flexDirection="row">
<Text color={colors.subagent.treeChar}>
{" "}
{continueChar}
</Text>
<Text dimColor>
{" "}
{tc.name}({formattedArgs})
</Text>
</Box>
);
})}
{/* Status line */}
<Box flexDirection="row">
{agent.status === "completed" ? (
<>
<Text color={colors.subagent.treeChar}>
{" "}
{continueChar}
</Text>
<Text dimColor>{" Done"}</Text>
</>
) : agent.status === "error" ? (
<>
<Box width={gutterWidth} flexShrink={0}>
<Text>
<Text color={colors.subagent.treeChar}>
{" "}
{continueChar}
</Text>
<Text dimColor>{" "}</Text>
</Text>
</Box>
<Box flexGrow={1} width={contentWidth}>
<Text wrap="wrap" color={colors.subagent.error}>
{agent.error}
</Text>
</Box>
</>
) : lastTool ? (
<>
<Text color={colors.subagent.treeChar}>
{" "}
{continueChar}
</Text>
<Text dimColor>
{" "}
{tc.name}({formattedArgs})
{" "}
{lastTool.name}
</Text>
</Box>
);
})}
{/* Status line */}
<Box flexDirection="row">
{agent.status === "completed" ? (
<>
<Text color={colors.subagent.treeChar}>
{" "}
{continueChar}
</Text>
<Text dimColor>{" Done"}</Text>
</>
) : agent.status === "error" ? (
<>
<Box width={gutterWidth} flexShrink={0}>
<Text>
<Text color={colors.subagent.treeChar}>
{" "}
{continueChar}
</Text>
<Text dimColor>{" "}</Text>
</>
) : (
<>
<Text color={colors.subagent.treeChar}>
{" "}
{continueChar}
</Text>
</Box>
<Box flexGrow={1} width={contentWidth}>
<Text wrap="wrap" color={colors.subagent.error}>
{agent.error}
</Text>
</Box>
</>
) : lastTool ? (
<>
<Text color={colors.subagent.treeChar}>
{" "}
{continueChar}
</Text>
<Text dimColor>
{" "}
{lastTool.name}
</Text>
</>
) : (
<>
<Text color={colors.subagent.treeChar}>
{" "}
{continueChar}
</Text>
<Text dimColor>{" Starting..."}</Text>
</>
)}
<Text dimColor>{" Starting..."}</Text>
</>
)}
</Box>
</Box>
</Box>
);
});
);
},
);
AgentRow.displayName = "AgentRow";
interface GroupHeaderProps {
@@ -203,6 +248,7 @@ const GroupHeader = memo(
{allCompleted ? (
<Text color={dotColor}></Text>
) : (
// BlinkDot now gets shouldAnimate from AnimationContext
<BlinkDot color={colors.subagent.header} />
)}
<Text color={colors.subagent.header}> {statusText} </Text>
@@ -220,6 +266,7 @@ GroupHeader.displayName = "GroupHeader";
export const SubagentGroupDisplay = memo(() => {
const { agents, expanded } = useSyncExternalStore(subscribe, getSnapshot);
const { shouldAnimate } = useAnimation();
// Handle ctrl+o for expand/collapse
useInput((input, key) => {
@@ -233,6 +280,10 @@ export const SubagentGroupDisplay = memo(() => {
return null;
}
// Use condensed mode when animation is disabled (overflow detected by AnimationContext)
// This ensures consistent behavior - when we disable animation, we also simplify the view
const condensed = !shouldAnimate;
const allCompleted = agents.every(
(a) => a.status === "completed" || a.status === "error",
);
@@ -252,6 +303,7 @@ export const SubagentGroupDisplay = memo(() => {
agent={agent}
isLast={index === agents.length - 1}
expanded={expanded}
condensed={condensed}
/>
))}
</Box>