
    it:                     ,   d Z ddlZddlZddlZddlZddlZddlZddlmZmZ ddl	m
Z
 ddlmZ ddlmZmZ dZdZd	Zej        d
k    Z e            Zde
fdZde
fdZde
fdZdefdZdddededdfdZdedefdZdedede
fdZ dedee         fdZ!dedee         fdZ"dedefdZ#de$eef         defdZ%de$fdZ&de$eef         fdZ'd e
dee$eef                  fd!Z(d e
d"e$eef         ddfd#Z)dee$         fd$Z*d8d%Z+eeeeeeeed&d'ed(ed)ed*ed+ed,ed-ed.eddfd/Z,dee$eef                  fd0Z-d8d1Z.d9deded2ee$eef                  de/eee$eef                  f         fd3Z0dededdfd4Z1defd5Z2dee         fd6Z3defd7Z4dS ):u  
Gateway runtime status helpers.

Provides PID-file based detection of whether the gateway daemon is running,
used by send_message's check_fn to gate availability in the CLI.

The PID file lives at ``{HERMES_HOME}/gateway.pid``.  HERMES_HOME defaults to
``~/.hermes`` but can be overridden via the environment variable.  This means
separate HERMES_HOME directories naturally get separate PID files — a property
that will be useful when we add named profiles (multiple agents running
concurrently under distinct configurations).
    N)datetimetimezone)Pathget_hermes_home)AnyOptionalzhermes-gatewayzgateway_state.jsonzgateway-lockswin32returnc                  (    t                      } | dz  S )z@Return the path to the gateway PID file, respecting HERMES_HOME.zgateway.pidr   )homes    6/home/agentuser/.hermes/hermes-agent/gateway/status.py_get_pid_pathr       s    D-    c                  N    t                                          t                    S )z5Return the persisted runtime health/status file path.)r   	with_name_RUNTIME_STATUS_FILE r   r   _get_runtime_status_pathr   &   s    ??$$%9:::r   c                      t          j        d          } | rt          |           S t          t          j        dt          j                    dz  dz                      }|dz  t          z  S )zBReturn the machine-local directory for token-scoped gateway locks.HERMES_GATEWAY_LOCK_DIRXDG_STATE_HOMEz.localstatehermes)osgetenvr   r   _LOCKS_DIRNAME)override
