+
    jHàjú ã            	      ó  € 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HtHt ^ RIHtHtHtHtHt ^ RIHt ^ RIHtHtHtHtHtHtHtH t H!t!H"t"H#t#H$t$ ^ RI%H&t&H't' ^ RI(H)t) ^ R	I*H+t+H,t,H-t-H.t.H/t/H0t0H1t1H2t2H3t3 ^ R
I4H5t5 ^ RI6H7t7 ^ RI8H9t9H:t:H;t;H<t<H=t=H>t>H?t? ]P€                  ! R4      tARtB]C! ]Pˆ                  ! R]BP‹                  R^4      ^,          P‹                  R^4      ^ ,          4      4      tF]G! ]F4      0 R3m,          tHRtIRtJRtKRtLR]3! RR],           4       R2tMR]L,           tNR]L,           R,           tORtPRtQRtRR tSR!tTR"tUR#tVR$tW]X! 4       tYR% R& ltZR' R( lt[R) R* lt\R4R+ R, llt]R- R. lt^R/ R0 lt_ ! R1 R2]5]74      t`R# )5zÑTranscript persistence for SessionDB: message append/replace/rewind, reactions, resume assembly,
replayed-user dedupe. Mixin bound via the MRO, built on SessionDB's _read_ctx/_execute_write/_read_* primitives.)ÚannotationsN)ÚAnyÚDictÚListÚOptionalÚSetÚTuple)Ú_DB_PERSISTED_MARKERÚMODEL_ONLY_DISPLAY_METADATA_KEYÚ_is_checkpoint_itemÚ_newest_checkpoint_carrierÚsplit_user_originated_turn)Úsanitize_context)ÚCANONICAL_ROWÚDATA_USEÚDB_ROW_SNAPSHOTÚlift_data_useÚMESSAGE_UIDÚTOOL_CALL_UIDÚTOOL_CALL_UIDSÚindex_tool_call_uidsÚmessage_uid_or_noneÚresolve_tool_call_uidÚmint_uidÚstamp_message_uid)Ú_sanitize_surrogatesÚcoalesce_tool_call_id©Úcoerce_epoch)	Ú_COMPRESSION_LOCK_ROW_SQLÚ_ENDED_ROW_SQLÚ_RESET_END_REASONSÚ_RESET_END_REASONS_SQLÚ_ended_by_compressionÚ_json_orÚ_legacy_reset_child_sqlÚ_placeholdersÚ_sql_json_extract)ÚSessionDetachedResultsMixin)ÚSessionTodoResultMixin)Ú_absorbed_uids_jsonÚ_restore_identity_columnsÚ_stable_tool_keyÚ_tool_call_uid_mapÚ_tool_call_uid_or_noneÚ_tool_call_uids_jsonÚ	_uid_listÚhermes_stateav  INSERT INTO messages (session_id, role, content, tool_call_id,
                   tool_calls, tool_name, effect_disposition, timestamp, token_count, finish_reason,
                   reasoning, reasoning_content, reasoning_details, codex_reasoning_items,
                   codex_message_items, platform_message_id, observed, _compressed_summary, active, api_content, display_kind,
                   display_metadata, display_identity, message_uid, absorbed_message_uids, tool_call_uids,
                   tool_call_uid)
                   VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)z\w+Ú(Ú)zý
            INSERT INTO conversation_generations (source, session_key, generation)
            VALUES (?, ?, 1)
            ON CONFLICT(source, session_key) DO UPDATE
                SET generation = conversation_generations.generation + 1
            zLSELECT holder, expires_at FROM session_turn_leases WHERE conversation_id = ?zADELETE FROM compression_locks WHERE session_id = ? AND holder = ?z" AND (active = 1 OR compacted = 1)z AND COALESCE(Údisplay_metadataú$.z, 0) = 0zLSELECT display_metadata FROM messages WHERE id = ? AND session_id IN ({ids})z+SELECT 1 FROM messages WHERE session_id = ?z@ AND (display_order IS NULL OR display_identity IS NULL) LIMIT 1zGSELECT id FROM messages WHERE session_id = ? AND active = 1 ORDER BY idz…SELECT id, role, content, tool_call_id, tool_calls, message_uid FROM messages WHERE session_id = ? AND active = 1 ORDER BY id LIMIT ?z:UPDATE sessions SET message_count = ?, tool_call_count = ?zGUPDATE sessions SET message_count = 0, tool_call_count = 0 WHERE id = ?z5UPDATE messages SET display_metadata = ? WHERE id = ?zQUPDATE messages SET active = 0, compacted = 1 WHERE session_id = ? AND active = 1z¤SELECT id, codex_reasoning_items FROM messages WHERE session_id = ? AND active = 1 AND role = 'assistant' AND id < ? AND codex_reasoning_items LIKE '%"compaction"%'z:UPDATE messages SET codex_reasoning_items = ? WHERE id = ?c               ó$   € V ^8„  d   QhRRRRRR/# )é   Úvaluer   ÚdefaultÚfloatÚreturn© )Úformats   "Ú:/home/ubuntu/.hermes/hermes-agent/hermes_state_messages.pyÚ__annotate__r?   O   s!   € ÷ 	,ñ 	,˜Sð 	,¨5ð 	,°Uñ 	,ó    c                ó>   € \        V RR7      pVf   V# VR8X  d   R# T# )uÂ   Explicit message timestamp (datetime or number) or *default* when invalid or outside the sane
epoch window â€” the write-side twin of the readers' ``coerce_epoch``: a bad row is never persisted.úmessage timestamp©Úfieldg        r   )r8   r9   Úresults   && r>   Ú_coerce_timestamprF   O   s.   € ô ˜%Ð':Ô;€FØ‚~Øˆð ˜C”-ˆ3Ð+ VÐ+r@   c               ó    € V ^8„  d   QhRRRR/# )r7   Ú
tool_callsr   r;   r<   )r=   s   "r>   r?   r?   [   s   € ÷ ñ  #ð ¨#ñ r@   c                ó¦   € \        V \        4      '       g   V #  \        P                  ! V 4      #   \        P                  \
        3 d    . u # i ; i)zktool_calls is a list (live agent) or JSON string (import/export); parse so json.dumps never double-encodes.)Ú
isinstanceÚstrÚjsonÚloadsÚJSONDecodeErrorÚ	TypeError©rH   s   &r>   Ú_parse_tool_callsrQ   [   sH   € ä�j¤#×&Ò&ØÐðÜ�zŠz˜*Ó%Ð%øÜ× Ñ ¤)Ð,ô ØŠ	ðús   š0 °AÁAc               ó    € V ^8„  d   QhRRRR/# )r7   rH   r   r;   Úintr<   )r=   s   "r>   r?   r?   e   s   € ÷ añ a #ð a¨#ñ ar@   c                óT   € V f   ^ # \        V \        4      '       d   \        V 4      # ^# ©N)rJ   ÚlistÚlenrP   s   &r>   Ú_tool_calls_countrX   e   s)   € ØÒ"ˆ1Ð`¼JÀzÔSW×<XÒ<X¬¨Z«Ð`Ð^_Ð`r@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   Úrawr   ÚscalarrS   r;   r<   )r=   s   "r>   r?   r?   i   s&   € ÷ Rñ R˜ð R cð R°#ñ Rr@   c                ór   € \        V 4      p\        V\        4      '       d   \        V4      # V'       d   V# ^ # )z]Count of a stored ``tool_calls`` column: list length, *scalar* for a truthy non-list, else 0.)rQ   rJ   rV   rW   )rZ   r[   Úparseds   && r>   Ú_tool_calls_lenr^   i   s.   € ä˜sÓ#€FÜ$ V¬T×2Ò2Œ3ˆv‹;ÐQÇ6¸ÐQÈqÐQr@   c               ó    € V ^8„  d   QhRRRR/# )r7   r8   r   r;   r<   )r=   s   "r>   r?   r?   o   s   € ÷ Lñ L˜Sð L Sñ Lr@   c                óH   € \        V \        4      '       d   \        V 4      # T # )zPLone surrogates make sqlite3 raise UnicodeEncodeError and abort the whole write.)rJ   rK   r   ©r8   s   &r>   Ú_scrub_surrogatesrb   o   s   € ä*4°U¼C×*@Ò*@Ô Ó&ÐKÀeÐKr@   c               ó    € V ^8„  d   QhRRRR/# )r7   Únowr:   r;   Úboolr<   )r=   s   "r>   r?   r?   t   s   € ÷ fñ f˜Eð f dñ fr@   c                óf   € ^ RI Hp \        V R,          4      V8*  ;'       g    V! V R,          4      # )zJA lock/lease row whose holder is expired or a provably dead local process.)Ú(_compression_lock_holder_process_is_deadÚ
expires_atÚholder)r1   rg   r:   )Úrowrd   rg   s   && r>   Ú_stale_holderrk   t   s/   € åEÜ��\Õ"Ó# sÑ*×eÐeÑ.VÐWZÐ[cÕWdÓ.eÐer@   c                  ó  € ] tR t^ztRtR R lt]R R l4       t]R R l4       t]	R R	 l4       t
]	R
 R l4       t]	R R l4       tRáR R lltR R lt]	R R l4       tR R ltR R lt]	R R l4       tRâR R lltRãR R  lltR!RR"RR#R/R$ ltR%]R&3tR' R( ltR)R/R* R+ lltR, R- ltR.R//R0 R1 lltR2 R3 ltR4 R5 ltR.R//R6 R7 lltR8R/R9^ R:R;/R< R= lltR> R? lt R@ RA lt!RB RC lt"RDR;/RE RF llt#RG RH lt$RI RJ lt%RäRK RL llt&RMRRNRRORRPR/RQ RR llt']RS RT l4       t(RU RV lt)]	RW RX l4       t*RY RZ lt+R[ R\ lt,R] R^ lt-R_R/R` Ra llt.Rb Rc lt/RåRd Re llt0RfRRgRRhRRi^ RjRRkRRlRRmR/Rn Ro llt1Rp Rq lt2Rr Rs lt3Rt Ru lt4Rv Rw lt5Rx Ry lt6Rz R{ lt7R| R} lt8]	R~ R l4       t9]	R€ R� l4       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R9^ RŽR/R� R� ll4       t@R‘ R’ ltAR“ R” ltB]	R• R– l4       tCRæR— R˜ lltDR™ Rš ltERçR› Rœ lltFR� Rž ltGRŸR/R  R¡ lltHR¢ R£ ltIRèR¤ R¥ lltJRéR¦ R§ lltKR¨ R© ltLRªRR«R/R¬ R­ lltMR® R¯ ltNR° R± ltOR² R³ ltPR´R/Rµ R¶ lltQRêR´R/R· R¸ llltRR¹ Rº ltSR» R¼ ltT]	R½ R¾ l4       tU]	R¿ RÀ l4       tV]	RÁ RÂ l4       tWRÃ RÄ ltX]	RÅ RÆ l4       tYRÇ RÈ ltZRÉRRPRRÊR/RË RÌ llt[RêRÍ RÎ llt\RÏ RÐ lt]RÑ RÒ lt^RÓR/RÔ RÕ llt_RÖ R× lt`RØ RÙ ltaRÚ RÛ ltbRÜRRÝR;/RÞ Rß lltcRàtdR# )ëÚSessionMessagesMixinzkMessage append/replace/rewind, reactions, resume conversations, replay dedupe (detached results: the base).c               ó$   € V ^8„  d   QhRRRRRR/# )r7   Ú
session_idrK   Ú
end_reasonr;   ÚNoner<   )r=   s   "r>   r?   Ú!SessionMessagesMixin.__annotate__}   s'   € ÷ Fñ F¸cð FÈsð FÐW[ñ Fr@   c                óâ   a€ V\         9  d   R# VP                  RV34      P                  4       oSf   R# V3R lR 4       w  rEV'       d$   V'       d   VP                  \        WE34       R# R# R# )a  Advance the peer's conversation generation past a boundary, in the txn that writes it. Only
``_RESET_END_REASONS`` count (compression continues one conversation). Never derived from session
rows (deletes/prunes could re-emit a retired affinity identity); it only ever increments.Nz5SELECT source, session_key FROM sessions WHERE id = ?c              3  óx   <"  € T F/  p\        SV,          ;'       g    R 4      P                  4       x € K1  	  R# 5i)Ú N)rK   Ústrip)Ú.0Úkrj   s   & €r>   Ú	<genexpr>ÚESessionMessagesMixin._bump_conversation_generation.<locals>.<genexpr>†   s.   øé € Ð\ÑB[¸Qœs 3 q¥6§< <¨RÓ0×6Ñ6×8Ð8ÓB[ùs   ƒ:�:)ÚsourceÚsession_key)r!   ÚexecuteÚfetchoneÚ_BUMP_GENERATION_SQL)ÚselfÚconnro   rp   r{   r|   rj   s   &&&&  @r>   Ú_bump_conversation_generationÚ2SessionMessagesMixin._bump_conversation_generation}   sc   ø€ ð Ô/Ô/ÙØ�l‰lÐRÐU_ÐTaÓb×kÑkÓmˆØŠ;ÙÜ\ÑB[Ó\Ñˆß—kØ�L‰LÔ-°Ð/DÖEñ "‰6r@   c               ó    € V ^8„  d   QhRRRR/# ©r7   Úcontentr   r;   r<   )r=   s   "r>   r?   rr   ‹   s   € ÷ 6ñ 6 cð 6¨cñ 6r@   c                ó€  € \        V\        4      '       d0   \        V4      pVP                  V P                  4      '       g   V# M'Ve"   \        V\
        \        \        34      '       d   V#  V P                  \        P                  ! V4      ,           #   \        \        3 d    \        \        T4      4      u # i ; i)uk  Serialize list/dict content (multimodal parts) as a sentinel-prefixed JSON string (sqlite3 binds
only scalars). Lone UTF-16 surrogates (unsanitized web-scraped tool results) are scrubbed here: left
raw, sqlite3 raises UnicodeEncodeError and the session silently stops persisting. Pairs with
:meth:`_decode_content`.

A literal string carrying the reserved ``\x00json:`` prefix is indistinguishable from
stored structured content, so it is JSON-string-wrapped (decoded back verbatim on read)
instead of written raw â€” a crafted import can't smuggle the marker in and have the read
path decode attacker-chosen structure.)rJ   rK   r   Ú
startswithÚ_CONTENT_JSON_PREFIXÚbytesrS   r:   rL   ÚdumpsrO   Ú
ValueError©Úclsr†   s   &&r>   Ú_encode_contentÚ$SessionMessagesMixin._encode_contentŠ   sž   € ô �gœs×#Ò#Ü*¨7Ó3ˆGØ×%Ñ% c×&>Ñ&>×?Ò?Ø�ð @ð Š_¤
¨7´U¼CÄÐ4G× HÒ HØˆNð	6Ø×+Ñ+¬d¯jªj¸Ó.AÕAÐAøÜœ:Ð&ô 	6Ü'¬¨G«Ó5Ò5ð	6ús   Á.&B Â%B=Â<B=c               ó    € V ^8„  d   QhRRRR/# r…   r<   )r=   s   "r>   r?   rr   ¢   s   € ÷ ñ  cð ¨cñ r@   c                óº   € \        V\        4      '       dE   VP                  V P                  4      '       d$   \	        V\        V P                  4      R VR4      # V# )z¯Reverse :meth:`_encode_content`; returns scalars unchanged. Fail closed: a reserved-prefix
string that isn't valid JSON (a legacy/malformed marker row) returns the raw string.NzCFailed to decode JSON-encoded message content; returning raw string)rJ   rK   rˆ   r‰   r$   rW   r�   s   &&r>   Ú_decode_contentÚ$SessionMessagesMixin._decode_content¡   sU   € ô �gœs×#Ò#¨×(:Ñ(:¸3×;SÑ;S×(TÒ(TÜ˜G¤C¨×(@Ñ(@Ó$AÐ$BÐCÀWØUóWð Wàˆr@   c               ó    € V ^8„  d   QhRRRR/# )r7   r4   r   r;   úOptional[str]r<   )r=   s   "r>   r?   rr   «   s   € ÷ ,ñ ,°3ð ,¸=ñ ,r@   c                ó†  € V '       g   R# \        V \        4      '       dL   \        V \        R4      p V \        J d   R# \        V \        4      '       g   \
        P                  R4       R# MA\        V \        4      '       g,   \
        P                  R\        V 4      P                  4       R# \        P                  ! V 4      # )zƒSerialize ``display_metadata`` for its TEXT column; an already-serialized JSON string
(import/replace paths) is not double-encoded.Nz+Ignoring non-JSON display metadata on writez-Ignoring non-object display metadata on writez6Ignoring unexpected display metadata type on write: %s)rJ   rK   r$   Ú_INVALIDÚdictÚloggerÚwarningÚtypeÚ__name__rL   r‹   )r4   s   &r>   Ú_encode_display_metadataÚ-SessionMessagesMixin._encode_display_metadataª   s–   € ÷  ÙÜÐ&¬×,Ò,Ü'Ð(8¼(ÐDqÓrÐØ¤8Ó+ÙÜÐ.´×5Ò5Ü—‘ÐNÔOÙð 6ô Ð,¬d×3Ò3Ü�N‰NÐSÔUYÐZjÓUk×UtÑUtÔuÙÜ�zŠzÐ*Ó+Ð+r@   c               ó    € V ^8„  d   QhRRRR/# )r7   rZ   r   r;   úOptional[dict[str, Any]]r<   )r=   s   "r>   r?   rr   ½   s   € ÷ ñ  cð Ð.Fñ r@   c                óþ   € V f   R# T p\        ^4       F,  p\        V\        4      '       g   K  \        V\        R4      pK.  	  V\        J d   R# \        V\
        4      '       g   \        P                  R4       R# V# )zµDecode a ``display_metadata`` column to a dict (never raw TEXT: the desktop does ``'task_count'
in meta``). Pre-guard rows are double-encoded, so a second string layer is unwrapped.Nz0Ignoring invalid display metadata on message rowz3Ignoring non-object display metadata on message row)ÚrangerJ   rK   r$   r˜   r™   rš   r›   )rZ   ÚmetaÚ_s   &  r>   Ú_decode_display_metadataÚ-SessionMessagesMixin._decode_display_metadata¼   sj   € ð Š;ÙØˆÜ�q–ˆAÜ˜$¤×$Ô$Ü ¤hÐ0bÓc’ñ ð ”8ÓÙÜ˜$¤×%Ò%Ü�N‰NÐPÔQÙØˆr@   c               ó    € V ^8„  d   QhRRRR/# )r7   r8   r   r;   r–   r<   )r=   s   "r>   r?   rr   Î   s   € ÷ _ñ _ Cð _¨Mñ _r@   c                ór   € V '       g   R# \        V \        4      '       d   V # \        P                  ! V 4      # )a  Serialize a structured reasoning field for its TEXT column. Strings are stored as-is: round-trips
(get_messages -> replace_messages) hand back raw TEXT; re-dumping would double-encode it and
reasoning-replay consumers (``isinstance(..., list)``) would drop it.N)rJ   rK   rL   r‹   ra   s   &r>   Ú_reasoning_json_textÚ)SessionMessagesMixin._reasoning_json_textÍ   s,   € ÷
 !ˆtÐ^´
¸5Ä#×0FÒ0F uÐ^ÌDÏJÊJÐW\ÓL]Ð^r@   Nç     Àr@Fc               ó8   € V ^8„  d   QhRRRRRRRRRR	R
R	RR	RR/# )r7   ro   rK   Úcompression_lock_holderr–   Úturn_lease_holderÚturn_lease_ttl_secondsr:   Úreject_active_turn_leasere   Úreject_active_compression_lockÚallow_closed_compression_parentr;   rq   r<   )r=   s   "r>   r?   rr   Ô   sO   € ÷ 7<ñ 7<¸sð 7<Ð]jð 7<Ø(ð7<ØINð7<à"&ð7<àPTð7<ð *.ð7<ð ;?ñ7<r@   c	           
     óÒ  € ^ RI Hp	 ^ RIHp
Hp V'       d‰   VP                  \        V34      P                  4       pVe`   \        V\        P                  ! 4       4      '       d    VP                  \        W,R,          34       MVR,          V8w  d   V	! RV: R24      hV'       g	   V'       dó   V P                  W4      pVP                  \        V34      P                  4       p\        P                  ! 4       pV'       de   Ve   VR,          V8w  d   V! RV: 24      h\        VR,          4      V8:  d/   VP                  R	V\        R
\        V4      4      ,           WÔ34       M;Ve8   \        Wï4      '       g   V! RV: 24      hVP                  RWÞR,          34       \        VP                  \         V34      P                  4       4      '       d   V'       g	   V
! V4      hR# R# )u  Transcript-write admission checks, run INSIDE the write txn by every writer. Ordinary appends do
NOT check compression_locks: the lock only stops two COMPRESSIONS colliding and archive_and_compact()
commits against a watermark, so concurrent appends are safe (blocking them killed turns during slow
summaries). Destructive user mutations opt in via ``reject_active_*`` so a compressor that captured
its watermark cannot resurrect the removed turn.

Shared by :meth:`append_message` and :meth:`append_messages_batch` so the two writers can never
diverge on these correctness invariants (this guard has already needed targeted fixes â€” see the
#74478 patience note below). User-initiated transcript mutations may opt in to rejecting an active
unowned turn lease in that same transaction.
©Ú!SessionCompressionInProgressError)ÚCompressionSessionClosedErrorÚSessionTurnLeaseLostErrorNri   zSession z& is being compressed by another writerz7Session turn lease lost; refusing transcript write for rh   zVUPDATE session_turn_leases SET expires_at = ? WHERE conversation_id = ? AND holder = ?gš™™™™™¹?zCSession has an active turn lease; refusing transcript mutation for zHDELETE FROM session_turn_leases WHERE conversation_id = ? AND holder = ?)r1   r¶   Úhermes_state_errorsr·   r¸   r}   r   r~   rk   ÚtimeÚ_DELETE_COMPRESSION_LOCK_SQLÚ_session_turn_lease_key_on_connÚ_TURN_LEASE_ROW_SQLr:   Úmaxr#   r    )r€   r�   ro   r®   r¯   r°   r±   r²   r³   r¶   r·   r¸   Úactive_lockÚconversation_idÚleaserd   s   &&&&&&&&&       r>   Ú_check_transcript_write_guardsÚ3SessionMessagesMixin._check_transcript_write_guardsÔ   s¼  € õ 	Cß`÷ *ØŸ,™,Ô'@À:À-ÓP×YÑYÓ[ˆKØÒ&Ü  ¬d¯iªi«k×:Ò:Ø—L‘LÔ!=À
ÐX`ÕLaÐ?bÕcØ  Õ*Ð.EÔEÙ;Ø" :¡.Ð0VÐWóYð Yç× 8Ø"×BÑBÀ4ÓTˆOØ—L‘LÔ!4°Ð6HÓI×RÑRÓTˆEÜ—)’)“+ˆCß Ø’= E¨(¥OÐ7HÔ$HÙ3ØQÐR\ÑQ_Ð`óbð bä˜˜|Õ,Ó-°Ô4ð —L‘Lð "Càœs 3¬Ð.DÓ(EÓFÕFÈÐkômøð Ò"Ü$ U×0Ò0Ù3Ø]Ð^hÑ]kÐlónð nð —‘ÐgØ$¨H¥oÐ6ô8ä  §¡¬n¸z¸mÓ!L×!UÑ!UÓ!W×XÒX÷  bAÙ/°
Ó;Ð;ñ bAÑXr@   c               ó4   € V ^8„  d   QhRRRRRRRRRR	R
RRR/# )r7   ro   rK   ÚroleÚmsgúdict[str, Any]rH   r   Úmessage_timestampr:   Úkeep_reasoningre   r;   Útupler<   )r=   s   "r>   r?   rr     sJ   € ÷  Dñ  D¨cð  D¸ð  DÀ>ð  DÐ_bð  DØ ð DØ59ð DØ>Cñ Dr@   c               óæ  a aaa€ R pVV3R loS P                  SP                  R4      4      pV'       d   \        P                  ! V4      MRp	\	        SP                  R4      4      p
SP                  R4      pSP                  \
        4      '       dL   \        V\        \        R4      34      '       d+   / T;'       g    / C\
        \        S\
        ,          4      /CpS P                  V4      pRVRVRVR	SP                  R	4      R
