
    PmjXM              
           d Z ddlZddlZddlZddlmZ d Zdddee         dz  de	e
eef                  fdZ	 dd
ddededeee	f         fdZdddededz  de	e
eef                  fdZdS )u  Dashboard process-hygiene helpers — extracted from ``hermes_cli/main.py``.

Mechanical move (main.py decomposition): the three leaf process-hygiene
helpers (``_scan_dashboard_processes``, ``_kill_stale_dashboard_processes``,
``_detect_concurrent_hermes_instances``) are lifted verbatim. References to
helpers that STAY in ``hermes_cli.main`` (``_find_stale_dashboard_pids``,
``_respawn_dashboard_processes``, ``_is_windows``, ...) are routed through a
lazy ``_m()`` main reference so existing test monkeypatches on
``hermes_cli.main.<name>`` keep reaching this code path, and imports stay
one-way at import time (main.py imports this module, never the reverse).
``main.py`` re-exports all three names (``# noqa: F401``) so callers and test
patches on ``hermes_cli.main`` resolve unchanged.
    N)Pathc                      ddl m}  | S )zFLazy ``hermes_cli.main`` reference (call-time; keeps patches working).r   main)
hermes_clir   r   s    @/home/thesage/.hermes/hermes-agent/hermes_cli/dashboard_procs.py_mr	      s    K    exclude_pidsr   returnc           
           g d}t          j                    }g }	 t          j        dk    r0ddlm} t          j        g dddddd	 |            
          }|j        dk    s|j	        g S d|j	        
                    d          D ]}|                                }|                    d          r|t          d          d         C|                    d          r{|t          d          d         }t          fd|D                       rIt          |          |k    r6	 |                    t          |          f           # t"          $ r Y w xY wnt          j        g dddddd          }|j        dk    rt%          |dd          
                    d          D ]}|                                }|rd|v r|
                    dd          }	t          |	          dk    rG	 t          |	d                   }
n# t"          $ r Y jw xY w|	d         t          fd|D                       r|
|k    r|                    |
f           n$# t&          t          j        t*          f$ r g cY S w xY w r fd|D             }|S )uD  Return matching ``dashboard``/``serve`` processes with their cmdlines.

    ``hermes dashboard`` is a long-lived server process commonly started and
    forgotten.  When ``hermes update`` replaces files on disk, the running
    process keeps the old Python backend in memory while the JS bundle on
    disk is updated, causing a silent frontend/backend mismatch (e.g. new
    auth headers the old backend doesn't recognise → every API call 401s).

    The dashboard may be manually started or managed by the optional
    ``hermes-dashboard.service`` systemd unit.  Managed units are restarted
    through their owning systemd scope; only manually-started processes use
    the kill path because we can't know their original launch args.

    *exclude_pids* is an optional set of PIDs that must never be returned.
    This is used by the Hermes Desktop Electron app to protect its own
    backend child process: when the desktop spawns ``hermes serve`` as
    a backend and triggers an auto-update, the update must not kill the
    backend that the desktop itself manages.  The desktop sets the
    environment variable ``HERMES_DESKTOP_CHILD_PID`` on the spawned
    backend process; ``_kill_stale_dashboard_processes`` reads it and
    passes it here.  (#37532)

    Returns an empty list on any scan error (missing ps/wmic, timeout, etc.).
    )zhermes dashboardzhermes_cli.main dashboardzhermes_cli/main.py dashboardzhermes servezhermes_cli.main servezhermes_cli/main.py servewin32r   )windows_hide_flags)wmicprocessgetzProcessId,CommandLinez/FORMAT:LISTT
   utf-8ignore)capture_outputtexttimeoutencodingerrorscreationflagsN 
zCommandLine=z
ProcessId=c              3       K   | ]}|v V  	d S N ).0pcurrent_cmds     r   	<genexpr>z,_scan_dashboard_processes.<locals>.<genexpr>g   s(      ??A,??????r
   )psz-Az-ozpid=,command=replacer   r   r   r   r   stdoutgrep      c              3       K   | ]}|v V  	d S r    r!   )r"   r#   commands     r   r%   z,_scan_dashboard_processes.<locals>.<genexpr>   s'      ::A1<::::::r
   c                 (    g | ]}|d          v|S r   r!   )r"   procr   s     r   