state_homes     r   _get_lock_dirr    +   s`    y233H H~~bi 0$)++2H72RSSTTJ >11r   c                  b    t          j        t          j                                                  S N)r   nowr   utc	isoformatr   r   r   _utc_now_isor&   4   s     <%%//111r   F)forcepidr'   c                   |rt           r	 t          j        ddt          |           ddgddd          }n0# t          $ r# t          j        | t          j                   Y dS w xY w|j	        d	k    r6|j
        p|j        pd
                                }t          |pd|            dS |st          j        nt          t          dt          j                  }t          j        | |           dS )zTerminate a PID with platform-appropriate force semantics.

    POSIX uses SIGTERM/SIGKILL. Windows uses taskkill /T /F for true force-kill
    because os.kill(..., SIGTERM) is not equivalent to a tree-killing hard stop.
    taskkillz/PIDz/Tz/FT
   )capture_outputtexttimeoutNr    ztaskkill failed for PID SIGKILL)_IS_WINDOWS
subprocessrunstrFileNotFoundErrorr   killsignalSIGTERM
returncodestderrstdoutstripOSErrorgetattr)r(   r'   resultdetailssigs        r   terminate_pidrB   8   s      		^VSXXtT:#	  FF ! 	 	 	GC(((FF	 !!};;BBDDG'E%E%E%EFFF %
U&..769fn+U+UCGCs   *6 )A#"A#identityc                     t          j        |                     d                                                    d d         S )Nutf-8   )hashlibsha256encode	hexdigest)rC   s    r   _scope_hashrK   S   s3    >(//'2233==??DDr   scopec                 J    t                      |  dt          |           dz  S )N-z.lock)r    rK   )rL   rC   s     r   _get_scope_lock_pathrO   W   s*    ??DDH(=(=DDDDDr   c                     t          d|  d          }	 t          |                                                                d                   S # t          t
          t          t          t          f$ r Y dS w xY w)z:Return the kernel start time for a process when available./proc/z/stat   N)	r   int	read_textsplitr5   
IndexErrorPermissionError
ValueErrorr=   )r(   	stat_paths     r   _get_process_start_timerZ   [   sx    (c((())I9&&((..004555z?JP   tts   8A #A54A5c                    t          d|  d          }	 |                                }n# t          t          t          f$ r Y dS w xY w|sdS |                    dd                              dd                                          S )	z<Return the process command line as a space-separated string.rQ   z/cmdlineN        rE   ignore)errors)r   
read_bytesr5   rW   r=   replacedecoder<   )r(   cmdline_pathraws      r   _read_process_cmdlinere   e   s    ....//L%%''8   tt  t;;w%%,,WX,FFLLNNNs   * AAc                 d    t          |           sdS d}t          fd|D                       S )zBReturn True when the live PID still looks like the Hermes gateway.Fzhermes_cli.main gatewayzhermes_cli/main.py gatewayzhermes gatewayzgateway/run.pyc              3       K   | ]}|v V  	d S r"   r   .0patterncmdlines     r   	<genexpr>z._looks_like_gateway_process.<locals>.<genexpr>~   (      ::gw'!::::::r   )re   any)r(   patternsrl   s     @r   _looks_like_gateway_processrq   r   sI    #C((G uH ::::::::::r   recordc                    |                      d          t          k    rdS |                      d          }t          |t                    r|sdS d                    d |D                       d}t          fd|D                       S )zMValidate gateway identity from PID-file metadata when cmdline is unavailable.kindFargv c              3   4   K   | ]}t          |          V  d S r"   )r4   )rj   parts     r   rm   z-_record_looks_like_gateway.<locals>.<genexpr>   s(      22Ts4yy222222r   rg   c              3       K   | ]}|v V  	d S r"   r   ri   s     r   rm   z-_record_looks_like_gateway.<locals>.<genexpr>   rn   r   )get_GATEWAY_KIND
isinstancelistjoinro   )rr   ru   rp   rl   s      @r   _record_looks_like_gatewayr      s    zz&]**u::fDdD!!  uhh22T22222GH ::::::::::r   c                      t          j                    t          t          t          j                  t          t          j                              dS )N)r(   rt   ru   
start_time)r   getpidr{   r}   sysru   rZ   r   r   r   _build_pid_recordr      s9    y{{SX-bikk::	  r   c            	      r    t                      } |                     dd ddi t                      d           | S )NstartingFr   )gateway_stateexit_reasonrestart_requestedactive_agents	platforms
updated_at)r   updater&   )payloads    r   _build_runtime_status_recordr      sJ    !!GNN#""nn     Nr   pathc                 2   |                                  sd S 	 |                                                                 }n# t          $ r Y d S w xY w|sd S 	 t	          j        |          }n# t          j        $ r Y d S w xY wt          |t                    r|nd S r"   )	existsrT   r<   r=   jsonloadsJSONDecodeErrorr|   dict)r   rd   r   s      r   _read_json_filer      s    ;;== tnn$$&&   tt t*S//   tt $//977T9s!   &? 
AAA* *A=<A=r   c                     | j                             dd           |                     t          j        |                     d S )NTparentsexist_ok)parentmkdir
write_textr   dumps)r   r   s     r   _write_json_filer      s?    KdT222OODJw''(((((r   c                     t                      } |                                 sd S |                                                                 }|sd S 	 t	          j        |          }n9# t          j        $ r' 	 dt          |          icY S # t          $ r Y Y d S w xY ww xY wt          |t                    rd|iS t          |t                    r|S d S )Nr(   )r   r   rT   r<   r   r   r   rS   rX   r|   r   )pid_pathrd   r   s      r   _read_pid_recordr      s    H?? t





$
$
&
&C t*S//   	3s88$$$$ 	 	 	444	 '3  w'4   4s0   A% %B5BB
BBBBc                  V    t          t                      t                                 dS )zCWrite the current process PID and metadata to the gateway PID file.N)r   r   r   r   r   r   write_pid_filer      s#    ]__&7&9&9:::::r   )r   r   r   r   platformplatform_state
error_codeerror_messager   r   r   r   r   r   r   r   c                    t                      }t          |          pt                      }	|	                    di            |	                    dt                     t          j                    |	d<   t          t          j                              |	d<   t                      |	d<   | t          ur| |	d<   |t          ur||	d<   |t          urt          |          |	d<   |t          ur t          d	t          |                    |	d
<   |t          urb|	d                             |i           }
|t          ur||
d<   |t          ur||
d<   |t          ur||
d<   t                      |
d<   |
|	d         |<   t          ||	           dS )zBPersist gateway runtime health information for diagnostics/status.r   rt   r(   r   r   r   r   r   r   r   r   r   r   N)r   r   r   
setdefaultr{   r   r   rZ   r&   _UNSETboolmaxrS   rz   r   )r   r   r   r   r   r   r   r   r   r   platform_payloads              r   write_runtime_statusr      s    $%%Dd##E'C'E'EG{B'''v}---Y[[GEN3BIKK@@GL(NNGLF""#0 &  !,&&'+,='>'>#$F""#&q#m*<*<#=#= v";/33HbAA''(6W%V##-7\*&&0=_-)5&)9X&T7#####r   c                  8    t          t                                S )z=Read the persisted gateway runtime health/status information.)r   r   r   r   r   read_runtime_statusr     s    355666r   c                  6   	 t                      } t          |           }|Q	 t          |d                   }n# t          t          t
          f$ r d}Y nw xY w||t          j                    k    rdS |                     d           dS # t          $ r Y dS w xY w)ah  Remove the gateway PID file, but only if it belongs to this process.

    During --replace handoffs, the old process's atexit handler can fire AFTER
    the new process has written its own PID file.  Blindly removing the file
    would delete the new process's record, leaving the gateway running with no
    PID file (invisible to ``get_running_pid()``).
    Nr(   T
missing_ok)
r   r   rS   KeyError	TypeErrorrX   r   r   unlink	Exception)r   rr   file_pids      r   remove_pid_filer     s     && ve}--i4       #BIKK(?(?t$$$$$   s7   B
 8 B
 AB
 AB
 2B
 

BBmetadatac                    t          | |          }|j                            dd           i t                      | t	          |          |pi t                      d}t          |          }|<|                                r(	 |                    d           n# t          $ r Y nw xY w|r	 t          |d                   }n# t          t          t          f$ r d}Y nw xY w|t          j                    k    r@|                    d          |                    d          k    rt#          ||           d|fS |du }|s	 t          j        |d           t'          |          }|                    d          |||                    d          k    rd}|s	 t)          d	| d
          }	|	                                r`|	                                                                D ]9}
