
    }-j5                        U d Z ddlm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ZdZd	Zd
ed<   d(dZdedd)dZdedd)dZdedd)dZd Zd*dZd+dZd,d"Zd-d$Zd.d&Zd/d'ZdS )0a  Hermes agent/gateway heartbeat payload helpers (#716, #1879).

The WebUI process is not always paired with a long-running Hermes gateway. Some
setups use WebUI only, while self-hosted messaging deployments run a separate
Hermes gateway daemon that records runtime metadata in the Hermes Agent home.
This module turns those existing safe runtime signals into a small UI-facing
heartbeat without shelling out or adding psutil as a hard dependency.

Cross-container note (#1879): ``gateway.status.get_running_pid()`` uses
``fcntl.flock`` and ``os.kill(pid, 0)``, both of which require the caller to
share a PID namespace with the gateway process. In multi-container deployments
where the WebUI runs separately from ``hermes-agent`` and only a Hermes data
volume is shared, those checks always return ``None`` and the dashboard
incorrectly shows "Gateway not running". To stay accurate without forcing a
``pid: "service:hermes-agent"`` compose workaround, we accept a recent
``updated_at`` timestamp on ``gateway_state.json`` (combined with
``gateway_state == "running"``) as an equivalent live-process signal.  Older
gateway builds do not refresh that file periodically, so a stale
``gateway_state == "running"`` record is treated as inconclusive rather than a
confirmed outage.
    )annotationsN)datetimetimezone)Path)Anyzgateway.pidzgateway_state.jsong      ^@floatGATEWAY_FRESHNESS_THRESHOLD_Sreturnstrc                 b    t          j        t          j                                                  S )N)r   nowr   utc	isoformat     &/root/hermes-webui/api/agent_health.py_checked_atr   *   s     <%%//111r   )r   threshold_sruntime_statusdict[str, Any] | Noner   datetime | Noner   boolc                  t          | t                    sdS |                     d          dk    rdS |                     d          }t          |t                    r|sdS 	 t	          j        |          }n# t          t          f$ r Y dS w xY w|j        dS ||nt	          j	        t          j                  }||z
                                  }|dk     r| |k    S ||k    S )u  Return ``True`` when ``gateway_state.json`` looks freshly written.

    "Fresh" means the gateway self-reported ``running`` and the ``updated_at``
    ISO-8601 timestamp is no older than ``threshold_s`` seconds. This is the
    cross-container liveness signal used when ``get_running_pid()`` returns
    ``None`` purely because of PID-namespace isolation (#1879).

    Any unparseable input is treated as "not fresh" — a stale or missing
    timestamp must never report alive.
    Fgateway_staterunning
updated_atNr   
isinstancedictgetr   r   fromisoformat	TypeError
ValueErrortzinfor   r   r   total_secondsr   r   r   raw_updated_atr   	referenceage_ss          r   _runtime_status_is_freshr*   .   s     nd++ u/**i77u#''55Nnc** . u+N;;

z"   uu   uHL,F,FI#2244Eqyy
 v$$K   "A7 7BBc                  t          | t                    sdS |                     d          dk    rdS |                     d          }t          |t                    r|sdS 	 t	          j        |          }n# t          t          f$ r Y dS w xY w|j        dS ||nt	          j	        t          j                  }||z
                                  }||k    S )a  Return ``True`` for an old clean-stop root gateway state.

    A user may run only profile-scoped gateways while a root
    ``gateway_state.json`` from an older, intentionally stopped gateway remains
    on disk (#1944). Treat that stale stopped file like "no root gateway
    configured" so the heartbeat banner does not keep warning about a service
    the user is not running. Fresh stopped state still reports down.
    Fr   stoppedr   r   r&   s          r    _runtime_status_is_stale_stoppedr.   `        nd++ u/**i77u#''55Nnc** . u+N;;

z"   uu uHL,F,FI#2244E;r+   c                  t          | t                    sdS |                     d          dk    rdS |                     d          }t          |t                    r|sdS 	 t	          j        |          }n# t          t          f$ r Y dS w xY w|j        dS ||nt	          j	        t          j                  }||z
                                  }||k    S )a  Return ``True`` when the gateway last self-reported running, but stale.

    WebUI often runs in a separate container from the gateway. In that shape PID
    checks can be impossible, and older gateway versions only update
    ``gateway_state.json`` on lifecycle/platform changes. A stale ``running``
    file therefore means "not enough information from WebUI" rather than
    "gateway is down".
    Fr   r   r   r   r&   s          r    _runtime_status_is_stale_runningr1      r/   r+   c                 *    t          j        d          S )zJLoad gateway.status lazily so tests and WebUI-only installs stay isolated.gateway.status)	importlibimport_moduler   r   r   _gateway_status_moduler6      s    "#3444r   Path | Nonec                 V    	 ddl m}   |             t          z  S # t          $ r Y dS w xY w)a  Return the root Hermes gateway PID path.

    Gateway runtime files are root-level singletons.  A profile-scoped WebUI
    process may have HERMES_HOME=<root>/profiles/<name>, but gateway.pid,
    gateway.lock, and gateway_state.json still live under <root>.
    r   get_default_hermes_rootN)hermes_constantsr:   _GATEWAY_PID_FILE	Exceptionr9   s    r   _gateway_root_pid_pathr>      sP    <<<<<<&&((+<<<   tts    
((pathr   c                    	 t          j        |                     d                    }n## t          t          t           j        f$ r Y d S w xY wt          |t                    r|S d S )Nzutf-8)encoding)jsonloads	read_textOSErrorUnicodeDecodeErrorJSONDecodeErrorr   r   )r?   payloads     r   _read_runtime_status_pathrI      so    *T^^W^==>>')=>   tt'4   4s   (+ A
Agateway_statusr   pid_pathc           	        | j         }|	  ||          S # t          $ r 	  ||          cY S # t          $ ru t          | dd          dk    st          | d          rMt	          t          | dt
                              }t          |                    |                    }||cY cY S Y nw xY wY nw xY w |            S )N)rK   __name__ r3   _read_json_file_RUNTIME_STATUS_FILE)read_runtime_statusr"   getattrhasattrr   _GATEWAY_RUNTIME_STATUS_FILErI   	with_name)rJ   rK   rQ   runtime_status_filer   s        r   _read_gateway_runtime_statusrW      s   (<	.&&9999 	. 	. 	..**844444 
. 
. 
.>:r::>NNNRY"%S SN +.0FHdee+ +' &?x?Q?QRe?f?f%g%gN%1------
.	.    s6    
B4
/B4A8B.'B4+B4-B..B43B4
int | Nonec                    | j         }|>	  ||d          S # t          $ r# 	  ||d          cY S # t          $ r Y nw xY wY nw xY w	  |d          S # t          $ r  |            cY S w xY w)NF)rK   cleanup_stale)rZ   )get_running_pidr"   )rJ   rK   r[   s      r   _gateway_running_pidr\      s    $4O	"?HEJJJJ 	 	 	&xuEEEEEE   		
!U3333 ! ! !     !s<    
A2A
?A?AA	A A,+A,dict[str, Any]c                   t          | t                    si S i }|                     d          }t          |t                    r|r||d<   |                     d          }t          |t                    r|r||d<   	 t	          dt          |                     d          pd                    |d<   n# t          t          f$ r Y nw xY w|                     d          }t          |t                    rt          |          |d<   i }|	                                D ]`}t          |t                    s|                    d          }t          |t                    r|r|                    |d          dz   ||<   a|r||d	<   |S )
a  Return only non-sensitive runtime fields for the browser.

    gateway.status records argv/PID metadata so the CLI can validate process
    identity. The WebUI alert only needs health semantics, never raw command
    lines, paths, environment, or tokens.
    r   r   r   active_agents	platformsplatform_countstate   platform_states)
r   r   r    r   maxintr"   r#   lenvalues)r   detailsr   r   r`   statesrH   rb   s           r   _runtime_detail_subsetrk      s    nd++ 	 G"&&77M-%% 1- 1#0 ##L11J*c"" +z + *#&q#n.@.@.Q.Q.VUV*W*W#X#X  z"    "";//I)T"" 
0$'	NN !!# '')) 	9 	9Ggt,, KK((E%%% 9% 9 &

5! 4 4q 8u 	0)/G%&Ns   =5B3 3CCc                 x   t                      } 	 t                      }n3# t          $ r&}d| ddt          |          j        ddcY d}~S d}~ww xY wt                      }d}	 t          ||          }n# t          $ r d}Y nw xY w	 t          ||          }n# t          $ r d}Y nw xY wt          |          }|
d| ddi|dS t          |          rd| dd	d
|dS t          |          rd| ddd
|dS t          |          rd| ddd
|dS t          |t                    rd| ddd
|dS d| ddd
dS )a  Return `{alive, checked_at, details}` for the Hermes gateway/agent.

    `alive` is intentionally tri-state:
      * True: a gateway runtime signal says the process is alive.
      * False: gateway metadata exists, but no live gateway process owns it.
      * None: no gateway metadata/status is available, so this WebUI setup is
        probably not configured with a separate gateway process.
    Nunknowngateway_status_unavailable)rb   reasonerror)alive
checked_atri   Trb   rq   cross_container_freshness)rb   ro   gateway_stale_stopped_stategateway_stale_running_stateFdowngateway_not_runninggateway_not_configured)r   r6   r=   typerM   r>   rW   r\   rk   r*   r.   r1   r   r   )rr   rJ   excgateway_pid_pathr   running_pidsafe_detailss          r   build_agent_health_payloadr~     sz    J
/11 	
 	
 	
$"6c+ 
 
 	
 	
 	
 	
 	
 	
	
 .//N5nFVWW   *>;KLL    *.99L$
 
 	
  // 	
$ 5  
 
 	
 (77 	
$"7  
 
 	
 (77 	
$"7  
 
 	
 .$'' 	
$/  
 
 	
  .
 
  s>    
AA
A
A#A4 4BBB B'&B')r
   r   )r   r   r   r   r   r   r
   r   )r
   r7   )r?   r   r
   r   )rJ   r   rK   r7   r
   r   )rJ   r   rK   r7   r
   rX   )r   r   r
   r]   )r
   r]   )__doc__
__future__r   r4   rB   r   r   pathlibr   typingr   r<   rT   r	   __annotations__r   r*   r.   r1   r6   r>   rI   rW   r\   rk   r~   r   r   r   <module>r      s    , # " " " " "      ' ' ' ' ' ' ' '            ! 3  (-  , , , ,2 2 2 2  6	/  /  /  /  /  / j  6	           L  6	           F5 5 5
      ! ! ! !,! ! ! !"% % % %Ph h h h h hr   