diff --git a/components/frontend/src/components/ui/tool-message.tsx b/components/frontend/src/components/ui/tool-message.tsx index 512fd259b..7c876ff19 100644 --- a/components/frontend/src/components/ui/tool-message.tsx +++ b/components/frontend/src/components/ui/tool-message.tsx @@ -11,6 +11,7 @@ import { Check, X, Cog, + ListTodo, } from "lucide-react"; import ReactMarkdown from "react-markdown"; import type { Components } from "react-markdown"; @@ -26,6 +27,76 @@ export type ToolMessageProps = { timestamp?: string; }; +// TodoWrite types and helpers +type TodoItem = { + id?: string; + content: string; + status: "pending" | "in_progress" | "completed"; + priority?: "high" | "medium" | "low"; +}; + +const parseTodoItems = (input?: Record): TodoItem[] | null => { + if (!input) return null; + const todos = input.todos; + if (!Array.isArray(todos) || todos.length === 0) return null; + return todos.filter( + (item): item is TodoItem => + item != null && + typeof item === "object" && + typeof (item as Record).content === "string" && + typeof (item as Record).status === "string" + ); +}; + +const isTodoWriteTool = (toolName: string) => + toolName.toLowerCase() === "todowrite"; + +const TodoListView: React.FC<{ todos: TodoItem[] }> = ({ todos }) => ( +
+ {todos.map((todo, idx) => ( +
+
+ {todo.status === "completed" && ( + + )} + {todo.status === "in_progress" && ( + + )} + {todo.status === "pending" && ( +
+ )} + {todo.status !== "completed" && todo.status !== "in_progress" && todo.status !== "pending" && ( +
+ )} +
+ + {todo.content} + + {todo.priority && ( + + {todo.priority} + + )} +
+ ))} +
+); + const formatToolName = (toolName?: string) => { if (!toolName) return "Unknown Tool"; // Remove mcp__ prefix and format nicely @@ -308,6 +379,22 @@ const extractTextFromResultContent = (content: unknown): string => { const generateToolSummary = (toolName: string, input?: Record): string => { if (!input || Object.keys(input).length === 0) return formatToolName(toolName); + // TodoWrite - summarize task counts by status + if (isTodoWriteTool(toolName)) { + const todos = parseTodoItems(input); + if (todos) { + const total = todos.length; + const completed = todos.filter((t) => t.status === "completed").length; + const inProgress = todos.filter((t) => t.status === "in_progress").length; + const pending = todos.filter((t) => t.status === "pending").length; + const parts: string[] = []; + if (completed > 0) parts.push(`${completed} done`); + if (inProgress > 0) parts.push(`${inProgress} in progress`); + if (pending > 0) parts.push(`${pending} pending`); + return `${total} task${total !== 1 ? "s" : ""}${parts.length > 0 ? `: ${parts.join(", ")}` : ""}`; + } + } + // AskUserQuestion - show first question text if (toolName.toLowerCase().replace(/[^a-z]/g, "") === "askuserquestion") { const questions = input.questions as Array<{ question: string }> | undefined; @@ -318,7 +405,6 @@ const generateToolSummary = (toolName: string, input?: Record): return "Asking a question"; } - // WebSearch - show query if (toolName.toLowerCase().includes("websearch") || toolName.toLowerCase().includes("web_search")) { const query = input.query as string | undefined; @@ -537,6 +623,10 @@ export const ToolMessage = React.forwardRef( {getInitials(subagentType)}
+ ) : isTodoWriteTool(toolUseBlock?.name ?? "") ? ( +
+ +
) : (
@@ -701,7 +791,25 @@ export const ToolMessage = React.forwardRef( // Default tool rendering (existing behavior) isExpanded && (
- {toolUseBlock?.input && ( + {/* TodoWrite: render structured task list */} + {isTodoWriteTool(toolUseBlock?.name ?? "") && (() => { + const todos = parseTodoItems(inputData); + if (!todos) return null; + return ( +
+

+ + Tasks +

+
+ +
+
+ ); + })()} + + {/* Generic input for non-TodoWrite tools */} + {toolUseBlock?.input && !isTodoWriteTool(toolUseBlock.name) && (

Input