diff --git a/Cargo.lock b/Cargo.lock index 8014cbae..b04364d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11300,9 +11300,9 @@ checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] name = "unicode-segmentation" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da36089a805484bcccfffe0739803392c8298778a2d2f09febf76fac5ad9025b" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-width" diff --git a/deny.toml b/deny.toml index a4e5203d..3f45e55f 100644 --- a/deny.toml +++ b/deny.toml @@ -9,6 +9,5 @@ unsound = "none" # Ignore proc-macro-error unmaintained warning # See: https://github.com/block/goose/issues/7008 ignore = [ - "RUSTSEC-2024-0370", # proc-macro-error is unmaintained "RUSTSEC-2023-0071", # rsa: Marvin Attack timing sidechannel (no safe upgrade available, via jsonwebtoken) ] diff --git a/ui/text/src/toolcall.tsx b/ui/text/src/toolcall.tsx index e0fd61b8..38defdc5 100644 --- a/ui/text/src/toolcall.tsx +++ b/ui/text/src/toolcall.tsx @@ -171,7 +171,6 @@ export function buildToolCallCardLines( const bodyRows: Array<{ text: string; color?: string; italic?: boolean }> = []; const runningText = info.status === "in_progress" ? " running…" : ""; - const tabHint = hasTruncated && !expanded ? "tab ↔" : ""; bodyRows.push({ text: "__HEADER__" }); if (hasLocations) { @@ -220,7 +219,6 @@ export function buildToolCallCardLines( {info.title} {runningText ? {runningText} : null} - {tabHint ? {tabHint} : null} , @@ -252,28 +250,55 @@ export function ToolCallCompact({ info, indent, width, + keyPrefix, + showTabHint, }: { info: ToolCallInfo; indent: number; width: number; -}) { + keyPrefix: string; + showTabHint: boolean; +}): React.ReactNode[] { const statusInfo = STATUS_INDICATORS[info.status] ?? STATUS_INDICATORS.pending!; const kindIcon = KIND_ICONS[info.kind ?? "other"] ?? "⚙"; const summary = summarizeContent(info); - const maxSummaryWidth = width - indent - 12 - info.title.length; + const borderColor = info.status === "failed" ? CRANBERRY : CEDAR; + const dimBorder = info.status !== "failed"; + + const cardWidth = Math.min(width - indent - 2, 72); + const innerWidth = cardWidth - 2; + + const tabHintText = "tab ↔"; + const maxSummaryWidth = innerWidth - info.title.length - 8 - (showTabHint ? tabHintText.length + 2 : 0); const trimmedSummary = summary.length > maxSummaryWidth && maxSummaryWidth > 3 ? summary.slice(0, maxSummaryWidth - 1) + "…" : summary; - return ( - - {statusInfo.icon} - {kindIcon} - {info.title} - {trimmedSummary ? ( - — {trimmedSummary} - ) : null} - - ); + const topBorder = "╭" + "─".repeat(innerWidth) + "╮"; + const botBorder = "╰" + "─".repeat(innerWidth) + "╯"; + + return [ + + {topBorder} + , + + + + + {statusInfo.icon} + {kindIcon} + {info.title} + {trimmedSummary ? ( + — {trimmedSummary} + ) : null} + + {showTabHint && {tabHintText}} + + + , + + {botBorder} + , + ]; } diff --git a/ui/text/src/tui.tsx b/ui/text/src/tui.tsx index b644cb0d..57fc4d17 100644 --- a/ui/text/src/tui.tsx +++ b/ui/text/src/tui.tsx @@ -319,7 +319,7 @@ function buildTurnBodyLines({ spinIdx, pendingPermission, permissionIdx, - expandedToolCall, + toolCallsExpanded, }: { turn: Turn; width: number; @@ -328,32 +328,32 @@ function buildTurnBodyLines({ spinIdx: number; pendingPermission: PendingPermission | null; permissionIdx: number; - expandedToolCall: string | null; + toolCallsExpanded: boolean; }): React.ReactNode[] { const toolCallIds = turn.toolCallOrder; const toolCalls = turn.toolCalls; - const featuredId = findFeaturedToolCallId(toolCallIds, toolCalls); const lines: React.ReactNode[] = []; lines.push(); - for (const tcId of toolCallIds) { + for (let i = 0; i < toolCallIds.length; i++) { + const tcId = toolCallIds[i]!; const tc = toolCalls.get(tcId); if (!tc) continue; - if (tcId === featuredId || expandedToolCall === tcId) { - const cardLines = buildToolCallCardLines(tc, CONTENT_INDENT, width, expandedToolCall === tcId, `tc-${tcId}`); + if (toolCallsExpanded) { + const cardLines = buildToolCallCardLines(tc, CONTENT_INDENT, width, true, `tc-${tcId}`); lines.push(...cardLines); } else { - lines.push( - , - ); + const compactLines = ToolCallCompact({ + info: tc, + indent: CONTENT_INDENT, + width, + keyPrefix: `tc-${tcId}`, + showTabHint: i === 0, + }); + lines.push(...compactLines); } } @@ -566,7 +566,7 @@ function App({ const [queuedMessages, setQueuedMessages] = useState([]); const [viewTurnIdx, setViewTurnIdx] = useState(-1); - const [expandedToolCall, setExpandedToolCall] = useState(null); + const [toolCallsExpanded, setToolCallsExpanded] = useState(false); const [scrollOffset, setScrollOffset] = useState(0); const clientRef = useRef(null); @@ -589,7 +589,7 @@ function App({ }, [turns]); useEffect(() => { - setExpandedToolCall(null); + setToolCallsExpanded(false); setScrollOffset(0); }, [viewTurnIdx, turns.length]); @@ -684,7 +684,7 @@ function App({ }, ]); setViewTurnIdx(-1); - setExpandedToolCall(null); + setToolCallsExpanded(false); setScrollOffset(0); }, []); @@ -874,7 +874,7 @@ function App({ if (!trimmed) return; setInput(""); setViewTurnIdx(-1); - setExpandedToolCall(null); + setToolCallsExpanded(false); setScrollOffset(0); if (loading || isProcessingRef.current) { @@ -933,10 +933,7 @@ function App({ const currentTurn = turns[effectiveIdx]; if (!currentTurn || currentTurn.toolCallOrder.length === 0) return; - const featuredId = findFeaturedToolCallId(currentTurn.toolCallOrder, currentTurn.toolCalls); - if (!featuredId) return; - - setExpandedToolCall((prev) => (prev === featuredId ? null : featuredId)); + setToolCallsExpanded((prev) => !prev); return; } @@ -1019,7 +1016,7 @@ function App({ spinIdx, pendingPermission: isLatest ? pendingPermission : null, permissionIdx, - expandedToolCall, + toolCallsExpanded, }); const allBodyLines = isLatest