V	RV
RSP                  R4      RV/pWVSP                  R	4      WšSP                  R4      VSP                  R4      SP                  R4      \	        S! R4      4      \	        S! R4      4      .VV 3R lR 4       OSP                  R4      ;'       g    SP                  R4      NSP                  R4      '       d   ^M^ NSP                  R4      '       d   ^M^ N^NV! SP                  R4      4      NV! SP                  R4      4      NVNS P                  S P                  V4      4      N\        S4      N\        S4      N\        S4      N\!        S4      N5# )zëBind values for ``_INSERT_MESSAGE_SQL`` from one message dict (*tool_calls* already parsed;
*keep_reasoning* False NULLs every reasoning column). ``platform_message_id`` falls back to
``message_id`` (yuanbao's message-dict convention).c                óH   € \        V \        4      '       d   \        V 4      # R # rU   )rJ   rK   rb   )Úvs   &r>   Ú<lambda>Ú:SessionMessagesMixin._message_row_params.<locals>.<lambda>  s   € ¼ÀAÄs×9KÒ9KÔ!2°1Ó!5Ð!UÐQUÐ!Ur@   c                ó:   <€ S'       d   SP                  V 4      # R # rU   ©Úget)ÚkeyrÉ   rÆ   s   &€€r>   rÎ   rÏ     s   ø€ · §¡¨£Ð!IÀTÐ!Ir@   r†   NÚ	tool_namer4   rÅ   Ú	timestampÚtool_call_idrH   Údisplay_kindÚeffect_dispositionÚtoken_countÚfinish_reasonÚ	reasoningÚreasoning_contentc              3  óR   <"  € T F  pSP                  S! V4      4      x € K  	  R # 5irU   )rª   )rw   rx   Ú
_reasoningr€   s   & €€r>   ry   Ú;SessionMessagesMixin._message_row_params.<locals>.<genexpr>&  s+   øé € ð ^Ù\�!ð ×'Ñ'©
°1«×6Ð6Û\ùs   ƒ$'Úplatform_message_idÚ
message_idÚobservedÚ_compressed_summaryÚapi_content©Úreasoning_detailsÚcodex_reasoning_itemsÚcodex_message_items)r�   rÒ   rL   r‹   rb   r   rJ   r™   rœ   rV   rž   Ú_display_identityÚ_display_dedupe_keyr   r*   r/   r.   )r€   ro   rÅ   rÆ   rH   rÈ   rÉ   Ú_str_or_noneÚencoded_contentÚencoded_tool_callsÚencoded_tool_namer4   Úidentity_rowrÞ   s   f&&f&&d      @r>   Ú_message_row_paramsÚ(SessionMessagesMixin._message_row_params  s€  û€ ñ
 VˆÝIˆ
Ø×.Ñ.¨s¯w©w°yÓ/AÓBˆß7AœTŸZšZ¨
Ô3ÀtÐÜ-¨c¯g©g°kÓ.BÓCÐØŸ7™7Ð#5Ó6ÐØ�7‰7”8×Ò¤Ð,<¼tÄTÈ$ÃZÐ>P×!QÒ!QàZÐ#3×#9Ð#9°rÐZ¼HÄdÈ3ÌxÍ=ÓFYÑZÐØ×8Ñ8Ð9IÓJÐà�D˜) _°kÐCTØ˜CŸG™G NÓ3°\ÐCUØÐ*¨N¸C¿G¹GÀNÓ<SØÐ 0ð	
ˆð  /°3·7±7¸>Ó3JØØ�G‰GÐ(Ó)Ð+<¸c¿g¹gÀmÓ>TÐVY×V]ÑV]Ð^mÓVnÜ™j¨Ó5Ó6Ô8IÉ*ÐUhÓJiÓ8jðDõ^Ù\ó^ð	Dð �G‰GÐ)Ó*×CÐC¨c¯g©g°lÓ.CðDð —‘˜×$Ò$‰A¨!ðDð 36·'±'Ð:O×2PÒ2P©QÐVWðDð Z[ðDñ ˜Ÿ™ Ó/Ó0ðDñ 3?¸s¿w¹wÀ~Ó?VÓ2WðDð ðDð #×4Ñ4°T×5MÑ5MÈlÓ5[Ó\ðDô   Ó$ðDô ':¸#Ó&>ðDô ! Ó%ðDô (>¸cÓ'BñDð 	Dr@   c               ó(   € V ^8„  d   QhRRRRRRRR/# )	r7   rÆ   rÇ   rH   r   Úbatch_indexzdict[str, str]r;   rq   r<   )r=   s   "r>   r?   rr   0  s*   € ÷ )ñ ) >ð )¸sð )ÐQ_ð )Ðdhñ )r@   c                ó´  € V P                  R4      pVR8X  dc   V'       d[   \        V 4      pV F,  p\        V4      pV'       g   K  Wd9  g   K   \        4       WF&   K.  	  V'       d	   W@\        &   \        W 4       R# VR8X  d   VP                  4        R# VR8X  d>   \        V 4      f/   \        W P                  R4      4      pV'       d   Wp\        &   R# R# R# R# )aÕ  Per-occurrence tool-call identity at insert: an assistant row gets a uid for every tool call that
has none yet (``_tool_call_uids``, keyed by the effective provider id, which stays untouched); a
tool-result row that has none is resolved through *batch_index*, the calls the same batch's
assistant rows named. A result flushed in a later batch is resolved from the live list by the flush
(``tool_call_uid_from_history``) or, on restore, from the preceding assistant row.rÅ   Ú	assistantÚuserÚtoolNrÖ   )
rÒ   r-   r   r   r   r   Úclearr.   r   r   )rÆ   rH   ró   rÅ   ÚuidsÚtcÚcall_idÚuids   &&&     r>   Ú_stamp_tool_call_uidsÚ*SessionMessagesMixin._stamp_tool_call_uids/  s±   € ð �w‰w�v‹ˆØ�;Ô§:Ü% cÓ*ˆDÛ �Ü/°Ó3�ß‘7˜wÖ2Ü$,£J�D“Mñ !÷ Ø&*”NÑ#Ü  Ö2Ø�VŒ^Ø×ÑÖØ�VŒ^Ô 6°sÓ ;Ò CÜ'¨·W±W¸^Ó5LÓMˆCßØ%(”MÓ"ñ ñ !D‰^r@   c               ó(   € V ^8„  d   QhRRRRRRRR/# )r7   ro   rK   rÆ   rÇ   rÈ   r:   r;   r<   )r=   s   "r>   r?   rr   G  s,   € ÷ 9ñ 9Øð9Ø$2ð9ØGLð9à	ñ9r@   c           
     ó¼   € VP                  RR4      pV P                  VVV\        VP                  R4      4      VVR8H  R7      p\        \	        \
        V4      4      # )zGSerialize one live message with the exact durable shape used by INSERT.rÅ   ÚunknownrH   rõ   ©rÉ   )rÒ   rð   rQ   r™   ÚzipÚ_MESSAGE_WRITE_COLUMNS)r€   ro   rÆ   rÈ   rÅ   Úparamss   &&&&  r>   Ú_serialized_message_rowÚ,SessionMessagesMixin._serialized_message_rowG  sc   € ð �w‰w�v˜yÓ)ˆØ×)Ñ)ØØØÜ˜cŸg™g lÓ3Ó4ØØ ;Ñ.ð *ó 
ˆô ”CÔ.°Ó7Ó8Ð8r@   c               ó   € V ^8„  d   QhRR/# )r7   r;   rÇ   r<   )r=   s   "r>   r?   rr   V  s   € ÷ &ñ &¨.ñ &r@   c                óò  € RVR,          RV P                  VR,          4      /pR F  pW,          f   K  W,          W#&   K  	  VR,          '       d   \        VR,          . R4      VR&   VR,          e   VR,          VR&   VR,          VR&   VR,          '       d   R	VR&   VR
,          '       d   R	VR
&   \        W4       VR,          e   VR,          VR&   VR,          e   VR,          VR&   VR,          e!   V P                  VR,          4      pVe   WBR&   VR,          R8X  dR   R F  pW,          f   K  W,          W#&   K  	  R F,  pW,          '       g   K  \        W,          RRV R24      W#&   K.  	  V# )zWDecode one durable row without replay dedupe, alternation repair, or content stripping.rÅ   r†   NrH   z=Failed to deserialize repaired tool_calls, falling back to []rà   rá   râ   Trã   rä   r×   r4   rõ   zFailed to deserialize repaired ú, falling back to None)rÖ   rÔ   rØ   rÙ   rÚ   )rÛ   rÜ   rå   )r“   r$   r+   r¦   )r€   rj   rÆ   ÚcolumnÚmetadatas   &&   r>   Ú_decoded_repair_rowÚ(SessionMessagesMixin._decoded_repair_rowV  s˜  € ð �C˜•KØ�t×+Ñ+¨C°	­NÓ;ð
ˆó jˆFØ�{Ô&Ø!�k�“ñ jð ˆ|×ÔÜ (Ø�LÕ! 2Ð'fó!ˆC�Ñð Ð$Õ%Ò1Ø #Ð$9Õ :ˆC�ÑØ),Ð-BÕ)CˆCÐ%Ñ&Øˆz�?Œ?Ø"ˆC�
‰OØÐ$×%Ô%Ø)-ˆCÐ%Ñ&Ü! #Ô+Øˆ}ÕÒ)Ø!$ ]Õ!3ˆC�ÑØˆ~ÕÒ*Ø"% nÕ"5ˆC�ÑØÐ!Õ"Ò.Ø×4Ñ4°SÐ9KÕ5LÓMˆHØÒ#Ø*2Ð&Ñ'Øˆv�;˜+Ô%Û<�Ø•;Ô*Ø"%¥+�C“Kñ =ó `�Ø—;–;Ü"*Ø� TÐ-LÈVÈHÐTjÐ+kó#�C“Kñ `ð
 ˆ
r@   c          
     ó,   € V ^8„  d   QhRRRRRRRRRR	/# )
r7   ro   rK   ÚinsertedrS   rH   Úunitre   r;   rq   r<   )r=   s   "r>   r?   rr     s9   € ÷ 
rñ 
r°ð 
rÀð 
rÐQTð 
rÐ_cð 
rÐhlñ 
rr@   c               ó¼   € V'       d   RMRV33w  rVV^ 8”  d    V P                  RV R2. VOVNVN54       R# V^ 8”  d   V P                  RV R2. VOVN54       R# R# )zXBump sessions.* counters after an insert; *unit* bakes the ``+ 1`` literal into the SQL.Ú?z4UPDATE sessions SET message_count = message_count + zK,
                       tool_call_count = tool_call_count + ? WHERE id = ?ú WHERE id = ?N)Ú1r<   )r}   )r�   ro   r  rH   r  Úincr  s   &&&&$  r>   Ú_bump_session_countersÚ+SessionMessagesMixin._bump_session_counters~  s…   € ÷ $(‘i¨c°H°;Ð-?‰ˆØ˜Œ>Ø�L‰LØHÈÈð NJð Mà1�&Ð1˜*Ð1 jÑ1ö3ð ˜Œ\Ø�L‰LØFÀsÀeÈ=ÐYÐ[pÐ]cÐ[pÐeoÑ[pörñ r@   c          4     ó€   € V ^8„  d   QhRRRRRRRRRRR	RR
RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR R!RR"R#/# )$r7   ro   rK   rÅ   r†   ú
str | NonerÔ   rH   r   rÖ   rÙ   z
int | NonerÚ   rÛ   rÜ   ræ   rç   rè   rà   râ   re   rØ   r–   rã   rÕ   rä   r×   r4   r¡   r®   r¯   r°   r:   Úmessage_uidr;   rS   r<   )r=   s   "r>   r?   rr   ‹  s.  € ÷  Vñ  VØð VØ%(ð VØ3=ð VØQ[ð VØpsð Và ð Và6@ð VàXbð Vð wAð Vð &ð Vð BEð Vð ehð Vð !ð	 Vð ?Ið	 Vð \`ð	 Vð
 *ð Vð
 HLð Vð
 adð Vð #ð Vð :Gð Vð 3ð Vð Ubð Vð )ð Vð JOð Vð #ð Vð /2ñ Vr@   c           	     ón  a aaaaaa€ \        \        4       4      pS P                  V4      VR&   \        S4      o\	        V\
        P
                  ! 4       4      p\        V4       S P                  VS/ 4       S P                  SVVSVRR7      oVVV VVVV3R lpS P                  VS P                  R7      # )aµ  Append one message; returns the row id and bumps the session counters. ``platform_message_id``:
the platform's own id. ``api_content``: byte-fidelity sidecar, the exact string sent to the API when
it differed from ``content``, stored as sent except lone surrogates. ``message_uid``: the id a caller
that keeps the message as a live dict already stamped on it (``stamp_message_uid``); minted when
absent, but then only the row carries it.r4   Tr  c                ó²   <€ SP                  V SSSSR 7       V P                  \        S4      P                  pSP	                  V S^\        S4      RR7       V# )©r¯   r°   T©r  )rÂ   r}   Ú_INSERT_MESSAGE_SQLÚ	lastrowidr  rX   )	r�   Úmsg_idr®   r  r€   ro   rH   r¯   r°   s	   & €€€€€€€r>   Ú_doÚ0SessionMessagesMixin.append_message.<locals>._do£  s`   ø€ Ø×/Ñ/°°jÐBYØ"3ÐLbð 0ô dà—\‘\Ô"5°vÓ>×HÑHˆFØ×'Ñ'¨¨j¸!Ô=NÈzÓ=ZÐaeÐ'ÔfØˆMr@   ©Ú
patience_s)r™   Úlocalsrž   rQ   rF   rº   r   rý   rð   Ú_execute_writeÚ_TRANSCRIPT_WRITE_PATIENCE_S)r€   ro   rÅ   r†   rÔ   rH   rÖ   rÙ   rÚ   rÛ   rÜ   ræ   rç   rè   rà   râ   rØ   rã   rÕ   rä   r×   r4   r®   r¯   r°   r  rÆ   rÈ   r#  r  s   ff&&&f&&&&&&&&&&&&&&&&fff&   @r>   Úappend_messageÚ#SessionMessagesMixin.append_message‹  s«   þ€ ô ”6“8‹nˆà"&×"?Ñ"?Ð@PÓ"QˆÐÑÜ& zÓ2ˆ
Ü-¨i¼¿º»ÓEÐÜ˜#ÔØ×"Ñ" 3¨
°BÔ7Ø×)Ñ)Ø˜˜c :Ð/@ÐQUð *ó Wˆ÷	ó 	ð ×"Ñ" 3°4×3TÑ3TÐ"ÓUÐUr@   c               ó4   € V ^8„  d   QhRRRRRRRRRR	R
RRR/# )r7   ro   rK   Úmessagesúlist[dict[str, Any]]r®   r–   r¯   Ú
chunk_rowsúOptional[int]r°   r:   r;   rS   r<   )r=   s   "r>   r?   rr   ­  sQ   € ÷ Dñ DØðDØ)=ðDØXeðDà(ðDà=JðDð !&ðDð 36ñDr@   c           	     ód  a aaaaaa€ S'       g   ^ # Sev   \        S4      S8”  df   / pS F.  pS P                  V\        VP                  R4      4      V4       K0  	  \	        VVVV VVV3R l\        ^ \        S4      S4       4       4      # S P                  VVV VVV3R lSS P                  R7      # )zÂAppend *messages* in ONE write txn (all rows land or none, guards run once); returns the inserted
count. ``chunk_rows`` bounds txn size for LARGE copies (branch seeds; FTS triggers run per row).rH   c           	   3  ób   <"  € T F$  pSP                  SSWS,            SSSR 7      x € K&  	  R# 5i)©r®   r¯   r°   N)Úappend_messages_batch)	rw   Ústartr/  r®   r-  r€   ro   r¯   r°   s	   & €€€€€€€r>   ry   Ú=SessionMessagesMixin.append_messages_batch.<locals>.<genexpr>¹  sN   øé € ð Bñ A�Eð ×1Ñ1°*¸hÀuÐU_ÕM_Ð>`Ø,CÐWhØ+Að 2÷ Cð Có Aùs   ƒ,/c           	     ó2   <€ SP                  V SSSSSR 7      # )r3  )Ú_append_messages_in_transaction)r�   r®   r-  r€   ro   r¯   r°   s   &€€€€€€r>   rÎ   Ú<SessionMessagesMixin.append_messages_batch.<locals>.<lambda>½  s'   ø€ ¸4×;_Ñ;_Ø�*˜hÐ@WØ/ÐH^ð <`ô <`r@   r%  )rW   rý   rQ   rÒ   Úsumr£   Ú_execute_transcript_writer)  )	r€   ro   r-  r®   r¯   r/  r°   Útool_uid_indexrÆ   s	   fffffff  r>   r4  Ú*SessionMessagesMixin.append_messages_batch­  sµ   þ€ ÷ ÙØÒ!¤c¨(£m°jÔ&@Ø-/ˆNÛ�Ø×*Ñ*¨3Ô0AÀ#Ç'Á'È,ÓBWÓ0XÐZhÖiñ  ä÷ Bò Bô # 1¤c¨(£m°ZÔ@óBó Bð Bð ×-Ñ-÷ /`ñ /`ð  ×!BÑ!Bð .ó Dð 	Dr@   r®   r¯   r°   c          
     	óø   a a€ S P                  VSVWVR 7       ^ RIHp V! VSVS P                  S P                  V V3R lS P
                  R7      pS P                  VSV4      w  ršS P                  VSWšRR7       V	# )r  )Ú#resolve_and_repair_transcript_batchc                ó(   <€ SP                  SW4      # rU   )r  )rÆ   rÕ   r€   ro   s   &&€€r>   rÎ   ÚFSessionMessagesMixin._append_messages_in_transaction.<locals>.<lambda>Í  s   ø€ ¸×8TÑ8TØ˜Cô9r@   )Úencode_content_fnÚdecode_content_fnÚserialize_message_fnÚdecode_row_fnFr  )rÂ   Úagent.transcript_repairr?  r�   r“   r  Ú_insert_message_rowsr  )r€   r�   ro   r-  r®   r¯   r°   r?  Úinserted_rowsr  Útool_calls_totals   f&f&$$$    r>   r8  Ú4SessionMessagesMixin._append_messages_in_transactionÂ  s�   ù€ à×+Ñ+¨D°*Ð>UØ/ð 	,ô 	`åOÙ;ØØØØ"×2Ñ2Ø"×2Ñ2õ"ð ×2Ñ2ô

ˆð &*×%>Ñ%>¸tÀZÐQ^Ó%_Ñ"ˆØ×#Ñ# D¨*°hÐW\Ð#Ô]Øˆr@   Ú_row_idrÕ   c               ó   € V ^8„  d   QhRR/# )r7   r-  r.  r<   )r=   s   "r>   r?   rr   Ø  s   € ÷ ñ Ð6Jñ r@   c                ó\  a aaaaaa€ \        4       oS Uau. uFK  o\        ;QJ d"    . VV3R lS P                   4       F  NK  	  5M! VV3R lS P                   4       4      NKM  	  upoR VVVV 3R lloVV3R lp S P                  ! V3/ VB # u upi   \         d
    S! 4        h i ; i)a¯  ``_execute_write(fn)`` for callbacks that stamp row state onto the caller's *messages* (every
:meth:`_insert_message_rows` caller that passes caller-owned dicts; rewind and import insert fresh copies). Each attempt, and a final failure, restores the caller's
``_row_id`` / digest / timestamp: a rolled-back insert's id is reused by SQLite, so a stale stamp
would make a later flush adopt another writer's row and drop this message.c              3  óH   <"  € T F  pSP                  VS4      x € K  	  R # 5irU   rÑ   )rw   rx   Ú_absentÚms   & €€r>   ry   ÚASessionMessagesMixin._execute_transcript_write.<locals>.<genexpr>Þ  s!   øé € ÐKÑ6J°˜1Ÿ5™5  G×,Ð,Ó6Jùs   ƒ"c               ó   € V ^8„  d   QhRR/# )r7   r;   rq   r<   )r=   s   "r>   r?   ÚDSessionMessagesMixin._execute_transcript_write.<locals>.__annotate__à  s   € ÷ 	)ñ 	)˜$ñ 	)r@   c                 óØ   <€ \        SS4       FX  w  rV P                  \        R 4       \        SP                  V4       F#  w  r#VSJ d   V P                  VR 4       K  W0V&   K%  	  KZ  	  R # rU   )r  Úpopr   Ú_ROW_STATE_KEYS)rÆ   ÚstaterÓ   r8   rO  r-  Ú	pre_stater€   s       €€€€r>   Ú_restoreÚ@SessionMessagesMixin._execute_transcript_write.<locals>._restoreà  s[   ø€ Ü! (¨IÖ6‘
�Ø—‘œ tÔ,Ü"% d×&:Ñ&:¸EÖ"B‘J�CØ Ó'ØŸ™  TÖ*à#(˜C›ó	 #Có 7r@   c                ó"   <€ S! 4        S! V 4      # rU   r<   )r�   rY  Úfns   &€€r>   Ú_attemptÚ@SessionMessagesMixin._execute_transcript_write.<locals>._attempté  s   ø€ ÙŒJÙ�d“8ˆOr@   )ÚobjectrÊ   rV  r(  ÚBaseException)	r€   r\  r-  ÚkwargsrP  r]  rO  rY  rX  s	   fff,` @@@r>   r;  Ú.SessionMessagesMixin._execute_transcript_writeØ  s‘   þ€ ô
 “(ˆÙU]Ô^ÑU]ÐPQ—U”UÕK°d×6JÒ6JÓK—U‘UÕK°d×6JÒ6JÓKÖKÑU]Ñ^ˆ	÷	)ó 	)ö	ð	Ø×&Ò& xÑ:°6Ñ:Ð:ùò _øô  ô 	ÙŒJØð	ús   —B§ABÁ?B ÂB+r4   c               ó0   € V ^8„  d   QhRRRRRRRRRRRR	/# )
