+
    iHàjá/  ã                  óž  a € 0 t $ R t^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	H
t
HtHtHt ^ RIHt ^ RIHt ^ RIHt ^ RIHt ]P,                  ! ]4      tR%tR&t]P6                  ! R]P8                  4      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$R R lt%RR/R R llt&Rt'R't(R])R&   Rt*R  R! lt+R"]'/R# R$ llt,R# )(u   Replay-history sanitization shared by EVERY resume surface (messaging gateway, TUI/WebUI gateway).

A turn that died mid-tool-loop (restart command, stale timeout, interrupt before the result was written)
persists a dangling ``assistant(tool_calls)`` or interrupted ``assistantâ†’tool`` tail; on resume the model
re-issues the unanswered call â†’ endless "thinking"/reboot loop. These pure helpers strip those tails.)ÚannotationsN)ÚAnyÚDictÚListÚOptional)Úmake_tool_result_message©Útool_may_have_side_effect)Údrop_stale_api_content)Úcoerce_epochz2^\[(?:command|execution) interrupted\b[^\n]*\]\s*$c               ó    € V ^8„  d   QhRRRR/# ©é   Úcontentr   ÚreturnÚbool© )Úformats   "Ú9/home/ubuntu/.hermes/hermes-agent/agent/replay_cleanup.pyÚ__annotate__r   (   s   € ÷ ?ñ ?¨ð ?°ñ ?ó    c                óü  € \        V \        4      '       g   R# T pV P                  4       P                  R4      '       do    \        P
                  ! V 4      p\        T\        4      '       d   TP                  R4      R9   d   R# TP                  R4      p\        T\        4      '       g   R# VP                  4       P                  R^4      R,          p\        P                  V4      RJ#   \         d     R# i ; i)	uV  True only when the result has the executor's interrupt SHAPE: the marker is the last
