@@ -440,8 +440,18 @@ async def execute_single_tool(
440440
441441 # Register tool with cancellation token for visibility
442442 if coordinator :
443+ # Build semantic display name for delegate calls
444+ display_name = tool_name
445+ if tool_name == "delegate" :
446+ try :
447+ _args = args if isinstance (args , dict ) else json .loads (args )
448+ _agent = _args .get ("agent" , "" )
449+ if _agent :
450+ display_name = _agent
451+ except (json .JSONDecodeError , TypeError , AttributeError ):
452+ pass
443453 coordinator .cancellation .register_tool_start (
444- tool_call_id , tool_name
454+ tool_call_id , display_name
445455 )
446456
447457 try :
@@ -616,6 +626,14 @@ async def execute_single_tool(
616626 "turn_count" : iteration ,
617627 },
618628 )
629+ # Write synthetic assistant message to close the turn.
630+ # Without this, transcript has tool_results without a closing assistant
631+ # message, triggering FM3 (incomplete_assistant_turn) on resume.
632+ if hasattr (context , "add_message" ):
633+ await context .add_message ({
634+ "role" : "assistant" ,
635+ "content" : "The previous operation was cancelled. Results from completed tools have been preserved." ,
636+ })
619637 # Re-raise to let the cancellation propagate
620638 raise
621639
@@ -665,6 +683,14 @@ async def execute_single_tool(
665683 "status" : "cancelled" ,
666684 },
667685 )
686+ # Write synthetic assistant message to close the turn.
687+ # Without this, transcript has tool_results without a closing assistant
688+ # message, triggering FM3 (incomplete_assistant_turn) on resume.
689+ if hasattr (context , "add_message" ):
690+ await context .add_message ({
691+ "role" : "assistant" ,
692+ "content" : "The previous operation was cancelled. Results from completed tools have been preserved." ,
693+ })
668694 return final_content
669695
670696 # Add all tool results to context in original order (deterministic)
0 commit comments