Ë
    áß²jòa  ã            
       ój  — d Z ddlZddlZddlZddlmZ ddlmZmZm	Z	m
Z
mZ ddlmZmZmZmZmZmZ ddlmZmZ ddlmZ ddlmZmZ dd	lmZ dd
l m!Z!  ejD                  d«      Z#dZ$h d£Z%dZ&dZ'dede(fd„Z)de*de*fd„Z+dededede(fd„Z,dedededeeef   fd„Z-dedefd„Z.dedefd„Z/d„ Z0de	ee*ef      dedede(fd „Z1de2fd!„Z3d"e	e   de4fd#„Z5ded$e(dee*ef   fd%„Z6d"e	e   d&e
e	e      fd'„Z7d(e	ee*ef      d)e	e   d"e	e   ddfd*„Z8de(fd+„Z9d,e:d(e	ee*ef      d-e4de(fd.„Z; G d/„ d0«      Z<y)1zÑDurable transcript persistence for ``AIAgent`` (mixin; MRO-resolved from ``run_agent``): SQLite flush
with intrinsic ``_DB_PERSISTED_MARKER`` dedup, ephemeral-scaffolding filtering, explicit
trajectory export.é    N)Únullcontext)ÚAnyÚDictÚListÚOptionalÚTuple)ÚCOMPRESSED_SUMMARY_METADATA_KEYÚ_DB_PERSISTED_MARKERÚContextCompressorÚ_newest_checkpoint_carrierÚdrop_shadowed_checkpointsÚuser_originated_turn_view)ÚforwardÚforward_static)Úsanitize_context)Ú_is_multimodal_tool_resultÚ_multimodal_text_summary)Úsave_trajectory)Úsync_flushed_message_markersÚ	run_agent)Ú_empty_recovery_syntheticÚ_empty_terminal_sentinelÚ_thinking_prefillÚ_verification_stop_syntheticÚ_pre_verify_syntheticÚ_kanban_stop_syntheticÚ_dropped_toolcall_nudge>   ÚimageÚ	image_urlÚinput_image)Ú	reasoningÚreasoning_contentÚreasoning_detailsÚcodex_reasoning_itemsÚcodex_message_itemsÚ"_persist_after_admission_interruptÚmsgÚreturnc                 óX   ‡ — t        ‰ t        «      xr t        ˆ fd„t        D «       «      S )z`True when ``msg`` is internal recovery scaffolding that must never reach the durable transcript.c              3   ó@   •K  — | ]  }‰j                  |«      –— Œ y ­w©N©Úget)Ú.0Úflagr'   s     €ú>/home/ubuntu/.hermes/hermes-agent/agent/session_persistence.pyú	<genexpr>z,_is_ephemeral_scaffolding.<locals>.<genexpr>2   s   øè ø€ Ò(`¸4¨¯©°¯Ñ(`ùs   ƒ)Ú
isinstanceÚdictÚanyÚ_EPHEMERAL_SCAFFOLDING_FLAGS©r'   s   `r0   Ú_is_ephemeral_scaffoldingr7   0   s!   ø€ ä�cœ4Ó Ò`¤SÓ(`ÔC_Ô(`Ó%`Ð`ó    Ú
session_idc                 ó   — t        | xs d«      j                  «       }t        j                  dd|«      j                  d«      dd xs d}|r||k(  r|S |› dt	        j
                  |j                  dd	¬
«      «      j                  «       dd › �S )u¸   Path-safe component for a (possibly untrusted ``X-Hermes-Session-Id``) ID: non ``[A-Za-z0-9_-]`` â†’ ``_``,
    capped, plus a content hash when changed so distinct IDs cannot collide.Ú z[^\w-]Ú_z._Né`   Úsessionzutf-8Úsurrogatepass)Úerrorsé   )ÚstrÚstripÚreÚsubÚhashlibÚsha256ÚencodeÚ	hexdigest)r9   ÚrawÚ	sanitizeds      r0   Ú _safe_session_filename_componentrL   ;   s�   € ô ˆjÒ˜BÓ
×
%Ñ
%Ó
'€CÜ—‘�y # sÓ+×1Ñ1°$Ó7¸¸Ð<ÒIÀ	€IÙ
ˆy˜CÒØÐØˆ[˜œ'Ÿ.™.¨¯©°GÀO¨Ó)TÓU×_Ñ_ÓaÐbeÐceÐfÐgÐhÐhr8   ÚcontentÚoverridec                 ó„   — |duxr; | j                  t        «       xr# t        |t        «       xs t        |t        «      S )a  May the persist override replace ``content``? A plain-text override must not replace native image/audio
    blocks (a list override is the clean multimodal payload and does), nor a message MERGED with a compaction
    summary (overwriting would drop the summary).N)r-   r	   r2   Úlist)r'   rM   rN   s      r0   Ú_override_replaces_contentrQ   E   sF   € ð
 	˜Ðò 	JØ—‘Ô7Ó8Ð8ò	Jä˜G¤TÓ*Ð*ÒH¬j¸Ä4Ó.Hðr8   Úapi_contentc                 ót   — t        | dd«      }t        |||«      r|€t        |t        «      r||k7  r|}|}||fS )uX  ``(content, api_content)`` as the current turn's user row is written: the persist override is the
    clean transcript, the live content is what the wire sent â€” so when they differ and nothing else was
    injected, the live bytes ARE the sidecar. Shared by the flush and the turn-start stamp so the stamp
    matches the row the flush wrote.Ú_persist_user_message_overrideN)ÚgetattrrQ   r2   rB   )Úagentr'   rM   rR   rN   s        r0   Údurable_user_row_contentrW   P   sJ   € ô
 �uÐ>ÀÓE€HÜ! # w°Ô9ØÐ¤:¨g´sÔ#;ÀÈ8Ò@SØ!ˆKØˆØ�KÐÐr8   c                 óÖ   — | j                  t        «      rDt        | «      €9t        j                  | j                  d«      «      dk(  s| j                  d«      sy| j                  d«      S )zšStandalone handoffs are hidden so they never occupy the active user slot in retry/undo dispatch;
    merge-into-tail carriers keep their prior visibility.rM   Ú
standaloneÚ!_compressed_summary_has_user_turnÚhiddenÚdisplay_kind)r-   r	   r   r   Úclassify_summary_contentr6   s    r0   Ú_summary_display_kindr^   ]   sY   € ð 	�‰Ô/Ô0Ü% cÓ*Ð2ä×6Ñ6°s·w±w¸yÓ7IÓJÈlÒZØ—7‘7Ð>Ô?ð Ø�7‰7�>Ó"Ð"r8   c           	      óx  — t        | «      rt        | «      S t        | t        «      s| S | D �cg c]n  }t        |t        «      r\|j                  d«      dk(  s|j                  d«      t        v r1|j                  d«      dk(  rt        |j                  dd«      «      nd‘Œp }}|rdj                  |«      S dS c c}w )um   Text-only DB projection: multimodal envelopes â†’ summary; part lists keep text, images â†’ ``[screenshot]``.ÚtypeÚtextr;   z[screenshot]Ú
N)	r   r   r2   rP   r3   r-   Ú_IMAGE_PART_TYPESrB   Újoin)rM   ÚpÚtxts      r0   Ú_durable_contentrg   l   s¬   € ä! 'Ô*Ü'¨Ó0Ð0Ü�gœtÔ$Øˆð öàÜ�aœÔ A§E¡E¨&£M°VÒ$;¸q¿u¹uÀV»}ÔPaÑ?að #$§%¡%¨£-°6Ò"9ŒˆA�E‰E�&˜"ÓÔ¸~ÑMð€Cð ñ
 !ˆ4�9‰9�S‹>Ð* dÐ*ùòs   ­A3B7c                 ó8   — t        | dd«      }|€
t        «       S |S )a  Close and turn-start persistence can run on separate CLI threads: one critical section.

    ``__init__`` always creates ``_session_persist_lock``; only ``object.__new__``-built test stubs lack it
    (they run unlocked, matching the historical ``if persist_lock is None`` branch).
    Ú_session_persist_lockN)rU   r   )rV   Úlocks     r0   Ú_persist_lockrk   z   s$   € ô �5Ð1°4Ó8€DØ ˜LŒ;‹=Ð2¨dÐ2r8   ÚhistoryÚqueryrV   c                 óø   ‡ — t        ˆ fd„t        t        ‰ «      dz
  dd«      D «       d«      }|�‰ |   j                  d«      |k7  ryt	        d„ ‰ |dz   d D «       «      sy‰ |   }‰ |d…= d|t
        <   ||_        y)	uœ  Re-stage the transcript's unanswered tail row as THIS turn's user message; True when adopted.

    A dispatcher's re-run of a failed delivery turn resumes the DM its first attempt already persisted
    instead of appending it again. Rows loaded from the store are born durable (``_rows_to_conversation``),
    so handing the tail row back as ``agent._pending_cli_user_message`` makes ``_stage_turn_user_message``
    reuse it as this turn's user dict and the flush writes no second row. What differs per lane is only HOW
    the dispatcher knows the DM is unanswered:

    * ``hermes_cli.quiet_single_query.adopt_unanswered_turn`` â€” the delivery lanes' re-run is a fresh CLI
      process, told so through ``tools.bot_relay.RESUME_UNANSWERED_TURN_ENV``.
    * ``gateway.platforms.api_server`` â€” the peer-DM lane re-runs the turn in-process and calls this
      directly on the agent it just built for the re-run (#115325).

    The DM is not always the literal tail: a turn that died mid-way persisted its tool scaffolding â€” assistant
    ``tool_calls`` rows and their ``tool`` results â€” behind the DM before the failure text was built, and the
    dispatcher retries that too. The DM is still unanswered while nothing after it is a plain assistant reply,
    so it is adopted and the failed attempt's scaffolding leaves the in-memory transcript: the re-run starts
    the turn over from the DM (the rows stay in the DB as the record of the failed attempt; the re-run's
    answer lands after them as a valid continuation). Anything else declines â€” no user row at the tail, or a
    different text there â€” so a person's deliberate re-send of the same text is never swallowed.
    c              3   óv   •K  — | ]0  }t        ‰|   t        «      r‰|   j                  d «      dk(  r|–— Œ2 y­w)ÚroleÚuserN©r2   r3   r-   )r.   Úirl   s     €r0   r1   z(adopt_unanswered_turn.<locals>.<genexpr>š   s<   øè ø€ ò V�aÜ˜g a™j¬$Ô/°G¸A±J·N±NÀ6Ó4JÈfÒ4Tô ñ Vùs   ƒ69é   éÿÿÿÿNrM   Fc              3   óº   K  — | ]S  }t        |t        «      xr= |j                  d «      dk(  xs' |j                  d «      dk(  xr |j                  d«      –— ŒU y­w)rp   ÚtoolÚ	assistantÚ
tool_callsNrr   )r.   Úrows     r0   r1   z(adopt_unanswered_turn.<locals>.<genexpr>ž   sk   è ø€ ò -Øô ˜#œtÓ$ò  C¨#¯'©'°&«/¸VÑ*Cò  +BÈÏÉÐPVËÐ[fÑHfò  IAÐkn×krÑkrÐsó  lAó  Cñ -ùs   ‚AAT)ÚnextÚrangeÚlenr-   Úallr
   Ú_pending_cli_user_message)rl   rm   rV   ÚidxÚtails   `    r0   Úadopt_unanswered_turnr‚   „   sœ   ø€ ô, ó Vœ5¤ W£°Ñ!1°2°rÓ:ô VØW[ó]€Cà