r7   ro   rK   rÅ   r†   r×   r4   r¡   r;   re   r<   )r=   s   "r>   r?   rr   ò  s;   € ÷ (ñ (À3ð (ÐQTð (Ð_bð (Ø?Bð(àC[ð(àgkñ(r@   c               óz   a aaaaa€ S'       d   S'       d	   S'       g   R# VVVVV V3R lpS P                  V4      # )a	  Stamp presentation metadata on this turn's freshly persisted row (newest active row by content,
right after the serial turn flushed); the model still sees ``role``/``content`` unchanged, so
producer provenance survives without classifying by content at render time.Fc                óà   <€ V P                  R SSSP                  S4      34      P                  4       pVf   R# V P                  R\        S4      SP	                  S4      V^ ,          34       R# )zqSELECT id FROM messages WHERE session_id = ? AND role = ? AND content = ? AND active = 1 ORDER BY id DESC LIMIT 1FzGUPDATE messages SET display_kind = ?, display_metadata = ? WHERE id = ?T)r}   r�   r~   rb   rž   )r�   rj   r†   r×   r4   rÅ   r€   ro   s   & €€€€€€r>   r#  ÚJSessionMessagesMixin.set_latest_matching_message_display_kind.<locals>._doú  sv   ø€ Ø—,‘,ð  Jà˜T 4×#7Ñ#7¸Ó#@ÐAóCçCKÁ8Ã:ð ð Š{ÙØ�L‰LÐbÜ" <Ó0°$×2OÑ2OÐP`Ó2aÐcfÐghÕciÐjôlár@   ©r(  )r€   ro   rÅ   r†   r×   r4   r#  s   ffdddd r>   Ú(set_latest_matching_message_display_kindÚ=SessionMessagesMixin.set_latest_matching_message_display_kindò  s,   ý€ ÷ §·Ù÷	ò 	ð ×"Ñ" 3Ó'Ð'r@   c               ó    € V ^8„  d   QhRRRR/# )r7   r¤   r¡   r;   r.  r<   )r=   s   "r>   r?   rr     s    € ÷ dñ dÐ#;ð dÐ@Tñ dr@   c                óÚ   € T;'       g    / P                  V P                  4      p\        V\        4      '       d+   V Uu. uF  p\        V\        4      '       g   K  VNK  	  up# . # u upi )zEWell-formed (dict) reactions stored under ``REACTIONS_METADATA_KEY``.)rÒ   ÚREACTIONS_METADATA_KEYrJ   rV   r™   )r€   r¤   Ú	reactionsÚrs   &&  r>   Ú_reaction_listÚ#SessionMessagesMixin._reaction_list  sT   € à—Z�Z˜R×$Ñ$ T×%@Ñ%@ÓAˆ	Ü@JÈ9ÔVZ×@[Ò@[™9Ó<™9�a¬
°1´d×(;—�™9Ñ<ÐcÐacÐcùÒ<s   Á A(ÁA(Úauthorrö   c          
     ó,   € V ^8„  d   QhRRRRRRRRRR	/# )
r7   ro   rK   Úmessage_row_idrS   Úemojir–   rq  r;   úOptional[list[dict[str, Any]]]r<   )r=   s   "r>   r?   rr   
  s2   € ÷ (ñ (¨sð (ÀCð (ÐP]ð (Ø%(ð(Ø6Tñ(r@   c               óŒ   a aaaaa€ V'       d   Sf   R# S P                  VS4      w  ooVVVVV V3R lpS P                  V4      # )a  Set (``emoji=None``: clear) *author*'s reaction. Tapback semantics: one per author per message;
the same emoji again clears, a different one replaces. Returns the list after the write, or
``None`` for a row outside the session's visible resume lineage (see ``_reaction_row_query``).Nc           	     ó²  <€ V P                  SS
4      P                  4       pVf   R # SP                  V^ ,          4      ;'       g    / pSP                  V4      pV Uu. uF  qDP	                  R4      S8w  g   K  VNK  	  pp\        V3R lV 4       R 4      pS'       dN   Ve   VP	                  R4      S8w  d4   VP                  R\        S4      RSR\        P                  ! 4       /4       V'       d   WRSP                  &   MVP                  SP                  R 4       T P                  \        V'       d   SP                  V4      MR S	34       V# u upi )Nrq  c              3  óV   <"  € T F  qP                  R 4      S8X  g   K  Vx € K   	  R# 5i)rq  NrÑ   )rw   rn  rq  s   & €r>   ry   ÚISessionMessagesMixin.set_message_reaction.<locals>._do.<locals>.<genexpr>  s    øé € ÐN© 1·E±E¸(³OÀvÑ4MŸQšQ«ùs   ƒ)Ÿ
)rt  Úat)r}   r~   r¦   ro  rÒ   ÚnextÚappendrb   rº   rl  rU  Ú_SET_DISPLAY_META_SQLrž   )r�   rj   r¤   Úexistingrn  rm  Úpreviousrq  rt  rs  r  r€   Úsqls   &      €€€€€€r>   r#  Ú6SessionMessagesMixin.set_message_reaction.<locals>._do  s!  ø€ Ø—,‘,˜s FÓ+×4Ñ4Ó6ˆCØŠ{ÙØ×0Ñ0°°QµÓ8×>Ð>¸BˆDØ×*Ñ*¨4Ó0ˆHÙ$,ÓJ¡H˜q·±°h³À6Ñ0IŸ˜¡HˆIÐJÜÔN©ÓNÐPTÓUˆHß˜(Ò*¨h¯l©l¸7Ó.CÀuÔ.LØ× Ñ  'Ô+<¸UÓ+CÀXÈvÐW[Ô]a×]fÒ]fÓ]hÐ!iÔjßØ4=�T×0Ñ0Ò1à—‘˜×4Ñ4°dÔ;Ø�L‰LÔ.×X\°×1NÑ1NÈtÔ1TÐbfÐhvÐ0wÔxØÐùò Ks   ÁEÁ;E)Ú_reaction_row_queryr(  )r€   ro   rs  rt  rq  r#  r  r€  s   f&ffd @@r>   Úset_message_reactionÚ)SessionMessagesMixin.set_message_reaction
  sC   ý€ ÷
 ˜^Ò3ÙØ×.Ñ.¨z¸>ÓJ‰ˆˆV÷	ò 	ð  ×"Ñ" 3Ó'Ð'r@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   ro   rK   rs  rS   r;   r.  r<   )r=   s   "r>   r?   rr   $  s'   € ÷ eñ e°ð eÀSð eÐMañ er@   c                ó¶   € V'       d   Vf   . # V P                   ! V P                  W4      !  pVe(   V P                  V P                  V^ ,          4      4      # . # )z<Reaction list persisted on one message row (never ``None``).)Ú	_read_oner‚  ro  r¦   )r€   ro   rs  rj   s   &&& r>   Úget_message_reactionsÚ*SessionMessagesMixin.get_message_reactions$  sR   € ç˜^Ò3ØˆIØ�nŠn˜d×6Ñ6°zÓRÑSˆØMPÊ_ˆt×"Ñ" 4×#@Ñ#@ÀÀQÅÓ#HÓIÐdÐbdÐdr@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   ro   rK   rs  rS   r;   ztuple[str, tuple]r<   )r=   s   "r>   r?   rr   +  s'   € ÷ dñ d¨cð dÀ3ð dÐK\ñ dr@   c                ón   € V P                  V4      p\        P                  \        V4      R7      V.VO53# )uV  A reaction addresses a row the client can SEE, and a display resume materializes the whole
compression lineage (active + compacted rows, with row ids) â€” so a row is "in this session" when
its owner is any lineage segment, not only the tip, and a rewound row is not. Explicit ``/branch``
copies keep their own rows (``_resume_lineage_ids``).)Úids)Ú_resume_lineage_idsÚ_DISPLAY_META_ROW_SQLr=   r&   )r€   ro   rs  Úlineages   &&& r>   r‚  Ú(SessionMessagesMixin._reaction_row_query+  s;   € ð
 ×*Ñ*¨:Ó6ˆÜ$×+Ñ+´¸gÓ0FÐ+ÓGÈ.ÐIcÐ[bÑIcÐcÐcr@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   ro   rK   rq  r;   r.  r<   )r=   s   "r>   r?   rr   3  s"   € ÷  (ñ  (°ð  (Àð  (ÐQeñ  (r@   c               óp   a aa€ V'       g   . # S P                  V4      oVVV 3R lpS P                  V4      # )a#  Return *author*'s not-yet-surfaced reactions and mark them seen. Reactions are announced on the
NEXT user turn (never by rewriting the reacted message: cache-safe); ``seen`` makes it exactly once.
Include compaction-archived history that remains visible, but exclude rewound/superseded rows.c                ó~  <€ . pV P                  R \        S	4       R\         R\        RRS
P                  ,           4       R2\        S	4      4      P                  4        EFY  pS
P                  VR,          4      pV'       d   VP                  S
P                  4      MRp\        V\        4      '       g   KY  RpV F¿  p\        V\        4      '       d.   VP                  R4      S8w  g   VP                  R	4      '       d   KH  R
VR	&   R
pS
P                  VR,          4      pTP                  RVR,          RVR,          RVP                  R4      ;'       g    RR\        V\        4      '       d   TMR/4       KÁ  	  V'       g   EK+  V P                  \        S
P!                  V4      VR,          34       EK\  	  V# )zNSELECT id, role, content, display_metadata FROM messages WHERE session_id IN (r3   z AND r4   r5   z IS NOT NULL ORDER BY idNFrq  ÚseenTr†   Úrow_idÚidrÅ   rt  ru   Útext)r}   r&   Ú_DISPLAY_ACTIVE_CLAUSEr'   rl  rÊ   Úfetchallr¦   rÒ   rJ   rV   r™   r“   r|  rK   r}  rž   )r�   Úpendingrj   r¤   rm  ÚchangedÚreactionr†   rq  r�  r€   s   &       €€€r>   r#  Ú7SessionMessagesMixin.take_unseen_reactions.<locals>._do:  sž  ø€ ØˆGð —|‘|ð %,Ü,9¸'Ó,BÐ+CÀ1ÔE[ÐD\ð ]Ü,Ð-?ÀÈ×HcÑHcÕAcÓdÐeð f"ð%"ô $)¨£>ó3÷ 4<±8³:ó>�ð ×4Ñ4°SÐ9KÕ5LÓM�ßEI˜DŸH™H T×%@Ñ%@ÔAÈt�	Ü! )¬T×2Ò2ÙØ�Û )�HÜ% h´×5Ò5¸¿¹ÀhÓ9OÐSYÔ9YÐ]e×]iÑ]iÐjp×]qÒ]qÙ Ø'+�H˜VÑ$Ø"�GØ"×2Ñ2°3°yµ>ÓB�GØ—N‘NØ  # d¥)¨V°S¸µ[À'È8Ï<É<ÐX_ÓK`×KfÐKfÐdfØ¬:°g¼s×+CÒ+C¡Èð$Mö Nñ !*÷ ’7Ø—L‘LÔ!6¸×9VÑ9VÐW[Ó9\Ð^aÐbfÕ^gÐ8h×iñ'>ð( ˆNr@   )r�  r(  )r€   ro   rq  r#  r�  s   f&d @r>   Útake_unseen_reactionsÚ*SessionMessagesMixin.take_unseen_reactions3  s5   ú€ ÷ ØˆIØ×*Ñ*¨:Ó6ˆ÷	ð2 ×"Ñ" 3Ó'Ð'r@   rÅ   ÚoffsetÚrequire_textTc          
     ó,   € V ^8„  d   QhRRRRRRRRRR	/# )
r7   ro   rK   rÅ   r   rS   r¡  re   r;   r0  r<   )r=   s   "r>   r?   rr   U  s2   € ÷ 
'ñ 
'°ð 
'¸cð 
'ÐTWð 
'Ø,0ð
'Ø<Iñ
'r@   c               ó²   € V'       d   VR9  g   V^ 8  d   R# V'       d   RMRpV P                  RV R2W\        V4      34      pV'       d
   V^ ,          # R# )zÊRow id of the most recent active *role* message, or ``None``. ``offset`` steps back; ``require_text``
skips rows without plain-text content so "the latest message" never resolves to an invisible bubble.Nz0AND content IS NOT NULL AND TRIM(content) != '' ru   zISELECT id FROM messages WHERE session_id = ? AND role = ? AND active = 1 z!ORDER BY id DESC LIMIT 1 OFFSET ?>   rö   rõ   ©r‡  rS   )r€   ro   rÅ   r   r¡  Útext_filterrj   s   &&$$$  r>   Úlatest_message_row_idÚ*SessionMessagesMixin.latest_message_row_idU  sc   € ÷ ˜TÐ)>Ô>À&È1Ä*ÙßLXÑHÐ^`ˆØ�n‰nð Ø)˜]Ð*KðMàœs 6›{Ð+ó-ˆ÷ ˆs�1�vÐ& $Ð&r@   c               ó    € V ^8„  d   QhRRRR/# )r7   ro   rK   r;   r–   r<   )r=   s   "r>   r?   rr   a  s   € ÷ 'ñ '°3ð '¸=ñ 'r@   c                óN   € V P                  RV34      pV'       d
   V^ ,          # R# )zòRole of the newest active user/assistant/tool row, or ``None``. ``session_meta`` /
``system`` rows are transcript bookkeeping stripped before the model sees history, so
they must not hide an open user tail from the failed-turn boundary check.z�SELECT role FROM messages WHERE session_id = ? AND active = 1 AND role NOT IN ('session_meta', 'system') ORDER BY id DESC LIMIT 1N©r‡  ©r€   ro   rj   s   && r>   Úlatest_conversation_roleÚ-SessionMessagesMixin.latest_conversation_rolea  s2   € ð �n‰nðRØT^ÐS`óbˆ÷ ˆs�1�vÐ& $Ð&r@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   ro   rK   r•  rS   r;   r–   r<   )r=   s   "r>   r?   rr   j  s!   € ÷ 'ñ '¨3ð '¸ð 'Àñ 'r@   c                óv   € V'       g   R# V P                  R\        V4      V34      pV'       d
   V^ ,          # R# )zDRole of the active message at *row_id* in *session_id*, or ``None``.NzHSELECT role FROM messages WHERE id = ? AND session_id = ? AND active = 1r¤  )r€   ro   r•  rj   s   &&& r>   Úget_message_roleÚ%SessionMessagesMixin.get_message_rolej  sB   € çÙØ�n‰nÐgÔjmÐntÓjuð  xBð  jCó  Dˆßˆs�1�vÐ& $Ð&r@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   Úparent_session_idrK   r-  r.  r;   rq   r<   )r=   s   "r>   r?   rr   q  s#   € ÷ ñ Àð ÐOcð Ðhlñ r@   c           
     ó”  €  \        V4       UUu. uF?  w  rE\        V\        4      '       g   K  \        VP	                  R4      RR7      e   K=  VNKA  	  pppV'       g   R# / pVP                  RV34      P                  4        FS  pVP                  VR,          VR,          VR,          VR	,          VR
,          3. 4      P                  VR,          4       KU  	  V FÎ  pW4,          p\        VP	                  R	4      4      p	VP	                  RR4      V P                  VP	                  R4      4      VP	                  R4      V	'       d   \        P                  ! V	4      MR\        VP	                  R
4      4      3p
VP	                  V
4      pV'       g   Kº  VP                  ^ 4      VR&   KÐ  	  R# u uppi   \         d     R# i ; i)a³  Adopt the durable parent row's timestamp onto carried handoff rows so the re-inserted child row keeps
the original's display identity (see _display_dedupe_key). Idempotent; never overwrites a timestamp the
dict already carries; rows with no match keep their caller-supplied/now_ts value.

Content identity = _display_dedupe_key's content key minus the timestamp (role, encoded content,
tool_call_id, encoded tool_calls, tool_name). Donors are the parent's ACTIVE rows, consumed first-match-wins in id
order so two identical-content turns cannot both grab the same parent row. Best-effort: on any error
the insert falls back to today's behavior (publish-time stamp) and never breaks publish.
rÕ   rB   rC   Nz†SELECT id, role, content, timestamp, tool_call_id, tool_calls, tool_name FROM messages WHERE session_id = ? AND active = 1 ORDER BY idrÅ   r†   rÖ   rH   rÔ   r  )Ú	enumeraterJ   r™   r   rÒ   r}   r™  Ú
setdefaultr|  rQ   r�   rL   r‹   rb   rU  Ú	Exception)r€   r�   r³  r-  ÚirÆ   Ú
candidatesÚdonorsrj   rH   rÓ   Úqueues   &&&&        r>   Ú_carry_parent_timestampsÚ-SessionMessagesMixin._carry_parent_timestampsq  s—  € ð	Ü*3°HÔ*=ô tÑ*=¡ Ü˜c¤4×(ô Ü-9¸#¿'¹'À+Ó:NÐViÔ-j÷ ˜!Ñ*=ˆJñ tçÙØ79ˆFØ—|‘|ðFØHYÐG[ó]ç]eÑ]eÓ]gòh�ð ×!Ñ! 3 v¥;°°IµÀÀNÕ@SÐUXÐYeÕUfØ˜Õ$ð#&Ø')ó+ß+1©6°#°kÕ2BÖ+Cñ	hó
  �Ø•k�Ü.¨s¯w©w°|Ó/DÓE�
Ø—w‘w˜v yÓ1°4×3GÑ3GÈÏÉÐPYÓHZÓ3[Ø—G‘G˜NÓ+Çz¬T¯ZªZ¸
Ô-CÐW[Ü% c§g¡g¨kÓ&:Ó;ð=�ð Ÿ
™
 3›�ß‘5Ø',§y¡y°£|�C˜Ó$ó  ùótøô& ô 	Úð	ús:   ‚F8 �F2¯F2ÁF2ÁF8 Á"D1F8 ÆF8 Æ2F8 Æ8GÇGÚprune_checkpointsc               ó(   € V ^8„  d   QhRRRRRRRR/# )	r7   ro   rK   r-  r.  r¾  re   r;   útuple[int, int]r<   )r=   s   "r>   r?   rr   ’  s+   € ÷ %*ñ %*°Sð %*ÐDXð %*Ø04ð%*Ø@Oñ%*r@   c               óZ  € \         P                   ! 4       p^ ;rg/ pV Fà  p	V	P                  RR4      p
\        V	P                  R4      4      p\        V	P                  R4      V4      p\	        V	4       V P                  W›V4       VP                  \        V P                  W*W›WÊR8H  R7      4      pWÉR&   VP                  e   VP                  V	R&   V^,          pV\        V4      ,          p\        W\4      R,           pKâ  	  V'       d   V P                  WV4       ^ R	IHp V! WV4       Wg3# )
ae  Insert *messages* as fresh active rows in the caller's txn -> ``(inserted, tool_call_count)``.
Never touches sessions.* counters (callers reconcile differently); reasoning kept for assistant rows.
A caller that re-archives some rows afterwards passes ``prune_checkpoints=False`` and prunes once they
are archived again (:meth:`_prune_shadowed_checkpoints`).rÅ   r  rH   rÕ   rõ   r  rK  g�íµ ÷Æ°>)Ústamp_inserted_row_snapshots)rº   rÒ   rQ   rF   r   rý   r}   r   rð   r!  rX   r¾   Ú_prune_shadowed_checkpointsrF  rÂ  )r€   r�   ro   r-  r¾  Únow_tsr  rI  Úbatch_tool_indexrÆ   rÅ   rH   rÈ   ÚcurrÂ  s   &&&&$          r>   rG  Ú)SessionMessagesMixin._insert_message_rows’  s'  € ô —’“ˆØ&'Ð'ˆØ+-ÐÛˆCØ—7‘7˜6 9Ó-ˆDÜ*¨3¯7©7°<Ó+@ÓAˆJÜ 1°#·'±'¸+Ó2FÈÓ OÐô ˜cÔ"Ø×&Ñ& sÐ8HÔIØ—,‘,Ô2°D×4LÑ4LØ #Ð3DÐ]hÑUhð 5Mó 5jó kˆCð  1�ÑØ�}‰}Ò(Ø!$§¡��I‘Ø˜�MˆHØÔ 1°*Ó =Õ=ÐÜ˜Ó3°dÕ:ŠFñ) ÷* Ø×,Ñ,¨T¸xÔHõ 	IÙ$ T°xÔ@ØÐ)Ð)r@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   ro   rK   Úlive_messagesr.  r;   rq   r<   )r=   s   "r>   r?   rr   ¹  s(   € ÷ eñ e¸Cð eÐPdð eÐimñ er@   c                óÈ   € \        VR4      pV^ 8¼  dO   \        W4,          P                  R4      \        4      '       d"   V P	                  WW4,          R,          4       R# R# R# )z[Keep only the newest checkpoint among *live_messages* (inserted rows carrying ``_row_id``).rç   rK  N)r   rJ   rÒ   rS   Ú_drop_shadowed_checkpoint_rows)r€   r�   ro   rÉ  Úcarriers   &&&& r>   rÃ  Ú0SessionMessagesMixin._prune_shadowed_checkpoints¹  sQ   € ä,¨]Ð<SÓTˆØ�aŒ<œJ }Õ'=×'AÑ'AÀ)Ó'LÌc×RÒRØ×/Ñ/°À-ÕBXÐYbÕBcÖdñ S‰<r@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   ro   rK   Úcarrier_row_idrS   r;   r<   )r=   s   "r>   r?   rr   ¿  s#   € ÷ ñ ¸sð ÐTWð Ð\_ñ r@   c                óÖ  € ^ pVP                  \        W#34      P                  4        F¸  w  rV\        VRR4      p\	        V\
        4      '       d;   \        ;QJ d    R V 4       F  '       g   K   RM	  RM! R V 4       4      '       g   Kd  V Uu. uF  p\        V4      '       d   K  VNK  	  p	pVP                  \        V P                  V	4      V34       V^,          pKº  	  V# u upi )uÁ  Rewrite older active assistant rows so only the row *carrier_row_id* keeps a ``type: "compaction"``
checkpoint (durable twin of ``context_compressor.drop_shadowed_checkpoints``). Under native compaction
every assistant response re-persists a ~120 KB checkpoint the wire builder will never replay once a
newer one lands, and local compaction â€” the only other prune site â€” rarely fires (#102374).
Non-checkpoint items stay; returns rows rewritten.Nz7Ignoring malformed codex_reasoning_items on message rowc              3  ó8   "  € T F  p\        V4      x € K  	  R # 5irU   )r   )rw   Úitems   & r>   ry   ÚFSessionMessagesMixin._drop_shadowed_checkpoint_rows.<locals>.<genexpr>È  s   é € Ð5bÑ\aÐTXÔ6IÈ$×6OÐ6OÓ\aùs   ‚TF)
r}   Ú_SHADOWED_CHECKPOINT_ROWS_SQLr™  r$   rJ   rV   Úanyr   Ú_SET_CODEX_REASONING_SQLrª   )
r€   r�   ro   rÏ  Ú	rewrittenr•  rZ   ÚitemsrÒ  Úkepts
   &&&&      r>   rË  Ú3SessionMessagesMixin._drop_shadowed_checkpoint_rows¿  sÄ   € ð ˆ	ØŸ<™<Ô(EÈ
ÐGcÓd×mÑmÖo‰KˆFÜ˜S $Ð(aÓbˆEÜ˜e¤T×*Ò*·#³#Ñ5bÑ\aÓ5b·#·#²#Ñ5bÑ\aÓ5b×2bÒ2bÙÙ%*ÓL¡U˜TÔ2EÀd×2K—D�D¡UˆDÐLØ�L‰LÔ1°D×4MÑ4MÈdÓ4SÐU[Ð3\Ô]Ø˜�NŠIñ pð Ðùò Ms   ÂC&Â(C&c               ó0   € V ^8„  d   QhRRRRRRRRRRR	R
/# )r7   ro   rK   r-  r.  Úactive_onlyre   Úarchive_droppedr±   r;   rq   r<   )r=   s   "r>   r?   rr   Ï  s;   € ÷ 6ñ 6¨3ð 6Ð:Nð 6Ð]að 6Øð6ØAEð6ØRVñ6r@   c                óL   a aaaaa€ VVVVV V3R lpS P                  VS4       R# )uÜ  Atomically replace a session's messages (/retry, /undo, /compress). DESTRUCTIVE by default (rows
DELETEd, leave FTS). ``active_only`` spares soft-archived rows (needed with in-place compaction).
``archive_dropped`` SOFT-archives live rows rewind-style: what rewind/edit/regenerate must use, since
DELETE leaves nothing to recover. ``reject_active_turn_lease``: in-txn lease check for user rewrites.

Pass ``archive_dropped=True`` to SOFT-archive the live rows instead of DELETEing them: the replaced
turns stay on disk with ``active = 0``, ``compacted = 0`` â€” the same "the user took it back" marking
:meth:`rewind_to_message` applies â€” and stay readable via :meth:`get_messages` with
``include_inactive=True``. This is the mode a rewind/edit/regenerate must use: those flows overwrite
a transcript the user may not have meant to drop, and a plain DELETE also evicts the rows from the
FTS index, leaving nothing to recover from (#82756). It implies active-only handling â€”
already-archived rows are never touched â€” so ``active_only`` is redundant with it. Live rows that
*messages* keeps in place (the common in-order prefix, matched on role/content/tool identity) are
left untouched with their ids; only the divergent live suffix is archived and only the new suffix
of *messages* is inserted (#82956: archiving and re-inserting the kept prefix grew the archive by
the whole transcript on every rewind). The live view is identical either way; only the durability
of the dropped turns differs.
c           	     ó6   <€ SP                  V SSSSSR 7       R# ))rÜ  rÝ  r±   N)Úreplace_messages_in_transaction)r�   rÜ  rÝ  r-  r±   r€   ro   s   &€€€€€€r>   r#  Ú2SessionMessagesMixin.replace_messages.<locals>._doã  s&   ø€ Ø×0Ñ0Ø�j (¸ÐUdØ)Að 1ö Cr@   N©r;  )r€   ro   r-  rÜ  rÝ  r±   r#  s   ffffff r>   Úreplace_messagesÚ%SessionMessagesMixin.replace_messagesÏ  s!   ý€ ÷(	Cò 	Cð 	×&Ñ& s¨HÖ5r@   rÜ  rÝ  r±   Úexpected_active_idsc               ó4   € V ^8„  d   QhRRRRRRRRRRR	R
RR/# )r7   ro   rK   r-  r.  rÜ  re   rÝ  r±   rå  úOptional[list[int]]r;   rq   r<   )r=   s   "r>   r?   rr   é  sS   € ÷ [ñ [Àð [ÐOcð [Ø59ð[ØTXð[àBFð[ð >Qð[ð ]añ[r@   c               óT  € ^ RI Hp V'       d   V P                  WRRRR7       M<\        VP	                  \
        V34      P                  4       4      '       d	   V! V4      hVeH   VP	                  \        V34       U	u. uF  p	\        V	^ ,          4      NK  	  up	V8w  d   \        R4      h^ ;r«V'       d�   VP	                  \        V\        V4      ^,           34      P                  4       pV P                  WÃ4      p
\        R VRV
  4       4      pV
\        V4      8  d"   VP	                  RW,V
,          ^ ,          34       M TP	                  RV'       d   R	MR
 2V34       V P                  WW:R 4      w  rÞVP	                  \          R2W­,           W¾,           V34       R# u up	i )a  :meth:`replace_messages` inside the caller's write transaction (the gateway ``/retry``
rewrite commits in its re-send's own admission). ``expected_active_ids`` pins the active set
the replacement was computed from: a row committed since refuses (``RuntimeError``).)r·   NT)r±   r²   z?active transcript changed before the rewrite could be persistedc              3  óJ   "  € T F  p\        V^,          ^R7      x € K  	  R# 5i)é   ©r[   N©r^   ©rw   rj   s   & r>   ry   ÚGSessionMessagesMixin.replace_messages_in_transaction.<locals>.<genexpr>ÿ  s   é € Ð![É{È¤/°#°aµ&À×"CÑ"CË{ùó   ‚!#zNUPDATE messages SET active = 0 WHERE session_id = ? AND active = 1 AND id >= ?ú)DELETE FROM messages WHERE session_id = ?ú AND active = 1ru   r  )r¹   r·   rÂ   r#   r}   r    r~   Ú_ACTIVE_IDS_SQLrS   ÚRuntimeErrorÚ_LIVE_IDENTITY_SQLrW   r™  Ú_stamp_kept_live_prefixr:  rG  Ú_SET_COUNTERS_SQL)r€   r�   ro   r-  rÜ  rÝ  r±   rå  r·   rj   rÙ  Úkept_tool_callsÚliver  Úinserted_tool_callss   &&&&$$$$       r>   rà  Ú4SessionMessagesMixin.replace_messages_in_transactioné  s”  € õ 	Fß#Ø×/Ñ/Ø $ÀÐfjð 0õ lä" 4§<¡<´ÀÀÓ#N×#WÑ#WÓ#Y×ZÒZÙ/°
Ó;Ð;ØÒ*Ø'+§|¡|´OÀjÀ]Ô'Só0UÙ'S ”�C˜•F–Ñ'Sñ0UØXkô0läÐ`ÓaÐaØ!"Ð"ˆßð —<‘<Ô 2°ZÄÀXÃÐQRÕARÐ4SÓT×]Ñ]Ó_ˆDØ×/Ñ/°Ó?ˆDÜ!Ñ![ÈtÐTYÐUYÉ{Ó![Ó[ˆOØ”c˜$“iÔà—‘ÐmØ(¨t­*°Q­-Ð8ô:øð �L‰LÐD×ZeÑEVÐkmÐDnÐoÐr|Ðq~ÔØ(,×(AÑ(AÀ$ÐT\Ð]bÐTcÓ(dÑ%ˆØ�‰Ô)Ð*¨-Ð8Ø•o Õ'LÈjÐYö	[ùò#0Us   Á>F%c          
     ó,   € V ^8„  d   QhRRRRRRRRRR/# )	r7   rÅ   rK   r†   r   rÖ   rH   r;   rÊ   r<   )r=   s   "r>   r?   rr     s8   € ÷ @ñ @ ð @¨sð @À#ð @ÐSVð @Ð[`ñ @r@   c                ó†   € YP                  V P                  W4      4      TV'       d   \        P                  ! V4      3# R3# )uì   The (role, content, tool_call_id, tool_calls) columns a message writes â€” the identity the
