+
    lHàj4f  ã                  óh  a € 0 t $ R t^ RIHt ^ RIt^ RIt^ RIt^ RIHt ^RI	H
t
 R R ltR R ltR	 R
 ltRFR R lltRFR R lltRtRFR R llt/ tR]R&   RsR R ltR R ltR R ltR R ltRFR R lltR R  ltR! R" ltR# R$ ltR% R& ltR' R( ltR) R* ltR+ R, lt RGR- R. llt!R/ R0 lt"RHt#Rs$]PJ                  ! 4       t&R1 R2 lt'R3 R4 lt(])! R5]! R6R74      4      t*Rs+]PJ                  ! 4       t,]PZ                  ! ^4      t.R8 R9 lt/R: R; lt0R< R= lt1R> R? lt2R@ RA lt3RB RC lt4RD RE lt5R# )Iu›  Session flush / reaping / orphan sweep / cross-backend heartbeat: exit-flush signal handlers, idle + LRU
eviction, orphaned session-row sweep, backend heartbeat refresher. Bodies are rebound onto server.py's
globals at install time (method_ctx.bind_module), so they reference server.py globals bare â€” including the
knobs _SESSION_TTL_S, _REAPER_SCAN_S, _EXIT_FLUSH_BUDGET_S and _INCREMENTAL_FLUSH_INTERVAL_S.
)ÚannotationsN)Ú	env_float)Úbind_modulec               ó    € V ^8„  d   QhRRRR/# ©é   Úsessionzdict | NoneÚreturnÚbool© )Úformats   "Ú?/home/ubuntu/.hermes/hermes-agent/tui_gateway/session_reaper.pyÚ__annotate__r      s   € ÷ bñ b¨ð b¸ñ bó    c                ó¨   € \        V \        4      '       d   V P                  R4      MRp\        \	        VR4      ;'       d    \        VRR4      4      # )z”Would :func:`_flush_session_messages` write anything for this session? (The exit flush counts
these to tell a complete flush from a budget overrun.)ÚagentNÚ_persist_sessionÚ_session_messages)Ú
isinstanceÚdictÚgetr
   ÚhasattrÚgetattr)r   r   s   & r   Ú_session_has_pending_flushr      sF   € ô %/¨w¼×$=Ò$=ˆG�K‰K˜Ô À4€EÜ”˜Ð1Ó2×`Ð`´w¸uÐFYÐ[_Ó7`ÓaÐar   c               ó    € V ^8„  d   QhRRRR/# r   r   )r   s   "r   r   r      s   € ÷ ñ  [ð °Tñ r   c                ó€  € V '       d   V P                  R4      MRp\        VR4      '       d   \        VRR4      MRpV'       g   R#  \        T ;'       g    / RR7      ;_uu_ 4        VP	                  V4       RRR4       R#   + '       g   i     R# ; i  \
         d    \        P                  RRR	7        R# i ; i)
zÀBest-effort durable flush of one session's transcript via ``agent._persist_session`` (same marker-deduped
contract as ``_finalize_session``: repeated calls never duplicate rows).

See #13121.
r   Nr   r   F)Úhydrate_secretsTz incremental session flush failed©Úexc_info)r   r   r   Ú_session_profile_runtime_scoper   Ú	ExceptionÚloggerÚdebug)r   r   Úsnapshots   &  r   Ú_flush_session_messagesr$      s™   € ÷ %,ˆG�K‰K˜Ô °€EÜ<CÀEÐK]×<^Ò<^Œw�uÐ1°4Ô8Ðdh€HßÙðÜ+¨G¯M¨M°rÈ5×QÖQØ×"Ñ" 8Ô,÷ Rá÷ RÖQáûÜô Ü�‰Ð7À$ˆÔGÚðús6   ÁB ÁB Á'BÁ9B ÂB	ÂB ÂB Â"B=Â<B=c               ó   € V ^8„  d   QhRR/# )r   r	   Úlistr   )r   s   "r   r   r   ;   s   € ÷ (ñ ( $ñ (r   c                 ó–   € \         ;_uu_ 4        \        \        P                  4       4      uuR R R 4       #   + '       g   i     R # ; i©N)Ú_sessions_lockr&   Ú	_sessionsÚvaluesr   r   r   Ú_reaper_session_snapshotr,   ;   s$   € ß	ŽÜ”I×$Ñ$Ó&Ó'÷ 
��‹ús	   �7·A	c               ó    € V ^8„  d   QhRRRR/# )r   Únowúfloat | Noner	   Úintr   )r   s   "r   r   r   @   s   € ÷ ñ ˜|ð °sñ r   c                ó‚  € \         ^ 8:  d   ^ # V f   \        P                  4       MT p ^ p\        4        F‡  p\	        V\
        4      '       d   VP                  R4      '       d   K2  \        VP                  R4      ;'       g    R4      pV'       d   W,
          \         8  d   Kq  V\        V4      ,          pWR&   K‰  	  V# )aL  Periodic incremental flush, driven by the idle-reaper scan. Skips ``running`` sessions: the turn thread
owns mid-turn persistence and mutates the live message list, so racing it from the reaper thread is never
safe. Idle sessions flush at most once per ``_INCREMENTAL_FLUSH_INTERVAL_S``; ``now`` (monotonic) is
injectable for tests.ÚrunningÚ_last_incremental_flushç        )	Ú_INCREMENTAL_FLUSH_INTERVAL_SÚtimeÚ	monotonicr,   r   r   r   Úfloatr$   )r.   Úflushedr   Úlasts   &   r   Ú_flush_dirty_sessionsr;   @   s�   € ô
 %¨Ô)ÙØ!škŒ$�.‰.Ô
¨s€CØ€GÜ+Ö-ˆÜ˜'¤4×(Ò(¨G¯K©K¸	×,BÒ,BÙÜ�W—[‘[Ð!:Ó;×BÐB¸sÓCˆß�S•ZÔ#@Ô@ÙØÔ*¨7Ó3Õ3ˆØ-0Ð)Ó*ñ .ð €Nr   c               ó    € V ^8„  d   QhRRRR/# )r   Úbudget_sr/   r	   r0   r   )r   s   "r   r   r   T   s   € ÷ ñ ¨,ð À#ñ r   c                ó|  aaa€ V f   \         M\        RV 4      oS^ 8:  d   ^ # R^ /o\        4       o\        R S 4       4      pR VVV3R llp\        P
                  ! VRRR7      pVP                  4        VP                  S4       SR,          V8  d    \        P                  R	SR,          VS4       SR,          # )
u  Bounded flush of ALL in-memory sessions on the way out, on a daemon worker joined with the budget so a
hung SQLite write can't block exit past ``HERMES_TUI_EXIT_FLUSH_BUDGET_S`` (default 5s). Running sessions
are included â€” the process is dying, a partial transcript beats loss.r4   r9   c              3  óL   "  € T F  p\        V4      '       g   K  ^x € K  	  R# 5i)é   N)r   )Ú.0Úss   & r   Ú	<genexpr>Ú._flush_sessions_before_exit.<locals>.<genexpr>]   s   é € ÐI™x˜!Ô+EÀa×+H—A’A›xùs   ‚$š
$c               ó   € V ^8„  d   QhRR/# ©r   r	   ÚNoner   )r   s   "r   r   Ú1_flush_sessions_before_exit.<locals>.__annotate___   s   € ÷ Bñ B�$ñ Br   c                 óÄ   <€ \         P                  4       S,           p S F=  p\         P                  4       V 8¼  d    R# SR ;;,          \        V4      ,          uu&   K?  	  R# )r9   N)r6   r7   r$   )Údeadliner   ÚbudgetÚresultÚsessionss     €€€r   Ú_runÚ)_flush_sessions_before_exit.<locals>._run_   sF   ø€ Ü—>‘>Ó# fÕ,ˆÛˆGÜ�~‰~Ó 8Ô+ÚØ�9×Ô!8¸Ó!AÕAÕó  r   Tzhermes-exit-flush)ÚtargetÚdaemonÚnamezˆExit flush persisted %d of %d in-memory session transcript(s) within %.1fs; the rest may have been lost (HERMES_TUI_EXIT_FLUSH_BUDGET_S))
Ú_EXIT_FLUSH_BUDGET_SÚmaxr,   ÚsumÚ	threadingÚThreadÚstartÚjoinr!   Úwarning)r=   Ú	flushablerN   ÚworkerrK   rL   rM   s   &   @@@r   Ú_flush_sessions_before_exitr]   T   s¶   ú€ ð &.Ò%5Õ!¼3¸sÀHÓ;M€FØ�„{ÙØ˜ˆ^€FÜ'Ó)€HÜÑI™xÓIÓI€I÷Bò Bô ×Ò T°$Ð=PÔQ€FØ
‡L�L„NØ
‡K�K�Ôð ˆiÕ˜9Ô$Ü�‰ðBà�9Õ˜y¨&ô	2ð �)ÕÐr   g      à?c               ó    € V ^8„  d   QhRRRR/# )r   r=   r/   r	   rG   r   )r   s   "r   r   r   x   s   € ÷ ñ  lð ¸dñ r   c           	     óê  a
€ \         ;_uu_ 4        \        P                  4        UUu. uF!  w  rVP                  R4      '       g   K  W3NK#  	  pppRRR4       . o
X F~  w  r\        P
                  ! \        4      ;_uu_ 4        \        W4       RRR4       VP                  R4      ;pf   KR  V\        P                  ! 4       Jg   Km  S
P                  V4       K€  	  V f   \        M\        RV 4      p\        P                  4       V,           pR V
3R llpV! Wv^,          ,
          4       ^ RIHp	 V	! RR	7       V! V4       R# u uppi   + '       g   i     EL; i  + '       g   i     LÙ; i)
aÍ  Interrupt every in-flight turn and give it ``budget_s`` to settle, so a running tool call ends
with a result the teardown's final persist records. A foreground command runs in its own process
group and would outlive the gateway, reparented to init. One still alive halfway through the budget
ignored the interrupt's SIGTERM: SIGKILL it then, early enough for its result to land as well (the
interrupt's own TERM, 1s, KILL outlasts the SIGTERM path's ~1s grace).r2   NÚ_run_threadr4   c               ó    € V ^8„  d   QhRRRR/# )r   Úuntilr8   r	   rG   r   )r   s   "r   r   Ú-_stop_turns_before_exit.<locals>.__annotate__‰   s   € ÷ 7ñ 7�Uð 7˜tñ 7r   c           	     ó‚   <€ S F7  pVP                  \        R V \        P                  4       ,
          4      4       K9  	  R# )r4   N)rY   rT   r6   r7   )rb   ÚtÚthreadss   & €r   Ú_joinÚ&_stop_turns_before_exit.<locals>._join‰   s,   ø€ ÛˆAØ�F‰F”3�s˜E¤D§N¡NÓ$4Õ4Ó5Ö6ó r   )Úkill_live_foreground_processesT)r.   )r)   r*   Úitemsr   Ú
contextlibÚsuppressr    Ú_interrupt_session_turnrV   Úcurrent_threadÚappendÚ_EXIT_TURN_SETTLE_SrT   r6   r7   Útools.environments.baseri   )r=   ÚsidrB   r2   r   re   rK   rJ   rg   ri   rf   s   &         @r   Ú_stop_turns_before_exitrs   x   s	  ø€ ÷ 
ŽÜ*3¯/©/Ô*;ÔPÑ*;¡ ¸q¿u¹uÀY×?O”8�C“8Ñ*;ˆÑP÷ 
à€GÛ‰ˆÜ× Ò ¤×+Õ+Ü# CÔ1÷ ,à—‘˜]Ó+Ð+ˆAÔ8¸QÄi×F^ÒF^ÓF`Õ=`Ø�N‰N˜1Öñ	  ð
 %-Ò$4Õ ¼#¸cÀ8Ó:L€FÜ�~‰~Ó &Õ(€H÷7ð 7ñ 
ˆ(˜a•ZÕ
Ô ÝFÙ" tÕ,Ù	ˆ(†Oùó# Q÷ 
�ˆú÷ ,×+ús.   �E¨E
ÁE
ÁEÂE"ÅEÅE	Å"E2zdict[int, Any]Ú_exit_flush_prev_handlersFc               ó   € V ^8„  d   QhRR/# rF   r   )r   s   "r   r   r   —   s   € ÷ :ñ :°ñ :r   c                óŽ  € ^ RI p\        P                  V 4      pW2P                  J d   R# \        P
                  ! \        4      ;_uu_ 4        \        4        RRR4       \        P
                  ! \        4      ;_uu_ 4        \        4        RRR4       \        V4      '       d   V! W4       R#  VP                  WP                  4       \        P                  \        P                  4       V 4       R#   + '       g   i     L¦; i  + '       g   i     L„; i  \         d    \        ^€\        T 4      ,           4      Rhi ; i)u¸   Flush in-memory sessions, then hand off to the prior handler (uvicorn's graceful shutdown, a supervisor's
handler, or the default disposition) â€” this only *prepends* a bounded flush.N)Úsignalrt   r   ÚSIG_IGNrk   rl   r    r]   rs   ÚcallableÚSIG_DFLÚosÚkillÚgetpidÚ
SystemExitr0   )ÚsignumÚframeÚ_signalÚprevs   &&  r   Ú_handle_exit_flush_signalrƒ   —   sÒ   € ó Ü$×(Ñ(¨Ó0€DØ�‰Óñ 	Ü	×	Ò	œY×	'Õ	'Ü#Ô%÷ 
(ô 
×	Ò	œY×	'Õ	'ÜÔ!÷ 
(ä�‡~‚~ÙˆVÖð	:Ø�N‰N˜6§?¡?Ô3Ü�G‰G”B—I‘I“K Ö(÷ 
(×	'ú÷ 
(×	'ûô ô 	:Ü˜S¤3 v£;Õ.Ó/°TÐ9ð	:ús%   ÁC6ÂD	Â1AD Ã6D	Ä	D	Ä(Ec               ó   € V ^8„  d   QhRR/# ©r   r	   r
   r   )r   s   "r   r   r   ²   s   € ÷ ñ ¨Dñ r   c                 óÂ  € \         '       d   R# \        P                  ! 4       \        P                  ! 4       Jd   R# ^ RIp RpV P
                  V P                  3 Fj  p\        P                  ! \        \        \        4      ;_uu_ 4        V P                  V4      pV P	                  V\        4       V\        V&   RpRRR4       Kl  	  Vs V#   + '       g   i     Kƒ  ; i)aR  Install chaining SIGTERM/SIGINT flush handlers (main thread only). Called before uvicorn takes over
signals: its ``capture_signals()`` saves these as the "original" handlers and re-raises into them after
graceful shutdown, so the flush also covers terminations outside uvicorn's serve window. Idempotent; False
off-main-thread/on failure.TFN)Ú_exit_flush_handlers_installedrV   rn   Úmain_threadrw   ÚSIGTERMÚSIGINTrk   rl   Ú
ValueErrorÚOSErrorÚRuntimeErrorÚ	getsignalrƒ   rt   )r�   Ú	installedr   r‚   s       r   Ú"install_exit_flush_signal_handlersr�   ²   s±   € ÷ &Ó%ÙÜ×ÒÓ!¬×)>Ò)>Ó)@Ó@ÙÛØ€IØ—?‘? G§N¡NÓ3ˆÜ× Ò ¤¬W´l×CÕCØ×$Ñ$ VÓ,ˆDØ�N‰N˜6Ô#<Ô=Ø04Ô% fÑ-ØˆI÷	 DÑCñ 4ð &/Ð"ØÐ÷ D×CÐCús   Â
3CÃCc               ó   € V ^8„  d   QhRR/# r…   r   )r   s   "r   r   r   È   s   € ÷ 7ñ 7 Tñ 7r   c                ó  € V \         J d   R # \        V \        4      '       dQ   \        ;QJ d*    R V P	                  4        4       F  '       d   K   R# 	  R # ! R V P	                  4        4       4      # \        V RR4      R J # )Tc              3  ó8   "  € T F  p\        V4      x € K  	  R # 5ir(   )Ú_transport_is_dead)rA   Úpeers   & r   rC   Ú%_transport_is_dead.<locals>.<genexpr>Õ   s   é € ÐOÑ8N°Ô% d×+Ð+Ó8Nùs   ‚FÚ_closedN)Ú_detached_ws_transportr   ÚFanoutTransportÚallÚ
transportsr   )Ú	transports   &r   r”   r”   È   sn   € ð Ô*Ó*ÙÜ�)œ_×-Ò-÷ ‹sÑO¸	×8LÑ8LÔ8NÓO�sŒsÐOŠsÐOˆsÑO¸	×8LÑ8LÔ8NÓOÓOÐOÜ�9˜i¨Ó.°$Ð6Ð6r   c               ó$   € V ^8„  d   QhRRRRRR/# ©r   rr   Ústrr   r   r	   r
   r   )r   s   "r   r   r   Ù   s!   € ÷ 
8ñ 
8 3ð 
8°ð 
8¸$ñ 
8r   c                ó4  € VP                  R4      '       g#   \        V 4      '       g   \        W4      '       d   R# VP                  R4      pVe0   VP                  4       '       g   VP                  R4      '       g   R# \	        VP                  R4      4      # )aV  Shared hard exemptions for both reapers (the LRU cap applies them WITHOUT the age gate: eligible the moment
it loses its client): never evict a session mid-turn, awaiting input, still building, owning live delegated
work, or on a live transport. Lazy watch sessions never start a build, so their unset agent_ready must not
make them immortal.r2   FÚagent_readyÚlazyrœ   )r   Ú_session_pending_kindÚ_session_has_active_delegationsÚis_setr”   )rr   r   Úreadys   && r   Ú_session_is_lru_evictabler§   Ù   sp   € ð
 ‡{�{�9×ÒÔ!6°s×!;Ò!;Ô?^Ð_b×?lÒ?lÙØ�K‰K˜Ó&€EØÒ §¡§¢¸¿¹ÀF×8KÒ8KÙÜ˜gŸk™k¨+Ó6Ó7Ð7r   c               ó    € V ^8„  d   QhRRRR/# )r   Úexcludez
str | Noner	   r
   r   )r   s   "r   r   r   æ   s   € ÷ 	Gñ 	G ð 	G°tñ 	Gr   c                ó*  € \         ;_uu_ 4        \        P                  4        UUu. uF  w  rW8w  g   K  W3NK  	  pppRRR4       \        ;QJ d    R X 4       F  '       d   K   R# 	  R# ! R X 4       4      # u uppi   + '       g   i     LJ; i)u4  No session but ``exclude`` is mid-turn, building, awaiting input, holding live delegations, or on a live
transport. A non-forced memory trim holds the GIL (gc.collect) and every glibc arena lock (malloc_trim) for
its whole duration â€” 20-50 s on multi-GB heaps â€” which stalls the event loop, drops WS clients past the
write deadline and interrupts their turns (#58576); this is the moment it costs no other session. The
predicate is advisory (a turn can start right after), so the per-session checks â€” one may read state.db â€”
run outside ``_sessions_lock``.Nc              3  ó<   "  € T F  w  r\        W4      x € K  	  R # 5ir(   )r§   )rA   rr   rB   s   &  r   rC   Ú&_sessions_quiescent.<locals>.<genexpr>ï   s   é € ÐF¹v±V°SÔ(¨×0Ð0»vùs   ‚FT)r)   r*   rj   rš   )r©   rr   rB   Úotherss   &   r   Ú_sessions_quiescentr®   æ   sl   € ÷ 
ŽÜ)2¯©Ô):ÔMÑ):™v˜s¸c¹n”(�3“(Ñ):ˆÑM÷ 
ç‹3ÑF¹vÓF�3Œ3ÐFŠ3ÐFˆ3ÑF¹vÓFÓFÐFùó N÷ 
�ús    �B§
A<
¶A<
½BÁ<BÂB	c               ó$   € V ^8„  d   QhRRRRRR/# rž   r   )r   s   "r   r   r   ò   s&   € ÷ Iñ I˜cð I¨Dð I°Tñ Ir   c                óx   € \        W4      ;'       d)    \        V4      '       * ;'       d    \        V4      '       * # )u„  Reaper eligibility: the LRU exemptions, and not the sole receiver of a pending wake-up â€” an active /loop or
/heartbeat, or a running background process that will notify it (closing the session stops the notification
poller that delivers both, and ``agent_close`` kills the process). Kept out of ``_sessions_quiescent``: an idle
session waiting on a wake-up does not block a memory trim.)r§   Ú"_session_owns_live_wakeup_scheduleÚ*_session_owns_pending_process_notification)rr   r   s   &&r   Ú_session_is_reapabler³   ò   sA   € ô
 & cÓ3÷ Hð HÔ<^Ð_fÓ<gÔ8g÷ Hð HÜ>¸wÓGÔGðIr   c               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   rr   rŸ   r   r   r.   r8   r	   r
   r   )r   s   "r   r   r   û   s(   € ÷ .ñ .˜sð .¨Tð .¸ð .À$ñ .r   c                óð   € \        VP                  R4      ;'       g    R4      p\        VP                  R4      ;'       g    R4      pW#,
          \        8:  g   W$,
          \        8:  d   R# \        W4      # )zÂTTL eviction: idle-for-TTL AND older-than-TTL, plus the reap exemptions (the cheap clock check first: the
exemptions read the session store, and this runs for every session on each reaper pass).Úlast_activer4   Ú
created_atF)r8   r   Ú_SESSION_TTL_Sr³   )rr   r   r.   r¶   r·   s   &&&  r   Ú_session_is_evictabler¹   û   s]   € ô ˜Ÿ™ MÓ2×9Ð9°cÓ:€KÜ�w—{‘{ <Ó0×7Ð7°CÓ8€JØÕœnÔ,°Õ1AÄnÔ0TÙÜ Ó-Ð-r   c               ó   € V ^8„  d   QhRR/# rF   r   )r   s   "r   r   r     s   € ÷ Xñ X˜Tñ Xr   c                 óÐ  € \         P                  4       p  \        4        \
        ;_uu_ 4        \        P                  4        UUu. uF  w  r\        WV 4      '       g   K  VNK  	  pppRRR4       X F  p\        VRV3R lR7       K  	  \        4        \        4        \        4        \        4       '       g   \        P	                  R4       R#  ^ RIHp V! R	R
7       R#   \         d    \        P	                  R RR7        Lèi ; iu uppi   + '       g   i     L°; i  \         d6   p\        P	                  R\!        T4      P"                  T4        Rp?R# Rp?ii ; i)z)periodic incremental session flush failedTr   NÚidle_timeoutc                ó>   € \        W\        P                  4       4      # r(   )r¹   r6   ©r   Úvss   &&r   Ú<lambda>Ú%_reap_idle_sessions.<locals>.<lambda>  s   € Ô.CÀBÔQU×QZÑQZÓQ\Ô.]r   ©Ú
end_reasonÚ	predicatezAidle reaper periodic trim deferred: a session is busy or attached)Útrim_memoryzidle reaper periodic trim)Úreasonz&idle reaper memory trim failed: %s: %s)r6   r;   r    r!   r"   r)   r*   rj   r¹   Ú_close_session_by_idÚ_repair_missing_ws_orphan_reapsÚ_enforce_session_capÚ_reclaim_orphaned_leasesr®   Úhermes_cli.mem_trimrÅ   ÚtypeÚ__name__)r.   rr   rB   ÚvictimsrÅ   Úexcs         r   Ú_reap_idle_sessionsrÐ     s  € Ü
�)‰)‹+€CðQÜÔ÷ 
ŽÜ%.§_¡_Ô%6Ô]Ñ%6™6˜3Ô:OÐPSÐX[×:\—3�3Ñ%6ˆÑ]÷ 
ãˆÜØ˜NØ),Ó]÷	_ñ ô $Ô%ÜÔÜÔô × Ò Ü�‰ÐXÔYÙðXÝ3ÙÐ6×7øô' ô QÜ�‰Ð@È4ˆÖPðQüó ^÷ 
�ûô$ ô XÜ�‰Ð=¼tÀC»y×?QÑ?QÐSV×WÒWûðXúsL   –
C$ ®DÁD
Á!D
Á'DÃD% Ã$"D	ÄD	ÄDÄD"	Ä%E%Ä0*E Å E%c               ó   € V ^8„  d   QhRR/# rF   r   )r   s   "r   r   r   !  s   € ÷ *ñ *¨ñ *r   c            
     óœ  € \         ^ 8:  d   R# \        ;_uu_ 4        \        P                  4        U u. uFQ  p V P	                  R4      \
        Jg   K  \        V P	                  R4      4      '       g   K@  V P	                  R4      NKS  	  pp RRR4       X F  p\        V4       K  	  \        ;_uu_ 4        \        P                  4        UU u. uF'  w  r0\        V 4      '       g   K  V\        9  g   K%  VNK)  	  ppp V F  p\        V4       K  	  RRR4       R# u up i   + '       g   i     L�; iu up pi   + '       g   i     R# ; i)a?  Re-arm detached sessions whose disconnect path lost its teardown timer.

A resident record otherwise vouches for its lease during every orphan sweep,
while the live process prevents PID pruning. Reusing the normal WS grace
path preserves reconnect and in-flight-work protections instead of stealing
the lease directly.
Nrœ   )Ú_WS_ORPHAN_REAP_GRACE_Sr)   r*   r+   r   r˜   r”   Ú_close_sessions_for_transportrj   Ú_ws_session_is_detachedÚ_pending_ws_reapsÚ_schedule_ws_orphan_reap)r   Úclosed_transportsrœ   rr   Úmissings        r   rÈ   rÈ   !  s  € ô  !Ô#Ù÷ 
ŽÜEN×EUÑEUÔEWó OÑEW¸'Ø '§¡¨KÓ 8Ô@VÐ Vô 6ä!3°G·K±KÀÓ4L×!Mô 6˜WŸ[™[¨Ö5ÑEWÐð O÷ 
ó 'ˆ	Ü% iÖ0ñ 'ç	Žä$-§O¡OÔ$5ô
Ù$5‘L�CÜ& w×/ô Ø47Ô?PÑ4P÷ ˆCÑ$5ð 	ñ 
ó ˆCÜ$ SÖ)ñ ÷ 
‰ùòO÷ 
�üó
÷ 
�ˆúsR   œD!³DÁDÁ5DÂ
D!Â6D:ÃD4
Ã(D4
Ã5D4
Ã;D:ÄD!Ä!D1	Ä4D:Ä:E	c               ó   € V ^8„  d   QhRR/# rF   r   )r   s   "r   r   r   =  s   € ÷ Eñ E $ñ Er   c                 ó,  €  \        4         ^ RIHp  V ! \        4       4      ;p'       d   \        P                  RV4       R# R#   \         d    \        P                  RRR7        L]i ; i  \         d    \        P                  RRR7        R# i ; i)zEHand the registry the lease ids we still own so it can drop the rest.z!stale deferred lease sweep failedTr   )Úrelease_orphaned_leasesz-Reclaimed %d orphaned active-session lease(s)zorphaned lease reclaim failedN)Ú_reap_stale_deferred_leasesr    r!   r"   Úhermes_cli.active_sessionsrÜ   Ú_own_live_lease_idsÚinfo)rÜ   Údroppeds     r   rÊ   rÊ   =  s‰   € ðIô 	$Ô%ðEÝFÙ-Ô.AÓ.CÓDÐDˆ7ÖDÜ�K‰KÐGÈÖQñ Eøô	 ô IÜ�‰Ð8À4ˆÖHðIûô ô EÜ�‰Ð4¸tˆ×DðEús"   ‚
A �4A- Á"A*Á)A*Á-"BÂBc               ó   € V ^8„  d   QhRR/# )r   r	   r0   r   )r   s   "r   r   r   P  s   € ÷ 
ñ 
˜Cñ 
r   c                 ó4  €  ^ RI Hp  \        4       ;'       g    / pVP                  R4      pVf9   \	        VP                  R4      ;p\
        4      '       d   VP                  R4      pV ! VRR7      pV'       d   \        V4      # ^ #   \         d     ^ # i ; i)é    )Úcoerce_max_concurrent_sessionsÚmax_live_sessionsÚgateway©Úkey)rÞ   rå   Ú	_load_cfgr   r   r   r0   r    )rå   ÚcfgÚrawÚgateway_cfgÚcoerceds        r   Ú_max_live_sessionsrï   P  s†   € ð	ÝMÜ‹k×Ð˜RˆØ�g‰gÐ)Ó*ˆØŠ;œ:°S·W±W¸YÓ5GÐ&G kÌ×NÒNØ—/‘/Ð"5Ó6ˆCÙ0°Ð:MÔNˆß&Œs�7‹|Ð-¨AÐ-øÜô Úðús#   ‚B ™A!B Á;
B ÂB ÂBÂBc               ó   € V ^8„  d   QhRR/# rF   r   )r   s   "r   r   r   ]  s   € ÷ nñ n˜dñ nr   c            
     ó6  € \        4       p V ^ 8:  d   R# \        ;_uu_ 4        \        \        4      V 8:  d    RRR4       R# \        P	                  4        UUu. uF  w  r\        W4      '       g   K  W3NK  	  pppRRR4       XP                  R R7       V FO  w  r\        ;_uu_ 4        \        \        4      V 8:  d    RRR4        R#  RRR4       \        VRV3R lR7       KQ  	  R# u uppi   + '       g   i     L€; i  + '       g   i     LA; i)rä   Nc                óX   € \        V ^,          P                  R4      ;'       g    R4      # )r@   r¶   r4   )r8   r   )Úkvs   &r   rÀ   Ú&_enforce_session_cap.<locals>.<lambda>f  s   € ¤%¨¨1­¯	©	°-Ó(@×(GÐ(GÀCÔ"Hr   rè   Ú	lru_evictc                ó   € \        W4      # r(   )r³   r¾   s   &&r   rÀ   rô   l  s   € ÔK_Ð`bÔKlr   rÂ   )rï   r)   Úlenr*   rj   r³   ÚsortrÇ   )Úcaprr   rB   Ú	evictableÚ_ss        r   rÉ   rÉ   ]  sÍ   € Ü