€{�g˜c‘l×&Ñ& yÓ1°UÒ:ØÜñ -Ø! #¨¡' (Ð+ô-ô -àØ�3‰<€DØ�‘ˆØ!%€DÔ	ÑØ&*€EÔ#Ør8   c                 óÂ   — t        | dd«      }t        | dd«      |k(  }|r| j                  dk7  rt        | dd«      nd}|| _        t        |t        «      r|S t	        «       S )z‘One-shot ``_flushed_db_message_ids`` seed (same session, after a non-empty flush); the scan translates
    it to markers and the flush clears it.r9   NÚ_flushed_db_message_session_idr   Ú_flushed_db_message_ids)rU   Ú_last_flushed_db_idxr„   r2   Úset)rV   Úcurrent_session_idÚsame_sessionÚseed_idss       r0   Ú_db_flush_seed_idsr‹   ª   si   € ô ! ¨°dÓ;ÐÜ˜5Ð"BÀDÓIÐM_Ñ_€LÙBNÐSX×SmÑSmÐqrÒSrŒw�uÐ7¸Ô>Ðx|€HØ+=€EÔ(Ü! (¬CÔ0ˆ8Ð;´c³eÐ;r8   Úmessagesc                 ó’   — d}t        t        | dd«      xs d|«      D ]&  \  }}||us|j                  t        «      s |S |dz  }Œ( |S )zPSkip the identity-matched, still-marked prefix of the previous flush's snapshot.r   Ú_db_flush_scan_prefixN© rt   )ÚziprU   r-   r
   )rV   rŒ   Ú