kept-prefix match compares. *content* is passed through the loader's lens first so a message
read back from the DB matches the row it came from.N)r�   Ú_loaded_view_contentrL   r‹   )rŽ   rÅ   r†   rÖ   rH   s   &&&&&r>   Ú_row_identityÚ"SessionMessagesMixin._row_identity
  sI   € ð
 ×)Ñ)¨#×*BÑ*BÀ4Ó*QÓRÐT`ß*4”—
’
˜:Ó&ð@ð 	@Ø:>ð@ð 	@r@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   rø  rV   r-  r.  r;   rS   r<   )r=   s   "r>   r?   rr     s#   € ÷ ñ ¨Dð Ð<Pð ÐUXñ r@   c                óN  € ^ p\        W4       EF  w  rEVP                  RR4      pV^,          V8w  g   V^,          VP                  R4      8w  d    V# V P                  WeP                  R4      VP                  R4      \        VP                  R4      4      4      pWpP                  V^,          V P	                  V^,          4      V^,          \        V^,          4      4      8w  d    V# V^ ,          VR&   V^,          '       d   V^,          V\
        &   V^,          pEK  	  V# )zÉLength of the in-order prefix of *messages* already present as the leading live rows; each
matched message gets its existing ``_row_id`` (and ``message_uid``) stamped, as a fresh insert would set them.rÅ   r  rÖ   r†   rH   rK  )r  rÒ   rþ  rQ   r“   r   )r€   rø  r-  rÙ  rj   rÆ   rÅ   Úidentitys   &&&     r>   rõ  Ú,SessionMessagesMixin._stamp_kept_live_prefix  s  € ð ˆÜ˜D×+‰HˆCØ—7‘7˜6 9Ó-ˆDà�1�v˜Œ~  Q¥¨3¯7©7°>Ó+BÔ!BØð ˆð ×)Ñ)¨$·±¸	Ó0BÀCÇGÁGÈNÓD[Ü*;¸C¿G¹GÀLÓ<QÓ*RóTˆHà×-Ñ-¨c°!­f°d×6JÑ6JÈ3ÈqÍ6Ó6RÐTWÐXYÕTZÔ\mÐnqÐrsÕntÓ\uÓvÔvØð
 ˆð	 ! �VˆC�	‰NØ�1�vŒvØ#& q¥6�”KÑ Ø�A�I‹Dñ ,ð ˆr@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   rÅ   rK   r†   r   r;   r<   )r=   s   "r>   r?   rr   &  s!   € ÷ ñ  3ð °ð ¸ñ r@   c                ór   € V R9   d0   \        V\        4      '       d   \        V4      P                  4       # V# )zÁContent as ``_rows_to_messages`` hands it to callers: user/assistant strings are sanitized and
stripped on load, so a reloaded session's messages must be compared to rows through the same lens.>   rö   rõ   )rJ   rK   r   rv   )rÅ   r†   s   &&r>   rý  Ú)SessionMessagesMixin._loaded_view_content%  s2   € ð Ð(Ô(¬Z¸Ä×-EÒ-EÜ# GÓ,×2Ñ2Ó4Ð4Øˆr@   c               ó    € V ^8„  d   QhRRRR/# ©r7   ro   rK   r;   re   r<   )r=   s   "r>   r?   rr   -  s   € ÷ mñ m°ð m¸ñ mr@   c                ó,   € V P                  RV34      RJ# )u]  True if the session has any soft-archived (``active = 0``) rows (tests/diagnostics).

Cheap existence probe â€” does not load rows. NOTE: production rewrite paths no longer branch on this
(they pass ``active_only=True`` unconditionally â€” a probe can fail open or race a concurrent
``archive_and_compact``, #80216); kept for tests and diagnostics.
zBSELECT 1 FROM messages WHERE session_id = ? AND active = 0 LIMIT 1Nrª  ©r€   ro   s   &&r>   Úhas_archived_messagesÚ*SessionMessagesMixin.has_archived_messages-  s)   € ð �~‰~ØPÐS]ÐR_óaØhlðmð 	mr@   c               ó    € V ^8„  d   QhRRRR/# ©r7   ro   rK   r;   rS   r<   )r=   s   "r>   r?   rr   7  s   € ÷ pñ p°sð p¸sñ pr@   c                ó\   € V'       g   ^ # \        V P                  RV34      ^ ,          4      # )z›MAX(id) of the active rows (0 if none), captured at compression START: every active row above it
arrived concurrently and must survive compaction verbatim.zMSELECT COALESCE(MAX(id), 0) FROM messages WHERE session_id = ? AND active = 1)rS   r‡  r
  s   &&r>   Úget_active_message_watermarkÚ1SessionMessagesMixin.get_active_message_watermark7  s7   € ÷ ÙÜ�4—>‘>Ø[Ð^hÐ]jólØlmõoó pð 	pr@   c               ó    € V ^8„  d   QhRRRR/# )r7   r€  rK   r;   ztuple[list[int], int]r<   )r=   s   "r>   r?   rr   ?  s   € ÷ añ a°Cð aÐDYñ ar@   c                ó²   € VP                  W#4      P                  4       pV Uu. uF  p\        VR,          4      NK  	  up\        R V 4       4      3# u upi )ze``(ids, tool_call_count)`` of the concurrent-tail rows selected by *sql* (``SELECT id, tool_calls``).r–  c              3  óF   "  € T F  p\        VR ,          4      x € K  	  R# 5i)rH   Nrì  )rw   rn  s   & r>   ry   ÚBSessionMessagesMixin._tail_rows_after_watermark.<locals>.<genexpr>B  s   é € Ð0`Ñ[_ÐVW´ÀÀ<Å×1QÐ1QÓ[_ùs   ‚!)r}   r™  rS   r:  )r€   r�   r€  r  Úrowsrn  s   &&&&  r>   Ú_tail_rows_after_watermarkÚ/SessionMessagesMixin._tail_rows_after_watermark?  sJ   € à�|‰|˜CÓ(×1Ñ1Ó3ˆÙ&*Ó+¡d ”�A�d•G–¡dÑ+¬SÑ0`Ñ[_Ó0`Ó-`Ð`Ð`ùÒ+s   ¤Aro   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   Útail_idsú	list[int]ro   r–   r;   rq   r<   )r=   s   "r>   r?   rr   D  s"   € ÷ ?ñ ?°)ð ?ÈMð ?Ðeiñ ?r@   c               ó.  a€ VRJpRV'       d   RMR,           oRP                  V3R lV P                  V4       4       4      pTP                  RT RV'       d   RMR RT RV'       d   RMR R	\        V4       R
2V'       d   V.VO4       R# T4       R# )zÇPure-SQL clone of *tail_ids* as fresh live rows (new id/display order, active=1, compacted=0;
message payload columns stay byte-exact and FTS triggers index the clones), into *session_id* when given.Nz, c              3  ó8   <"  € T F  qS9  g   K  Vx € K  	  R # 5irU   r<   )rw   ÚcÚskips   & €r>   ry   Ú;SessionMessagesMixin._clone_message_rows.<locals>.<genexpr>L  s   øé € ÐZÑ(H 1ÐUYÉMŸQšQÓ(Hùs   ƒ�
zINSERT INTO messages (úsession_id, ru   zactive, compacted) SELECT z?, z 1, 0 FROM messages WHERE id IN (ú) ORDER BY id)r–  ÚactiveÚ	compactedÚdisplay_order)ro   r<   )ÚjoinÚ_message_column_namesr}   r&   )r€   r�   r  ro   ÚretargetÚcol_listr  s   &&&$  @r>   Ú_clone_message_rowsÚ(SessionMessagesMixin._clone_message_rowsD  s¦   ø€ ð  TÐ)ˆð >×T\ÁÐbdÕeˆØ—9‘9ÔZ¨×(BÑ(BÀ4Ô(HÓZÓZˆØ�‰Ø$ X J¨bÇ8±ÐQSÐ0Tð UØ�Z˜r¯8¡%¸Ð!<ð =Ü)¨(Ó3Ð4°MðC÷ (0ˆZÐ#˜(Ð#ö		?ð 6>ö		?r@   c               ó<   € V ^8„  d   QhRRRRRRRRR	R
RRRRRRRR/	# )r7   ro   rK   Úcompacted_messagesr.  Úcoveredr  Ú
tail_countrS   Úcarried_messagesru  Úpatched_model_configr   Úpatchre   Úmerged_awayzset[int]r;   r<   )r=   s   "r>   r?   rr   S  sZ   € ÷ 0ñ 0Ø #ð0Ø9Mð0ØXað0àð0à+Ið0àadð0ð ð0ð #+ð0ð 
ñ	0r@   c               óH  € VP                  \        V34      P                  4        U
u. uF  p
\        V
R,          4      NK  	  pp
\	        V4      pT P                  YT;'       g    . 4      pVP                  V4       V Uu. uF  qîV9  g   K  VNK  	  ppV Uu. uF  qîV9   g   K  VNK  	  pp\        V4      pV^ 8”  d   VV P                  VWY4      ,          pVV,          p\        \        P                  V4      4      pV'       d%   \        V4      pVP                  RV R2V.VO4       VP                  \        V34       V P                  WV4       V P                  WV4      w  ppV'       dY   V P                  VR\        V4       R2\!        V4      4      w  ppV P#                  W4       V\%        V4      ,          pVV,          pV P'                  W4       TP                  \(         V'       d   RMR R2VV.V'       d   V3MR	OVN54       V# u up
i u upi u upi )
aP  Archive *covered* as summarized and clone every other active row after the new set.

A gap below the newest held id, and rows appended after an unpersisted turn, are not
in *covered*. They take the concurrent-append path: rewind the original, insert the
compacted transcript, then clone them so they stay live and searchable once each.
r–  úNUPDATE messages SET active = 0, compacted = 0 WHERE session_id = ? AND id IN (r3   z1SELECT id, tool_calls FROM messages WHERE id IN (r"  ú, model_config = ?ru   r  r<   )r}   rò  r™  rS   ÚsetÚ_resolve_carried_row_idsÚupdaterV   Ú_tail_originalsr™   Úfromkeysr&   Ú_ARCHIVE_ACTIVE_SQLr¼  rG  r  rÊ   r*  rW   Ú_reconcile_display_ordersrö  )r€   r�   ro   r-  r.  r/  r0  r1  r2  r3  rj   Ú
active_idsÚcovered_setÚcarried_idsr•  ÚunseenÚcovered_activeÚ
rewind_idsÚplaceholdersr  rI  Ú_idsÚunseen_tool_callss   &&&&&$$$$$             r>   Ú_archive_named_rowsÚ(SessionMessagesMixin._archive_named_rowsS  s"  € ð 15·±¼_ÈzÈmÓ0\×0eÑ0eÔ0gÓhÑ0g¨”c˜#˜d�)–nÑ0gˆ
ÐhÜ˜'“lˆØ×3Ñ3°DÐFV×F\ÐF\ÐZ\Ó]ˆØ×Ñ˜;Ô'Ù'1ÓO¡z˜VÀ;Ñ5N—&�&¡zˆÐOÙ/9ÓS©z VÀ{Ñ=RŸ&˜&©zˆÐSÜ˜+Ó&ˆ
Ø˜Œ>Ø˜$×.Ñ.¨~¸zÓWÕWˆJØ�fÕˆ
Üœ$Ÿ-™-¨
Ó3Ó4ˆ
ßÜ(¨Ó4ˆLØ�L‰Lð3Ø3?°.ÀðCàÐ)˜jÐ)ô+ð 	�‰Ô(¨:¨-Ô8ð 	×%Ñ% dÐ8JÔKØ%)×%>Ñ%>¸tÐQcÓ%dÑ"ˆÐ"ßØ&*×&EÑ&EØØCÄMÐRXÓDYÐCZÐZgÐhÜ�f“ó'Ñ#ˆDÐ#ð ×$Ñ$ TÔ2Øœ˜F›Õ#ˆHØÐ 1Õ1Ðð 	×&Ñ& tÔ8Ø�‰Ü Ð!¿%Ñ"6ÀRÐ!HÈÐVØÐ'ÐaÇeÐ,@Ñ+BÐQSÐaÐV`Ñaô	cð ˆùòK iùò PùÚSs   ¨HÂHÂHÂHÂ'Hc               óD   € V ^8„  d   QhRRRRRRRRR	R
RRRRRRRRRRRR/# ©r7   ro   rK   r-  r.  Úmodel_config_patchr¡   Ú	watermarkr0  Úlock_holderr–   r/  rS   r0  ru  Úcovered_idsrç  Úunresolved_heldÚheld_for_casr;   r<   )r=   s   "r>   r?   rr   …  s|   € ÷ )]ñ )]¨cð )]ÐG[ð )]Ø4ð)]ØHUð)]à"ð)]à7:ð)]ð 9ð)]ð )ð	)]ð
 8ð)]ð 5ð)]ð ADñ)]r@   c                óX   a aaaaaaaaa	a
€ S P                  VVVV
VVV VVV	V3R lS4      # )u6
  Non-destructive in-place compaction under ONE session id: soft-archive the active rows (``active=0,
compacted=1``: summarized away, still searchable) and insert *compacted_messages* as fresh active
rows, atomically; returns the new ACTIVE count (= ``message_count``). *watermark* (compression
START): rows ``id > watermark`` arrived during the slow summary and are re-sequenced after the
compacted set by a pure-SQL clone (fresh ids); ``None`` archives everything. *covered_ids*: the
rows the compressor actually held. When proved, only those are summarized; every other active
row is cloned after the new set, so a gap below the newest held id is not archived unseen.
``None`` keeps the watermark path. *unresolved_held*: held dicts with no row id, matched inside
the transaction. *lock_holder*: verified
in-txn so a reclaimed lease fails instead of clobbering the winner. *tail_count*: the LAST N compacted
rows are the verbatim carried tail; *carried_messages* names exact durable originals carried forward
verbatim when they are not a contiguous suffix (micro-compaction's prefix + marker + suffix shape).
They are resolved inside this transaction by row id when present, else by unique durable identity +
timestamp. Those originals and the clones' originals are superseded duplicates and get rewind flags
(``active=0, compacted=0``) so search doesn't return each carried message once per compaction.
``held_for_cas``: the caller's held message dicts for the lease-less staleness gate â€” re-verified
against the live rows INSIDE this transaction (see :meth:`held_row_mismatches`), so a rewrite that
lands after the caller's precheck aborts the commit as :class:`HeldRowRewrittenError` before any
row is archived (#124102). The lease path leaves it ``None``: its lease rules out an overlapping
rewrite, and the turn's own writes are the ones a CAS here would misread.
``model_config_patch`` merges in the same txn (``None`` removes a key).

Concurrent-append safety (#75316): when *watermark* is provided (the value of
:meth:`get_active_message_watermark` captured at compression START), rows that arrived during the
slow provider summary call (``id > watermark``) are NOT summarized away. They are re-sequenced after
the compacted set by a pure-SQL column clone (every column except ``id`` â€” content, api_content,
platform_message_id, token counts, reasoning sidecars all survive byte-exact, and the FTS triggers
index the clones naturally), and the originals are archived. NOTE: re-sequencing assigns the tail
rows fresh ids; consumers that reference durable row ids re-resolve by content (see 3e8ab0610).
c                ó<   <€ SP                  V SSSSSS	SSS
SR 7      # ))rK  rL  rM  r/  r0  rN  rO  rP  )Ú_archive_and_compact_on_conn)r�   r0  r-  rN  rP  rM  rK  r€   ro   r/  rO  rL  s   &€€€€€€€€€€€r>   rÎ   Ú:SessionMessagesMixin.archive_and_compact.<locals>.<lambda>ª  s3   ø€ ¸4×;\Ñ;\Ø�*Ð0ÐEWØ¨[ÀZØ-¸;Ø+¸,ð	 <]ô <Hr@   râ  )r€   ro   r-  rK  rL  rM  r/  r0  rN  rO  rP  s   fffffffffffr>   Úarchive_and_compactÚ(SessionMessagesMixin.archive_and_compact…  s/   ÿú€ ðJ ×-Ñ-÷ /Hö /Hð J\ó	]ð 	]r@   rK  rL  rM  r/  r0  rN  rO  rP  c               óD   € V ^8„  d   QhRRRRRRRRR	R
RRRRRRRRRRRR/# rJ  r<   )r=   s   "r>   r?   rr   °  s�   € ÷ Iñ IØ$'ðIØ=QðIà 8ðIàLYðIð 'ðIð <?ðIð =ð	Ið
 -ðIð <ðIð 9ðIð EHñIr@   c               ó~  € ^ RI Hp Veo   VP                  \        V34      P	                  4       pVe9   VR,          V8w  g+   \        VR,          4      \        P                  ! 4       8:  d   V! RV: R24      hVRJpV'       d   V P                  WVRR7      MRpV'       d@   \        V R	R4      p\        V4      '       d"   V! WV4      '       d   ^ R
I
Hp V! RV R24      hV P                  WWšV4      pVe'   V P                  WVV^ ,          VW�VV^,          R7	      # Vf   . ^ 3MV P                  VRV\        V4      34      w  ppT P!                  YT;'       g    . 4      pV^ 8”  d    VRJpTTP                  RV'       d   RMR R2V.V'       d   \        V4      3MRO\        V4      V P#                  V\        V4      ) R 4      ,           N54      P%                  4        Uu. uF  p\        VR,          4      NK  	  up,          pVV,          p\'        \(        P+                  V4      4      pV'       dE   \-        V4      pVP                  RV R2V.VO4       VP                  \.         RV R2V.VO4       MVP                  \.        V34       V P1                  WV4       V P3                  WV4      w  ppV'       d.   V P5                  VV4       V\7        V4      ,          pVV,          pV P9                  W4       TP                  \:         V'       d   RMR R2VV.V'       d   V3MROVN54       V# u upi )zª:meth:`archive_and_compact` on the caller's write transaction (never commits). The owner
and the worker/canonical-mutation verbs share this one body so they cannot drift.rµ   Nri   rh   zCompression lease for z; lost before commit; refusing to publish a stale compactionÚraise)Ú
on_missingÚheld_row_mismatches)ÚHeldRowRewrittenErrorza held row of session z) was rewritten in place before the commit)r/  r0  r1  r2  r3  z^SELECT id, tool_calls FROM messages WHERE session_id = ? AND active = 1 AND id > ? ORDER BY idz;SELECT id FROM messages WHERE session_id = ? AND active = 1z AND id <= ?ru   z ORDER BY id DESC LIMIT ?r–  r5  r3   z AND id NOT IN (r6  r  r<   )r1   r¶   r}   r   r~   r:   rº   Ú_merge_model_config_jsonÚgetattrÚcallabler¹   r\  Ú_proved_coveragerG  r  rS   r8  Ú_uncounted_merged_rowsr™  rV   r™   r;  r&   r<  r¼  rG  r*  rW   r=  rö  )r€   r�   ro   r-  rK  rL  rM  r/  r0  rN  rO  rP  r¶   Úlock_rowr2  r1  Ú
mismatchesr\  Úprovedr  Útail_tool_callsrC  Úboundrj   rD  r  rI  s   &&&&$$$$$$$$               r>   rS  Ú1SessionMessagesMixin._archive_and_compact_on_conn°  s   € õ 	CØÒ"Ø—|‘|Ô$=À
¸}ÓM×VÑVÓXˆHØÒ 8¨HÕ#5¸Ô#DÌÈhÐWcÕNdÓHeÔim×irÒirÓitÔHtÙ7Ø,¨Z©NÐ:uÐvóxð xà"¨$Ð.ˆ÷ JOð  $×<Ñ<ØÐ0¸Wð  =ô  FØTXð 	÷ Ü  Ð'<¸dÓCˆJÜ˜
×#Ò#©
°4À\×(RÒ(RÝEÙ+Ø,¨Z¨LÐ8aÐbódð dà×&Ñ& t¸ÐW`ÓaˆØÒØ×+Ñ+ØÐ"4°f¸QµiÈJØ!1ÐdiØ" 1�Ið ,ó 'ð 'ð 09Ò/@ R¨¡GÀd×FeÑFeØÐrØœ˜Y›Ð(óG*Ñ!ˆ�/ð
 !%× =Ñ =ØÐ.×4Ð4°"ó!6ˆ
à˜Œ>Ø TÐ)ˆEØ°T·\±\ØM×`eÉnÐkmÐMnð o+ð +àð g·E¤ I£Ñ0¸rð gÜ�Z“ 4×#>Ñ#>Ð?QÔSVÐWaÓSbÐRbÐRcÐ?dÓ#eÕeñgó6h÷ iqÑhpÓhrð	6só tñ 6s¨cœ3˜s 4�yž>ñ 6sñ tõ tˆJð
 	�hÕˆ
Üœ$Ÿ-™-¨
Ó3Ó4ˆ
ßÜ(¨Ó4ˆLØ�L‰Lð 3Ø3?°.ÀðCØEOÐD]ÐR\ÐD]ô_à�L‰LÔ/Ð0Ð0@ÀÀÈaÐPÐS]ÐRkÐ`jÐRkÕlà�L‰LÔ,¨z¨mÔ<ð 	×%Ñ% dÐ8JÔKØ%)×%>Ñ%>¸tÐQcÓ%dÑ"ˆÐ"ßØ×$Ñ$ T¨8Ô4Øœ˜H›Õ%ˆHØ Õ/Ðð 	×&Ñ& tÔ8Ø�‰Ô)Ð*Ç5Ñ+?ÈbÐ*QÐQ^Ð_ØÐ'ÐaÇeÐ,@Ñ+BÐQSÐaÐV`Ñaô	càˆùò9ts   Ç&L:c               ó   € V ^8„  d   QhRR/# )r7   r;   ú	list[str]r<   )r=   s   "r>   r?   rr   û  s   € ÷ +ñ +¨Yñ +r@   c                ó¾   € \        V RR4      '       g;   VP                  R4      P                  4        Uu. uF  q"^,          NK  	  upV n        V P                  # u upi )z>Column names of the messages table, cached per-connection era.Ú_message_columns_cacheNzPRAGMA table_info(messages))r^  r}   r™  rk  )r€   r�   rn  s   && r>   r'  Ú*SessionMessagesMixin._message_column_namesû  sT   € ä�tÐ5°t×<Ò<Ø9=¿¹ÐFcÓ9d×9mÑ9mÔ9oÓ*pÑ9o°A¨Q¯4¨4Ñ9oÑ*pˆDÔ'Ø×*Ñ*Ð*ùò +qs   µAc               ó(   € V ^8„  d   QhRRRRRRRR/# )r7   ro   rK   r•  rS   r†   r   r;   r<   )r=   s   "r>   r?   rr     s/   € ÷ 	Añ 	A°3ð 	AÀð 	AÈcð 	AÐVYñ 	Ar@   c                óÂ   € V'       d4   \        V\        4      '       g   \        V\        4      '       d   V^ 8:  d   ^ # V P                  RV P	                  V4      W!34      # )aj  Rewrite the content of ONE known active user row. Used when a user turn was written at submit
time (before the agent ran) and the turn prologue then rewrote the prompt it persists (@-file
expansion, native image parts): the early row must show what the transcript will replay, not the
raw keystrokes, and the turn must not append a second row for the same input.z`UPDATE messages SET content = ? WHERE id = ? AND session_id = ? AND role = 'user' AND active = 1)rJ   re   rS   Ú_write_rowcountr�   )r€   ro   r•  r†   s   &&&&r>   Úset_user_message_contentÚ-SessionMessagesMixin.set_user_message_content  sW   € ÷
 œZ¨´×5Ò5¼ZÈÔPS×=TÒ=TÐX^ÐbcÔXcÙØ×#Ñ#ØnØ×!Ñ! 'Ó*¨FÐ?óAð 	Ar@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   ro   rK   r•  rS   r;   r<   )r=   s   "r>   r?   rr     s!   € ÷ 
"ñ 
"¨Sð 
"¸#ð 
"À#ñ 
"r@   c                ó¢   € V'       d4   \        V\        4      '       g   \        V\        4      '       d   V^ 8:  d   ^ # V P                  RW!34      # )uÉ  Deactivate ONE known row (id-addressed, idempotent; returns the affected row count). Used by
the queued-prompt drain: the row written at accept time sits ahead of the in-flight turn's
assistant reply, and the drain re-appends an identical row at the transcript end â€” leaving the
early row active would put two user rows before that reply and the alternation repair would
glue the two turns into one. The durable row is preserved (inactive), never deleted.z>UPDATE messages SET active = 0 WHERE id = ? AND session_id = ?)rJ   re   rS   ro  )r€   ro   r•  s   &&&r>   Údeactivate_messageÚ'SessionMessagesMixin.deactivate_message  sH   € ÷ œZ¨´×5Ò5¼ZÈÔPS×=TÒ=TÐX^ÐbcÔXcÙØ×#Ñ#ØLØÐ ó"ð 	"r@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   ro   rK   r•  rS   r;   r0  r<   )r=   s   "r>   r?   rr     s!   € ÷ ?ñ ?°ð ?¸Sð ?À]ñ ?r@   c                óL  € V'       d4   \        V\        4      '       g   \        V\        4      '       d   V^ 8:  d   R# V P                  RW!34      pVf   R# V^ ,          '       d   V# V P	                  RW!34      p\        V4      ^8X  d   \        V^ ,          ^ ,          4      # R# )a³  The active row that still carries *row_id*'s message: *row_id* itself while active, else the one
row an in-place compaction re-sequenced it into (``_clone_message_rows`` copies role, content and
timestamp byte-exact to a higher id). ``None`` when neither exists or the clone is ambiguous.
A caller holding a row id across a compaction (the queued-prompt envelope) re-resolves it here
before deactivating or rewriting the row (#123675).Nz;SELECT active FROM messages WHERE id = ? AND session_id = ?z¼SELECT c.id FROM messages c JOIN messages o ON o.id = ? WHERE c.session_id = ? AND c.active = 1 AND c.id > o.id AND c.role = o.role AND c.content IS o.content AND c.timestamp = o.timestamp)rJ   re   rS   r‡  Ú	_read_allrW   )r€   ro   r•  ÚoriginÚcloness   &&&  r>   Úresolve_active_row_idÚ*SessionMessagesMixin.resolve_active_row_id  s˜   € ÷ œZ¨´×5Ò5¼ZÈÔPS×=TÒ=TÐX^ÐbcÔXcÙØ—‘Ð ]Ð`fÐ_sÓtˆØŠ>ÙØ�!�9Œ9ØˆMØ—‘ðGð Ð ó	"ˆô
 %(¨£K°1Ô$4Œs�6˜!•9˜Q•<Ó Ð>¸$Ð>r@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   ro   rK   r×   r;   rS   r<   )r=   s   "r>   r?   rr   ,  s#   € ÷ ;ñ ;¸cð ;ÐQTð ;ÐY\ñ ;r@   c                ó`   € V'       d	   V'       g   ^ # V P                  RV\        V4      34      # )uæ  Deactivate every live row of one ``display_kind`` (idempotent; returns the affected row count).
The durable counterpart to the in-memory strip a self-replacing pivot performs: the in-memory path
drops the prior entry so N pivots leave one, but a durable append has no such step, so every switch
left another active row and all of them replayed on resume. Rows are preserved (inactive), never
deleted â€” the same contract as :meth:`deactivate_message`, keyed by class instead of by id.
zWUPDATE messages SET active = 0 WHERE session_id = ? AND display_kind = ? AND active = 1)ro  rb   )r€   ro   r×   s   &&&r>   Ú#deactivate_messages_by_display_kindÚ8SessionMessagesMixin.deactivate_messages_by_display_kind,  s0   € ÷ §ÙØ×#Ñ#ØeØÔ*¨<Ó8Ð9ó;ð 	;r@   c               ó   € V ^8„  d   QhRR/# )r7   r;   útuple[Any, ...]r<   )r=   s   "r>   r?   rr   9  s   € ÷ uñ u¨/ñ ur@   c                óŠ  € VR,          pVR,          R8X  dq   \        RRRV P                  VR,          4      RVR,          RV P                  VR,          4      /4      w  r4Ve%   Ve!   V P                  VP	                  R4      4      p\        V4      ;'       g+    VR,          W!R,          VR,          VR,          VR	,          3# )
zZHistorical display identity, including normalized live content from user handoff carriers.r†   rÅ   rö   r×   r4   rÕ   rÖ   rH   rÔ   )r   r“   r¦   r�   rÒ   r,   )r€   rj   Údedupe_contentÚhandoffÚ	live_views   &&   r>   rê   Ú(SessionMessagesMixin._display_dedupe_key9  sÐ   € à˜Y�ˆØˆv�;˜&Ô Ü!;Ø˜ 	¨4×+?Ñ+?ÀÀIÅÓ+OØ  NÕ 3Ø" D×$AÑ$AÀ#ÐFXÕBYÓ$Zð=\ó "]ÑˆGð Ò" yÒ'<Ø!%×!5Ñ!5°i·m±mÀIÓ6NÓ!O�Ü Ó$÷ uð uØ��K˜¨[Õ)9¸3¸~Õ;NÐPSÐT`ÕPaÐcfÐgrÕcsð)uð 	ur@   c               ó   € V ^8„  d   QhRR/# )r7   r;   re   r<   )r=   s   "r>   r?   rr   F  s   € ÷ yñ y¨ñ yr@   c                ó~   € \        V P                  VR,          4      ;'       g    / P                  \        4      4      # )z+Python twin of :data:`DISPLAY_VISIBLE_SQL`.r4   )re   r¦   rÒ   r
   )r€   rj   s   &&r>   Ú_is_model_only_rowÚ'SessionMessagesMixin._is_model_only_rowF  s2   € ä�T×2Ñ2°3Ð7IÕ3JÓK×QÐQÈr×VÑVÔWvÓwÓxÐxr@   c               ó    € V ^8„  d   QhRRRR/# )r7   rÓ   r‚  r;   rŠ   r<   )r=   s   "r>   r?   rr   K  s   € ÷ Sñ S˜ð S°5ñ Sr@   c                ó|   € \         P                  ! \        V 4      P                  RR4      4      P	                  4       # )zCFixed-width durable identity for indexed display-generation lookup.zutf-8Úsurrogatepass)ÚhashlibÚsha256ÚreprÚencodeÚdigest©rÓ   s   &r>   ré   Ú&SessionMessagesMixin._display_identityJ  s-   € ô �~Š~œd 3›i×.Ñ.¨w¸ÓHÓI×PÑPÓRÐRr@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   rÓ   r   Úfirst_by_uidr™   r;   r<   )r=   s   "r>   r?   rr   P  s!   € ÷ ñ ¨ð ¸4ð ÀCñ r@   c                óŽ   € V R,          pV'       g   V# V R,          '       d   VP                  W14      # VP                  W14       V# )a	  The display identity both display reads give *row*, scanning rows in ``id`` order.