<listcomp>z-_scan_dashboard_processes.<locals>.<listcomp>   s.     
 
 
DG<4O4OD4O4O4Or
   )osgetpidsysplatformhermes_cli._subprocess_compatr   
subprocessrun
returncoder)   splitstrip
startswithlenanyintappend
ValueErrorgetattrFileNotFoundErrorTimeoutExpiredOSError)r   patternsself_piddashboard_processesr   resultlinepid_strstrippedpartspidr.   r$   s   `          @@r   _scan_dashboard_processesrP      sU   8
 
 
H y{{H13E<7"" IHHHHH^SSS# 0022  F  A%%)>	K++D11 ! !zz||??>22 !"&s>':':'<'<"=KK__\22 	!"3|#4#4#6#67G????h?????!LLH44!/66Gk7RSSSS) ! ! ! D!!*  ^333#GI	  F  A%%#FHb99??EE C CD#zz||H# !v'9'9 $NN433E5zzQ !!%(mm% ! ! ! !#AhG::::::::: Csh+22C>BBBz8'B   			  

 
 
 
0
 
 
 sc   AI .B9I ($EI 
EI EBI 2HI 
HI HAI I;:I;:the running backend no longer matches the updated frontendF)restart_managedreasonrR   c          	      t    |r't                                          |           rg g g dS d}t          j                            d          }|r{t                      }|                    d          D ]S}|                                }|s	 |                    t          |                     =# t          t          f$ r Y Pw xY w|r|}t                                          |          }|sg g g dS t                       t          dt          |           d|  d           i }i }i  |rt          j        d	k    r|D ]|}	t                                          |	          }
|
||	<   t                                          |	          ||	<   ||	         s(t                                          |	          }|r| |	<   }g g t          j        d	k    r|D ]}		 t)          j        d
dt-          |	          dgddddd          }|j        dk    r                    |	           n7                    |	|j        p|j        pd                                f           # t6          t(          j        t:          f$ r.}                    |	t-          |          f           Y d}~d}~ww xY wnddl}ddl}|D ]}		 t          j         |	|j!                   # tD          $ r                     |	           Y ?tF          t:          f$ r.}                    |	t-          |          f           Y d}~xd}~ww xY w|$                                dz   }fd|D             }|r|$                                |k     rt|%                    d           g }ddl&m'} |D ]8}	 ||	          r|                    |	           #                    |	           9|}|r|$                                |k     t|D ]}		 t          j         |	|j(                                       |	           3# tD          $ r                     |	           Y TtF          t:          f$ r.}                    |	t-          |          f           Y d}~d}~ww xY wD ]}	t          d|	            D ]\  }	}t          d|	 d|            g }g }r|rg }t                      }g }D ]}	|                    |	          }|r||v r|                    |           t                      )                    ||                    |	                    r|                    |           ~|                    |df           |                    |	           |	 v r|                     |	                    |                    |	           |D ]}t          d|            |D ]\  }}t          d| d|            |rEt                      *                    |          r"|+                     fdD                        |s|rt          d           t          d            n/r-tY                    }t          d!           t          d            tY          |          tY                    tY                    tY          |          d"S )#u%  Kill running ``hermes dashboard`` / ``hermes serve`` processes.

    Called at the end of ``hermes update`` (default ``reason``) and also
    from ``hermes dashboard --stop`` (which overrides ``reason``).  The
    dashboard has no service manager, so after a code update the running
    process is guaranteed to be serving stale Python against a
    freshly-updated JS bundle.  Leaving it alive produces silent
    frontend/backend mismatches (new auth headers the old backend doesn't
    recognise → every API call 401s).

    POSIX: SIGTERM, wait up to ~3s for graceful exit, SIGKILL any survivors.
    Windows: ``taskkill /PID <pid> /F`` since there's no clean SIGTERM
    equivalent for background console apps.

    Manually-started dashboards are not auto-restarted because we don't know
    the original launch args (--host, --port, --insecure, --tui, --no-open).
    When ``restart_managed`` is true (the ``hermes update`` path), a detected
    ``hermes-dashboard.service`` is restarted through systemd; any OTHER
    killed PID that was supervised by a systemd unit (custom unit names —
    e.g. a remote backend's ``hermes-serve.service``) has its owning unit
    restarted after the kill, because systemd treats our SIGTERM as a clean
    stop and ``Restart=on-failure`` would never fire (#68934).
    )matchedkilledfailedNHERMES_DESKTOP_CHILD_PID,r   u   ⟲ Stopping z dashboard process(es) ()r   taskkillz/PIDz/FTr   r'   r   r(   r   r   g      @c                 8    g | ]}|v|d  D             v|S )c                     h | ]
}|d          S r0   r!   )r"   fs     r   	<setcomp>z=_kill_stale_dashboard_processes.<locals>.<listcomp>.<setcomp>  s    <R<R<RaQqT<R<R<Rr
   r!   )r"   r#   rW   rV   s     r   r2   z3_kill_stale_dashboard_processes.<locals>.<listcomp>  sA     
 
 
q1<R<R6<R<R<R3R3RA3R3R3Rr
   g?)_pid_existsu       ✓ stopped PID u       ✗ failed to stop PID z: z#systemctl restart returned non-zerou"       ✓ restarted systemd service u       ⚠ c              3   J   K   | ]}                     |          v |V  d S r    )r   )r"   r#   failed_cmdspid_cmdlines     r   r%   z2_kill_stale_dashboard_processes.<locals>.<genexpr>G  s9      "Z"Z8J8Jk8Y8Y18Y8Y8Y8Y"Z"Zr
   z8  Restart anything not auto-restarted when you're ready:z"    hermes dashboard --port <port>z*  Restart the dashboard when you're ready:)rU   rV   rW   unrecovered)-r	   "_restart_managed_dashboard_servicer3   environr   setr;   r<   addr@   rB   	TypeError_find_stale_dashboard_pidsprintr>   r5   r6   _get_pid_cgroup_path_get_systemd_service_for_pid_dashboard_cmdline_for_pidr8   r9   strr:   rA   stderrr)   rD   rE   rF   signaltimekillSIGTERMProcessLookupErrorPermissionError	monotonicsleepgateway.statusr`   SIGKILL_try_restart_systemd_service_respawn_dashboard_processesextendlist)!rS   rR   excluderaw_pidparsedpartpids