scan_startÚprevÚcurs        r0   Ú_db_flush_scan_startr”   ´   s_   € à€JÜœ Ð(?ÀÓFÒLÈ"ÈhÓWò ‰	ˆˆcØ�d‰? #§'¡'Ô*>Ô"?ØàÐð 	�a‰‰
ðð Ðr8   Úis_current_turn_userc           	      ó¼  — |j                  dd«      }|j                  d«      }t        |j                  d«      t        «      r|j                  d«      nd}|j                  d«      }|r)|dk(  r$t        | |||«      \  }}t	        | dd«      }|€|n|}||k(  rd}|€B|d	v r>t        |t        «      r.|r,t        |«      j                  «       |j                  «       k7  r|}|t        |«      |j                  d
«      t        |j                  d«      t        «      r|d   nd|j                  d«      |j                  d«      dœt        D �ci c]  }||j                  |«      “Œ c}¥t        |j                  t        «      «      ||t        |«      |j                  d«      |j                  d«      dœ¥}	t        |j                  d«      t        «      r|d   |	d<   |	S c c}w )zUBuild the session-db row for ``msg``, applying the persist override to THIS row only.rp   ÚunknownrM   rR   NÚ	timestamprq   Ú_persist_user_message_timestamp)rq   rx   Ú	tool_namery   Útool_call_idÚfinish_reason)rp   rM   rš   ry   r›   rœ   Údisplay_metadataÚplatform_message_id)Ú_compressed_summaryr˜   rR   r\   r�   rž   Ú_row_id)r-   r2   rB   rW   rU   r   rC   rg   rP   Ú_ROW_REASONING_KEYSÚboolr	   r^   Úint)
rV   r'   r•   rp   rM   rR   r˜   Úov_timestampÚkrz   s
             r0   Ú_db_flush_rowr¦   ¾   sµ  € à�7‰7�6˜9Ó%€DØ�g‰g�iÓ €Gä,6°s·w±w¸}Ó7MÌsÔ,S�#—'‘'˜-Ô(ÐY]€KØ—‘˜Ó$€IÙ ¨¢Ü7¸¸sÀGÈ[ÓYÑˆ�Ü˜uÐ&GÈÓNˆØ!-Ð!5‘I¸<ˆ	Ø�gÒØˆð 	Ð Ð(=Ñ =Ä*ÈWÔVYÔBZÑ_fÜ˜WÓ%×+Ñ+Ó-°·±³Ò@àˆð Ô!1°'Ó!:ÈÏÉÐQ\ÓI]Ü+5°c·g±g¸lÓ6KÌTÔ+R�c˜,Ò'ÐX\ØŸ™ Ó/À#Ç'Á'È/ÓBZñ	ô #6Ö
