import React, { useMemo } from 'react' import type { DeepImmutable } from 'src/types/utils.js' import { useElapsedTime } from '../../hooks/useElapsedTime.js' import type { KeyboardEvent } from '../../ink/events/keyboard-event.js' import { Box, Text, useTheme } from '../../ink.js' import { useKeybindings } from '../../keybindings/useKeybinding.js' import { getEmptyToolPermissionContext } from '../../Tool.js' import type { LocalAgentTaskState } from '../../tasks/LocalAgentTask/LocalAgentTask.js' import { getTools } from '../../tools.js' import { formatNumber } from '../../utils/format.js' import { extractTag } from '../../utils/messages.js' import { Byline } from '../design-system/Byline.js' import { Dialog } from '../design-system/Dialog.js' import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js' import { UserPlanMessage } from '../messages/UserPlanMessage.js' import { renderToolActivity } from './renderToolActivity.js' import { getTaskStatusColor, getTaskStatusIcon } from './taskStatusUtils.js' type Props = { agent: DeepImmutable onDone: () => void onKillAgent?: () => void onBack?: () => void } export function AsyncAgentDetailDialog({ agent, onDone, onKillAgent, onBack, }: Props): React.ReactNode { const [theme] = useTheme() // Get tools for rendering activity messages const tools = useMemo(() => getTools(getEmptyToolPermissionContext()), []) const elapsedTime = useElapsedTime( agent.startTime, agent.status === 'running', 1000, agent.totalPausedMs ?? 0, ) // Restore confirm:yes (Enter/y) dismissal — Dialog handles confirm:no (Esc) // internally but does NOT auto-wire confirm:yes. useKeybindings( { 'confirm:yes': onDone, }, { context: 'Confirmation' }, ) // Component-specific shortcuts shown in UI hints (x=stop) and // navigation keys (space=dismiss, left=back). These are context-dependent // actions tied to agent state, not standard dialog keybindings. // Note: Dialog component already handles ESC via confirm:no keybinding; // confirm:yes (Enter/y) is handled by useKeybindings above. const handleKeyDown = (e: KeyboardEvent) => { if (e.key === ' ') { e.preventDefault() onDone() } else if (e.key === 'left' && onBack) { e.preventDefault() onBack() } else if (e.key === 'x' && agent.status === 'running' && onKillAgent) { e.preventDefault() onKillAgent() } } // Extract plan from prompt - if present, we show the plan instead of the prompt const planContent = extractTag(agent.prompt, 'plan') const displayPrompt = agent.prompt.length > 300 ? agent.prompt.substring(0, 297) + '…' : agent.prompt // Get tokens and tool uses (from result if completed, otherwise from progress) const tokenCount = agent.result?.totalTokens ?? agent.progress?.tokenCount const toolUseCount = agent.result?.totalToolUseCount ?? agent.progress?.toolUseCount const title = ( {agent.selectedAgent?.agentType ?? 'agent'} ›{' '} {agent.description || 'Async agent'} ) // Build subtitle with status and stats const subtitle = ( {agent.status !== 'running' && ( {getTaskStatusIcon(agent.status)}{' '} {agent.status === 'completed' ? 'Completed' : agent.status === 'failed' ? 'Failed' : 'Stopped'} {' · '} )} {elapsedTime} {tokenCount !== undefined && tokenCount > 0 && ( <> · {formatNumber(tokenCount)} tokens )} {toolUseCount !== undefined && toolUseCount > 0 && ( <> {' '} · {toolUseCount} {toolUseCount === 1 ? 'tool' : 'tools'} )} ) return ( exitState.pending ? ( Press {exitState.keyName} again to exit ) : ( {onBack && } {agent.status === 'running' && onKillAgent && ( )} ) } > {/* Recent activities for running agents */} {agent.status === 'running' && agent.progress?.recentActivities && agent.progress.recentActivities.length > 0 && ( Progress {agent.progress.recentActivities.map((activity, i) => ( {i === agent.progress!.recentActivities!.length - 1 ? '› ' : ' '} {renderToolActivity(activity, tools, theme)} ))} )} {/* Plan section (if present) - shown instead of prompt */} {planContent ? ( ) : ( /* Prompt section - only shown when no plan */ Prompt {displayPrompt} )} {/* Error details if failed */} {agent.status === 'failed' && agent.error && ( Error {agent.error} )} ) }