+
    jHàjâ« ã                   óÎ  € R t ^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RIHt ^ RI	H
t
HtHtHtHtHtHtHt ^ RI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$H%t%H&t&H't(H)t)H*t*H+t,H-t-H.t.H/t/ ^ RI0H1t1H2t2 ]Pf                  ! R4      t4R	 R
 lt5RER R llt6]7! 4       t8Rt9R R lt:R R lt;R R lt<RFR R llt=RRRRRRRRRRRRR^ RRR RR!RR"R/R# R$ llt>R% R& lt?R' R( lt@R) R* ltARGtBR+tCR,tDR-tER.tF]G! 0 RHm4      tHR/ R0 ltIR1tJR2P—                  R3 RI 4       4      R2,           R5]2P˜                  ! R6R77      ,           R8,           ,           tMRFR9 R: lltNRJtOR;]OP—                  . R< RK 4       O]N! R4RLR=]J R>2,           4      N54      ,           R?,           tPRP—                  R@ RM 4       4      tQR;]OP—                  RA RN 4       4      ,           RB,           ]Q,           tR ! RC RD4      tSR# )OzÌSession lifecycle mixin for SessionDB: row upsert/inheritance, lifecycle
flags (end/reopen/archive/pin/hide/read), model_config patching, listing and
counting, delete cascades, and the auto-archive sweep.N)ÚPath)ÚAnyÚCallableÚDictÚListÚOptionalÚSequenceÚTupleÚUnion)ÚActivityProvenanceÚbound_activity_descriptionÚnormalize_activity_provenance)ÚSessionActiveWriteGuardError)Ú_LISTABLE_CHILD_SQLÚ_RECOVERABLE_END_REASONSÚ_RECOVERABLE_END_REASONS_SQLÚ_RESET_CHILD_SQLÚ_RESET_END_REASONSÚ_legacy_reset_child_sqlÚ_non_continuation_child_sqlÚ_shape_previewÚ_sql_preview_rawÚQUEUED_PROMPT_METADATA_KEYÚ_sql_in_windowÚ_sql_json_extractÚ_sql_session_last_activeÚ_sql_session_last_active_by_idÚescape_likeÚ_SQL_IN_CHUNKÚ
_id_chunksÚ_placeholdersÚ_source_match_clauseÚemit_session_deletedÚlaunch_stub_filter_sql)Ú
_CHAIN_CAPÚ_UNSET_CREATED_SQLÚhermes_statec                óh   € V ^8„  d   QhR\         \        \        3,          R\        \        ,          /# ©é   ÚrowÚreturn)ÚdictÚstrr   r   )Úformats   "Ú:/home/ubuntu/.hermes/hermes-agent/hermes_state_sessions.pyÚ__annotate__r0       s*   € ÷ ^ñ ^”tœC¤˜H•~ð ^¬(´3­-ñ ^ó    c                óÊ   € V P                  R4      ;'       g    RP                  4       ;'       g4    V P                  R4      ;'       g    RP                  4       ;'       g    R# )zsWorkspace grouping key: git repo root, else cwd, else None (branch excluded: a checkout must not
fragment history).Úgit_repo_rootÚ ÚcwdN)ÚgetÚstrip)r*   s   &r/   Úworkspace_keyr8       sN   € ð �G‰G�OÓ$×*Ð*¨×1Ñ1Ó3×]Ð]¸¿¹À»×8LÐ8LÈ"×7SÑ7SÓ7U×]Ð]ÐY]Ð]r1   c                ó0   € V ^8„  d   QhR\         R\         /# )r)   Úcolr+   ©r-   )r.   s   "r/   r0   r0   &   s   € ÷ 6ñ 6œSð 6´cñ 6r1   c                 ó   € \        V R 4      # )ú$._delegate_from©r   ©r:   s   &r/   Ú_delegate_from_jsonr@   &   s   € Ü˜SÐ"4Ó5Ð5r1   at  
            WITH RECURSIVE
              ancestors(id) AS (
                SELECT ?
                UNION
                SELECT parent.id
                FROM ancestors a
                JOIN sessions child ON child.id = a.id
                JOIN sessions parent ON parent.id = child.parent_session_id
                WHERE parent.end_reason = 'compression'
              ),
              descendants(id) AS (
                SELECT ?
                UNION
                SELECT child.id
                FROM descendants d
                JOIN sessions parent ON parent.id = d.id
                JOIN sessions child ON child.parent_session_id = parent.id
                WHERE parent.end_reason = 'compression'
              ),
              lineage(id) AS (
                SELECT id FROM ancestors
                UNION
                SELECT id FROM descendants
              )c                óR   € V ^8„  d   QhR\         R\        \        \         3,          /# )r)   Úrawr+   )r   r,   r-   )r.   s   "r/   r0   r0   K   s"   € ÷ 6ñ 6œSð 6¤T¬#¬s¨(¥^ñ 6r1   c                ó  € \        V \        4      '       d.   V P                  4       '       d    \        P                  ! V 4      p \        V \        4      '       d   \        V 4      # / #   \        P
                  \        3 d    / u # i ; i)zVTolerant ``model_config`` decode: JSON text or dict -> dict copy; anything else -> {}.)Ú
isinstancer-   r7   ÚjsonÚloadsÚJSONDecodeErrorÚ	TypeErrorr,   )rB   s   &r/   Ú_parse_model_configrI   K   si   € ä�#”s×Ò §	¡	§¢ð	Ü—*’*˜S“/ˆCô # 3¬×-Ò-Œ4�‹9Ð5°2Ð5øô ×$Ñ$¤iÐ0ô 	ØŠIð	ús   ®A' Á'BÂBc                óh   € V ^8„  d   QhR\         R\        \         \        \         ,          3,          /# )r)   Ú
cwd_prefixr+   ©r-   ÚtupleÚlist)r.   s   "r/   r0   r0   U   s'   € ÷ ñ ¤3ð ¬5´´d¼3µi°Õ+@ñ r1   c                 óf   € V P                  R 4      ;'       g    T p\        V4      pRW R2V R2.3# )ú/\zA(s.cwd = ? OR s.cwd LIKE ? ESCAPE '\' OR s.cwd LIKE ? ESCAPE '\')z/%z\\%)ÚrstripÚ_escape_like)rK   ÚprefixÚescs   &  r/   Ú_cwd_prefix_clauserU   U   sG   € Ø×Ñ˜uÓ%×3Ð3¨€Fô �vÓ
€CàMØ	�5˜� ˜u E˜]Ð+ðð r1   c                óh   € V ^8„  d   QhR\         R\        \         \        \         ,          3,          /# ©r)   Úkeyr+   rL   )r.   s   "r/   r0   r0   `   s'   € ÷ ñ œsð ¤u¬S´$´sµ)¨^Õ'<ñ r1   c                óh   € V P                  R4      ;'       g    T p\        V4      w  r#RV R2V.VO3# )zŒWHERE for ``workspace_key(row) == key``: git_repo_root equals ``key``, or (rows predating
per-session git metadata) cwd is at/under ``key``.rP   z@(s.git_repo_root = ? OR (COALESCE(s.git_repo_root, '') = '' AND ú)))rQ   rU   )rX   rS   Ú
cwd_clauseÚ
cwd_paramss   &   r/   Ú_workspace_key_clauser]   `   sI   € ð �Z‰Z˜Ó×%Ð% #€FÜ/°Ó7Ñ€Jà
JÈ:È,ÐVXÐYØ	Ð�*Ððð r1   r4   c                óR   € V ^8„  d   QhR\         \        ,          R\        R\        /# )r)   ÚclausesÚleadr+   ©rN   r-   )r.   s   "r/   r0   r0   k   s*   € ÷ Eñ EœœS�	ð E¬ð E´cñ Er1   c                óB   € V '       d   V RRP                  V 4       2# R# )zG``WHERE a AND b`` (with *lead* prefix) or "" when there are no clauses.úWHERE ú AND r4   )Újoin)r_   r`   s   &&r/   Ú
_where_sqlrf   k   s$   € ç5<ˆdˆV�6˜'Ÿ,™, wÓ/Ð0Ð1ÐDÀ"ÐDr1   Úexclude_childrenFÚsourceÚsourcesÚsession_keyÚexclude_sourcesrK   Úmin_message_countÚarchived_onlyÚinclude_archivedÚinclude_subagentsÚlocal_created_sourcesc                óŒ  € V ^8„  d   QhR\         R\        R,          R\        \        ,          R,          R\        R,          R\        \        ,          R,          R\        R,          R\        R	\         R
\         R\         R\        \        ,          R,          R\
        \        \        ,          \        \        ,          3,          /# )r)   rg   rh   Nri   rj   rk   rK   rl   rm   rn   ro   rp   r+   )Úboolr-   rN   Úintr   rM   r   )r.   s   "r/   r0   r0   p   s¸   € ÷ (ñ (Üð(Ü/2°T­zð(ÜKOÔPSÍ9ÐW[ÕK[ð(ä�t•ð(ä59¼#µYÀÕ5Eð(äZ]Ð`dÕZdð(ô ð(ô 04ð(ô PTð(ô ð	(ô =EÄS½MÈDÕ<Pð	(ô
 Œ4”�9”dœ3•iÐÕ ñ(r1   c                ó¤  € . p. pV '       d/   V	'       d'   VP                  R\         R\        R4       R24       M#V '       d   V\        \        R4       R2.,          pV'       d   V.M\        T;'       g    . 4      p\	        WÚ4      w  rïWï3RV'       d   V.M. 3R\        T;'       g    R4       R2T;'       g    . 3V'       d   \        V4      MR	. 3R
V^ 8”  d   V.M. 33 F2  w  ppV'       g   K  VP                  V4       VP                  V4       K4  	  V'       d   VP                  R4       W¼3# V'       g   VP                  R4       W¼3# )aP  Shared ``sessions s`` WHERE builder so counts line up with listed rows. ``exclude_children``
hides sub-agent runs and compression continuations but keeps branch/reset children
(``_LISTABLE_CHILD_SQL``); ``include_subagents`` re-admits the sub-agent runs only
(``sessions.show_subagents``). Clause order is part of the SQL text contract.Ú(z OR ús.model_configú IS NOT NULL)ú IS NULLzs.session_key = ?ús.source NOT IN (Ú)r4   zs.message_count >= ?zs.archived = 1ús.archived = 0© )Úappendr   r@   rN   r!   Ú_session_ids_placeholdersrU   Úextend)rg   rh   ri   rj   rk   rK   rl   rm   rn   ro   rp   ÚwhereÚparamsÚinclude_sourcesÚsource_clauseÚsource_paramsÚclauseÚvaluess   $$$$$$$$$$$       r/   Ú_session_filter_wherer‡   p   sM  € ð €EØ€Fß×-Ø�‰�qÔ,Ð-¨TÔ2EÐFVÓ2WÐ1XÐXeÐfÕgß	ØÔ%Ô*=Ð>NÓ*OÐ)PÐPXÐ'YÐZÕZˆ÷ #)�v‘h¬d°7·=°=¸bÓ.A€OÜ#7¸Ó#_Ñ €Mà	Ð&Ø	¯{˜{™mÀÐCØÔ6°×7LÐ7LÈ"ÓMÐNÈaÐ	PÐRa×RgÐRgÐegÐhß+5Ô	˜JÔ	'¸BÀ¸8Ø	Ð8IÈAÔ8MÐ"3Ñ!4ÐSUÐVó‰ˆ�÷ ‰6Ø�L‰L˜Ô Ø�M‰M˜&Ö!ñ÷ Ø�‰Ð%Ô&ð ˆ=Ð÷ Ø�‰Ð%Ô&Øˆ=Ðr1   c                ó\   € V ^8„  d   QhR\         \        ,          R\         \        ,          /# )r)   Úfrontierr+   ra   )r.   s   "r/   r0   r0   ›   s"   € ÷ !ñ !´D¼µIð !Ä$ÄsÅ)ñ !r1   c           
     ó  € V Uu. uF  q"'       g   K  VNK  	  ppV'       g   . # . p\        V\        ^,          4       Fº  p\        V4      p\        RR4       R\        RR4       R\        P
                  ! RR7       R2pVP                  R	 V P                  R
V RV 2V4      P                  4        4       4       VP                  R V P                  RV RV 2V4      P                  4        4       4       K¼  	  V# u upi )u˜  One both-direction compression-edge step from *frontier*: forward through every
continuation child and backward through the compression parent, using the SAME edge
definition as ``_CHAIN_STEP_SQL`` / the list-projection CTE (parent ended with
``reason='compression'``, child is not a branch / reset / delegate / tool session).
Branch/reset children are NOT continuations â€” they are their own conversations.úchild.model_configú$._branched_fromú IS NULL AND r=   z IS NULL AND NOT (Úchild©Úaz*) AND COALESCE(child.source, '') != 'tool'c              3   ó2   "  € T F  qR ,          x € K  	  R# 5i©ÚidNr|   ©Ú.0r*   s   & r/   Ú	<genexpr>Ú2_expand_compression_lineage_ids.<locals>.<genexpr>­   s   é € ð ñ ) �t—9’9ó )ùó   ‚ztSELECT child.id FROM sessions parent JOIN sessions child ON child.parent_session_id = parent.id WHERE parent.id IN (z,) AND parent.end_reason = 'compression' AND c              3   ó2   "  € T F  qR ,          x € K  	  R# 5ir’   r|   r”   s   & r/   r–   r—   ´   s   é € ð ñ ) �t—9’9ó )ùr˜   ztSELECT parent.id FROM sessions child JOIN sessions parent ON parent.id = child.parent_session_id WHERE child.id IN ()	r   r   r~   r   r   r.   r   ÚexecuteÚfetchall)Úconnr‰   ÚsidÚidsÚoutÚchunkÚphÚchild_filters   &&      r/   Ú_expand_compression_lineage_idsr£   ›   s=  € ñ #Ó
*™(�3£c�3ˆ3™(€CÐ
*ßØˆ	Ø€CÜ˜C¤°!Õ!3Ö4ˆÜ& uÓ-ˆä Ð!5Ð7IÓJÐKð LÜ$Ð%9Ð;MÓNÐOð PÜ(×/Ò/°'Ô:Ð;ð <7ð7ð 	ð 	�
‰
ñ ¨¯©ð#à#% $Ð&RÐS_ÐR`ðbð ó)
÷ ‰(‹*ð)ó ô 	ð 	�
‰
ñ ¨¯©ð"à"$ ð &Ø�.ð"ð ó)
÷ ‰(‹*ð)ó ö 	ñ 5ð. €Jùò7 +s
   …	C>“C>c                ó\   € V ^8„  d   QhR\         \        ,          R\         \        ,          /# ©r)   Ú
parent_idsr+   ra   )r.   s   "r/   r0   r0   ¿   s"   € ÷ 6ñ 6´$´sµ)ð 6ÄÄSÅ	ñ 6r1   c                ó$  € \        4       pV Uu0 uF  q3'       g   K  VkK  	  pp\        V4      p\        V4      pV'       d¬   . p\        V\        ^,          4       FŠ  p\        V4      p	V P                  RV RV	 RV	 RV R2	Wˆ,           4      p
V
P                  4        FC  pVR,          V9  g   K  VP                  VR,          4       VP                  VR,          4       KE  	  KŒ  	  TpK³  V Uu. uF  q3V9  g   K  VNK  	  up# u upi u upi )z˜Delegate-subagent ids (``_delegate_from`` marker, walked recursively) to cascade-delete with
*parent_ids*; untagged children stay orphaned, not deleted.úSELECT id FROM sessions WHERE z IN (z) OR (parent_session_id IN (z) AND rw   r“   )
r@   ÚsetrN   r   r   r~   rš   r›   Úaddr}   )rœ   r¦   Údfr�   ÚseedsÚfoundr‰   Únext_frontierr    r¡   Úcursorr*   s   &&          r/   Ú_collect_delegate_child_idsr°   ¿   s  € ô 
Ó	€BÙ&Ó.™J�S«#�SˆS™J€EÐ.ô ˜%“j€EÜ�E‹{€Hß
Ø#%ˆÜ ¬-¸1Õ*<Ö=ˆEÜ*¨5Ó1ˆBØ—\‘\Ø0°°°E¸"¸ð >-Ø-/¨D°°r°d¸-ðIØJOÍ-óˆFð —‘Ö(�Ø�t•9 EÖ)Ø—I‘I˜c $�iÔ(Ø!×(Ñ(¨¨T­Ö3ó )ñ >ð !ŠÙ Ó5™5�C¨uÑ$4�CˆC™5Ñ5Ð5ùò/ /ùò. 6s   �	D�DÃ2DÃ?Dc                ó\   € V ^8„  d   QhR\         \        ,          R\         \        ,          /# r¥   ra   )r.   s   "r/   r0   r0   Ý   s"   € ÷ 
ñ 
´´Sµ	ð 
¼dÄ3½iñ 
r1   c                 óö   € \        W4      p^ RIHp V! W4       \        V4       FP  p\	        V4      pV P                  RV R2V4       V P                  RV R2V4       V P                  RV R2V4       KR  	  V# )é    ©Úretire_sessionsú*DELETE FROM messages WHERE session_id IN (rz   úIUPDATE sessions SET parent_session_id = NULL WHERE parent_session_id IN (ú"DELETE FROM sessions WHERE id IN ()r°   Ú hermes_state_mutation_retirementrµ   r   r~   rš   )rœ   r¦   rž   rµ   r    r¡   s   &&    r/   Ú_delete_delegate_childrenrº   Ý   s‚   € Ü
% dÓ
7€CÝ@Ù�DÔÜ˜C–ˆÜ& uÓ-ˆØ�‰ÐAÀ"ÀÀQÐGÈÔOà�‰Ð`ÐacÐ`dÐdeÐfÐhmÔnØ�‰Ð9¸"¸¸QÐ?ÀÖGñ !ð €Jr1   ÚcompleteÚinterruptedÚerrorÚemptyc                ót   € V ^8„  d   QhR\         \        ,          R\        R\         \        ,          R\        /# )r)   ÚroleÚhas_tool_callsÚfinish_reasonr+   )r   r-   rr   )r.   s   "r/   r0   r0   ú   s4   € ÷ #ñ #¤(¬3¥-ð #Äð #ÔV^Ô_bÕVcð #Ôhkñ #r1   c                ó  € T;'       g    RP                  4       P                  4       \        9   d   \        # T ;'       g    RP                  4       P                  4       pVR9   g   VR8X  d   V'       d   \        # \
        # )uÁ   Error finish â†’ ``error``; assistant with pending tool_calls or a trailing user/tool row â†’
``interrupted``; otherwise ``complete`` (benign default: pickers must not alarm on unknown shapes).r4   Ú	assistant>   ÚtoolÚuser)r7   ÚlowerÚ_ERROR_FINISH_REASONSÚSESSION_STATUS_ERRORÚSESSION_STATUS_INTERRUPTEDÚSESSION_STATUS_COMPLETE)rÀ   rÁ   rÂ   Úrs   &&& r/   Úclassify_session_statusrÍ   ú   sh   € ð 	×Ð˜×"Ñ"Ó$×*Ñ*Ó,Ô0EÔEÜ#Ð#Ø	�ˆ�×ÑÓ×"Ñ"Ó$€AØÐÔ  kÔ!1·nÜ)Ð)Ü"Ð"r1   zÏp.session_key IS NULL OR sessions.session_key IS NULL OR substr(p.session_key, 1, instr(substr(p.session_key, 7), ':') + 6)  = substr(sessions.session_key, 1, instr(substr(sessions.session_key, 7), ':') + 6)ú,
c              #   ó:   "  € T F  pR V RV RV R2x € K  	  R# 5i)z                       ú = COALESCE(sessions.z, excluded.rz   Nr|   )r•   r:   s   & r/   r–   r–     s2   é € ð 'ñ[ÐSVÐ˜c˜UÐ"7¸°u¸KÈÀuÈAÕNó [ùs   ‚Úprofile_namez2                       created_source = CASE WHEN zsessions.created_sourcer?   zr THEN COALESCE(NULLIF(excluded.created_source, 'local'), sessions.created_source) ELSE sessions.created_source ENDc                ó<   € V ^8„  d   QhR\         R\         R\         /# )r)   r:   Úextrar+   r;   )r.   s   "r/   r0   r0   $  s!   € ÷ ñ œ#ð ¤cð ´3ñ r1   c                ó\   € R^\        V 4      ,           ,          pV  RV  RV RV  RV RV R2# )zb``col = COALESCE(sessions.col, (SELECT p.col FROM parent))`` (whitespace is part of the SQL text).Ú rÐ   rÎ   z
(SELECT p.z FROM sessions p
z)  WHERE p.id = sessions.parent_session_idrZ   )Úlen)r:   rÓ   Úpads   && r/   Ú_inherit_col_sqlrØ   $  sH   € à
�”c˜#“h•Õ
€Càˆ%Ð$ S E¨¨S¨E°¸C¸5Ð@RØˆ%Ð8¸¸¸rð	Cðr1   z+UPDATE sessions
                       SET c              #   ó8   "  € T F  p\        V4      x € K  	  R # 5i©N©rØ   ©r•   Úcs   & r/   r–   r–   1  s   é € Ð	NÑ'M !Ô