6˜Qˆ1ˆc�g‰g�a‹j‰=Ò
6ð		ô
  $ C§G¡GÔ,KÓ$LÓMØ¨{Ü-¨cÓ2ÈÏÉÐPbÓHcØ"Ÿw™wÐ'<Ó=ò	€Cô �#—'‘'˜)Ó$¤cÔ*Ø˜Y™ˆˆI‰Ø€Jùò 7s   ÅGÚconversation_historyc           	      ó²  — t        | «      }|xs g D �ch c]  }t        |t        «      sŒt        |«      ’Œ  }}t	        | dd«      }t	        | dd«      }g }g }	t        t        | |«      t        |«      «      D ]×  }
||
   }t        |t        «      r t        |«      s|j                  t        «      rŒ9t        |«      |v st        |«      |v r|j                  t        «      s
d|t        <   Œrt	        | dd«      r#d|d<   i |j                  d	«      xs i ¥d
di¥|d	<   |j                  t        | |||
k(  xs ||u «      «       |	j                  |«       ŒÙ ||	fS c c}w )zSScan for un-flushed messages; returns ``(rows, msgs)`` to write in one transaction.Ú_persist_user_message_idxNr   TÚ_mute_notification_replyFr[   r\   r�   Únotification_categoryÚ
diagnostic)r‹   r2   r3   ÚidrU   r|   r”   r}   r7   r-   r
   Ú"_PERSIST_AFTER_ADMISSION_INTERRUPTÚappendr¦   )rV   rŒ   r§   rŠ   ÚitemÚhistory_idsÚov_idxÚpending_cli_messageÚ