A compaction carrier keeps its message's ``message_uid`` but not its content (the prior tail
wrapped around the summary), so it is a copy of the first row with that uid: shown once.r  rã   )rÒ   r¶  )rj   rÓ   r—  rü   s   &&& r>   Ú_fold_summary_carrierÚ*SessionMessagesMixin._fold_summary_carrierO  sF   € ð �-Õ ˆßØˆJØÐ$×%Ô%Ø×#Ñ# CÓ-Ð-Ø×Ñ Ô)Øˆ
r@   Údrop_removedc               ó   € V ^8„  d   QhRR/# )r7   r›  re   r<   )r=   s   "r>   r?   rr   ]  s   € ÷ 4+ñ 4+Àñ 4+r@   c               ó¼  a€ / p/ p/ p/ o/ p\        4       pV'       dh   \        V\        4      '       d   TM
\        V4      pV Uu0 uF-  qˆR,          '       d   K  VR,          '       d   K$  VR,          kK/  	  pp\        V4      pV EF®  pRRr©V'       dê   VR,          '       * ;'       d    VR,          '       * pV'       d
   VR,          MRpV'       d   RMV P	                  V4      p
T;'       g.    V
e(   V'       g   VR,          V9   d   V P                  V
4      MRp. V'       d   V3MROR VR,          .\        VR,          4      O 4       Op	SP                  \        P                  W›4      4       V'       d   Kø  V P                  V4      '       d   EK  T
;'       g    V P	                  V4      p
V P                  WŠV4      p
VP                  V
4      pVe(   VR,          VR	,          3VR,          VR	,          38”  d	   WƒV
&   W–V
&   \        VP                  W¨R	,          4      VR	,          4      WJ&   EK±  	  \        W4P                  R
7      pV'       d`   V U
u. uFS  p
\         ;QJ d%    V3R lWj,           4       F  '       g   K   RM	  RM! V3R lWj,           4       4      '       d   KQ  V
NKU  	  pp
V U
u. uF  q£V
,          NK  	  up
# u upi u up
i u up
i )a¡  Collapse compaction generations so each logical message appears once (the protected tail is copied
into each generation: same role/content/timestamp, different ``active``/id); prefer the live row, then
the newest. The ONE definition every display projection shares. *rows* must be ordered by ``id``.

*drop_removed*: *rows* also hold the removed rows (``active=0, compacted=0``: rewound, or replaced by a
newer copy) and ``compacted``. A message whose newest copy is removed is not shown, so a rewind in a
compression child never brings back the parent's copy. Copies share the display key or ``message_uid``
(a micro-compaction merge also stands for the rows it absorbed).r#  r$  rÕ   NÚdisplay_identityc              3  ó<   "  € T F  q'       g   K  R V3x € K  	  R# 5i)rü   Nr<   )rw   rü   s   & r>   ry   ÚCSessionMessagesMixin._dedupe_display_generations.<locals>.<genexpr>|  s   é € ð <sÙ,j SÓnq”L�U˜C•LÓ,jùs   ‚	�r  Úabsorbed_message_uidsr–  r”  c              3  ó6   <"  € T F  pSV,          x € K  	  R # 5irU   r<   )rw   ÚcopyÚremoveds   & €r>   ry   r   �  s   øé € Ð2]É_ÀT°7¸4·=²=Ë_ùs   ƒTFr<   )r7  rJ   rV   re   rê   ré   r0   r9  r™   r;  rŠ  r™  rÒ   ÚminÚsortedÚ__getitem__rÕ  )r€   r  r›  r”  Úfirst_idÚuid_keyÚ
identitiesÚ
removed_tsrj   ÚcopiesrÓ   ÚgoneÚstoredÚhashedrÆ  Úkeysr¤  s   &&$             @r>   Ú_dedupe_display_generationsÚ0SessionMessagesMixin._dedupe_display_generations]  s  ø€ ð ,.ˆØ/1ˆØ.0ˆØ#%ˆØ79ˆ
Ü"›uˆ
ßÜ% d¬D×1Ò1‘4´t¸D³zˆDñ 7;Ói±d¨sÀhÇ-Á-Ô*ÐX[Ð\g×XhÑXhÔ*˜#˜k×*Ð*±dˆJÐiÜ 
Ó+ˆLÜˆCØ �Cßà˜x�=Ô(×AÐA°°[Õ1AÔ-A�ß48˜Ð/Ö0¸d�ß$‘d¨$×*BÑ*BÀ3Ó*G�Ø÷ Gð GÀ3Â?ß˜C Õ,°
Ô:ð %)×$:Ñ$:¸3Ô$?ØAEð ðt¯˜V™I°Rð tñ <sØ-0°Õ-?Ð,jÄ)ÈCÐPgÕLhÓBiÑ,jó<sð t�à—‘œtŸ}™}¨VÓ:Ô;ßÙØ×&Ñ& s×+Ò+ÚØ×6Ð6˜×1Ñ1°#Ó6ˆCð ×,Ñ,¨S°wÓ?ˆCØ—(‘(˜3“-ˆCØŠ{˜s 8�}¨c°$­iÐ8¸CÀ½MÈ3ÈtÍ9Ð;UÔUØ�S‘	Ø"(˜3‘Ü §¡¨S°dµ)Ó <¸cÀ$½iÓHˆHŒMñ1 ô6 �d× 4Ñ 4Ô5ˆßÙ#'Ó^¡4˜C¯s«sÔ2]ÈZÎ_Ó2]¯s¯sªsÔ2]ÈZÎ_Ó2]×/]—C�C¡4ˆDÐ^Ù%)Ó*¡T˜c�S—	�	¡TÑ*Ð*ùòA jùò> _ùÚ*s0   ÁKÁKÁ+KÉKÉ,KÊ	!KÊ/KÊ;Kc               ó    € V ^8„  d   QhRRRR/# ©r7   ro   rK   r;   rq   r<   )r=   s   "r>   r?   rr   “  s   € ÷ $eñ $e¸#ð $eÀ$ñ $er@   c           	     óØ  € / p/ p^ p VP                  RW%34      pTp. pV F›  p	V	R,          pV P                  W�P                  V P                  V	4      4      V4      p
V	R,          V
8X  d
   V	R,          MRpTP	                  Y«f   TMT4      pWÉR,          8w  g   W©R,          8w  g   Kˆ  VP                  WÊV34       K�  	  VP                  4        WW8X  d   R# VP                  RV4       Kå  )a8  Re-fold split display generations by the recomputed display key.

A compaction generation that computed the display identity differently (or cloned
without inheriting it) leaves one logical message in two ``display_order`` groups,
and ``GROUP BY display_order`` then projects it twice (#122167). Folding by the same
recomputed key :meth:`_dedupe_display_generations` uses keeps every display projection
on one definition of a logical message; the live copy wins its group via the read
path's ``ORDER BY candidate.active DESC, candidate.id DESC``. Writes only on drift.a5  SELECT id, role, content, timestamp, tool_call_id, tool_calls, tool_name, display_kind, display_metadata, display_order, display_identity, message_uid, _compressed_summary FROM messages INDEXED BY idx_messages_session_id WHERE session_id = ? AND id > ? AND (active = 1 OR compacted = 1) ORDER BY id LIMIT 1000r–  rž  r%  NzHUPDATE messages SET display_order = ?, display_identity = ? WHERE id = ?)r}   r™  ré   rê   r¶  r|  ÚcloseÚexecutemany)r€   r�   ro   r¨  Úuid_identityÚlast_idr  Úbatch_startÚupdatesrj   r  rÙ  Úorders   &&&          r>   r=  Ú.SessionMessagesMixin._reconcile_display_orders“  s   € ð &(ˆØ)+ˆØˆØØ—<‘<ð)ð
 Ð%ó'ˆDð "ˆKØˆGÛ�Ø˜d�)�Ø×5Ñ5Ø×/Ñ/°×0HÑ0HÈÓ0MÓNÐP\ó^�ð 03Ð3EÕ/FÈ(Ô/R�s˜?Ö+ÐX\�Ø ×+Ñ+¨HÂ±gÐSWÓX�Ø Õ0Ô0°HÐDVÕ@WÖ4WØ—N‘N E°WÐ#=Ö>ñ ð �J‰JŒLØÔ%ÙØ×ÑØZÐ\cöer@   c               ó    € V ^8„  d   QhRRRR/# r  r<   )r=   s   "r>   r?   rr   ¹  s   € ÷ .ñ .°ð .¸ñ .r@   c                óZ  a a€ S P                  4       ;_uu_ 4       p\        S P                  V4      4      pRRR4       RR0X8:  g   R# S P                  \        S34      f   R# \        S RR4      '       d   R# V V3R lp\        S P                  V4      4      #   + '       g   i     Ll; i)zTBackfill one legacy session once, preserving the pre-index display identity exactly.Nr%  rž  FTÚ	read_onlyc                ó‚   <€ V P                  \        S34      P                  4       pVf   R# SP                  V S4       R# )NT)r}   Ú_DISPLAY_INDEX_MISSING_SQLr~   r=  )r�   Úmissingr€   ro   s   & €€r>   r#  Ú7SessionMessagesMixin._ensure_display_order.<locals>._doÄ  s:   ø€ Ø—l‘lÔ#=À
¸}ÓM×VÑVÓXˆGØŠÙØ×*Ñ*¨4°Ô<Ùr@   )Ú	_read_ctxr7  r'  r‡  rÂ  r^  re   r(  )r€   ro   r�   Úcolumnsr#  s   ff   r>   Ú_ensure_display_orderÚ*SessionMessagesMixin._ensure_display_order¹  sŒ   ù€ à�^‰^×Ô Ü˜$×4Ñ4°TÓ:Ó;ˆG÷ àÐ!3Ð4¸Ô?ÙØ�>‰>Ô4°z°mÓDÒLÙÜ�4˜ e×,Ò,Ùö	ô �D×'Ñ'¨Ó,Ó-Ð-÷! ×ús   ›BÂB*	c               ó0   € V ^8„  d   QhRRRRRRRRRR	R
R/# )r7   ro   rK   Úactive_clauseÚlimitr0  r   rS   Úlatestre   r;   ú	list[Any]r<   )r=   s   "r>   r?   rr   Í  s:   € ÷ (-ñ (-¨sð (-Àcð (-ÐR_ð (-Ðilð (-Ø%)ð(-Ø.7ñ(-r@   c               óÂ  € / pV P                  4       ;_uu_ 4       pVP                  R4        VP                  RR4      P                  4       RJpV'       d   RMRp	VP                  RV	 RV R2V34      p
V
 Fo  pV P                  V4      '       d   K  V P	                  V P                  V4      4      pVP                  V4      pVR	,          VR
,          3pVe	   Wí8”  g   Kk  WæV&   Kq  	  V
P                  4        \        V4      pV'       d   VRRR1,          VR RV RRR1,          MWôR RV pV Uu. uF  qÆV,          ^,          NK  	  pp/ p\        ^ \        V4      R4       F[  pVVVR,            pTP                  VP                  RV R\        V4       R2V.VO54       Uu/ uF  q»R
,          VbK  	  up4       K]  	  V Uu. uF  pVV9   g   K  VV,          NK  	  upVP                  '       d   VP                  R4       uuRRR4       # u upi u upi u upi   TP                  '       d   TP                  R4       i i ; i  + '       g   i     R# ; i)zNProject a legacy read-only display page without retaining transcript payloads.ÚBEGINz=SELECT 1 FROM sqlite_master WHERE type = 'index' AND name = ?Nz"INDEXED BY idx_messages_session_idzNOT INDEXEDzSELECT id, role, content, timestamp, tool_call_id, tool_calls, tool_name, active, display_kind, display_metadata FROM messages ú WHERE session_id = ?ú ORDER BY id ASCr#  r–  é„  ú+SELECT * FROM messages WHERE session_id = ?z AND id IN (r3   ÚROLLBACK)Úidx_messages_session_idéÿÿÿÿ)rÅ  r}   r~   rŠ  ré   rê   rÒ   r¶  rV   r£   rW   r9  r&   Úin_transaction)r€   ro   rÊ  rË  r   rÌ  Úrepresentativesr�   Úhas_session_indexÚ
index_hintr  rj   r  ÚcurrentÚ	candidaterª  Úselected_idsÚselectedr5  Úchunkr•  s   &&$$$$               r>   Ú_legacy_display_pageÚ)SessionMessagesMixin._legacy_display_pageÍ  sƒ  € ð 9;ˆØ�^‰^×Ô Ø�L‰L˜Ô!ð"-Ø$(§L¡LØSØ0ó%÷ ‘(“* Dð%)Ð!÷ FWÑAÐ\i�
Ø—|‘|ðDØDNÀ<ð P+Ø+8¨/Ð9IðKð  �Mó	#�ó
  �CØ×.Ñ.¨s×3Ò3Ù Ø#×5Ñ5°d×6NÑ6NÈsÓ6SÓT�HØ-×1Ñ1°(Ó;�GØ!$ X¥°°Dµ	Ð :�IØ’¨)Ö*=Ø4=¨Ó1ñ  ð —
‘
”ä! /Ó2�
ßHN˜Z©¨"¨Õ-¨f¨gÐ6°v¸Ð>¹tÀ¸tÖDÐT^Ð_fÐTgÐhnÐinÐTo�
ÙMWÓXÉZÀ°Õ 9¸!× <Ð <ÉZ�ÐXØ�Ü" 1¤c¨,Ó&7¸Ö=�EØ(¨¨u°s­{Ð;�EØ—O‘O¸t¿|¹|ØEÀmÀ_ð U&Ü&3°EÓ&:Ð%;¸1ð>à#Ð, eÑ,ô@.ó %/ñ @.¸¨¥Y°¢^ñ @.ñ %/ö 0ñ >ñ 8DÓZ±|¨VÀvÐQYÑGYÔ(˜ ×(Ð(±|ÑZà×&×&Ð&Ø—L‘L Ô,÷I Òùò2  Yùò%/ùò [øà×&×&Ð&Ø—L‘L Õ,ð 'ú÷G ×Ðús[   ›I®B0H$Ã#AH$Ä5HÅAH$Æ)HÆ;H$Ç
	HÇHÇ%H$Ç'#IÈH$È$&I
