@@ -144,17 +144,25 @@ def execute_optimization(
144144 """
145145 if console is None :
146146 console = Console ()
147-
148147 # Global variables for this optimization run
149148 heartbeat_thread = None
150149 stop_heartbeat_event = threading .Event ()
151150 current_run_id_for_heartbeat = None
152151 current_auth_headers_for_heartbeat = {}
152+ live_ref = None # Reference to the Live object for the optimization run
153+
154+ best_solution_code = None
155+ original_source_code = None
153156
154157 # --- Signal Handler for this optimization run ---
155158 def signal_handler (signum , frame ):
159+ nonlocal live_ref
160+
161+ if live_ref is not None :
162+ live_ref .stop () # Stop the live update loop so that messages are printed to the console
163+
156164 signal_name = signal .Signals (signum ).name
157- console .print (f"\n [bold yellow]Termination signal ({ signal_name } ) received. Shutting down...[/]" )
165+ console .print (f"\n [bold yellow]Termination signal ({ signal_name } ) received. Shutting down...[/]\n " )
158166
159167 # Stop heartbeat thread
160168 stop_heartbeat_event .set ()
@@ -170,7 +178,7 @@ def signal_handler(signum, frame):
170178 details = f"Process terminated by signal { signal_name } ({ signum } )." ,
171179 auth_headers = current_auth_headers_for_heartbeat ,
172180 )
173- console .print (f"\n [cyan]To resume this run, use:[/] [bold cyan]weco resume { current_run_id_for_heartbeat } [/]" )
181+ console .print (f"[cyan]To resume this run, use:[/] [bold cyan]weco resume { current_run_id_for_heartbeat } [/]\n " )
174182
175183 # Exit gracefully
176184 sys .exit (0 )
@@ -183,8 +191,6 @@ def signal_handler(signum, frame):
183191 optimization_completed_normally = False
184192 user_stop_requested_flag = False
185193
186- best_solution_code = None
187- original_source_code = None # Make available to the finally block
188194 try :
189195 # --- Login/Authentication Handling (now mandatory) ---
190196 weco_api_key , auth_headers = handle_authentication (console )
@@ -256,6 +262,7 @@ def signal_handler(signum, frame):
256262 # --- Live Update Loop ---
257263 refresh_rate = 4
258264 with Live (layout , refresh_per_second = refresh_rate ) as live :
265+ live_ref = live
259266 # Define the runs directory (.runs/<run-id>) to store logs and results
260267 runs_dir = pathlib .Path (log_dir ) / run_id
261268 runs_dir .mkdir (parents = True , exist_ok = True )
@@ -387,6 +394,16 @@ def signal_handler(signum, frame):
387394 status_response = status_response , solution_id = eval_and_next_solution_response ["solution_id" ]
388395 )
389396
397+ # Set best solution and save optimization results
398+ try :
399+ best_solution_code = best_solution_node .code
400+ except AttributeError :
401+ # Can happen if the code was buggy
402+ best_solution_code = read_from_path (fp = runs_dir / f"step_0{ source_fp .suffix } " , is_json = False )
403+
404+ # Save best solution to .runs/<run-id>/best.<extension>
405+ write_to_path (fp = runs_dir / f"best{ source_fp .suffix } " , content = best_solution_code )
406+
390407 # Update the solution panels with the current and best solution
391408 solution_panels .update (current_node = current_solution_node , best_node = best_solution_node )
392409 current_solution_panel , best_solution_panel = solution_panels .get_display (current_step = step )
@@ -443,10 +460,13 @@ def signal_handler(signum, frame):
443460 )
444461 # No need to set any solution to unevaluated since we have finished the optimization
445462 # and all solutions have been evaluated
446- # No neeed to update the current solution panel since we have finished the optimization
463+ # No need to update the current solution panel since we have finished the optimization
447464 # We only need to update the best solution panel
448465 # Figure out if we have a best solution so far
449466 best_solution_node = get_best_node_from_status (status_response = status_response )
467+ best_solution_code = best_solution_node .code
468+ # Save best solution to .runs/<run-id>/best.<extension>
469+ write_to_path (fp = runs_dir / f"best{ source_fp .suffix } " , content = best_solution_code )
450470 solution_panels .update (current_node = None , best_node = best_solution_node )
451471 _ , best_solution_panel = solution_panels .get_display (current_step = steps )
452472 # Update the end optimization layout
@@ -459,17 +479,6 @@ def signal_handler(signum, frame):
459479 end_optimization_layout ["tree" ].update (tree_panel .get_display (is_done = True ))
460480 end_optimization_layout ["best_solution" ].update (best_solution_panel )
461481
462- # Save optimization results
463- # If the best solution does not exist or is has not been measured at the end of the optimization
464- # save the original solution as the best solution
465- try :
466- best_solution_code = best_solution_node .code
467- except AttributeError :
468- best_solution_code = read_from_path (fp = runs_dir / f"step_0{ source_fp .suffix } " , is_json = False )
469-
470- # Save best solution to .runs/<run-id>/best.<extension>
471- write_to_path (fp = runs_dir / f"best{ source_fp .suffix } " , content = best_solution_code )
472-
473482 # Mark as completed normally for the finally block
474483 optimization_completed_normally = True
475484 live .update (end_optimization_layout )
@@ -481,7 +490,7 @@ def signal_handler(signum, frame):
481490 except Exception :
482491 error_message = str (e )
483492 console .print (Panel (f"[bold red]Error: { error_message } " , title = "[bold red]Optimization Error" , border_style = "red" ))
484- console .print (f"\n [cyan]To resume this run, use:[/] [bold cyan]weco resume { run_id } [/]" )
493+ console .print (f"\n [cyan]To resume this run, use:[/] [bold cyan]weco resume { run_id } [/]\n " )
485494 # Ensure optimization_completed_normally is False
486495 optimization_completed_normally = False
487496 finally :
@@ -508,20 +517,16 @@ def signal_handler(signum, frame):
508517 else "CLI terminated unexpectedly without a specific exception captured."
509518 )
510519
511- # raise Exception(best_solution_code, original_source_code)
512520 if best_solution_code and best_solution_code != original_source_code :
513521 # Determine whether to apply: automatically if --apply-change is set, otherwise ask user
514- should_apply = apply_change or summary_panel .ask_user_feedback (
515- live = live ,
516- layout = end_optimization_layout ,
517- question = "Would you like to apply the best solution to the source file?" ,
518- default = True ,
522+ should_apply = apply_change or Confirm .ask (
523+ "Would you like to apply the best solution to the source file?" , default = True
519524 )
520525 if should_apply :
521526 write_to_path (fp = source_fp , content = best_solution_code )
522- console .print ("[green]Best solution applied to the source file.[/]\n " )
527+ console .print ("\n [green]Best solution applied to the source file.[/]\n " )
523528 else :
524- console .print ("[green]A better solution was not found. No changes to apply.[/]\n " )
529+ console .print ("\n [green]A better solution was not found. No changes to apply.[/]\n " )
525530
526531 report_termination (
527532 run_id = run_id ,
@@ -534,7 +539,7 @@ def signal_handler(signum, frame):
534539 # Handle exit
535540 if user_stop_requested_flag :
536541 console .print ("[yellow]Run terminated by user request.[/]" )
537- console .print (f"\n [cyan]To resume this run, use:[/] [bold cyan]weco resume { run_id } [/]" )
542+ console .print (f"\n [cyan]To resume this run, use:[/] [bold cyan]weco resume { run_id } [/]\n " )
538543
539544 return optimization_completed_normally or user_stop_requested_flag
540545
@@ -549,11 +554,19 @@ def resume_optimization(run_id: str, console: Optional[Console] = None, apply_ch
549554 stop_heartbeat_event = threading .Event ()
550555 current_run_id_for_heartbeat = None
551556 current_auth_headers_for_heartbeat = {}
557+ live_ref = None # Reference to the Live object for the optimization run
558+
559+ best_solution_code = None
560+ original_source_code = None
552561
553562 # Signal handler for this optimization run
554563 def signal_handler (signum , frame ):
564+ nonlocal live_ref
565+ if live_ref is not None :
566+ live_ref .stop () # Stop the live update loop so that messages are printed to the console
567+
555568 signal_name = signal .Signals (signum ).name
556- console .print (f"\n [bold yellow]Termination signal ({ signal_name } ) received. Shutting down...[/]" )
569+ console .print (f"\n [bold yellow]Termination signal ({ signal_name } ) received. Shutting down...[/]\n " )
557570 stop_heartbeat_event .set ()
558571 if heartbeat_thread and heartbeat_thread .is_alive ():
559572 heartbeat_thread .join (timeout = 2 )
@@ -565,7 +578,7 @@ def signal_handler(signum, frame):
565578 details = f"Process terminated by signal { signal_name } ({ signum } )." ,
566579 auth_headers = current_auth_headers_for_heartbeat ,
567580 )
568- console .print (f"\n [cyan]To resume this run, use:[/] [bold cyan]weco resume { current_run_id_for_heartbeat } [/]" )
581+ console .print (f"\n [cyan]To resume this run, use:[/] [bold cyan]weco resume { current_run_id_for_heartbeat } [/]\n " )
569582 sys .exit (0 )
570583
571584 # Set up signal handlers for this run
@@ -575,9 +588,6 @@ def signal_handler(signum, frame):
575588 optimization_completed_normally = False
576589 user_stop_requested_flag = False
577590
578- best_solution_code = None
579- original_source_code = None
580-
581591 try :
582592 # --- Login/Authentication Handling (now mandatory) ---
583593 weco_api_key , auth_headers = handle_authentication (console )
@@ -659,9 +669,8 @@ def signal_handler(signum, frame):
659669 source_fp .parent .mkdir (parents = True , exist_ok = True )
660670 # Store the original content to restore after each evaluation
661671 original_source_code = read_from_path (fp = source_fp , is_json = False ) if source_fp .exists () else ""
662-
672+ # The code to restore is the code from the last step of the previous run
663673 code_to_restore = resume_resp .get ("code" ) or resume_resp .get ("source_code" ) or ""
664- write_to_path (fp = source_fp , content = code_to_restore )
665674
666675 # Prepare UI panels
667676 summary_panel = SummaryPanel (
@@ -687,12 +696,25 @@ def signal_handler(signum, frame):
687696 best_solution_node = get_best_node_from_status (status_response = status )
688697 current_solution_node = get_node_from_status (status_response = status , solution_id = resume_resp .get ("solution_id" ))
689698
699+ # If there's no best solution yet (baseline evaluation didn't complete),
700+ # mark the current node as unevaluated so the tree renders correctly
701+ if best_solution_node is None :
702+ tree_panel .set_unevaluated_node (node_id = resume_resp .get ("solution_id" ))
703+
690704 # Ensure runs dir exists
691705 runs_dir = pathlib .Path (log_dir ) / resume_resp ["run_id" ]
692706 runs_dir .mkdir (parents = True , exist_ok = True )
693707 # Persist last step's code into logs as step_<current_step>
694708 write_to_path (fp = runs_dir / f"step_{ current_step } { source_fp .suffix } " , content = code_to_restore )
695709
710+ # Initialize best solution code
711+ try :
712+ best_solution_code = best_solution_node .code
713+ except AttributeError :
714+ # Edge case: best solution node is not available.
715+ # This can happen if the user has cancelled the run before even running the baseline solution
716+ pass # Leave best solution code as None
717+
696718 # Start Heartbeat Thread
697719 stop_heartbeat_event .clear ()
698720 heartbeat_thread = HeartbeatSender (resume_resp ["run_id" ], auth_headers , stop_heartbeat_event )
@@ -705,6 +727,7 @@ def signal_handler(signum, frame):
705727 # --- Live UI ---
706728 refresh_rate = 4
707729 with Live (layout , refresh_per_second = refresh_rate ) as live :
730+ live_ref = live
708731 # Initial panels
709732 current_solution_panel , best_solution_panel = solution_panels .get_display (current_step = current_step )
710733 # Use backend-provided execution output only (no fallback)
@@ -788,6 +811,16 @@ def signal_handler(signum, frame):
788811 status_response = status_response , solution_id = eval_and_next_solution_response ["solution_id" ]
789812 )
790813
814+ # Set best solution and save optimization results
815+ try :
816+ best_solution_code = best_solution_node .code
817+ except AttributeError :
818+ # Can happen if the code was buggy
819+ best_solution_code = read_from_path (fp = runs_dir / f"step_0{ source_fp .suffix } " , is_json = False )
820+
821+ # Save best solution to .runs/<run-id>/best.<extension>
822+ write_to_path (fp = runs_dir / f"best{ source_fp .suffix } " , content = best_solution_code )
823+
791824 solution_panels .update (current_node = current_solution_node , best_node = best_solution_node )
792825 current_solution_panel , best_solution_panel = solution_panels .get_display (current_step = step )
793826 eval_output_panel .clear ()
@@ -839,6 +872,10 @@ def signal_handler(signum, frame):
839872 tree_panel .build_metric_tree (nodes = nodes_final )
840873 # Best solution panel and final message
841874 best_solution_node = get_best_node_from_status (status_response = status_response )
875+ best_solution_code = best_solution_node .code
876+ # Save best solution to .runs/<run-id>/best.<extension>
877+ write_to_path (fp = runs_dir / f"best{ source_fp .suffix } " , content = best_solution_code )
878+
842879 solution_panels .update (current_node = None , best_node = best_solution_node )
843880 _ , best_solution_panel = solution_panels .get_display (current_step = total_steps )
844881 final_message = (
@@ -850,14 +887,6 @@ def signal_handler(signum, frame):
850887 end_optimization_layout ["tree" ].update (tree_panel .get_display (is_done = True ))
851888 end_optimization_layout ["best_solution" ].update (best_solution_panel )
852889
853- # Save best
854- try :
855- best_solution_code = best_solution_node .code
856- except AttributeError :
857- best_solution_code = read_from_path (fp = runs_dir / f"step_0{ source_fp .suffix } " , is_json = False )
858-
859- write_to_path (fp = runs_dir / f"best{ source_fp .suffix } " , content = best_solution_code )
860-
861890 optimization_completed_normally = True
862891 live .update (end_optimization_layout )
863892
@@ -867,7 +896,7 @@ def signal_handler(signum, frame):
867896 except Exception :
868897 error_message = str (e )
869898 console .print (Panel (f"[bold red]Error: { error_message } " , title = "[bold red]Optimization Error" , border_style = "red" ))
870- console .print (f"\n [cyan]To resume this run, use:[/] [bold cyan]weco resume { run_id } [/]" )
899+ console .print (f"\n [cyan]To resume this run, use:[/] [bold cyan]weco resume { run_id } [/]\n " )
871900 optimization_completed_normally = False
872901 finally :
873902 signal .signal (signal .SIGINT , original_sigint_handler )
@@ -896,17 +925,14 @@ def signal_handler(signum, frame):
896925 )
897926
898927 if best_solution_code and best_solution_code != original_source_code :
899- should_apply = apply_change or summary_panel .ask_user_feedback (
900- live = live ,
901- layout = end_optimization_layout ,
902- question = "Would you like to apply the best solution to the source file?" ,
903- default = True ,
928+ should_apply = apply_change or Confirm .ask (
929+ "Would you like to apply the best solution to the source file?" , default = True
904930 )
905931 if should_apply :
906932 write_to_path (fp = source_fp , content = best_solution_code )
907- console .print ("[green]Best solution applied to the source file.[/]\n " )
933+ console .print ("\n [green]Best solution applied to the source file.[/]\n " )
908934 else :
909- console .print ("[green]A better solution was not found. No changes to apply.[/]\n " )
935+ console .print ("\n [green]A better solution was not found. No changes to apply.[/]\n " )
910936
911937 report_termination (
912938 run_id = run_id ,
@@ -917,5 +943,5 @@ def signal_handler(signum, frame):
917943 )
918944 if user_stop_requested_flag :
919945 console .print ("[yellow]Run terminated by user request.[/]" )
920- console .print (f"\n [cyan]To resume this run, use:[/] [bold cyan]weco resume { run_id } [/]" )
946+ console .print (f"\n [cyan]To resume this run, use:[/] [bold cyan]weco resume { run_id } [/]\n " )
921947 return optimization_completed_normally or user_stop_requested_flag
0 commit comments