+
    jHàj  ã                  ó  € R t ^ RIHt ^ RIt^ RIt^ RIHt ^ RIHt ^ RI	H
t
HtHtHtHt . ROt]! 0 Rm4      t]! 0 Rm4      t]! RR	7       ! R
 R4      4       tR R ltR R ltR R ltR R ltR R lt ! R R4      tR# )at  Sliding-window budget for bot-authored inbound messages.

``{PLATFORM}_ALLOW_BOTS`` only decides admission, so two Hermes profiles replying to each other never stop.
The guard counts admitted bot messages per conversation and drops further ones for ``cooldown_seconds``
once ``max_events`` land inside ``window_seconds``. Settings: config.yaml ``gateway.bot_loop_guard``.
)ÚannotationsN)Údeque)Ú	dataclass)ÚCallableÚDequeÚDictÚHashableÚTupleÚBotLoopGuardÚBotLoopGuardSettingsT)Úfrozenc                  óP   € ] tR t^t$ RtR]R&   ^tR]R&   RtR]R&   R	tR]R
&   Rt	R# )r   TÚboolÚenabledÚintÚ
max_eventsg     Àr@ÚfloatÚwindow_secondsg     À‚@Úcooldown_seconds© N)
Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__r   Ú__annotations__r   r   r   Ú__static_attributes__r   ó    Ú;/home/ubuntu/.hermes/hermes-agent/gateway/bot_loop_guard.pyr   r      s+   ‡ à€GˆTÓØ€J�ÓØ!€N�EÓ!Ø#Ð�e×#r   c               ó    € V ^8„  d   QhRRRR/# )é   Údefaultr   Úreturnr   )Úformats   "r   Ú__annotate__r#      s   € ÷ ñ ˜4ð  Dñ r   c                óÄ   € \        V \        4      '       d   V # V e(   \        V 4      P                  4       P	                  4       MRpV\
        9   d   R# V\        9   d   R# V# )NÚ TF)Ú