line of the output (JSON envelope with a non-zero exit code, or a bare text result). A
marker quoted inside successful output â€” a grep hit, a doc example â€” is ordinary data;
this runs on every live request, so a false positive rewrites real tool output.FÚ{Ú	exit_codeNÚoutputÚ
)é    Néÿÿÿÿ)Ú
isinstanceÚstrÚlstripÚ
startswithÚjsonÚloadsÚ
ValueErrorÚdictÚgetÚrstripÚrsplitÚ_INTERRUPT_MARKER_LINEÚmatch)r   r   ÚenvelopeÚ	last_lines   &   r   Úis_interrupted_tool_resultr-   (   sË   € ô
 �gœs×#Ò#ÙØ€FØ‡~�~Ó×"Ñ" 3×'Ò'ð	Ü—z’z 'Ó*ˆHô ˜(¤D×)Ò)¨X¯\©\¸+Ó-FÈ)Ô-SÙØ—‘˜hÓ'ˆÜ˜&¤#×&Ò&ÙØ—‘“×&Ñ& t¨QÓ/°Õ3€IÜ!×'Ñ'¨	Ó2¸$Ð>Ð>øô ô 	Úð	ús   ÁC, Ã,C;Ã:C;c               ó    € V ^8„  d   QhRRRR/# ©r   Úcallzdict[str, Any]r   r   r   )r   s   "r   r   r   >   s   € ÷ ?ñ ?�^ð ?¨ñ ?r   c                ó|   € \        V P                  R 4      ;'       g    / P                  R4      ;'       g    R4      # )ÚfunctionÚnameÚ ©r   r&   ©r0   s   &r   Ú
_call_namer7   >   s1   € Ü�—‘˜Ó$×*Ð*¨×/Ñ/°Ó7×=Ð=¸2Ó>Ð>r   c               ó    € V ^8„  d   QhRRRR/# r/   r   )r   s   "r   r   r   B   s   € ÷ <ñ <�>ð < cñ <r   c                ó|   € \        V P                  R 4      ;'       g    V P                  R4      ;'       g    R4      # )ÚidÚcall_idr4   r5   r6   s   &r   Ú_call_idr<   B   s.   € Üˆt�x‰x˜‹~×:Ð: §¡¨)Ó!4×:Ð:¸Ó;Ð;r   c               ó    € V ^8„  d   QhRRRR/# )r   Úcallsúlist[dict[str, Any]]r   r   r   )r   s   "r   r   r   F   s   € ÷ Nñ NÐ3ð N¸ñ Nr   c                ój   € \         ;QJ d    R  V  4       F  '       g   K   R# 	  R# ! R  V  4       4      # )c              3  óJ   "  € T F  p\        \        V4      4      x € K  	  R # 5i©N)r	   r7   )Ú.0r0   s   & r   Ú	<genexpr>Ú&_any_side_effecting.<locals>.<genexpr>G   s   é € ÐMÁu¸tÔ(¬°DÓ)9×:Ð:Ãuùs   ‚!#TF)Úany)r>   s   &r   Ú_any_side_effectingrG   F   s+   € ß‹3ÑMÁuÓM�3Œ3ÐMŠ3ÐMˆ3ÑMÁuÓMÓMÐMr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   r3   r   ÚnoticesÚtupler   r   )r   s   "r   r   r   J   s!   € ÷ ñ ˜3ð ¨ð °5ñ r   c                óP   € \        V 4      '       d   RV^ ,          3# RV^,          3# )zN(effect_disposition, content) for an interrupted/dangling call named ``name``.ÚunknownÚnoner   )r3   rI   s   &&r   Ú_orphan_recoveryrN   J   s+   € ä  ×&Ò&Ø˜' !�*Ð$Ð$Ø�7˜1•:ÐÐr   c               ó    € V ^8„  d   QhRRRR/# ©r   Úagent_historyr?   r   r   )r   s   "r   r   r   Q   s   € ÷ $ñ $Ð0Dð $ÐI]ñ $r   c                óš  € V '       g   V # . p^ \        V 4      r2W#8  Ed'   W,          pVP                  R4      R8X  Edž   RV9   Ed–   V^,           pWS8  d(   W,          P                  R4      R8X  d   V^,          pK-  W^,           V p\        ;QJ d    R V 4       F  '       g   K   RM	  RM! R V 4       4      '       Ed   VP                  R4      ;'       g    . p\        V4      '       dÂ   V Uu/ uF  p\	        V4      \        V4      bK  	  p	pVP                  V4       V F…  p
\        V
P                  RR	4      4      '       dQ   T	P                  \        V
P                  R
4      ;'       g    R	4      R	4      p\        V\        4      w  rÍ/ V
CRVRV/Cp
VP                  V
4       K‡  	  M'\        P                  RW%^,
          \        V4      4       TpEKÃ  VP                  R4      R8X  d8   \        VP                  RR	4      4      '       d   \        P                  R4       MVP                  V4       V^,          pEK-  V# u upi )u  Strip interrupted assistantâ†’tool blocks anywhere in history (a queued user message may follow one).
Read-only blocks are dropped; blocks with a side-effecting call are KEPT with the interrupted results
rewritten as orphan-recovery notices, since the effect may have happened.ÚroleÚ	assistantÚ
tool_callsÚtoolc              3  óX   "  € T F   p\        VP                  R R4      4      x € K"  	  R# 5i)r   r4   N)r-   r&   )rC   Úms   & r   rD   Ú/strip_interrupted_tool_tails.<locals>.<genexpr>`   s$   é € ÐZÉ\ÈÔ-¨a¯e©e°I¸rÓ.B×CÐCË\ùs   ‚(*TFr   r4   Útool_call_idÚeffect_dispositionu`   Stripping interrupted read-only assistantâ†’tool replay block (indices %dâ€“%d, tool_results=%d)z<Stripping orphan interrupted tool result from replay history)Úlenr&   rF   rG   r<   r7   Úappendr-   r   rN   Ú_INTERRUPTED_NOTICESÚloggerÚdebug)rQ   ÚcleanedÚiÚnÚmsgÚjÚtool_resultsr>   r0   Ú
call_namesÚtool_resultr3   Údispositionr   s   &             r   Ústrip_interrupted_tool_tailsrj   Q   sè  € ÷ ØÐØ$&€GØŒc�-Ó €qØ
�%ØÕˆØ�7‰7�6‹?˜kÕ)¨l¸cÕ.AØ�A•ˆAØ”%˜MÕ,×0Ñ0°Ó8¸FÔBØ�Q•’Ø(¨Q­¨qÐ1ˆLß‹sÑZÉ\ÓZ�s�sŠsÑZÉ\ÓZ×ZÓZØŸ™ Ó-×3Ð3°�Ü& u×-Ò-ÙOTÓ!UÉuÀt¤(¨4£.´*¸TÓ2BÒ"BÉu�JÐ!UØ—N‘N 3Ô'Û'3˜Ü5°k·o±oÀiÐQSÓ6T×UÒUØ#-§>¡>´#°k·o±oÀnÓ6U×6[Ð6[ÐY[Ó2\Ð^`Ó#a˜DÜ3CÀDÔJ^Ó3_Ñ0˜KØ*p¨[Ð*pÐ:NÐP[Ð]fÐhoÑ*p˜KØŸ™ {Ö3ò (4ô —L‘Lð  "DØ!"¨¥E¬3¨|Ó+<ô>à�ÚØ�7‰7�6‹?˜fÔ$Ô)CÀCÇGÁGÈIÐWYÓDZ×)[Ò)[Ü�L‰LÐWÕXà�N‰N˜3ÔØ	ˆQ�‹Ø€Nùò% "Vs   Ã3Ic               ó    € V ^8„  d   QhRRRR/# rP   r   )r   s   "r   r   r   x   s   € ÷ ñ Ð1Eð ÐJ^ñ r   c           
     óB  € V '       g   V # V R	,          p\        V\        4      '       d.   VP                  R4      R8X  d   VP                  R4      '       g   V # VP                  R4      ;'       g    . p\        V4      '       dy   \	        V 4      pV FP  p\        V4      ;'       g    Rp\        V\        4      w  rgVP                  \        WW\        V4      VR7      4       KR  	  \        P                  R4       V# \        P                  R\        V4      4       V RR	 # )
u_  Strip a trailing ``assistant(tool_calls)`` with NO answers â€” a call that killed the gateway itself
(``docker restart``) left zero ``tool`` rows, invisible to ``strip_interrupted_tool_tails``. A partially
answered block still resumes. Read-only tails are dropped; side-effecting ones get UNKNOWN-effect results.

On resume the model sees an unanswered tool call at the tail and naturally re-issues it â€” which restarts
the gateway again, producing the infinite reboot loop in #49201. ``strip_interrupted_tool_tails`` does
not catch this because there is no tool result to inspect for an interrupt marker.
rS   rT   rU   rL   )r[   zQRecovered dangling side-effecting tool call(s) as UNKNOWN instead of erasing themzOStripping dangling unanswered read-only assistant(tool_calls) tail (%d call(s))Nr   )r   r%   r&   rG   Úlistr7   rN   Ú_DANGLING_NOTICESr]   r   r<   r_   Úwarningr`   r\   )rQ   ÚlastrU   Ú	recoveredr0   r3   ri   r   s   &       r   Ústrip_dangling_tool_call_tailrr   x   sõ   € ÷ ØÐØ˜Õ€DÜ�tœT×"Ò" t§x¡x°Ó'7¸;Ô'FÈ4Ï8É8ÐT`×KaÒKaØÐØ—‘˜,Ó'×-Ð-¨2€JÜ˜:×&Ò&Ü˜Ó'ˆ	ÛˆDÜ˜dÓ#×0Ð0 yˆDÜ#3°DÔ:KÓ#LÑ ˆKØ×ÑÔ5°dÄXÈdÃ^ÐhsÔtÖuñ ô 	�‰ÐjÔkØÐÜ
‡L�LÐbÔdgÐhrÓdsÔtØ˜˜"ÐÐr   c               ó    € V ^8„  d   QhRRRR/# rP   r   )r   s   "r   r   r   “   s    € ÷ Vñ VÐ+?ð VÐDXñ Vr   c                ó>   € V '       g   V # \        \        V 4      4      # )zqBoth strippers in canonical order (interrupted blocks, then dangling tail); same list object when nothing strips.)rr   rj   )rQ   s   &r   Úsanitize_replay_historyru   “   s   € çØÐÜ(Ô)EÀmÓ)TÓUÐUr   Únowc               ó$   € V ^8„  d   QhRRRRRR/# )r   rQ   r?   rv   zOptional[float]r   r   )r   s   "r   r   r   š   s)   € ÷ Añ AØ'ðAØ1@ðAàñAr   c               óŒ   € V '       g   V # Vf   \         P                   ! 4       p\        V 4      p\        V4      p\        W!R7      # )am  Apply every destructive replay transform in the shared, fixed order.