|
                    d          r"|
                                d         }|dv rd} n:n1# t          t2          f$ r Y nw xY wn# t4          t2          f$ r d}Y nw xY w|r(	 |                    d           n# t          $ r Y nw xY wd|fS 	 t          j        |t          j        t          j        z  t          j        z            }n!# t>          $ r dt          |          fcY S w xY w	 t          j         |dd          5 }tC          j"        ||           ddd           n# 1 swxY w Y   n7# tF          $ r* 	 |                    d           n# t          $ r Y nw xY w w xY wdS )zAcquire a machine-local lock keyed by scope + identity.

    Used to prevent multiple local gateways from using the same external identity
    at once (e.g. the same Telegram bot token across different HERMES_HOME dirs).
    Tr   )rL   identity_hashr   r   Nr   r(   r   r   rQ   z/statuszState:   )TtFwrE   )encoding)TN)$rO   r   r   r   rK   r&   r   r   r   r=   rS   r   r   rX   r   r   rz   r   r6   rZ   r   rT   
splitlines
startswithrU   rW   ProcessLookupErroropenO_CREATO_EXCLO_WRONLYFileExistsErrorfdopenr   dumpr   )rL   rC   r   	lock_pathrr   existingexisting_pidstalecurrent_start_proc_status_line_statefdhandles                 r   acquire_scoped_lockr   $  sn    %UH55I4$777


