chore: Improve subagents UI (#205)
This commit is contained in:
20
src/cli/components/BlinkDot.tsx
Normal file
20
src/cli/components/BlinkDot.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Text } from "ink";
|
||||
import { memo, useEffect, useState } from "react";
|
||||
import { colors } from "./colors.js";
|
||||
|
||||
/**
|
||||
* A blinking dot indicator for running/pending states.
|
||||
* Toggles visibility every 400ms to create a blinking effect.
|
||||
*/
|
||||
export const BlinkDot = memo(
|
||||
({ color = colors.tool.pending }: { color?: string }) => {
|
||||
const [on, setOn] = useState(true);
|
||||
useEffect(() => {
|
||||
const t = setInterval(() => setOn((v) => !v), 400);
|
||||
return () => clearInterval(t);
|
||||
}, []);
|
||||
return <Text color={color}>{on ? "●" : " "}</Text>;
|
||||
},
|
||||
);
|
||||
|
||||
BlinkDot.displayName = "BlinkDot";
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Box, Text } from "ink";
|
||||
import { memo, useEffect, useState } from "react";
|
||||
import { memo } from "react";
|
||||
import { useTerminalWidth } from "../hooks/useTerminalWidth";
|
||||
import { BlinkDot } from "./BlinkDot.js";
|
||||
import { colors } from "./colors.js";
|
||||
import { MarkdownDisplay } from "./MarkdownDisplay.js";
|
||||
|
||||
@@ -13,17 +14,6 @@ type CommandLine = {
|
||||
success?: boolean;
|
||||
};
|
||||
|
||||
// BlinkDot component for running commands
|
||||
const BlinkDot: React.FC<{ color?: string }> = ({ color = "yellow" }) => {
|
||||
const [on, setOn] = useState(true);
|
||||
useEffect(() => {
|
||||
const t = setInterval(() => setOn((v) => !v), 400);
|
||||
return () => clearInterval(t);
|
||||
}, []);
|
||||
// Visible = colored dot; Off = space (keeps width/alignment)
|
||||
return <Text color={color}>{on ? "●" : " "}</Text>;
|
||||
};
|
||||
|
||||
/**
|
||||
* CommandMessage - Rich formatting version with two-column layout
|
||||
* Matches the formatting pattern used by other message types
|
||||
|
||||
222
src/cli/components/SubagentGroupDisplay.tsx
Normal file
222
src/cli/components/SubagentGroupDisplay.tsx
Normal file
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* SubagentGroupDisplay - Live/interactive subagent status display
|
||||
*
|
||||
* Used in the ACTIVE render area for subagents that may still be running.
|
||||
* Subscribes to external store and handles keyboard input - these hooks
|
||||
* require the component to stay "alive" and re-rendering.
|
||||
*
|
||||
* Features:
|
||||
* - Real-time updates via useSyncExternalStore
|
||||
* - Blinking dots for running agents
|
||||
* - Expand/collapse tool calls (ctrl+o)
|
||||
* - Shows "Running N subagents..." while active
|
||||
*
|
||||
* When agents complete, they get committed to Ink's <Static> area using
|
||||
* SubagentGroupStatic instead (a pure props-based snapshot with no hooks).
|
||||
*/
|
||||
|
||||
import { Box, Text, useInput } from "ink";
|
||||
import { memo, useSyncExternalStore } from "react";
|
||||
import { formatStats, getTreeChars } from "../helpers/subagentDisplay.js";
|
||||
import {
|
||||
getSnapshot,
|
||||
type SubagentState,
|
||||
subscribe,
|
||||
toggleExpanded,
|
||||
} from "../helpers/subagentState.js";
|
||||
import { BlinkDot } from "./BlinkDot.js";
|
||||
import { colors } from "./colors.js";
|
||||
|
||||
function formatToolArgs(argsStr: string): string {
|
||||
try {
|
||||
const args = JSON.parse(argsStr);
|
||||
const entries = Object.entries(args)
|
||||
.filter(([_, value]) => value !== undefined && value !== null)
|
||||
.slice(0, 2);
|
||||
|
||||
if (entries.length === 0) return "";
|
||||
|
||||
return entries
|
||||
.map(([key, value]) => {
|
||||
let displayValue = String(value);
|
||||
if (displayValue.length > 50) {
|
||||
displayValue = `${displayValue.slice(0, 47)}...`;
|
||||
}
|
||||
return `${key}: "${displayValue}"`;
|
||||
})
|
||||
.join(", ");
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Subcomponents
|
||||
// ============================================================================
|
||||
|
||||
interface AgentRowProps {
|
||||
agent: SubagentState;
|
||||
isLast: boolean;
|
||||
expanded: boolean;
|
||||
}
|
||||
|
||||
const AgentRow = memo(({ agent, isLast, expanded }: AgentRowProps) => {
|
||||
const { treeChar, continueChar } = getTreeChars(isLast);
|
||||
|
||||
const getDotElement = () => {
|
||||
switch (agent.status) {
|
||||
case "pending":
|
||||
return <BlinkDot color={colors.subagent.running} />;
|
||||
case "running":
|
||||
return <BlinkDot color={colors.subagent.running} />;
|
||||
case "completed":
|
||||
return <Text color={colors.subagent.completed}>●</Text>;
|
||||
case "error":
|
||||
return <Text color={colors.subagent.error}>●</Text>;
|
||||
default:
|
||||
return <Text>●</Text>;
|
||||
}
|
||||
};
|
||||
|
||||
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 + stats */}
|
||||
<Box flexDirection="row">
|
||||
<Text color={colors.subagent.treeChar}>{treeChar} </Text>
|
||||
{getDotElement()}
|
||||
<Text> {agent.description}</Text>
|
||||
<Text dimColor> · {agent.type.toLowerCase()}</Text>
|
||||
<Text color={colors.subagent.stats}> · {stats}</Text>
|
||||
</Box>
|
||||
|
||||
{/* Subagent URL */}
|
||||
{agent.agentURL && (
|
||||
<Box flexDirection="row">
|
||||
<Text color={colors.subagent.treeChar}>{continueChar}</Text>
|
||||
<Text dimColor>
|
||||
{" ⎿ Subagent: "}
|
||||
{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">
|
||||
<Text color={colors.subagent.treeChar}>{continueChar}</Text>
|
||||
{agent.status === "completed" ? (
|
||||
<Text dimColor>{" ⎿ Done"}</Text>
|
||||
) : agent.status === "error" ? (
|
||||
<Text color={colors.subagent.error}>
|
||||
{" ⎿ Error: "}
|
||||
{agent.error}
|
||||
</Text>
|
||||
) : lastTool ? (
|
||||
<Text dimColor>
|
||||
{" ⎿ "}
|
||||
{lastTool.name}
|
||||
</Text>
|
||||
) : (
|
||||
<Text dimColor>{" ⎿ Starting..."}</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
AgentRow.displayName = "AgentRow";
|
||||
|
||||
interface GroupHeaderProps {
|
||||
count: number;
|
||||
allCompleted: boolean;
|
||||
expanded: boolean;
|
||||
}
|
||||
|
||||
const GroupHeader = memo(
|
||||
({ count, allCompleted, 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)";
|
||||
|
||||
return (
|
||||
<Box flexDirection="row">
|
||||
{allCompleted ? (
|
||||
<Text color={colors.subagent.completed}>⏺</Text>
|
||||
) : (
|
||||
<BlinkDot color={colors.subagent.header} />
|
||||
)}
|
||||
<Text color={colors.subagent.header}> {statusText} </Text>
|
||||
<Text color={colors.subagent.hint}>{hint}</Text>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
GroupHeader.displayName = "GroupHeader";
|
||||
|
||||
// ============================================================================
|
||||
// Main Component
|
||||
// ============================================================================
|
||||
|
||||
export const SubagentGroupDisplay = memo(() => {
|
||||
const { agents, expanded } = useSyncExternalStore(subscribe, getSnapshot);
|
||||
|
||||
// Handle ctrl+o for expand/collapse
|
||||
useInput((input, key) => {
|
||||
if (key.ctrl && input === "o") {
|
||||
toggleExpanded();
|
||||
}
|
||||
});
|
||||
|
||||
// Don't render if no agents
|
||||
if (agents.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const allCompleted = agents.every(
|
||||
(a) => a.status === "completed" || a.status === "error",
|
||||
);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" marginTop={1}>
|
||||
<GroupHeader
|
||||
count={agents.length}
|
||||
allCompleted={allCompleted}
|
||||
expanded={expanded}
|
||||
/>
|
||||
{agents.map((agent, index) => (
|
||||
<AgentRow
|
||||
key={agent.id}
|
||||
agent={agent}
|
||||
isLast={index === agents.length - 1}
|
||||
expanded={expanded}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
SubagentGroupDisplay.displayName = "SubagentGroupDisplay";
|
||||
132
src/cli/components/SubagentGroupStatic.tsx
Normal file
132
src/cli/components/SubagentGroupStatic.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* SubagentGroupStatic - Frozen snapshot of completed subagents
|
||||
*
|
||||
* Used in Ink's <Static> area for historical/committed items that have
|
||||
* scrolled up and should no longer re-render. Pure props-based component
|
||||
* with NO hooks (no store subscriptions, no keyboard handlers).
|
||||
*
|
||||
* This separation from SubagentGroupDisplay is necessary because:
|
||||
* - Static area components shouldn't have active subscriptions (memory leaks)
|
||||
* - Keyboard handlers would stack up across frozen components
|
||||
* - We only need a simple snapshot, not live updates
|
||||
*
|
||||
* Shows: "Ran N subagents" with final stats (tool count, tokens).
|
||||
*/
|
||||
|
||||
import { Box, Text } from "ink";
|
||||
import { memo } from "react";
|
||||
import { formatStats, getTreeChars } from "../helpers/subagentDisplay.js";
|
||||
import { colors } from "./colors.js";
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
// ============================================================================
|
||||
|
||||
export interface StaticSubagent {
|
||||
id: string;
|
||||
type: string;
|
||||
description: string;
|
||||
status: "completed" | "error";
|
||||
toolCount: number;
|
||||
totalTokens: number;
|
||||
agentURL: string | null;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface SubagentGroupStaticProps {
|
||||
agents: StaticSubagent[];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Subcomponents
|
||||
// ============================================================================
|
||||
|
||||
interface AgentRowProps {
|
||||
agent: StaticSubagent;
|
||||
isLast: boolean;
|
||||
}
|
||||
|
||||
const AgentRow = memo(({ agent, isLast }: AgentRowProps) => {
|
||||
const { treeChar, continueChar } = getTreeChars(isLast);
|
||||
|
||||
const dotColor =
|
||||
agent.status === "completed"
|
||||
? colors.subagent.completed
|
||||
: colors.subagent.error;
|
||||
|
||||
const stats = formatStats(agent.toolCount, agent.totalTokens);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
{/* Main row: tree char + description + type + stats */}
|
||||
<Box flexDirection="row">
|
||||
<Text color={colors.subagent.treeChar}>{treeChar} </Text>
|
||||
<Text color={dotColor}>●</Text>
|
||||
<Text> {agent.description}</Text>
|
||||
<Text dimColor> · {agent.type.toLowerCase()}</Text>
|
||||
<Text color={colors.subagent.stats}> · {stats}</Text>
|
||||
</Box>
|
||||
|
||||
{/* Subagent URL */}
|
||||
{agent.agentURL && (
|
||||
<Box flexDirection="row">
|
||||
<Text color={colors.subagent.treeChar}>{continueChar}</Text>
|
||||
<Text dimColor>
|
||||
{" ⎿ Subagent: "}
|
||||
{agent.agentURL}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Status line */}
|
||||
<Box flexDirection="row">
|
||||
<Text color={colors.subagent.treeChar}>{continueChar}</Text>
|
||||
{agent.status === "completed" ? (
|
||||
<Text dimColor>{" ⎿ Done"}</Text>
|
||||
) : (
|
||||
<Text color={colors.subagent.error}>
|
||||
{" ⎿ Error: "}
|
||||
{agent.error}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
AgentRow.displayName = "AgentRow";
|
||||
|
||||
// ============================================================================
|
||||
// Main Component
|
||||
// ============================================================================
|
||||
|
||||
export const SubagentGroupStatic = memo(
|
||||
({ agents }: SubagentGroupStaticProps) => {
|
||||
if (agents.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const statusText = `Ran ${agents.length} subagent${agents.length !== 1 ? "s" : ""}`;
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
{/* Header */}
|
||||
<Box flexDirection="row">
|
||||
<Text color={colors.subagent.completed}>⏺</Text>
|
||||
<Text color={colors.subagent.header}> {statusText}</Text>
|
||||
</Box>
|
||||
|
||||
{/* Agent rows */}
|
||||
{agents.map((agent, index) => (
|
||||
<AgentRow
|
||||
key={agent.id}
|
||||
agent={agent}
|
||||
isLast={index === agents.length - 1}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
SubagentGroupStatic.displayName = "SubagentGroupStatic";
|
||||
@@ -1,60 +0,0 @@
|
||||
import { Box, Text } from "ink";
|
||||
import { memo } from "react";
|
||||
|
||||
type ToolCallLine = {
|
||||
kind: "tool_call";
|
||||
id: string;
|
||||
toolCallId?: string;
|
||||
name?: string;
|
||||
argsText?: string;
|
||||
resultText?: string;
|
||||
resultOk?: boolean;
|
||||
phase: "streaming" | "ready" | "running" | "finished";
|
||||
};
|
||||
|
||||
export const ToolCallMessage = memo(({ line }: { line: ToolCallLine }) => {
|
||||
const name = line.name ?? "?";
|
||||
const args = line.argsText ?? "...";
|
||||
|
||||
let dotColor: string | undefined;
|
||||
if (line.phase === "streaming") {
|
||||
dotColor = "gray";
|
||||
} else if (line.phase === "running") {
|
||||
dotColor = "yellow";
|
||||
} else if (line.phase === "finished") {
|
||||
dotColor = line.resultOk === false ? "red" : "green";
|
||||
}
|
||||
|
||||
// Parse and clean up result text for display
|
||||
const displayText = (() => {
|
||||
if (!line.resultText) return undefined;
|
||||
|
||||
// Try to parse JSON and extract error message for cleaner display
|
||||
try {
|
||||
const parsed = JSON.parse(line.resultText);
|
||||
if (parsed.error && typeof parsed.error === "string") {
|
||||
return parsed.error;
|
||||
}
|
||||
} catch {
|
||||
// Not JSON or parse failed, use raw text
|
||||
}
|
||||
|
||||
// Truncate long results
|
||||
return line.resultText.length > 80
|
||||
? `${line.resultText.slice(0, 80)}...`
|
||||
: line.resultText;
|
||||
})();
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Text>
|
||||
<Text color={dotColor}>•</Text> {name}({args})
|
||||
</Text>
|
||||
{displayText && (
|
||||
<Text>
|
||||
└ {line.resultOk === false ? "Error" : "Success"}: {displayText}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
@@ -1,8 +1,15 @@
|
||||
import { Box, Text } from "ink";
|
||||
import { memo, useEffect, useState } from "react";
|
||||
import { memo } from "react";
|
||||
import { clipToolReturn } from "../../tools/manager.js";
|
||||
import { formatArgsDisplay } from "../helpers/formatArgsDisplay.js";
|
||||
import {
|
||||
getDisplayToolName,
|
||||
isPlanTool,
|
||||
isTaskTool,
|
||||
isTodoTool,
|
||||
} from "../helpers/toolNameMapping.js";
|
||||
import { useTerminalWidth } from "../hooks/useTerminalWidth";
|
||||
import { BlinkDot } from "./BlinkDot.js";
|
||||
import { colors } from "./colors.js";
|
||||
import { MarkdownDisplay } from "./MarkdownDisplay.js";
|
||||
import { PlanRenderer } from "./PlanRenderer.js";
|
||||
@@ -19,19 +26,6 @@ type ToolCallLine = {
|
||||
phase: "streaming" | "ready" | "running" | "finished";
|
||||
};
|
||||
|
||||
// BlinkDot component copied verbatim from old codebase
|
||||
const BlinkDot: React.FC<{ color?: string }> = ({
|
||||
color = colors.tool.pending,
|
||||
}) => {
|
||||
const [on, setOn] = useState(true);
|
||||
useEffect(() => {
|
||||
const t = setInterval(() => setOn((v) => !v), 400);
|
||||
return () => clearInterval(t);
|
||||
}, []);
|
||||
// Visible = colored dot; Off = space (keeps width/alignment)
|
||||
return <Text color={color}>{on ? "●" : " "}</Text>;
|
||||
};
|
||||
|
||||
/**
|
||||
* ToolCallMessageRich - Rich formatting version with old layout logic
|
||||
* This preserves the exact wrapping and spacing logic from the old codebase
|
||||
@@ -49,63 +43,13 @@ export const ToolCallMessage = memo(({ line }: { line: ToolCallLine }) => {
|
||||
const rawName = line.name ?? "?";
|
||||
const argsText = line.argsText ?? "...";
|
||||
|
||||
// Task tool handles its own display via console.log - suppress UI rendering entirely
|
||||
if (rawName === "Task" || rawName === "task") {
|
||||
// Task tool - handled by SubagentGroupDisplay, don't render here
|
||||
if (isTaskTool(rawName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Apply tool name remapping from old codebase
|
||||
let displayName = rawName;
|
||||
// Anthropic toolset
|
||||
if (displayName === "write") displayName = "Write";
|
||||
else if (displayName === "edit" || displayName === "multi_edit")
|
||||
displayName = "Edit";
|
||||
else if (displayName === "read") displayName = "Read";
|
||||
else if (displayName === "bash") displayName = "Bash";
|
||||
else if (displayName === "grep") displayName = "Grep";
|
||||
else if (displayName === "glob") displayName = "Glob";
|
||||
else if (displayName === "ls") displayName = "LS";
|
||||
else if (displayName === "todo_write") displayName = "TODO";
|
||||
else if (displayName === "TodoWrite") displayName = "TODO";
|
||||
else if (displayName === "EnterPlanMode") displayName = "Planning";
|
||||
else if (displayName === "ExitPlanMode") displayName = "Planning";
|
||||
else if (displayName === "AskUserQuestion") displayName = "Question";
|
||||
// Codex toolset (snake_case)
|
||||
else if (displayName === "update_plan") displayName = "Planning";
|
||||
else if (displayName === "shell_command") displayName = "Shell";
|
||||
else if (displayName === "shell") displayName = "Shell";
|
||||
else if (displayName === "read_file") displayName = "Read";
|
||||
else if (displayName === "list_dir") displayName = "LS";
|
||||
else if (displayName === "grep_files") displayName = "Grep";
|
||||
else if (displayName === "apply_patch") displayName = "Patch";
|
||||
// Codex toolset (PascalCase)
|
||||
else if (displayName === "UpdatePlan") displayName = "Planning";
|
||||
else if (displayName === "ShellCommand") displayName = "Shell";
|
||||
else if (displayName === "Shell") displayName = "Shell";
|
||||
else if (displayName === "ReadFile") displayName = "Read";
|
||||
else if (displayName === "ListDir") displayName = "LS";
|
||||
else if (displayName === "GrepFiles") displayName = "Grep";
|
||||
else if (displayName === "ApplyPatch") displayName = "Patch";
|
||||
// Gemini toolset (snake_case)
|
||||
else if (displayName === "run_shell_command") displayName = "Shell";
|
||||
else if (displayName === "list_directory") displayName = "LS";
|
||||
else if (displayName === "search_file_content") displayName = "Grep";
|
||||
else if (displayName === "write_todos") displayName = "TODO";
|
||||
else if (displayName === "read_many_files") displayName = "Read Multiple";
|
||||
// Gemini toolset (PascalCase)
|
||||
else if (displayName === "RunShellCommand") displayName = "Shell";
|
||||
else if (displayName === "ListDirectory") displayName = "LS";
|
||||
else if (displayName === "SearchFileContent") displayName = "Grep";
|
||||
else if (displayName === "WriteTodos") displayName = "TODO";
|
||||
else if (displayName === "ReadManyFiles") displayName = "Read Multiple";
|
||||
// Additional tools
|
||||
else if (displayName === "Replace" || displayName === "replace")
|
||||
displayName = "Edit";
|
||||
else if (displayName === "WriteFile" || displayName === "write_file")
|
||||
displayName = "Write";
|
||||
else if (displayName === "KillBash") displayName = "Kill Shell";
|
||||
else if (displayName === "BashOutput") displayName = "Shell Output";
|
||||
else if (displayName === "MultiEdit") displayName = "Edit";
|
||||
// Apply tool name remapping
|
||||
const displayName = getDisplayToolName(rawName);
|
||||
|
||||
// Format arguments for display using the old formatting logic
|
||||
const formatted = formatArgsDisplay(argsText);
|
||||
@@ -182,14 +126,11 @@ export const ToolCallMessage = memo(({ line }: { line: ToolCallLine }) => {
|
||||
typeof v === "object" && v !== null;
|
||||
|
||||
// Check if this is a todo_write tool with successful result
|
||||
const isTodoTool =
|
||||
rawName === "todo_write" ||
|
||||
rawName === "TodoWrite" ||
|
||||
rawName === "write_todos" ||
|
||||
rawName === "WriteTodos" ||
|
||||
displayName === "TODO";
|
||||
|
||||
if (isTodoTool && line.resultOk !== false && line.argsText) {
|
||||
if (
|
||||
isTodoTool(rawName, displayName) &&
|
||||
line.resultOk !== false &&
|
||||
line.argsText
|
||||
) {
|
||||
try {
|
||||
const parsedArgs = JSON.parse(line.argsText);
|
||||
if (parsedArgs.todos && Array.isArray(parsedArgs.todos)) {
|
||||
@@ -225,12 +166,11 @@ export const ToolCallMessage = memo(({ line }: { line: ToolCallLine }) => {
|
||||
}
|
||||
|
||||
// Check if this is an update_plan tool with successful result
|
||||
const isPlanTool =
|
||||
rawName === "update_plan" ||
|
||||
rawName === "UpdatePlan" ||
|
||||
displayName === "Planning";
|
||||
|
||||
if (isPlanTool && line.resultOk !== false && line.argsText) {
|
||||
if (
|
||||
isPlanTool(rawName, displayName) &&
|
||||
line.resultOk !== false &&
|
||||
line.argsText
|
||||
) {
|
||||
try {
|
||||
const parsedArgs = JSON.parse(line.argsText);
|
||||
if (parsedArgs.plan && Array.isArray(parsedArgs.plan)) {
|
||||
|
||||
@@ -113,6 +113,17 @@ export const colors = {
|
||||
inProgress: brandColors.primaryAccent,
|
||||
},
|
||||
|
||||
// Subagent display
|
||||
subagent: {
|
||||
header: brandColors.primaryAccent,
|
||||
running: brandColors.statusWarning,
|
||||
completed: brandColors.statusSuccess,
|
||||
error: brandColors.statusError,
|
||||
treeChar: brandColors.textDisabled,
|
||||
stats: brandColors.textSecondary,
|
||||
hint: brandColors.textDisabled,
|
||||
},
|
||||
|
||||
// Info/modal views
|
||||
info: {
|
||||
border: brandColors.primaryAccent,
|
||||
|
||||
Reference in New Issue
Block a user