@@ -633,8 +633,7 @@ def execute(
633633 fail_on_run_error : bool ,
634634 max_wait_time_seconds : Optional [int ] = None ,
635635 poll_interval_seconds : Optional [int ] = None ,
636- _tui_app : Optional [Any ] = None ,
637- _tui_log_callback : Optional [Any ] = None ,
636+ _tui_event_bus : Optional [Any ] = None ,
638637 ) -> Any :
639638 """
640639 Execute attack with server tracking.
@@ -652,8 +651,9 @@ def execute(
652651 fail_on_run_error: Whether to raise on errors
653652 max_wait_time_seconds: Unused for local execution
654653 poll_interval_seconds: Unused for local execution
655- _tui_app: Optional TUI app for logging
656- _tui_log_callback: Optional TUI log callback
654+ _tui_event_bus: Optional :class:`hackagent.cli.tui.events.TUIEventBus`
655+ that receives structured events (step start/end, tool calls,
656+ progress, etc.) during execution.
657657
658658 Returns:
659659 Attack results from local execution
@@ -710,6 +710,22 @@ def execute(
710710 except Exception as e :
711711 logger .warning (f"Failed to update run status to RUNNING: { e } " )
712712
713+ # Make the event bus available to the technique impl and to the
714+ # tracker via the shared config bag (alongside _run_id / _backend).
715+ if _tui_event_bus is not None :
716+ attack_config = {** attack_config , "_tui_event_bus" : _tui_event_bus }
717+ effective_run_config = {
718+ ** effective_run_config ,
719+ "_tui_event_bus" : _tui_event_bus ,
720+ }
721+ _tui_event_bus .emit (
722+ "step_started" ,
723+ step_name = "Attack Execution" ,
724+ attack_type = self .attack_type ,
725+ run_id = run_id ,
726+ expected_total_goals = effective_run_config .get ("expected_total_goals" ),
727+ )
728+
713729 # 5. Execute locally
714730 try :
715731 _total_t0 = time .perf_counter ()
@@ -734,6 +750,9 @@ def execute(
734750 "_backend" : self .hackagent_agent .backend ,
735751 }
736752
753+ if _tui_event_bus is not None :
754+ _tui_event_bus .emit ("step_started" , step_name = "Evaluation Pipeline" )
755+
737756 if (self .attack_type or "" ).lower () == "pair" :
738757 from hackagent .attacks .techniques .pair .evaluation import (
739758 PAIREvaluation ,
@@ -778,10 +797,31 @@ def execute(
778797 except Exception as e :
779798 logger .warning (f"Evaluation failed: { e } " , exc_info = True )
780799 final_results = results # fallback
800+ if _tui_event_bus is not None :
801+ _tui_event_bus .emit (
802+ "step_ended" ,
803+ step_name = "Evaluation Pipeline" ,
804+ success = False ,
805+ error = str (e ),
806+ )
807+ else :
808+ if _tui_event_bus is not None :
809+ _tui_event_bus .emit (
810+ "step_ended" ,
811+ step_name = "Evaluation Pipeline" ,
812+ success = True ,
813+ )
781814
782815 # ⏱ timing AFTER evaluation
783816 _total_elapsed = round (time .perf_counter () - _total_t0 , 3 )
784817 logger .info (f"Total run time: { _total_elapsed :.1f} s" )
818+ if _tui_event_bus is not None :
819+ _tui_event_bus .emit (
820+ "step_ended" ,
821+ step_name = "Attack Execution" ,
822+ success = True ,
823+ elapsed_s = _total_elapsed ,
824+ )
785825
786826 # ✅ Update run status to COMPLETED
787827 try :
@@ -806,6 +846,13 @@ def execute(
806846 )
807847 except Exception as update_error :
808848 logger .warning (f"Failed to update run status to FAILED: { update_error } " )
849+ if _tui_event_bus is not None :
850+ _tui_event_bus .emit (
851+ "step_ended" ,
852+ step_name = "Attack Execution" ,
853+ success = False ,
854+ error = str (e ),
855+ )
809856 raise
810857
811858 # ========================================================================
0 commit comments