@@ -136,6 +136,8 @@ export namespace ACP {
136136 private eventAbort = new AbortController ( )
137137 private eventStarted = false
138138 private permissionQueues = new Map < string , Promise < void > > ( )
139+ private seenTools = new Set < string > ( )
140+ private bashSnapshots = new Map < string , string > ( )
139141 private permissionOptions : PermissionOption [ ] = [
140142 { optionId : "once" , kind : "allow_once" , name : "Allow once" } ,
141143 { optionId : "always" , kind : "allow_always" , name : "Always allow" } ,
@@ -288,6 +290,8 @@ export namespace ACP {
288290 if ( part . type === "tool" ) {
289291 switch ( part . state . status ) {
290292 case "pending" :
293+ this . seenTools . add ( part . callID )
294+ this . bashSnapshots . delete ( part . callID )
291295 await this . connection
292296 . sessionUpdate ( {
293297 sessionId,
@@ -306,7 +310,62 @@ export namespace ACP {
306310 } )
307311 return
308312
309- case "running" :
313+ case "running" : {
314+ // Emit synthetic pending if we haven't seen this tool yet
315+ if ( ! this . seenTools . has ( part . callID ) ) {
316+ this . seenTools . add ( part . callID )
317+ await this . connection
318+ . sessionUpdate ( {
319+ sessionId,
320+ update : {
321+ sessionUpdate : "tool_call" ,
322+ toolCallId : part . callID ,
323+ title : part . tool ,
324+ kind : toToolKind ( part . tool ) ,
325+ status : "pending" ,
326+ locations : [ ] ,
327+ rawInput : { } ,
328+ } ,
329+ } )
330+ . catch ( ( error ) => {
331+ log . error ( "failed to send synthetic tool pending to ACP" , { error } )
332+ } )
333+ }
334+
335+ // Extract bash output from metadata for streaming
336+ const metadata = part . state . metadata as Record < string , unknown > | undefined
337+ const bashOutput = metadata && typeof metadata . output === "string" ? metadata . output : undefined
338+ const content : ToolCallContent [ ] = [ ]
339+
340+ if ( bashOutput !== undefined ) {
341+ const lastSnapshot = this . bashSnapshots . get ( part . callID )
342+ if ( lastSnapshot === bashOutput ) {
343+ // De-duplicate: identical to last snapshot, send update without content
344+ await this . connection
345+ . sessionUpdate ( {
346+ sessionId,
347+ update : {
348+ sessionUpdate : "tool_call_update" ,
349+ toolCallId : part . callID ,
350+ status : "in_progress" ,
351+ kind : toToolKind ( part . tool ) ,
352+ title : part . tool ,
353+ locations : toLocations ( part . tool , part . state . input ) ,
354+ rawInput : part . state . input ,
355+ } ,
356+ } )
357+ . catch ( ( error ) => {
358+ log . error ( "failed to send tool in_progress to ACP" , { error } )
359+ } )
360+ return
361+ }
362+ this . bashSnapshots . set ( part . callID , bashOutput )
363+ content . push ( {
364+ type : "content" ,
365+ content : { type : "text" , text : bashOutput } ,
366+ } )
367+ }
368+
310369 await this . connection
311370 . sessionUpdate ( {
312371 sessionId,
@@ -318,12 +377,14 @@ export namespace ACP {
318377 title : part . tool ,
319378 locations : toLocations ( part . tool , part . state . input ) ,
320379 rawInput : part . state . input ,
380+ ...( content . length > 0 && { content } ) ,
321381 } ,
322382 } )
323383 . catch ( ( error ) => {
324384 log . error ( "failed to send tool in_progress to ACP" , { error } )
325385 } )
326386 return
387+ }
327388
328389 case "completed" : {
329390 const kind = toToolKind ( part . tool )
@@ -802,6 +863,8 @@ export namespace ACP {
802863 if ( part . type === "tool" ) {
803864 switch ( part . state . status ) {
804865 case "pending" :
866+ this . seenTools . add ( part . callID )
867+ this . bashSnapshots . delete ( part . callID )
805868 await this . connection
806869 . sessionUpdate ( {
807870 sessionId,
@@ -819,7 +882,60 @@ export namespace ACP {
819882 log . error ( "failed to send tool pending to ACP" , { error : err } )
820883 } )
821884 break
822- case "running" :
885+ case "running" : {
886+ // Emit synthetic pending if we haven't seen this tool yet
887+ if ( ! this . seenTools . has ( part . callID ) ) {
888+ this . seenTools . add ( part . callID )
889+ await this . connection
890+ . sessionUpdate ( {
891+ sessionId,
892+ update : {
893+ sessionUpdate : "tool_call" ,
894+ toolCallId : part . callID ,
895+ title : part . tool ,
896+ kind : toToolKind ( part . tool ) ,
897+ status : "pending" ,
898+ locations : [ ] ,
899+ rawInput : { } ,
900+ } ,
901+ } )
902+ . catch ( ( err ) => {
903+ log . error ( "failed to send synthetic tool pending to ACP" , { error : err } )
904+ } )
905+ }
906+
907+ const metadata = part . state . metadata as Record < string , unknown > | undefined
908+ const bashOutput = metadata && typeof metadata . output === "string" ? metadata . output : undefined
909+ const content : ToolCallContent [ ] = [ ]
910+
911+ if ( bashOutput !== undefined ) {
912+ const lastSnapshot = this . bashSnapshots . get ( part . callID )
913+ if ( lastSnapshot === bashOutput ) {
914+ await this . connection
915+ . sessionUpdate ( {
916+ sessionId,
917+ update : {
918+ sessionUpdate : "tool_call_update" ,
919+ toolCallId : part . callID ,
920+ status : "in_progress" ,
921+ kind : toToolKind ( part . tool ) ,
922+ title : part . tool ,
923+ locations : toLocations ( part . tool , part . state . input ) ,
924+ rawInput : part . state . input ,
925+ } ,
926+ } )
927+ . catch ( ( err ) => {
928+ log . error ( "failed to send tool in_progress to ACP" , { error : err } )
929+ } )
930+ break
931+ }
932+ this . bashSnapshots . set ( part . callID , bashOutput )
933+ content . push ( {
934+ type : "content" ,
935+ content : { type : "text" , text : bashOutput } ,
936+ } )
937+ }
938+
823939 await this . connection
824940 . sessionUpdate ( {
825941 sessionId,
@@ -831,12 +947,14 @@ export namespace ACP {
831947 title : part . tool ,
832948 locations : toLocations ( part . tool , part . state . input ) ,
833949 rawInput : part . state . input ,
950+ ...( content . length > 0 && { content } ) ,
834951 } ,
835952 } )
836953 . catch ( ( err ) => {
837954 log . error ( "failed to send tool in_progress to ACP" , { error : err } )
838955 } )
839956 break
957+ }
840958 case "completed" :
841959 const kind = toToolKind ( part . tool )
842960 const content : ToolCallContent [ ] = [
0 commit comments