˜1×
Ð
Ó'Mùó   ‚zAND (rz   zD
                     WHERE id = ? AND parent_session_id IS NOT NULLc              #   óH   "  € T F  pR \        RRV 24       R2x € K  	  R# 5i)z%
                       AND COALESCE(Úmodel_configú$.z5, '')
                           != parent_session_idNr>   )r•   Úmarkers   & r/   r–   r–   :  s:   é € ð #ñ 7ˆð -Ô->¸~ÐQSÐTZÐS[È}Ó-]Ð,^ð _8õ 8ã6ùs   ‚ "c              #   ó8   "  € T F  p\        V4      x € K  	  R # 5irÚ   rÛ   rÜ   s   & r/   r–   r–   A  s!   é € ð ñ 6°Ô(¨×+Ð+ó 6ùrÞ   a6  
                     WHERE id = ? AND parent_session_id IS NOT NULL
                       AND EXISTS (
                           SELECT 1 FROM sessions p
                           WHERE p.id = sessions.parent_session_id
                             AND p.end_reason = 'compression'
                       )c            
       ó¤  a € ] tR tRt o RtV 3R lR lt]V 3R lR l4       tR´V 3R lR	 lltV 3R
 lR lt	RµV 3R lR llt
R¶V 3R lR lltRRRR/V 3R lR lltRt]! R4      tV 3R lR ltV 3R lR lt]V 3R lR l4       tV 3R lR ltV 3R lR  ltR·V 3R! lR" lltR¸V 3R$ lR% lltR¹V 3R& lR' lltV 3R( lR) ltRºR*RR+R/V 3R, lR- llltV 3R. lR/ ltRºV 3R0 lR1 lltV 3R2 lR3 ltV 3R4 lR5 ltRºR6RR7R/V 3R8 lR9 llltR»V 3R: lR; lltR<R=/V 3R> lR? llt V 3R@ lRA lt!RºV 3RB lRC llt"V 3RD lRE lt#V 3RF lRG lt$RHRRIRRJRRKRRLR#/V 3RM lRN llt%V 3RO lRP lt&]V 3RQ lRR l4       t'V 3RS lRT lt(V 3RU lRV lt)V 3RW lRX lt*V 3RY lRZ lt+V 3R[ lR\ lt,V 3R] lR^ lt-R_R`/V 3Ra lRb llt.R_R`/V 3Rc lRd llt/ReR/V 3Rf lRg llt0V 3Rh lRi lt1]V 3Rj lRk l4       t2]3t4V 3Rl lRm lt5V 3Rn lRo lt6V 3Rp lRq lt7R¶V 3Rr lRs llt8]V 3Rt lRu l4       t9]:! 0 R¼m4      t;Rt<]V 3Rv lRw l4       t=V 3Rx lRy lt>Rz^R{RR|R}R~RRR/V 3R€ lR� llt?]@V 3R‚ lRƒ l4       tAR½V 3R„ lR… lltBV 3R† lR‡ ltCRºV 3Rˆ lR‰ lltDRºV 3RŠ lR‹ lltEV 3RŒ lR� ltFV 3RŽ lR� ltGR¾V 3R� lR‘ lltHR¿V 3R’ lR“ lltIRÀV 3R” lR• lltJR–R#R—R#R˜R#/V 3R™ lRš lltKV 3R› lRœ ltL]V 3R� lRž l4       tMV 3RŸ lR  ltNV 3R¡ lR¢ ltORÁV 3R£ lR¤ lltPV 3R¥ lR¦ ltQRºV 3R§ lR¨ lltRRÂV 3R© lRª lltSR«tTV 3R¬ lR­ ltURºV 3R® lR¯ lltVR¹V 3R° lR± lltWV 3R² ltXR³tYV tZR# )ÃÚSessionSessionsMixiniO  zOSession rows: create/inherit, lifecycle flags, model_config, listing, deletion.c                ó0   <€ V ^8„  d   QhRS[ S[,          /# ©r)   r+   ©r   r-   )r.   Ú__classdict__s   "€r/   r0   Ú!SessionSessionsMixin.__annotate__R  s   ø€ ÷ ñ ¡8©C¥=ñ r1   c                óœ  €  ^ RI Hp V! 4       P                  4       p\        V P                  4      P                  4       P
                  pW28X  d   R# VP
                  VR,          8H  pV'       d4   \        P                  ! RVP                  4      '       d   VP                  # R#   \         d    \        P                  RRR7        R# i ; i)	u
  The profile owning THIS store, from ``db_path`` alone (``<root>/state.db`` â†’ default,
``<root>/profiles/<name>/state.db`` â†’ name): a gateway serving a NON-launch profile opens that
profile's store. None outside the profile tree â€” NULL beats a fabricated owner.)Úget_default_hermes_rootÚdefaultÚprofilesz[a-z0-9][a-z0-9_-]{0,63}zown-profile derivation failedT)Úexc_infoN)Úhermes_constantsrì   Úresolver   Údb_pathÚparentÚreÚ	fullmatchÚnameÚ	ExceptionÚloggerÚdebug)Úselfrì   Úrootró   Úis_profile_dirs   &    r/   Ú_own_profile_nameÚ&SessionSessionsMixin._own_profile_nameR  s¢   € ð
	IÝ@Ù*Ó,×4Ñ4Ó6ˆDÜ˜$Ÿ,™,Ó'×/Ñ/Ó1×8Ñ8ˆFØŒ~Ù Ø#Ÿ]™]¨d°ZÕ.?Ñ?ˆNß¤"§,¢,Ð/JÈFÏKÉK×"XÒ"XØ—{‘{Ð"ñ øô ô 	IÜ�L‰LÐ8À4ˆLÕHÙð	Iús$   ‚AB% ÁB% Á0&B% ÂB% Â%"CÃ