isinstancer   ÚstrÚstripÚlowerÚ_TRUTHYÚ_FALSY)Úrawr    Útexts   && r   Ú_as_boolr.      sL   € Ü�#”t×ÒØˆ
Ø'*¢Œ3ˆs‹8�>‰>Ó×!Ñ!Ô#¸B€DØŒw„ÙØŒv„~ÙØ€Nr   c               ó    € V ^8„  d   QhRRRR/# )r   r    r   r!   r   )r"   s   "r   r#   r#   )   s   € ÷ +ñ +˜uð +¨ñ +r   c                ó’   € \        V \        4      '       d   V#  \        V 4      pT^ 8”  d   T# T#   \        \        3 d    Tu # i ; i)é    )r&   r   r   Ú	TypeErrorÚ
ValueError©r,   r    Úvalues   && r   Ú_as_positiver6   )   sP   € Ü�#”t×ÒØˆðÜ�c“
ˆð ˜A”Iˆ5Ð* 7Ð*øô ”zÐ"ô ØŠðús   š0 °AÁAc               ó    € V ^8„  d   QhRRRR/# )r   r    r   r!   r   )r"   s   "r   r#   r#   3   s   € ÷ Iñ I 3ð I¨3ñ Ir   c                ób   € \        V R 4      pV^8¼  d   V\        V4      8X  d   \        V4      # T# )ç        )r6   r   r4   s   && r   Ú_as_positive_intr:   3   s.   € Ü˜˜cÓ"€EØ !œ¨´°U³Ô(;Œ3ˆu‹:ÐHÀÐHr   c               ó   € V ^8„  d   QhRR/# ©r   r!   r   r   )r"   s   "r   r#   r#   8   s   € ÷ ñ Ð!5ñ r   c           
     ó°  € ^ RI Hp V! V RRRR7      p\        V\        4      '       g   \	        4       # \	        4       p\	        \        VP                  R4      VP                  4      \        VP                  R4      VP                  4      \        VP                  R4      VP                  4      \        VP                  R	4      VP                  4      R
7      # )z\Read ``gateway.bot_loop_guard`` from a loaded config dict. Unusable values keep the default.)Úcfg_getÚgatewayÚbot_loop_guardN)r    r   r   r   r   )r   r   r   r   )Úhermes_cli.configr>   r&   Údictr   r.   Úgetr   r:   r   r6   r   r   )Úcfgr>   ÚblockÚdefaultss   &   r   Úsettings_from_configrG   8   s§   € å)á�C˜Ð$4¸dÔC€EÜ�eœT×"Ò"Ü#Ó%Ð%Ü#Ó%€HÜÜ˜Ÿ™ 9Ó-¨x×/?Ñ/?Ó@Ü# E§I¡I¨lÓ$;¸X×=PÑ=PÓQÜ# E§I¡IÐ.>Ó$?À×AXÑAXÓYÜ% e§i¡iÐ0BÓ&CÀX×E^ÑE^Ó_ô	ð r   c               ó   € V ^8„  d   QhRR/# r<   r   )r"   s   "r   r#   r#   H   s   € ÷ &ñ &Ð+ñ &r   c                 óf   €  ^ RI Hp  \        V ! 4       4      #   \         d    \	        4       u # i ; i)zLSettings from the live config.yaml. Defaults when the config cannot be read.©Úload_config_readonly)rA   rK   rG   Ú	Exceptionr   rJ   s    r   Úload_settingsrM   H   s1   € ð&Ý:ä#Ñ$8Ó$:Ó;Ð;øÜô &Ü#Ó%Ò%ð&ús   ‚ ˜0¯0c                  ó|   € ] tR t^RtRt]]P                  3R R llt]	R R l4       t
R R ltR R	 ltR
 R ltRtR# )r
   z¬Per-conversation sliding window with a cooldown once the budget trips. Thread-safe.
Settings are re-read on every call so a config.yaml edit takes effect without a restart.c               ó$   € V ^8„  d   QhRRRRRR/# )r   Úsettingsz"Callable[[], BotLoopGuardSettings]ÚclockzCallable[[], float]r!   ÚNoner   )r"   s   "r   r#   ÚBotLoopGuard.__annotate__V   s(   € ÷ 
ñ 
à4ð
ð #ð
ð 
ñ	
r   c                	ó|   € Wn         W n        \        P                  ! 4       V n        / V n        / V n        R V n        R# )r9   N)Ú	_settingsÚ_clockÚ	threadingÚLockÚ_lockÚ_eventsÚ_cooldown_untilÚ_last_sweep)ÚselfrP   rQ   s   &&&r   Ú__init__ÚBotLoopGuard.__init__V   s3   € ð
 "ŒØŒÜ—^’^Ó%ˆŒ
Ø57ˆŒØ68ˆÔØˆÖr   c               ó   € V ^8„  d   QhRR/# )r   r!   r   r   )r"   s   "r   r#   rS   c   s   € ÷ %ñ % sñ %r   c                	ó’   € V P                   ;_uu_ 4        \        V P                  4      uuR R R 4       #   + '       g   i     R # ; i)N)rY   ÚlenrZ   )r]   s   &r   Útracked_conversationsÚ"BotLoopGuard.tracked_conversationsb   s#   € à�Z�Z‹ZÜ�t—|‘|Ó$÷ �Z�Z‹Zús	   •5µA	c               ó    € V ^8„  d   QhRRRR/# )r   Úconversationr   r!   r   r   )r"   s   "r   r#   rS   g   s   € ÷ Oñ O Hð O°ñ Or   c                ó  € V P                  4       P                  '       g   R# V P                  ;_uu_ 4        V P                  P	                  VR4      V P                  4       8„  uuRRR4       #   + '       g   i     R# ; i)zbTrue while ``conversation`` is cooling down. Reads only, so callers may ask as often as they like.Fr9   N)rU   r   rY   r[   rC   rV   )r]   rf   s   &&r   ÚblockedÚBotLoopGuard.blockedg   sN   € à�~‰~Ó×'×'Ð'ÙØ�Z�Z‹ZØ×'Ñ'×+Ñ+¨L¸#Ó>ÀÇÁÃÑN÷ �Z�Z‹Zús   ·-A/Á/B 	c               ó    € V ^8„  d   QhRRRR/# )r   rf   r   r!   ztuple[bool, str]r   )r"   s   "r   r#   rS   n   s   € ÷ ñ  (ð Ð/?ñ r   c                óØ  € V P                  4       pVP                  '       g   R# V P                  4       pV P                  ;_uu_ 4        V P	                  W24       V P
                  P                  VR4      V8”  d   RuuRRR4       # V P                  P                  V\        4       4      pW2P                  ,
          pV'       d!   V^ ,          V8:  d   VP                  4        K(  \        V4      VP                  8¼  d<   W2P                  ,           V P
                  V&   VP                  4        RuuRRR4       # VP!                  V4       RuuRRR4       #   + '       g   i     R# ; i)z»Count one admitted bot-authored message for ``conversation``.
Returns ``(allowed, state)``; state is ``disabled``, ``ok``, ``tripped`` (this message started the cooldown) or ``cooldown``.r9   N)TÚdisabled)FÚcooldown)FÚtripped)TÚok)rU   r   rV   rY   Ú_sweepr[   rC   rZ   Ú
setdefaultr   r   Úpopleftrb   r   r   ÚclearÚappend)r]   rf   rP   ÚnowÚeventsÚcutoffs   &&    r   ÚadmitÚBotLoopGuard.admitn   s  € ð —>‘>Ó#ˆØ××ÐØ#Ð#Ø�k‰k‹mˆØ�Z�Z‹ZØ�K‰K˜Ô&Ø×#Ñ#×'Ñ'¨°cÓ:¸SÔ@Ø(÷ ŠZð —\‘\×,Ñ,¨\¼5»7ÓCˆFØ×2Ñ2Õ2ˆFß˜V A�Y¨&Ô0Ø—‘Ö Ü�6‹{˜h×1Ñ1Ô1Ø58×;TÑ;TÕ5T�×$Ñ$ \Ñ2Ø—‘”Ø'÷ ŠZð �M‰M˜#ÔØ÷ �Z�Z‹Zús   Á	4EÂ=EÃA*EÄ;EÅE)	c               ó$   € V ^8„  d   QhRRRRRR/# )r   ru   r   rP   r   r!   rR   r   )r"   s   "r   r#   rS   „   s"   € ÷ 	*ñ 	*˜%ð 	*Ð+?ð 	*ÀDñ 	*r   c                óÜ  € WP                   ,
          VP                  8  d   R# Wn         WP                  ,
          pV P                  P                  4        UUu. uF  w  rEV'       d   VR,          V8:  g   K  VNK!  	  upp F  pV P                  V K  	  V P                  P                  4        UUu. uF  w  rGWq8:  g   K  VNK  	  upp F  pV P                  V K  	  R# u uppi u uppi )zHDrop idle conversations at most once per window so memory stays bounded.Néÿÿÿÿ)r\   r   rZ   Úitemsr[   )r]   ru   rP   Úidle_cutoffÚkÚdqÚkeyÚuntils   &&&     r   rp   ÚBotLoopGuard._sweep„   sÀ   € à×!Ñ!Õ! H×$;Ñ$;Ô;ÙØÔØ×3Ñ3Õ3ˆØ#'§<¡<×#5Ñ#5Ô#7Ô[Ñ#7™%˜!¿rÀRÈÅVÈ{ÑEZ—A�AÑ#7Õ[ˆCØ—‘˜SÒ!ñ \à&*×&:Ñ&:×&@Ñ&@Ô&BÔSÑ&B™(˜!ÀeÁl—A�AÑ&BÕSˆCØ×$Ñ$ SÒ)ó Tùó \ùãSs   ÁC"Á8C"Â3
C(ÃC()rV   r[   rZ   r\   rY   rU   N)r   r   r   r   Ú__doc__rM   ÚtimeÚ	monotonicr^   Úpropertyrc   rh   rx   rp   r   r   r   r   r
   r
   R   sC   † ñ`ð
 8EØ%)§^¡^÷
ð ô%ó ð%õOõ÷,	*ñ 	*r   )r
   r   rM   rG   >   Ú1ÚonÚyesÚtrue>   Ú0ÚnoÚoffÚfalse)r„   Ú
__future__r   rW   r…   Úcollectionsr   Údataclassesr   Útypingr   r   r   r   r	   Ú__all__Ú	frozensetr*   r+   r   r.   r6   r:   rG   rM   r
   r   r   r   Ú<module>r–      s~   ðñõ #ã Û Ý Ý !ß 9Õ 9â
[€á
Ò.Ó
/€Ù	Ò.Ó	/€ñ �$Ô÷$ð $ó ð$õõ+õIõ
õ &÷;*ó ;*r   