pid_cgrouppid_servicerO   cg_pathcmdlinerJ   e_signal_timedeadlinependingstill_pendingr`   err_msgrestarted_servicesrd   failed_restartsseen_servicesrespawn_cmdssvc_namesvcerrrW   rb   rV   rc   s!                                @@@@r   _kill_stale_dashboard_processesr      sL   8  ;244BB6JJ ;r:::
  $Gjnn788G  55MM#&& 	 	D::<<D 

3t99%%%%	*    	G44***@@D ;r:::	GGG	
F#d))
F
FV
F
F
FGGG )+J)+K(*K 
/3<722 		/ 		/Cdd//44G%JsO!tt@@EEKs# / $$99#>> /'.K$F$&F
|w 	- 	-C-#S48#'		   $))MM#&&&&MM3)M&-)M2(T(T(V(V"WXXX%z'@'J - - -sCFFm,,,,,,,,-	- 	!     	- 	-C-W_----% # # #c"""""#W- - - -sCFFm,,,,,,,,- ??$$s*
 
 
 
 

 
 
  	$%//++h66KKM 322222 ' ';s## '!((----MM#&&&&#G  	$%//++h66  	- 	-C-W_---c""""% # # #c"""""#W- - - -sCFFm,,,,,,,,-  , ,*S**++++ > >W<C<<7<<==== %'K $4/ $413"%%%(* 	( 	(C"s++H (},,!!(+++4444Xz~~c?R?RSS ,&--h7777#**H6[+\]]]&&s++++####K$45555""3''''% 	> 	>C<s<<====' 	+ 	+HC)S))C))**** 	[$$;;LIIK ["""Z"Z"Z"Z"Zf"Z"Z"ZZZZ 	8k 	8LMMM6777	 46ll:;;;2333 ::v,,v,,K((	  sg   "B11CCBIJ%7$J  J%7KL13L1$L,,L1-/PQ<>Q<$Q77Q<)exclude_pidscripts_dirr   c                   t                                                      sg S 	 ddl}n# t          $ r g cY S w xY wt	                      }t                                          |           D ]}	 |                    t          |                                          	                                           J# t          $ r7 |                    t          |          	                                           Y w xY w|sg S |t          |          h}nt          j                    h}	 t          t          |                    }	 |                    |                                          }n# t          $ r g }Y nw xY w|D ]}	 |                                }	n# t          $ r Y $w xY w|	s+	 t          t'          |	                                                    	                                }
n8# t          t(          f$ r$ t          |	          	                                }
Y nw xY w|
|v r9	 |                    t          |j                             # t          $ r Y w xY wn# t          $ r Y nw xY wg }	 |                    g d          }n# t          $ r g cY S w xY w|D ]'}	 |j        }n# t          $ r Y w xY w|                    d          }|                    d          }|r|||v rO	 t          t'          |                                                    	                                }n8# t          t(          f$ r$ t          |          	                                }Y nw xY w||v rZ|                    d          pt'          |          j        }|                    t          |          t          |          f           )|S )um  Find other live processes whose .exe is one of our entry-point shims.

    Windows blocks DELETE/REPLACE on a running .exe — and even RENAME on the
    same .exe when another process opened it without ``FILE_SHARE_DELETE``.
    The Hermes Desktop Electron app spawns ``hermes.EXE`` as a backend child,
    so during ``hermes update`` the user-invoked process and the desktop's
    child both hold the same file. The quarantine rename then fails with
    ``[WinError 32]`` and uv inherits the lock.

    This helper enumerates processes whose ``exe`` matches one of the venv's
    shims (``hermes.exe`` / ``hermes-gateway.exe``) and returns ``(pid,
    process_name)`` pairs. The caller's own PID and its entire ancestor
    chain are excluded so the running ``hermes update`` invocation never
    reports itself — this matters on Windows where the setuptools .exe
    launcher (``hermes.exe``) is a separate process from the Python
    interpreter it loads (``python.exe``).

    Returns an empty list off-Windows, on missing psutil, or when no other
    instances exist. Never raises — process enumeration is best-effort.
    r   N)rO   exenamerO   r   r   )r	   _is_windowspsutil	Exceptionrg   _hermes_exe_shimsrh   ro   resolvelowerrF   r@   r3   r4   nextiterProcessparentsr   r   rB   rO   process_iterinfor   r   rA   )r   r   r   
shim_pathsshimr   seed	ancestorsancestoranc_exeanc_normmatches	proc_iterr1   r   rO   r   exe_normr   s                      r   #_detect_concurrent_hermes_instancesr   X  sG   . 44 	   			 55J&&{33 . .	.NN3t||~~..44667777 	. 	. 	.NN3t99??,,-----	. 	. "%k"2"2!3	}D&&''	t,,4466II 	 	 	III	! 	 	H",,..    0tG}}446677==??Z( 0 0 0w<<--//0:%% $$S%6%67777    H &	      &(G''(>(>(>??		   			  2 2	9DD 	 	 	H	hhuoohhuoo 	ckSL%8%8	(499,,..//5577HH$ 	( 	( 	(3xx~~''HHH	(z!!88F##5tCyy~DNNCHHc$ii0111Ns   ) 88-AB44>C54C5$I! 'E) (I! )E85I! 7E88I!  FI! 
F"I! !F""I! )A G*)I! *2HI! HI! ''II! 
II! II! !
I.-I.4J JJ$J,,
J98J90A L112M&%M&)rQ   )__doc__r3   r8   r5   pathlibr   r	   rg   r@   r~   tuplero   rP   booldictr   r   r!   r
   r   <module>r      sa    
			     



         %)u u uc(T/u 
%S/u u u up OC "C C CC C 
#t)_	C C C CL 59r r rr'*Tzr	%S/r r r r r rr
   