+
    jHàje  ã                  ó  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	t	^ RI
t
^ RIt^ RIt^ RIHt ^ RIHtHtHt ^ RIHt ]P*                  ! ]4      tRtRtRt]P6                  ! 4       t]! 4       tR	]R
&   R t R R lt!R R lt"RR/R R llt#RR/R R llt$R R lt%RR/R 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-R* R+ lt.R7R,R/R- R. lllt/R7R,R/R/ R0 lllt0R,R/R1 R2 llt1R3 R4 lt2R5 R6 lt3R# )8aÁ  Flush pending messages and agent transcripts to disk before shutdown to prevent data loss.

When FTS5 corruption blocks ``INSERT INTO messages``, ``_pending_messages`` and the live
``agent._session_messages`` are the only surviving copies; shutdown ``.clear()`` would drop them.
All hooks write atomic JSON payloads under ``<hermes_home>/pending_messages/``:
``flush_pending_to_file`` / ``flush_overflow_to_file`` (queue head / FIFO tail, before clear),
``recover_pending_to_db`` (at startup, before resume turns; replays via ``SessionDB.append_message``,
deletes each file on success), ``flush_agent_history_to_file`` (DB flush raised),
``spool_dropped_transcript_message`` / ``drain_transcript_spool``.
)ÚannotationsN)ÚPath)ÚAnyÚDictÚOptional)Úmake_user_dirÚtranscript_cap_dropz'shutdown-with-unpersisted-agent-historyz.badz	set[Path]Ú_REPLAYED_UNREMOVABLEc                 óÀ   € ^ RI Hp  V ! 4       R,          p^ RI Hp V! V4       \        VRRRR7       \        P
                  R8X  d   \        P                  ! VR4       V# )zIReturn the pending-messages flush directory under the active HERMES_HOME.)Úget_hermes_homeÚpending_messages)Úassert_named_profile_home_liveTiÀ  )ÚparentsÚexist_okÚmodeÚposix)Úhermes_constantsr   r   r   ÚosÚnameÚchmod)r   Ú	flush_dirr   s      Ú;/home/ubuntu/.hermes/hermes-agent/gateway/shutdown_flush.pyÚ_get_flush_dirr   ,   sL   € å0ÙÓ!Ð$6Õ6€IÝ?Ù" 9Ô-Ü�) T°D¸uÕEÜ	‡w�w�'ÔÜ
�Š�˜EÔ"ØÐó    c               ó$   € V ^8„  d   QhRRRRRR/# )é   r   r   Úpayloadúdict[str, Any]Úreturn© )Úformats   "r   Ú__annotate__r!   8   s!   € ÷ ñ ˜dð ¨^ð Àñ r   c                óH  € ^ RI Hp V R\        P                  ! 4       P                   R2,          pV! W1R\
        R7       \        P                  R8X  dV    \        P                  ! V \        P                  4      p \        P                  ! V4       \        P                  ! V4       V# V#   \         d!   p\        P                  RT4        Rp?L@Rp?ii ; i  \        P                  ! T4       i ; i  \         d"   p\        P                  RT4        Rp?T# Rp?ii ; i)	zOAtomically write one private, uniquely named recovery payload; return its path.)Úatomic_json_writezpending-z.jsoni€  )r   Údefaultr   z-Failed to fsync pending-message directory: %sN)Úutilsr#   ÚuuidÚuuid4ÚhexÚstrr   r   ÚopenÚO_RDONLYÚfsyncÚOSErrorÚloggerÚdebugÚclose)r   r   r#   Ú
