66
77import { spawn } from 'child_process' ;
88import { StringDecoder } from 'string_decoder' ;
9- import type { HistoryItemWithoutId } from '../types.js' ;
9+ import {
10+ HistoryItemWithoutId ,
11+ IndividualToolCallDisplay ,
12+ ToolCallStatus ,
13+ } from '../types.js' ;
1014import { useCallback } from 'react' ;
1115import { Config , GeminiClient } from '@google/gemini-cli-core' ;
1216import { type PartListUnion } from '@google/genai' ;
1317import { formatMemoryUsage } from '../utils/formatters.js' ;
1418import { isBinary } from '../utils/textUtils.js' ;
1519import { UseHistoryManagerReturn } from './useHistoryManager.js' ;
20+ import { SHELL_COMMAND_NAME } from '../constants.js' ;
1621import crypto from 'crypto' ;
1722import path from 'path' ;
1823import os from 'os' ;
@@ -204,6 +209,7 @@ ${modelContent}
204209 * Hook to process shell commands.
205210 * Orchestrates command execution and updates history and agent context.
206211 */
212+
207213export const useShellCommandProcessor = (
208214 addItemToHistory : UseHistoryManagerReturn [ 'addItem' ] ,
209215 setPendingHistoryItem : React . Dispatch <
@@ -221,6 +227,7 @@ export const useShellCommandProcessor = (
221227 }
222228
223229 const userMessageTimestamp = Date . now ( ) ;
230+ const callId = `shell-${ userMessageTimestamp } ` ;
224231 addItemToHistory (
225232 { type : 'user_shell' , text : rawQuery } ,
226233 userMessageTimestamp ,
@@ -246,6 +253,20 @@ export const useShellCommandProcessor = (
246253 const execPromise = new Promise < void > ( ( resolve ) => {
247254 let lastUpdateTime = 0 ;
248255
256+ const initialToolDisplay : IndividualToolCallDisplay = {
257+ callId,
258+ name : SHELL_COMMAND_NAME ,
259+ description : rawQuery ,
260+ status : ToolCallStatus . Executing ,
261+ resultDisplay : '' ,
262+ confirmationDetails : undefined ,
263+ } ;
264+
265+ setPendingHistoryItem ( {
266+ type : 'tool_group' ,
267+ tools : [ initialToolDisplay ] ,
268+ } ) ;
269+
249270 onDebugMessage ( `Executing in ${ targetDir } : ${ commandToExecute } ` ) ;
250271 executeShellCommand (
251272 commandToExecute ,
@@ -254,23 +275,22 @@ export const useShellCommandProcessor = (
254275 ( streamedOutput ) => {
255276 // Throttle pending UI updates to avoid excessive re-renders.
256277 if ( Date . now ( ) - lastUpdateTime > OUTPUT_UPDATE_INTERVAL_MS ) {
257- setPendingHistoryItem ( { type : 'info' , text : streamedOutput } ) ;
278+ setPendingHistoryItem ( {
279+ type : 'tool_group' ,
280+ tools : [
281+ { ...initialToolDisplay , resultDisplay : streamedOutput } ,
282+ ] ,
283+ } ) ;
258284 lastUpdateTime = Date . now ( ) ;
259285 }
260286 } ,
261287 onDebugMessage ,
262288 )
263289 . then ( ( result ) => {
264- // TODO(abhipatel12) - Consider updating pending item and using timeout to ensure
265- // there is no jump where intermediate output is skipped.
266290 setPendingHistoryItem ( null ) ;
267291
268- let historyItemType : HistoryItemWithoutId [ 'type' ] = 'info' ;
269292 let mainContent : string ;
270293
271- // The context sent to the model utilizes a text tokenizer which means raw binary data is
272- // cannot be parsed and understood and thus would only pollute the context window and waste
273- // tokens.
274294 if ( isBinary ( result . rawOutput ) ) {
275295 mainContent =
276296 '[Command produced binary output, which is not shown.]' ;
@@ -280,17 +300,19 @@ export const useShellCommandProcessor = (
280300 }
281301
282302 let finalOutput = mainContent ;
303+ let finalStatus = ToolCallStatus . Success ;
283304
284305 if ( result . error ) {
285- historyItemType = 'error' ;
306+ finalStatus = ToolCallStatus . Error ;
286307 finalOutput = `${ result . error . message } \n${ finalOutput } ` ;
287308 } else if ( result . aborted ) {
309+ finalStatus = ToolCallStatus . Canceled ;
288310 finalOutput = `Command was cancelled.\n${ finalOutput } ` ;
289311 } else if ( result . signal ) {
290- historyItemType = 'error' ;
312+ finalStatus = ToolCallStatus . Error ;
291313 finalOutput = `Command terminated by signal: ${ result . signal } .\n${ finalOutput } ` ;
292314 } else if ( result . exitCode !== 0 ) {
293- historyItemType = 'error' ;
315+ finalStatus = ToolCallStatus . Error ;
294316 finalOutput = `Command exited with code ${ result . exitCode } .\n${ finalOutput } ` ;
295317 }
296318
@@ -302,9 +324,18 @@ export const useShellCommandProcessor = (
302324 }
303325 }
304326
327+ const finalToolDisplay : IndividualToolCallDisplay = {
328+ ...initialToolDisplay ,
329+ status : finalStatus ,
330+ resultDisplay : finalOutput ,
331+ } ;
332+
305333 // Add the complete, contextual result to the local UI history.
306334 addItemToHistory (
307- { type : historyItemType , text : finalOutput } ,
335+ {
336+ type : 'tool_group' ,
337+ tools : [ finalToolDisplay ] ,
338+ } as HistoryItemWithoutId ,
308339 userMessageTimestamp ,
309340 ) ;
310341
0 commit comments