/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import React from 'react'; import { Box, Text } from 'ink'; import { Colors } from '../../colors.js'; import { TaskResultDisplay } from '@qwen-code/qwen-code-core'; export interface SubagentExecutionDisplayProps { data: TaskResultDisplay; } /** * Component to display subagent execution progress and results. * This is now a pure component that renders the provided SubagentExecutionResultDisplay data. * Real-time updates are handled by the parent component updating the data prop. */ export const SubagentExecutionDisplay: React.FC< SubagentExecutionDisplayProps > = ({ data }) => ( {/* Header with subagent name and status */} {data.subagentName} {/* Task description */} Task: {data.taskDescription} {/* Progress section for running tasks */} {data.status === 'running' && ( )} {/* Results section for completed/failed tasks */} {(data.status === 'completed' || data.status === 'failed') && ( )} ); /** * Status dot component with similar height as text */ const StatusDot: React.FC<{ status: TaskResultDisplay['status']; }> = ({ status }) => { const color = React.useMemo(() => { switch (status) { case 'running': return Colors.AccentYellow; case 'completed': return Colors.AccentGreen; case 'failed': return Colors.AccentRed; default: return Colors.Gray; } }, [status]); return ( ); }; /** * Status indicator component */ const StatusIndicator: React.FC<{ status: TaskResultDisplay['status']; }> = ({ status }) => { switch (status) { case 'running': return Running; case 'completed': return Completed; case 'failed': return Failed; default: return Unknown; } }; /** * Progress section for running executions */ const ProgressSection: React.FC<{ progress: { toolCalls?: Array<{ name: string; status: 'executing' | 'success' | 'failed'; error?: string; args?: Record; result?: string; returnDisplay?: string; }>; }; }> = ({ progress }) => ( {progress.toolCalls && progress.toolCalls.length > 0 && ( )} ); /** * Clean tool calls list - format consistent with ToolInfo in ToolMessage.tsx */ const CleanToolCallsList: React.FC<{ toolCalls: Array<{ name: string; status: 'executing' | 'success' | 'failed'; error?: string; args?: Record; result?: string; returnDisplay?: string; }>; }> = ({ toolCalls }) => ( Tools: {toolCalls.map((toolCall, index) => ( ))} ); /** * Individual tool call item - consistent with ToolInfo format */ const CleanToolCallItem: React.FC<{ toolCall: { name: string; status: 'executing' | 'success' | 'failed'; error?: string; args?: Record; result?: string; returnDisplay?: string; }; }> = ({ toolCall }) => { const STATUS_INDICATOR_WIDTH = 3; // Map subagent status to ToolCallStatus-like display const statusIcon = React.useMemo(() => { switch (toolCall.status) { case 'executing': return ; // Using same as ToolMessage case 'success': return ; case 'failed': return ( x ); default: return o; } }, [toolCall.status]); const description = getToolDescription(toolCall); // Get first line of returnDisplay for truncated output const truncatedOutput = React.useMemo(() => { if (!toolCall.returnDisplay) return ''; const firstLine = toolCall.returnDisplay.split('\n')[0]; return firstLine.length > 80 ? firstLine.substring(0, 80) + '...' : firstLine; }, [toolCall.returnDisplay]); return ( {/* First line: status icon + tool name + description (consistent with ToolInfo) */} {statusIcon} {toolCall.name} {' '} {description} {toolCall.error && ( - {toolCall.error} )} {/* Second line: truncated returnDisplay output */} {truncatedOutput && ( {truncatedOutput} )} ); }; /** * Helper function to get tool description from args */ const getToolDescription = (toolCall: { name: string; args?: Record; }): string => { if (!toolCall.args) return ''; // Handle common tool patterns if (toolCall.name === 'Glob' && toolCall.args['glob_pattern']) { return `"${toolCall.args['glob_pattern']}"`; } if (toolCall.name === 'ReadFile' && toolCall.args['target_file']) { const path = toolCall.args['target_file'] as string; return path.split('/').pop() || path; } if (toolCall.name === 'SearchFileContent' && toolCall.args['pattern']) { return `"${toolCall.args['pattern']}"`; } // Generic fallback const firstArg = Object.values(toolCall.args)[0]; if (typeof firstArg === 'string' && firstArg.length < 50) { return firstArg; } return ''; }; /** * Execution summary details component */ const ExecutionSummaryDetails: React.FC<{ data: TaskResultDisplay; }> = ({ data }) => { // Parse execution summary for structured data const summaryData = React.useMemo(() => { if (!data.executionSummary) return null; // Try to extract structured data from execution summary const durationMatch = data.executionSummary.match(/Duration:\s*([^\n]+)/i); const roundsMatch = data.executionSummary.match(/Rounds:\s*(\d+)/i); const tokensMatch = data.executionSummary.match(/Tokens:\s*([\d,]+)/i); return { duration: durationMatch?.[1] || 'N/A', rounds: roundsMatch?.[1] || 'N/A', tokens: tokensMatch?.[1] || 'N/A', }; }, [data.executionSummary]); if (!summaryData) { return ( • No summary available ); } return ( Duration: {summaryData.duration} Rounds: {summaryData.rounds} Tokens: {summaryData.tokens} ); }; /** * Tool usage statistics component */ const ToolUsageStats: React.FC<{ toolCalls: Array<{ name: string; status: 'executing' | 'success' | 'failed'; error?: string; args?: Record; result?: string; returnDisplay?: string; }>; }> = ({ toolCalls }) => { const stats = React.useMemo(() => { const total = toolCalls.length; const successful = toolCalls.filter( (call) => call.status === 'success', ).length; const failed = toolCalls.filter((call) => call.status === 'failed').length; const successRate = total > 0 ? ((successful / total) * 100).toFixed(1) : '0.0'; return { total, successful, failed, successRate }; }, [toolCalls]); return ( Total Calls: {stats.total} Success Rate:{' '} {stats.successRate}% ( {stats.successful} success,{' '} {stats.failed} failed) ); }; /** * Results section for completed executions - matches the clean layout from the image */ const ResultsSection: React.FC<{ data: TaskResultDisplay; }> = ({ data }) => ( {/* Tool calls section - clean list format */} {data.progress?.toolCalls && data.progress.toolCalls.length > 0 && ( )} {/* Task Completed section */} 📄 Task Completed: {data.taskDescription} {/* Execution Summary section */} 📊 Execution Summary: {/* Tool Usage section */} {data.progress?.toolCalls && data.progress.toolCalls.length > 0 && ( 🔧 Tool Usage: )} {/* Error reason for failed tasks */} {data.status === 'failed' && data.terminateReason && ( ❌ Failed: {data.terminateReason} )} );