Cc                ó$   <€ V ^8„  d   QhRS[ RR/# ©r)   Ú
session_idr+   Nr;   )r.   ré   s   "€r/   r0   rê   d  s   ø€ ÷ 	Añ 	A¹3ð 	AÀ4ñ 	Ar1   c                ób   € V P                  \        V34       V P                  \        V34       R# )u7  NULL-fill a child's cwd/git/profile from its parent (profile_name only within the same
``agent:<ns>:`` namespace). Gateway routing columns are inherited ONLY by compression forks
(a crash before the gateway re-records the peer would strand the child unroutable); delegate
and branch children must NOT inherit them (peer recovery could repoint traffic into a
subagent's session), including when their parent row itself ended on compression â€” a long
batch outlives its coordinator's rotation, and two live rows holding one routing key is the
shape reported in #92859.N)rš   Ú_INHERIT_PARENT_META_SQLÚ_INHERIT_PARENT_ROUTING_SQL©rœ   r  s   &&r/   Ú _inherit_parent_session_metadataÚ5SessionSessionsMixin._inherit_parent_session_metadatac  s&   € ð 	�‰Ô-°
¨}Ô=Ø�‰Ô0°:°-Ö@r1   Nc          $      ór  <€ V ^8„  d   QhRS[ RS[ RS[ R,          RS[S[ S[3,          R,          RS[ R,          RS[ R,          RS[S[ ,          R	S[ R,          R
S[ R,          RS[ R,          RS[ R,          RS[ R,          RS[S[ ,          RS[ R,          RS[ R,          RS[ R,          RS[S[ ,          RR/# )r)   r  rh   ÚmodelNrà   Úsystem_promptÚuser_idrj   Úchat_idÚ	chat_typeÚ	thread_idÚparent_session_idr5   rÑ   r3   Úorigin_jsonÚdisplay_nameÚtransport_profiler+   )r-   r,   r   r   )r.   ré   s   "€r/   r0   rê   o  s  ø€ ÷ -Jñ -JÙð-JÙ'*ð-JÙ36¸µ:ð-JÙTXÑY\Ñ^aÐYaÕTbÐeiÕTið-Já˜T•zð-Já36¸µ:ð-JáS[Ñ\_ÕS`ð-Jñ �t•ð-Jñ 03°T­zð-Jñ NQÐSWÍZð-Jñ  �:ð	-Jñ 47¸µ:ð	-Jñ U]Ñ]`ÕTað	-Jñ
 ˜T•zð-Jñ
 8;¸Tµzð-Jñ
 Y\Ð^bÕXbð-Jñ $¡C�=ð-Jð 
ñ-Jr1   c                óÖ   a aa€ ^ RI Ho \        R/ RVbRVbRVbRVbRVbRVbRVbR	VbR
V	bRV
bRVbRVbRVbRVbRVbRVbRVb oS P                  VVV 3R lS P                  R7       R# )u‘	  Upsert a session row, never overwriting what an earlier writer set (the gateway creates a
bare row before create_session carries the real model/prompt) â€” the one exception is the
token-accounting guard's placeholder ``source='unknown'``, which a later writer's real surface
replaces (#111999): once minted, that placeholder otherwise labelled a real session anonymous
for life, because this upsert is the only writer that could correct it. chat_id/thread_id scope gateway
/resume (IDOR). Children backfill from the parent; a missing profile_name is stamped with THIS
store's own (NULL reads as unowned).

When ``parent_session_id`` is set (compression fork, delegate/subagent spawn, branch continuation)
and this row's own ``cwd``/``git_repo_root``/ ``git_branch``/``profile_name`` are still NULL after
the insert, they are backfilled from the parent row. Callers of ``create_session`` for a child
session historically didn't propagate these fields themselves (e.g. the compression-fork path), so a
lineage could silently lose its working directory and drop out of the project sidebar every time it
forked (#64709), or lose its owning profile and be aggregated as "default" every time it rotated or
branched (the cross-profile session-jump bug). This only fills NULLs â€” an explicit value on the
child is never overwritten. For compression forks specifically (parent ended with
``end_reason='compression'``), the gateway origin columns
(``user_id``/``session_key``/``chat_id``/``chat_type``/
``thread_id``/``display_name``/``origin_json``) are inherited too, so a crash before the gateway
re-records the peer can't strand the child without a recoverable routing mapping (#59527).
When the caller passes no ``profile_name`` at all, the row is stamped with THIS store's own profile
(:meth:`_own_profile_name`) instead of NULL. Every ``state.db`` belongs to exactly one profile â€” the
same single-match contract :meth:`backfill_null_session_profiles` relies on â€” so the stamp is
derivation, not a guess. Rows minted NULL after that one-shot #94724 backfill ran stayed NULL
forever, and profile-keyed consumers (desktop sidebar scope matching, ``@session:<profile>/<id>``
deep links, the fail-closed owner ladder) treat NULL as unowned: the session vanishes from the
sidebar even though its transcript is intact (#99222). Stores outside the profile tree (explicit
``db_path`` in tests, ad-hoc copies) derive nothing and keep NULL â€” never guess.
)Ú!insert_session_row_in_transactionr  rh   r	  rà   r
  r  rj   r  r  r  r  r5   rÑ   r3   r  r  r  c                 ó   <€ S! SV 3/ SB # rÚ   r|   )rœ   r  r�   rú   s   &€€€r/   Ú<lambda>Ú:SessionSessionsMixin._insert_session_row.<locals>.<lambda>›  s   ø€ Ñ)JÈ4ÐQUÑ)`ÐY_Ò)`r1   ©Ú
patience_sNr|   )Úhermes_state_worker_lifecycler  r,   Ú_execute_writeÚ_TRANSCRIPT_WRITE_PATIENCE_S)rú   r  rh   r	  rà   r
  r  rj   r  r  r  r  r5   rÑ   r3   r  r  r  r  r�   s   f&&&&&&&&&&&&&&&&&@@r/   Ú_insert_session_rowÚ(SessionSessionsMixin._insert_session_rowo  sÑ   ú€ õJ 	TÜò ;¡ð ;±Fð ;Á%ð ;ÑVbð ;Ù$1ð;Ù;Bð;ÙP[ð;á%ð;á1:ð;áFOð;ñ ):ð;ñ @Cð;ñ R^ð;ñ %2ð	;ñ @Kð	;ñ Zfð	;ñ
 ):ð;ˆð 	×ÑÖ`Ø'+×'HÑ'Hð 	ö 	Jr1   c                ó,   <€ V ^8„  d   QhRS[ RS[ RS[ /# )r)   r  rh   r+   r;   )r.   ré   s   "€r/   r0   rê   ž  s"   ø€ ÷ ñ ©ð ±cð Éñ r1   c                ó,   € V P                   ! W3/ VB  V# )z9Create (upsert) a session record. Returns the session_id.©r  )rú   r  rh   Úkwargss   &&&,r/   Úcreate_sessionÚ#SessionSessionsMixin.create_sessionž  s   € à× Ò  Ñ>°vÒ>ØÐr1   c                ó@   <€ V ^8„  d   QhRS[ RS[ RS[ R,          RS[ /# )r)   r  rh   r	  Nr+   r;   )r.   ré   s   "€r/   r0   rê   £  s/   ø€ ÷ ñ ©ð ±cð ÉcÐTXÍjð Ñnqñ r1   c                ó0   € V P                   ! W3RV/VB  V# )z>Ensure a session row exists (upsert). Accepts optional kwargs.r	  r!  )rú   r  rh   r	  r"  s   &&&&,r/   Úensure_sessionÚ#SessionSessionsMixin.ensure_session£  s    € à× Ò  ÑK¸5ÐKÀFÒKØÐr1   Tc                ó*   <€ V ^8„  d   QhRS[ RS[RR/# )r)   r  Ú	finalizedr+   N©r-   rr   )r.   ré   s   "€r/   r0   rê   ¨  s"   ø€ ÷ 	
ñ 	
©sð 	
¹tð 	
Ètñ 	
r1   c                óV   € V'       g   R# T P                  RV'       d   ^M^ V34       R# )zZMirror ``SessionEntry.expiry_finalized`` so it survives a lost sessions.json.

See #9006.
Nz5UPDATE sessions SET expiry_finalized = ? WHERE id = ?)Ú
_write_sql)rú   r  r*  s   &&&r/   Úset_expiry_finalizedÚ)SessionSessionsMixin.set_expiry_finalized¨  s&   € ÷
 ÙØ�‰ØCÏ9ÁaÐZ[Ð]gÐEhö	
r1   r  r  c                óh   <€ V ^8„  d   QhRS[ RS[ RS[S[ ,          RS[S[ ,          RS[S[ ,          /# )r)   Úplatformr  r  r  r+   ©r-   r   )r.   ré   s   "€r/   r0   rê   ³  sC   ø€ ÷ "ñ "Ùð"Ù),ð"Ù9AÁ#½ð"á™#•ð"ñ 
‘#�ñ"r1   c               ó¼  € V'       d   VR	9   d   R# RpV\        V4      .pVe$   VR,          pVP                  \        V4      4       V P                  VR,           V4       Uu. uF  p\        V4      NK  	  ppV'       g   R# V'       dz   V Uu. uF9  p\        VP	                  R4      ;'       g    R4      \        V4      8X  g   K7  VNK;  	  p	pV	'       d   \        V	^ ,          R,          4      # \        V4      ^8”  d   R# M2\        R V 4        U
u0 uF  qª'       g   K  V
kK  	  up
4      ^8”  d   R# \        V^ ,          R,          4      # u upi u upi u up
i )
u¾   Most recent live session_id for source + chat_id (+ thread_id). With ``user_id`` exact sender
matches win; several distinct users and no match â†’ None (never another participant's session).Nr4   zØ
            SELECT id, user_id, started_at FROM sessions
            WHERE LOWER(source) = LOWER(?)
              AND session_key IS NOT NULL
              AND chat_id = ?
              AND ended_at IS NULL
        z  AND COALESCE(thread_id, '') = ?z ORDER BY started_at DESCr  r“   c              3   ó†   "  € T F7  p\        VP                  R 4      ;'       g    R4      P                  4       x € K9  	  R# 5i)r  r4   N)r-   r6   r7   )r•   rÌ   s   & r/   r–   Ú>SessionSessionsMixin.find_session_by_origin.<locals>.<genexpr>Ï  s2   é € ÐPÉ4Àaœc !§%¡%¨	Ó"2×"8Ð"8°bÓ9×?Ñ?×AÐAË4ùs
   ‚!A¤A)Nr4   )r-   r}   Ú	_read_allr,   r6   rÖ   )rú   r1  r  r  r  Úqueryr�   rÌ   ÚrowsÚexactÚus   &$$$$      r/   Úfind_session_by_originÚ+SessionSessionsMixin.find_session_by_origin³  s0  € ÷ ˜7 jÔ0Ùðˆð !¤# g£,Ð/ˆØÒ ØÐ7Õ7ˆEØ�M‰Mœ#˜i›.Ô)Ø!%§¡°Ð8SÕ0SÐU[Ô!\Ó]Ñ!\˜A”�Q–Ñ!\ˆÐ]ßÙßÙ $ÓT¡˜1¬¨A¯E©E°)Ó,<×,BÐ,BÀÓ(CÄsÈ7Ã|Ñ(S—Q�Q¡ˆEÐTßÜ˜5 �8 D�>Ó*Ð*Ü�4‹y˜1Œ}Ùð äÑPÉ4ÔPÓVÑP˜ÓTU—!�!ÑPÑVÓWÐZ[Ô[ÙÜ�4˜•7˜4•=Ó!Ð!ùò ^ùò Uùò
 Ws$   Á#EÂ!EÂ1EÃEÄ	EÄ"Eg      Œ@z{alias}c                ó*   <€ V ^8„  d   QhRS[ RS[ RR/# )r)   r  Ú
end_reasonr+   Nr;   )r.   ré   s   "€r/   r0   rê   Û  s"   ø€ ÷ ñ ¡cð ±sð ¸tñ r1   c                ó:   a aa€ S P                  VV V3R l4       R# )z¨Mark a session ended; the first end_reason wins (a compression split must keep
``'compression'`` even if a stale end_session() lands later); reopen_session() to re-end.c                 óZ   <€ SP                  V R \        P                  ! 4       SS3SS4      # )zRUPDATE sessions SET ended_at = ?, end_reason = ? WHERE id = ? AND ended_at IS NULL)Ú_end_and_bumpÚtime)rœ   r>  rú   r  s   &€€€r/   r  Ú2SessionSessionsMixin.end_session.<locals>.<lambda>Þ  s*   ø€ ¨×);Ñ);ØÐfÜ�YŠY‹[˜* jÐ1°:¸zô*
r1   N©r  )rú   r  r>  s   fffr/   Úend_sessionÚ SessionSessionsMixin.end_sessionÛ  s   ú€ ð 	×Ñö 
ö 	r1   c          
      ó8   <€ V ^8„  d   QhRS[ RS[RS[ RS[ RS[/# )r)   Úsqlr�   r  Úreasonr+   )r-   rM   rs   )r.   ré   s   "€r/   r0   rê   ã  s2   ø€ ÷ ñ ¡sð ±Eð Ásð ÑTWð Ñ\_ñ r1   c                óp   € VP                  W#4      P                  pV'       d   V P                  WV4       V# )zœRun an end-stamp UPDATE; only a boundary this call actually wrote advances the
conversation generation (a no-op must not rotate the peer). Returns rowcount.)rš   ÚrowcountÚ_bump_conversation_generation)rú   rœ   rH  r�   r  rI  Úchangeds   &&&&&& r/   rA  Ú"SessionSessionsMixin._end_and_bumpã  s0   € ð —,‘,˜sÓ+×4Ñ4ˆßØ×.Ñ.¨tÀÔHØˆr1   c                ó&   <€ V ^8„  d   QhRS[ RS[/# ©r)   r  r+   ©r-   rs   )r.   ré   s   "€r/   r0   rê   ì  s   ø€ ÷ ñ ¹Cð ÁCñ r1   c                ón   € V P                  R\        RR\        ,           4       R2V34      P                  # )zXThe still-marked == never-drained accept-row UPDATE (#125577), on a caller's connection.zbUPDATE messages SET active = 0 WHERE session_id = ? AND role = 'user' AND active = 1 AND COALESCE(Údisplay_metadatará   z, 0) = 1)rš   r   r   rK  r  s   &&r/   Ú!_retire_undrained_queue_rows_connÚ6SessionSessionsMixin._retire_undrained_queue_rows_connë  sE   € ð �|‰|ðä-Ð.@À$ÔIcÕBcÓdÐeÐemðoð ˆMó	
÷
 ‰(ð	r1   c                ó&   <€ V ^8„  d   QhRS[ RS[/# rP  rQ  )r.   ré   s   "€r/   r0   rê   õ  s   ø€ ÷ Sñ S±cð S¹cñ Sr1   c                ó2   a a€ S P                  V V3R l4      # )u<  Deactivate busy-queue accept rows that never drained (#125577): a restart discarded the
in-memory queue, so nothing re-placed/deactivated the row written at accept time and
alternation repair would glue the never-run prompt into the previous turn's user message.
The drain's replacement row carries no marker, so still-marked == never drained; a
dispatched-then-interrupted turn's row is never marked (its discard is handled in-process,
#123532). Deactivated, never deleted â€” the row stays as history.

Split out of :meth:`reopen_session` so the read-only mount paths (#85303: resume,
lazy watch, hydration) can retire stale accept rows WITHOUT clearing ``ended_at`` â€”
after a restart the marked row would otherwise stay active and visible on every
mount until the next send. Idempotent; returns the number of rows retired.
c                 ó(   <€ SP                  V S4      # rÚ   )rT  ©rœ   rú   r  s   &€€r/   r  ÚBSessionSessionsMixin.retire_undrained_queue_rows.<locals>.<lambda>  s   ø€ ˜×?Ñ?ÀÀjÔQr1   rD  ©rú   r  s   ffr/   Úretire_undrained_queue_rowsÚ0SessionSessionsMixin.retire_undrained_queue_rowsõ  s   ù€ ð ×"Ñ"ÝQóSð 	Sr1   c                ó$   <€ V ^8„  d   QhRS[ RR/# r   r;   )r.   ré   s   "€r/   r0   rê     s   ø€ ÷ !ñ !©ð !°ñ !r1   c                ó:   a a€ V V3R lpS P                  V4       R# )ap  Clear ended_at/end_reason so a session can be resumed; first freeze markerless legacy reset
children, skipping explicit fork/delegate provenance and children that predate the parent itself.
The guard compares against the parent's started_at, not its current ended_at: a parent that was
reopened and re-ended later still owns reset children from its earlier boundaries.c                 ó,  <€ SP                  V S4       V P                  R \        RR4       R\        RR4       R\        RR4       R\        R\	        \
        4      4       2S.\
        O54       V P                  RS34       SP                  V S4       R	# )
z©UPDATE sessions AS child SET model_config = json_set(COALESCE(child.model_config, '{}'), '$._reset_from', child.parent_session_id) WHERE child.parent_session_id = ? AND r‹   z$._reset_fromr�   rŒ   r=   z™ IS NULL AND COALESCE(child.source, '') != 'tool' AND child.started_at >= (SELECT p.started_at FROM sessions p WHERE p.id = child.parent_session_id) AND rŽ   úCUPDATE sessions SET ended_at = NULL, end_reason = NULL WHERE id = ?N)rT  rš   r   r   r~   r   Ú _unarchive_auto_archived_lineagerY  s   &€€r/   Ú_doÚ0SessionSessionsMixin.reopen_session.<locals>._do
  s¶   ø€ Ø×2Ñ2°4¸ÔDØ�L‰Lð9ä9JÐK_ÐapÓ9qÐ8rð sÜ(Ð)=Ð?QÓRÐSð TÜ(Ð)=Ð?QÓRÐSð Tô /¨wÔ8QÔRdÓ8eÓfÐgðið Ð1Ô0Ñ1ô
ð �L‰LØUÐXbÐWdôð ×1Ñ1°$¸
ÖCr1   NrD  )rú   r  rc  s   ff r/   Úreopen_sessionÚ#SessionSessionsMixin.reopen_session  s   ù€ ö
	Dð$ 	×Ñ˜CÖ r1   c                ó,   <€ V ^8„  d   QhRS[ RS[ RS[/# )r)   r  rI  r+   r+  )r.   ré   s   "€r/   r0   rê     s#   ø€ ÷ ñ ±3ð Áð ÑZ^ñ r1   c                ó°   a aaa€ S'       g   R# \         P                   ! 4       o \        S P                  VVV V3R l4      4      #   \         d     R# i ; i)aB  Durably mark an intentional reset boundary on live rows or rows with a *recoverable* accidental
end_reason (explicit boundaries are preserved): an ``agent_close`` row left recoverable would be
resurrected by stale-route recovery. Keep in sync with find_latest_gateway_session_for_peer.

Plain ``end_session()`` is NOT sufficient for reset boundaries: it no-ops on an already-ended row,
so a row that agent cleanup already closed as ``agent_close`` would stay recoverable and stale-route
recovery would resurrect the reset session with its full history (#61220, #61993, #63539).
Fc                 óD   <€ SP                  V R \         R2SSS3SS4      # )zfUPDATE sessions SET ended_at = ?, end_reason = ? WHERE id = ? AND (ended_at IS NULL OR end_reason IN (rZ   )rA  r   )rœ   ÚnowrI  rú   r  s   &€€€€r/   r  Ú?SessionSessionsMixin.promote_to_session_reset.<locals>.<lambda>-  s4   ø€ ¸×9KÑ9KØð %Ü%AÐ$BÀ"ðFà�f˜jÐ)¨:°vô:r1   )rB  rr   r  r÷   )rú   r  rI  rj  s   fff@r/   Úpromote_to_session_resetÚ-SessionSessionsMixin.promote_to_session_reset  sN   û€ ÷ ÙÜ�iŠi‹kˆð	Ü˜×+Ñ+÷ -ó ó ð øô
 ô 	Úð	ús   ¥ A ÁAÁAFc                ón   <€ V ^8„  d   QhRS[ RS[ RS[S[ ,          RS[S[ ,          RS[RS[S[,          /# )r)   r  r5   Ú
git_branchr3   Úreplace_git_metar+   )r-   r   rr   rs   )r.   ré   s   "€r/   r0   rê   5  sK   ø€ ÷ (ñ (Ùð(Ù$'ð(Ù5=¹cµ]ð(á¡•}ð(á?Cð(ñ 
‘#�ñ(r1   c                óÎ   aaaaa€ S'       d	   S'       g   R# T;'       g    RP                  4       oT;'       g    RP                  4       oVVVVV3R lpV P                  V4      # )a`  Persist the authoritative cwd and claim a Git metadata generation. git fields are written
only when non-empty (a probe failure never clobbers a value) except under ``replace_git_meta``
(a workspace MOVE overwrites the old repo identity). Async probes publish with the returned
generation so an older worker cannot overwrite a newer claim (A -> B -> A).Nr4   c                 óB  <€ V P                  R S34      P                  4       pVf   R# RR.pS.pV^ ,          S8w  g	   S	'       d:   VP                  R4       TP                  S;'       g    RS
;'       g    R34       M@RS3RS
33 F4  w  rEV'       g   K  VP                  V R24       VP                  V4       K6  	  V P                  RRP	                  V4       R	2. VOSN4       V P                  R
S34      P                  4       pVf   R# \        V^ ,          4      # )z%SELECT cwd FROM sessions WHERE id = ?Nzcwd = ?zBgit_metadata_generation = COALESCE(git_metadata_generation, 0) + 1ro  r3   ú = ?úUPDATE sessions SET ú, z WHERE id = ?z9SELECT git_metadata_generation FROM sessions WHERE id = ?)zgit_branch = ?zgit_repo_root = ?)rš   Úfetchoner   r}   re   rs   )rœ   ÚcurrentÚsetsr�   r:   Úvalr*   Úbranchr5   rp  Ú	repo_rootr  s   &      €€€€€r/   rc  Ú4SessionSessionsMixin.update_session_cwd.<locals>._doA  s  ø€ Ø—l‘lÐ#JÈZÈMÓZ×cÑcÓeˆGØŠÙØÐcÐdˆDØ!$ ˆFØ�q�z˜SÔ ×$4Ø—‘ÐCÔDØ—‘˜vŸ~˜~¨¨y×/@Ð/@¸DÐAÕBà".°Ð!7¸/È9Ð9UÓ V‘H�Cß‘sØŸ™ s e¨4 LÔ1ØŸ™ cÖ*ñ !Wð �L‰LÐ/°·	±	¸$³Ð/@ÀÐNÐPeÐRXÐPeÐZdÐPeÔfØ—,‘,ØKÈjÈ]óç‰h‹jð ð š;�4Ð7¬C°°Aµ«KÐ7r1   )r7   r  )	rú   r  r5   ro  r3   rp  rc  rz  r{  s	   &ff&&f @@r/   Úupdate_session_cwdÚ'SessionSessionsMixin.update_session_cwd5  sV   ü€ ÷ §ÙØ×"Ð" ×)Ñ)Ó+ˆØ"×(Ð( b×/Ñ/Ó1ˆ	÷	8ñ 	8ð& ×"Ñ" 3Ó'Ð'r1   c                ó^   <€ V ^8„  d   QhRS[ RS[ RS[RS[S[ ,          RS[S[ ,          RS[/# )r)   r  r5   Ú
generationro  r3   r+   )r-   rs   r   rr   )r.   ré   s   "€r/   r0   rê   V  sG   ø€ ÷ ñ ÙðÙ$'ðÙ58ðÙFNÉsÅmðá¡•}ðñ 
ñr1   c           	     ó  € \        V\        4      ;'       d#    \        V\        4      '       * ;'       d    V^8¬  pV'       d   V'       d	   V'       g   R# RT;'       g    RP                  4       3RT;'       g    RP                  4       33 UUu. uF  w  rxV'       g   K  Wx3NK  	  p	ppV	'       g   R# T P	                  RRP                  R V	 4       4       R2V	 U
Uu. uF  w  r¨VNK	  	  upp
WV.,           4      ^8H  # u uppi u upp
i )	zAPublish async Git enrichment only while its cwd claim is current.Fro  r4   r3   rt  ru  c              3   ó0   "  € T F  w  rV R 2x € K  	  R# 5i)rs  Nr|   )r•   r:   Ú_s   &  r/   r–   ÚDSessionSessionsMixin.publish_session_git_metadata.<locals>.<genexpr>f  s   é € Ð,OÉ¹f¸c°¨u°D­\Ëùs   ‚z9 WHERE id = ? AND cwd = ? AND git_metadata_generation = ?)rD   rs   rr   r7   Ú_write_rowcountre   )rú   r  r5   r€  ro  r3   Úvalid_generationr:   ry  Úfieldsrƒ  s   &&&&&&     r/   Úpublish_session_git_metadataÚ1SessionSessionsMixin.publish_session_git_metadataV  s  € ô
 & j´#Ó6×oÐo¼zÈ*ÔVZÓ?[Ô;[×oÐoÐ`jÐnoÑ`oÐß§×,<Ùð  
× 0Ð 0¨b×7Ñ7Ó9Ð:¸_È}×ObÐObÐ`b×NiÑNiÓNkÐ<lñ(ô
ñ(™8˜3äô ˆS‹Jñ (ð 	ñ 
÷
 ÙØ×#Ñ#Ø" 4§9¡9Ñ,OÉÓ,OÓ#OÐ"Pð QGð Gá%Ô&™v‘V�Q‹S™vÒ&¨*¸:Ð)FÕFó
ð ñ	ð 	ùó
ùó 's   ÂDÂ!DÃ Dc                ó:   <€ V ^8„  d   QhRS[ S[S[3,          RR/# )r)   Úcwd_to_rootr+   N)r,   r-   )r.   ré   s   "€r/   r0   rê   k  s#   ø€ ÷ ñ ©t±C¹°H­~ð À$ñ r1   c                ó¾   € VP                  4        UUu. uF  w  r#V'       g   K  V'       g   K  W23NK  	  pppV'       d   V P                  RVRR7       R# R# u uppi )zMBackfill git repo roots for cwds without one; never clobbers a recorded root.zXUPDATE sessions SET git_repo_root = ? WHERE cwd = ? AND COALESCE(git_repo_root, '') = ''T)ÚmanyN)Úitemsr-  )rú   r‹  r5   rû   Úpairss   &&   r/   Úbackfill_repo_rootsÚ(SessionSessionsMixin.backfill_repo_rootsk  sT   € à.9×.?Ñ.?Ô.AÔRÑ.A¡ ÄT”Ìc”�$“Ñ.AˆÑRßØ�O‰OØjØ˜Dð ö ñ ùó Ss   ”A¥A¯AÚdescriptionÚ
provenancec          
      óf   <€ V ^8„  d   QhRS[ RS[S[,          RS[S[ ,          RS[S[,          RR/# )r)   r  Útsr’  r“  r+   N)r-   r   Úfloatr   )r.   ré   s   "€r/   r0   rê   t  sF   ø€ ÷ 
ñ 
Ùð
Ù#+©E¥?ð
ÙKSÑTWÍ=ð
áÑ/Õ0ð
ð 
ñ
r1   c               óÞ   € V'       g   R# \        Ve   TM\        P                  ! 4       4      pV P                  RV\        V4      \	        V4      P
                  W3V P                  R7       R# )a€  Stamp durable mid-turn activity (observation-only; rate-limited by the caller) so surfaces see
activity before any message row lands. Never moves ``last_activity_at`` backwards.

Called (rate-limited) from ``AIAgent._touch_activity`` so gateway/CLI surfaces and stall consumers
observe API/tool/compaction activity even when no new message row has been written yet (#72016 /
#72039).
Nz©UPDATE sessions SET last_activity_at = ?, last_activity_description = ?, last_activity_provenance = ? WHERE id = ? AND (last_activity_at IS NULL OR last_activity_at < ?)r  )r–  rB  r-  r   r   ÚvalueÚ_ACTIVITY_WRITE_PATIENCE_S)rú   r  r•  r’  r“  Úwhens   &&&$$ r/   Útouch_session_activityÚ+SessionSessionsMixin.touch_session_activityt  sd   € ÷ ÙÜ˜2š>‘R¬t¯yªy«{Ó;ˆØ�‰ðRð Ô0°Ó=Ü-¨jÓ9×?Ñ?Àðð ×6Ñ6ð 	ö 		
r1   c                ó$   <€ V ^8„  d   QhRS[ RR/# r   r;   )r.   ré   s   "€r/   r0   rê   �  s   ø€ ÷ 
ñ 
¹ð 
Àñ 
r1   c                ó‚  € V'       g   R#  V P                  RV34      pVeG   V^ ,          '       g8   V^,          '       d'   V^,          \        P                  P
                  8X  d   R# V P                  RR\        P                  P
                  V3V P                  R7       R#   \        P                   d    Rp Lži ; i)a–  Clear activity labels after a turn (``last_activity_at`` is kept so idle / watchdog clocks stay
continuous). A no-op clear skips the write transaction.

Description and provenance are observation labels for *what was happening at* that timestamp during
an active turn; once the turn is idle they must not keep advertising "compressing" / "executing
tool" (#72039).
Response-critical-path contract (#76354 review S1): runs in the turn's ``finally``; a no-op clear
(labels already empty) skips the write transaction entirely, and a real clear uses the same short
sub-second busy budget as :meth:`touch_session_activity` instead of the full routine write patience.
NzUSELECT last_activity_description, last_activity_provenance FROM sessions WHERE id = ?z\UPDATE sessions SET last_activity_description = ?, last_activity_provenance = ? WHERE id = ?r4   r  )Ú	_read_oneÚsqlite3ÚErrorr   ÚUNKNOWNr˜  r-  r™  ©rú   r  r*   s   && r/   Úclear_session_activity_labelsÚ2SessionSessionsMixin.clear_session_activity_labels�  s¤   € ÷ Ùð	Ø—.‘.ØgØ�óˆCð Š? 3 q§6¤6°3°q·6´6¸SÀ½VÔGY×GaÑGa×GgÑGgÔ=gÙØ�‰ØjØÔ#×+Ñ+×1Ñ1°:Ð>È4×KjÑKjð 	ö 	
øô	 �}‰}ô 	ØŠCð	ús   ŒB$ Â$B>Â=B>c                ó@   <€ V ^8„  d   QhRS[ RS[ RS[S[ ,          RR/# )r)   r  Úmodel_config_jsonr	  r+   Nr2  )r.   ré   s   "€r/   r0   rê   «  s1   ø€ ÷ 
ñ 
Ùð
Ù25ð
Ù>FÁs½mð
à	ñ
r1   c                óN   € V P                  4        V P                  RW#V34       R# )z4Update model_config and (COALESCE) optionally model.zMUPDATE sessions SET model_config = ?, model = COALESCE(?, model) WHERE id = ?N)Úflush_token_countsr-  )rú   r  r§  r	  s   &&&&r/   Úupdate_session_metaÚ(SessionSessionsMixin.update_session_meta«  s'   € ð 	×ÑÔ!Ø�‰Ø[Ø zÐ2ö	
r1   c                ó:   <€ V ^8„  d   QhRS[ RS[S[ ,          RR/# )r)   r  r
  r+   Nr2  )r.   ré   s   "€r/   r0   rê   µ  s'   ø€ ÷ !ñ !©sð !Á8ÉCÅ=ð !ÐUYñ !r1   c                ó>   a aa€ V VV3R lpS P                  V4       R# )z0Store the full assembled system prompt snapshot.c                 ór   <€ V P                  R SP                  V S4      S34       SP                  V 4       R# )zMUPDATE sessions SET system_prompt_hash = ?, system_prompt = NULL WHERE id = ?N©rš   Ú_store_system_promptÚ#_delete_unreferenced_system_prompts)rœ   rú   r  r
  s   &€€€r/   rc  Ú6SessionSessionsMixin.update_system_prompt.<locals>._do·  s7   ø€ Ø�L‰LØ_Ø×*Ñ*¨4°Ó?ÀÐLôð ×4Ñ4°TÖ:r1   NrD  )rú   r  r
  rc  s   fff r/   Úupdate_system_promptÚ)SessionSessionsMixin.update_system_promptµ  s   ú€ ÷	;ð 	×Ñ˜CÖ r1   c                ó*   <€ V ^8„  d   QhRS[ RS[RR/# )r)   r  Úpinr+   N©r-   r   )r.   ré   s   "€r/   r0   rê   ¿  s"   ø€ ÷ 
!ñ 
!±Cð 
!¹cð 
!Àdñ 
!r1   c                óv   a aa€ Ve   \         P                  ! V4      MRoVV V3R lpS P                  V4       R# )aa  Persist the session's ``tools[]`` pin (JSON-serializable) so a rebuilt AIAgent sends the
same bytes; ``None`` clears. The array repeats across sessions like a system prompt does, so it
is stored in the same content-addressed ``system_prompts`` table and the column holds its hash
(legacy rows: an inline JSON name list); ``get_session`` resolves either.Nc                 ór   <€ V P                  R SP                  V S4      S34       SP                  V 4       R# )z/UPDATE sessions SET tool_names = ? WHERE id = ?Nr¯  )rœ   Úpayloadrú   r  s   &€€€r/   rc  Ú;SessionSessionsMixin.update_session_tool_names.<locals>._doÅ  s6   ø€ Ø�L‰LÐJØ×3Ñ3°D¸'ÓBÀJÐOôQà×4Ñ4°TÖ:r1   )rE   Údumpsr  )rú   r  r¶  rc  rº  s   ff& @r/   Úupdate_session_tool_namesÚ.SessionSessionsMixin.update_session_tool_names¿  s-   ú€ ð
 &)¢_”$—*’*˜S”/¸$ˆ÷	;ð 	×Ñ˜CÖ r1   Úbase_urlÚapi_modec                ól   <€ V ^8„  d   QhRS[ RS[ RS[S[ ,          RS[S[ ,          RS[S[ ,          RR/# )r)   r  r	  Úproviderr¿  rÀ  r+   Nr2  )r.   ré   s   "€r/   r0   rê   Ë  sK   ø€ ÷ 
ñ 
Ùð
Ù&)ð
Ù5=¹cµ]ð
á™3•-ð
á2:¹3µ-ð
ð 
ñ
r1   c               óê   aa€ V P                  4        RR/pS'       d   SVR&   V'       d/   RTRT;'       g    RRT;'       g    R/pVP                  WwR7       V P                  SVRVV3R	 l4       R# )
aÐ  Set the model after a mid-session /model switch (unconditionally) and drop any Browser
runtime lock (lineage markers survive).

Route writers never touch the stored prompt; ``_stored_prompt_matches_runtime`` decides staleness.

When *provider* is given the whole route is written, in both shapes resume reads (top-level
keys for the TUI/Desktop, ``gateway_runtime`` for the CLI), so a later resume recombines the
model with the provider that serves it (#79536). ``base_url``/``api_mode`` are always
replaced then (``None`` deletes): the previous provider's endpoint must not survive a switch,
or resume sends the new provider's model to the old host. Callers without provider knowledge
leave the stored route untouched.
Úbrowser_model_lockNr	  rÂ  r¿  rÀ  )Úgateway_runtimez<UPDATE sessions SET model = ?, model_config = ? WHERE id = ?c                 ó   <€ SV S3# rÚ   r|   ©Úmergedr	  r  s   &€€r/   r  Ú;SessionSessionsMixin.update_session_model.<locals>.<lambda>æ  s   ø€ ˜E 6¨:Ñ6r1   )r©  ÚupdateÚ_write_model_config_patch)rú   r  r	  rÂ  r¿  rÀ  ÚpatchÚroutes   &ff&$$  r/   Úupdate_session_modelÚ)SessionSessionsMixin.update_session_modelË  su   ù€ ð$ 	×ÑÔ!Ø!5°tÐ <ˆßØ"ˆE�'‰NßØ ¨:°x×7GÐ7GÀ4ÈÐU]×UeÐUeÐaeÐfˆEØ�L‰L˜ˆLÔ6Ø×&Ñ&Ø˜Ð]Ý6ö	
r1   c                ó„   <€ V ^8„  d   QhRS[ RS[S[ S[3,          RS[ RS[S[S[S[ ,          .S[3,          ,          RR/# )r)   r  rÌ  rH  r�   r+   N)r-   r,   r   r   r   rM   )r.   ré   s   "€r/   r0   rê   é  sV   ø€ ÷ !ñ !Ùð!Ù&*©3±¨8¥nð!áð!ñ ™¡8©C¥= /±5Ð"8Õ9Õ:ð!ð 
ñ	!r1   c                óF   a aaaa€ VVV VV3R lpS P                  V4       R# )z†Merge ``patch`` into model_config then run ``sql`` with ``params(merged)`` in one write
transaction; no-op when the row doesn't exist.c                 óš   <€ SP                  V SS4      pV\        J d   R # T P                  SS'       d   S! V4      4       R # VS34       R # rÚ   )Ú_merge_model_config_jsonÚ_MODEL_CONFIG_ROW_MISSINGrš   )rœ   rÈ  r�   rÌ  rú   r  rH  s   & €€€€€r/   rc  Ú;SessionSessionsMixin._write_model_config_patch.<locals>._doð  sA   ø€ Ø×2Ñ2°4¸ÀUÓKˆFØÔ2Ó2ÙØ�L‰L˜·™f V›nÖQ¸VÀZÐ<PÖQr1   NrD  )rú   r  rÌ  rH  r�   rc  s   fffff r/   rË  Ú.SessionSessionsMixin._write_model_config_patché  s   ü€ ÷	Rñ 	Rð
 	×Ñ˜CÖ r1   Ú
on_missingÚskipc                óB   <€ V ^8„  d   QhRS[ RS[S[ S[3,          RS[ /# )r)   r  rÌ  r×  ©r-   r,   r   )r.   ré   s   "€r/   r0   rê   ÷  s-   ø€ ÷ 6ñ 6Ù #ð6Ù,0±±c°­Nð6ÙKNñ6r1   c               óP  € VP                  RV34      P                  4       pVf   VR8X  d   \        RV 24      h\        # \	        V^ ,          4      pVP                  4        F!  w  rxVf   VP                  VR4       K  W†V&   K#  	  V'       d   \        P                  ! V4      # R# )u  SELECT + tolerant-parse + merge ``patch`` into model_config (the one place that keeps
``_branched_from``/``_delegate_from`` alive); ``None`` deletes a key. Returns serialized JSON
(``None`` when empty) or ``_MODEL_CONFIG_ROW_MISSING`` (``on_missing="raise"`` â†’ ValueError).ú.SELECT model_config FROM sessions WHERE id = ?NÚraisezSession not found: )	rš   rv  Ú
ValueErrorrÔ  rI   rŽ  ÚpoprE   r¼  )	rú   rœ   r  rÌ  r×  r*   ÚconfigrX   r˜  s	   &&&&$    r/   rÓ  Ú-SessionSessionsMixin._merge_model_config_json÷  s˜   € ð �l‰lÐKÈjÈ]Ó[×dÑdÓfˆØŠ;Ø˜WÔ$Ü Ð#6°z°lÐ!CÓDÐDÜ,Ð,Ü$ S¨¥VÓ,ˆØŸ+™+ž-‰JˆCØŠ}Ø—
‘
˜3 Ö%à#�s“ñ	 (÷
 &,Œt�zŠz˜&Ó!Ð5°Ð5r1   c                ó@   <€ V ^8„  d   QhRS[ RS[S[ S[3,          RR/# )r)   r  rÌ  r+   NrÚ  )r.   ré   s   "€r/   r0   rê   
  s+   ø€ ÷ :ñ :±Sð :ÁÁcÉ3ÀhÅð :ÐTXñ :r1   c                óL   € V'       d	   V'       g   R# V P                  W4       R# )zlMerge ``patch`` into model_config atomically (``None`` removes a key);
no-op when the row or patch is empty.N)rË  )rú   r  rÌ  s   &&&r/   Úpatch_session_model_configÚ/SessionSessionsMixin.patch_session_model_config
  s   € ÷ §ÙØ×&Ñ& zÖ9r1   c                ó2   <€ V ^8„  d   QhRS[ RS[ RS[RS[/# )r)   r  rX   rí   r+   r·  )r.   ré   s   "€r/   r0   rê     s1   ø€ ÷ Rñ R¹ð RÁ3ð RÑQTð RÑ`cñ Rr1   c                óŠ   € V P                  V4      ;'       g    / p\        VP                  R4      4      P                  W#4      # )zCRead one key out of a session's model_config JSON (tolerant parse).rà   )Úget_sessionrI   r6   )rú   r  rX   rí   Úsessions   &&&& r/   Úget_session_model_config_valueÚ3SessionSessionsMixin.get_session_model_config_value  s9   € à×"Ñ" :Ó.×4Ð4°"ˆÜ" 7§;¡;¨~Ó#>Ó?×CÑCÀCÓQÐQr1   c          	      óZ   <€ V ^8„  d   QhRS[ RS[ RS[S[.S[3,          RS[S[ ,          /# )r)   r  rX   rÊ  r+   )r-   r   r   r   )r.   ré   s   "€r/   r0   rê     sB   ø€ ÷ Uñ UÁð UÉ3ð UÙ7?ÁÀÁsÀ
Õ7KðUÙPXÑY\ÕP]ñUr1   c                ó    € ^ RI Hp V! WW#4      # )z\Write ``update(value)`` of a conversation-wide key at the compression tip (one transaction).)Ú&update_conversation_model_config_value)Ú"hermes_state_sessions_model_configrî  )rú   r  rX   rÊ  rî  s   &&&& r/   rî  Ú;SessionSessionsMixin.update_conversation_model_config_value  s   € õ 	^Ù5°dÈÓTÐTr1   c                ó6   <€ V ^8„  d   QhRS[ RS[S[ ,          /# rW   ©r-   rN   )r.   ré   s   "€r/   r0   rê     s   ø€ ÷ <ñ <±Sð <¹TÁ#½Yñ <r1   c                ó   € ^ RI Hp V! W4      # )zEIds of the sessions whose model_config carries ``key``, newest first.)Ú!session_ids_with_model_config_key)rï  rô  )rú   rX   rô  s   && r/   rô  Ú6SessionSessionsMixin.session_ids_with_model_config_key  s   € åXÙ0°Ó;Ð;r1   r	  rÂ  Úmodel_optionsÚroute_sourceÚ	confirmedc                ó˜   <€ V ^8„  d   QhRS[ RS[S[ ,          RS[S[ ,          RS[S[S[ S[3,          ,          RS[S[ ,          RS[RR/# )	r)   r  r	  rÂ  rö  r÷  rø  r+   N)r-   r   r,   r   rr   )r.   ré   s   "€r/   r0   rê   !  sc   ø€ ÷ 
ñ 
Ùð
Ù)1±#­ð
ÙIQÑRUÍð
á¡¡S©# X¥Õ/ð
áFNÉsÅmð
ñ ð
ð 
ñ	
r1   c               óä   aa€ RT;'       g    RRS;'       g    RRT;'       g    / RT;'       g    RR\        V4      R\        P                  ! 4       /pV P                  SRV/R	VV3R
 l4       R# )z»Persist a Browser / API-client runtime lock into model_config (lineage markers survive).
Route writers never touch the stored prompt; ``_stored_prompt_matches_runtime`` decides staleness.rÂ  r4   r	  rö  r÷  rø  Ú
updated_atrÄ  z†UPDATE sessions SET
                   model_config = ?,
                   model = COALESCE(?, model)
                   WHERE id = ?c                 ó   <€ V SS3# rÚ   r|   rÇ  s   &€€r/   r  ÚBSessionSessionsMixin.update_session_runtime_lock.<locals>.<lambda>2  s   ø€ ˜F E¨:Ñ6r1   N)rr   rB  rË  )rú   r  r	  rÂ  rö  r÷  rø  Úlocks   &fd$$$$ r/   Úupdate_session_runtime_lockÚ0SessionSessionsMixin.update_session_runtime_lock!  sv   ù€ ð ˜Ÿ˜ B¨°·°¸"¸oÈ}×ObÐObÐ`bØ˜L×.Ð.¨B°¼TÀ)»_ÈlÔ\`×\eÒ\eÓ\gð
ˆð 	×&Ñ&ØÐ-¨tÐ4ð#õ 7ö	
r1   c                ó*   <€ V ^8„  d   QhRS[ RS[RR/# )r)   r  Úenabledr+   Nr+  )r.   ré   s   "€r/   r0   rê   5  s'   ø€ ÷ Qñ Q©3ð Q¹ð QÀ$ñ Qr1   c                óT   € V'       g   R# V P                  VR\        V4      /4       R# )zZPersist the per-session YOLO flag so ``/yolo`` survives ``--resume``; no-op without a row.NÚ	yolo_mode)rË  rr   )rú   r  r  s   &&&r/   Úset_session_yoloÚ%SessionSessionsMixin.set_session_yolo5  s!   € çÙØ×&Ñ& z°KÄÀgÃÐ3OÖPr1   c                óL   <€ V ^8„  d   QhRS[ S[S[S[3,          ,          RS[/# )r)   Úsession_metar+   )r   r,   r-   r   rr   )r.   ré   s   "€r/   r0   rê   <  s,   ø€ ÷ dñ d©8±D¹¹c¸µNÕ+Cð dÉñ dr1   c                óz   € \        \        T ;'       g    / P                  R4      4      P                  R4      4      # )zVPersisted YOLO flag; False on any parse failure (resume must never enable the bypass).rà   r  )rr   rI   r6   )r  s   &r/   Úsession_yolo_enabledÚ)SessionSessionsMixin.session_yolo_enabled;  s4   € ô Ô'¨×);Ð);¸×(@Ñ(@ÀÓ(PÓQ×UÑUÐVaÓbÓcÐcr1   c                óL   <€ V ^8„  d   QhRS[ RS[S[S[ S[3,          ,          /# rP  ©r-   r   r,   r   )r.   ré   s   "€r/   r0   rê   @  s(   ø€ ÷ 
<ñ 
<¡cð 
<©h±t¹CÁ¸Hµ~Õ.Fñ 
<r1   c                ó~   € V P                  4        V P                  RV34      pV'       d   V P                  V4      # R# )zXGet a session by ID (drains queued token deltas first so cost readers see exact totals).a  SELECT s.*, COALESCE(sp.prompt, s.system_prompt) AS _system_prompt_resolved, COALESCE(tp.prompt, s.tool_names) AS _tool_names_resolved FROM sessions s LEFT JOIN system_prompts sp ON sp.hash = s.system_prompt_hash LEFT JOIN system_prompts tp ON tp.hash = s.tool_names WHERE s.id = ?N)r©  rŸ  Ú_session_row_dictr£  s   && r/   rè  Ú SessionSessionsMixin.get_session@  sC   € à×ÑÔ!Ø�n‰nðSð ˆMó
ˆ÷ /2ˆt×%Ñ% cÓ*Ð;°tÐ;r1   c                óL   <€ V ^8„  d   QhRS[ RS[S[S[ S[3,          ,          /# rP  r  )r.   ré   s   "€r/   r0   rê   L  s(   ø€ ÷ *ñ *©sð *±xÁÁSÉ#ÀXÅÕ7Oñ *r1   c                óR   € V P                  RV34      pV'       d   \        V4      # R# )zYPicker columns only: the live listing skips ``get_session``'s prompt/tool JOIN and flush.zqSELECT id, source, title, started_at, ended_at, message_count, model, hidden, archived FROM sessions WHERE id = ?N)rŸ  r,   r£  s   && r/   Úget_session_list_rowÚ)SessionSessionsMixin.get_session_list_rowL  s-   € à�n‰nð)àˆMó
ˆ÷
  Œt�C‹yÐ) TÐ)r1   c                óL   <€ V ^8„  d   QhRS[ RS[S[S[ S[3,          ,          /# rP  r  )r.   ré   s   "€r/   r0   rê   U  s)   ø€ ÷ *ñ *¹ð *ÁÉ$ÉsÑTWÈxÍ.ÕAYñ *r1   c                ór   € V P                  4        V P                  RV34      pV'       d   \        V4      # R# )aÿ  Most recently used main-loop model route as one coherent per-call tuple
(``session_model_usage`` keeps model+provider together; ``sessions`` mixes route changes).
Recency, not lifetime call count: on a long session a route retired weeks ago can hold the
highest ``api_call_count`` forever, and /status and /usage would keep calling it current.
``rowid DESC`` breaks same-timestamp ties toward the route that first appeared later; without
it SQLite's temp-sort order is unspecified and the retired route can win.au  SELECT model, billing_provider, billing_base_url, billing_mode,
                      api_call_count
                 FROM session_model_usage
                WHERE session_id = ?
                  AND task = ''
                  AND model <> 'unknown'
                  AND billing_provider <> ''
                ORDER BY last_seen DESC, rowid DESC
                LIMIT 1N)r©  rŸ  r,   r£  s   && r/   Úget_recent_session_model_routeÚ3SessionSessionsMixin.get_recent_session_model_routeU  s=   € ð 	×ÑÔ!Ø�n‰nðð ˆMó
ˆ÷  Œt�C‹yÐ) TÐ)r1   c                ó6   <€ V ^8„  d   QhRS[ RS[S[ ,          /# )r)   Úsession_id_or_prefixr+   r2  )r.   ré   s   "€r/   r0   rê   k  s   ø€ ÷ 	?ñ 	?±sð 	?¹xÉ½}ñ 	?r1   c                óÈ   € V P                  V4      pV'       d
   VR,          # V P                  R\        V4       R234      p\        V4      ^8X  d   V^ ,          R,          # R# )z>Exact id, else the single unambiguous prefix match, else None.r“   zSSELECT id FROM sessions WHERE id LIKE ? ESCAPE '\' ORDER BY started_at DESC LIMIT 2Ú%N)rè  r6  rR   rÖ   )rú   r  r9  Úmatchess   &&  r/   Úresolve_session_idÚ'SessionSessionsMixin.resolve_session_idk  sf   € à× Ñ Ð!5Ó6ˆßØ˜•;ÐØ—.‘.ØbÜÐ1Ó2Ð3°1Ð5Ð7ó
ˆô $' w£<°1Ô#4ˆw�q�z˜$ÕÐ>¸$Ð>r1   c                ó&   <€ V ^8„  d   QhRS[ RS[/# )r)   rÑ   r+   rQ  )r.   ré   s   "€r/   r0   rê   v  s   ø€ ÷ ñ ¹3ð Á3ñ r1   c                ó–   € T;'       g    RP                  4       pV'       g   ^ # \        V P                  RV34      ;'       g    ^ 4      # )u9  Stamp this store's own profile onto legacy ``profile_name IS NULL`` rows, which the fail-closed
owner ladder cannot route. Never overwrites a non-NULL owner. Returns rows stamped.

Sessions created before the durable-ownership work (#95407 lineage) carry ``profile_name = NULL``.
On single-backend installs that was harmless, but once a Desktop registers a second connection the
fail-closed owner ladder (which is correct for new sessions) can no longer route those rows anywhere
â€” every pre-campaign session becomes unresumable after upgrade (#94724, field report).
r4   zvUPDATE sessions
               SET profile_name = ?
             WHERE profile_name IS NULL OR TRIM(profile_name) = '')r7   rs   r…  )rú   rÑ   Ústamps   && r/   Úbackfill_null_session_profilesÚ3SessionSessionsMixin.backfill_null_session_profilesv  sV   € ð ×#Ð# ×*Ñ*Ó,ˆßÙÜ�4×'Ñ'ðFð ˆHó	
÷ ð ð
 óð 	r1   c                ó    <€ V ^8„  d   QhRS[ /# rç   ©rs   )r.   ré   s   "€r/   r0   rê   ‰  s   ø€ ÷ ñ ©#ñ r1   c                óL   € \        V P                  RR4      ;'       g    ^ 4      # )uŒ  Promote ``model_config.cwd`` into the cwd column for ACP rows lacking one.

ACP sessions minted before the adapter populated the column still carry
their workspace inside ``model_config``, written by the same adapter that
knew the real directory â€” so this is a record being promoted, not a guess.
Only fills NULL/empty; an explicit column value always wins. Returns the
number of rows changed.
a  UPDATE sessions
                  SET cwd = json_extract(model_config, '$.cwd')
                WHERE source = 'acp'
                  AND COALESCE(cwd, '') = ''
                  AND json_valid(model_config)
                  AND COALESCE(json_extract(model_config, '$.cwd'), '') != ''r|   )rs   r…  ©rú   s   &r/   Úbackfill_acp_session_cwdÚ-SessionSessionsMixin.backfill_acp_session_cwd‰  s8   € ô �4×'Ñ'ðQð ó
÷ ð ð óð 	r1   Úextra_set_sqlr4   c          
      ó8   <€ V ^8„  d   QhRS[ RS[ RS[RS[ RS[/# )r)   Úcolumnr  r˜  r+  r+   )r-   r   rr   )r.   ré   s   "€r/   r0   rê   œ  s2   ø€ ÷ ñ ©#ð ¹3ð Ásð Ù+.ðÙ8<ñr1   c          	     óP   a aaaa€ \        S P                  VVV VV3R l4      4      # )züSet one ``sessions`` column across a whole compression lineage: Desktop projects roots
forward to their tip, so updating only the tip would let the root resurrect it on refresh.
*extra_set_sql* (trusted literal, ``, col = expr``) rides the same UPDATE.c                 ó0   <€ SP                  V SSSSR 7      # )©r+  )Ú"_set_lineage_column_in_transaction)rœ   r-  r+  rú   r  r˜  s   &€€€€€r/   r  Ú:SessionSessionsMixin._set_lineage_column.<locals>.<lambda>¢  s"   ø€ ˜×@Ñ@Ø�f˜j¨%¸}ð Aô Nr1   )rr   r  )rú   r-  r  r˜  r+  s   ffffdr/   Ú_set_lineage_columnÚ(SessionSessionsMixin._set_lineage_columnœ  s+   ü€ ô
 �D×'Ñ'÷Nð Nó
ó ð 	r1   c                ó2   <€ V ^8„  d   QhRS[ RS[ RS[RS[ /# )r)   r-  r  r˜  r+  r·  )r.   ré   s   "€r/   r0   rê   ¦  s,   ø€ ÷ ñ ¹sð ÑPSð Ñ\_ð Ù:=ñr1   c               óÂ   € VR8X  d   V'       g   RpVP                  \        RV RV R2,           W3V34      P                  4        Uu. uF  qf^ ,          NK  	  up# u upi )a  Return affected IDs so authority revisions share this exact lineage selector. A plain
``archived`` write (canonical mutation, user/CLI/API) is the DELIBERATE archive and clears
the idle sweep's ``auto_archived`` provenance, same as :meth:`set_session_archived`.Úarchivedú, auto_archived = 0z-
            UPDATE sessions
            SET rs  zW
            WHERE id IN (SELECT id FROM lineage)
            RETURNING id
            )rš   Ú_LINEAGE_CTE_SQLr›   )rú   rœ   r-  r  r˜  r+  r*   s   &&&&&$ r/   r1  Ú7SessionSessionsMixin._set_lineage_column_in_transaction¦  s€   € ð
 �ZÔ¯Ø1ˆMØ"&§,¡,Üð $à�˜˜]˜Oð ,ð õ ð  UÐ+ó#
÷ ‰(‹*ð#ó ñ #˜3�A—�ñ #ñ ð 	ùò s   ÁAÚnotifyc                ó2   <€ V ^8„  d   QhRS[ RS[RS[RS[/# )r)   r  r7  r;  r+   r+  )r.   ré   s   "€r/   r0   rê   ·  s*   ø€ ÷ ñ ©sð ¹dð Étð Ñ_cñ r1   c               ó’   a aa€ VV V3R lpS P                  V4      w  rVV'       d   V'       d   \        S VRSR7       \        V4      # )a¡  Soft-hide (or unhide) a session and its compression lineage; messages are kept.
This is the DELIBERATE archive (user, CLI, API): it clears the ``auto_archived``
provenance, so re-activation never un-hides it on the user's behalf. A real
unarchived -> archived flip fires ``on_session_delete`` (``reason="archive"``) after commit;
``notify=False`` is for housekeeping that hides a row whose session lives on elsewhere.c                 óö   <€ V P                  R S34      P                  4       pSP                  V RS\        S4      RR7      pT\	        S;'       d%    T;'       d    VRJ;'       d    V^ ,          '       * 4      3# )z*SELECT archived FROM sessions WHERE id = ?r7  r8  r0  N)rš   rv  r1  rs   rr   )rœ   r*   rž   r7  rú   r  s   &  €€€r/   rc  Ú6SessionSessionsMixin.set_session_archived.<locals>._do½  sw   ø€ Ø—,‘,ÐKÈjÈ]Ó[×dÑdÓfˆCØ×9Ñ9Ø�j *¬c°(«mÐK`ð :ó bˆCàœ˜X×PÐP¨#×PÐP°#¸T°/×PÐPÈ#ÈaÍ&ÄjÓQÐQÐQr1   Úarchive)rI  Úprimary)r  r"   rr   )rú   r  r7  r;  rc  rž   Únewly_archiveds   fff$   r/   Úset_session_archivedÚ)SessionSessionsMixin.set_session_archived·  s;   ú€ ÷	Rð
 #×1Ñ1°#Ó6ÑˆßŸfÜ   s°9ÀjÕQÜ�C‹yÐr1   c                ó&   <€ V ^8„  d   QhRS[ RS[/# rP  r+  )r.   ré   s   "€r/   r0   rê   Ç  s   ø€ ÷ dñ d±ð d¹ñ dr1   c                ó,   € V P                  RV^RR7      # )a&  The idle sweep's archive: like :meth:`set_session_archived` but stamps
``auto_archived`` on the rows IT hides. A row already archived keeps its provenance, so a
deliberately archived ancestor is never relabelled as sweep-owned (SQLite evaluates every
SET expression against the pre-update row).r7  zF, auto_archived = CASE WHEN archived = 0 THEN 1 ELSE auto_archived ENDr0  )r3  r[  s   &&r/   Ú_auto_archive_lineageÚ*SessionSessionsMixin._auto_archive_lineageÇ  s(   € ð
 ×'Ñ'Ø˜
 AØbð (ó dð 	dr1   c                ó&   <€ V ^8„  d   QhRS[ RS[/# rP  r+  )r.   ré   s   "€r/   r0   rê   Ñ  s   ø€ ÷ &ñ &¹3ð &Á4ñ &r1   c                ó¾   € W3pV P                  \        R,           V4      P                  4       pVe   R# V P                  \        R,           V4      P                  ^ 8„  # )aÎ  Un-hide a lineage the idle sweep archived, on *conn* (caller's write txn). A tip that is
published (compression) or reopened (resume) under it is live again, and the listing admits
a lineage by its ROOT's flag, so leaving the sweep's stamp would hide an active chat (#117713).
A lineage with ANY deliberately archived row (``archived = 1 AND auto_archived = 0``) is left
alone: a manual archive stays until the user un-archives it. True when rows were un-hidden.zª
            SELECT 1 FROM sessions
            WHERE id IN (SELECT id FROM lineage) AND archived <> 0 AND COALESCE(auto_archived, 0) = 0
            LIMIT 1
            Fz�
            UPDATE sessions SET archived = 0, auto_archived = 0
            WHERE id IN (SELECT id FROM lineage) AND archived <> 0
            )rš   r9  rv  rK  )rœ   r  r�   Úmanuals   &&  r/   rb  Ú5SessionSessionsMixin._unarchive_auto_archived_lineageÐ  st   € ð Ð)ˆØ—‘Üð  õ ð ó÷
 "™›ð 	ð ÒÙØ�|‰|Üð  õ ð ó	÷ "™ Añ	&ð 	&r1   c                ó&   <€ V ^8„  d   QhRS[ RS[/# rP  r+  )r.   ré   s   "€r/   r0   rê   ê  s   ø€ ÷ %ñ %¹ð %Áñ %r1   c                ó  € V'       g   R#  V P                  V4      pT'       d   TP                  R4      '       g   R# Tp T P                  T4      ;'       g    TpYA8w  d   T P                  T4      ;'       g    TpTP                  R4      ;'       g    RT P                  9  d   R# T P                  TR4      '       g   R# T P                  RTR,          34       R#   \         d     R# i ; i  \         d     L�i ; i)uö  Un-archive a session archived by a recoverable accident (ws_orphan_reap, agent_close);
deliberate archives are left alone. True when un-archived.

Registry-style lookups (Bot Mode's canonical "Bot Chat") use this to resurrect a row the ws-orphan
reaper (``ws_orphan_reap``) or older agent cleanup (``agent_close``) archived: those ends are
accidents, not user intent, so the identity-scoped canonical chat must survive them (#92687).
Sessions archived with no end_reason or an explicit boundary reason (user archived deliberately,
``session_reset``, â€¦) are left untouched â€” returns ``False`` for those, ``True`` only when the row
was archived for a recoverable reason and is now un-archived (whole compression lineage, via
:meth:`set_session_archived`).
Fr7  r>  r4   ra  r“   T)rè  r÷   r6   Úget_compression_tipÚRECOVERABLE_END_REASONSrC  r-  )rú   r  r*   ÚtipÚtip_ids   &&   r/   Úunarchive_recoverable_sessionÚ2SessionSessionsMixin.unarchive_recoverable_sessionê  sø   € ÷ Ùð	Ø×"Ñ" :Ó.ˆC÷ ˜#Ÿ'™' *×-Ò-Ùàˆð	Ø×-Ñ-¨jÓ9×GÐG¸ZˆFØÔ#Ø×&Ñ& vÓ.×5Ð5°#�ð �G‰G�LÓ!×'Ð' R°×0LÑ0LÔLÙØ×(Ñ(¨°U×;Ò;Ùð 	�‰ØQÐTWÐX\ÕT]ÐS_ô	
ñ øô+ ô 	Úð	ûô ô 	Ùð	ús/   ŒC ÁC1 Á C1 Á:C1 ÃC.Ã-C.Ã1C?Ã>C?c                ó,   <€ V ^8„  d   QhRS[ RS[RS[/# )r)   r  Úpinnedr+   r+  )r.   ré   s   "€r/   r0   rê     s"   ø€ ÷ ñ ©Sð ¹$ð Á4ñ r1   c                ó`  € V P                  RV\        V4      4      pV'       dŠ   V P                  V4      p\        V4      ;'       dK    \        VP	                  R4      4      ;'       d)    VP	                  R4      ;'       g    RV P
                  8H  pV'       g   V P                  RV^ 4       V# )a  Pin/unpin a session and its compression lineage (pins are exempt from the auto_archive sweep).
Pinning also clears ``hidden``: a pin means "keep this visible", and a hidden+pinned row is
otherwise absent from both the default listing and the pinned back-fill (see #106171).
Exempt the canonical Bot Chat (hidden + exact registry title): the desktop contract keeps it
hidden and reachable only through the bot row, and unhiding it would also disable the
rename guard in ``_set_session_title`` that protects its identity (see review on #106180).rV  ÚhiddenÚtitler4   )r3  rs   rè  rr   r6   ÚCANONICAL_BOT_CHAT_TITLE)rú   r  rV  Úresultr*   Úis_canonical_bot_chats   &&&   r/   Úset_session_pinnedÚ'SessionSessionsMixin.set_session_pinned  sš   € ð ×)Ñ)¨(°JÄÀFÃÓLˆßØ×"Ñ" :Ó.ˆCÜ$(¨£I÷ %ð %´$°s·w±w¸xÓ7HÓ2I÷ %ð %Ø—‘˜Ó!×'Ð' R¨D×,IÑ,IÑIð "÷ )Ø×(Ñ(¨°:¸qÔAØˆr1   c                ó,   <€ V ^8„  d   QhRS[ RS[RS[/# )r)   r  rX  r+   r+  )r.   ré   s   "€r/   r0   rê   "  s'   ø€ ÷ Kñ K©Sð K¹$ð KÁ4ñ Kr1   c                ó:   € V P                  RV\        V4      4      # )z\Hide/unhide a session and its compression lineage from the default listing; still resumable.rX  )r3  rs   )rú   r  rX  s   &&&r/   Úset_session_hiddenÚ'SessionSessionsMixin.set_session_hidden"  s   € à×'Ñ'¨°*¼cÀ&»kÓJÐJr1   c                ó,   <€ V ^8„  d   QhRS[ RS[RS[/# )r)   r  Úreadr+   r+  )r.   ré   s   "€r/   r0   rê   &  s'   ø€ ÷ bñ b©3ð b±dð bÁdñ br1   c                óh   € T P                  RY'       d   \        P                  ! 4       4      # R4      # )z¼Mark read/unread across the compression lineage. ``last_read_at`` is a watermark: unread when
activity postdates it (no write on the message path). NULL = never tracked = read; 0 = unread.Úlast_read_atç        )r3  rB  )rú   r  rd  s   &&&r/   Úset_session_readÚ%SessionSessionsMixin.set_session_read&  s+   € ð ×'Ñ'¨¸
ÖSWÄDÇIÂIÃKÓaÐaÐ]`ÓaÐar1   c                ó<   <€ V ^8„  d   QhRS[ S[S[3,          RS[/# )r)   Úsession_rowr+   )r,   r-   r   rr   )r.   ré   s   "€r/   r0   rê   ,  s#   ø€ ÷ :ñ :¡D©©c¨¥Nð :±tñ :r1   c                óÆ   € V P                  R4      pVf   R# V P                  R4      ;'       g    V P                  R4      p\        T;'       g    ^ 4      \        V4      8„  # )zIUnread = activity postdates the ``last_read_at`` watermark (NULL = read).rf  FÚlast_activeÚ
started_at)r6   r–  )rk  Ú	last_readrm  s   &  r/   Úsession_unreadÚ#SessionSessionsMixin.session_unread+  sW   € ð  —O‘O NÓ3ˆ	ØÒÙØ!—o‘o mÓ4×UÐU¸¿¹ÈÓ8UˆÜ�[×%Ð% AÓ&¬¨yÓ)9Ñ9Ð9r1   c                óX   <€ V ^8„  d   QhRS[ RS[ RS[ RS[S[ S[S[,          3,          /# )r)   Ú	where_sqlÚ	id_needleÚsearch_needler+   )r-   rM   rN   r   )r.   ré   s   "€r/   r0   rê   ;  sB   ø€ ÷  ]ñ  ]¡sð  ]±sð  ]É3ð  ]ÑSXÑY\Ñ^bÑcfÕ^gÐYgÕShñ  ]r1   c                óà  € . p. pR R lpV'       d)   VP                  R4       VP                  V! V4      4       V'       dz   \        P                  ! RRV4      pRpVP                  V! V4      .^,          4       V'       d!   VR,          pVP                  V! V4      4       VP                  VR,           4       V'       g   W3# R	P	                  V4      pV '       d
   V  R	V 2V3# R
V 2V3# )a  Extend ``where_sql`` with the id_query / search_query filters: a row is admitted when its own
id or any id in its forward compression chain matches (search also matches titles and a
punctuation-stripped form so ``an94`` finds ``AN-94``); chain membership bounds the LIKE.c                ó0   € V ^8„  d   QhR\         R\         /# )r)   Úneedler+   r;   )r.   s   "r/   r0   Ú>SessionSessionsMixin._chain_search_where.<locals>.__annotate__A  s   € ÷ 	/ñ 	/œð 	/¤ñ 	/r1   c                 ó    € R \        V 4       R 2# )r  )rR   )rx  s   &r/   ÚlikeÚ6SessionSessionsMixin._chain_search_where.<locals>.likeA  s   € Ø”| FÓ+Ð,¨AÐ.Ð.r1   znEXISTS (SELECT 1 FROM chain cq        WHERE cq.root_id = s.id          AND LOWER(cq.cur_id) LIKE ? ESCAPE '\')z[\W_]+r4   z·EXISTS (SELECT 1 FROM chain cq JOIN sessions cs ON cs.id = cq.cur_id WHERE cq.root_id = s.id AND (LOWER(COALESCE(cs.title, '')) LIKE ? ESCAPE '\' OR LOWER(cq.cur_id) LIKE ? ESCAPE '\'z{ OR REPLACE(REPLACE(REPLACE(REPLACE(LOWER(COALESCE(cs.title, '')), '-', ''), '_', ''), '.', ''), ' ', '') LIKE ? ESCAPE '\'rZ   rd   rc   )r}   rô   Úsubr   re   )	rs  rt  ru  r�   r_   r{  Úcompact_needleÚsearch_clauseÚcombineds	   &&&      r/   Ú_chain_search_whereÚ(SessionSessionsMixin._chain_search_where:  sð   € ð
 ˆØˆõ	/çØ�N‰NðEôð �M‰M™$˜y›/Ô*ßÜŸVšV I¨r°=ÓAˆNð:ð ð
 �M‰M™4 Ó.Ð/°!Õ3Ô4ßØðQõ�ð —‘™d >Ó2Ô3Ø�N‰N˜=¨4Õ/Ô0ßØÐ$Ð$Ø—<‘< Ó(ˆß1:�9�+˜U 8 *Ð-ÐV\Ð\Ð\À&ÈÈ
Ð@SÐV\Ð\Ð\r1   c          	      óx   <€ V ^8„  d   QhRS[ S[S[S[3,          ,          RS[RS[ S[S[S[3,          ,          /# )r)   ÚsessionsÚcompact_rowsr+   )rN   r,   r-   r   rr   )r.   ré   s   "€r/   r0   rê   ]  sC   ø€ ÷ *ñ *±$±t¹CÁ¸Hµ~Õ2Fð *ÑVZð *Ñ_cÑdhÑilÑnqÐiqÕdrÕ_sñ *r1   c                ó  € / pV F_  pVP                  R4      R8X  g   K  V P                  VR,          4      pV'       g   K=  VR
,          VR,          8w  g   KT  WSVR,          &   Ka  	  V'       d8   T P                  VP                  4        Uu0 uF  qUR
,          kK  	  upVR7      M/ p. pV FË  pVP                  VR,          4      pV'       d   VP                  VR
,          4      MRpV'       g   VP	                  V4       KX  \        V4      p	R F  p
W¨9   g   K  WŠ,          Wš&   K  	  V	P                  R4      f   VP                  R4      V	R&   VR,          V	R&   WYR&   RV	R	&   VP	                  V	4       KÍ  	  V# u upi )z×Replace each compression root's surfaced fields with its live tip's (root ``started_at`` kept
for stable ordering), one batched query. ``_lineage_ids`` carries every chain id (a tile may
hold a MIDDLE segment's id).r>  Úcompressionr“   )r…  NrY  Ú_lineage_root_idÚ_lineage_idsÚcontinuation_kindéÿÿÿÿ)r“   Úended_atr>  Úmessage_countÚtool_call_countrY  rm  Úpreviewr	  r
  r5   ro  r3   )r6   Úget_compression_chainÚ_get_session_rich_rows_batchr†   r}   r,   )rú   r„  r…  Úchain_by_rootÚsÚchainÚtip_rowsÚ	projectedÚtip_rowrÈ  rX   s   &&&        r/   Ú_project_compression_tipsÚ.SessionSessionsMixin._project_compression_tips]  su  € ð /1ˆÛˆAØ�u‰u�\Ó" mÖ3Ø×2Ñ2°1°Tµ7Ó;�ß‘5˜U 2�Y¨!¨D­'Ö1Ø-2 ! D¥'Ó*ñ	 ÷ ð ×-Ñ-Ø(5×(<Ñ(<Ô(>Ó?Ñ(>˜u�r—�Ñ(>Ñ?Èlð .ô à$&ð 	ð
 ˆ	ÛˆAØ!×%Ñ% a¨¥gÓ.ˆEß16�h—l‘l 5¨¥9Ô-¸DˆGßØ× Ñ  Ô#ÙÜ˜!“WˆFó�ð –>Ø")¥,�F“Kñð �z‰z˜'Ó"Ò*ð #$§%¡%¨£.��w‘Ø)*¨4­ˆFÐ%Ñ&Ø%*�>Ñ"ð
 +8ˆFÐ&Ñ'Ø×Ñ˜VÖ$ñ3 ð4 Ðùò= @s   ÂE>Úlimitrk   Útimeout_secondsg      @Úcandidate_limitÚlineage_limitc                óž   <€ V ^8„  d   QhRS[ RS[S[,          R,          RS[RS[ R,          RS[ R,          RS[S[S[S[3,          ,          /# )r)   rš  rk   Nr›  rœ  r�  r+   )rs   rN   r-   r–  r,   r   )r.   ré   s   "€r/   r0   rê   ‰  so   ø€ ÷ nñ nñ ðnñ ™c� TÕ)ð	nñ
 ðnñ ˜t�ðnñ ˜T•zðnñ 
‰d‘3™�8�nÕ	ñnr1   c               ó–  aa€ \        ^\        V4      4      p\        R\        V4      4      pVf   \        ^€V^,          4      p\        V\        \        V4      R4      4      pVf   \        RV^,          4      p\        V\        \        V4      R4      4      pRR\	        R4       R2.p. pV'       d?   R	P                  R
 V 4       4      pVP                  RV R24       VP                  V4       RP                  V4      p	R\	        R4       R2p
RV	 RV
 RV
 RV
 R\        R4       RV
 R\        R4       R\         R\	        R4       R2pVVVVV.,           p\        P                  ! 4       V,           oRoR VV3R llp V P                  4       ;_uu_ 4       pVP                  VR 4        VP                  W¼4      P!                  4       pVP                  R^ 4        RRR4       . pX FW  pT P-                  T4      p\/        TP1                  R%R&4      4      TR'&   T P3                  T4      TR(&   TP                  T4       KY  	  T#   TP                  R^ 4       i ; i  + '       g   i     Lˆ; i  \"        P$                   d>   pS'       d0   R!\'        T4      P)                  4       9   d   \+        R"TR# R$24      Thh Rp?ii ; i))aô  Latency-bounded recent-conversation browse (``session_search()``): preselect a small candidate set
from the indexed durable activity timestamp (fallback ``started_at``), resolve only those across
compression ancestry/chains, then hydrate activity/previews for that bounded set. Lineage traversal
uses ``UNION`` plus a total-row ceiling so a corrupt cycle or a deep/branching lineage cannot defeat
the bound; a lineage that hits the ceiling before a terminal root/tip is omitted, not expanded.
A cooperative SQLite progress deadline interrupts sustained work past ``timeout_seconds`` and raises
``TimeoutError`` (cheap statements may finish between callbacks). Supports only the agent-tool
browse filters; rich callers keep using :meth:`list_sessions_rich`.rg  Ni   i    r{   ús.hidden = 0rv   rx   Ú,c              3   ó&   "  € T F  pR x € K	  	  R# 5i)Ú?Nr|   )r•   rƒ  s   & r/   r–   ÚDSessionSessionsMixin.list_recent_sessions_bounded.<locals>.<genexpr>ª  s   é € Ð#A±¨A¥C³ùs   ‚ry   rz   rd   zî
            parent.end_reason = 'compression'
            AND child.parent_session_id = parent.id
            AND json_extract(
                COALESCE(child.model_config, '{}'), '$._branched_from'
            ) IS NULL
            AND r‹   zF IS NULL
            AND COALESCE(child.source, '') != 'tool'
        z•
            WITH RECURSIVE
            recent_candidates(id) AS (
                SELECT s.id
                FROM sessions s
                WHERE aÙ  
                ORDER BY COALESCE(s.last_activity_at, s.started_at) DESC,
                         s.started_at DESC, s.id DESC
                LIMIT ?
            ),
            ancestors(candidate_id, cur_id) AS (
                SELECT id, id FROM recent_candidates
                UNION
                SELECT a.candidate_id, parent.id
                FROM ancestors a
                JOIN sessions child ON child.id = a.cur_id
                JOIN sessions parent ON aZ  
                LIMIT ?
            ),
            candidate_roots(root_id) AS (
                SELECT DISTINCT a.cur_id
                FROM ancestors a
                JOIN sessions child ON child.id = a.cur_id
                WHERE NOT EXISTS (
                    SELECT 1
                    FROM sessions parent
                    WHERE aI  
                )
            ),
            chain(root_id, cur_id) AS (
                SELECT root_id, root_id FROM candidate_roots
                UNION
                SELECT c.root_id, child.id
                FROM chain c
                JOIN sessions parent ON parent.id = c.cur_id
                JOIN sessions child ON z¬
                LIMIT ?
            ),
            chain_rows AS (
                SELECT
                    c.root_id,
                    c.cur_id,
                    zc.cur_idz² AS activity,
                    CASE WHEN EXISTS (
                        SELECT 1
                        FROM sessions parent
                        JOIN sessions child ON az  
                        WHERE parent.id = c.cur_id
                    ) THEN 0 ELSE 1 END AS is_tip
                FROM chain c
            ),
            ranked_tips AS (
                SELECT root_id, cur_id, activity,
                       ROW_NUMBER() OVER (
                           PARTITION BY root_id
                           ORDER BY activity DESC, cur_id DESC
                       ) AS rank_in_root
                FROM chain_rows
                WHERE is_tip = 1
            )
            SELECT
                tip.id,
                tip.source,
                tip.model,
                COALESCE(tip.title, s.title) AS title,
                s.started_at AS started_at,
                tip.ended_at,
                tip.end_reason,
                tip.message_count,
                tip.tool_call_count,
                rt.activity AS last_active,
                ztip.idah  ,
                CASE WHEN s.id != tip.id THEN s.id ELSE NULL END
                    AS _lineage_root_id
            FROM ranked_tips rt
            JOIN sessions s ON s.id = rt.root_id
            JOIN sessions tip ON tip.id = rt.cur_id
            WHERE rt.rank_in_root = 1
              AND s.archived = 0
              AND s.hidden = 0
              AND z
              AND zk IS NULL
            ORDER BY rt.activity DESC, s.started_at DESC, tip.id DESC
            LIMIT ?
        Fc                ó$   € V ^8„  d   QhR\         /# rç   r&  )r.   s   "r/   r0   ÚGSessionSessionsMixin.list_recent_sessions_bounded.<locals>.__annotate__  s   € ÷ 	ñ 	¬Cñ 	r1   c                  óD   <€ \         P                  ! 4       S 8¼  d   R o^# ^ # ©T)rB  Ú	monotonic)ÚdeadlineÚinterrupted_by_deadlines   €€r/   Ú_deadline_progress_handlerÚUSessionSessionsMixin.list_recent_sessions_bounded.<locals>._deadline_progress_handler  s   ø€ ä�~Š~Ó 8Ô+Ø*.Ð'ÙÙr1   iè  Ú	interruptzrecent-session browse exceeded Úgz
s deadlineÚ_preview_rawr4   r�  Úunread)Úmaxrs   r–  Úminr@   re   r}   r   r   r   r   rB  r©  Ú	_read_ctxÚset_progress_handlerrš   r›   r   ÚOperationalErrorr-   rÇ   ÚTimeoutErrorr  r   rß  rp  )rú   rš  rk   r›  rœ  r�  Úcandidate_clausesÚcandidate_paramsÚplaceholdersÚcandidate_whereÚcompression_parent_edger7  r�   r¬  rœ   r8  Úexcr„  r*   ré  rª  r«  s   &$$$$$              @@r/   Úlist_recent_sessions_boundedÚ1SessionSessionsMixin.list_recent_sessions_bounded‰  s  ù€ ô" �A”s˜5“zÓ"ˆÜ˜c¤5¨Ó#9Ó:ˆØÒ"Ü! # u¨q¥yÓ1ˆOÜ˜e¤S¬¨_Ó)=¸tÓ%DÓEˆØÒ Ü  o¸Õ&9Ó:ˆMÜ˜O¬S´°]Ó1CÀTÓ-JÓKˆð ØÜ"Ð#3Ó4Ð5°XÐ>ð
Ðð
 ')ÐßØŸ8™8Ñ#A±Ó#AÓAˆLØ×$Ñ$Ð'8¸¸ÀaÐ%HÔIØ×#Ñ# OÔ4Ø!Ÿ,™,Ð'8Ó9ˆð'ô %Ð%9Ó:Ð;ð <	ð#Ððð
 'Ð'ð ()ð *AÐ(Að 
Bð 3Ð3ð 	4(ð )@Ð'@ð Aô 4°JÓ?Ð@ð A0ð 1HÐ/Hð Iô2 " (Ó+Ð,ð 	-ô 'Ð'ð (Ü&Ð'7Ó8Ð9ð :	ðcTˆðj "ØØØØð	%
õ 
ˆô —>’>Ó# oÕ5ˆØ"'Ð÷	ñ 	ð	Ø—‘×!Ô! TØ×)Ñ)Ð*DÀdÔKð7ØŸ<™<¨Ó6×?Ñ?ÓA�Dà×-Ñ-¨d°AÕ6÷ "ð ˆÛˆCØ×,Ñ,¨SÓ1ˆGÜ!/°·±¸NÈBÓ0OÓ!PˆG�IÑØ $× 3Ñ 3°GÓ <ˆG�HÑØ�O‰O˜GÖ$ñ	 ð
 ˆøð ×-Ñ-¨d°AÕ6ú÷ "×!ûô ×'Ñ'ô 	ß&¨;¼#¸c»(¿.¹.Ó:JÔ+JÜ"Ø5°oÀaÐ5HÈ
ÐSóàðð ûð	úsN   ÆI6 ÆI#Æ1IÇI#Ç#I6 ÉI É I#É#I3	É.I6 É3I6 É6KÊ8KËKc                óP   <€ V ^8„  d   QhRS[ P                  RS[S[S[3,          /# r(   )r   ÚRowr,   r-   r   )r.   ré   s   "€r/   r0   rê   :  s'   ø€ ÷ ñ ™GŸK™Kð ©D±±c°­Nñ r1   c                óˆ   € V P                  V4      p\        VP                  RR4      4      VR&   VP                  RR4       V# )zTProject a list_sessions_rich row: shape the preview, drop internal ordering columns.r°  r4   r�  Ú_effective_last_activeN)r  r   rß  )Úclsr*   r“  s   && r/   Ú	_list_rowÚSessionSessionsMixin._list_row9  sA   € ð ×!Ñ! #Ó&ˆÜ% a§e¡e¨N¸BÓ&?Ó@ˆˆ)‰Ø	�‰Ð&¨Ô-Øˆr1   c          -      ó^  <€ V ^8„  d   QhRS[ R,          RS[S[ ,          R,          RS[S[ ,          R,          RS[ R,          RS[RS[RS[R	S[R
S[RS[RS[RS[RS[ R,          RS[ R,          RS[RS[RS[ R,          RS[RS[RS[S[ ,          R,          RS[S[S[ S[3,          ,          /# )r)   rh   Nri   rk   rK   rš  ÚoffsetÚinclude_childrenrl   Úproject_compression_tipsÚorder_by_last_activern   rm   Úid_queryÚsearch_queryr…  Úinclude_pinnedrj   Úinclude_hiddenro   rp   r+   )r-   rN   rs   rr   r   r,   r   )r.   ré   s   "€r/   r0   rê   A  s2  ø€ ÷ Cñ CÙ˜D•jðCÙ26±sµ)¸dÕ2BðCÙ\`ÑadÕ\eÐhlÕ\lðCá˜$•JðCá.1ðCá@CðCá[_ðCñ ðCñ ?CðCñ #ð	Cñ ?Cð	Cñ \`ð	Cñ
 ˜•*ðCñ
 47¸µ:ðCñ
 UYðCñ ðCñ 47¸µ:ðCñ W[ðCñ  ðCñ AIÉÅÐPTÕ@TðCñ 
‰d‘3™�8�nÕ	ñCr1   c                óÖ  € V P                  4        \        V'       * WVW4VWËVVR7      w  ppV'       g   V'       g   VP                  R4       / p^ RIHp V'       g   V! V VV4      p\        V4      pRV'       d   V P                  4       MR 2V'       d   RMR,           R\        4        R2,           pV'       d   RMR	pR
V 2pV
'       dà   T P                  TT;'       g    RP                  4       P                  4       T;'       g    RP                  4       P                  4       4      w  ppRV R\        RR4       R\        RR4       R\        P                  ! RR7       R\        R4       RV \        R4       R\!        R4       RV RV R2pVV,           V,           WV.,           pM)RV \        R4       RV RV R2pVP#                  WV.4       V P%                  VV4       U u. uF  p V P'                  V 4      NK  	  p!p V'       dþ   V! U"u0 uF  p"V"R,          kK  	  p#p"\        V'       * WVW4VR R!VVR7      w  p$p%V'       g   V'       g   V$P                  R4       V'       d   V! V V$V%4       V$P                  R"4       \        V$4      p&RV \        R4       RV RV& R#2p'V P%                  V'V%4       FM  p V P'                  V 4      p"V"R,          V#9  g   K$  V#P)                  V"R,          4       V!P                  V"4       KO  	  V	'       d   V'       g   V P+                  V!V4      p!V'       d   ^ R$IHp( V(! V!V4      p!V! F  p"V P/                  V"4      V"R%&   K  	  V!# u up i u up"i )&aÒ  List sessions with preview and ``last_active`` in one query. ``order_by_last_active`` sorts
by the chain TIP via a recursive CTE (the only path honouring ``id_query`` / ``search_query``);
``include_pinned`` back-fills pins the page missed, still obeying the other
filters except archived: a pin is an explicit keep, so a pinned row stamped
archived must still return. ``local_created_sources`` restricts ``source='local'``
rows to those surfaces before LIMIT/OFFSET.)rg   rh   ri   rj   rk   rK   rl   rm   rn   ro   rp   r   ©Úexclude_superseded_segmentszSELECT zs.*r4   zA, COALESCE(sp.prompt, s.system_prompt) AS _system_prompt_resolvedz,
                    z=LEFT JOIN system_prompts sp ON sp.hash = s.system_prompt_hashz FROM sessions s
                zr
                WITH RECURSIVE chain(root_id, cur_id) AS (
                    SELECT s.id, s.id FROM sessions s aT  
                    UNION ALL
                    SELECT c.root_id, child.id
                    FROM chain c
                    JOIN sessions parent ON parent.id = c.cur_id
                    JOIN sessions child ON child.parent_session_id = c.cur_id
                    WHERE parent.end_reason = 'compression'
                      AND r‹   rŒ   z# IS NULL
                      AND r=   z( IS NULL
                      AND NOT (rŽ   r�   zË)
                      AND COALESCE(child.source, '') != 'tool'
                ),
                chain_max AS (
                    SELECT
                        root_id,
                        MAX(Úcur_idz�) AS effective_last_active
                    FROM chain
                    GROUP BY root_id
                )
                r“  zH AS last_active,
                    COALESCE(cm.effective_last_active, zs.started_atzˆ) AS _effective_last_active
                FROM sessions s
                LEFT JOIN chain_max cm ON cm.root_id = s.id
                z
                z�
                ORDER BY _effective_last_active DESC, s.started_at DESC, s.id DESC
                LIMIT ? OFFSET ?
            z  AS last_active
                zY
                ORDER BY s.started_at DESC
                LIMIT ? OFFSET ?
            r“   FTzs.pinned = 1z8
                ORDER BY s.started_at DESC
            )Úannotate_local_lineagesr±  )r©  r‡   r}   Úhermes_state_localrÒ  rf   Ú_compact_session_colsr   r�  r7   rÇ   r   r   r.   r   r   r   r   r6  rÅ  rª   r˜  rÔ  rp  ))rú   rh   ri   rk   rK   rš  rÈ  rÉ  rl   rÊ  rË  rn   rm   rÌ  rÍ  r…  rÎ  rj   rÏ  ro   rp   Úwhere_clausesr�   ÚheadsrÒ  rs  Úselect_headÚprompt_joinÚfrom_sessionsÚouter_whereÚ	id_paramsr7  r*   r„  r“  Úseen_idsÚpinned_clausesÚpinned_paramsÚpinned_whereÚpinned_queryrÔ  s)   &&&&&&&&&&&&&&&&&&&&&                    r/   Úlist_sessions_richÚ'SessionSessionsMixin.list_sessions_richA  s¼  € ð 	×ÑÔ!Ü 5Ø!1Ô1¸&Ð_jØ+ÐVgØ'Ð^oØ"7ô	!
Ñˆ�v÷ §mØ× Ñ  Ô0ØˆÝBßñ 0°°mÀVÓLˆEÜ˜}Ó-ˆ	ð ·l�d×0Ñ0Ô2ÈÐNÐOß!‰rÐ'jõlà'Ô(8Ó(:Ð';Ð;RÐSõTð 	÷ ‰BÐ$cð 	ð <¸K¸=ÐIˆßð
 &*×%=Ñ%=Ø˜HŸN˜N¨×1Ñ1Ó3×9Ñ9Ó;¸l×>PÐ>PÈb×=WÑ=WÓ=Y×=_Ñ=_Ó=aó&Ñ"ˆK˜ð7à7@°kð Bô -Ð-AÐCUÓVÐWð XÜ,Ð-AÐCUÓVÐWð X Ü 0× 7Ò 7¸'Ô BÐCð Dô <¸HÓEÐFð Gð �Ô6°sÓ;Ð<ð =8Ü8FÀ~Ó8VÐ7Wð Xð �ð Ø�ð ð5ˆEð< ˜f•_ yÕ0°E°?ÕB‰FðØ�Ô6°sÓ;Ð<ð =Ø�ð  Ø�ð ðˆEð �M‰M˜5˜/Ô*Ø37·>±>À%ÈÔ3PÓQÑ3P¨C�D—N‘N 3Ö'Ñ3PˆÐQ÷
 Ù)1Ó2© A˜˜$Ÿ˜©ˆHÐ2Ü,AØ%5Ô!5¸fØ'¸Ø"3À5Ð[_Ø"3ÐK`ô	-Ñ)ˆN˜M÷ "¯-Ø×%Ñ% nÔ5ßÙ+¨D°.À-ÔPØ×!Ñ! .Ô1Ü% nÓ5ˆLð Ø�Ô6°sÓ;Ð<ð =Ø�ð  Ø�ð ðˆLð —~‘~ l°MÖB�Ø—N‘N 3Ó'�Ø�T•7 (Ö*Ø—L‘L  4¥Ô)Ø—O‘O AÖ&ñ	 C÷
 $×,<Ø×5Ñ5°hÀÓMˆHßÝBÙ.¨x¸Ó?ˆHãˆAØ×-Ñ-¨aÓ0ˆAˆh‹Kñ àˆùòM Rùò 3s   Ç(M!ÈM&c                óL   <€ V ^8„  d   QhRS[ S[,          RS[S[S[3,          /# ©r)   Úsession_idsr+   )rN   r-   r,   )r.   ré   s   "€r/   r0   rê   Æ  s'   ø€ ÷ ñ ±d¹3µið ÁDÉÉcÈÅNñ r1   c                óR  € T;'       g    .  Uu. uF  q"'       g   K  VNK  	  ppV'       g   / # V Uu/ uF  q"RbK  	  ppV P                  R\        V4       R2V4      pV F8  p\        VR,          \        VR,          4      VR,          R7      WFR,          &   K:  	  V# u upi u upi )	už   ``{session_id: status}`` from each session's LAST message row (``'empty'`` when none); one
query, MAX(id) per session joined back â€” never scans transcripts.r¾   a3  
            SELECT m.session_id, m.role,
                   m.tool_calls IS NOT NULL AS has_tool_calls,
                   m.finish_reason
            FROM messages m
            JOIN (
                SELECT session_id, MAX(id) AS max_id
                FROM messages
                WHERE session_id IN (z[)
                GROUP BY session_id
            ) latest ON m.id = latest.max_id
        rÀ   rÁ   rÂ   )rÀ   rÁ   rÂ   r  )r6  r~   rÍ   rr   )rú   rç  r�   rž   Ústatusesr8  r*   s   &&     r/   Úsession_lifecycle_statusesÚ/SessionSessionsMixin.session_lifecycle_statusesÆ  sÄ   € ð  +×0Ð0¨bÐ0Ó9Ñ0�s³S�sˆsÑ0ˆÐ9ßØˆIÙ<?Ó#@¹C°S¨¢L¹CˆÐ#@Ø�~‰~ð #&ô '@ÀÓ&DÐ%Eð F	ðð óˆó ˆCÜ*AØ˜•[´°cÐ:JÕ6KÓ1LØ! /Õ2ô+ˆH˜Õ&Ó'ñ ð
 ˆùò+ :ùò $As   �	B�B³B$c                ó<   <€ V ^8„  d   QhRS[ RS[S[,          RS[/# )r)   r  Úmax_messagesr+   )r-   r   rs   )r.   ré   s   "€r/   r0   rê   à  s'   ø€ ÷ ñ ©Sð ÁÉÅð ÑY\ñ r1   c                óì   € ^ RI HpHp Vf   V! 4       pV^ 8  d   \        R4      hV^ 8X  d   ^ # V P	                  RW^,           34      p\        V'       d
   V^ ,          M^ 4      pWb8”  d
   V! WV4      hV# )u  Row count of this segment â€” every row, archived included, as the transfer export materializes
it â€” or raise SessionExportTooLargeError (the LIMITed subquery
stops once the bound is exceeded). ``None`` resolves ``sessions.max_export_messages``; 0 disables
the guard.)ÚSessionExportTooLargeErrorÚresolved_max_export_messagesz!max_messages must be non-negativezJSELECT COUNT(*) FROM (SELECT 1 FROM messages WHERE session_id = ? LIMIT ?))r&   rï  rð  rÞ  rŸ  rs   )rú   r  rí  rï  rð  r*   r�  s   &&&    r/   Úassert_export_safeÚ'SessionSessionsMixin.assert_export_safeà  s~   € ÷
 	ZØÒÙ7Ó9ˆLØ˜!ÔÜÐ@ÓAÐAØ˜1ÔÙØ�n‰nØXØ¨Õ)Ð*ó
ˆô §c˜C žF¨qÓ1ˆØÔ'Ù,¨ZÈÓUÐUØÐr1   c                ó4   <€ V ^8„  d   QhRS[ S[,          RR/# )r)   rí  r+   N)r   rs   )r.   ré   s   "€r/   r0   rê   õ  s$   ø€ ÷ Kñ K¹XÁc½]ð KÐVZñ Kr1   c                óp   € ^ RI Hp Vf   V! 4       pV^ 8X  d   R# V F  pV P                  WBR7       K  	  R# )zUassert_export_safe for each id with the limit resolved once; 0 disables (no queries).)rð  N)rí  )r&   rð  rñ  )rú   rç  rí  rð  r  s   &&&  r/   Úassert_exports_safeÚ(SessionSessionsMixin.assert_exports_safeõ  s9   € å=ØÒÙ7Ó9ˆLØ˜1ÔÙÛ%ˆJØ×#Ñ# JÐ#ÖJó &r1   c                ó&   <€ V ^8„  d   QhRS[ RS[/# rP  r+  )r.   ré   s   "€r/   r0   rê   ÿ  s   ø€ ÷ [ñ [±cð [¹dñ [r1   c                ó¨   € V'       g   R# V P                  RV34      pVRJ;'       d+    \        \        V^ ,          4      P                  R4      4      # )zŒCopied user-facing branch (``_branched_from``)? Branches own a copied transcript;
compression continuations need the parent's archived rows.FrÜ  NÚ_branched_from)rŸ  rr   rI   r6   r£  s   && r/   Ú_is_explicit_branch_sessionÚ0SessionSessionsMixin._is_explicit_branch_sessionÿ  sK   € ÷ ÙØ�n‰nÐMÐPZÈ}Ó]ˆØ˜$ˆ×ZÐZ¤4Ô(;¸CÀ½FÓ(C×(GÑ(GÐHXÓ(YÓ#ZÐZr1   c                ó6   <€ V ^8„  d   QhRS[ RS[S[ ,          /# rP  rò  )r.   ré   s   "€r/   r0   rê     s   ø€ ÷ 5ñ 5±sð 5¹tÁC½yñ 5r1   c                ó†  € V'       g   V.# . pTpV P                  4       ;_uu_ 4       pV'       dY   W29  dS   \        V4      ^d8  dC   VP                  V4       VP                  RV34      P	                  4       pVf   MV^ ,          pK`  RRR4       \        \        V4      4      ;'       g    V.#   + '       g   i     L/; i)éd   z3SELECT parent_session_id FROM sessions WHERE id = ?N)r´  rÖ   r}   rš   rv  rN   Úreversed)rú   r  r”  rw  rœ   r*   s   &&    r/   Ú_session_lineage_root_to_tipÚ1SessionSessionsMixin._session_lineage_root_to_tip  s    € ßØ�<ÐØˆØˆØ�^‰^×Ô ß˜gÔ2´s¸5³zÀCÔ7GØ—‘˜WÔ%Ø—l‘lØIÈGÈ:óç‘(“*ð ð ’;ØØ˜a�&’÷ ô ”H˜U“OÓ$×4Ð4¨¨Ð4÷ ×ús   ¨B0±AB0Â0C 	c                óÄ   <€ V ^8„  d   QhRS[ S[S[ ,          ,          R,          RS[RS[RS[ R,          RS[S[ ,          R,          RS[RS[S[S[ S[3,          ,          /# )	r)   rh   Nrš  rÈ  r8   rp   Úskip_launch_stubsr+   )r-   r   rs   rr   rN   r,   r   )r.   ré   s   "€r/   r0   rê     sr   ø€ ÷ ñ Ù™H¡S�MÕ)¨DÕ0ðÙ@CðÙRUðá˜T•zðáAIÉ#ÅÐQUÕAUðñ  ðñ 
‰d‘3™�8�nÕ	ñ	r1   c           	     ó   € V'       d   \        V 4      .M. p. pV'       dT   \        V\        4      '       d   V.M
\        V4      p	\	        W•4      w  r«VP                  V
4       VP                  V4       V'       d0   \        V4      w  rÍVP                  V4       VP                  V4       V P                  R\        R4       R\        VR4       R2. VOVNVN4       Uu. uF  qàP                  V4      NK  	  up# u upi )ag  Sessions MRU-first with a computed ``last_active``; ``workspace_key`` scopes to one workspace
so ``hermes -c``/``--resume`` picks its last session. ``source`` may be one label or several.
``local_created_sources`` restricts ``source='local'`` rows before LIMIT/OFFSET, and
``skip_launch_stubs`` drops empty launch stubs (launch_stub_filter_sql) before it too.zMSELECT s.*, COALESCE(sp.prompt, s.system_prompt) AS _system_prompt_resolved, r“  z^ AS last_active FROM sessions s LEFT JOIN system_prompts sp ON sp.hash = s.system_prompt_hash rÕ   zI ORDER BY last_active DESC, s.started_at DESC, s.id DESC LIMIT ? OFFSET ?)r#   rD   r-   rN   r!   r}   r   r]   r6  r   rf   r  )rú   rh   rš  rÈ  r8   rp   r  r×  r�   ri   r…   ÚboundÚ	ws_clauseÚ	ws_paramsr*   s   &&&&&&&        r/   Úsearch_sessionsÚ$SessionSessionsMixin.search_sessions  s  € ÷ ;LÔ/°Ó5Ñ6ÐQSˆØˆßÜ",¨V´S×"9Ò"9�v‘h¼tÀF»|ˆGÜ0°ÓP‰MˆFØ× Ñ  Ô(Ø�M‰M˜%Ô ßÜ#8¸Ó#GÑ ˆIØ× Ñ  Ô+Ø�M‰M˜)Ô$Ø7;·~±~Ø[Ü'¨Ó,Ð-ð .]ä˜-¨Ó-Ð.ð /WðWð
 %ˆfÐ$�eÐ$˜VÐ$ô8
ó ñ 8
°×&Ñ& sÖ+ñ 8
ñ ð 	ùò s   ÃC;c                óÒ   <€ V ^8„  d   QhRS[ R,          RS[S[ ,          R,          RS[ R,          RS[RS[RS[RS[R	S[S[ ,          R,          R
S[RS[S[ ,          R,          RS[/# )r)   rh   Nri   rK   rl   rn   rm   rg   rk   ro   rp   r+   )r-   rN   rs   rr   r   )r.   ré   s   "€r/   r0   rê   4  s£   ø€ ÷ mñ mÙ˜D•jðmÙ26±sµ)¸dÕ2BðmÙWZÐ]aÕWaðmáðmá6:ðmáSWðmñ ðmñ :>¹c½ÀTÕ9Iðmñ fjðmñ  (©�}¨tÕ3ð	mñ
 
ñmr1   c                ó¤   € \        WqVWƒVWeV	V
R7
      w  r¼V'       d   ^ RIHp V! WV4       V P                  R\	        VR4       2V4      ^ ,          # )zWCount sessions with list_sessions_rich's filters so a paired "load more" total matches.)
rg   rh   ri   rk   rK   rl   rm   rn   ro   rp   rÑ  zSELECT COUNT(*) FROM sessions srÕ   )r‡   rÕ  rÒ  rŸ  rf   )rú   rh   ri   rK   rl   rn   rm   rg   rk   ro   rp   r×  r�   rÒ  s   &&&&&&&&&&&   r/   Úsession_countÚ"SessionSessionsMixin.session_count4  s`   € ô !6Ø-ÀgØ+ÐVgØ'Ð^oØ"7ô	!
Ñˆ÷ ÝFÙ'¨¸VÔDØ�~‰~Ð ?Ä
È=ÐZ]Ó@^Ð?_Ð`ÐbhÓiÐjkÕlÐlr1   c                ó&   <€ V ^8„  d   QhRS[ RS[/# )r)   Únr+   )rs   rr   )r.   ré   s   "€r/   r0   rê   F  s   ø€ ÷ Pñ P¡#ð P©dñ Pr1   c                ó@   € \        V P                  RV34      4      V8¬  # )z[At least N sessions exist (archived included); LIMIT short-circuits session_count()'s scan.zSELECT 1 FROM sessions LIMIT ?)rÖ   r6  )rú   r  s   &&r/   Úsession_count_geÚ%SessionSessionsMixin.session_count_geF  s    € ä�4—>‘>Ð"BÀQÀDÓIÓJÈaÑOÐOr1   rn   rm   rg   c          
      óH   <€ V ^8„  d   QhRS[ RS[ RS[ RS[S[S[3,          /# )r)   rn   rm   rg   r+   )rr   r,   r-   rs   )r.   ré   s   "€r/   r0   rê   J  s=   ø€ ÷ Lñ LÙ#'ðLÙ@DðLáðLñ 
‰c‘3ˆh�ñLr1   c          	     óÈ  € \        W2VR7      w  rEV'       d   ^ RIHp V! WV4       V P                  4       ;_uu_ 4       pV P                  f   \        R4      hVP                  R\        VR4       R2V4      P                  4       pRRR4       X U	u/ uF0  p	\        V	R,          4      \        V	R	,          ;'       g    ^ 4      bK2  	  up	#   + '       g   i     LM; iu up	i )
zV``{source: count}`` via one GROUP BY; ``exclude_children`` mirrors listing visibility.)rg   rm   rn   rÑ  NzSessionDB connection is closedzYSELECT COALESCE(NULLIF(s.source, ''), 'cli') AS source, COUNT(*) AS count FROM sessions srÕ   zC GROUP BY COALESCE(NULLIF(s.source, ''), 'cli') ORDER BY count DESCrh   Úcount)r‡   rÕ  rÒ  r´  Ú_connÚRuntimeErrorrš   rf   r›   r-   rs   )
rú   rn   rm   rg   r×  r�   rÒ  rœ   r8  r*   s
   &$$$      r/   Úsession_count_by_sourceÚ,SessionSessionsMixin.session_count_by_sourceJ  sÕ   € ô
 !6Ø-Ø-ô!
Ñˆ÷ ÝFÙ'¨¸VÔDØ�^‰^×Ô Ø�z‰zÒ!Ü"Ð#CÓDÐDØ—<‘<ð"Ü",¨]¸CÓ"@Ð!Að BUðUàV\ó÷ ‰h‹jð	 ÷ ñ GKÓKÁd¸s”�C˜•MÓ"¤C¨¨G­×(9Ð(9¸Ó$:Ò:ÁdÑKÐK÷ ×üò Ls   ¿ACÂ*CÂ>CÃC	c                ó<   <€ V ^8„  d   QhRS[ RS[S[S[ 3,          /# rP  )r-   rM   rr   )r.   ré   s   "€r/   r0   rê   `  s(   ø€ ÷ cñ c±#ð c¹%ÁÁcÀ	Õ:Jñ cr1   c                ó¾   € V P                  V4      pV'       g   R# V P                  V4      \        VP                  R4      ;'       g    R4      P	                  4       3# )uð  (is_fork_child, source) in ONE read (prompt_cache_scope needs both from the same row).
Missing row â†’ (False, ""); DB errors propagate (fail closed).

``agent/prompt_cache_scope.py`` needs both to resolve a host-declared conversation scope, and both
live on the same ``sessions`` row; asking for them separately read that row twice per resolution
(@teknium1 on 98811). The marker rules stay here, beside :meth:`is_explicit_fork_child`, instead of
being re-implemented by the caller. See #98811.
r4   rh   )Fr4   )rè  Ú_is_explicit_fork_child_rowr-   r6   r7   )rú   r  ré  s   && r/   Údeclared_scope_identityÚ,SessionSessionsMixin.declared_scope_identity`  sS   € ð ×"Ñ" :Ó.ˆßØÐØ×/Ñ/°Ó8¼#¸g¿k¹kÈ(Ó>S×>YÐ>YÐWYÓ:Z×:`Ñ:`Ó:bÐbÐbr1   c                ó:   <€ V ^8„  d   QhRS[ S[,          RS[RR/# )r)   Úsessions_dirr  r+   N)r   r   r-   )r.   ré   s   "€r/   r0   rê   o  s'   ø€ ÷ ñ ©H±T­Nð Éð ÐPTñ r1   c                óz  € V f   R# R Uu. uF  q V V 2,          NK  	  ppVP                  V RV R2,          4        VP                  V P                  R\        P                  ! V4       R24      4       V F  p VP                  RR7       K  	  R# u upi   \         d     L0i ; i  \         d     K@  i ; i)	a9  Remove ``<id>.json``/``.jsonl``, the legacy ``session_<id>.json`` snapshot, and gateway
``request_dump_<id>_*.json``; OSError is swallowed so a filesystem hiccup never blocks a
DB operation. Every historical writer name is swept because a "deleted" session's snapshot
can carry plaintext secrets (#20334, #60207).Nú.jsonÚsession_Úrequest_dump_z_*.jsonT)Ú
missing_ok)r"  z.jsonl)r}   r   ÚglobÚescapeÚOSErrorÚunlink)r   r  ÚsuffixÚtargetsÚps   &&   r/   Ú_remove_session_filesÚ*SessionSessionsMixin._remove_session_filesn  sÊ   € ð ÒÙÙH[Ó\ÑH[¸f Z L°°Ð"9×9Ð9ÑH[ˆÐ\Ø�‰�|¨°°¸EÐ&BÕBÔCð	ð �N‰N˜<×,Ñ,¨}¼T¿[º[ÈÓ=TÐ<UÐU\Ð-]Ó^Ô_ó ˆAðØ—‘ D�Ö)ó ùò ]øô ô 	Ùð	ûô
 ô Úðús(   ‹B¿8B Á=B+ÂB(Â'B(Â+B:Â9B:c                ó6   <€ V ^8„  d   QhRS[ RS[S[ ,          /# rP  rò  )r.   ré   s   "€r/   r0   rê   „  s#   ø€ ÷ Wñ W±Sð W¹TÁ#½Yñ Wr1   c                óV  € V P                  4       ;_uu_ 4       pVP                  RV34      P                  4       '       g   . uuRRR4       # ^ RIHp V! W!4      p\        W$4      pRRR4       V.\        \        X4      V0,
          4      O\        X4      O#   + '       g   i     L:; i)z”Rows :meth:`delete_session` would remove: the session, then its recursive delegate children
(branch/compression children are orphaned, not deleted).ú+SELECT 1 FROM sessions WHERE id = ? LIMIT 1N©Úowned_lineage_ids)r´  rš   rv  rÕ  r3  r°   Úsortedr©   )rú   r  rœ   r3  ÚscopeÚdelegate_idss   &&    r/   Úget_session_delete_targetsÚ/SessionSessionsMixin.get_session_delete_targets„  s�   € ð �^‰^×Ô Ø—<‘<Ð MÐPZÈ}Ó]×fÑf×hÒhØ÷ Òõ =Ù% dÓ7ˆEÜ6°tÓCˆL÷ ð ÐVœV¤C¨£J°*°Õ$=Ó>ÐVÄÈÓAUÐVÐV÷ ×ús   ™)BÁBÂB(	c                óF   <€ V ^8„  d   QhRS[ S[,          RS[ S[,          /# ræ  ra   )r.   ré   s   "€r/   r0   rê   ’  s$   ø€ ÷ #ñ #¹TÁ#½Yð #É4ÑPSÍ9ñ #r1   c                óV  € \        V4      p\        V4      p\        \        4       FB  pV'       g    M9\	        W4      pV Uu. uF  qwV9  g   K  VNK  	  ppVP                  V4       KD  	  ^ RIHp \        V4       F  pVP                  V! W4      4       K  	  \        V4      # u upi )u  Expand *session_ids* to every member of their compression chains.

The dashboard sessions list surfaces one row per logical conversation: compression
roots are projected forward to their latest continuation, and the surfaced row
carries the *tip's* id (see :meth:`list_sessions_rich`). Deleting only that tip row
leaves the root behind, and the next list re-projects the root onto the previous
link â€” the "deleted" conversation reappears (#57543).

Walks compression-continuation edges in BOTH directions from the given ids:
backward to the chain root, and forward through every continuation â€” including
stale sibling continuations (e.g. ``ws_orphan_reap`` leftovers), which would
otherwise be orphaned into brand-new visible top-level rows by the delete itself.
The edge definition matches ``_CHAIN_STEP_SQL`` / ``get_compression_tip`` and the
list-projection CTE: parent ended with ``reason='compression'``, child is not a
branch / reset / delegate / tool session. Branch, reset and delegate children are
deliberately NOT pulled in â€” a branch/reset is its own conversation, and delegates
are handled by the existing cascade in the delete paths.

Runs inside the caller's write transaction (*conn*). The walk is defensively
bounded like ``get_compression_chain`` â€” ``seen`` guards cycles.
)Úlocal_conversation_ids)r©   rN   Úranger$   r£   rÊ  rÕ  r;  )	rú   rœ   rç  Úseenr‰   rƒ  r+  r�   r;  s	   &&&      r/   Ú_expand_compression_lineageÚ0SessionSessionsMixin._expand_compression_lineage’  s’   € ô, �;ÓˆÜ˜Ó$ˆÜ”zÖ"ˆAßÙÜ5°dÓEˆGÙ'.ÓB¡w ¸T±/Ÿ˜¡wˆHÐBØ�K‰K˜Ö!ñ #õ 	>Ü˜–:ˆCØ�K‰KÑ.¨tÓ9Ö:ñ ä�D‹zÐùò Cs   ÁB&ÁB&c                óÀ   <€ V ^8„  d   QhRS[ RS[S[,          RS[S[S[ ,          ,          RS[S[S[ S[S[S[ S[3,          ,          3,          ,          RS[RS[RS[/# )r)   r  r   Úexpected_delete_idsÚexpected_display_messagesÚexclude_active_write_guardsÚinclude_compression_chainr+   )r-   r   r   rN   r,   r   rr   )r.   ré   s   "€r/   r0   rê   ·  s   ø€ ÷ Eñ EÙðEÙ-5±d­^ðEá%¡d©3¥iÕ0ðEñ $,©D±±d¹4ÁÁSÀ½>Õ6JÐ1JÕ,KÕ#LðEñ &*ð	Eñ
 $(ðEñ 
ñEr1   c                óD  a aaaaa€ V'       d   S P                  S.VSRR7      ^ 8„  # . oVe   \        V4      MRoVVVVV V3R lpS P                  V4      pV'       d   \        S SRSVR7       ^ RIHp	 V	! S 4       S F  p
S P                  W*4       K  	  \        V4      # )u$  Delete a session and its messages; delegate children cascade, branch/compression children
are orphaned. Optional expected ids fence delegate drift; expected display snapshots fence
transcript drift. Both checks run inside the same write transaction as deletion.
With ``exclude_active_write_guards``, raises :class:`SessionActiveWriteGuardError` if the row
is protected by an active turn lease or compression lock.

With ``include_compression_chain=True`` the whole compression chain the session belongs to
is deleted with it (root and every continuation) â€” the dashboard delete endpoints use this
variant because their list rows carry chain-*tip* ids; deleting only that physical row
would leave the rest of the chain to resurface on the next list (#57543). Default False so
callers that operate on a single physical session keep the historic contract. Routing
through :meth:`delete_sessions` keeps the chain-aware guard/counting semantics in one
place; its return value counts the *requested* ids that existed, so ``> 0`` is exactly
"the session was found".T)r   rC  rD  Nc                 óD  <a € S P                  R S34      P                  4       f   R# ^ RIHp V! S S4      pS'       g   Se   . VO\	        S V4      OMRpS'       d(   S
P                  S V4      '       d   \        RS R24      hSe   S\        V4      8w  d   R# Sea   \        ;QJ d.    V V
3R lSP                  4        4       F  '       g   K   RM#	  RM! V V
3R lSP                  4        4       4      '       d   R# ^ RI
Hp V! S V4       S	P                  \        S V4      4       V F)  pS P                  R	V34       S P                  R
V34       K+  	  S P                  RV Uu. uF  qU3NK  	  up4       S
P                  S 4       S	P                  V4       R# u upi )r1  NFr2  z	session 'zD' (or a delegate child) has an active turn lease or compression lockc              3   óR   <"  € T F  w  rSP                  SV4      V8g  x € K  	  R # 5irÚ   )Ú_display_messages_from_conn)r•   Ú
covered_idÚexpectedrœ   rú   s   &  €€r/   r–   ÚCSessionSessionsMixin.delete_session.<locals>._do.<locals>.<genexpr>æ  s-   øé € ð =á,MÑ(�Jð ×0Ñ0°°zÓBÀhÖNÛ,Mùs   ƒ$'Tr´   zHUPDATE sessions SET parent_session_id = NULL WHERE parent_session_id = ?ú)DELETE FROM messages WHERE session_id = ?ú!DELETE FROM sessions WHERE id = ?)rš   rv  rÕ  r3  r°   Ú_guarded_idsr   r©   ÚanyrŽ  r¹   rµ   r   rº   Úexecutemanyr±  )rœ   r3  r5  Ú
target_idsrµ   r�   rC  rB  Úexpected_idsÚremoved_idsrú   r  s   f     €€€€€€r/   rc  Ú0SessionSessionsMixin.delete_session.<locals>._doÔ  sŒ  ù€ Ø�|‰|ÐIÈJÈ=ÓY×bÑbÓdÒlÙõ =Ù% d¨JÓ7ˆE÷ /°,Ò2Jð D�%ÐCÔ5°d¸EÓBÑCØPTð ÷ +¨t×/@Ñ/@ÀÀz×/RÒ/Rä2Ø 
˜|Ð+oÐpóð ð Ò'¨L¼CÀ
»OÔ,KÙØ(Ò4¿»õ =à,E×,KÑ,KÔ,Mó=¿¿ºõ =à,E×,KÑ,KÔ,Mó=÷ :ò :ñ ÝHÙ˜D %Ô(Ø×ÑÔ8¸¸uÓEÔFÛ�Ø—‘ÐgÐjmÐioÔpØ—‘ÐHÈ3È&ÖQñ ð ×ÑÐ@ÑUZÓB[ÑUZÈcÃ6ÑUZÑB[Ô\Ø×4Ñ4°TÔ:Ø×Ñ˜uÔ%Ùùò C\s   Å'F
Údelete©rI  rA  r   ©Úcollect_retired_media)Údelete_sessionsr©   r  r"   Úhermes_state_mediarX  r-  rr   )rú   r  r   rA  rB  rC  rD  rc  ÚdeletedrX  r�   rR  rS  s   ff&&ff&    @@r/   Údelete_sessionÚ#SessionSessionsMixin.delete_session·  s¯   ý€ ÷* %Ø×'Ñ'Ø�¨<Ø,GØ*.ð (ó ð ñ	ð ð
 "$ˆØ3FÒ3R”sÐ.Ô/ÐX\ˆ÷ 	ò  	ðB ×%Ñ% cÓ*ˆßÜ   {¸8ÈZÐfrÕsÝ<Ù˜dÔ#ÛˆCØ×&Ñ& |Ö9ñ ä�G‹}Ðr1   c                ó&   <€ V ^8„  d   QhRS[ RS[/# rP  r+  )r.   ré   s   "€r/   r0   rê   þ  s   ø€ ÷ ñ ±Sð ¹Tñ r1   c                ór   a€ V3R lpV P                  V4      pVf   V P                  S4      # \        V4      # )a'  Abort a row this same flow just created and nobody has been admitted against (seed-copy
compensation): remove it WITHOUT the retirement fence so the id can be lazily recreated.
A row with any admission or worker receipt is a real session and takes the fenced
:meth:`delete_session` path instead.c                 óÂ   <€ V P                  R SS34      P                  4       '       d   R# V P                  RS34       V P                  RS34      pVP                  ^ 8„  # )zSELECT 1 FROM session_admissions WHERE target_session_id=? UNION ALL SELECT 1 FROM worker_executions WHERE session_id=? LIMIT 1NrL  rM  )rš   rv  rK  )rœ   Úcurr  s   & €r/   rc  Ú<SessionSessionsMixin.discard_unadmitted_session.<locals>._do  sf   ø€ Ø�|‰|ðMØOYÐ[eÐNfóhçhpÑhpÓhrösñ Ø�L‰LÐDÀzÀmÔTØ—,‘,ÐBÀZÀMÓRˆCØ—<‘< !Ñ#Ð#r1   )r  r\  rr   )rú   r  rc  r[  s   &f  r/   Údiscard_unadmitted_sessionÚ/SessionSessionsMixin.discard_unadmitted_sessionþ  s:   ø€ õ
	$ð ×$Ñ$ SÓ)ˆØŠ>Ø×&Ñ& zÓ2Ð2Ü�F‹|Ðr1   c                ó<   <€ V ^8„  d   QhRS[ RS[S[,          RS[/# )r)   r  r   r+   )r-   r   r   rr   )r.   ré   s   "€r/   r0   rê     s'   ø€ ÷ *ñ *±#ð *ÁXÉdÅ^ð *Ñ_cñ *r1   c                óº   a a€ V V3R lpS P                  V4      pV'       d   \        S S.RVR7       ^ RIHp V! S 4       V'       d   S P	                  VS4       V# )aÅ  Delete *session_id* only if it has no messages, no title and no children; check and delete
share one transaction so a concurrent flush can't be lost. A row under an active turn lease
or compression lock is never a candidate: the emptiness predicate reads committed state, so a
row whose first turn is already leased but not yet flushed would be deleted mid-turn
(#123583). A guarded row is simply not deleted (returns ``False``), like any non-empty row.c                 ó$  <€ ^ RI Hp SP                  V S.4      '       g   V! V S4      S.8w  d   R# V P                  RS34      P	                  4       pVf   R# ^ RIHp V! V S.S.R7       V P                  RS34       SP                  V 4       R# )r³   r2  Fa±  
                SELECT 1 FROM sessions
                WHERE id = ?
                  AND title IS NULL
                  AND NOT EXISTS (
                      SELECT 1 FROM messages WHERE messages.session_id = sessions.id
                  )
                  AND NOT EXISTS (
                      SELECT 1 FROM sessions child
                      WHERE child.parent_session_id = sessions.id
                  )
                r´   )ÚexpiringrM  T)rÕ  r3  rN  rš   rv  r¹   rµ   r±  )rœ   r3  Úeligiblerµ   rú   r  s   &   €€r/   rc  Ú9SessionSessionsMixin.delete_session_if_empty.<locals>._do  s›   ø€ Ý<Ø× Ñ  ¨
 |×4Ò4Ñ8IÈ$ÐPZÓ8[Ð`jÐ_kÔ8kÙØ—|‘|ðð �ó÷ ‰h‹jð ð ÒÙõ IÙ˜D : ,¸*¸ÕFØ�L‰LÐ<¸z¸mÔLØ×4Ñ4°TÔ:Ùr1   Úempty_sweep©rI  r   rW  ©r  r"   rZ  rX  r-  )rú   r  r   rc  r[  rX  s   ff&   r/   Údelete_session_if_emptyÚ,SessionSessionsMixin.delete_session_if_empty  sQ   ù€ ö	ð: ×%Ñ% cÓ*ˆßÜ  ¨
 |¸MÐXdÕeÝ<Ù˜dÔ#ßØ×&Ñ& |°ZÔ@Øˆr1   c                ó~   <€ V ^8„  d   QhRS[ S[,          RS[S[,          RS[RS[S[ S[,          ,          RS[RS[/# )r)   rç  r   rC  Úskipped_idsrD  r+   )rN   r-   r   r   rr   rs   )r.   ré   s   "€r/   r0   rê   <  sZ   ø€ ÷ Xñ XÙ¡�9ðXÙ4<¹TµNðXá%)ðXá@HÉÉcÍÕ@SðXñ $(ðXñ 
ñ	Xr1   c           	     ó¬  a aaaa
a€ \        T;'       g    R Uu0 uF'  p\        V\        4      '       g   K  V'       g   K%  VkK)  	  up4      oS'       g   ^ # . o
VVV
V VV3R lpS P                  V4      pV'       d*   \	        S S
R\        S4      ^8X  d
   S^ ,          MRVR7       ^ RIHp	 V	! S 4       S
 F  pS P                  W&4       K  	  V# u upi )uh  Bulk delete with :meth:`delete_session` semantics per row, in ONE transaction. Unknown ids
are skipped (UI selection can race another tab's delete). With ``exclude_active_write_guards``, rows
protected by an active turn lease or compression lock are skipped and, when given, appended
to ``skipped_ids`` so callers can tell the user. Returns the number deleted.

With ``include_compression_chain=True``, each selected id is expanded to its full
compression chain (root + every continuation, including stale sibling continuations)
via :meth:`_expand_compression_lineage` â€” the dashboard rows carry chain-tip ids, so
without the expansion a "deleted" conversation resurfaces as the previous chain link on
the next list reload (#57543). The returned count still reflects the *requested* rows,
not the expanded chain links, so the caller's toast matches the user's selection.c                 ó  <a a€ \        S4       UUu. uF?  pS P                  R \        V4       R2V4      P                  4        F  q"R,          NK  	  KA  	  pppV'       g   ^ # ^ RIHo V VVV3R lpV! V4      p. VO\        S V4      OpS'       Ed/   \        4       pSP                  S V4      '       dÄ   S'       dz   V Fr  p\        SP                  S V.4      4      p	V	P                  V4       SP                  S . V	O\        S \        V	4      4      O4      '       g   Ka  VP                  V4       Kt  	  MBV Uu0 uF6  pSP                  S . V! V.4      ;p
O\        S V
4      O4      '       g   K4  VkK8  	  ppV Uu. uF  qˆV9  g   K  VNK  	  ppSe   SP                  \        V4      4       V'       g   ^ # V! V4      p^ RIHp V! S V4       SP                  \!        S V4      4       \        V4       FP  p\        V4      pS P                  RV R2V4       S P                  RV R2V4       S P                  RV R2V4       KR  	  SP#                  S 4       SP                  V4       \%        V4      # u uppi u upi u upi )	z%SELECT id FROM sessions WHERE id IN (rz   r“   r2  c                 óŠ   <€ S'       d   SP                  SV 4      # \        \        P                  VV3R  lV  4       4      4      # )c              3   óF   <"  € T F  pS! SV4       F  q"x € K  	  K  	  R # 5irÚ   r|   )r•   r�   Úxrœ   r3  s   &  €€r/   r–   ÚTSessionSessionsMixin.delete_sessions.<locals>._do.<locals>.expand.<locals>.<genexpr>\  s$   øé € Ð)a±s°ÑDUÐVZÐ\_ÖD`¸q¬!ÑD`©!³sùs   ƒ!)r>  rN   r,   Úfromkeys)rž   rœ   rD  r3  rú   s   &€€€€r/   ÚexpandÚASessionSessionsMixin.delete_sessions.<locals>._do.<locals>.expandX  s3   ø€ ç,Ø×;Ñ;¸DÀ#ÓFÐFÜœDŸM™MÕ)a±sÓ)aÓaÓbÐbr1   r´   r·   r¶   r¸   )r   rš   r~   r›   rÕ  r3  r°   r©   rN  r>  rª   rN   r   r4  r¹   rµ   rº   r±  rÖ   )rœ   r    r*   Úexistingry  ÚexpandedÚ
guard_poolÚ
active_idsr�   Úmember_lineageÚmembersrµ   r¡   r3  rC  rD  rS  rú   rq  Ú
unique_idss   f            @€€€€€€r/   rc  Ú1SessionSessionsMixin.delete_sessions.<locals>._doP  s«  ú€ Ü/9¸*Ô/Eô Ñ/E eÐQU×Q]ÑQ]Ø7Ô8QÐRWÓ8XÐ7YÐYZÐ[Ð]bóRç‰h‹jòRÈ#˜DŸ	˜	ñ R™	Ñ/EˆHñ ÷ ÙÝ<÷cð cñ ˜hÓ'ˆHØR˜8ÐRÔ&AÀ$ÈÓ&QÐRˆJß*Ð*ô #&£%�
Ø×$Ñ$ T¨:×6Ò6ß0Û#+˜CÜ-0°×1QÑ1QÐRVÐY\ÐX]Ó1^Ó-_˜NØ*×.Ñ.¨sÔ3Ø#×0Ñ0°ð 8^¸ð 8^Ü%@ÀÄtÈNÓG[Ó%\ð8^÷  _ô  _à *§¡¨sÖ 3ò $,ñ ,4ó&Ù+3 CØ#×0Ñ0°ð 8eÁVÈSÈEÃ]Ð:R¸'ð 8eÜ9TÐUYÐ[bÓ9cð8e÷  f÷  ˜C©8ð #ð &ñ
 ,4ÓM©8 CÀ*Ñ7LŸC˜C©8�ÐMØÒ*Ø×&Ñ&¤v¨jÓ'9Ô:ßÙÙ! (Ó+�ÝHÙ˜D (Ô+Ø×ÑÔ8¸¸xÓHÔIÜ# HÖ-�Ü.¨uÓ5�Ø—‘Ø_Ð`bÐ_cÐcdÐeÐglôð —‘ÐIÈ"ÈÈQÐOÐQVÔWØ—‘ÐAÀ"ÀÀQÐGÈÖOñ .ð ×4Ñ4°TÔ:Ø×Ñ˜xÔ(ô �x“=Ð ùóuùò@&ùò
 Ns   ’AI8Ä;1I>Å1I>Å=JÆ
JrU  NrV  rW  r|   )	rN   rD   r-   r  r"   rÖ   rZ  rX  r-  )rú   rç  r   rC  rq  rD  r�   rc  r  rX  rS  r�  s   f&&fff    @@r/   rY  Ú$SessionSessionsMixin.delete_sessions<  s¿   ý€ ô  ¨+×*;Ð*;¸Ð*;Ó\Ñ*; 3¼zÈ#Ìs×?Sœ3ÔX[Ÿ3˜3Ñ*;Ñ\Ó]ˆ
ßÙØ!#ˆ÷;	!ò ;	!ðx ×#Ñ# CÓ(ˆßÜ   {¸8Ô^aÐblÓ^mÐqrÔ^rÈZÐXYÎ]Ðx|Ø.:õ<å<Ù˜dÔ#ÛˆCØ×&Ñ& |Ö9ñ àˆùòQ ]s   šC·CÁCz‹message_count = 0 AND ended_at IS NOT NULL AND archived = 0 AND NOT EXISTS (SELECT 1 FROM messages WHERE messages.session_id = sessions.id)c                ó    <€ V ^8„  d   QhRS[ /# rç   r&  )r.   ré   s   "€r/   r0   rê   Ÿ  s   ø€ ÷ eñ e¡cñ er1   c                óL   € V P                  RV P                   24      ^ ,          # )z^Count of empty, ended, non-archived sessions; ended_at guards a fresh session's first message.z$SELECT COUNT(*) FROM sessions WHERE )rŸ  Ú_EMPTY_SESSION_WHEREr(  s   &r/   Úcount_empty_sessionsÚ)SessionSessionsMixin.count_empty_sessionsŸ  s&   € à�~‰~Ð DÀT×E^ÑE^ÐD_Ð`ÓaÐbcÕdÐdr1   c                ó6   <€ V ^8„  d   QhRS[ S[,          RS[/# )r)   r   r+   )r   r   rs   )r.   ré   s   "€r/   r0   rê   £  s   ø€ ÷ ñ ±(¹4µ.ð ÉCñ r1   c                ó¼   a a€ . oVV 3R lpS P                  V4      pV'       d   \        S SRVR7       ^ RIHp V! S 4       S F  pS P	                  W4       K  	  V# )z‡Delete every empty, ended, non-archived session in one transaction, orphaning (not cascading)
children; transcript files are swept too.c                 óì  <€ V P                  R SP                   24      P                  4        Uu0 uF  qR,          kK  	  pp^ RIHp V! V \        V4      4      pV'       g   ^ # \        V4       Fa  p\        V4      pV P                  RV R2V4       V P                  RV R2V4       V P                  RV R2V4       SP                  V4       Kc  	  SP                  V 4       \        V4      # u upi )r¨   r“   )Úretire_prunabler·   rz   r¶   r¸   )rš   r†  r›   r¹   rŒ  r4  r   r~   r   r±  rÖ   )rœ   r*   rç  rŒ  r    r¡   rS  rú   s   &     €€r/   rc  Ú7SessionSessionsMixin.delete_empty_sessions.<locals>._do§  sþ   ø€ Ø04·±Ø0°×1JÑ1JÐ0KÐLó1ç‰h‹jð1ó ñ 1¨˜tŸ9˜9ñ 1ˆKð õ IÙ)¨$´°{Ó0CÓDˆKßÙÜ# KÖ0�Ü.¨uÓ5�Ø—‘ÐhÐikÐhlÐlmÐnÐpuÔvð —‘ÐIÈ"ÈÈQÐOÐQVÔWØ—‘ÐAÀ"ÀÀQÐGÈÔOØ×"Ñ" 5Ö)ñ 1ð ×4Ñ4°TÔ:Ü�{Ó#Ð#ùò!s   °C1rk  rl  rW  rm  )rú   r   rc  r  rX  r�   rS  s   f&    @r/   Údelete_empty_sessionsÚ*SessionSessionsMixin.delete_empty_sessions£  s[   ù€ ð "$ˆö	$ð$ ×#Ñ# CÓ(ˆßÜ   {¸=ÐWcÕdÝ<Ù˜dÔ#ÛˆCØ×&Ñ& |Ö9ñ àˆr1   c                óJ   <€ V ^8„  d   QhRS[ S[,          RS[R,          RS[/# )r)   Úolder_than_daysrh   Nr+   )r   r–  r-   rs   )r.   ré   s   "€r/   r0   rê   Â  s-   ø€ ÷ ñ Ù'©�ðÙ?BÀT½zðá	ñr1   c                ó¶   € VP                  RR4       RVR&   V P                  ! RRVRV/VB pV F  pV P                  VR,          R4       K  	  \        V4      # )	a<  Bulk soft-hide with prune_sessions' filter surface, via set_session_archived so each lineage
flips as a unit; idempotent. Returns matches. A lineage is matched through its TIP only: an
old compression ancestor never qualifies on its own age, or the fan-out would hide an open,
recently active continuation (#115489).r7  FTÚlineage_tips_onlyr‘  rh   r“   r|   )Ú
setdefaultÚlist_prune_candidatesrC  rÖ   )rú   r‘  rh   Úfiltersr8  r*   s   &&&,  r/   Úarchive_sessionsÚ%SessionSessionsMixin.archive_sessionsÂ  sf   € ð 	×Ñ˜: uÔ-Ø'+ˆÐ#Ñ$Ø×)Ò)Ñd¸/ÐdÐRXÐdÐ\cÑdˆÛˆCØ×%Ñ% c¨$¥i°Ö6ñ ä�4‹yÐr1   c                ó6   <€ V ^8„  d   Qh/ S[ S[,          ;R&   # )r)   Ú_session_compact_cols_sqlrè   )r.   ré   s   "€r/   r0   rê   O  s   ø‡ ‚ ñR  (©�}Ñ3òS r1   r|   )NNNNNNNNNNNNNNN)ÚunknownNr¨  )Úsession_reset)NNF)NNrÚ   )z1UPDATE sessions SET model_config = ? WHERE id = ?N>   r
  Úsystem_prompt_hashÚgit_metadata_generation)NNNNé   r³   Fr³   TFFFNNFFNFFN)NrŸ  r³   NNF)
NNNr³   FFFNFN)é   )NNNFF)NFNF)[Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__rý   Ústaticmethodr  r  r#  r'  r.  r;  Ú_ORPHAN_ADOPTION_MAX_GAP_Sr   Ú"_NON_CONTINUATION_CHILD_FILTER_SQLrE  rA  rT  r\  re  rl  r}  rˆ  r�  r›  r¤  rª  r³  r½  rÎ  rË  rÓ  rä  rê  rî  rô  rÿ  r  r
  rè  r  r  r  r#  r)  r3  r1  rC  rG  rb  r   rP  rS  r]  ra  rh  rp  Ú	frozensetÚ_SESSION_COMPACT_EXCLUDEDrš  r�  r˜  r¾  ÚclassmethodrÅ  rã  rê  rñ  rõ  rú  r   r  r  r  r  r  r-  r7  r>  r\  rc  rn  rY  r†  r‡  rŽ  r—  Ú__annotate_func__Ú__static_attributes__Ú__classdictcell__)ré   s   @r/   rå   rå   O  su  ø‡ € ÙY÷ð ð" ÷	Aó ð	A÷-Jò -J÷^ð ÷
ò ÷
	
ò 	
ð"ØIMð"à!%÷"ò "ðD "'Ðñ *EÀYÓ)OÐ&÷ð ÷ð ð ÷ó ð÷Sð S÷ !ð !÷2ò ÷.(ò (÷Bò ÷*ð ñ
Ø[_ð
à37÷
ó 
÷8
ð 
÷6
ò 
÷!ð !÷
!ð 
!ñ
à"&ð
àBF÷
ó 
÷<!ò !ð6ØQW÷6ò 6÷&:ð :÷Rò R÷
Uð U÷<ð <ð

Ø9=ð
ØY]ð
à26ð
àVZð
ð  ÷
ò 
÷(Qð Qð ÷dó ðd÷
<ð 
<÷*ð *÷*ð *÷,	?ð 	?÷ð ÷&ð ð&Ø13÷ò ðØ@B÷ò ð"ÐVZ÷ ò ÷ dð dð ÷&ó ð&ð. 7Ð÷%ð %÷Nð ÷"Kð K÷bò bð
 ÷:ó ð:ñ !*ÚJó!Ðð 04Ðà÷ ]ó ð ]÷D*ð *ðXnð ðnð -1ð	nð
 "%ðnð '+ðnð %)÷nò nð` ÷ó ð÷Cò C÷Jð ÷4ò ÷*Kò K÷[ð [÷5ð 5÷ ò ÷:mò m÷$Pò PðLØ*/ðLØGLðLà!&÷Lò L÷,cð cð ÷ó ð÷*Wð W÷#ð #÷JEò E÷Nð ÷$*ò *÷XXò Xð~	Jð ÷
eð e÷ò ÷>ò ÷g3 ƒ r1   rå   )rà   )r4   )ÚkanbanrÅ   Úoneshot>   r½   Úagent_errorÚcontent_filter)rj   r  r  r  r  r5   rÑ   r  r3   r  r  z,
                           )r5   r3   ro  z/
                                              )Ú_delegate_fromrù  )r  rj   r  r  r  r  r  r  )Tr¥  r&  rE   Úloggingrô   r   rB  Úpathlibr   Útypingr   r   r   r   r   r   r	   r
   Úagent.session_activityr   r   r   Úhermes_state_errorsr   Úhermes_state_commonr   r   r   r   r   r   r   r   r   r   r   r   r   r   r   rR   r   r   r    r~   r!   r"   r#   Úhermes_state_compressionr$   r%   Ú	getLoggerrø   r8   r@   ÚobjectrÔ  r9  rI   rU   r]   rf   r‡   r£   r°   rº   ÚINTERNAL_LISTING_SOURCESrË   rÊ   rÉ   ÚSESSION_STATUS_EMPTYr©  rÈ   rÍ   Ú_SAME_KEY_NAMESPACE_SQLre   r.   Ú_UPSERT_KEEP_EXISTING_SQLrØ   Ú_INHERIT_SEPr  Ú_FORK_EDGE_EXCLUSION_SQLr  rå   r|   r1   r/   Ú<module>rÃ     s€  ðñ:ó Û Û Û 	Û Û Ý ß N× NÓ N÷ñ õ =÷÷ ÷ ÷ ÷ õ ÷ Dð 
×	Ò	˜>Ó	*€õ^÷6ñ #›HÐ ðÐ õ66õõ÷Eð
(Ø %ð(Ø<@ð(Ø^bð(à"ð(àHLð(àgkð(ð ð(ð 7<ð(ð W\ð(ð $ð	(ð TX÷	(õV!õH6õ<
ð$ 9Ð à$Ð Ø*Ð ØÐ ØÐ ñ "Ò"LÓMÐ õ#ð$[ð ð "ŸJ™Jñ 'ñ[ó'ó ð
 
õ
ð 9Ø×ÒÐ$=Ô>õ?ð(õ(õÐ ÷ ð  €à2Ø×Ñð Ù	NÑ'MÓ	Nðá˜¨¸UÐCZÐB[Ð[\Ð;]Õ)]Ó^ñó õð
 NõNð ð Ÿ7™7ñ #ñ 7ó#ó Ð ð 3Ø×Ññ ñ 6ó ó õð
õ
ð õð ÷ ó r1   