Resume surfaces and the send path must serialize the same history bytes, or a
resumed request diverges in the middle of the cached prefix.

The input is never modified. ``now`` is the expiry clock; the send path passes the
turn's admission time so every request in one turn sees the same bytes.
)rv   )Útimerj   rr   Ú#strip_stale_dangerous_confirmations)rQ   rv   ra   s   &$ r   Úcanonicalize_replay_historyr{   š   s<   € ÷ ØÐØ
‚{Ü�iŠi‹kˆÜ*¨=Ó9€GÜ+¨GÓ4€GÜ.¨wÔ@Ð@r   g      N@rJ   Ú _DANGEROUS_CONFIRMATION_PATTERNSz¦[A high-risk confirmation previously given here has EXPIRED and must not be acted on. Ask the user to re-confirm explicitly before performing any destructive action.]c               ó    € V ^8„  d   QhRRRR/# r   r   )r   s   "r   r   r   Å   s   € ÷ Sñ S sð S¨tñ Sr   c                óô   a€ \        V \        4      '       g   R# V P                  4       P                  4       o\        ;QJ d#    V3R l\
         4       F  '       g   K   R# 	  R# ! V3R l\
         4       4      # )zITrue if user-message text contains a known dangerous confirmation phrase.Fc              3  ó,   <"  € T F	  qS9   x € K  	  R # 5irB   r   )rC   ÚpatternÚlowereds   & €r   rD   Ú,is_dangerous_confirmation.<locals>.<genexpr>Ê   s   øé € ÐRÑ1Q g˜'Ö!Ó1Qùs   ƒT)r   r   ÚstripÚlowerrF   r|   )r   r�   s   &@r   Úis_dangerous_confirmationr…   Å   sR   ø€ ä�gœs×#Ò#ÙØ�m‰m‹o×#Ñ#Ó%€Gß‹3ÔRÕ1QÓR�3Œ3ÐRŠ3ÐRˆ3ÔRÕ1QÓRÓRÐRr   Úexpiry_secondsc               ó(   € V ^8„  d   QhRRRRRRRR/# )r   rQ   r?   rv   Úfloatr†   r   r   )r   s   "r   r   r   Í   s2   € ÷ (ñ (Ø'ð(ð 
