@@ -122,6 +122,7 @@ public void onToolUse(String toolId, String toolName, String summary) {
122122 wasThinking = false ;
123123 }
124124 stopSpinner ();
125+ emitToolTraceStart (toolName , summary );
125126 statusRenderer .onToolStarted (toolId , toolName , summary );
126127 }
127128 }
@@ -130,6 +131,7 @@ public void onToolUse(String toolId, String toolName, String summary) {
130131 public void onToolCompleted (String toolId , String toolName ,
131132 long durationMs , boolean isError , String error ) {
132133 synchronized (lock ) {
134+ emitToolTraceCompleted (toolName , durationMs , isError , error );
133135 statusRenderer .onToolCompleted (toolId , toolName , durationMs , isError , error );
134136 }
135137 }
@@ -290,6 +292,40 @@ private static String truncate(String text, int maxLen) {
290292 return normalized .substring (0 , maxLen - 3 ) + "..." ;
291293 }
292294
295+ private void emitToolTraceStart (String toolName , String summary ) {
296+ statusRenderer .hide ();
297+ String label = safeName (toolName );
298+ String detail = truncate (summary , 90 );
299+ if (detail .isBlank ()) {
300+ out .println (MUTED + "[tool:start] " + label + RESET );
301+ } else {
302+ out .println (MUTED + "[tool:start] " + label + " - " + detail + RESET );
303+ }
304+ out .flush ();
305+ }
306+
307+ private void emitToolTraceCompleted (String toolName , long durationMs , boolean isError , String error ) {
308+ statusRenderer .hide ();
309+ String label = safeName (toolName );
310+ String elapsed = String .format (Locale .ROOT , "%.1fs" , Math .max (0L , durationMs ) / 1000.0 );
311+ if (isError ) {
312+ String reason = truncate (error , 90 );
313+ if (reason .isBlank ()) {
314+ out .println (MUTED + "[tool:error] " + label + " (" + elapsed + ")" + RESET );
315+ } else {
316+ out .println (MUTED + "[tool:error] " + label + " (" + elapsed + ") - " + reason + RESET );
317+ }
318+ } else {
319+ out .println (MUTED + "[tool:done] " + label + " (" + elapsed + ")" + RESET );
320+ }
321+ out .flush ();
322+ }
323+
324+ private static String safeName (String toolName ) {
325+ if (toolName == null || toolName .isBlank ()) return "unknown" ;
326+ return toolName ;
327+ }
328+
293329 /**
294330 * Lightweight status renderer for tool/sub-agent/plan progress lines.
295331 *
@@ -335,13 +371,31 @@ private static final class StatusEntry {
335371
336372 void onToolStarted (String toolId , String toolName , String summary ) {
337373 pruneExpired ();
338- String key = (toolId != null && !toolId .isBlank ())
339- ? toolId : "tool-" + (++nextSyntheticId );
340374 String parent = firstActiveKey (Kind .SUBAGENT );
341- var entry = new StatusEntry (key , Kind .TOOL , safe (toolName , "unknown" ),
342- truncate (summary , 60 ), System .nanoTime ());
375+ String normalizedToolName = safe (toolName , "unknown" );
376+
377+ StatusEntry entry = null ;
378+ if (toolId != null && !toolId .isBlank ()) {
379+ entry = entries .get (toolId );
380+ }
381+ if (entry == null ) {
382+ entry = firstReusableTool (normalizedToolName );
383+ }
384+
385+ if (entry == null ) {
386+ String key = (toolId != null && !toolId .isBlank ())
387+ ? toolId : "tool-" + (++nextSyntheticId );
388+ entry = new StatusEntry (key , Kind .TOOL , normalizedToolName ,
389+ truncate (summary , 60 ), System .nanoTime ());
390+ entries .put (key , entry );
391+ } else {
392+ entry .name = normalizedToolName ;
393+ entry .detail = truncate (summary , 60 );
394+ entry .state = State .ACTIVE ;
395+ entry .durationMs = 0L ;
396+ entry .expiresAtMs = 0L ;
397+ }
343398 entry .parentKey = parent ;
344- entries .put (key , entry );
345399 redraw ();
346400 }
347401
@@ -570,6 +624,17 @@ private StatusEntry firstActiveTool(String toolName) {
570624 return null ;
571625 }
572626
627+ private StatusEntry firstReusableTool (String toolName ) {
628+ StatusEntry active = firstActiveTool (toolName );
629+ if (active != null ) return active ;
630+ for (var entry : entries .values ()) {
631+ if (entry .kind == Kind .TOOL && toolName .equals (entry .name )) {
632+ return entry ;
633+ }
634+ }
635+ return null ;
636+ }
637+
573638 private String firstActiveKey (Kind kind ) {
574639 for (var entry : entries .values ()) {
575640 if (entry .kind == kind && entry .state == State .ACTIVE ) {
0 commit comments