Ó
€CØ
ˆa„xÙß	ŽÜŒy‹>˜SÔ Ø÷ 
‰ô -6¯O©OÔ,=Ô^Ñ,=¡& #ÔAUÐVY×A]”X�c“XÑ,=ˆ	Ñ^÷ 
ð
 ‡N�NÑH€NÔIÛ‰ˆßŽ^Ü”9‹~ Ô$Ø÷ Š^Ø$÷ ô 	Ø˜KÀcÓ3l÷	nó	 ùó _÷ 
�ú÷ �^ús5   ¢C5ÁC5ÁC/
Á3C/
Á:C5Â-DÃ/C5Ã5D	ÄDc               ó(   € V ^8„  d   QhRRRRRRRR/# )r   Údelayr8   Úfail_logrŸ   Úlevelr	   rG   r   )r   s   "r   r   r   o  s)   € ÷ 
ñ 
 ð 
°Sð 
Àð 
ÐSWñ 
r   c                ó|   aaa€ R VVV3R llp\         P                  ! W4      pRVn        VP                  4        R# )zPRun ``fn`` once on a daemon Timer after ``delay``; log (never raise) on failure.c               ó   € V ^8„  d   QhRR/# rF   r   )r   s   "r   r   Ú*_reaper_daemon_timer.<locals>.__annotate__q  s   € ÷ <ñ <�$ñ <r   c                 ól   <€  S! 4        R#   \          d    \        \        S4      ! S R R7        R# i ; i)Tr   N)r    r   r!   )rþ   Úfnrÿ   s   €€€r   rN   Ú"_reaper_daemon_timer.<locals>._runq  s0   ø€ ð	<ÙŽDøÜô 	<Ü”F˜EÔ" 8°d×;Ð;ð	<ús   ƒ Œ#3²3TN)rV   ÚTimerrQ   rX   )rý   r  rþ   rÿ   rN   Útimers   &fff  r   Ú_reaper_daemon_timerr  o  s-   ú€ ÷<ò <ô �OŠO˜EÓ(€EØ€E„LØ	‡K�K†Mr   c               ó   € V ^8„  d   QhRR/# rF   r   )r   s   "r   r   r   |  s   € ÷ Vñ V¨4ñ Vr   c                 ó(   € \        R\        R4       R# )zHRun the LRU sweep off the response path (eviction can call agent.close).gš™™™™™¹?zsession cap enforcement failedN)r  rÉ   r   r   r   Ú!_schedule_session_cap_enforcementr  |  s   € ä˜Ô2Ð4TÖUr   c               ó   € V ^8„  d   QhRR/# r…   r   )r   s   "r   r   r   �  s   € ÷ 	ñ 	¨ñ 	r   c                 óø   €  \        4       ;'       g    / P                  R4      ;'       g    / p \        V \        4      '       d$   RV 9   d   \	        V P                  R4      RR7      # R#   \
         d     R# i ; i)z�``dashboard.startup_orphan_sweep`` (default on). Fail-open on errors and on a missing key (raw yaml, no
DEFAULT_CONFIG merge on this loader).Ú	dashboardÚstartup_orphan_sweepT)Údefault)rê   r   r   r   Úis_truthy_valuer    )Údashboard_cfgs    r   Ú_session_orphan_reaper_enabledr  �  st   € ðÜ"›×*Ð*¨×/Ñ/°Ó<×BÐBÀˆÜ�m¤T×*Ò*Ð/EÈÔ/VÜ" =×#4Ñ#4Ð5KÓ#LÐVZÔ[Ð[ñ øô ô ØÙðús   ‚A* “A* ¬;A* Á*A9Á8A9c               ó   € V ^8„  d   QhRR/# )r   r	   z	list[str]r   )r   s   "r   r   r   ™  s   € ÷ ñ  iñ r   c            	     ót  € \        4       p V e   \        ^ 8:  d   . # \        4       p\        ;_uu_ 4        \        P                  4        Fj  w  r#V.p\        V\        4      '       d5   V\        VP                  R4      RR4      VP                  R4      .,          pVP                  R V 4       4       Kl  	  RRR4       V P                  \        \        \        \        V4      4      RR7      pV'       d0   \        P!                  R\#        V4      R	P%                  V4      4       V#   + '       g   i     Lx; i)
u  End orphaned tui/desktop/subagent rows left by a dead process. "Provably orphaned" is inferred
conservatively: the row must have been created AND last messaged at least the session TTL ago (a fresh row
that copied an old transcript is protected by its own ``started_at``). Rows held in memory (e.g. a
``session.resume`` in the startup grace window) are excluded. Cross-backend: the sweep refuses to close a
row any live backend (heartbeat within ``2 * TTL``) could own â€” see ``SessionDB.sweep_orphaned_sessions``.Nr   Ú
session_idÚsession_keyc              3  óJ   "  € T F  q'       g   K  \        V4      x € K  	  R # 5ir(   )rŸ   )rA   Úcs   & r   rC   Ú/_sweep_orphaned_session_rows.<locals>.<genexpr>¨  s   é € Ð<©J q»!œFœC ŸF˜F«Jùs   ‚	#�#T)Úmax_idle_secondsÚsourcesÚexclude_idsÚexclude_pinnedz[Closed %d orphaned session row(s) from a previous gateway process (startup_orphan_reap): %sz, )Ú_get_dbr¸   Úsetr)   r*   rj   r   r   r   r   ÚupdateÚsweep_orphaned_sessionsÚ_ORPHAN_SWEEP_SOURCESÚtupleÚsortedr!   rà   r÷   rY   )ÚdbÚlive_idsrr   r   Ú
candidatesÚswepts         r   Ú_sweep_orphaned_session_rowsr*  ™  sö   € ô 
‹€BØ	‚z”^ qÔ(Øˆ	Ü›€Hß	ŽÜ%ŸO™OÖ-‰LˆCØ˜ˆJÜ˜'¤4×(Ò(Øœw w§{¡{°7Ó';¸\È4ÓPÐRY×R]ÑR]Ð^kÓRlÐmÕm�
Ø�O‰OÑ<©JÓ<Ö<ñ	 .÷ 
ð ×&Ñ&Ü'Ô1FÜœ& Ó*Ó+¸Dð 'ó B€E÷ Ü�‰ØiÜ�‹J˜Ÿ	™	 %Ó(ô	*ð €L÷ 
�ús   ´BD'Ä'D7	r4   Ú"HERMES_GATEWAY_HEARTBEAT_REFRESH_Sg      N@c               ó   € V ^8„  d   QhRR/# ©r   r	   rŸ   r   )r   s   "r   r   r   ¾  s   € ÷ Cñ C˜#ñ Cr   c                 ón   € \        \        R 4      '       d   \        P                  4       P                  # R# )ÚunameÚhost)r   r{   r/  Únodenamer   r   r   Ú_reaper_hostnamer2  ¾  s%   € Ü")¬"¨g×"6Ò"6Œ2�8‰8‹:×ÑÐB¸FÐBr   c               ó   € V ^8„  d   QhRR/# r-  r   )r   s   "r   r   r   Â  s   € ÷ \ñ \ cñ \r   c                 óh   € \        4        R\        4        R\        P                  4        R\         2# )zŸStable identity for this process's heartbeat row: pid (readability) AND a startup nonce so a PID-reuse
respawn cannot inherit the dead predecessor's heartbeat.Ú@Ú:)Ú_current_profile_namer2  r{   r}   Ú_BACKEND_NONCEr   r   r   Ú_backend_id_for_this_processr9  Â  s1   € ô $Ó%Ð& aÔ(8Ó(:Ð';¸1¼R¿Y¹Y»[¸MÈÌ>ÐJZÐ[Ð[r   c               ó   € V ^8„  d   QhRR/# )r   r	   r8   r   )r   s   "r   r   r   È  s   € ÷ "ñ "˜Uñ "r   c                 ó‚   € \        \        RR4      f   \        P                  4       \        n        \        P                  # )z�Wall-clock time this process started (first-call time is a good-enough proxy: the heartbeat refresher
runs after the gateway is fully wired up).Ú_tN)r   Ú_gateway_started_atr6   r<  r   r   r   r=  r=  È  s/   € ô Ô" D¨$Ó/Ò7Ü!%§¡£ÔÔÜ×!Ñ!Ð!r   c               ó   € V ^8„  d   QhRR/# rF   r   )r   s   "r   r   r   Ð  s   € ÷ 
Hñ 
H Dñ 
Hr   c                 ó
  € \        4       p V f   R#  V P                  \        4       \        P	                  4       \        4       \        4       \        4       R7       R#   \         d    \        P                  RRR7        R# i ; i)z@Refresh this backend's heartbeat row. No-op when DB unavailable.N)Ú
backend_idÚpidÚ
started_atÚprofiler0  z backend heartbeat refresh failedTr   )r  Úregister_backend_heartbeatr9  r{   r}   r=  r7  r2  r    r!   r"   )r&  s    r   Ú_refresh_backend_heartbeatrE  Ð  so   € ä	‹€BØ	‚zÙðHØ
×%Ñ%Ü3Ó5¼2¿9¹9»;ÔSfÓShÜ)Ó+Ô2BÓ2Dð 	&ö 	Føô ô HÜ�‰Ð7À$ˆ×GðHús   ’AA Á"BÂBc               ó   € V ^8„  d   QhRR/# rF   r   )r   s   "r   r   r   Ý  s   € ÷  Yñ  Y¨Dñ  Yr   c                 óÎ  a€ \         ;_uu_ 4        \        '       d    RRR4       R# RsRRR4        \        4        \        ^ 8:  d   R# \        P                  ! 4       oR V3R llp V3R lp\        P                  V4       \        P                  ! V RRR	7      P                  4        R#   + '       g   i     LŠ; i  \         d    \        P                  RRR7        L¦i ; i)
zûRegister this backend and start the refresher thread (once per process). The first refresh writes the row
synchronously so this process's own sweep sees itself in the heartbeat table. ``_HEARTBEAT_REFRESH_S <= 0``
means "register once, never refresh".NTz&initial backend heartbeat write failedr   c               ó   € V ^8„  d   QhRR/# rF   r   )r   s   "r   r   Ú8_start_backend_heartbeat_refresher.<locals>.__annotate__î  s   € ÷ 2ñ 2�4ñ 2r   c                 óÈ   <€ S P                  4       '       g#    \        4        S P                  \        4       K8  R#   \         d    \        P	                  R RR7        L=i ; i)z'heartbeat refresh loop iteration failedTr   N)r¥   rE  r    r!   r"   ÚwaitÚ_HEARTBEAT_REFRESH_S)Ú
stop_events   €r   Ú_loopÚ1_start_backend_heartbeat_refresher.<locals>._loopî  sW   ø€ Ø×#Ñ#×%Ò%ðWÜ*Ô,ð �O‰OÔ0Ö1ñ &øô ô WÜ—‘ÐFÐQU�ÖVðWús   ™
< ¼"A!Á A!c                 óô   <€ SP                  4        \        P                  ! \        4      ;_uu_ 4        \	        4       ;p e   V P                  \        4       4       R R R 4       R #   + '       g   i     R # ; ir(   )r   rk   rl   r    r  Úclear_backend_heartbeatr9  )r&  rM  s    €r   Ú_atexit_clearÚ9_start_backend_heartbeat_refresher.<locals>._atexit_clearö  sK   ø€ Ø�‰ÔÜ× Ò ¤×+Õ+Ü“i��Ò,Ø×*Ñ*Ô+GÓ+IÔJ÷ ,×+×+Ò+ús   ´(A&Á&A7	zhermes-gateway-heartbeat)rP   rR   rQ   )Ú_heartbeat_refresher_lockÚ_heartbeat_refresher_startedrE  r    r!   r"   rL  rV   ÚEventÚatexitÚregisterrW   rX   )rN  rR  rM  s     @r   Ú"_start_backend_heartbeat_refresherrY  Ý  s´   ø€ ÷
 