É
IÉI	c               ó$   € V ^8„  d   QhRRRRRR/# )r7   Úwarn_contextrK   Úsummary_flagre   r;   rÇ   r<   )r=   s   "r>   r?   rr   ÷  s"   € ÷ ñ ¸ð ÈDð ÐUcñ r@   c               ó`  € \        V4      pVP                  RR4       VP                  RR4       V'       d   VP                  R^ 4      '       d   RVR&   V P                  VR,          4      VR&   VP                  R4      '       d   \	        VR,          . RV R	24      VR&   VP                  R
4      e   V P                  VR
,          4      VR
&   \        VP                  4       4       FA  w  rVV\        9  g   K  \        V\        \        34      '       g   K0  VP                  V4       KC  	  V# )z~``dict(row)`` with content/tool_calls/display_metadata decoded; *summary_flag* keeps
``_compressed_summary`` only as ``True``.rž  Nr%  rã   Tr†   rH   z$Failed to deserialize tool_calls in z, falling back to []r4   )r™   rU  r“   rÒ   r$   r¦   rV   rØ  Ú_MESSAGE_SCHEMA_KEYSrJ   rŠ   Ú	bytearray)r€   rj   rã  rä  rÆ   rÓ   r8   s   &&$$   r>   Ú_row_to_message_dictÚ)SessionMessagesMixin._row_to_message_dict÷  s  € ô �3‹iˆØ�‰Ð" DÔ)Ø�‰� Ô&ß˜CŸG™GÐ$9¸1×=Ò=Ø)-ˆCÐ%Ñ&Ø×-Ñ-¨c°)­nÓ=ˆˆI‰Ø�7‰7�<× Ò Ü (Ø�LÕ! 2Ð)MÈlÈ^Ð[oÐ'pó!rˆC�Ñà�7‰7Ð%Ó&Ò2Ø&*×&CÑ&CÀCÐHZÕD[Ó&\ˆCÐ"Ñ#ô ˜sŸy™y›{Ö+‰JˆCØÔ.Ö.´:¸eÄeÌYÐEW×3XÔ3XØ—‘˜–ñ ,ð ˆ
r@   rË  rÌ  c               ó(   € V ^8„  d   QhRRRRRRRR/# )	r7   ro   rK   rË  r0  r   rS   rÌ  re   r<   )r=   s   "r>   r?   rr     s*   € ÷ ñ °#ð Àð Ø(+ðØ9=ñr@   c          	     óŽ   € V'       d   RMRpT P                  R\         RV R\         R2Yf   RMTW134      P                  4       # )zOOne display-history projection for normal reads and transactional verification.ÚDESCÚASCzŽWITH page AS (
                   SELECT display_order FROM messages
                   WHERE session_id = ? AND (active = 1 OR compacted = 1)zB
                   GROUP BY display_order ORDER BY display_order a›  
                   LIMIT ? OFFSET ?
               )
               SELECT chosen.* FROM page
               JOIN messages AS chosen ON chosen.id = (
                   SELECT candidate.id FROM messages AS candidate
                   WHERE candidate.session_id = ?
                     AND candidate.display_order = page.display_order
                     AND (candidate.active = 1 OR candidate.compacted = 1)z�
                   ORDER BY candidate.active DESC, candidate.id DESC LIMIT 1
               )
               ORDER BY page.display_order ASCrÖ  )r}   ÚDISPLAY_VISIBLE_SQLr™  )r�   ro   rË  r   rÌ  Ú	directions   &&$$$ r>   Ú_display_rows_from_connÚ,SessionMessagesMixin._display_rows_from_conn  sk   € ÷ %‘F¨%ˆ	Ø�|‰|ðJäJ]ÐI^ð _BØBKÀð MKô L_ÐJ_ð `/ð2ð š}™°%¸ÐLó!
÷" ‰(‹*ð#	r@   c               ó    € V ^8„  d   QhRRRR/# r  r<   )r=   s   "r>   r?   rr   '  s   € ÷ ñ °ð ¸ñ r@   c                óh   € V P                  R\         \         R2V34      p\        V^ ,          4      # )zðRows a display read of this segment paints: one per ``display_order`` group of
``_display_rows_from_conn``'s set. Unindexed legacy rows count as one group (the read
backfills them), so the count is zero exactly when the read paints nothing.zVSELECT COUNT(*) FROM (SELECT DISTINCT display_order FROM messages WHERE session_id = ?r3   )r‡  r˜  rî  rS   r«  s   && r>   Údisplay_message_countÚ*SessionMessagesMixin.display_message_count'  sC   € ð �n‰nð$Ü$:Ð#;Ô<OÐ;PÐPQðSØU_ÐTaócˆô �3�q•6‹{Ðr@   c               ó    € V ^8„  d   QhRRRR/# )r7   ro   rK   r;   ru  r<   )r=   s   "r>   r?   rr   0  s   € ÷ 	
ñ 	
¸Cð 	
ÐDbñ 	
r@   c           	     ó  € VP                  RV34      P                  4       f   R# VP                  \        V34      P                  4       '       d   R# V P                  W4       Uu. uF  pV P	                  VRRR7      NK  	  up# u upi )zNExact display snapshot on an already-held transaction; None means fail closed.z+SELECT 1 FROM sessions WHERE id = ? LIMIT 1Nzverified deleteT©rã  rä  )r}   r~   rÂ  rð  rè  )r€   r�   ro   rj   s   &&& r>   Ú_display_messages_from_connÚ0SessionMessagesMixin._display_messages_from_conn0  sˆ   € à�<‰<ÐEÈ
À}ÓU×^Ñ^Ó`ÒhÙØ�<‰<Ô2°Z°MÓB×KÑK×MÒMÙð ×3Ñ3°DÔEó
áE�ð ×%Ñ% cÐ8IÐX\Ð%Ö]ÙEñ
ð 	
ùò 
s   Á&Bc               ó$   € V ^8„  d   QhRRRRRR/# )r7   Úinclude_inactivere   Úinclude_compactedr;   rK   r<   )r=   s   "r>   r?   rr   <  s&   € ÷ pñ p¨ð pÀ$ð pÈ3ñ pr@   c                ó6   € V '       d   R# V'       d   \         # R# )zcAudit: every row; display: active plus compaction-archived (never Undo/Rewind rows); default: live.ru   rñ  )r˜  )rü  rý  s   &&r>   Ú_active_clauseÚ#SessionMessagesMixin._active_clause;  s   € ÷ &ˆrÐo×FWÔ,BÐoÐ]nÐor@   c               ó<   € V ^8„  d   QhRRRRRRRRRR	R
RRRRRRR/	# )r7   ro   rK   rü  re   rý  rË  r0  r   rS   rÌ  Úafter_idÚinclude_ancestorsr;   r.  r<   )r=   s   "r>   r?   rr   @  s]   € ÷ 2pñ 2p sð 2p¸dð 2pÐ_cð 2pØ)ð2pØ:=ð2pØKOð2pà,ð2pàHLð2pàYmñ2pr@   c	           	     óX  a aaaa€ Ve   S'       g	   S'       d   \        R4      hVe   V'       d   \        R4      hVe   V'       d   \        R4      hS P                  W#4      p	V'       d   S P                  S4      MS.p
\        V
4      ^8”  d˜   T;'       d    V'       * pS P	                  R\        V
4       R2V	,           R,           V
4      pS P                  V'       d   S P                  WÊ4      MTVR7      pS'       d   VRRR1,          SR RS RRR1,          MVSR RS pMÜV'       d:   V'       g2   S P                  S4      '       d   S P                  VVVV V3R	 l4      pM›V'       d   S P                  SV	SSSR
7      pM|RT	 Ve   RMR RS'       d   RMR 2pVf   S.MSV.pSf	   S'       d#   VR,          pTP                  Sf   RMSS.4       S P	                  WÞ4      pS'       d   VP                  4        V Uu. uF  pS P                  VRRR7      NK  	  up# u upi )u:  Load messages in insertion order (id, never timestamp: clocks regress). ``include_inactive``:
rewind rows too; ``include_compacted``: compaction-archived display history (not rewind rows).
``latest`` pages back from the newest but returns chronological order; ``after_id``: keyset paging.
``include_ancestors``: also load the compression lineage (root â†’ tip, branch sessions exempt),
mirroring ``get_messages_as_conversation(include_ancestors=True)`` â€” after a compression rotation
the full transcript spans parent sessions, not just the child continuation (#51058).Nz2after_id is incompatible with latest/offset pagingzYafter_id is incompatible with include_compacted (deduped display reads use offset paging)zXafter_id is incompatible with include_ancestors (merged-lineage reads use offset paging)z,SELECT * FROM messages WHERE session_id IN (r3   rÑ  ©r›  c                ó0   <€ SP                  V SSSSR 7      # ))rË  r   rÌ  )rð  )r�   rÌ  rË  r   r€   ro   s   &€€€€€r>   rÎ   Ú3SessionMessagesMixin.get_messages.<locals>.<lambda>`  s!   ø€ ˜T×9Ñ9Ø˜*¨E¸&Èð :ô Qr@   )rÊ  rË  r   rÌ  rÓ  z AND id > ?ru   z ORDER BY id rì  rí  z LIMIT ? OFFSET ?Úget_messagesTrø  rÖ  )rŒ   rÿ  r�  rW   rx  r&   r±  Ú_with_removed_identitiesrÇ  Ú_read_retrying_ioerrrà  ÚextendÚreverserè  )r€   ro   rü  rý  rË  r   rÌ  r  r  rÊ  Úsession_idsÚdisplayr  r€  r  rj   s   ff&&fff&&       r>   r  Ú!SessionMessagesMixin.get_messages@  s7  ü€ ð Ò§V¯vÜÐQÓRÐRØÒ×$5ÜÐxÓyÐyØÒ×$5ÜÐwÓxÐxØ×+Ñ+Ð,<ÓPˆç>O�d×.Ñ.¨zÔ:ÐV`ÐUaˆÜˆ{Ó˜aÔð
 (×@Ð@Ð0@Ô,@ˆGØ—>‘>Ð$PÔQ^Ð_jÓQkÐPlÐlmÐ"nØ$1õ#2Ø4Fõ#GØHSóUˆDà×3Ñ3ßDK�×-Ñ-¨dÔ@ÐQUÐdkð 4ó mˆDç8>�4™˜"˜•:˜f˜gÐ& v¨Ð.©t°¨tÖ4ÀDÈÈÀMÐRXÐSXÐDY‰Dß×'7¸D×<VÑ<VÐWa×<bÒ<bà×,Ñ,÷Qð QóR‰D÷ ð ×,Ñ,Ø¨-¸uÈVÐ\bð -ó d‰Dð AÀÀØ$,Ò$8‘=¸bÐAÀ×Y_ÉvÐejÐNkðmˆCà+3Ò+;˜J™<À*ÈhÐAWˆFØÒ §FàÐ*Õ*�Ø—‘ U¢]™r¸¸vÐFÔGØ—>‘> #Ó.ˆDßØ—‘”ÙjnÓoÑjnÐcf�×)Ñ)¨#¸NÐY]Ð)Ö^ÑjnÑoÐoùÒos   ÈH'c               ó    € V ^8„  d   QhRRRR/# )r7   r  ri  r;   r.  r<   )r=   s   "r>   r?   rr   t  s   € ÷ 	ñ 	°	ð 	Ð>Rñ 	r@   c           
     ó&  a€ V Uu. uF  q"'       g   K  VNK  	  upoV3R l\        ^ \        S4      R4       4       pV UUu. uF<  q@P                  R\        V4       R2V4       F  pRV^ ,          RV^,          /NK  	  K>  	  upp# u upi u uppi )ziTool results containing ``/pull/``: a deliberately loose scan, oldest-first so the caller takes the last.c              3  ó:   <"  € T F  pSWR ,            x € K  	  R# 5i)rÒ  Nr<   )rw   r5  rŒ  s   & €r>   ry   Ú<SessionMessagesMixin.find_pr_url_messages.<locals>.<genexpr>w  s   øé € ÐPÑ8O¨u�#�e c�kÕ*Ó8Oùs   ƒrÒ  zRSELECT session_id, content FROM messages
                    WHERE session_id IN (zi)
                      AND role = 'tool' AND content LIKE '%/pull/%'
                    ORDER BY id ASCro   r†   )r£   rW   rx  r&   )r€   r  ÚsÚchunksrß  rj   rŒ  s   &&    @r>   Úfind_pr_url_messagesÚ)SessionMessagesMixin.find_pr_url_messagest  s    ø€ á%Ó+™+�Q«�qˆq™+Ñ+ˆÜP¼¸aÄÀSÃÈ3Ô8OÓPˆÙGMô Áv¸e×YgÑYgð*Ü*7¸Ó*>Ð)?ð @$ð'ð öZÐRU�˜s 1�v y°#°aµ&Ó9ñ ZÑ9Ávò ð 	ùò ,ùós   †	B”BÁABc               ó(   € V ^8„  d   QhRRRRRRRR/# )r7   ro   rK   Úaround_message_idrS   Úwindowr;   rÇ   r<   )r=   s   "r>   r?   rr     s0   € ÷ {ñ {¨cð {Àcð {ÐSVð {Ð_mñ {r@   c           	     óJ  € \        V^ 4      pV P                  4       ;_uu_ 4       pVP                  RW!34      P                  4       '       g   R. R^ R^ /uuRRR4       # VP                  RWV^,           34      P	                  4       pVP                  RWV34      P	                  4       pRRR4       . \        X4      OXO Uu. uF  pV P                  VRR	R
7      NK  	  ppRVR\        ^ \        V4      ^,
          4      R\        V4      /#   + '       g   i     Lk; iu upi )zÆUp to *window* messages either side of an anchor id (ascending). ``messages_before``/``_after`` count
strictly around the anchor (fewer than *window* = session boundary). Empty for a foreign anchor.z>SELECT 1 FROM messages WHERE id = ? AND session_id = ? LIMIT 1r  Úmessages_beforeÚmessages_afterNzPSELECT * FROM messages WHERE session_id = ? AND id <= ? ORDER BY id DESC LIMIT ?zNSELECT * FROM messages WHERE session_id = ? AND id > ? ORDER BY id ASC LIMIT ?Úget_messages_aroundFrø  )r¾   rÅ  r}   r~   r™  Úreversedrè  rW   )	r€   ro   r  r  r�   Úbefore_rowsÚ
after_rowsrn  Úwindow_msgss	   &&&&     r>   r  Ú(SessionMessagesMixin.get_messages_around  s4  € ô �V˜Q“ˆØ�^‰^×Ô Ø—<‘<Ð `Ø!2Ð ?óAßAIÁÃöLà  "Ð&7¸Ð<LÈaÐP÷ Òð Ÿ,™,ØbØ°¸µ
Ð;ó=ç=E¹X»Zð ð Ÿ™Ø`Ø°Ð7ó9ç9A¹»ð ÷ ð !F¤(¨;Ó"7Ð E¸*Ñ EóGÙ E˜1ð ×0Ñ0°ÐAVÐejÐ0ÖkÙ Eð 	ð Gð ˜+Ð'8¼#¸aÄÀ[ÓAQÐTUÕAUÓ:VÐXhÔjmÐnxÓjyÐzÐz÷ ×üòGs   ¥/DÁADÃD ÄD	c               ó    € V ^8„  d   QhRRRR/# ©r7   ro   rK   r;   r<   )r=   s   "r>   r?   rr   ’  s   € ÷ '<ñ '<°Cð '<¸Cñ '<r@   c                óÚ  € V'       g   V#  V P                  V4      ;'       g    TpV P                  4       ;_uu_ 4       pTpV0pRp\        ^ 4       FÀ  p VP	                  RV34      P                  4       e   TpVP	                  R\        RR4       R\        RR4       R\        RR4       R	\        R
\        4       R2	V34      P                  4       pTe   TR,          '       d   TR,          T9   d    MTR,          pTP                  T4       KÂ  	  Ve   TMTuuRRR4       #   \         d     ELi ; i  \         d    Tu u uuRRR4       # i ; i  + '       g   i     R# ; i)uÀ  Redirect a resume target to the descendant holding the messages: follow the compression chain to
the live tip (lineage-aware, so delegate/branch children never hijack it), then walk
``parent_session_id`` forward to the DEEPEST node with messages (a continuation may hold newer
turns), skipping branch/delegate/reset/tool children. Unchanged when nothing has messages; depth cap 32.

Context compression ends the current session and forks a new child session (linked via
``parent_session_id``). The flush cursor is reset, so the child is where new messages actually land
â€” the parent ends up with ``message_count = 0`` rows unless messages had already been flushed to it
before compression. See #15000.
Nz3SELECT 1 FROM messages WHERE session_id = ? LIMIT 1zISELECT id FROM sessions AS child WHERE child.parent_session_id = ?   AND zchild.model_configz$._branched_fromz IS NULL   AND z$._delegate_fromz$._reset_fromz IS NULL   AND NOT Úchildza   AND COALESCE(child.source, '') != 'tool' ORDER BY child.started_at DESC, child.id DESC LIMIT 1r–  )
Úget_compression_tipr·  rÅ  r£   r}   r~   r'   r%   r"   Úadd)r€   ro   r�   rÛ  r”  Úbestr¥   Ú	child_rows   &&      r>   Úresolve_resume_session_idÚ.SessionMessagesMixin.resolve_resume_session_id’  s�  € ÷ ØÐð	Ø×1Ñ1°*Ó=×KÐKÀˆJð �^‰^×Ô Ø ˆGØ�9ˆDØˆDÜ˜2–Y�ð&Ø—|‘|Ð$YÐ\cÐ[eÓf×oÑoÓqÒ}Ø&˜Ø $§¡ð!Ü!2Ð3GÐI[Ó!\Ð ]ð ^!Ü!2Ð3GÐI[Ó!\Ð ]ð ^!Ü!2Ð3GÈÓ!YÐ Zð [%Ü%<¸WÔF\Ó%]Ð$^ð _Pð	Pð SZÐQ[ó!]÷ ^fÑ]eÓ]gð ð Ò$¨I°d¯O¬O¸yÈ½ÐRVÔ?VÙØ# D�/�Ø—‘˜Ö!ñ# ð$  Ò+‘4°÷- Òøô ô 	Úð	ûô$ !ô &Ø%Ó%÷# Òð &ú÷! ×ÐúsL   ŒD( ¤D( ¿EÁA?D:ÃEÃ(5EÄ(D7Ä6D7Ä:E	ÅEÅE	ÅEÅE*	Úwith_compactedc               ó(   € V ^8„  d   QhRRRRRRRR/# )r7   r  ri  rÊ  rK   Úwith_session_idre   r.  r<   )r=   s   "r>   r?   rr   »  s0   € ÷ 
\ñ 
\°Ið 
\Ècð 
\Ðgkð 
\Ø15ñ
\r@   c               óô   € T P                  RV'       d   RMR V'       d   RMR V P                   R\        V4       RV'       d   \        MT R2	\	        V4      4      pV'       d   V P                  WQ4      # T# )a  ``_CONVERSATION_ROW_COLUMNS`` rows for *session_ids* ORDER BY id (timestamps are not monotonic
and would break tool-call adjacency). ``with_compacted``: the display set (``active_clause`` is
ignored) with the ``compacted`` column, plus the removed rows' identities for ``drop_removed``.zSELECT r!  ru   zcompacted, z$ FROM messages WHERE session_id IN (r3   z ORDER BY id)rx  Ú_CONVERSATION_ROW_COLUMNSr&   r˜  rÊ   r	  )r€   r  rÊ  r0  r.  r  s   &&&$$ r>   Ú_fetch_conversation_rowsÚ-SessionMessagesMixin._fetch_conversation_rows»  s…   € ð
 �~‰~Ø¯‘n¸RÐ@×R`ÁÐfhÐ@iØ×-Ñ-Ð.ð /2Ü2?ÀÓ2LÐ1MÈQß)7Õ%¸]ÐKÈ<ðYô [`Ð`kÓZló	nˆ÷
 DRˆt×,Ñ,¨TÓ?Ð[ÐW[Ð[r@   c               ó    € V ^8„  d   QhRRRR/# )r7   r  ri  r;   rÍ  r<   )r=   s   "r>   r?   rr   Ç  s   € ÷ 
_ñ 
_¸)ð 
_È	ñ 
_r@   c                óœ   € V P                  R\        V4       R2\        V4      4      pV'       d   \        . VOVOR R7      # \	        V4      # )a'  *rows* (ordered by id) merged with the identities of the lineage's removed rows (``active=0,
compacted=0``), which ``_dedupe_display_generations(drop_removed=True)`` needs only to hide a copy, never
to show: no body (a legacy row without a stored display identity keeps its content for the key).a  SELECT session_id, id, role, timestamp, tool_call_id, tool_calls, tool_name, display_kind, display_metadata, message_uid, absorbed_message_uids, display_identity, active, compacted, CASE WHEN display_identity IS NULL THEN content END AS content FROM messages WHERE session_id IN (z.) AND active = 0 AND compacted = 0 ORDER BY idc                ó   € V R ,          # )r–  r<   )rj   s   &r>   rÎ   Ú?SessionMessagesMixin._with_removed_identities.<locals>.<lambda>Ñ  s   € ¸¸T¾r@   r”  )rx  r&   rÊ   r¦  rV   )r€   r  r  r¤  s   &&& r>   r	  Ú-SessionMessagesMixin._with_removed_identitiesÇ  se   € ð —.‘.ð2ô 3@ÀÓ2LÐ1Mð Nðô ! Ó-ó/ˆ÷ HOŒvÐ'˜Ð'˜wÐ'Ñ-BÔCÐ^ÔTXÐY]ÓT^Ð^r@   c               ó4   € V ^8„  d   QhRRRRRRRRRRRRR	R
/# )r7   ro   rK   r  re   rü  Úrepair_alternationÚinclude_row_idsrý  r;   r.  r<   )r=   s   "r>   r?   rr   Ó  sH   € ÷ 8ñ 8°sð 8Ètð 8Ø7;ð8ØY]ð8à6:ð8ð 9=ð8ð J^ñ8r@   c           	     óþ   € T;'       d    V'       * pT P                  V'       d   V P                  V4      MV.V P                  W64      RVR7      pV'       d   V P                  W‡R7      pV P	                  W�VWEVR7      # )aØ  Load messages in OpenAI format. ``include_compacted`` (deduped display history) is for DISPLAY reads
only: the model-fed restore must not regrow what compaction summarized away. ``repair_alternation``
repairs the loaded list for LIVE REPLAY callers (a durable ``user;user`` pair would re-trigger the
per-request repair forever), preserving summary markers before repair so derivative context
cannot merge with an original user turn; the stored transcript is never mutated.F©r0  r.  r  ©ro   r  r;  r<  Úinclude_summary_markers)r3  r�  rÿ  r±  Ú_rows_to_conversation)	r€   ro   r  rü  r;  r<  rý  r  r  s	   &&&&&&&  r>   Úget_messages_as_conversationÚ1SessionMessagesMixin.get_messages_as_conversationÓ  s�   € ð $×<Ð<Ð,<Ô(<ˆØ×,Ñ,ß4EˆD×$Ñ$ ZÔ0ÈJÈ<Ø×ÑÐ 0ÓDÐV[Ðlsð -ó uˆ÷ Ø×3Ñ3°DÐ3ÓOˆDØ×)Ñ)¨$ÐYjØ1Ø$6ð *ó 8ð 	8r@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   ro   rK   r  re   r;   rÊ   r<   )r=   s   "r>   r?   rr   æ  s'   € ÷ Cñ C°ð CÈð CÐY^ñ Cr@   c                ó
  € V'       d   V P                  V4      MV.p\        V4      pV P                  RV RV R2. VOVO54      p\        V4      \	        V^ ,          4      \	        V^,          4      \	        V^,          4      3# )aw  A cheap fingerprint of what ``get_messages_as_conversation`` (live rows) would load: the
lineage ids, the active rows' count and highest id, and the sessions' write-side
``transcript_revision`` (bumped by a trigger on every in-place row rewrite). Any append,
rewind, undo, compaction or rewrite moves it, so a payload derived from the transcript can be
reused while it holds.zpSELECT COUNT(*), COALESCE(MAX(id), 0), (SELECT COALESCE(SUM(transcript_revision), 0) FROM sessions WHERE id IN (z&)) FROM messages WHERE session_id IN (z) AND active = 1)r�  r&   r‡  rÊ   rS   )r€   ro   r  rŒ  Úmarksrj   s   &&&   r>   Úconversation_revisionÚ*SessionMessagesMixin.conversation_revisionæ  sŠ   € ÷ 7Hˆd×&Ñ& zÔ2ÈjÈ\ˆÜ˜cÓ"ˆØ�n‰nðØ"˜GÐ#IÈ%ÈÐP`ðbØcoÐehÐcoÐknÑcoóqˆô �c“
œC  A¥›K¬¨S°­V«´c¸#¸a½&³kÐBÐBr@   c               ó   € V ^8„  d   QhRR/# )r7   r;   ztuple[bool, Any]r<   )r=   s   "r>   r?   rr   ó  s   € ÷ 3ñ 3ÐIYñ 3r@   c                ó„  a
€ V P                  V4      ^ ,          pV P                  VP                  R4      V4      pVe   VP                  V4      MRo
RpS
e(   \        V
3R l\	        V4       4       R4      pVe   VR3pVf   V P                  W4      pVf   RV3# Vw  r‰V	'       d   VP                  V4       V	'       * V3# )aI  Ancestor-lineage dedupe of one decoded user *msg* -> ``(skip, exact_clone_key)``. Rotation
column-clones the concurrent tail into the child, so copies need not be adjacent: the exact
``(timestamp, canonical content)`` clone index is checked first, then the adjacent heuristic. A
rotated child carrier wins over the ancestor copy.rÕ   Nc              3  ó<   <"  € T F  w  rVSJ g   K  Vx € K  	  R # 5irU   r<   )rw   r¸  rÜ  Úprevious_exacts   &  €r>   ry   Ú=SessionMessagesMixin._dedupe_replayed_user.<locals>.<genexpr>ý  s    øé € Ð"lÑ9L©¨ÐPYÐ]kÐPk§1¢1Ó9Lùs   ƒ
’
TF)Ú _canonical_replayed_user_contentÚ_exact_replayed_user_clone_keyrÒ   r{  rµ  Ú%_find_duplicate_replayed_user_messagerU  )r€   r-  rÆ   Úexact_user_clonesÚcanonical_contentÚexact_clone_keyÚ	duplicateÚprevious_indexÚduplicate_indexÚprefer_currentrL  s   &&&&      @r>   Ú_dedupe_replayed_userÚ*SessionMessagesMixin._dedupe_replayed_useró  sÏ   ø€ ð
 !×AÑAÀ#ÓFÀqÕIÐØ×=Ñ=¸c¿g¹gÀkÓ>RÐTeÓfˆØCRÒC^Ð*×.Ñ.¨Ô?ÐdhˆØˆ	ØÒ%Ü!Ô"l¼À8Ô9LÓ"lÐnrÓsˆNØÒ)Ø+¨TÐ2�	ØÒØ×BÑBÀ8ÓQˆIØÒØ˜/Ð)Ð)Ø*3Ñ'ˆßØ�L‰L˜Ô)Ø!Ô! ?Ð2Ð2r@   r<  r@  c               ó0   € V ^8„  d   QhRRRRRRRRRRRR	/# )