final_pathÚdirectory_fdÚexcs   &&    r   Ú_write_payloadr4   8   sâ   € å'Ø˜x¬¯
ª
«×(8Ñ(8Ð'9¸Ð?Õ?€JÙ�j°¼sÕCÜ	‡w�w�'Ôð
	'ÜŸ7š7 9¬b¯k©kÓ:ˆLð'Ü—’˜Ô&ô —’˜Ô&ØÐˆ:Ðøô	 ô SÜ—‘ÐLÈc×RÑRûðSûô —’˜Õ&ûô ô 	OÜ�L‰LÐHÈ#×NÐNð Ðûð	OúsB   Á%C5 Á<B, Â,CÂ7CÃC ÃCÃC ÃC2Ã5D!Ä DÄD!c               ó0   € V ^8„  d   QhRRRRRRRRRRR	R
/# )r   r   r   Úkindr)   Úsession_keyÚvaluer   Úextrar   Úboolr   )r    s   "r   r!   r!   N   s8   € ÷ 
ñ 
˜Dð 
¨ð 
¸#ð 
Àcð 
ÐTWð 
Ð\`ñ 
r   c                ó²   €  \        V4      pVf   R# \        V RV/VCRV/C4       R#   \         d#   p\        P	                  RYT4        Rp?R# Rp?ii ; i)zNSerialise and write one pending value; return True when a payload was written.NFr7   ÚdataTz%Failed to flush %s message for %s: %s)Ú_serialise_valuer4   Ú	Exceptionr.   r/   )r   r6   r7   r8   r9   Ú
serialisedr3   s   &&&&,  r   Ú_flush_valuer@   N   s^   € ðÜ% eÓ,ˆ
ØÒÙÜ�y =°+Ð"[ÀÐ"[ÈÐPZÑ"[Ô\ÙøÜô Ü�‰Ð<¸dÐQTÔUÝûðús   ‚) “) ©A´AÁAÚreasonÚshutdownc               ó$   € V ^8„  d   QhRRRRRR/# )r   Úpendingr   rA   r)   r   Úintr   )r    s   "r   r!   r!   [   s"   € ÷ 
ñ 
 >ð 
¸cð 
ÐSVñ 
r   c               ó&  € V '       g   ^ # \        4       \        \        P                  ! 4       4      ^ rCp\        V P	                  4       4       F!  w  rVVf   K  V\        VRWVWR7      ,          pK#  	  V'       d   \        P                  RWBV4       V# )zXSerialise non-empty ``_pending_messages`` slots (``MessageEvent`` or str); return count.rD   )rA   Útsz/Flushed %d pending message(s) to %s (reason=%s))r   rE   ÚtimeÚlistÚitemsr@   r.   Úinfo)rD   rA   r   rG   Úflushedr7   r8   s   &$     r   Úflush_pending_to_filerM   [   sq   € çÙÜ+Ó-¬s´4·9²9³;Ó/?À�7€IÜ" 7§=¡=£?Ö3ÑˆØÔØ”| I¨y¸+ÐU[ÔcÕcŠGñ 4÷ Ü�‰ÐEÀwÐ[aÔbØ€Nr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   Úoverflow_by_sessionr   rA   r)   r   rE   r   )r    s   "r   r!   r!   h   s"   € ÷ ñ °ð È3ð Ð`cñ r   c               ó†  € V '       g   ^ # \        4       \        \        P                  ! 4       4      ^ rCp\        V P	                  4       4       FQ  w  rVV'       d	   V'       g   K  \        \        V4      4       F"  w  rxVf   K  V\        VRWXVW7R7      ,          pK$  	  KS  	  V'       d   \        P                  RWBV4       V# )aj  Serialise the FIFO overflow tails (``queued_events``) to disk; return events flushed.

The adapter slot holds the queue head and ``SessionState.conversation.queued_events`` the
tail; both must survive restart. Each event is its own payload in the slot-flush shape so
``recover_pending_to_db`` replays them unchanged; ``seq`` preserves arrival order per session.
Úoverflow)rA   rG   Úseqz7Flushed %d queued overflow message(s) to %s (reason=%s))	r   rE   rH   rI   rJ   Ú	enumerater@   r.   rK   )	rO   rA   r   rG   rL   r7   ÚeventsrR   r8   s	   &$       r   Úflush_overflow_to_filerU   h   sž   € ÷ ÙÜ+Ó-¬s´4·9²9³;Ó/?À�7€IÜ#Ð$7×$=Ñ$=Ó$?Ö@Ñˆß§&ÙÜ#¤D¨£LÖ1‰JˆCØÔ Øœ<¨	°:¸{ÐZ`Ø+-ô8õ 8’ó 2ñ  A÷ Ü�‰ÐMÈwØô	à€Nr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   Ú
session_idr)   Úmessager   r   zOptional[Path]r   )r    s   "r   r!   r!      s"   € ÷ ñ °ð ¸~ð ÐR`ñ r   c                ó   €  \        \        4       RV R\        R\        \        P                  ! 4       4      R\        \        4      RRV RV//4      #   \         d"   p\        P                  RY4        R	p?R	# R	p?ii ; i)
aW  Spool a cap-evicted transcript message; ``None`` on failure (callers degrade to drop+log).

Uses the same on-disk pending spool as :func:`flush_pending_to_file` (one atomic JSON payload per
message under ``<hermes_home>/pending_messages/``), so a runtime cap rotation no longer silently
discards user data while the process stays up (#78182).
r7   rA   rG   rR   r<   rW   rX   z9Failed to spool cap-dropped transcript message for %s: %sN)
r4   r   ÚTRANSCRIPT_CAP_DROP_REASONrE   rH   ÚnextÚ_TRANSCRIPT_SPOOL_SEQr>   r.   r/   )rW   rX   r3   s   && r   Ú spool_dropped_transcript_messager]      sz   € ðÜœnÓ.Ø˜: xÔ1KÈTÔSVÔW[×W`ÒW`ÓWbÓScØ”4Ô-Ó.Ø�\ :¨y¸'ÐBð1
ó ð 	øô
 ô Ü�‰ÐPÐR\ÔbÝûðús   ‚AA ÁA=ÁA8Á8A=Údb_known_failingFc               ó$   € V ^8„  d   QhRRRRRR/# )r   rW   r)   r^   r:   r   ztuple[int, int]r   )r    s   "r   r!   r!   ‘   s"   € ÷ 5ñ 5 sð 5Èð 5ÐZiñ 5r   c               óÎ  €  \        \        4       P                  R4      4      p. pT EF  pT\        9   d   K   \        P                  ! TP                  RR7      4      p\        T\        4      '       d1   TP                  R4      \        8w  g   TP                  R4      T 8w  d   K€  TP                  R4      ;'       g    / P                  R	4      p\        T\        4      '       g+   \        P                  R
T4       TP                  RR7       Kê  TP!                  \#        YvP$                  4      Yh34       EK  	  \'        TR R7      ^ ^ rºp	\)        T	4       F&  w  pw  rÖp T! T4       \1        Y`4       T
^,          p
K(  	  T
'       d   \        P3                  RT
T 4       Y«3#   \         d#   p\        P                  RT4       Ru Rp?# Rp?ii ; i  \         d     EK¼  i ; i  \         dr   p\+        T4      '       d   \-        Y`T4        Rp?K½  T'       d   \        P
                  M\        P                  ! RY`T4       \/        T	4      T,
          p Rp? KÜ  Rp?ii ; i)a’  Replay cap-dropped transcript messages spooled for *session_id*; return ``(replayed,
remaining)``. ``replay(message_dict)`` runs per message in drop order; a spool file is deleted
only after its replay succeeds. The first failure stops the drain (the DB is likely still
unhealthy) and keeps the rest for retry, unless the DB rejected that row's own values
(:func:`is_row_rejection`): it is quarantined and the drain goes on. With ``db_known_failing``
(the caller's last write already failed and is being logged/escalated) a replay failure is
expected and logs at DEBUG, so a stalled session does not add one WARNING per append on top of
its ERROR (#114266).
zpending-*.jsonz Cannot scan transcript spool: %sNú	utf-8-sig©ÚencodingrA   r7   r<   rX   z6Removing structurally invalid transcript spool file %sT©Ú
missing_okc                ó   € V ^ ,          # ©é    r   )Úes   &r   Ú<lambda>Ú(drain_transcript_spool.<locals>.<lambda>´   s   € ÀÀ1Ær   ©ÚkeyzWReplay of spooled transcript message %s for %s failed; keeping spool file for retry: %szBReplayed %d spooled transcript message(s) for %s after DB recovery)rh   rh   )rI   r   Úglobr>   r.   r/   r	   ÚjsonÚloadsÚ	read_textÚ
isinstanceÚdictÚgetrZ   ÚwarningÚunlinkÚappendÚ_spool_sort_keyr   ÚsortedrS   Úis_row_rejectionÚ_quarantine_spool_fileÚlenÚ_remove_replayedrK   )rW   Úreplayr^   Ú
candidatesr3   ÚentriesÚpathr   rX   ÚorderedÚreplayedÚ	remainingÚidxÚ_keys   &&$           r   Údrain_transcript_spoolr‡   ‘   s   € ðÜœ.Ó*×/Ñ/Ð0@ÓAÓBˆ
ð €GÜˆØÔ(Ô(Ùð	Ü—j’j §¡¸ Ó!EÓFˆGô
 ˜7¤D×)Ò)Ø—;‘;˜xÓ(Ô,FÔFØ—;‘;˜}Ó-°Ô;ÙØ—;‘;˜vÓ&×,Ð,¨"×1Ñ1°)Ó<ˆÜ˜'¤4×(Ò(Ü�N‰NÐSÐUYÔZØ�K‰K 4ˆKÔ(ÙØ�‰œ¨·±Ó;¸TÐK×Lñ% ô& $*¨'±~Ô#FÈÈ1�y€GÜ&/°Ö&8Ñ"ˆÑ"ˆd˜'ð
	Ù�7ŒOô 	˜Ô*Ø�A�Šñ '9÷ Ü�‰ÐXÐZbØô	 àÐÐøôS ô Ü�‰Ð7¸Ô=Ø�ûðûô ô 	Ûð	ûô$ ô 	Ü ×$Ò$Ü& t¸Ô=Ýß-ŒV�\Š\´6·>±>ÐAð3Ø48ÀcôKô ˜G› sÕ*ˆIÞûð	úsR   ‚"F% º&GÅ#G(Æ%GÆ0GÇGÇGÇG%Ç$G%Ç(I$Ç3IÈIÈ:IÉI$c               ó    € V ^8„  d   QhRRRR/# )r   r8   r   r   r:   r   )r    s   "r   r!   r!   É   s   € ÷ ñ �cð ˜dñ r   c                ód   €  \         P                  ! V 4       R #   \        \        3 d     R# i ; i)TF)ro   ÚdumpsÚ	TypeErrorÚ
ValueError)r8   s   &r   Ú
_json_safer�   É   s-   € ðÜ�
Š
�5ÔÙøÜ”zÐ"ô Úðús   ‚ š/®/c               ó    € V ^8„  d   QhRRRR/# )r   r8   r   r   zOptional[dict]r   )r    s   "r   r!   r!   Ñ   s   € ÷  ñ  ˜Cð   Nñ  r   c                ó^  € \        V R4      '       dM   R\        V RR4      /pR F5  p\        WR4      pVf   K  \        V4      '       d   TM
\        V4      W&   K7  	  V# \	        V \        4      '       d   RV /# \	        V \
        4      '       d   \        V 4      '       d   V # R\        V 4      /# )z<Convert a pending message value to a JSON-serialisable dict.ÚtextÚ N)rW   ÚplatformÚ	sender_idÚsender_nameÚreply_toÚmediaÚ	raw_event)ÚhasattrÚgetattrr�   r)   rr   rs   )r8   ÚresultÚattrÚvals   &   r   r=   r=   Ñ   s�   € äˆu�f×ÒØ"(¬'°%¸ÀÓ*DÐ!Eˆó"ˆDä˜% tÓ,ˆCØŒÜ&0°§o¢o™s¼3¸s»8�“ñ	"ð
 ˆÜ�%œ×ÒØ˜ˆÐÜ�%œ×Ò¤:¨e×#4Ò#4ØˆØ”C˜“JÐÐr   c               ó    € V ^8„  d   QhRRRR/# )r   r8   r   r   Úfloatr   )r    s   "r   r!   r!   â   s   € ÷ 4ñ 4˜ð 4 ñ 4r   c                óæ   € \        V \        4      '       g   \        V \        \        34      '       g   R#  \        V 4      p\
        P                  ! T4      '       d   T# R#   \         d     R# i ; i)aã  Coerce a spool ordering field to a float; unusable values sort first.

Ordering fields are read back from JSON on disk and may be missing or
corrupt.  A sort key that mixes ``str`` and ``int`` raises ``TypeError``
and would abort the entire recovery pass, so anything non-numeric is
normalised to ``0.0``.  So is a JSON integer too large for a float
(``float(10**400)`` raises ``OverflowError``) and a NaN/Infinity literal
(NaN compares false both ways and would scramble the sort).
ç        )rr   r:   rE   rž   ÚOverflowErrorÚmathÚisfinite)r8   Únumbers   & r   Ú_sort_numberr¥   â   sa   € ô �%œ×Ò¤j°¼¼e¸×&EÒ&EÙðÜ�u“ˆô —]’] 6×*Ò*ˆ6Ð3°Ð3øô ô Úðús   ¶A! Á!A0Á/A0c               ó$   € V ^8„  d   QhRRRRRR/# )r   r   r   r   r)   r   Útupler   )r    s   "r   r!   r!   õ   s&   € ÷ Wñ W˜^ð W°3ð W¸5ñ Wr   c                ón   € \        V P                  R4      4      \        V P                  RR4      4      V3# )z¦Drop-order key ``(ts, seq, filename)`` for one spool payload. The live drain and the restart
pass both sort on it, so they replay a session's files in the same order.rG   rR   éÿÿÿÿ)r¥   rt   )r   r   s   &&r   rx   rx   õ   s0   € ô ˜Ÿ™ DÓ)Ó*¬L¸¿¹ÀUÈBÓ9OÓ,PÐRVÐVÐVr   c               ó    € V ^8„  d   QhRRRR/# )r   r3   ÚBaseExceptionr   r:   r   )r    s   "r   r!   r!   þ   s   € ÷ 
Sñ 
S˜-ð 
S¨Dñ 
Sr   c                ó  € \        V \        P                  4      '       dO   \        V \        P                  \        P                  34      ;'       d    R\        V 4      P                  4       9   # \        V \        \        \        \        34      # )a  True when the database refused this row's own values, so no retry can succeed: a value sqlite
cannot bind (an unsupported type, or an int outside 64 bits), or a malformed field that fails
before the write. Lock, I/O, corruption and routing failures stay retryable, and so do constraint
failures, which can be session-wide. Only a binding message counts among sqlite errors:
``InterfaceError("no more rows available")`` is WAL contention that ``SessionDB`` retries and
re-raises unchanged once its patience runs out.zbinding parameter)rr   Úsqlite3ÚErrorÚInterfaceErrorÚProgrammingErrorr)   Úlowerr‹   rŒ   ÚAttributeErrorr¡   )r3   s   &r   rz   rz   þ   sh   € ô �#”w—}‘}×%Ò%Ü˜3¤×!7Ñ!7¼×9QÑ9QÐ RÓS÷ <ð <Ø'¬3¨s«8¯>©>Ó+;Ñ;ð	=ä�cœI¤z´>Ä=ÐQÓRÐRr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   r�   r   rW   r)   r   ÚNoner   )r    s   "r   r!   r!     s&   € ÷ 	jñ 	j˜4ð 	j¨Sð 	j°Tñ 	jr   c                ó¶   €  V P                  RR7       R#   \         d8   p\        P                  T 4       \        P                  RYT4        Rp?R# Rp?ii ; i)a  Delete a spool file whose row was just written. If it cannot be deleted, this process never
replays it again, and replay goes on with the files after it, which are still older than any
live row. Nothing on disk marks it, so a restart writes the row a second time.Trd   z‡Spooled transcript row for %s was written, but %s could not be deleted (%s); delete it by hand, or the next start writes that row againN)rv   r-   r	   Úaddr.   Úerror)r�   rW   r3   s   && r   r}   r}     sU   € ðjØ�‰˜tˆÖ$øÜô jÜ×!Ñ! $Ô'Ü�‰ð RØS]Ðeh÷	jò 	jûðjús   ‚ –A¡,AÁAc               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   r�   r   rW   r)   r3   r«   r   r´   r   )r    s   "r   r!   r!     s(   € ÷ ,ñ , ð ,°3ð ,¸]ð ,Ètñ ,r   c                ó  € V P                  V P                  \        ,           4      p V P                  V4       \
        P                  RYT4       R#   \         d#   p\
        P                  RYY$4        Rp?R# Rp?ii ; i)z¾Move a spool file whose row the database rejected out of every replay, so it cannot hold its
session's later rows back on every write and every restart. It stays on disk for manual recovery.zfSpooled transcript row for %s in %s was rejected by the database (%s) and could not be quarantined: %sNz�Spooled transcript row for %s was rejected by the database (%s); moved it to %s for manual recovery and continuing with the session's later rows)Ú	with_namer   ÚQUARANTINE_SUFFIXÚreplacer-   r.   r·   ru   )r�   rW   r3   ÚtargetÚmove_excs   &&&  r   r{   r{     sr   € ð �^‰^˜DŸI™IÔ(9Õ9Ó:€FðØ�‰�VÔô
 ‡N�Nð Và Fö,øô	 ô Ü�‰ð .Ø/9Àô	Påûðús   ¨A ÁA?ÁA:Á:A?c               ó   € V ^8„  d   QhRR/# )r   r   z
list[Path]r   )r    s   "r   r!   r!   &  s   € ÷ ,ñ , ñ ,r   c                óÆ  € . pV  F�  p \         P                  ! VP                  RR7      4      p\        V\        4      '       d-   VP                  ^ .\        W2P                  4      O5V34       Kl  VP                  ^RRVP                  3V34       K�  	  VP                  R R7       V UUu. uF  w  rBVNK	  	  upp#   \        \        \
        3 d    Rp Lªi ; iu uppi )a�  Order recovery payloads by :func:`_spool_sort_key`, as :func:`drain_transcript_spool` does.
``SessionDB`` restores a conversation by AUTOINCREMENT id and spool files have random names, so
any other order permanently scrambles the recovered transcript.

Keeps only the key and path, not decoded payloads; the caller re-reads each file to replay it.
Unparseable files sort last by name.
ra   rb   Nr    c                ó   € V ^ ,          # rg   r   )Úentrys   &r   rj   Ú$_order_flush_files.<locals>.<lambda>9  s   €  5¨¦8r   rl   )ro   rp   rq   r-   rŒ   ÚRecursionErrorrr   rs   rw   rx   r   Úsort)Úpathsr€   r�   r   r†   s   &    r   Ú_order_flush_filesrÇ   &  sÉ   € ð €GÛˆð	Ü—j’j §¡¸ Ó!EÓFˆGô �gœt×$Ò$Ø�N‰N˜QÐE¤°¿)¹)Ó!DÑEÀtÐLÖMà�N‰N˜Q  S¨$¯)©)Ð4°dÐ;Ö<ñ ð ‡L�LÑ+€LÔ,Ù#*Ô+¡7‘Z�T‹D¡7Ò+Ð+øô œ¤^Ð4ô 	ØŠGð	üó ,s   ‰&B?Â.CÂ?CÃCÚsession_resolverc               ó   € V ^8„  d   QhRR/# ©r   r   rE   r   )r    s   "r   r!   r!   =  s   € ÷ Sñ SÈñ Sr   c               ó(   € \        WR7      ^ ,          # )zªReplay flush-dir ``*.json`` files into state.db; return the number of messages recovered.
See :func:`recover_pending_spool`, which also reports the sessions it held back.©rÈ   )Úrecover_pending_spool)Ú
session_dbrÈ   s   &$r   Úrecover_pending_to_dbrÏ   =  s   € ô ! ÔOÐPQÕRÐRr   c               ó   € V ^8„  d   QhRR/# )r   r   ztuple[int, set[str]]r   )r    s   "r   r!   r!   C  s   € ÷ 8,ñ 8,ÐH\ñ 8,r   c          	     óœ  € \        \        4       P                  R4      4      pV'       g   ^ \        4       3# V RJ pV'       d   ^ RIHp V! 4       p ^ p/ p V FŽ  pV\        9   d   K   \        P                  ! VP                  RR7      4      pVP                  R4      \        8X  d   KS  \        WVVVR7      '       d'   V^,          p\        WxP                  RR	4      4       KŽ  K�  	  V'       d;   \"        P$                  ! \        4      ;_uu_ 4        ^ RIHp
 V
! V 4       RRR4       M V'       d   \        P)                  RV4       V'       dS   \        P)                  R\+        VP-                  4       4      RP/                  R VP1                  4        4       4      4       V\        V4      3#   \         d#   p	\        P!                  R
Yy4        Rp	?	EK‚  Rp	?	ii ; i  + '       g   i     LÅ; i  T'       dN   \"        P$                  ! \        4      ;_uu_ 4        ^ RIHp
 T
! T 4       RRR4       i   + '       g   i     i ; ii ; i)a4  Replay flush-dir ``*.json`` files via ``SessionDB.append_message``, deleting each on success.

``session_db=None`` opens (and afterwards releases) the shared default ``state.db``.
``session_resolver`` (optional ``(session_key, not_after=ts) -> (session_id, db) | None``, e.g.
``SessionStore.resolve_session_id_for_key``) is required for real flush files: adapter
``MessageEvent`` objects carry no ``session_id``, so without it every recovery lands in the skip
branch. A returned ``db`` routes the append to the profile store owning the key (multiplexed
gateways); ``None`` falls back to ``session_db``. Returns ``(recovered, held_back)``: the number of
messages recovered, and the session ids whose spool files this pass held back after a failed
replay, so the live writer can drain them before that session's next row.
z*.jsonN)Úacquirera   rb   rA   )rÈ   Úblocked_sessionsr7   r‘   z-Failed to recover pending message from %s: %s)Úrelease_or_closez3Recovered %d pending message(s) from shutdown flushzTHeld back %d spooled transcript file(s) for the next start after a failed replay: %sz, c              3  ó6   "  € T F  w  rV R V R2x € K  	  R# 5i)z (Ú)Nr   )Ú.0ÚsidÚcounts   &  r   Ú	<genexpr>Ú(recover_pending_spool.<locals>.<genexpr>z  s!   é € Ð\ÑC[±Z°S   R¨ w¨aÕ0ÓC[ùs   ‚)rÇ   r   rn   ÚsetÚhermes_state_registryrÒ   r	   ro   rp   rq   rt   ÚAGENT_HISTORY_REASONÚ_recover_one_payloadr}   r>   r.   ru   Ú
contextlibÚsuppressrÔ   rK   ÚsumÚvaluesÚjoinrJ   )rÎ   rÈ   Úflush_filesÚown_dbrÒ   Ú	recoveredrÓ   r�   r   r3   rÔ   s   &$         r   rÍ   rÍ   C  sØ  € ô %¤^Ó%5×%:Ñ%:¸8Ó%DÓE€KßØ”#“%ˆxˆØ˜4Ð€FßÝ1Ù“Yˆ
Ø€Ið (*Ðð-ÛˆDØÔ,Ô,Ùð[ÜŸ*š* T§^¡^¸[ ^Ó%IÓJ�ð —;‘;˜xÓ(Ô,@Ô@ÙÜ'¨
¸'Ø9IØ9I÷Kó Kð  •N�IÜ$ T¯;©;°}ÀbÓ+IÖJñ	Kñ  ÷* Ü×$Ò$¤Y×/Õ/ÝBÙ  Ô,÷ 0Ð/ð ÷ Ü�‰ÐIÈ9ÔUßä�‰ÐjÜÐ(×/Ñ/Ó1Ó2Ø—I‘IÑ\ÐCS×CYÑCYÔC[Ó\Ó\ô	^ð ”cÐ*Ó+Ð+Ð+øô ô [Ü—‘ÐNÐPT×ZÓZûð[ú÷ 0×/û÷ Ü×$Ò$¤Y×/Õ/ÝBÙ  Ô,÷ 0×/×/Ð/úð úsg   ÁG4 Á(A F1Â(G4 Â*9F1Ã#G4 ÄG!Æ1GÆ<GÇG4 ÇGÇG4 Ç!G1	Ç4+IÈH7	È.	IÈ7IÉ	Ic               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   r�   r   r   r   rÓ   zdict[str, int]r   r:   r   )r    s   "r   r!   r!   ~  s*   € ÷ :ñ :¨4ð :¸.ð :ØBPð:ØUYñ:r   c               ó®  € VP                  R4      \        8X  dÃ   ^ RIHp VP                  R/ 4      ;'       g    / pVP                  RR4      VP                  R4      r‡V'       d   \	        V\
        4      '       g   \        P                  RV4       R# Wt9   d   WG;;,          ^,          uu&   R#  V P                  ! R/ V! WxVP                  R	4      R
7      B  R# VP                  RR4      VP                  R/ 4      rjVP                  RR4      pV'       d	   V
'       g   \        P                  RV4       R# VP                  RR4      T rÜV'       g5   Ve1    V! W¢P                  R	4      R7      pV'       d   V^,          e   Vw  rÍV'       g   \        P                  RW¡4       R# VP                  VRVVP                  R	\        \        P                  ! 4       4      4      R7       R#   \         d.   p	\        T	4      '       d   \        YT	4        Rp	?	R# ^YG&   h Rp	?	ii ; i  \         d#   p	\        P                  RY©4       Rp Rp	?	LÝRp	?	ii ; i)z˜Append one flush payload to ``session_db``; False when it was not replayed (the file is kept,
or quarantined when the database rejected the row itself).rA   )Útranscript_append_kwargsr<   rW   r‘   rX   z]Cannot recover structurally invalid transcript spool file %s; preserved for manual inspectionFrG   )Úfallback_tsNTr7   r�   z^Cannot recover structurally invalid pending message from %s; the flush file has been preserved)Ú	not_afterz,Session key->id resolution failed for %s: %sz�Cannot recover pending message for %s: no session_id in flush file and session_key-to-id resolution failed. The message text is preserved in %sÚuser)rW   ÚroleÚcontentÚ	timestampr   )rt   rZ   Úgateway.session_transcriptrê   rr   rs   r.   ru   Úappend_messager>   rz   r{   r/   rE   rH   )rÎ   r�   r   rÈ   rÓ   rê   r<   Úspooled_sidrX   r3   r7   r�   rW   Ú	target_dbÚresolveds   &&&$$          r   rß   rß   ~  s  € ð ‡{�{�8ÓÔ :Ô:ÝGØ�{‰{˜6 2Ó&×,Ð,¨"ˆØ#Ÿx™x¨°bÓ9¸4¿8¹8ÀIÓ;N�Wß¤*¨W´d×";Ò";Ü�N‰Nð FØGKôMáØÔ*ð ×)¨QÕ.Ó)Ùð		Ø×%Ò%ÑvÑ(@ÀÐcj×cnÑcnÐosÓctÔ(uÒvñ ØŸ™ M°2Ó6¸¿¹ÀFÈBÓ8O�Ø�8‰8�F˜BÓ€Dß—{Ü�‰ð ;Ø<@ô	Báð !ŸH™H \°2Ó6¸
�	ßÐ*Ò6ð	Ù'¨¿{¹{È4Ó?PÔQˆH÷ ˜ �Ò/Ø$,Ñ!ˆJßÜ�‰ð =à>Iô	Qñ Ø×Ñ¨
¸ÈØ'.§{¡{°4¼¼T¿YºY»[Ó9IÓ'Jð ô LáøôI ô 	Ü ×$Ò$Ü& t¸#Ô>Ýð -.ÐÑ)Øûð	ûô0 ô 	Ü�L‰LÐGÈÔZØ�Hûð	ús6   Â2)G, ÅH' Ç,H$Ç7HÈHÈH$È'IÈ2IÉIc               ó   € V ^8„  d   QhRR/# rÊ   r   )r    s   "r   r!   r!   »  s   € ÷ ñ  sñ r   c                óâ  € ^ RI Hp ^ RIHpHpHp V P                  p\        VP                  R7      w  rgVP                  V4       \        V P                  RR4      '       g   V# \        V! 4       4      P                  4       pV! V P                  4       F—  w  rš\        V
4      P                  4       V8X  g'   \        V
4      R,          P                  4       '       g   KK  V! \        V
4      4      p \        VP                  R7      w  rÇ T! T4       Yl,          pTP                  T4       K™  	  V#   \          d%    \"        P%                  RT	RR	7        T! T4       KË  i ; i  T! T4       i ; i)
a7  Replay every ``pending_messages`` spool this gateway owns into state.db; return the count.

``_get_flush_dir`` follows the active HERMES_HOME, so a routed turn on a multiplexed gateway spools
its stalled transcript backlog under ``profiles/<name>/`` and the runtime drain forgets it on
restart. After the launch home, replay each served profile inside its own home so the default
store ``recover_pending_spool`` opens is that profile's state.db (#123584). The sessions a pass
holds back are marked on the session store, so their next live row drains the spool first.
)Ú_multiplex_profile_homes)r   Úreset_hermes_home_overrideÚset_hermes_home_overriderÌ   Úmultiplex_profilesFr   z.Pending-message recovery failed for profile %sT)Úexc_info)Úgateway.runrø   r   r   rù   rú   Úsession_storerÍ   Úresolve_session_id_for_keyÚmark_spooled_drop_sessionsr™   Úconfigr   ÚresolveÚis_dirr)   r>   r.   ru   )Úrunnerrø   r   rù   rú   Ústorerç   Ú	held_backÚlaunch_homer   ÚhomeÚtokenrÙ   s   &            r   Úrecover_gateway_pendingr
  »  s9  € õ 5ßfÑfà× Ñ €EÜ0À%×BbÑBbÔcÑ€IØ	×$Ñ$ YÔ/Ü�6—=‘=Ð"6¸×>Ò>ØÐÜ‘Ó(Ó)×1Ñ1Ó3€KÙ.¨v¯}©}Ö=‰
ˆÜ�‹:×ÑÓ ;Ô.´t¸D³zÐDVÕ7V×6^Ñ6^×6`Ò6`ÙÙ(¬¨T«Ó3ˆð	.Ü4Àe×FfÑFfÔgÑˆE�9ñ
 ' uÔ-ØÕˆ	Ø×(Ñ(¨Ö3ñ >ð Ðøô ô 	Ü�N‰NÐKÈTÐ\`ˆNÔaØá& uÖ-ð		ûñ ' uÕ-ús$   Ã2D2Ä2#E!ÅE$Å E!Å!E$Å$
E.c               ó$   € V ^8„  d   QhRRRRRR/# )r   rW   zOptional[str]ÚhistoryrI   r   r´   r   )r    s   "r   r!   r!   Ý  s!   € ÷ ñ ¨Mð ÀDð ÈTñ r   c                ó  € V'       g   R#  \        4       p. pV F\  p \        V\        \        \        \
        \        \        \        R4      34      pTP                  V'       d   TM
\	        V4      4       K^  	  \        VR\        RRRV R\        V4      RV/4       \        P                  R\        V4      V 4       R#   \         d     K³  i ; i  \         d#   p\        P                  R	T T4        Rp?R# Rp?ii ; i)
uK  Best-effort dump of an agent's in-memory transcript before teardown. Used when
``_flush_messages_to_session_db`` raises (e.g. FTS/SQLite corruption): the transcript is written
outside the broken DB so an operator can salvage it after repairing state.db. Failures are
swallowed â€” shutdown must never block on a best-effort backup.NrA   Úissuez#72680rW   rÙ   Úmessagesuo   Preserved %d in-memory message(s) for session %s (possible FTS corruption â€” recover after repairing state.db)z=Agent-history shutdown preservation failed for session %s: %s)r   rr   rs   rI   r)   rE   rž   r:   Útyperw   r>   r4   rÞ   r|   r.   ru   )rW   r  r   ÚsnapshotÚ_mÚplainÚ_es   &&     r   Úflush_agent_history_to_filer  Ý  sç   € ÷
 ÙðÜ"Ó$ˆ	ØˆÛˆBðÜ" 2¬¬d´C¼¼eÄTÌ4ÐPTË:Ð'VÓW�Ø—‘§e¡´°R³Ö9ñ ô 	�yØÔ*¨G°XØ˜* g¬s°8«}¸jÈ(ð#
ô 	ô 	�‰ð Xä˜8“} jö	2øô ô Úðûô ô Ü�‰ÐVÐXbØ÷	ò 	ûðús<   ŒC žAB?Á6AC Â?CÃ
C ÃCÃC ÃC>ÃC9Ã9C>)N)4Ú__conditional_annotations__Ú__doc__Ú
__future__r   rà   Ú	itertoolsro   Úloggingr¢   r   r­   rH   r&   Úpathlibr   Útypingr   r   r   Úhermes_dir_aclr   Ú	getLoggerÚ__name__r.   rZ   rÞ   r»   rÙ   r\   rÜ   r	   Ú__annotations__r   r4   r@   rM   rU   r]   r‡   r�   r=   r¥   rx   rz   r}   r{   rÇ   rÏ   rÍ   rß   r
  r  )r  s   @r   Ú<module>r!     s  øðô	õ #ã Û Û Û Û Û 	Û Û Û Ý ß &Ñ &Ý (à	×	Ò	˜8Ó	$€ð
 3Ð à@Ð àÐ à!ŸšÓ)Ð ñ $'£5Ð �yÓ (ò	õõ,
ð
ÀJ÷ 
ðÐQ[÷ õ.ð$5ÐPU÷ 5õpõ õ"4õ&Wõ
Sõ	jõ,õ,ñ.S¸t÷ Sð Sñ8,¸t÷ 8,ð 8,ðv:Ø*.÷:õz÷Dr   