batch_rowsÚ
batch_msgsÚmsg_idxr'   s               r0   Ú_db_flush_collectr·   â   sj  € ä! %Ó(€HØ)=Ò)CÀÖ_ Ì
ÐSWÔY]ÕH^”2�d•8Ð_€KÐ_Ü�UÐ7¸Ó>€Fô " %Ð)DÀdÓKÐØ')€JØ€JÜÔ-¨e°XÓ>ÄÀHÃÓNò ˆØ�wÑˆô ˜#œtÔ$Ô(AÀ#Ô(FÈ#Ï'É'ÔRfÔJgØô ˆs‹G�{Ñ"¤b¨£g°Ñ&9Ø—'‘'Ô<Ô=Ø(,ˆCÔ$Ñ%ØÜ�5Ð4°eÔ<ð #+ˆC�ÑØ&t¨#¯'©'Ð2DÓ*EÒ*KÈÐ&tÐNeÐgsÑ&tˆCÐ"Ñ#Ø×Ñœ-¨¨s°F¸gÑ4EÒ4cÈÐPcÐIcÓdÔeØ×Ñ˜#Õð%ð& �zÐ!Ð!ùò5 `s
   ”EªEr´   rµ   c                 óö   — |sy| j                   j                  | j                  |t        | dd«      t        | dd«      t        | dd«      xs d¬«       t	        ||«       t        |d«      dk\  rt        |«       yy)	z]One transaction for the turn's new rows: on failure nothing lands and no markers are stamped.NÚ_active_compression_lock_holderÚ!_active_session_turn_lease_holderÚ&_active_session_turn_lease_ttl_secondsg     Àr@)r9   rŒ   Úcompression_lock_holderÚturn_lease_holderÚturn_lease_ttl_secondsr$   r   )Ú_session_dbÚappend_messages_batchr9   rU   r   r   r   )rV   r´   rµ   rŒ   s       r0   Ú_db_flush_writerÁ     s‡   € áØØ	×Ñ×+Ñ+Ø×#Ñ#¨jÜ '¨Ð/PÐRVÓ WÜ! %Ð)LÈdÓSÜ& uÐ.VÐX]Ó^ÒgÐbgð	 ,ô ô ! ¨ZÔ8Ü! *Ð.EÓFÈ!ÒKô 	" (Õ+ð	 Lr8   c                 ó¸  — | j                   }	 | j                  j                  |«      }|r||k(  ry	 | j                  j                  |«      }|�|j                  d«      �yt        j                  d||«       |t        «       dc| _         | _	        | _
        d| _        y# t        $ r!}t        j                  d||«       Y d}~yd}~ww xY w# t        $ r d}Y Œ‹w xY w)z¢Adopt the live continuation of a compression-closed session. Same-id tip = no continuation; a tip
    whose row is missing or already ended is not adopted either.z(compression tip lookup failed for %s: %sNFÚended_atzJAdopted live compression tip %s for closed session %s; retrying flush oncer   T)r9   r¿   Úget_compression_tipÚ	ExceptionÚloggerÚwarningÚget_sessionr-   r‡   r…   r†   Ú_compression_adoption_failed)rV   Úold_idÚtipÚtip_excÚtip_rows        r0   Ú_db_flush_adopt_compression_tiprÎ     sâ   € ð ×Ñ€FðØ×Ñ×3Ñ3°FÓ;ˆñ �#˜’-ØðØ×#Ñ#×/Ñ/°Ó4ˆð €˜'Ÿ+™+ jÓ1Ð=ØÜ
‡N�NÐ_ÐadÐflÔmØRUÔWZÓW\Ð^_ÐO€EÔ�eÔ3°UÔ5OØ).€EÔ&Øøô ò Ü�‰ÐAÀ6È7ÔSÜûðûô ò ØŠðús(   ŽB ²C Â	CÂ'CÃCÃCÃCÚeÚadoption_budgetc           
      óŒ  — d| _         ddlm}m}m}m} ddlm}  ||«      | _        t        |||f«      r	  |t        | dd«      xs d|«       t        ||«      r|dkD  rt        | «      ryd| _        t        j                  d
