Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Text } from 'ink';
import {
CompactToolGroupDisplay,
buildToolSummary,
estimateCompactToolGroupHeight,
isCollapsibleTool,
} from './CompactToolGroupDisplay.js';
import { ToolCallStatus } from '../../types.js';
Expand Down Expand Up @@ -147,6 +148,20 @@ describe('<CompactToolGroupDisplay /> — summary label', () => {
);
expect(lastFrame()).toContain('Ran ls -la');
});

it('wraps long summaries instead of truncating them', () => {
const description =
'packages/cli/src/ui/components/messages/CompactToolGroupDisplay.tsx';
const tool = toolCall({ name: 'ReadFile', description });
const { lastFrame } = render(
<CompactToolGroupDisplay toolCalls={[tool]} contentWidth={30} />,
);
const frame = lastFrame()!;

expect(frame.split('\n').length).toBeGreaterThan(1);
expect(frame).not.toContain('…');
expect(frame.replace(/\s/g, '')).toContain(`Read${description}`);
});
});

describe('buildToolSummary', () => {
Expand Down Expand Up @@ -294,6 +309,24 @@ describe('buildToolSummary', () => {
});
});

describe('estimateCompactToolGroupHeight', () => {
it('returns 0 when there are no tool calls', () => {
expect(estimateCompactToolGroupHeight([], 80)).toBe(0);
Comment thread
han-dreamer marked this conversation as resolved.
});

it('returns 1 for summaries that fit on one line', () => {
expect(estimateCompactToolGroupHeight([toolCall()], 80)).toBe(1);
});

it('accounts for wrapped long summaries', () => {
const description =
'packages/cli/src/ui/components/messages/CompactToolGroupDisplay.tsx';
const tool = toolCall({ name: 'ReadFile', description });

expect(estimateCompactToolGroupHeight([tool], 30)).toBeGreaterThan(1);
});
});

describe('isCollapsibleTool', () => {
it('returns true for read/search/list tools', () => {
expect(isCollapsibleTool('ReadFile')).toBe(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,20 @@ import type { AnsiOutputDisplay } from '@qwen-code/qwen-code-core';
import { ToolDisplayNames } from '@qwen-code/qwen-code-core';
import { t } from '../../../i18n/index.js';
import { SHELL_COMMAND_NAME } from '../../constants.js';
import { ToolStatusIndicator } from '../shared/ToolStatusIndicator.js';
import {
STATUS_INDICATOR_WIDTH,
ToolStatusIndicator,
} from '../shared/ToolStatusIndicator.js';
import { ToolElapsedTime } from '../shared/ToolElapsedTime.js';

interface CompactToolGroupDisplayProps {
toolCalls: IndividualToolCallDisplay[];
contentWidth: number;
}

const COMPACT_GROUP_HORIZONTAL_PADDING = 2;
const ACTIVE_ELAPSED_TIME_RESERVED_WIDTH = 4;

// Priority: Confirming > Executing > Error > Canceled > Pending > Success
export function getOverallStatus(
toolCalls: IndividualToolCallDisplay[],
Expand Down Expand Up @@ -312,6 +318,29 @@ export function buildToolSummary(
return parts.join(', ');
}

export function estimateCompactToolGroupHeight(
toolCalls: IndividualToolCallDisplay[],
contentWidth: number,
): number {
if (toolCalls.length === 0) return 0;

const overallStatus = getOverallStatus(toolCalls);
const isActive =
overallStatus === ToolCallStatus.Executing ||
overallStatus === ToolCallStatus.Pending ||
overallStatus === ToolCallStatus.Confirming;
Comment thread
han-dreamer marked this conversation as resolved.
Outdated
const summary = `${buildToolSummary(toolCalls, isActive)}${isActive ? '…' : ''}`;
const summaryWidth = Math.max(
1,
contentWidth -
COMPACT_GROUP_HORIZONTAL_PADDING -
STATUS_INDICATOR_WIDTH -
(isActive ? ACTIVE_ELAPSED_TIME_RESERVED_WIDTH : 0),
);

return Math.max(1, Math.ceil(Array.from(summary).length / summaryWidth));
Comment thread
han-dreamer marked this conversation as resolved.
Outdated
}

export const CompactToolGroupDisplay: React.FC<
CompactToolGroupDisplayProps
> = ({ toolCalls, contentWidth }) => {
Expand All @@ -329,7 +358,7 @@ export const CompactToolGroupDisplay: React.FC<
<Box flexDirection="row">
<ToolStatusIndicator status={overallStatus} name={activeTool.name} />
<Box flexGrow={1}>
<Text wrap="truncate-end" bold>
<Text wrap="wrap" bold>
Comment thread
han-dreamer marked this conversation as resolved.
{buildToolSummary(toolCalls, isActive)}
{isActive && <Text key="ellipsis">…</Text>}
</Text>
Expand Down
34 changes: 34 additions & 0 deletions packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,40 @@ describe('<ToolGroupMessage />', () => {
);
expect(lastFrame()).toMatchSnapshot();
});

it('reserves wrapped compact summary height before sizing tool results', () => {
vi.mocked(ToolMessage).mockClear();
const toolCalls = [
createToolCall({
callId: 'read-long',
name: 'ReadFile',
description:
'packages/cli/src/ui/components/messages/CompactToolGroupDisplay.tsx',
status: ToolCallStatus.Success,
}),
createToolCall({
callId: 'shell-result',
name: 'Shell',
description: 'npm test',
status: ToolCallStatus.Success,
resultDisplay: 'shell output',
}),
];

renderWithProviders(
<ToolGroupMessage
{...baseProps}
contentWidth={30}
toolCalls={toolCalls}
availableTerminalHeight={12}
/>,
);

const call = vi
.mocked(ToolMessage)
.mock.calls.find((c) => c[0].callId === 'shell-result');
expect(call?.[0].availableTerminalHeight).toBe(8);
});
});

describe('Confirmation Handling', () => {
Expand Down
6 changes: 5 additions & 1 deletion packages/cli/src/ui/components/messages/ToolGroupMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ToolMessage } from './ToolMessage.js';
import { ToolConfirmationMessage } from './ToolConfirmationMessage.js';
import {
CompactToolGroupDisplay,
estimateCompactToolGroupHeight,
isCollapsibleTool,
} from './CompactToolGroupDisplay.js';
import { InlineParallelAgentsDisplay } from './InlineParallelAgentsDisplay.js';
Expand Down Expand Up @@ -432,7 +433,10 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
}

// Full expanded view for non-collapsible tools
const collapsibleSummaryHeight = collapsibleTools.length > 0 ? 1 : 0;
const collapsibleSummaryHeight = estimateCompactToolGroupHeight(
collapsibleTools,
contentWidth,
);
const memoryBadgeHeight = hasMemoryBadge ? 1 : 0;
const staticHeight =
/* marginBottom */ 1 + collapsibleSummaryHeight + memoryBadgeHeight;
Expand Down
Loading