#Ö	"ß'Ó'Ø÷ 
#Ñ	"ð (,Ð$÷ 
#ðNÜ"Ô$ô ˜qÔ ÙÜ—’Ó"€J÷2ð 2õKô ‡O�O�MÔ"Ü×Ò˜EÐ(BÈ4ÔP×VÑVÖX÷7 
#×	"ûô ô NÜ�‰Ð=ÈˆÖMðNús!   �B,¨B,³
B? Â,B<	Â?"C$Ã#C$c               ó   € V ^8„  d   QhRR/# rF   r   )r   s   "r   r   r      s   € ÷ wñ w¨ñ wr   c                 ó
  € \         ^ 8:  g   \        ^ 8:  g   \        4       '       g   R# \        ;_uu_ 4        \        '       d    RRR4       R# RsRRR4       \        \         \        RRR7       R#   + '       g   i     L); i)a   Schedule the once-per-process startup orphan sweep, delayed by the WS-orphan grace window so a client
reconnecting right after a restart can ``session.resume`` its row first. Grace 0 (park forever), TTL 0 and
``dashboard.startup_orphan_sweep: false`` all suppress the sweep.

See #65194.
NTz#startup orphan session sweep failedrZ   )rÿ   )rÓ   r¸   r  Ú_startup_orphan_sweep_lockÚ_startup_orphan_sweep_ranr  r*  r   r   r   Ú_schedule_startup_orphan_sweepr^     sc   € ô  !Ô#¤~¸Ô':ÔB`×BbÒBbÙß	#Ö	#ß$Ó$Ø÷ 
$Ñ	#ð %)Ð!÷ 
$ô ÜÔ!=Ð?dÐlu÷w÷	 
$×	#ús   ·A2ÁA2Á2B	c               ó   € V ^8„  d   QhRR/# rF   r   )r   s   "r   r   r     s   € ÷ 0ñ 0˜ñ 0r   c                ó2   € \        \        4       V RR7       R# )zFPublish this module's helpers onto ``server``, rebound to its globals.)ÚskipN)Ú_)r   Úglobals)Úservers   &r   rX  rX    s   € ä”“	˜6¨×/r   r(   )r"   )ÚtuiÚdesktopÚsubagentÚunknown)6Ú__conditional_annotations__Ú__doc__Ú
__future__r   rk   ÚsecretsrV   Útui_gateway._envr   Ú
method_ctxr   r   r$   r,   r;   r]   rp   rs   rt   Ú__annotations__r‡   rƒ   r�   r”   r§   r®   r³   r¹   rÐ   rÈ   rÊ   rï   rÉ   r  r  r#  r]  ÚLockr\  r  r*  rT   rL  rU  rT  Ú	token_hexr8  r2  r9  r=  rE  rY  r^  rX  )ri  s   @r   Ú<module>rr     s0  øðôõ #ã Û Û å &å #õbõõ:(÷
÷(ðB Ð ÷ð6 -/Ð ˜>Ó .Ø!&Ð õ:õ6õ,7õ"
8÷	GõIõ.õXõ8*õ8Eõ&
õn÷$
õVð BÐ Ø!Ð Ø&Ÿ^š^Ó-Ð õ	õñ> ˜3¡	Ð*NÐPTÓ UÓVÐ Ø$Ð Ø%ŸNšNÓ,Ð Ø×"Ò" 1Ó%€õCõ\õ"õ
Hõ YõFw÷$0r   