|«       y# t        $ r1 t        j                  d| j                  t        | dd«      d¬	«       Y Œtw xY w)z^Classify a failed flush; True when the caller should retry once on an adopted compression tip.Nr   )ÚStateDbCorruptErrorÚStateDbReplacedErrorÚclassify_persistence_errorÚdivert_session_transcript_jsonl)ÚCompressionSessionClosedErrorr9   r;   z,JSONL divert failed after state.db %s for %sT)Úexc_infoz$Session DB append_message failed: %sF)rŽ   Úhermes_staterÒ   rÓ   rÔ   rÕ   Úhermes_state_errorsrÖ   Ú_last_persistence_error_causer2   rU   rÅ   rÆ   rÇ   rÎ   rÉ   )	rV   rÏ   r´   rÐ   rÒ   rÓ   rÔ   rÕ   rÖ   s	            r0   Ú_db_flush_failedrÛ   +  sÓ   € à"&€EÔ÷ Dó  DÝAÙ*DÀQÓ*G€EÔ'Ü�!Ð*Ð,?Ð@ÔAð	sÙ+¬G°E¸<ÈÓ,LÒ,RÐPRÐT^Ô_ô �!Ð2Ô3ð ˜QÒÔ#BÀ5Ô#IØØ-1ˆÔ*Ü
‡N�NÐ9¸1Ô=Øøô ò 	sÜ�N‰NÐIØ ×>Ñ>ÄÈÈ|Ð]aÓ@bÐmqð ö sð	sús   ¶B	 Â	7CÃCc                   óB  — e Zd ZdZdee   ddfd„Zddee   dee   fd„Zdee   ddfd„Z e	d	d
«      Z
ddee   deee      fd„Z	 ddee   deee      defd„Zdee   dee   fd„Z e	dd«      Z e	d	d«      Zdeeeef      dedefd„Z ed	d«      Z e	d	d«      Zy)ÚSessionPersistenceMixinzCSession DB flush and trajectory persistence (see module docstring).rŒ   r(   Nc                 ód  — t        | dd«      }t        | dd«      }t        | dd«      }t        | dd«      }|�|€|€|€yd|cxk  rt        |«      k  rn n||   nd}t        |t        «      r|j	                  d«      dk(  syt        ||j	                  d	«      |«      r||d	<   |�||d
<   |�||d<   yy)z“Rewrite the current-turn user message in place: some paths send an API-only variant that must not
        leak into transcripts or resumed history.r©   NrT   r™   Ú!_persist_user_message_platform_idr   rp   rq   rM   r˜   rž   )rU   r}   r2   r3   r-   rQ   )ÚselfrŒ   r€   rN   r˜   Úplatform_idr'   s          r0   Ú$_apply_persist_user_message_overridez<SessionPersistenceMixin._apply_persist_user_message_overrideG  sÒ   € ô �dÐ7¸Ó>ˆÜ˜4Ð!AÀ4ÓHˆÜ˜DÐ"CÀTÓJˆ	Ü˜dÐ$GÈÓNˆØˆ;˜8Ð+°	Ð0AÀkÐFYØØ  CÔ7¬#¨h«-Õ7ˆh�sŠm¸TˆÜ˜3¤Ô%¨#¯'©'°&«/¸VÒ*CØÜ% c¨3¯7©7°9Ó+=¸xÔHØ%ˆC�	‰NØÐ Ø(ˆC�ÑØÐ"Ø)4ˆCÐ%Ò&ð #r8   r§   c                 ó   — ddl m} t        | «      5  | j                  |«       || _        | j                  ||«       | j                  �| j                  j                  «         || «       ddd«       y# 1 sw Y   yxY w)uÔ  Save to SQLite on any exit path. Trailing empty-response scaffolding is dropped from
        the live list; the persist override is applied to the DB row only.

        The persist user-message *override* is NOT applied here â€” it is resolved inside
        ``_flush_messages_to_session_db`` and written only to the DB row, never mutating the live message
        list used by the API call (#48677 is thus closed for every persist caller, not just this one).
        r   )Únote_turn_persistedN)Úagent.agent_runtime_helpersrä   rk   Ú)_drop_trailing_empty_response_scaffoldingÚ_session_messagesÚ_flush_messages_to_session_dbr¿   Úflush_token_counts)rà   rŒ   r§   rä   s       r0   Ú_persist_sessionz(SessionPersistenceMixin._persist_sessionZ  sr   € õ 	DÜ˜4Ó ñ 	&Ø×:Ñ:¸8ÔDØ%-ˆDÔ"Ø×.Ñ.¨xÐ9MÔNà×ÑÐ+Ø× Ñ ×3Ñ3Ô5Ù Ô%÷	&÷ 	&ñ 	&ús   ’AA4Á4A=c                 ó(  ‡— dt         dt        fˆfd„}dt         dt        fˆfd„}d} |dd«      r‰j                  «        d	} |dd«      rŒ|sy
 |d«      r‰j                  «         |d«      rŒ |d«      r |d«      r‰j                  «        y
y
y
)u   Pop empty-response retry scaffolding from the tail, then (only if any was present) rewind the
        tool-result / assistant(tool_calls) pair the failed iteration left hanging â€” otherwise the next user
        turn lands as ``...tool, user`` and providers return empty content forever.Úkeysr(   c                  óp   •— t        ‰«      xr) t        ‰d   t        «      xr t        ˆfd„| D «       «      S )Nru   c              3   óF   •K  — | ]  }‰d    j                  |«      –— Œ y­w)ru   Nr,   )r.   r¥   rŒ   s     €r0   r1   zbSessionPersistenceMixin._drop_trailing_empty_response_scaffolding.<locals>.tail.<locals>.<genexpr>q  s#   øè ø€ ÒLoÐefÈXÐVXÉ\×M]ÑM]Ð^_×M`ÑLoùs   ƒ!)r¢   r2   r3   r4   )rì   rŒ   s    €r0   r�   zOSessionPersistenceMixin._drop_trailing_empty_response_scaffolding.<locals>.tailp  s.   ø€ Ü˜“>Òo¤j°¸"±¼tÓ&DÒoÌÓLoÐjnÔLoÓIoÐor8   rp   c                 óv   •— t        ‰«      xr, t        ‰d   t        «      xr ‰d   j                  d«      | k(  S )Nru   rp   )r¢   r2   r3   r-   )rp   rŒ   s    €r0   Ú	tail_rolezTSessionPersistenceMixin._drop_trailing_empty_response_scaffolding.<locals>.tail_roles  s9   ø€ Ü˜“>Òi¤j°¸"±¼tÓ&DÒiÈÐRTÉ×IYÑIYÐZ`ÓIaÐeiÑIiÐir8   Fr   r   TNrw   rx   ry   )rB   r¢   Úpop)rà   rŒ   r�   rð   Údropped_scaffoldings    `   r0   ræ   zASessionPersistenceMixin._drop_trailing_empty_response_scaffoldingl  s¥   ø€ ð	pœð 	p¤õ 	pð	jœCð 	j¤Dõ 	jð $ÐÙÐ.Ð0JÔKØ�L‰LŒNØ"&Ðñ Ð.Ð0JÕKñ #ØÙ˜ÔØ�L‰LŒNñ ˜Õñ �[Ô!¡d¨<Ô&8Ø�L‰L�Nð '9Ð!r8   zagent.agent_runtime_helpersÚrepair_message_sequencec                 óh   — t        | «      5  | j                  ||«      cddd«       S # 1 sw Y   yxY w)z=Serialize direct and turn-boundary session flushes per agent.N)rk   Ú&_flush_messages_to_session_db_unlocked)rà   rŒ   r§   s      r0   rè   z5SessionPersistenceMixin._flush_messages_to_session_db„  s4   € ä˜4Ó ñ 	_Ø×>Ñ>¸xÐI]Ó^÷	_÷ 	_ò 	_ús   Œ(¨1Ú_adoption_budgetc                 ó~  — t        | dd«      s| j                  syg }	 | j                  s| j                  «        t	        | ||«      \  }}t        | |||«       t        «       | _        t        |«      | _	        |dd | _
        y# t        $ r1}t        | |||«      r| j                  ||d¬«      cY d}~S Y d}~yd}~ww xY w)u¼  Persist un-flushed messages to SQLite. Dedup is the intrinsic ``_DB_PERSISTED_MARKER`` on each written
        dict â€” not positional slices (drift after sequence repair) nor an ``id(msg)`` set (address reuse). The
        persist override touches the written row only. A compression-closed session adopts its live tip and
        retries exactly once.

        Deduplicates via an intrinsic ``_DB_PERSISTED_MARKER`` stamped on each written message dict, so
        repeated calls (from multiple exit paths) only write truly new messages â€” preventing the
        duplicate-write bug (#860) without relying on positional slices that can drift after
        message-sequence repair, and without a retained ``id(msg)`` set that CPython could alias onto a
        freed-then-reused address (#50372). The ``_flushed_db_message_ids`` attribute is now only a one-shot
        seed (translated to markers, then cleared each flush), not a persisted set.
        Ú_persist_disabledFNTr   )rö   )rU   r¿   Ú_session_db_createdÚ_ensure_db_sessionr·   rÁ   r‡   r…   r}   r†   rŽ   rÅ   rÛ   rõ   )rà   rŒ   r§   rö   r´   rµ   rÏ   s          r0   rõ   z>SessionPersistenceMixin._flush_messages_to_session_db_unlocked‰  s¾   € ô" �4Ð,¨eÔ4¸D×<LÒ<LØØ+-ˆ