ð(ð ð	(ð
 ñ(r   c          	     óŒ  € V '       g   V # . pV  EF0  p\        V\        4      '       d(   VP                  R4      R8X  d   VP                  R4      MRpVe"   \        VP                  RR4      4      '       g   VP	                  V4       K{  \        VRR7      pVf   \        P                  MW,
          p^ Tu;8:  d   V8:  d   M MVP	                  V4       KÉ  \        P                  R	YrVP                  R4      ;'       g    RR
,          4       \        V4      p\        VR&   \        V4       VP	                  V4       EK3  	  V# )u"  Redact IN PLACE dangerous-confirmation text older than ``expiry_seconds`` in user messages: a confirmation
surviving a restart reads as a fresh re-confirmation minutes later. Untimestamped messages (legacy
transcripts, test scaffolding) are left untouched.

See #59607.
On the next inbound message â€” possibly a casual "are you there?" from the user minutes later â€” the LLM
sees the stale confirmation and may interpret the new turn as a fresh re-confirmation, re-executing the
destructive action. This is the failure mode reported in #59607.
rS   ÚuserÚ	timestampNr   r4   zmessage timestamp)ÚfieldzYRedacting stale dangerous-confirmation text in user message (age=%.1fs, expiry=%.1fs): %r:NéP   N)r   r%   r&   r…   r]   r   ÚmathÚinfr_   r`   Ú_EXPIRED_CONFIRMATION_SENTINELr
   )	rQ   rv   r†   ra   rd   ÚtsÚts_fÚageÚredacteds	   &$$      r   rz   rz   Í   s	  € ÷ ØÐØ$&€GÜˆÜ%/°´T×%:Ò%:¸s¿w¹wÀv»ÐRXÔ?XˆS�W‰W�[Ô!Ð^bˆØŠ:Ô6°s·w±w¸yÈ"Ó7M×NÒNØ�N‰N˜3ÔÙô ˜BÐ&9Ô:ˆØš,Œd�hŠh¨C­JˆØ�Ö%�~×%Ø�N‰N˜3ÔÙÜ�‰ØgØ #§'¡'¨)Ó"4×":Ð":¸¸CÕ!@ô	
ô ˜“9ˆÜ<ˆ�Ñä˜xÔ(Ø�‰�x× ñ+ ð, €Nr   )z{[Orphan recovery: interrupted side-effecting tool may have executed; its effect is UNKNOWN. Inspect state before retrying.]z?[Orphan recovery: interrupted read-only tool did not complete.])zƒ[Orphan recovery: this tool may have executed before Hermes stopped; its effect is UNKNOWN. Inspect current state before retrying.]zJ[Orphan recovery: this read-only tool did not complete and had no effect.])zconfirm forced restartzconfirm forced rebootzconfirm shutdownzconfirm rebootzconfirm power offzyes, delete everythingzconfirm wipezconfirm factory resetu   ç¢ºèª�å¼·åˆ¶é‡�é–‹æ©Ÿu   ç¢ºèª�å¼·åˆ¶é‡�é–‹u   ç¢ºèª�é‡�å•Ÿ)-Ú__conditional_annotations__Ú__doc__Ú
__future__r   r"   ÚloggingrŽ   Úrery   Útypingr   r   r   r   Úagent.tool_dispatch_helpersr   Ú agent.tool_result_classificationr	   Úagent.turn_contextr
   Úhermes_cli.timefmtr   Ú	getLoggerÚ__name__r_   r^   rn   ÚcompileÚ
IGNORECASEr)   r-   r7   r<   rG   rN   rj   rr   ru   r{   Ú&_DANGEROUS_CONFIRMATION_EXPIRY_SECONDSr|   Ú__annotations__r�   r…   rz   )r•   s   @r   Ú<module>r¥      sã   øðôkõ #ã Û Û Û 	Û ß ,Ó ,å @Ý FÝ 5Ý +à	×	Ò	˜8Ó	$€ðÐ ðÐ ð ŸšÐ$YÐ[]×[hÑ[hÓiÐ õ?õ,?õ<õNõõ$õNõ6VðAØCG÷Að2 *.Ð &ð+Ð   %ó ð*ð õSð(ð C÷	(ñ (r   