r7   ro   rK   r  re   r;  r<  r@  r;   r.  r<   )r=   s   "r>   r?   rr   	  sC   € ÷ ^ñ ^¸ð ^ÐQUð ^Ðkoð ^Ø/3ð^à7;ð^àH\ñ^r@   c               ó¦  a€ ^ RI HpHp ^ RIHp	 T;'       d    V'       * p
. p/ p/ p. pV EFÕ  oV P                  SR,          V P                  SR,          4      4      pRSR,          RV\        R/pV
'       d   V	! S4      V\        &   V'       d   SR,          e   SR,          VR&   \        SV4       VP                  V3R	 lR 4       4       SR
,          '       d-   V P                  SR
,          4      ;pe   VVR
&   \        V4       V'       d   SR,          '       d   RVR&   VP                  V3R lR 4       4       SR,          '       d   \        SR,          . R4      VR&   SR,          '       d   SR,          VR&   SR,          '       d   RVR&   SR,          R8X  dw   VP                  V3R lR 4       4       SR,          e   SR,          VR&   VP                  V3R lR 4       4       VP                  R4      '       d   VP                  V4       M¤SR,          R8X  d"   VP!                  4        VP!                  4        MuSR,          R8X  dh   SR,          '       gY   SR,          '       dJ   V F  p\#        VV4       K  	  VP!                  4        \%        VSR,          4      ;p'       d
   VV\&        &   V'       d+   V P)                  VVV4      w  ppV'       d   EK»  Ve   VVV&   VP                  V4       EKØ  	  V! V! V4      4      pV'       d7   V'       d/   ^ RIHp V! RV4      pV'       d   \.        P1                  RVV4       V# ) a›  Decode fetched rows (ordered by id, pre-filtered) into OpenAI format, stable key order. Every dict is
stamped ``_DB_PERSISTED_MARKER_KEY`` (born durable) so an identity-losing handoff never re-appends the
transcript on flush. Unaddressed live-replay projections also carry the stored-row CAS digest: if a later rewrite
loses its physical ``_row_id``, logical ``message_uid`` can recover the row without guessing by
mutable payload while the digest still fences a concurrent winner. ``_row_id`` is opt-in (gateway
reactions); reasoning restored on assistant rows only; ``api_content`` VERBATIM (no sanitize/strip)
so replay keeps the provider prompt cache byte-stable.)Ú _strip_background_review_harnessÚ_strip_stale_tool_call_markers)Útranscript_row_snapshotrÅ   r†   Tr–  NrK  c              3  ó\   <"  € T F!  pSV,          '       g   K  VSV,          3x € K#  	  R # 5irU   r<   ©rw   Úcolrj   s   & €r>   ry   Ú=SessionMessagesMixin._rows_to_conversation.<locals>.<genexpr>1  s)   øé € Ð^Ñ2Q¨3ÐUXÐY\×U]ÑU]”˜˜S �X•Ó2Qùó   ƒ,™,r4   rã   c              3  ó\   <"  € T F!  pSV,          '       g   K  VSV,          3x € K#  	  R # 5irU   r<   r`  s   & €r>   ry   rb  7  s,   øé € ð yÙ+k CÐorÐsv×owÑow”��c˜#•h•Ó+kùrc  rÖ   rH   zKFailed to deserialize tool_calls in conversation replay, falling back to []rà   rá   râ   rõ   c              3  ó\   <"  € T F!  pSV,          '       g   K  VSV,          3x € K#  	  R # 5irU   r<   r`  s   & €r>   ry   rb  A  s)   øé € ÐaÑ6T¨sÐX[Ð\_×X`ÑX`œ?˜C  S¥�?Ó6Tùrc  rÜ   c           	   3  óz   <"  € T F0  pSV,          '       g   K  V\        SV,          R RV R24      3x € K2  	  R # 5i)NzFailed to deserialize r
  )r$   r`  s   & €r>   ry   rb  D  sE   øé € ð rád˜ÐhkÐlo×hpÑhpô j�Sœ( 3 s¥8¨TÐ5KÈCÈ5ÐPfÐ3gÓhÕiÛdùs   ƒ;™";rö   r÷   Útool_call_uid)Úrepair_message_sequenceu…   Repaired %d message-alternation violation(s) while restoring session %s â€” durable transcript kept them, see repair_message_sequence)rä   r×   )rÕ   rÖ   rÔ   rØ   )rÚ   rÛ   rå   )r1   r\  r]  rF  r^  rý  r“   Ú_DB_PERSISTED_MARKER_KEYr   r+   r9  r¦   r   r$   rÒ   r|  rø   r   r   r   rX  Úagent.agent_runtime_helpersrh  rš   Úinfo)r€   r  ro   r  r;  r<  r@  r\  r]  r^  Ústamp_snapshotr-  rQ  r<  Úunindexed_tool_ownersr†   rÆ   ÚdecodedÚownerÚtool_uidr  rS  rh  Úrepairedrj   s   &&$$$$$                 @r>   rA  Ú*SessionMessagesMixin._rows_to_conversation	  s  ø€ ÷ 	båCð ,×CÐC°OÔ0CˆØˆØCEÐØ)+ˆð 79ÐÜˆCØ×/Ñ/°°Fµ¸T×=QÑ=QÐRUÐV_ÕR`Ó=aÓbˆGð ˜3˜v�;¨	°7Ô<TÐVZÐ[ˆCßÙ'>¸sÓ'C�”OÑ$÷  3 t¥9Ò#8Ø!$ T¥��I‘ô & c¨3Ô/Ø�J‰JÔ^Ñ2QÓ^Ô^ØÐ%×&Ô&°t×7TÑ7TÐUXÐYkÕUlÓ7mÐ,m¨GÒ+zØ*1�Ð&Ñ'Ü˜cÔ"ß&¨3Ð/D×+EÔ+EØ-1�Ð)Ñ*Ø�J‰Jô yÙ+kóyô yà�<× Ô Ü$,Ø˜Õ% rÐ+xó%z��LÑ!àÐ(×)Ô)Ø$'Ð(=Õ$>��LÑ!Ø�:�ŒØ"&��J‘Ø�6�{˜kÔ)Ø—
‘
ÔaÑ6TÓaÔaØÐ*Õ+Ò7Ø/2Ð3FÕ/G�CÐ+Ñ,Ø—
‘
ô rádórô rð —7‘7˜<×(Ò(Ø)×0Ñ0°Ô5øØ�V• Ô&Ø×$Ñ$Ô&Ø%×+Ñ+Õ-Ø�V• Ô&¨s°?×/CÔ/CÈÈN×H[ÔH[ó
 3�EÜ(¨¸Ö?ñ 3à%×+Ñ+Ô-Ü4°^ÀSÈÕEXÓYÐY�8ÖYØ)1�CœÑ&ß Ø(,×(BÑ(BÀ8ÈSÐRcÓ(dÑ%��oßÚØ"Ò.Ø9<Ð% oÑ6Ø�O‰O˜C× ñy ñ~ 2Ñ2RÐS[Ó2\Ó]ˆß§(ÝKÙ.¨t°XÓ>ˆHßÜ—‘ð 2à3;¸ZôIð ˆr@   c               ó    € V ^8„  d   QhRRRR/# )r7   ro   rK   r;   z1tuple[list[dict[str, Any]], list[dict[str, Any]]]r<   )r=   s   "r>   r?   rr   i  s   € ÷ .ñ .°3ð .Ð;lñ .r@   c           	     óV  € V P                  V P                  V4      RRRR7      pT P                  V Uu. uF5  q3R,          V8X  g   K  VR,          '       g   K#  VR,          R8w  g   K3  VNK7  	  upVRRRRR	7      pV P                  V P                  VRR
7      VRRRR7      pWE3# u upi )uï  ``(model_history, display_history)`` for a resume from ONE SELECT; byte-identical to the separate
reads. model: the tip's active rows, alternation-repaired, summary marker kept for pre-compress
checkpointing. display: the full lineage (``/branch`` stands alone), compaction-archived rows deduped.

The display projection also includes rows preserved by IN-PLACE compaction (``active=0,
compacted=1``), deduped by :meth:`_dedupe_display_generations`. Without them a compacted
conversation resumes showing only its summary plus the carried-forward tail â€” the user's own turns
read as deleted even though every row is still on disk, and the REST transcript read (which has
always included them) disagreed with this one about the same session (#92080).
ru   Tr>  ro   r#  rÅ   Úsession_metaFr?  r  ©ro   r  r;  r<  )r3  r�  rA  r±  )r€   ro   r  rn  Úmodel_historyÚdisplay_historys   &&    r>   Úget_resume_conversationsÚ-SessionMessagesMixin.get_resume_conversationsi  sÒ   € ð ×,Ñ,Ø×$Ñ$ ZÓ0°"ÀdÐ[_ð -ó aˆð ×2Ñ2ÙÓn™�1 ,¥°:Ñ =ŒQÀ!ÀHÇ+Á+ŒQÐRSÐTZÕR[Ð_mÑRm�QˆQ™ÑnØ!Ø#¸ÈdÐlpð 3ó rˆð ×4Ñ4Ø×,Ñ,¨TÀÐ,ÓEÐR\Ø"°uÈdð 5ó Tˆð Ð-Ð-ùò os   ´B&ÁB&ÁB&Á)B&c               ó    € V ^8„  d   QhRRRR/# )r7   ro   rK   r;   ri  r<   )r=   s   "r>   r?   rr   ƒ  s   € ÷ %ñ %¨cð %°iñ %r@   c                óÌ  € V'       g   V.# V P                  V4      pV'       d   V P                  V4      '       d   V.# V.pTpV0p\        V4      ^d8  d|   V P                  V4      '       g   McV P                  VR,          4      pV'       d   VR,          V9   d   M4VP	                  VR,          4       VP                  VR,          4       TpK‹  \        \        V4      4      # )u@  Session ids a display resume materializes: the VERIFIED compression lineage (root â†’ tip; each
hop's parent ended ``compression`` and the child is a genuine continuation), or the session alone
for anything else â€” an explicit ``/branch`` copy, an API fork, a reset child, or a plain session.
The raw parent walker ``_session_lineage_root_to_tip`` is intentionally NOT used here: it crosses
fork/reset boundaries and misclassifies API forks (parent ended ``branched``, no marker) as
compression lineages. Shared with the resume guard so it counts exactly what a resume loads.r³  r–  )Úget_sessionÚ_is_explicit_fork_child_rowrW   Ú_is_compression_child_rowr)  r|  rV   r  )r€   ro   ÚsessionÚchainrÛ  r”  Úparents   &&     r>   r�  Ú(SessionMessagesMixin._resume_lineage_idsƒ  sÊ   € ÷ Ø�<ÐØ×"Ñ" :Ó.ˆß˜$×:Ñ:¸7×CÒCØ�<Ðð �ˆØˆØˆ|ˆÜ�%‹j˜3ÔØ×1Ñ1°'×:Ò:ØØ×%Ñ% gÐ.AÕ&BÓCˆFß˜V D�\¨TÔ1ØØ�H‰H�V˜D•\Ô"Ø�L‰L˜ �Ô&ØŠGÜ”H˜U“OÓ$Ð$r@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   ro   rK   Útip_onlyre   r;   ztuple[list[str], str]r<   )r=   s   "r>   r?   rr   Ÿ  s'   € ÷ Uñ U¨cð U¸Tð UÐF[ñ Ur@   c                óB   € V'       d   V.R3# V P                  V4      R3# )zW``tip_only``: the tip's ACTIVE rows (model restore); else the full-lineage DISPLAY set.z
active = 1z(active = 1 OR compacted = 1))r�  )r€   ro   r…  s   &&&r>   Ú_resume_count_scopeÚ(SessionMessagesMixin._resume_count_scopeŸ  s)   € çØ�< Ð-Ð-Ø×'Ñ'¨
Ó3Ð5TÐTÐTr@   r…  c               ó$   € V ^8„  d   QhRRRRRR/# )r7   ro   rK   r…  re   r;   rS   r<   )r=   s   "r>   r?   rr   ¥  s"   € ÷ $ñ $°3ð $ÀTð $ÐVYñ $r@   c               óœ   € V P                  W4      w  r4\        V P                  R\        V4       RV 2\	        V4      4      ^ ,          4      # )zHCount the rows a resume would materialize (see ``_resume_count_scope``).z3SELECT COUNT(*) FROM messages WHERE session_id IN (ú) AND )r‡  rS   r‡  r&   rÊ   )r€   ro   r…  r  rÊ  s   &&$  r>   Úget_resume_message_countÚ-SessionMessagesMixin.get_resume_message_count¥  sW   € à%)×%=Ñ%=¸jÓ%SÑ"ˆÜ�4—>‘>ØAÄ-ÐP[ÓB\ÐA]Ð]cÐdqÐcrÐsÜ�+Óó à !õ#ó $ð 	$r@   c               ó(   € V ^8„  d   QhRRRRRRRR/# )	r7   ro   rK   Úmax_messagesr0  r…  re   r;   rS   r<   )r=   s   "r>   r?   rr   ¬  s*   € ÷ ñ ¨Sð Àð Ðcgð Ðtwñ r@   c          	     óB  € ^ RI HpHp Vf   V! 4       pV^ 8  d   \        R4      hV^ 8X  d   ^ # V P	                  W4      w  rg\        V P                  R\        V4       RV R2. VOV^,           N54      ^ ,          4      pW‚8”  d   T! Y‚V'       d   RR7      hRR7      hV# )	a%  Resume row count, or raise ``SessionResumeTooLargeError``. ``max_messages=None`` reads config; 0
disables the guard without counting. ``tip_only`` bounds only the tip's active rows for callers that
never materialize the lineage: a heavily compressed conversation is a success, not a rejection.)ÚSessionResumeTooLargeErrorÚresolved_max_resume_messagesz!max_messages must be non-negativezBSELECT COUNT(*) FROM (SELECT 1 FROM messages WHERE session_id IN (r‹  z	 LIMIT ?)zin its tip segmentzacross its lineage)Úscope)r1   r‘  r’  rŒ   r‡  rS   r‡  r&   )	r€   ro   r�  r…  r‘  r’  r  rÊ  Úmessage_counts	   &&&$     r>   Úassert_resume_safeÚ'SessionMessagesMixin.assert_resume_safe¬  sÐ   € ÷ 	ZØÒÙ7Ó9ˆLØ˜!ÔÜÐ@ÓAÐAØ˜1ÔÙØ%)×%=Ñ%=¸jÓ%SÑ"ˆÜ˜DŸN™Nð ,;Ü;HÈÓ;UÐ:Vð WØ �/ ð,,à-M¨{Ð-M¸LÈ1Õ<LÑ-MóOàOPõRó Sˆð Ô'Ù,ØÏ8Ð3Gôoð oØYmôoð oàÐr@   c               ó    € V ^8„  d   QhRRRR/# )r7   ro   rK   r;   r.  r<   )r=   s   "r>   r?   rr   À  s   € ÷ Nñ N°cð NÐ>Rñ Nr@   c                ó   € V P                  V4      p\        V4      ^8:  d   . # V P                  V P                  VRRRR7      RR7      pV Uu0 uF1  qDR,          V8w  g   K  VR,          f   K  \	        VR,          4      kK3  	  ppV'       g   . # V P                  W1RRRR7      pV UUU	u. uFG  qwP                  R	4      V9   g   K  VP                  4        UU	u/ uF  w  r‰VR	8w  g   K  W‰bK  	  up	pNKI  	  up	pp# u upi u up	pi u up	ppi )
zØAncestor-only display messages of a lineage (row ``session_id != tip``) that ``session.resume``
prepends. Identified by row origin, not ``display[:len(display) - len(model)]``, so alternation
repair cannot overcount.ru   Tr>  r  ro   r–  Frv  rK  )r�  rW   r±  r3  rS   rA  rÒ   rØ  )
r€   ro   r  r  rj   Úancestor_idsr�  Úmessagerx   rÍ   s
   &&        r>   Úget_ancestor_display_prefixÚ0SessionMessagesMixin.get_ancestor_display_prefixÀ  s!  € ð ×.Ñ.¨zÓ:ˆÜˆ{Ó˜qÔ ØˆIØ×/Ñ/°×0MÑ0MØ˜¨TÀ$ð 1Nó 1HØVZð 0ó \ˆá26Ót±$¨3¸lÕ:KÈzÑ:YœÐ^aÐbfÕ^gœœ˜C �Iž±$ˆÐtßØˆIØ×,Ñ,Ø¸4ÐTYÐkoð -ó qˆñ #õNÙ"�§k¡k°)Ó&<ÀÑ&Lô E '§-¡-¤/ÔD¡/™$˜!°Q¸)±^”�’¡/ÕDÙ"óNð 	Nùò uùó
 Eùô Ns6   ÁC>Á"C>Á/C>Â,D	ÃD	ÃDÃ+DÃ1D	ÄD	c               ó    € V ^8„  d   QhRRRR/# r%  r<   )r=   s   "r>   r?   rr   Ñ  s   € ÷ >ñ >°ð >¸ñ >r@   c                óh   € V P                  V4      pV'       d   V^ ,          '       d
   V^ ,          # T# )z®ROOT id of the lineage: the stable conversation id across compression segments and delegate
subagents (Nous Portal usage tagging). Unchanged when there is no recorded parent.)Ú_session_lineage_root_to_tip)r€   ro   r�  s   && r>   Úget_conversation_rootÚ*SessionMessagesMixin.get_conversation_rootÑ  s-   € ð ×1Ñ1°*Ó=ˆß  U¨1§X¤Xˆu�Q�xÐ=°:Ð=r@   c               ó    € V ^8„  d   QhRRRR/# )r7   rÆ   rÇ   r;   ztuple[Any, bool]r<   )r=   s   "r>   r?   rr   Ø  s   € ÷ ^ñ ^¨nð ^ÐAQñ ^r@   c                óÈ   € V P                  R4      R8w  d   R# \        V 4      w  rVRJ;'       d    VRJpV'       d   VP                  R4      V3# V P                  R4      V3# )z=Return canonical live content and whether *msg* is composite.rÅ   rö   Nr†   )NF)rÒ   r   )rÆ   r…  r†  Úis_composites   &   r>   rN  Ú5SessionMessagesMixin._canonical_replayed_user_content×  si   € ð �7‰7�6‹?˜fÔ$ØÐÜ7¸Ó<ÑˆØ dÐ*×DÐD¨yÀÐ/Dˆß+7ˆy�}‰}˜YÓ'ÐQ]Ð]Ð]¸S¿W¹WÀYÓ=OÐQ]Ð]Ð]r@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   rÕ   r   r†   r;   zOptional[tuple[Any, str]]r<   )r=   s   "r>   r?   rr   á  s"   € ÷ ñ °#ð Àð ÐHañ r@   c                óŒ   € V e   VRR. 39   d   R#  V \         P                  ! VRRRR7      3#   \        \        3 d     R# i ; i)z8Return a hashable key for a column-exact rotation clone.Nru   FT)Úensure_asciiÚ	sort_keysÚ
separators)Ú,Ú:)rL   r‹   rO   rŒ   )rÕ   r†   s   &&r>   rO  Ú3SessionMessagesMixin._exact_replayed_user_clone_keyà  sR   € ð Ò ¨D°"°b¨>Ô 9Ùð	ØœdŸjšj¨¸uÐPTÐakÔlÐlÐløÜœ:Ð&ô 	Úð	ús   ’. ®AÁAc               ó$   € V ^8„  d   QhRRRRRR/# )r7   r-  r.  rÆ   rÇ   r;   zOptional[tuple[int, bool]]r<   )r=   s   "r>   r?   rr   ë  s$   € ÷ ñ Ð8Lð Ø3AðØF`ñr@   c                ó  € VP                  R4      R8w  d   R# \        P                  pV! V4      w  r4VRR. 39   d   R# \        \	        V 4      ^,
          RR4       F©  pW,          pVP                  R4      R8X  d@   V! V4      w  rxWs8X  d.   V'       g   V'       g   \        V\        4      '       d   WT3u # K^  K`  VP                  R4      R8X  g   Kx  VP                  R4      '       g   VP                  R4      '       g   K¨   R# 	  R# )	a+  Adjacent replay duplicate ``(index, prefer_current)`` or None. Rotation may persist the current ask
