diff --git a/desktop/src/features/agents/ui/AgentSessionToolItem.tsx b/desktop/src/features/agents/ui/AgentSessionToolItem.tsx index 3ed633ac6..3de3478b1 100644 --- a/desktop/src/features/agents/ui/AgentSessionToolItem.tsx +++ b/desktop/src/features/agents/ui/AgentSessionToolItem.tsx @@ -15,6 +15,7 @@ import { formatToolTitle, getBuzzToolInfo, getToolStatusDisplay, + normalizeToolNameText, } from "./agentSessionToolCatalog"; import { asRecord, @@ -45,6 +46,8 @@ export function ToolItem({ const ToolIcon = buzzTool?.icon ?? Wrench; const showStatus = status.state !== "output-available"; const toolTitle = formatToolTitle(canonicalToolName, item.title); + const isCommandTool = isShellCommandTool(item); + const duration = getToolDuration(item); const handleToggle = React.useCallback( (event: React.SyntheticEvent) => { setIsExpanded(event.currentTarget.open); @@ -58,7 +61,7 @@ export function ToolItem({ "not-prose w-full", compact ? "px-0" : "px-1", isActive && - "rounded-lg border border-primary/15 bg-primary/[0.03] px-2 py-1", + "rounded-lg border border-primary/15 bg-primary/3 px-2 py-1", )} data-testid="transcript-tool-item" > @@ -67,43 +70,56 @@ export function ToolItem({ onToggle={handleToggle} open={isExpanded} > - - {ToolIcon ? ( - - ) : null} - - {toolTitle} - - {isActive ? ( - - - Live - - ) : null} - {buzzTool ? ( - - ) : null} - {showStatus ? ( - - - {status.label} - - ) : null} - - + + {isCommandTool ? ( + + ) : ( + <> + {ToolIcon ? ( + + ) : null} + + {toolTitle} + + {isActive ? ( + + + Live + + ) : null} + {buzzTool ? ( + + ) : null} + {showStatus ? ( + + + {status.label} + + ) : null} + + + + )} ; +}) { + const command = getToolString(item.args, ["command"]); + const label = + item.status === "failed" || item.isError + ? "Command failed" + : item.status === "executing" || item.status === "pending" + ? "Running command" + : "Ran command"; + + return ( + <> + {label} + {command ? ( + + {command} + + ) : null} + {duration ? ( + + {duration} + + ) : null} + + + ); +} + +function isShellCommandTool(item: Extract) { + return [item.buzzToolName, item.toolName, item.title].some((value) => { + if (!value) return false; + const normalized = normalizeToolNameText(value); + return normalized === "shell" || normalized.endsWith("_shell"); + }); +} + +function getToolDuration(item: Extract) { + if (item.startedAt && item.completedAt) { + return formatDuration(item.startedAt, item.completedAt); + } + + const resultRecord = asRecord(parseToolResultValue(item.result)); + const durationMs = + getToolNumber(resultRecord, ["duration_ms", "durationMs"]) ?? + getToolNumber(resultRecord, ["elapsed_ms", "elapsedMs"]); + return durationMs == null ? null : formatDurationMs(durationMs); +} + +function getToolNumber( + record: Record, + keys: string[], +): number | null { + for (const key of keys) { + const value = record[key]; + if (typeof value === "number" && Number.isFinite(value)) { + return value; + } + } + return null; +} + +function formatDurationMs(ms: number) { + if (ms < 0) return null; + const totalSeconds = ms / 1000; + if (totalSeconds < 60) { + return totalSeconds < 10 + ? `${totalSeconds.toFixed(1)}s` + : `${Math.round(totalSeconds)}s`; + } + let minutes = Math.floor(totalSeconds / 60); + let seconds = Math.round(totalSeconds % 60); + if (seconds === 60) { + minutes += 1; + seconds = 0; + } + return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`; +} + function ToolDetailBlocks({ args, description, @@ -180,7 +282,7 @@ function ToolCodeBlock({
;
 }) {
   const time = formatTranscriptTime(item.timestamp);
   if (!time) return null;
-  const duration =
-    item.startedAt && item.completedAt
-      ? formatDuration(item.startedAt, item.completedAt)
-      : null;
   const date = new Date(item.timestamp);
   const fullDateTime = Number.isNaN(date.getTime())
     ? item.timestamp
@@ -282,7 +382,7 @@ function BuzzToolInlineAction({
   if (action.onClick) {
     return (