$X..N"nn  F y))HI,,..
	---- 	 	 	D	 -#	 x//LL)Z0 	  	  	 LLL	  29;;&&8<<+E+ET`IaIa+a+aY///>!$ 	a((( !8 E ELL..:%1%l)C)CCC E  
'+,J\,J,J,J'K'K'..00 *)5)?)?)A)A)L)L)N)N * *#(#3#3H#=#= !*-2[[]]1-=F'-';';04$)E	!*
 $_5    '8   2  	#  D 1111    (?"1WY
RY 6 DEE 1 1 1oi0000001Yr3111 	&VIff%%%	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	&   	---- 	 	 	D	 :s   B 
B)(B)0C C"!C"H= BH% %H98H9=III0 0
I=<I=9J? ?KK!L& 8LL& LL& !L"L& &
M1MM
MMMMc                 f   t          | |          }t          |          }|sdS |                    d          t          j                    k    rdS |                    d          t          t          j                              k    rdS 	 |                    d           dS # t          $ r Y dS w xY w)zDRelease a previously-acquired scope lock when owned by this process.Nr(   r   Tr   )rO   r   rz   r   r   rZ   r   r=   )rL   rC   r   r   s       r   release_scoped_lockr   }  s    $UH55Iy))H ||Ebikk))||L!!%<RY[[%I%IIID)))))   s   
B" "
B0/B0c                      t                      } d}|                                 rD|                     d          D ].}	 |                    d           |dz  }# t          $ r Y +w xY w|S )zRemove all scoped lock files in the lock directory.

    Called during --replace to clean up stale locks left by stopped/killed
    gateway processes that did not release their locks gracefully.
    Returns the number of lock files removed.
    r   z*.lockTr   r   )r    r   globr   r=   )lock_dirremoved	lock_files      r   release_all_scoped_locksr     s     HG !x00 	 	I  D 1111   Ns   A
A&%A&c                     t                      } | st                       dS 	 t          | d                   }n,# t          t          t
          f$ r t                       Y dS w xY w	 t          j        |d           n&# t          t          f$ r t                       Y dS w xY w| 
                    d          }t          |          }||||k    rt                       dS t          |          st          |           st                       dS |S )zReturn the PID of a running gateway instance, or ``None``.

    Checks the PID file and verifies the process is actually alive.
    Cleans up stale PID files automatically.
    Nr(   r   r   )r   r   rS   r   r   rX   r   r6   r   rW   rz   rZ   rq   r   )rr   r(   recorded_startr   s       r   get_running_pidr     s;    F t&-  i,   tt
Q0   tt ZZ--N+C00M!m&?MUcDcDct&s++ )&11 	4Js!   8 %A! A!%A; ;BBc                  "    t                      duS )z1Check if the gateway daemon is currently running.N)r   r   r   r   is_gateway_runningr     s    D((r   )r   Nr"   )5__doc__rG   r   r   r7   r2   r   r   r   pathlibr   hermes_constantsr   typingr   r	   r{   r   r   r   r1   objectr   r   r   r    r4   r&   rS   r   rB   rK   rO   rZ   re   rq   r   r   r   r   r   r   r   r   r   r   r   tupler   r   r   r   r   r   r   r   <module>r      s
      				      



 ' ' ' ' ' ' ' '       , , , , , ,                 +  lg%	 t        ;$ ; ; ; ;
2t 2 2 2 22c 2 2 2 2 .3   s d t    6E# E# E E E EE Es Et E E E E #    
Os 
Ox} 
O 
O 
O 
O;S ;T ; ; ; ;;tCH~ ;$ ; ; ; ;&4    
d38n 
 
 
 
:$ :8DcN#; : : : : )4 )$sCx. )T ) ) ) )
(4.    0; ; ; ;  # ($ ($ ($($ ($ 	($
 ($ ($ ($ ($ ($ 
($ ($ ($ ($V7Xd38n5 7 7 7 7
   0V Vs Vc VXd3PS8n=U Vafgkmuvz{~  AD  |D  wE  nF  hF  bG V V V Vrs c d     #    &"# " " " "J)D ) ) ) ) ) )r   