ð	Ø×+Ò+Ø×'Ñ'Ô)Ü%6°t¸XÐG[Ó%\Ñ"ˆJ˜
Ü˜D *¨j¸(ÔCä+.«5ˆDÔ(Ü(+¨H«ˆDÔ%à)1±!¨ˆDÔ&ØøÜò 	Ü  a¨Ð5EÔFØ×BÑBÀ8ÐMaÐtuÐBÓvÕvÜûð	ús   žA#B Â	B<Â!B7Â,B<Â7B<c                 óš   — t        t        |«      dz
  dd«      D ]   }||   j                  d«      dk(  sŒ|d| c S  |j                  «       S )zcMessages before the last assistant turn (rollback point for a malformed final answer); all if none.rt   ru   rp   rx   N)r|   r}   r-   Úcopy)rà   rŒ   rs   s      r0   Ú"_get_messages_up_to_last_assistantz:SessionPersistenceMixin._get_messages_up_to_last_assistant­  sR   € ä”s˜8“} qÑ(¨"¨bÓ1ò 	$ˆAØ˜‰{�‰˜vÓ&¨+Ó5Ø  �|Ò#ð	$ð �}‰}‹Ðr8   zagent.system_promptÚformat_tools_for_system_messageÚconvert_to_trajectory_formatÚ
user_queryÚ	completedc                 ór   — | j                   sy| j                  |||«      }t        || j                  |«       y)z+Save conversation trajectory to JSONL file.N)Úsave_trajectoriesÚ_convert_to_trajectory_formatÚ_save_trajectory_to_fileÚmodel)rà   rŒ   r   r  Ú
trajectorys        r0   Ú_save_trajectoryz(SessionPersistenceMixin._save_trajectory·  s2   € à×%Ò%ØØ×7Ñ7¸À*ÈiÓXˆ
Ü  ¨T¯Z©Z¸ÕCr8   Úextract_api_error_contextÚdump_api_request_debugr+   )Nrt   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   r   râ   rê   ræ   Ú_forwardÚ_repair_message_sequencer   rè   r£   rõ   rý   Ú _format_tools_for_system_messager  rB   r   r¢   r  Ú_forward_staticÚ_extract_api_error_contextÚ_dump_api_request_debugr�   r8   r0   rÝ   rÝ   D  sA  „ ÙMð5¸TÀ$¹Zð 5ÈDó 5ñ&&¨¨d©ð &È4ÐPTÉ:ó &ð$À$ÀtÁ*ð ÐQUó ñ,  (Ð(EÐG`ÓaÐñ_°d¸4±jð _ÐX`ÐaeÐfjÑakÑXló _ð pqñ"Ø˜T™
ð"Ø:BÀ4ÈÁ:Ñ:Nð"Øiló"ðH¸4À¹:ð È$ÈtÉ*ó ñ (0Ð0EÐGhÓ'iÐ$Ù$,Ð-JÐLjÓ$kÐ!ðD¨¨d°3¸°8©nÑ)=ð DÈ3ð DÐ[_ó Dñ "1Ð1NÐPkÓ!lÐÙ&Ð'DÐF^Ó_Ñr8   rÝ   )=r  rF   ÚloggingrD   Ú
contextlibr   Útypingr   r   r   r   r   Úagent.context_compressorr	   r
   r   r   r   r   Úagent.lazy_forwardr   r  r   r  Úagent.memory_managerr   Úagent.tool_dispatch_helpersr   r   Úagent.trajectoryr   r  Úagent.transcript_repairr   Ú	getLoggerrÆ   r5   rc   r¡   r®   r¢   r7   rB   rL   rQ   rW   r^   rg   rk   r‚   r‡   r‹   r£   r”   r¦   r·   rÁ   rÎ   rÅ   rÛ   rÝ   r�   r8   r0   ú<module>r     s.  ðñó ã Û 	Ý "ç 3Õ 3÷÷ ÷ VÝ 1ç \Ý HÝ @ð 
ˆ×	Ñ	˜;Ó	'€ð Ð ò :Ð à}Ð Ø%IÐ "ða 3ð a¨4ó aði°ð i¸ó ið Dð °3ð À#ð È$ó ð
 ¨ð 
 ¸ð 
 È#ð 
 ÐRWÐX[Ð]`ÐX`ÑRaó 
 ð#˜tð #¨ó #ð+˜cð + có +ò3ð! 4¨¨S°#¨X©Ñ#7ð !Àð !ÈCð !ÐTXó !ðL< ó <ð¨$¨t©*ð ¸ó ð!˜dð !¸$ð !À4ÈÈSÈÁ>ó !ðH" t¨D¡zð "ÈÐRVÐW[ÑR\ÑI]ó "ð@, t¨D°°c°©NÑ';ð ,ÈÈdÉð ,Ð_cÐdhÑ_ið ,Ðnró ,ð$¨dó ð.˜yð °d¸4ÀÀSÀ¹>Ñ6Jð Ð]`ð Ðeió ÷2{`ò {`r8   