diff --git a/packages/cli/src/ui/components/messages/DenseToolMessage.tsx b/packages/cli/src/ui/components/messages/DenseToolMessage.tsx index d127b75e91..272b1f34fc 100644 --- a/packages/cli/src/ui/components/messages/DenseToolMessage.tsx +++ b/packages/cli/src/ui/components/messages/DenseToolMessage.tsx @@ -17,6 +17,10 @@ import { isGrepResult, isListResult, isReadManyFilesResult, + UPDATE_TOPIC_DISPLAY_NAME, + UPDATE_TOPIC_TOOL_NAME, + TOPIC_PARAM_TITLE, + TOPIC_PARAM_STRATEGIC_INTENT, } from '@google/gemini-cli-core'; import { type IndividualToolCallDisplay, isTodoList } from '../../types.js'; import { useAlternateBuffer } from '../../hooks/useAlternateBuffer.js'; @@ -306,10 +310,33 @@ function getGenericSuccessData( return { description, summary, payload }; } +function getUpdateTopicData(args?: Record): ViewParts { + const rawTitle = args?.[TOPIC_PARAM_TITLE]; + const title = typeof rawTitle === 'string' ? rawTitle : undefined; + const rawIntent = args?.[TOPIC_PARAM_STRATEGIC_INTENT]; + const intent = typeof rawIntent === 'string' ? rawIntent : undefined; + + const description = ( + + {title || 'Topic'} + + ); + + const summary = intent ? ( + + {' '} + -- {intent} + + ) : undefined; + + return { description, summary }; +} + export const DenseToolMessage: React.FC = (props) => { const { callId, name, + args, status, resultDisplay, confirmationDetails, @@ -323,6 +350,8 @@ export const DenseToolMessage: React.FC = (props) => { const isAlternateBuffer = useAlternateBuffer(); const { isExpanded: isExpandedInContext, toggleExpansion } = useToolActions(); + // ... (rest of component) + // Handle optional context members const [localIsExpanded, setLocalIsExpanded] = useState(false); const isExpanded = isExpandedInContext @@ -370,6 +399,9 @@ export const DenseToolMessage: React.FC = (props) => { // State-to-View Coordination const viewParts = useMemo((): ViewParts => { + if (name === UPDATE_TOPIC_DISPLAY_NAME || name === UPDATE_TOPIC_TOOL_NAME) { + return getUpdateTopicData(args); + } if (diff) { return getFileOpData( diff, @@ -431,6 +463,8 @@ export const DenseToolMessage: React.FC = (props) => { availableTerminalHeight, originalDescription, isAlternateBuffer, + name, + args, ]); const { description, summary } = viewParts; @@ -497,25 +531,42 @@ export const DenseToolMessage: React.FC = (props) => { return ( - - - - - {name}{' '} - - - - {description} - - {summary && ( - - {summary} - + + {name === UPDATE_TOPIC_DISPLAY_NAME || + name === UPDATE_TOPIC_TOOL_NAME ? ( + <> + + {description} + + {summary && ( + + {summary} + + )} + + ) : ( + <> + + + + {name}{' '} + + + + {description} + + {summary && ( + + {summary} + + )} + )} diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx index df0b9e4e4b..5240e993f2 100644 --- a/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx +++ b/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx @@ -22,6 +22,9 @@ import { EDIT_DISPLAY_NAME, READ_FILE_DISPLAY_NAME, GLOB_DISPLAY_NAME, + UPDATE_TOPIC_DISPLAY_NAME, + TOPIC_PARAM_TITLE, + TOPIC_PARAM_STRATEGIC_INTENT, } from '@google/gemini-cli-core'; import os from 'node:os'; import { createMockSettings } from '../../../test-utils/settings.js'; @@ -464,6 +467,60 @@ describe('', () => { unmount(); }); + describe('UpdateTopicTool', () => { + it('renders update_topic with Title -- Description format even when compact mode is disabled', async () => { + const toolCalls = [ + createToolCall({ + name: UPDATE_TOPIC_DISPLAY_NAME, + args: { + [TOPIC_PARAM_TITLE]: 'Research', + [TOPIC_PARAM_STRATEGIC_INTENT]: 'Researching Agent Skills', + }, + }), + ]; + const item = createItem(toolCalls); + const { lastFrame, unmount } = await renderWithProviders( + , + { + config: baseMockConfig, + settings: createMockSettings({ + ui: { compactToolOutput: false }, + }), + }, + ); + + const output = lastFrame(); + expect(output).toContain('Research'); + expect(output).toContain('-- Researching Agent Skills'); + expect(output).not.toContain('update_topic'); + unmount(); + }); + + it('renders update_topic with Topic -- Description format when title is missing', async () => { + const toolCalls = [ + createToolCall({ + name: UPDATE_TOPIC_DISPLAY_NAME, + args: { + [TOPIC_PARAM_STRATEGIC_INTENT]: 'Tactical update', + }, + }), + ]; + const item = createItem(toolCalls); + const { lastFrame, unmount } = await renderWithProviders( + , + { + config: baseMockConfig, + settings: fullVerbositySettings, + }, + ); + + const output = lastFrame(); + expect(output).toContain('Topic'); + expect(output).toContain('-- Tactical update'); + unmount(); + }); + }); + it('renders two tool groups where only the last line of the previous group is visible', async () => { const toolCalls1 = [ createToolCall({ diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx index b0c8ae4ea7..e23328c965 100644 --- a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx @@ -33,6 +33,8 @@ import { WEB_FETCH_DISPLAY_NAME, WRITE_FILE_DISPLAY_NAME, READ_MANY_FILES_DISPLAY_NAME, + UPDATE_TOPIC_DISPLAY_NAME, + UPDATE_TOPIC_TOOL_NAME, isFileDiff, isGrepResult, isListResult, @@ -51,6 +53,8 @@ const COMPACT_OUTPUT_ALLOWLIST = new Set([ WEB_FETCH_DISPLAY_NAME, WRITE_FILE_DISPLAY_NAME, READ_MANY_FILES_DISPLAY_NAME, + UPDATE_TOPIC_DISPLAY_NAME, + UPDATE_TOPIC_TOOL_NAME, ]); // Helper to identify if a tool should use the compact view @@ -60,6 +64,15 @@ export const isCompactTool = ( ): boolean => { const hasCompactOutputSupport = COMPACT_OUTPUT_ALLOWLIST.has(tool.name); const displayStatus = mapCoreStatusToDisplayStatus(tool.status); + + // update_topic always uses the dense/compact representation + if ( + tool.name === UPDATE_TOPIC_DISPLAY_NAME || + tool.name === UPDATE_TOPIC_TOOL_NAME + ) { + return displayStatus !== ToolCallStatus.Confirming; + } + return ( isCompactModeEnabled && hasCompactOutputSupport && diff --git a/packages/cli/src/ui/hooks/toolMapping.ts b/packages/cli/src/ui/hooks/toolMapping.ts index e06ebf5bb5..01fef7f95a 100644 --- a/packages/cli/src/ui/hooks/toolMapping.ts +++ b/packages/cli/src/ui/hooks/toolMapping.ts @@ -51,6 +51,7 @@ export function mapToDisplay( parentCallId: call.request.parentCallId, name: displayName, description, + args: call.request.args, renderOutputAsMarkdown, }; diff --git a/packages/cli/src/ui/types.ts b/packages/cli/src/ui/types.ts index f973295cd8..e781342b22 100644 --- a/packages/cli/src/ui/types.ts +++ b/packages/cli/src/ui/types.ts @@ -119,6 +119,7 @@ export interface IndividualToolCallDisplay { parentCallId?: string; name: string; description: string; + args?: Record; resultDisplay: ToolResultDisplay | undefined; status: CoreToolCallStatus; // True when the tool was initiated directly by the user (slash/@/shell flows). diff --git a/packages/core/src/tools/tool-names.ts b/packages/core/src/tools/tool-names.ts index f18680cea0..6c2b492382 100644 --- a/packages/core/src/tools/tool-names.ts +++ b/packages/core/src/tools/tool-names.ts @@ -193,6 +193,7 @@ export const GREP_DISPLAY_NAME = 'SearchText'; export const WEB_SEARCH_DISPLAY_NAME = 'GoogleSearch'; export const WEB_FETCH_DISPLAY_NAME = 'WebFetch'; export const READ_MANY_FILES_DISPLAY_NAME = 'ReadManyFiles'; +export const UPDATE_TOPIC_DISPLAY_NAME = 'Update Topic Context'; /** * Mapping of legacy tool names to their current names. diff --git a/packages/core/src/tools/topicTool.ts b/packages/core/src/tools/topicTool.ts index 51c7999fba..980d1cf712 100644 --- a/packages/core/src/tools/topicTool.ts +++ b/packages/core/src/tools/topicTool.ts @@ -10,6 +10,7 @@ import { TOPIC_PARAM_SUMMARY, TOPIC_PARAM_STRATEGIC_INTENT, } from './definitions/coreTools.js'; +import { UPDATE_TOPIC_DISPLAY_NAME } from './tool-names.js'; import { BaseDeclarativeTool, BaseToolInvocation, @@ -153,7 +154,7 @@ export class UpdateTopicTool extends BaseDeclarativeTool< const declaration = getUpdateTopicDeclaration(); super( UPDATE_TOPIC_TOOL_NAME, - 'Update Topic Context', + UPDATE_TOPIC_DISPLAY_NAME, declaration.description ?? '', Kind.Think, declaration.parametersJsonSchema,