in the parent and again inside a composite child carrier: carriers compare by canonical live payload,
ordinary users by exact string. The child carrier wins (it owns the durable row id and scaffold).rÅ   rö   Nru   rõ   r†   rH   rÖ  )rÒ   rm   rN  r£   rW   rJ   rK   )	r-  rÆ   Ú	canonicalr†   rW  ÚindexÚprevÚprev_contentÚprev_is_composites	   &&       r>   rP  Ú:SessionMessagesMixin._find_duplicate_replayed_user_messageê  sâ   € ð �7‰7�6‹?˜fÔ$ÙÜ(×IÑIˆ	Ù"+¨C£.ÑˆØ�t˜R �nÔ$ÙÜœ3˜x›=¨1Õ,¨b°"Ö5ˆEØ•?ˆDØ�x‰x˜Ó 6Ô)Ù2;¸D³/Ñ/�ØÔ*·×BSÔWaÐbiÔkn×WoÒWoØ Ð0Ò0ñ XpÑ*à—‘˜&Ó! [Ö0°d·h±h¸y×6IÒ6IÈTÏXÉXÐVb×McÔMcÚñ 6ñ r@   c               ó    € V ^8„  d   QhRRRR/# )r7   ro   rK   r;   r  r<   )r=   s   "r>   r?   rr     s   € ÷ Wñ W°ð W¸ñ Wr@   c                óz   € V P                  \        V34       Uu. uF  p\        V^ ,          4      NK  	  up# u upi )zbOrdered physical active ids for rewind CAS checks (includes legacy harness rows projections omit).)rx  rò  rS   r«  s   && r>   Úget_active_message_idsÚ+SessionMessagesMixin.get_active_message_ids  s1   € à'+§~¡~´oÈ
À}Ô'UÓVÑ'U ”�C˜•F–Ñ'UÑVÐVùÒVs   š8c               ó    € V ^8„  d   QhRRRR/# )r7   ro   rK   r;   rÀ  r<   )r=   s   "r>   r?   rr     s   € ÷ Qñ Q°Cð Q¸Oñ Qr@   c                ó~   € V P                  RV34      P                  4       p\        V4      \        R V 4       4      3# )z?Return active message/tool-call counts inside the caller's txn.zCSELECT tool_calls FROM messages WHERE session_id = ? AND active = 1c              3  óJ   "  € T F  p\        V^ ,          ^R7      x € K  	  R# 5i)é    rë  Nrì  rí  s   & r>   ry   ÚASessionMessagesMixin._active_transcript_counts.<locals>.<genexpr>  s   é € ÐPÉ4ÀCœo¨c°!­f¸Q×?Ñ?Ë4ùrï  )r}   r™  rW   r:  )r�   ro   r  s   && r>   Ú_active_transcript_countsÚ.SessionMessagesMixin._active_transcript_counts  s<   € ð �|‰|ÐaÐdnÐcpÓq×zÑzÓ|ˆÜ�4‹yœ#ÑPÉ4ÓPÓPÐPÐPr@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   Ú
target_rowrÇ   Úexpected_target_contentr   Úpreserve_compaction_handoffre   r<   )r=   s   "r>   r?   rr     s(   € ÷ @ñ @¨~ð @ÐX[ð @Ðz~ñ @r@   c                óö  € VP                  R4      '       g   \        R4      h\        / VCRV P                  VP                  R4      4      RV P	                  VP                  R4      4      /C4      w  rEVf   \        R4      hVP                  R4      p\        V\        4      '       d   \        V4      P                  4       pVe   Wb8w  d   \        R4      hV'       d   Vf   \        R4      hV'       d   V# R# )	z¼Validate an active rewind target; return its handoff scaffold (or None). ``ValueError``: inactive /
non-user-originated / missing composite carrier; ``RuntimeError``: live payload changed.r#  zrewind target is not activer†   r4   Nz+rewind target is not a user-originated turnz2rewind target changed before it could be persistedz@preserve_compaction_handoff requires an active composite carrier)
rÒ   rŒ   r   r“   r¦   rJ   rK   r   rv   ró  )r€   rÂ  rÃ  rÄ  r…  r†  Úlive_contents   &&&&   r>   Ú_split_rewind_targetÚ)SessionMessagesMixin._split_rewind_target  sî   € ð �~‰~˜h×'Ò'ÜÐ:Ó;Ð;Ü7ð 9cØð9cØ# T×%9Ñ%9¸*¿.¹.ÈÓ:SÓ%TØ × =Ñ =¸j¿n¹nÐM_Ó>`Ó añ9có dÑˆð ÒÜÐJÓKÐKØ —}‘} YÓ/ˆÜ�l¤C×(Ò(Ü+¨LÓ9×?Ñ?ÓAˆLØ"Ò.°<Ô3ZÜÐSÓTÐTß&¨7ª?ÜÐ_Ó`Ð`ß5ˆwÐ?¸4Ð?r@   rÄ  rÃ  c               ó0   € V ^8„  d   QhRRRRRRRRR	R
RR/# )r7   ro   rK   Útarget_message_idrS   rÄ  re   rå  rç  rÃ  r   r;   rÇ   r<   )r=   s   "r>   r?   rr      s:   € ÷ <ñ <¨Cð <ÀCð <Ðimð <Ø/Bð<à36ð<àBPñ<r@   c               óè   a aaaaaa€ ^ RI Ho VVVVV VV3R lpS P                  V4      w  rxršpS P                  VP	                  R4      4      VR&   R\        V4      RVRV	/S'       d   RV
RV/C# / C# )	aº  Soft-delete (``active=0``) every message with id >= *target_message_id*, target included (the caller
pre-fills it as the next prompt). Returns ``{"rewound_count", "target_message", "new_head_id"}``, plus
``replacement_message_id`` with ``preserve_compaction_handoff`` (archives a composite summary carrier,
inserts its hidden handoff scaffold as the new head). ``ValueError``: target missing or not ``user``.
``expected_active_ids`` / ``expected_target_content`` pin the active set and canonical live payload
in-txn before any mutation (presentation-only metadata changes don't invalidate a rewind). A live turn
lease refuses; expired/dead holders are reclaimed. ``rewind_count`` always increments.)Úrewind_in_transactionc           
     ó–   <€ S	! S
V SSSSSR 7      w  rr4T;'       d)    V P                  RV34      P                  4       ^ ,          pWW4V3# ))rÄ  rå  rÃ  z-SELECT message_uid FROM messages WHERE id = ?)r}   r~   )r�   rÂ  rŒ  Úhead_idÚreplacement_idÚreplacement_uidrå  rÃ  rÄ  rÌ  r€   ro   rÊ  s   &     €€€€€€€r>   r#  Ú3SessionMessagesMixin.rewind_to_message.<locals>._do+  sl   ø€ Ù7LØ�d˜JÐ(9Ø,GØ$7ÐQhô8jÑ4ˆJ˜Wð -÷ bð b°·±Ø?À.ÐARó2TßT\ÑT\ÓT^Ð_`õ2bˆOà G¸_ÐLÐLr@   r†   Úrewound_countÚtarget_messageÚnew_head_idÚreplacement_message_idÚreplacement_message_uid)Ú hermes_state_mutation_transcriptrÌ  r(  r“   rÒ   rW   )r€   ro   rÊ  rÄ  rå  rÃ  r#  rÂ  ÚrewoundrÔ  rÕ  rÖ  rÌ  s   fffddd      @r>   Úrewind_to_messageÚ&SessionMessagesMixin.rewind_to_message   sš   þ€ õ 	K÷		Mó 		Mð ]a×\oÑ\oÐpsÓ\tÑYˆ
˜[ÐBYà $× 4Ñ 4°Z·^±^ÀIÓ5NÓ Oˆ
�9ÑØ¤ W£Ð/?ÀÈ]Ð\gð <÷ 2ð -Ð.DØ-Ð/FðHð<ð 	<ð 8:ð<ð 	<r@   c               ó    € V ^8„  d   QhRRRR/# )r7   ro   r  r;   rS   r<   )r=   s   "r>   r?   rr   =  s   € ÷ Kñ K¨
ð K¸cñ Kr@   c                ó„   € RV'       d   RMR,           pT P                  Y!'       d   V34      ^ ,          # R4      ^ ,          # )z2Count messages, optionally for a specific session.zSELECT COUNT(*) FROM messagesrÐ  ru   r<   rª  )r€   ro   r€  s   && r>   r”  Ú"SessionMessagesMixin.message_count=  s9   € à-ÏJÑ1HÐ\^Õ_ˆØ�~‰~˜c¶J J =ÓGÈÕJÐJÀBÓGÈÕJÐJr@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   ro   rK   ro  r;   re   r<   )r=   s   "r>   r?   rr   B  s!   € ÷ -ñ -°#ð -¸cð -Àdñ -r@   c                ó,   € V P                  RW34      RJ# )zNProbe the accepted-input marker without allocating message bodies or archives.zèSELECT 1 FROM messages WHERE session_id = ? AND role = 'user' AND observed = 0 AND (active = 1 OR compacted = 1) AND CASE WHEN json_valid(display_metadata) THEN json_extract(display_metadata, '$.gateway_input_owner') END = ? LIMIT 1Nrª  )r€   ro   ro  s   &&&r>   Úhas_gateway_input_ownerÚ,SessionMessagesMixin.has_gateway_input_ownerB  s+   € à�~‰~ð[ð Ðó!ð
 )-ð-ð 	-r@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   ro   rK   rà   r;   re   r<   )r=   s   "r>   r?   rr   K  s"   € ÷ 	;ñ 	;°#ð 	;ÈCð 	;ÐTXñ 	;r@   c                ó,   € V P                  RW34      RJ# )ab  True when *platform_message_id* exists (partial-index probe; the gateway's transient-failure dedupe).

Uses the idx_messages_platform_msg_id partial index for efficient lookup. Used by the gateway's
transient-failure dedupe guard (#47237) to skip re-persisting a user message that was already saved
on a prior retry of the same inbound platform message.
zOSELECT 1 FROM messages WHERE session_id = ? AND platform_message_id = ? LIMIT 1Nrª  )r€   ro   rà   s   &&&r>   Úhas_platform_message_idÚ,SessionMessagesMixin.has_platform_message_idK  s&   € ð �~‰~Ø]ØÐ-ó/à6:ð;ð 	;r@   Úinclude_resetc               ó$   € V ^8„  d   QhRRRRRR/# )r7   r€  rÇ   ræ  re   r;   r<   )r=   s   "r>   r?   rr   V  s(   € ÷ Zñ Z°>ð ZÐUYð ZÐfjñ Zr@   c               ó<  € VP                  R4      R8X  d   R# VP                  R4      p\        V\        4      '       d    \        P                  ! V4      p\        V\        4      '       g   R# VP                  R4      VP                  R4      3pV'       d   WCP                  R4      3,          pVP                  R	4      pV'       d   WT9   # \        ;QJ d    R
 V 4       F  '       g   K   R# 	  R# ! R
 V 4       4      #   \        P
                   d     R# i ; i)aO  True when *session* is a branch, delegate, or tool child of its parent (``include_reset``: also a
reset fork). Markers only count when they point at ``parent_session_id``: compression copies
``model_config`` onto the continuation, so presence-only matching would misclassify it (same binding
as ``_NON_CONTINUATION_CHILD_FILTER_SQL``).r{   r÷   TÚmodel_configFÚ_branched_fromÚ_delegate_fromÚ_reset_fromr³  c              3  ó(   "  € T F  qR Jx € K
  	  R # 5irU   r<   )rw   rP  s   & r>   ry   ÚCSessionMessagesMixin._is_explicit_fork_child_row.<locals>.<genexpr>i  s   é € Ð9YÑQXÈAÀ4½-ÓQXùs   ‚)rÒ   rJ   rK   rL   rM   rN   r™   rÕ  )r€   r€  ræ  ÚcfgÚmarkersÚ	parent_ids   &&$   r>   r~  Ú0SessionMessagesMixin._is_explicit_fork_child_rowV  sê   € ð
 �;‰;�xÓ  FÔ*ÙØ�k‰k˜.Ó)ˆÜ�cœ3×ÒðÜ—j’j “o�ô ˜#œt×$Ò$ÙØ—7‘7Ð+Ó,¨c¯g©gÐ6FÓ.GÐHˆßØŸ™ Ó.Ð0Õ0ˆGØ—K‘KÐ 3Ó4ˆ	ß'0ˆyÑ#ÐY·c³cÑ9YÑQXÓ9Y·c´cÐY²cÐY°cÑ9YÑQXÓ9YÓ6YÐYøô ×'Ñ'ô Úðús   ÁD ÄDÄDc               ó    € V ^8„  d   QhRRRR/# r  r<   )r=   s   "r>   r?   rr   k  s   € ÷ Kñ K°ð K¸ñ Kr@   c                ól   € V P                  V4      p\        T;'       d    V P                  V4      4      # )z¢Read-only :meth:`_is_explicit_fork_child_row`; a missing row is not a fork (prompt_cache_scope keeps
a declared conversation key from crossing the fork boundary).)r}  re   r~  )r€   ro   r€  s   && r>   Úis_explicit_fork_childÚ+SessionMessagesMixin.is_explicit_fork_childk  s2   € ð ×"Ñ" :Ó.ˆÜ�G×IÐI × @Ñ @ÀÓ IÓJÐJr@   c               ó$   € V ^8„  d   QhRRRRRR/# )r7   r|   rK   r{   r;   r0  r<   )r=   s   "r>   r?   rr   q  s!   € ÷ 6ñ 6¸ð 6ÀSð 6È]ñ 6r@   c                ó¨   € V'       d	   V'       g   R# V P                  RW!34      pVe   VR,          e   \        VR,          4      M^ pV^ 8”  d   V# R# )aÛ  Conversation boundaries (``_RESET_END_REASONS`` ends) this peer has crossed, or ``None`` if never
reset. The peer is ``(session_key, source)``, never the key alone (an API caller may legally reuse a
Telegram row's key). Read from ``conversation_generations`` (advanced in each boundary's txn), not an
aggregate over session rows: deletes/prunes would re-emit a retired pair. Rows are never GC'd
(dropping one re-issues generation 1: the ABA this prevents). Wall-clock-free, so a backwards NTP
correction cannot reorder it. DBs upgraded mid-conversation take their first generation from the
next boundary written (a pre-upgrade reset shares its predecessor's scope once: costs a warm
prompt-cache bucket, never crosses an identity).NzTSELECT generation FROM conversation_generations WHERE source = ? AND session_key = ?Ú
generationr¤  )r€   r|   r{   rj   rù  s   &&&  r>   Úlatest_conversation_boundaryÚ1SessionMessagesMixin.latest_conversation_boundaryq  sV   € ÷ §&ÙØ�n‰nØbØÐ!ó#ˆð 03ªÀ3À|ÕCTÒC`”S˜˜\Õ*Ô+Ðfgˆ
Ø'¨!œ^ˆzÐ5°Ð5r@   c               ó    € V ^8„  d   QhRRRR/# r´  r<   )r=   s   "r>   r?   rr   ‚  s   € ÷ !ñ !¨ð !°ñ !r@   c                ó6   a€ V3R lpV P                  V4       R# )z9Delete all messages for a session and reset its counters.c                ó\   <€ V P                  R S34       V P                  \        S34       R# )rð  N)r}   Ú_RESET_COUNTERS_SQL)r�   ro   s   &€r>   r#  Ú0SessionMessagesMixin.clear_messages.<locals>._do„  s%   ø€ Ø�L‰LÐDÀzÀmÔTØ�L‰LÔ,¨z¨mÖ<r@   Nrg  )r€   ro   r#  s   &f r>   Úclear_messagesÚ#SessionMessagesMixin.clear_messages‚  s   ø€ õ	=ð 	×Ñ˜CÖ r@   Údry_runÚbackupc               ó$   € V ^8„  d   QhRRRRRR/# )r7   r  re   r  r;   rÇ   r<   )r=   s   "r>   r?   rr   ‰  s"   € ÷  2ñ  2¸ð  2Èdð  2Ð^lñ  2r@   c               ó  aa
a€ ^ RI Ho
 R V
3R lloRV3R llpV P                  4       ;_uu_ 4       pS! V4      pRRR4       S'       g	   X'       g	   V! X4      # RpV'       d»   ^ RIpVP                  P	                  4       P                  R4      p\        V P                  P                  V P                  P                   RV 24      4      pV P                  ;_uu_ 4        V P                  P                  RV34       RRR4       \        P                  R	V4       V3R
 lp	V P                  V	4      pV'       d    \        P                  R\!        V4      4       V! WV4      #   + '       g   i     EL3; i  + '       g   i     L€; i)a  Permanently clear bare tool-call marker content ("[memory]") left by pre-fix sessions
(``_rows_to_conversation`` repairs it in memory; this stops the re-scan). Only ``content`` is touched.
``backup``: ``VACUUM INTO`` snapshot first (none when nothing changes).)Ú_STALE_TOOL_CALL_MARKER_REc               ó   € V ^8„  d   QhRR/# )r7   r;   r  r<   )r=   s   "r>   r?   ÚHSessionMessagesMixin.purge_stale_tool_call_markers.<locals>.__annotate__Ž  s   € ÷ 	uñ 	u Iñ 	ur@   c                ó  <€ V P                  R 4      pVP                  4        Uu. uFY  p\        VR,          \        4      '       g   K"  SP	                  VR,          P                  4       4      '       g   KP  VR,          NK[  	  up# u upi )ziSELECT id, content FROM messages WHERE role = 'assistant' AND tool_calls IS NOT NULL AND tool_calls != ''r†   r–  )r}   r™  rJ   rK   Ú	fullmatchrv   )r�   Úcursorrj   r  s   &  €r>   Ú_find_affectedÚJSessionMessagesMixin.purge_stale_tool_call_markers.<locals>._find_affectedŽ  s�   ø€ Ø—\‘\ð #[ó \ˆFà)/¯©Ô):ó uÑ): #Ü˜c )�n¬c×2ô Ø7Q×7[Ñ7[Ð\_Ð`iÕ\j×\pÑ\pÓ\r×7sô �C˜—I�IÑ):ñ uð uùò us   ¥BÁ	)BÁ7BNc                ó*   <€ R SR\        V 4      RV RV/# )r  Úrows_affectedÚrow_idsÚbackup_path)rW   )Úaffectedr  r  s   &&€r>   Ú_resultÚCSessionMessagesMixin.purge_stale_tool_call_markers.<locals>._result“  s!   ø€ Ø˜w¨¼¸X»È	ÐS[Ð]jÐlwÐxÐxr@   z%Y%m%d_%H%M%Sz.pre-clean-markers-backup-zVACUUM INTO ?z3Backed up state.db to %s before clean-markers writec                óf   <€ S! V 4      pV'       d    V P                  R \        V4       R2V4       V# )z.UPDATE messages SET content = '' WHERE id IN (r3   )r}   r&   )r�   rŒ  r  s   & €r>   r#  Ú?SessionMessagesMixin.purge_stale_tool_call_markers.<locals>._do¡  s6   ø€ Ù  Ó&ˆCßØ—‘ÐMÌmÐ\_ÓN`ÐMaÐabÐcÐehÔiØˆJr@   zIPermanently cleared %d stale tool-call marker row(s) in state.db (#78148)rU   )r1   r  rÅ  Údatetimerd   ÚstrftimerK   Údb_pathÚ	with_nameÚnameÚ_lockÚ_connr}   rš   rk  r(  rW   )r€   r  r  r  r�   Úaffected_idsr  r  Ústampr#  r  r  s   &d$       @@r>   Úpurge_stale_tool_call_markersÚ2SessionMessagesMixin.purge_stale_tool_call_markers‰  s&  ú€ õ 	<÷	uð 	u÷
	yà�^‰^×Ô Ù)¨$Ó/ˆL÷ çŸ,Ù˜<Ó(Ð(Ø%)ˆßÛØ×%Ñ%×)Ñ)Ó+×4Ñ4°_ÓEˆEÜ˜dŸl™l×4Ñ4¸¿¹×8IÑ8IÐ7JÐJdÐejÐdkÐ5lÓmÓnˆKØ——“Ø—
‘
×"Ñ" ?°[°NÔC÷ ä�K‰KÐMÈ{Ô[õ	ð
 ×*Ñ*¨3Ó/ˆßÜ�K‰KÐcÔehÐiuÓevÔwÙ�|Ó1Ð1÷) ×Ðú÷ —ús   ³	E&Ã$E:Å&E7	Å:F
	)rk  )Nr¬   FFF)NNNNNNNNNNNNFNFNNNNNNr¬   N)NNNr¬   )FFF)NNNr½  NNNN)FFNr½  FNF)é   )FFFFF)FrU   )er�   Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__r‚   Úclassmethodr�   r“   Ústaticmethodrž   r¦   rª   rÂ   rð   rý   r  r  r  r*  r4  r8  r   rV  r;  rh  ro  rƒ  rˆ  r‚  rž  r¦  r¬  r°  r¼  rG  rÃ  rË  rã  rà  rþ  rõ  rý  r  r  r  r*  rG  rU  rS  r'  rp  rt  r{  r  rê   rŠ  ré   r™  r±  r=  rÇ  rà  rè  rð  rô  rù  rÿ  r  r  r  r,  r3  r	  rB  rG  rX  rA  ry  r�  r‡  rŒ  r•  r›  r   rN  rO  rP  r¸  r¿  rÇ  rÙ  r”  rà  rä  r~  rõ  rú  r  r!  Ú__static_attributes__r<   r@   r>   rm   rm   z   sr  † ÙuõFð ô6ó ð6ð, ôó ðð ô,ó ð,ð" ôó ðð  ô_ó ð_÷7<õr DðD ô)ó ð)õ.9õ&ðP ô
ró ð
r÷ V÷DDð*Ø$(ðØ<@ðØY^ôð( ! /°;Ð?€Oõð4(à^b÷(õ&dð
(Ø+1÷(õ4eõdð (Àf÷  (ðD
'ÀFð 
'ÐZ[ð 
'Ø37÷
'õ'õ'õðB%*Ø7;÷%*õNeõ÷ 6ð4[Ø<Að[Ø[`ð[àINð[ð TX÷[ðB ô@ó ð@õð& ôó ðõmõpõað
?Ð\`÷ ?õ0÷d)]ðVIà;?ðIà\`ðIð *.ðIð BCðIð @Dð	Ið
 04ðIð ?CðIð <@÷IõV+õ	Aõ
"õ?õ(;õuõyð ôSó ðSð ôó ðð4+È÷ 4+õl$eõL.õ((-õTð0 ðÐQUð Ø./ðØ@Eöó ðõ.õ	
ð ôpó ðp÷2põh	÷{õ&'<ðR
\Ø8=÷
\õ
_÷8÷&Cõ3ð,^Ø6;ð^à>C÷^õ@.õ4%õ8Uð$ÈE÷ $ñÐjo÷ ð õ(Nõ">ð ô^ó ð^ð ôó ðð ôó ðõ0Wð ôQó ðQõ
@ð&<Ðpuð <ØEIð<à9=÷<÷:Kõ
-õ	;ðZÐ\a÷ Zõ*Kõ6õ"!ð 2¸uð  2ÐUY÷  2ó  2r@   rm   >   r–  r$  r%  )r½  )ar'  Ú
__future__r   r�  rL   ÚloggingÚrerº   Útypingr   r   r   r   r   r   Úagent.context_compressorr	   ri  r
   r   r   r   Úagent.memory_contextr   Úagent.message_metadatar   r   r   r   r   r   r   r   r   r   r   r   Úagent.message_sanitizationr   r   Úhermes_cli.timefmtr   Úhermes_state_commonr   r    r!   r"   r#   r$   r%   r&   r'   Úhermes_state_detached_resultsr(   Úhermes_state_todor)   Úhermes_state_identityr*   r+   r,   r-   r.   r/   r0   Ú	getLoggerrš   r   rÊ   ÚfindallÚsplitr  Ú	frozensetræ  r   r½   r»   r˜  rî  rŽ  rÂ  rò  rô  rö  rÿ  r}  r<  rÔ  rÖ  r_  r˜   rF   rQ   rX   r^   rb   rk   rm   r<   r@   r>   Ú<module>r<     sÆ  ðñtõ #ã Û Û Û 	Û ß 8× 8÷<õ <õ 2÷c÷ c÷ có c÷ SÝ +÷I÷ Iõ Iõ FÝ 4÷S÷ Sñ Sð 
×	Ò	˜>Ó	*€ðoÐ ñ Ø‡J‚JˆvÐ*×0Ñ0°°aÓ8¸Õ;×AÑAÀ#ÀqÓIÈ!ÕLÓMóÐ ñ !Ð!7Ó8Ò;_Õ_Ð ðÐ ð eÐ ØbÐ Ø=Ð ð Ñ&Ð'9¸4ÐBaÕ;aÓbÐcÐckÐlð àfÐiÕÐ ð LÐNdÕdØ bõcÐ à[€ðPÐ àPÐ Ø_Ð ØOÐ ØiÐ ð"ZÐ àWÐ Ù‹8€õ	,õõa÷RõLõ
fôo2Ð6Ð8Nö o2r@   