fix: improve subagent UI display and interruption handling (#330)
Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
@@ -252,6 +252,45 @@ const DynamicPreview: React.FC<DynamicPreviewProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
// Task tool (subagent) - show nicely formatted preview
|
||||
if (t === "task") {
|
||||
const subagentType =
|
||||
typeof parsedArgs?.subagent_type === "string"
|
||||
? parsedArgs.subagent_type
|
||||
: "unknown";
|
||||
const description =
|
||||
typeof parsedArgs?.description === "string"
|
||||
? parsedArgs.description
|
||||
: "(no description)";
|
||||
const prompt =
|
||||
typeof parsedArgs?.prompt === "string"
|
||||
? parsedArgs.prompt
|
||||
: "(no prompt)";
|
||||
const model =
|
||||
typeof parsedArgs?.model === "string" ? parsedArgs.model : undefined;
|
||||
|
||||
// Truncate long prompts for preview (show first ~200 chars)
|
||||
const maxPromptLength = 200;
|
||||
const promptPreview =
|
||||
prompt.length > maxPromptLength
|
||||
? `${prompt.slice(0, maxPromptLength)}...`
|
||||
: prompt;
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" paddingLeft={2}>
|
||||
<Box flexDirection="row">
|
||||
<Text bold>{subagentType}</Text>
|
||||
<Text dimColor> · </Text>
|
||||
<Text>{description}</Text>
|
||||
</Box>
|
||||
{model && <Text dimColor>Model: {model}</Text>}
|
||||
<Box marginTop={1}>
|
||||
<Text dimColor>{promptPreview}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// File edit previews: write/edit/multi_edit/replace/write_file/write_file_gemini
|
||||
if (
|
||||
(t === "write" ||
|
||||
@@ -714,5 +753,6 @@ function getHeaderLabel(toolName: string): string {
|
||||
if (t === "write_file" || t === "writefile") return "Write File";
|
||||
if (t === "killbash") return "Kill Shell";
|
||||
if (t === "bashoutput") return "Shell Output";
|
||||
if (t === "task") return "Task";
|
||||
return toolName;
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ const AgentRow = memo(({ agent, isLast, expanded }: AgentRowProps) => {
|
||||
<Text dimColor>{" ⎿ Done"}</Text>
|
||||
) : agent.status === "error" ? (
|
||||
<Text color={colors.subagent.error}>
|
||||
{" ⎿ Error: "}
|
||||
{" ⎿ "}
|
||||
{agent.error}
|
||||
</Text>
|
||||
) : lastTool ? (
|
||||
@@ -151,21 +151,27 @@ AgentRow.displayName = "AgentRow";
|
||||
interface GroupHeaderProps {
|
||||
count: number;
|
||||
allCompleted: boolean;
|
||||
hasErrors: boolean;
|
||||
expanded: boolean;
|
||||
}
|
||||
|
||||
const GroupHeader = memo(
|
||||
({ count, allCompleted, expanded }: GroupHeaderProps) => {
|
||||
({ count, allCompleted, hasErrors, expanded }: GroupHeaderProps) => {
|
||||
const statusText = allCompleted
|
||||
? `Ran ${count} subagent${count !== 1 ? "s" : ""}`
|
||||
: `Running ${count} subagent${count !== 1 ? "s" : ""}…`;
|
||||
|
||||
const hint = expanded ? "(ctrl+o to collapse)" : "(ctrl+o to expand)";
|
||||
|
||||
// Use error color for dot if any subagent errored
|
||||
const dotColor = hasErrors
|
||||
? colors.subagent.error
|
||||
: colors.subagent.completed;
|
||||
|
||||
return (
|
||||
<Box flexDirection="row">
|
||||
{allCompleted ? (
|
||||
<Text color={colors.subagent.completed}>⏺</Text>
|
||||
<Text color={dotColor}>⏺</Text>
|
||||
) : (
|
||||
<BlinkDot color={colors.subagent.header} />
|
||||
)}
|
||||
@@ -200,12 +206,14 @@ export const SubagentGroupDisplay = memo(() => {
|
||||
const allCompleted = agents.every(
|
||||
(a) => a.status === "completed" || a.status === "error",
|
||||
);
|
||||
const hasErrors = agents.some((a) => a.status === "error");
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" marginTop={1}>
|
||||
<GroupHeader
|
||||
count={agents.length}
|
||||
allCompleted={allCompleted}
|
||||
hasErrors={hasErrors}
|
||||
expanded={expanded}
|
||||
/>
|
||||
{agents.map((agent, index) => (
|
||||
|
||||
@@ -87,7 +87,7 @@ const AgentRow = memo(({ agent, isLast }: AgentRowProps) => {
|
||||
<Text dimColor>{" ⎿ Done"}</Text>
|
||||
) : (
|
||||
<Text color={colors.subagent.error}>
|
||||
{" ⎿ Error: "}
|
||||
{" ⎿ "}
|
||||
{agent.error}
|
||||
</Text>
|
||||
)}
|
||||
@@ -109,12 +109,18 @@ export const SubagentGroupStatic = memo(
|
||||
}
|
||||
|
||||
const statusText = `Ran ${agents.length} subagent${agents.length !== 1 ? "s" : ""}`;
|
||||
const hasErrors = agents.some((a) => a.status === "error");
|
||||
|
||||
// Use error color for dot if any subagent errored
|
||||
const dotColor = hasErrors
|
||||
? colors.subagent.error
|
||||
: colors.subagent.completed;
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
{/* Header */}
|
||||
<Box flexDirection="row">
|
||||
<Text color={colors.subagent.completed}>⏺</Text>
|
||||
<Text color={dotColor}>⏺</Text>
|
||||
<Text color={colors.subagent.header}> {statusText}</Text>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Box, Text } from "ink";
|
||||
import { memo } from "react";
|
||||
import { INTERRUPTED_BY_USER } from "../../constants";
|
||||
import { clipToolReturn } from "../../tools/manager.js";
|
||||
import { formatArgsDisplay } from "../helpers/formatArgsDisplay.js";
|
||||
import {
|
||||
@@ -46,8 +47,14 @@ export const ToolCallMessage = memo(({ line }: { line: ToolCallLine }) => {
|
||||
const argsText = line.argsText ?? "...";
|
||||
|
||||
// Task tool - handled by SubagentGroupDisplay, don't render here
|
||||
// Exception: Cancelled/rejected Task tools should be rendered inline
|
||||
// since they won't appear in SubagentGroupDisplay
|
||||
if (isTaskTool(rawName)) {
|
||||
return null;
|
||||
const isCancelledOrRejected =
|
||||
line.phase === "finished" && line.resultOk === false;
|
||||
if (!isCancelledOrRejected) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply tool name remapping
|
||||
@@ -103,14 +110,14 @@ export const ToolCallMessage = memo(({ line }: { line: ToolCallLine }) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (line.resultText === "Interrupted by user") {
|
||||
if (line.resultText === INTERRUPTED_BY_USER) {
|
||||
return (
|
||||
<Box flexDirection="row">
|
||||
<Box width={prefixWidth} flexShrink={0}>
|
||||
<Text>{prefix}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} width={contentWidth}>
|
||||
<Text color={colors.status.interrupt}>Interrupted by user</Text>
|
||||
<Text color={colors.status.interrupt}>{INTERRUPTED_BY_USER}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user