fix(ui): resolve rebase conflicts and type errors for ToolDisplay

This commit is contained in:
Michael Bleigh
2026-04-10 20:18:51 -07:00
parent fbc87675f0
commit 43f93c3cde
24 changed files with 276 additions and 38 deletions
@@ -24,6 +24,7 @@ import {
type ToolResultDisplay,
isTodoList,
} from '../../types.js';
import { isCompactTool } from './ToolGroupMessage.js';
import { useAlternateBuffer } from '../../hooks/useAlternateBuffer.js';
import { ToolStatusIndicator } from './ToolShared.js';
import { theme } from '../../semantic-colors.js';
@@ -286,6 +287,7 @@ export const DenseToolMessage: React.FC<DenseToolMessageProps> = (props) => {
const [isFocused, setIsFocused] = useState(false);
const toggleRef = useRef<DOMElement>(null);
const isActuallyCompact = useMemo(() => isCompactTool(props, true), [props]);
// Unified File Data Extraction (Safely bridge resultDisplay and confirmationDetails)
const diff = useMemo((): FileDiff | undefined => {
@@ -369,11 +371,36 @@ export const DenseToolMessage: React.FC<DenseToolMessageProps> = (props) => {
}
}
// If we have a display.result or a simple success, use it
// If we have a display.result, use it as the payload
let payload: React.ReactNode;
if (display.result) {
if (display.result.type === 'text') {
const text = display.result.text;
if (text) {
payload = (
<Text color={theme.text.secondary} wrap="truncate-end">
{text}
</Text>
);
}
}
// Step 5 will expand this to handle 'diff' type
}
// Compact tools should elide text payloads by default unless expanded.
if (
isActuallyCompact &&
!isExpanded &&
display.result?.type === 'text' &&
!isAlternateBuffer
) {
payload = undefined;
}
return {
description: descriptionText,
summary: summaryText,
payload: undefined, // Payload rendering will be updated in Step 5
payload,
};
}
@@ -439,6 +466,8 @@ export const DenseToolMessage: React.FC<DenseToolMessageProps> = (props) => {
originalDescription,
isAlternateBuffer,
display,
isActuallyCompact,
isExpanded,
]);
const { description, summary } = viewParts;
@@ -476,6 +505,10 @@ export const DenseToolMessage: React.FC<DenseToolMessageProps> = (props) => {
}, [diff, isExpanded, isAlternateBuffer, terminalWidth, settings, status]);
const showPayload = useMemo(() => {
// If we are using the new display protocol and it's a compact tool,
// hide the payload by default unless expanded.
if (display && isActuallyCompact && !isExpanded) return false;
const policy = !isAlternateBuffer || !diff || isExpanded;
if (!policy) return false;
@@ -495,6 +528,8 @@ export const DenseToolMessage: React.FC<DenseToolMessageProps> = (props) => {
diffLines.length,
viewParts.payload,
outputFile,
isActuallyCompact,
display,
]);
const keyExtractor = (_item: React.ReactNode, index: number) =>
@@ -505,7 +540,16 @@ export const DenseToolMessage: React.FC<DenseToolMessageProps> = (props) => {
return (
<Box flexDirection="column">
<Box marginLeft={2} flexDirection="row" flexWrap="wrap">
<Box
marginLeft={2}
flexDirection="row"
flexWrap="wrap"
ref={
isActuallyCompact || (isAlternateBuffer && diff)
? toggleRef
: undefined
}
>
<Box flexDirection="row" flexShrink={1}>
<ToolStatusIndicator status={status} name={name} />
<Box maxWidth={25} flexShrink={0} flexGrow={0}>
@@ -519,12 +563,7 @@ export const DenseToolMessage: React.FC<DenseToolMessageProps> = (props) => {
</Box>
{summary && (
<Box
key="tool-summary"
ref={isAlternateBuffer && diff ? toggleRef : undefined}
marginLeft={1}
flexGrow={0}
>
<Box key="tool-summary" marginLeft={1} flexGrow={0}>
{summary}
</Box>
)}
@@ -87,6 +87,11 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
resultDisplay,
);
const effectiveResultDisplay =
display?.resultSummary && !display.result
? display.resultSummary
: resultDisplay;
return (
// It is crucial we don't replace this <> with a Box because otherwise the
// sticky header inside it would be sticky to that box rather than to the
@@ -139,7 +144,7 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
/>
)}
<ToolResultDisplay
resultDisplay={resultDisplay}
resultDisplay={effectiveResultDisplay}
availableTerminalHeight={availableTerminalHeight}
terminalWidth={terminalWidth}
renderOutputAsMarkdown={renderOutputAsMarkdown}
+17 -1
View File
@@ -8,6 +8,7 @@ import {
type ToolCall,
type SerializableConfirmationDetails,
type ToolResultDisplay,
type ToolDisplay,
debugLogger,
CoreToolCallStatus,
type SubagentActivityItem,
@@ -35,10 +36,17 @@ export function mapToDisplay(
borderBottom?: boolean;
borderColor?: string;
borderDimColor?: boolean;
isAgentSessionInteractive?: boolean;
} = {},
): HistoryItemToolGroup {
const toolCalls = Array.isArray(toolOrTools) ? toolOrTools : [toolOrTools];
const { borderTop, borderBottom, borderColor, borderDimColor } = options;
const {
borderTop,
borderBottom,
borderColor,
borderDimColor,
isAgentSessionInteractive,
} = options;
const toolDisplays = toolCalls.map((call): IndividualToolCallDisplay => {
let description: string;
@@ -63,6 +71,7 @@ export function mapToDisplay(
};
let resultDisplay: ToolResultDisplay | undefined = undefined;
let display: ToolDisplay | undefined = undefined;
let confirmationDetails: SerializableConfirmationDetails | undefined =
undefined;
let outputFile: string | undefined = undefined;
@@ -75,11 +84,17 @@ export function mapToDisplay(
switch (call.status) {
case CoreToolCallStatus.Success:
resultDisplay = call.response.resultDisplay;
if (isAgentSessionInteractive) {
display = call.response.display;
}
outputFile = call.response.outputFile;
break;
case CoreToolCallStatus.Error:
case CoreToolCallStatus.Cancelled:
resultDisplay = call.response.resultDisplay;
if (isAgentSessionInteractive) {
display = call.response.display;
}
break;
case CoreToolCallStatus.AwaitingApproval:
correlationId = call.correlationId;
@@ -112,6 +127,7 @@ export function mapToDisplay(
status: call.status,
isClientInitiated: !!call.request.isClientInitiated,
kind: call.tool?.kind,
display,
resultDisplay,
confirmationDetails,
outputFile,
+6 -4
View File
@@ -224,9 +224,9 @@ export const useAgentStream = ({
else if (evtStatus === 'success')
status = CoreToolCallStatus.Success;
const display = event.display?.result;
const liveOutput =
displayContentToString(display) ?? tc.resultDisplay;
displayContentToString(event.display?.result) ??
tc.resultDisplay;
const progressMessage =
legacyState?.progressMessage ?? tc.progressMessage;
const progress = legacyState?.progress ?? tc.progress;
@@ -237,6 +237,7 @@ export const useAgentStream = ({
return {
...tc,
name: event.display?.name ?? tc.name,
status,
display: event.display
? { ...tc.display, ...event.display }
@@ -259,12 +260,13 @@ export const useAgentStream = ({
const legacyState = event._meta?.legacyState;
const outputFile = legacyState?.outputFile;
const display = event.display?.result;
const display = event.display;
const resultDisplay =
displayContentToString(display) ?? tc.resultDisplay;
displayContentToString(display?.result) ?? tc.resultDisplay;
return {
...tc,
name: display?.name ?? tc.name,
status: event.isError
? CoreToolCallStatus.Error
: CoreToolCallStatus.Success,