1111import atexit
1212import signal
1313from pathlib import Path
14- from typing import Set
14+ from types import FrameType
15+ from typing import Set , TextIO
1516
1617import typer
1718
4041
4142# Global process tracking for cleanup
4243_ACTIVE_PROCESSES : Set [subprocess .Popen ] = set ()
44+ _PROCESS_LOG_HANDLES : dict [subprocess .Popen , TextIO ] = {}
45+ _CLEANUP_HANDLERS_REGISTERED = False
4346
4447def _cleanup_processes ():
4548 """Cleanup all spawned processes on exit."""
@@ -48,17 +51,36 @@ def _cleanup_processes():
4851 if proc .poll () is None : # Process still running
4952 proc .terminate ()
5053 proc .wait (timeout = 5 )
51- except :
54+ except Exception :
5255 try :
5356 proc .kill ()
54- except :
57+ except Exception :
58+ pass
59+ log_handle = _PROCESS_LOG_HANDLES .pop (proc , None )
60+ if log_handle :
61+ try :
62+ log_handle .close ()
63+ except Exception :
5564 pass
5665 _ACTIVE_PROCESSES .discard (proc )
5766
58- # Register cleanup handlers
59- atexit .register (_cleanup_processes )
60- signal .signal (signal .SIGTERM , lambda s , f : _cleanup_processes ())
61- signal .signal (signal .SIGINT , lambda s , f : _cleanup_processes ())
67+ def _resolve_check_host (host : str ) -> str :
68+ return "127.0.0.1" if host == "0.0.0.0" else host
69+
70+
71+ def _handle_shutdown_signal (signum : int , frame : FrameType | None ):
72+ _cleanup_processes ()
73+ sys .exit (0 )
74+
75+
76+ def _register_cleanup_handlers ():
77+ global _CLEANUP_HANDLERS_REGISTERED
78+ if _CLEANUP_HANDLERS_REGISTERED :
79+ return
80+ atexit .register (_cleanup_processes )
81+ signal .signal (signal .SIGTERM , _handle_shutdown_signal )
82+ signal .signal (signal .SIGINT , _handle_shutdown_signal )
83+ _CLEANUP_HANDLERS_REGISTERED = True
6284
6385
6486def _generate_dashboard_html (host : str = "localhost" ) -> str :
@@ -410,6 +432,7 @@ def unified(
410432
411433 # Create FastAPI app
412434 fastapi_app = FastAPI (title = "PraisonAI Unified Dashboard" )
435+ _register_cleanup_handlers ()
413436
414437 @fastapi_app .get ("/" , response_class = HTMLResponse )
415438 async def dashboard ():
@@ -426,45 +449,50 @@ async def start_service(service: str):
426449 # Check if service is already running
427450 import socket
428451 sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
452+ check_host = _resolve_check_host (host )
429453 try :
430- result = sock .connect_ex (("127.0.0.1" , service_port ))
431- if result == 0 :
432- sock .close ()
454+ connection_result = sock .connect_ex ((check_host , service_port ))
455+ if connection_result == 0 :
433456 return {"success" : True , "message" : f"Service { service } already running on port { service_port } " }
434- except :
457+ except OSError :
435458 pass
436459 finally :
437460 sock .close ()
438461
462+ log_handle = None
439463 try :
440464 # Create log directory for troubleshooting
441465 log_dir = Path .home () / ".praisonai" / "unified" / "logs"
442466 log_dir .mkdir (parents = True , exist_ok = True )
443467 log_file = log_dir / f"{ service } .log"
468+ log_handle = open (log_file , "a" , encoding = "utf-8" )
444469
445470 if service == "flow" :
446- # Use proper Typer entry point for flow
471+ # Use praisonai module entrypoint so command resolution matches CLI behavior.
447472 proc = subprocess .Popen ([
448473 sys .executable , "-m" , "praisonai" , "flow" ,
449474 "--port" , str (service_port ), "--no-open"
450- ], stdout = open ( log_file , "w" ) , stderr = subprocess .STDOUT )
475+ ], stdout = log_handle , stderr = subprocess .STDOUT )
451476 elif service == "claw" :
452477 proc = subprocess .Popen ([
453478 sys .executable , "-m" , "praisonai" , "claw" ,
454479 "--port" , str (service_port )
455- ], stdout = open ( log_file , "w" ) , stderr = subprocess .STDOUT )
480+ ], stdout = log_handle , stderr = subprocess .STDOUT )
456481 elif service == "ui" :
457482 proc = subprocess .Popen ([
458483 sys .executable , "-m" , "praisonai" , "ui" ,
459484 "--port" , str (service_port )
460- ], stdout = open ( log_file , "w" ) , stderr = subprocess .STDOUT )
485+ ], stdout = log_handle , stderr = subprocess .STDOUT )
461486
462487 # Track process for cleanup
463488 _ACTIVE_PROCESSES .add (proc )
489+ _PROCESS_LOG_HANDLES [proc ] = log_handle
464490
465491 return {"success" : True , "message" : f"Started { service } on port { service_port } " }
466492
467493 except Exception as e :
494+ if log_handle and not log_handle .closed :
495+ log_handle .close ()
468496 raise HTTPException (status_code = 500 , detail = f"Failed to start { service } : { str (e )} " )
469497
470498 @fastapi_app .get ("/health" )
@@ -482,7 +510,7 @@ async def health():
482510 fastapi_app ,
483511 host = host ,
484512 port = port ,
485- log_level = "info" # Show startup logs for debugging
513+ log_level = "error"
486514 )
487515 except KeyboardInterrupt :
488516 console .print ("\n [yellow]🌟 Unified Dashboard stopped.[/yellow]" )
0 commit comments