
    *j6`                    	   U d Z ddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlmZ ddlmZ ddlmZ ddlmZmZmZmZmZmZmZmZmZmZmZmZmZ ddl m!Z! ddl"m#Z# ddl$m%Z%m&Z&m'Z'  ej(        e)          Z*d	Z+d
Z, e	j-                    Z.i Z/dZ0 e	j1                    Z2 e	j-                    Z3da4ddZ5ddZ6ddZ7dde8de9fdZ:ddZ;de8ddfdZ<d Z=de>ddfdZ?ddde@dz  de>dz  fdZAd ZBd ZCd ZDd ZEd ZFd ZGdd!ZHd" ZId# ZJ G d$ d%          ZKdefd&ZLd'ZMd(ZNd)ZOd*ZPd+d,d-d.d/ZQddd0d1e8dz  de8fd2ZRd3e9d4e9d5e8de8fd6ZSd7d7ddd8d3e9d4e9d1e8dz  de>fd9ZTdd;e@de>fd<ZUde8fd=ZVd>e>deWfd?ZXdeWeYe9f         fd@ZZdAe8de@dz  fdBZ[ddCdDe8dEe8d1e8dz  de9fdFZ\d1e8dz  de9fdGZ]d1e8dz  de9fdHZ^d7dId1e8dz  dJe9de9fdKZ_dLZ`dMZai Zbe>e8e	j-        f         ecdN<    e	j-                    ZddOe8de	j-        fdPZeddQd3e9d1e8dz  de>fdRZfde9fdSZgdTe>ddfdUZhdVe@ddfdWZide9fdXZjd7dYdZe9de9fd[Zk	 dd\d\d]de9fd^Zld_Zmde9fd`Znde8fdaZodb Zpde8fdcZqde9fddZrde9fdeZsddfZtddgZuddhZvdie>de9fdjZwdie>de@fdkZxdie>dle>e8e>f         de8fdmZydie>de9fdnZzdoeYe>         deYe>         fdpZ{doeYe>         ddfdqZ|defdrZ}dse8dz  de9fdtZ~doeYe>         ddfduZdDe8ddfdvZddwZddye8fdzZ e	j-                    Zd7ad{eYde9fd|Zd\d}d~e9deYfdZddZdZ e	j-                    Zde8fdZdde8de8de9fdZ	 	 	 	 	 dde8dse8de8fdZdZdZdZdZdZdZdedz  fdZdede8fdZd Zde8fdZeddede@deWeYe>         e8dz  edz  edz  f         fdZdeeddee8z  dz  de@de@fdZdeYe>         de8dz  de8fdZdeeddee8z  dz  de@de@deYfdZddee8z  dz  deYfdZddZdoeYdeYfdZdefdZde8dz  fdZd ZdefdZd ZdededeYfdZdeYfdZd Zd7ddde9deYfdZddde>fdZd Zd Zde>fdZde>de8fdZde8de8fdZde>fdZde>fdZdeeW         de>fdZddeWdeeW         de>dz  fdZdeWdeeW         de9fdZdeYdeYdeYfdZddÜdeYdeYdeYfdńZd7ddƜde9deYdz  deYfdȄZdeYfdɄZddOe8dedz  de@fd˄ZdZde9fd̈́ZdS )z;Hermes Web UI -- Session model and in-memory session store.    N)closing)Path)SESSION_DIRSESSION_INDEX_FILESESSIONSSESSIONS_MAXLOCKSTREAMSSTREAMS_LOCKDEFAULT_WORKSPACEDEFAULT_MODELPROJECTS_FILEHOMEget_effective_default_model_get_session_agent_lock)get_last_workspace)prompt_cache_hit_percent)_is_continuation_session"read_importable_agent_session_rowsread_session_lineage_metadata         @i  returnc                  V   t          j                     t          z
  } 	 t          j        d          D ]f}	 |                                j        | k     r6|                    d           t                              d|j	                   W# t          $ r Y cw xY wdS # t          $ r Y dS w xY w)u8  Best-effort removal of stale ``*.tmp.*`` files from SESSION_DIR.

    Only files whose mtime is older than ``_STALE_TMP_AGE_SECONDS`` are
    removed so that in-flight writes from a long-running sibling process
    are not disturbed.  Errors are logged and swallowed — this must never
    prevent startup.
    z*.tmp.*T
missing_okzCleaned up stale tmp file: %sN)time_STALE_TMP_AGE_SECONDSr   globstatst_mtimeunlinkloggerdebugnameOSError	Exception)cutoffps     -/Users/bertmahoney/hermes-webui/api/models.py_cleanup_stale_tmp_filesr+   :   s     Y[[11F	!),, 	 	A6688$v--HHH---LL!@!&III   	 	    s5   B ABB 
BB BB 
B('B(c                      	 t          d            d S # t          $ r  t                              dd           Y d S w xY w)Nupdatesz'Background session-index rebuild failedTexc_info)_write_session_indexr'   r#   r$        r*   !_rebuild_session_index_backgroundr4   O   s^    OT****** O O O>NNNNNNOs    &>>c                  N   t           5  t          j                    r	 ddd           dS t          't                                          r	 ddd           dS t          j        t          dd          at                                           ddd           dS # 1 swxY w Y   dS )z@Start one background full-index rebuild if the index is missing.Nzsession-index-rebuildT)targetr%   daemon)	_SESSION_INDEX_REBUILD_LOCKr   exists_SESSION_INDEX_REBUILD_THREADis_alive	threadingThreadr4   startr2   r3   r*   #_start_session_index_rebuild_threadr?   V   s*    
% . .$&& 	. . . . . . . . *5-6688 6 . . . . . . . . )2(84()
 )
 )
%
 	&++---. . . . . . . . . . . . . . . . . .s   B!B5BB!B
session_idc                     | sdS |?t           5  t          t          j                              }ddd           n# 1 swxY w Y   | |v rdS t          |  dz  }|                                S )a<  Return True if an index entry still has backing state.

    A session can legitimately exist either as a persisted JSON file or as an
    in-memory Session object that has not been flushed yet.  This helper is used
    to prune stale `_index.json` rows left behind after session-id rotation or
    file removal.
    FNT.json)r	   setr   keysr   r9   )r@   in_memory_idsr)   s      r*   _index_entry_existsrF   i   s      u 	1 	100M	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1]""t****A88::s   !;??c                 
   t          j        dt          j                     dt	          j                    j                   }t          5  | t          j                    st                       i }t          j        d          D ]}|j                            d          r	 t                              |j                  }|ru|                                }|                    d          }|rJ|                    |          }|.|                    dd          |                    dd          k    r|||<   # t&          $ r t(                              d	|           Y w xY wt-          |                                          }t0          5  t3          |                                          }	t7          j                    D ]2}|j        |	vr'|                    |                                           3|                    d
 d           t?          j         |dd          }
ddd           n# 1 swxY w Y   	 tC          |dd          5 }|"                    |
           |#                                 t          j$        |%                                           ddd           n# 1 swxY w Y   t          j&        |t                      n7# t&          $ r* 	 |'                    d           n# t&          $ r Y nw xY w w xY w	 ddd           dS d}	 t0          5  t?          j(        t          j)        d                    }t3          t7          j                              d t          j        d          D             fd|D             }d | D             }d |D             }	|*                                D ]\  }}||	vr|                    |           tW          |          D ])\  }}|                    d          }||v r||         ||<   *|                    d d           t?          j         |dd          }
ddd           n# 1 swxY w Y   	 tC          |dd          5 }|"                    |
           |#                                 t          j$        |%                                           ddd           n# 1 swxY w Y   t          j&        |t                      n7# t&          $ r* 	 |'                    d           n# t&          $ r Y nw xY w w xY wn# t&          $ r d}Y nw xY wddd           n# 1 swxY w Y   |rtY          d           dS dS )u  Update the session index file.

    When *updates* is provided (a list of Session objects whose compact
    entries should be refreshed), this does a targeted in-place update of
    the existing index — O(1) for single-session changes.  When *updates*
    is None, a full rebuild is performed (used on startup / first call).

    LOCK protects in-memory state snapshots and payload construction only;
    disk I/O (write/flush/fsync/replace) always runs outside LOCK.
    .tmp..N*.json_r@   message_countr   Failed to load session from %sc                 .    |                      dd          S N
updated_atr   getss    r*   <lambda>z&_write_session_index.<locals>.<lambda>   s    155q+A+A r3   TkeyreverseF   ensure_asciiindentwutf-8encodingr   c                 P    h | ]#}|j                             d           |j        $S rK   r%   
startswithstem.0r)   s     r*   	<setcomp>z'_write_session_index.<locals>.<setcomp>   s@       6,,S11F  r3   c                 p    g | ]2}|                     d           v s|                     d           v 0|3S r@   rQ   )rg   erE   on_disk_idss     r*   
<listcomp>z(_write_session_index.<locals>.<listcomp>   sQ       l++}<<l@S@SWb@b@b @b@b@br3   c                 B    i | ]}|j         |                                S r2   )r@   compactrg   rT   s     r*   
<dictcomp>z(_write_session_index.<locals>.<dictcomp>   s$    JJJQq|QYY[[JJJr3   c                 8    h | ]}|                     d           S rj   rQ   )rg   rk   s     r*   rh   z'_write_session_index.<locals>.<setcomp>   s$    FFFl 3 3FFFr3   c                 .    |                      dd          S rO   rQ   rS   s    r*   rU   z&_write_session_index.<locals>.<lambda>   s    AEE,,B,B r3   r-   )-r   with_suffixosgetpidr<   current_threadident_INDEX_WRITE_LOCKr9   r+   r   r   r%   rd   Sessionloadre   ro   rR   r'   r#   r$   listvaluesr	   rC   rD   r   r@   appendsortjsondumpsopenwriteflushfsyncfilenoreplacer"   loads	read_textitems	enumerater1   )r.   _tmp	entry_mapr)   rT   csidexistingentriesexisting_ids_payloadf	_fallbackupdated_mapentryirk   rE   rl   s                    @@r*   r1   r1   |   sl    )*b")++*b*b	@X@Z@Z@`*b*bccD	 b b?"4";"="=?$&&&)+I %h// F F6$$S)) FQV,,A 3IIKKeeL11 3 (1}}S'9'9H'/ !oq 9 9HLLZ[<\<\ \ \12	#  F F FLL!A1EEEEEF9++--..G M M"9>>#3#344!** 4 4A|<77qyy{{333!A!A4PPP:gE!LLLM M M M M M M M M M M M M M M$g666 )!GGH%%%GGIIIHQXXZZ((() ) ) ) ) ) ) ) ) ) ) ) ) ) ) 
4!34444   KK4K0000    D _b b b b b b b bf 	.	 N N:&8&BG&T&T&TUU #HMOO 4 4 (-h77      '   KJ'JJJFFXFFF"-"3"3"5"5 / /JC,.. ...%h// 7 7DAq%%--Ck))&1#&6"B"BDQQQ:hU1MMM=N N N N N N N N N N N N N N N@$g666 )!GGH%%%GGIIIHQXXZZ((() ) ) ) ) ) ) ) ) ) ) ) ) ) ) 
4!34444   KK4K0000    D 5  	 	 	III	Cb b b b b b b b b b b b b b bH  +T******+ +sn  AU
$BD;:U
;%E# U
"E##+U
BH1%U
1H5	5U
8H5	9U
=KAJ+K+J/	/K2J/	3KU

LK32L3
L =L?L  LU
U
T, D(QT,Q	T,Q	T, S42ASS4S	S4S	S43T,4
T(?TT(
T# T("T##T((T,+U
,T;8U
:T;;U

UUc                    t          | pd          rt          j                    sdS t          j        dt	          j                     dt          j                    j                   }d}t          5  	 t          5  t          j        t          j        d                    }t          |t                    st!          d          fd	|D             }t#          |          t#          |          k    r	 ddd           ddd           dS t          j        |dd
          }ddd           n# 1 swxY w Y   	 t'          |dd          5 }|                    |           |                                 t	          j        |                                           ddd           n# 1 swxY w Y   t	          j        |t                     n7# t2          $ r* 	 |                    d           n# t2          $ r Y nw xY w w xY wn# t2          $ r d}Y nw xY wddd           n# 1 swxY w Y   |rt7          d           dS dS )zCRemove one session row from the persisted sidebar index if present. NrH   rI   Fr^   r_   zsession index must be a listc                 F    g | ]}|                     d           k    |S rj   rQ   )rg   rk   r   s     r*   rm   z,prune_session_from_index.<locals>.<listcomp>   s/    LLL|1D1D1K1K!1K1K1Kr3   rY   rZ   r]   Tr   r-   )strr   r9   rt   ru   rv   r<   rw   rx   ry   r	   r   r   r   
isinstancer|   
ValueErrorlenr   r   r   r   r   r   r   r'   r"   r1   )r@   r   r   r   prunedr   r   r   s          @r*   prune_session_from_indexr      sy   
jB

C (/11 )*b")++*b*b	@X@Z@Z@`*b*bccDI	  	 L L:&8&BG&T&T&TUU!(D11 E$%CDDDLLLLXLLLv;;#h--//L L L L L L         :f5KKKL L L L L L L L L L L L L L L$g666 )!GGH%%%GGIIIHQXXZZ((() ) ) ) ) ) ) ) ) ) ) ) ) ) ) 
4!34444   KK4K0000    D 5  	 	 	III	/              4  +T******+ +s   4H)6H=A;D38HD3'H3D7	7H:D7	;H?GAF-!G-F1	1G4F1	5GH
HG54H5
H?HHHH
H)HH)HH))H-0H-c                  .   t           5  t          t          j                              } d d d            n# 1 swxY w Y   t          j        5  |                     t          j                                                   d d d            n# 1 swxY w Y   | S N)r   rC   r
   rD   _cfgACTIVE_RUNS_LOCKupdateACTIVE_RUNS)
active_idss    r*   _active_stream_idsr     s    	 ) )((
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) 
	 3 3$*//112223 3 3 3 3 3 3 3 3 3 3 3 3 3 3s   !5992B

BB	recoveredc                    t          | dd           }t          |t                    r|sd S d                    t	          |                    d          pd                                                    }|rt          |dd                    D ]}t          |t                    r|                    d          dk    r1d                    t	          |                    d          pd                                                    }||k    r d S d |	                                D             }|
                    |           d S )	Ncontext_messages contentr   iroleuserc                 &    i | ]\  }}|d k    ||S 	timestampr2   )rg   kvs      r*   rq   z5_append_recovered_turn_to_context.<locals>.<dictcomp>2  s(    LLLda1;K;KQ;K;K;Kr3   )getattrr   r|   joinr   rR   splitreverseddictr   r~   )sessionr   r   recovered_textr   existing_textcontext_entrys          r*   !_append_recovered_turn_to_contextr   &  sC   w(:DAA&-- 5E XXc)--	":":"@bAAGGIIJJN  !1"##!677 	 	Hh-- f1E1E1O1OHHSi)@)@)FB%G%G%M%M%O%OPPM.. /LLioo&7&7LLLMM*****r3   r   r   c                   t          | j        pd          }|sd S t          t          j                              }t	          |t          t
          f          r|dk    rt          |          }d| j        |dd}| j        rt          | j                  |d<   | j        	                    |           t          | |           |S )Nr   r   r   T)r   r   r   
_recoveredattachments)r   pending_user_messageintr   r   floatpending_attachmentsr|   messagesr~   r   )r   r   pending_textrecovered_tsr   s        r*   _append_recovered_pending_turnr   6  s    w39r::L tty{{##L)c5\** &y1}}9~~/!	 I " E#'(C#D#D	- I&&&%gy999r3   c                 (    t          | o| |v           S r   )bool)active_stream_idactive_stream_idss     r*   _is_streaming_sessionr   J  s     J%59J%JKKKr3   c                     t          | t                    r,|                     d          p|                     d          pdS t          t	          | dd                     pt	          | dd          pdS )Nlast_message_atrP   r   r   )r   r   rR   _last_message_timestampr   r   s    r*   _session_sort_timestampr   M  sr    '4   P{{,--O\1J1JOaO"77J#E#EFFp'RY[gijJkJkpoppr3   c                     t          | t                    sd S |                     d          p|                     d          }	 |t          |          nd S # t          t
          f$ r Y d S w xY w)N_tsr   r   r   rR   r   	TypeErrorr   )messageraws     r*   _message_timestampr   S  s{    gt$$ t
++e


8K 8 8C _uSzzz$6z"   tts   A A+*A+c                     t          | t                    sd S t          |           D ]F}t          |t                    r|                    d          dk    r1t          |          }|r|c S Gd S )Nr   tool)r   r|   r   r   rR   r   )r   r   tss      r*   r   r   ]  s    h%% tH%%  gt$$ 	V)<)<)F)F(( 	III	4r3   c                     t          | t                    sdS t          |                     dd                                                                                    S )Nr   r   )r   r   r   rR   striplower)r   s    r*   _message_roler   i  sM    gt$$ rw{{62&&''--//55777r3   c                 "   d}d}t          |           }||k     r| |         }|dk    r|}|dz  }d}g }||k     rR| |         }	|r|                    |	           d}n%|	dk    rd}n|	dk    rn |                    |	           |dz  }||k     R||k    rdS |dk    rWd                    |          |k    r>|dz   }
|
|k     r| |
         d	v r|
dz  }
|
|k     r
| |
         d	v |
|k     r| |
         d
k    r|S n|dv r|dz  }n	|dv r|dz  }|dz  }||k     dS )zBReturn the byte offset of a top-level JSON object key, if present.r   "   F\TNr   z 	
:z{[z}])r   r~   r   )textrW   depthr   nchr>   escapedcharsr   js              r*   _find_top_level_json_keyr   o  s   E	AD		A
a%%!W99EFAGEa%%G $LLOOO#GG$YY"GG#XXLLOOOQ a%% Avvtzzbggenn33E!eeQ9 4 4FA !eeQ9 4 4q55T!W^^ L4ZZQJEE4ZZQJE	Q? a%%@ 4r3      c                    d}t          | dd          5 }t          |                    d                    |k     r|                    d          }|s	 ddd           dS ||z  }t	          |d          }|c|d|                                         }|                    d          r|dd	                                         }| d
cddd           S 	 ddd           n# 1 swxY w Y   dS )zCRead only the metadata portion before the top-level messages array.r   rr^   r_   i   Nr   ,z
})r   r   encodereadr   rstripendswith)pathmax_prefix_bytesbufr   chunkmessages_posprefixs          r*   _read_metadata_json_prefixr     su   
C	dC'	*	*	* #a#**W%%&&)999FF4LLE 	# # # # # # # #
 5LC3CDDL#,'..00Fs## .++--???# # # # # # # #9# # # # # # # # # # # # # # # 4s   ?C$!A)C$$C(+C(c                    	 t          j        t          j        d                    }n# t          $ r Y dS w xY wt          |t                    sdS |D ]}|                    d          | k    r|                    d          }t          |t                    r
|dk    r|c S 	 t          |          }n# t          t          f$ r Y  dS w xY w|dk    r|ndc S dS )zGReturn the indexed message count without loading the full session file.r^   r_   Nr@   rL   r   )r   r   r   r   r'   r   r|   rR   r   r   r   )r@   r   r   counts       r*   _lookup_index_message_countr    s   */97KKKLL   ttgt$$ t 
- 
-99\""j00		/**eS!! 	eqjjLLL	JJEE:& 	 	 	444	

uu,,,4s   '* 
88%B55C
Cc                     t          | t                    r| dk    r| S 	 t          |           }n# t          t          f$ r Y d S w xY w|dk    r|nd S Nr   )r   r   r   r   )valueparseds     r*   _parse_nonnegative_intr    sl    % %1**Uz"   ttq[[66d*s   / AAc            /       .   e Zd Zdd ee          edddddddddddddddddddddddddddddddddddddddddddf/dedededed	ed
ededededededededefdZe	d             Z
ddZddededdfdZed             Zed             Zd defdZdS )!rz   NUntitledFr   r@   titlepinnedarchived
project_idinput_tokensoutput_tokenscache_read_tokenscache_write_tokensr   r   pre_compression_snapshotllm_title_generatedparent_session_idc0                    |pt          j                    j        d d         | _        || _        t          t          |                                                                                    | _	        || _
        |r3t          |                                                                          nd | _        |pg | _        |	pg | _        |pt!          j                    | _        |pt!          j                    | _        t'          |
          | _        t'          |          | _        |pd | _        || _        |pd| _        |pd| _        || _        |pd| _        |pd| _        || _        || _        || _        |pg | _         || _!        tE          |tF                    r|ng | _$        || _%        || _&        || _'        t'          |          | _(        || _)        || _*        || _+        tE          | tX                    r| ni | _-        tE          |!tX                    r|!ni | _.        |"| _/        |#| _0        |$| _1        |%| _2        tE          |&tX                    r|&nd | _3        tE          |'tF                    r|'ng | _4        t'          |(          | _5        |)| _6        |*r@t          t          |*                                                                                    nd | _7        |+rt          |+          nd | _8        |,r@t          t          |,                                                                                    nd | _9        |-| _:        t'          |0;                    dd                    | _<        |0;                    d          | _=        |0;                    d          | _>        |0;                    d          | _?        |0;                    d          | _@        t'          |0;                    d	d                    | _A        |.| _B        tE          |/tX                    r|/ni | _C        |0;                    d
          }1d }2|1*	 t          |1          }2n# t          t          f$ r d }2Y nw xY w|2|2dk    r|2nd | _G        d S )N   r   is_cli_sessionF
source_tag
raw_sourcesession_sourcesource_label	read_onlyrL   )Huuiduuid4hexr@   r
  r   r   
expanduserresolve	workspacemodelr   r   model_providerr   
tool_callsr   
created_atrP   r   r  r  r  profiler  r  estimated_costr  r  personalityr   r   r   pending_started_atr   r|   r   compression_anchor_visible_idxcompression_anchor_message_keycompression_anchor_summaryr  context_enginecompression_anchor_enginecompression_anchor_moder   compression_anchor_detailscontext_engine_statecontext_lengththreshold_tokenslast_prompt_tokenstruncation_watermarkgateway_routinggateway_routing_historyr  r  worktree_pathworktree_branchworktree_repo_rootworktree_created_atrR   r  r  r  r  r  r  enabled_toolsetscomposer_draftr   r   r   _metadata_message_count)3selfr@   r
  r"  r#  r$  r   r&  rP   r%  r  r  r  r'  r  r  r(  r  r  r)  r   r   r   r*  r   r+  r,  r-  r  r.  r/  r0  r1  r2  r3  r4  r5  r6  r7  r8  r  r  r9  r:  r;  r<  r=  r>  kwargsraw_message_countparsed_message_counts3                                                      r*   __init__zSession.__init__  sK   H %=
(8"(=
T)__7799AACCDD
ES]c.117799??AAAY] B$*$3	$3	6llX$,(-A*/a,!2!7a"4"9& 0$8!#6#<" "44>?OQU4V4V ^ 0 0\^.L+.L+*D'(,-E(F(F%,)B&'>$HRSmosHtHt*|*D*Dz|'<FG[]a<b<b$j$8$8hj!, 0"4$8!2<_d2S2S]Y]BLMdfjBkBk's'>'>qs$#'(;#<#< !2P]gSm!4!4!?!?!A!A!I!I!K!KLLLcg7FPs?333DZl"v#d+=&>&>&I&I&K&K&S&S&U&U"V"V"Vrv#6 "6::.>#F#FGG **\22 **\22$jj)9::"JJ~66fjje<<== 00:>40P0PXnnVX"JJ77#(,'*+<'='=$$z* , , ,'+$$$,?S?_dx|}d}d}';';  DH$$$s   8Q QQc                 &    t           | j         dz  S )NrB   )r   r@   )r@  s    r*   r   zSession.path-  s    66666r3   r   c                     t          d| j        i          }|d S d }| j        pg D ]'}t          |          }|||nt          ||          }(|||k    rd | _        d S d S d S )Nr   )_message_timestamp_as_floatr6  r   max)r@  	watermarkmax_message_timestampmsgr   s        r*   !_maybe_clear_truncation_watermarkz)Session._maybe_clear_truncation_watermark1  s    /d>W0XYY	F $=&B 	~ 	~C3C88I$5J5R		X[\qs|X}X}% ,1F1R1R(,D%%% -,1R1Rr3   Ttouch_updated_at
skip_indexc                     t           dd          rt          d j        d          |rt          j                     _                                          g d fdD             }t           j        pg           |d<    j        |d<    j        |d	<   fd
 j	        
                                D             }t          j        i ||dd          }	  j                                        r j                            d          }	 t          j        |          }t          |                    d          pg           }n# t          j        t&          f$ r d}Y nw xY wt           j        pg           }	||	k    r$ j                            d          }
	 |
                    dt+          j                     dt/          j                    j                   }t5          |dd          5 }|                    |           |                                 t+          j        |                                           d d d            n# 1 swxY w Y   t+          j        ||
           n8# t@          $ r+ 	 |!                    d           n# tD          $ r Y nw xY wY nw xY wn# t@          $ r Y nw xY w j                            dt+          j                     dt/          j                    j                   }	 t5          |dd          5 }|                    |           |                                 t+          j        |                                           d d d            n# 1 swxY w Y   t+          j        | j                   n7# tD          $ r* 	 |!                    d           n# tD          $ r Y nw xY w w xY w|stG           g           d S d S )N_loaded_metadata_onlyFz'Refusing to save metadata-only session zx: would atomically overwrite on-disk messages with []. Reload with metadata_only=False before mutating state. See #1558.)2r@   r
  r"  r#  r$  r&  rP   r  r  r  r'  r  r  r(  r  r  r)  r   r   r   r*  r+  r,  r-  r  r.  r/  r0  r1  r2  r3  r4  r5  r6  r7  r8  r  r  r9  r:  r;  r<  r  r  r  r  r  r  r=  r>  c                 4    i | ]}|t          |d           S r   )r   )rg   r   r@  s     r*   rq   z Session.save.<locals>.<dictcomp>h  s'    CCCa74D))CCCr3   rL   r   r%  c                 V    i | ]%\  }}|v	|d v|                     d          "||&S ))r   r%  rK   )rd   )rg   r   r   METADATA_FIELDSs      r*   rq   z Session.save.<locals>.<dictcomp>m  sS     , , ,$!Q_,,:T1T1Tc** 2U A1T1T1Tr3   rY   rZ   r^   r_   r   z	.json.bakz	.bak.tmp.rI   r]   Tr   rH   r-   )$r   RuntimeErrorr@   r   rP   rL  r   r   r%  __dict__r   r   r   r   r9   r   r   rR   JSONDecodeErrorr   rt   ru   rv   r<   rw   rx   r   r   r   r   r   r   r&   r"   r'   r1   )r@  rM  rN  metaextrapayloadr   r   existing_msg_countincoming_msg_countbak_pathbak_tmpbftmpr   rS  s   `              @r*   savezSession.save=  s    40%88 	$/       	*"ikkDO..000
 
 
& DCCC?CCC #DM$7R 8 8_=Z!_\, , , ,$-"5"5"7"7 , , , *...U1MMM#	y!!  ! $	 3 3W 3 E E,#z-88H),X\\*-E-E-K)L)L&&,j9 , , ,)+&&&,%()<"%=%="%(:::#y44[AAH!"*"6"6X	XXi6N6P6P6VXX# # "'3AAA 2RHH]333HHJJJHRYY[[1112 2 2 2 2 2 2 2 2 2 2 2 2 2 2 
7H5555" ! ! !!#NNdN;;;;( ! ! ! D!	!  	 	 	D	 i##$\BIKK$\$\):R:T:T:Z$\$\]]	c3111 %Q   			$$$% % % % % % % % % % % % % % % JsDI&&&& 	 	 	

d
++++   	  	1 $000000	1 	1s   5J) 8E	 J) 	E$!J) #E$$:J) AI0 3AII0 II0 II0 /J) 0
J%;JJ%
JJ%JJ%"J) $J%%J) )
J65J6N AM/#N /M33N 6M37N 
O	 N76O	7
OO	OO	c                    |rt          d |D                       sd S t          | dz  }|                                sd S t          j        |                    d                    }t          |                    d                    \  |d<   } | di |}|rF	 |                    dd           n-# t          $ r  t                              d	|d
           Y nw xY w|S )Nc              3      K   | ]}|d v V  	dS %0123456789abcdefghijklmnopqrstuvwxyz_Nr2   rg   r   s     r*   	<genexpr>zSession.load.<locals>.<genexpr>  (      XXqa#JJXXXXXXr3   rB   r^   r_   r   FT)rM  rN  z5Failed to persist collapsed duplicate partials for %sr/   r2   )allr   r9   r   r   r   %_collapse_adjacent_duplicate_partialsrR   r`  r'   r#   r$   )clsr   r)   data_collapsed_partialsr   s         r*   r{   zSession.load  s     	#XXTWXXXXX 	4S---'xxzz 	4z!++w+77880UVZV^V^_iVjVj0k0k-Z-#++++ 	jj eEEEE j j jTVYdhiiiiijs   B4 4'CCc                    |rt          d |D                       sdS t          | dz  }|                                sdS 	 t          |          }|s|                     |          S t          j        |          }h d}|                    |                                          s|                     |          S g |d<   g |d<    | d
i |}t          |          }t          |                    d                    }d ||fD             }	|	rt          |	          nd|_        d	|_        |S # t          $ r |                     |          cY S w xY w)a  Load only the compact metadata fields, skipping the messages array.

        Session JSON files have metadata fields (session_id, title, model, etc.)
        at the top level, before the large messages array. Read only up to the
        top-level "messages" field and synthesize a small metadata-only object.
        Falls back to load() for legacy or unexpected file layouts.
        c              3      K   | ]}|d v V  	dS rc  r2   re  s     r*   rf  z-Session.load_metadata_only.<locals>.<genexpr>  rg  r3   NrB   >   r
  r&  r@   rP   r   r%  rL   c                     g | ]}||S r   r2   )rg   r  s     r*   rm   z.Session.load_metadata_only.<locals>.<listcomp>  s(       $ $$$r3   Tr2   )rh  r   r9   r   r{   r   r   issubsetrD   r  r  rR   rH  r?  rP  r'   )
rj  r   r)   r   r  neededr   index_message_countsidecar_message_countknown_countss
             r*   load_metadata_onlyzSession.load_metadata_only  s     	#XXTWXXXXX 	4S---'xxzz 	4	!/22F %xx}}$Z''FHHHF??6;;==11 %xx}}$!#F:#%F< cmmFmmG"=c"B"B$:6::o;V;V$W$W! $79N#O  L DP.Yc,.?.?.?UYG+ -1G)N 	! 	! 	!88C==   	!s    %D. (AD. <A1D. .EEc                    ||nt                      }t          | j                  }| j        | j        nt	          | j                  }|rt          |d          }t          | j                  p| j        }|r| j	        r| j	        }i d| j
        d| j        d| j        d| j        d| j        d|d| j        d	| j        d
|d| j        d| j        d| j        d| j        d| j        d| j        d| j        d| j        i d| j        dt3          | j        | j                  d| j        d| j        d| j        d| j        d| j        d| j        d| j         d| j!        d| j"        d| j#        d| j$        d | j%        d!| j&        d"| j'        d#| j(        | j)        r	d$| j)        ini | j*        r| j*        | j+        | j,        | j-        d%ni t]          | j        t^                    rta          d& | j        D                       nd'| j1        | j        || j2        | j3        | j4        | j5        | j6        | j7        | j8        t]          | j9        tt                    r| j9        ni |rtw          | j1        |          nd(d)S )*Nr   r@   r
  r"  r#  r$  rL   r&  rP   r   r  r  r  r'  r  r  r(  r  r  cache_hit_percentr)  r+  r,  r-  r  r.  r/  r0  r1  r2  r3  r4  r5  r7  r8  r  )r9  r:  r;  r<  c              3   @   K   | ]}t          |          d k    dV  dS )r   r   N)r   )rg   r   s     r*   rf  z"Session.compact.<locals>.<genexpr>0  s=       & &-2H2HF2R2R2R2R2R2R& &r3   r   F)user_message_countr   r   has_pending_user_messager  r  r  r  r  r  r=  r>  is_streaming)<rC   r   r   r?  r   r   rH  r   rP   r*  r@   r
  r"  r#  r$  r&  r  r  r  r'  r  r  r(  r  r  r   r)  r+  r,  r-  r  r.  r/  r0  r1  r2  r3  r4  r5  r7  r8  r  r9  r:  r;  r<  r   r|   sumr   r  r  r  r  r  r  r=  r>  r   r   )r@  include_runtimer   rz  rL   r   s         r*   ro   zSession.compact  s   1B1N--TWTYTY#'(A#B#B  +7 ((T]## 	
 $ 	2q11M1$-@@SDO# 	6(? 	6"5O=
$/=
TZ=
 =
 TZ	=

 d1=
 ]=
 $/=
 $/=
 =
 dk=
 =
 $/=
 t|=
 D-=
 T/=
  d1!=
"  !7#=
 =
$ !$"9%=
&  !9$:PRVRc!d!d'=
( 4+)=
* -d.Q+=
, -d.Q-=
. )$*I/=
0 '(E1=
2 d13=
4 ()G5=
6 &t'C7=
8 )$*I9=
: #D$=;=
< d1==
>  5?=
@ !$"9A=
B t3C=
D &t'CE=
 =
J AE@V^#T%;<<\^K=
V #,!%!3#'#7&*&='+'?	  
 *,W=
\ DM400#8# & &!%& & & # # #67 $ 5$($=(@"1//"1 - $ 55?@SUY5Z5Zbd11`b !,1%'8  &+y=
 =
 =
 =	
r3   r   N)TF)FN)__name__
__module____qualname__r   r   r   r   r   rD  propertyr   rL  r`  classmethodr{   ru  r   ro   r2   r3   r*   rz   rz     s0       '+
3011 $4D uU!%t#$4()1!'++/%)$("&0404,005 $+/)-,0&* $t$(&*!%t+0'+" $#'$(!%#E`H `H3 `HC `H +/	`H AE	`H
 !`H  #`H 69`H %(`H @C`H $'`H (+`H" ,0#`H6 '+7`H8 $'9`H `H `H `HD 7 7 X7
- 
- 
- 
-t1 t1T t1d t1t t1 t1 t1 t1l   [( ,! ,! [,!\J
 J
 J
 J
 J
 J
 J
 J
r3   rz   c                     	 ddl m} t           ||                     S # t          $ rC t          t          j                            d          pd                                          cY S w xY w)zResolve the hermes agent home directory for the given profile.

    Prefers the profile-specific helper from api.profiles; falls back to the
    HERMES_HOME environment variable or ~/.hermes, expanding ~ correctly.
    r   get_hermes_home_for_profileHERMES_HOMEz	~/.hermes)api.profilesr  r   ImportErrorru   environrR   r   )r'  r  s     r*   _get_profile_homer  C  s    O<<<<<<//88999 O O OBJNN=11@[AALLNNNNNOs     A
A-,A-z**Response interrupted.**

The live response stream stopped before this turn finished. The partial output above was recovered from the run journal, but the interrupted agent process could not continue.z**Response interrupted.**

The live response stream stopped before this turn finished. The user message above was preserved, but no agent output was recovered.u   **Response interrupted.**

The live response stream stopped before this turn finished. Recovering the partial output from the run journal — reload this session to retry.zy**Response interrupted.**

The live response stream stopped before this turn finished. Partial output may have been lost.znEvidence: the WebUI process started after this turn began, so this looks like a real process crash or restart.zEvidence: the browser response stream was gone but the worker registry still listed the run. This is a stream/run bookkeeping split-brain.zEvidence: the stream was gone and worker bookkeeping no longer had an active run for it. This usually means the worker state was lost or cleaned up without a terminal event.z_Evidence: the stream stopped, but the WebUI could not classify the interruption more precisely.)process_restartstream_run_split_brainlost_worker_bookkeepingunknown	stream_idr*  r  c                    	 |rt          |          nd}n# t          t          f$ r d}Y nw xY w|D	 t          t          t          dd          pd          |k    rdS n# t          t          f$ r Y nw xY w| rb	 t          j        5  t          |           t          j        v r	 ddd           dS 	 ddd           n# 1 swxY w Y   n# t          $ r Y nw xY wdS dS )zEClassify the stale live-response state without overstating certainty.NSERVER_START_TIME        r  r  r  r  )	r   r   r   r   r   r   r   r   r'   )r  r*  starteds      r*   _classify_interruption_causer  ~  s   /AK%*+++tz"    	WT#6<<CDDwNN(( O:& 	 	 	D	  )	& 4 4y>>T%55534 4 4 4 4 4 4 454 4 4 4 4 4 4 4 4 4 4 4 4 4 4  	 	 	D	((9sY    ,,)A A21A28C B;!C /C ;B??C B?C 
CCrecovered_outputpending_retryinterruption_causec                 v    | rd}n|rd}nd}t                               |t           d                   }d| d| S )NzrThe partial output above was recovered from the run journal, but the interrupted agent process could not continue.uT   Recovering the partial output from the run journal — reload this session to retry.zHThe user message above was preserved, but no agent output was recovered.r  zW**Response interrupted.**

The live response stream stopped before this turn finished. r   )_INTERRUPTION_CAUSE_DETAILSrR   )r  r  r  outcomecause_details        r*   _interrupted_content_forr    s}      ]D 	 
 ], 	
 ].22#I. L
	$	$ 	$!	$ 	$r3   F)r  r  r  r*  c                     t          ||          }t          | ||          }d|t          t          j                              dd|d}|r| sd|d<   |S )u  Build the standard interrupted-turn marker.

    ``recovered_output=True`` means the run journal already yielded visible
    text on this repair pass — the marker advertises that the partial output
    has been recovered.

    ``pending_retry=True`` is the lazy-retry hook: the journal was unreadable
    on this pass (page-cache loss, un-fsynced writes on slow FS, etc.). The
    marker carries a ``_pending_journal_recovery`` flag so a later
    ``get_session()`` can re-attempt recovery without baking a permanent
    "no output" claim into the transcript.

    The two are mutually exclusive; ``recovered_output`` wins if both are
    set so the caller cannot accidentally re-arm retry on a successful
    repair.
    r  )r  r  r  	assistantTinterrupted)r   r   r   _errortyper  _pending_journal_recovery)r  r  r   r   )r  r  r  r*  r  r   markers          r*   _interrupted_recovery_markerr    s    . 6-   ')#-  G %%0 F  3- 3.2*+Mr3      limitc                    t          | t                    si S i }t          |                                           d |         D ]F\  }}t	          |          }|d d         t          |          dk    rdndz   |t	          |          <   G|S )Nx   z...r   )r   r   r|   r   r   r   )argsr  outrW   r  r   s         r*   _truncate_journal_tool_argsr    s    dD!! 	
C4::<<((%0 H H
U5zzTcT
s4yy3eeBGCHHJr3   c                 n    d                     t          | pd                                                    S )Nr   r   )r   r   r   r  s    r*    _normalize_journal_recovery_textr    s,    88C$$**,,---r3   r   c                    t          | t                    sdS g }|                     d          pg D ]6}t          |t                    s	 t          j        |                    d          pi ddt
                    }n4# t          $ r' t          |                    d          pd          }Y nw xY w|                    t          |                    d          pd          |t          |                    d	d                    t          |                    d
d                    t          |                    d          p|                    d          pd          f           8t          |                     d          pd          	                                t          |                     d          pd          	                                t          |          fS )zIReturn a stable identity for partial assistant markers recovered on load.)r   r   r2   _partial_tool_callsr  FT)r[   	sort_keysdefaultr   r%   doneis_errorpreviewsnippetr   	reasoning)r   r   rR   r   r   r   r'   r~   r   r   tuple)r   tool_sig	tool_callargs_sigs       r*   _partial_message_signaturer    s   gt$$ |H[[!677=2  	)T** 		8zf%%+"	  HH  	8 	8 	89==006B77HHH	8	f%%+,,vu--..z51122	i((JIMM),D,DJKK
 	 	 	 	 	GKK	""(b))//11GKK$$*++1133h s   2A>>.B/.B/c                 
   t          | t                    s| dfS g }d}d}| D ]^}t          |t                    r0|                    d          rt	          |          }||k    rd}D|}nd}|                    |           _||fS )zFCollapse repeated identical partial markers from the same failed turn.FN_partialT)r   r|   r   rR   r  r~   )r   	collapsedchangedprevious_partial_sigr   sigs         r*   ri  ri    s    h%% IG 	" 	"gt$$ 	(Z)@)@ 	(,W55C#s**#&  #' !!!!gr3   r   c                    t          |          }|sd S t          | j        pg           D ]\  }}t          |t                    r|                    d          dk    r4|                    d          rJt          |                    d                    }|so||k    r|c S t          |          dk    r||v r|c S d S )Nr   r  r  r      )r  r   r   r   r   rR   r   )r   r   	candidateidxr   r   s         r*   ,_find_existing_assistant_for_journal_contentr  "  s    099I t!'"2"8b99  W'4(( 	GKK,?,?;,N,N;;x   	3GKK	4J4JKK 	y  JJJy>>RI$9$9JJJ4r3   r  r%   r  c                   t          |pd          }t          |          }|rt          |          nd}| j        pg D ]}t          |t                    st          |                    d          pd          |k    rAt          |                    d          p|                    d          pd          }||k    r|+|                    d          }	|	rt          |	          |k    r dS dS )	u}  Return True when an equivalent tool card already exists.

    Matching rule:

    * If the existing tool card carries ``_recovered_stream_id``, that means a
      previous journal-recovery run materialized it.  The retry can safely
      collapse against it only when both stream ids match — otherwise a
      legitimately-repeated tool (e.g. a second ``terminal: ls`` in a
      different turn) would be dropped.
    * If the existing tool card has no ``_recovered_stream_id`` (a live tool
      card, or a tool card carried over from a core transcript that pre-dates
      stream-id tagging), the legacy name+preview match still wins.  This
      preserves the "core transcript already has this tool, don't duplicate
      it" invariant the original repair path established.
    * When ``stream_id`` is omitted, the helper degrades cleanly to its
      pre-fix session-wide behaviour.
    r   Nr%   r  r  _recovered_stream_idTF)r   r  r%  r   r   rR   )
r   r%   r  r  candidate_namecandidate_previewcandidate_streamr  existing_previewexisting_streams
             r*   _journal_tool_already_presentr  5  s   0 __N8AA)2<s9~~~'-2  	)T** 	y}}V$$*++~==;MM)$$F	i(@(@FB
 
 000''mm,BCCO
  3#7#7;K#K#Ktt5r3   c                    |sdS 	 ddl m}  || j        |          }n# t          $ r Y dS w xY w|                    d          pg D ]}t          |t                    st          |                    d          p|                    d          pd          }t          |                    d          t                    r|                    d          ni }|d	k    r't          |                    d
          pd          r dS |dk    rO|                    d          rt          |                    d
          pd                                          r dS |dk    r dS dS )NFr   read_run_eventseventseventr  r   rY  tokenr   Tinterim_assistantalready_streamedr   )	api.run_journalr  r@   r'   rR   r   r   r   r   )r   r  r  journalr  
event_namerY  s          r*   _run_journal_has_visible_outputr  f  s    u333333!/'"4i@@   uuX&&,"  %&& 	7++Fuyy/@/@FBGG
*4UYYy5I5I4*P*PX%))I&&&VX  SV)<)<)B%C%C 44,,,{{-.. 7;;v&&,"--3355 tt44  5s    
,,c           	         |sdS 	 ddl m}m}  || j        |          } || j        |          }|                    d          rdS 	 |                                 p|                                j        dk    S # t          $ r Y dS w xY w# t          $ r1 t                              dt          | dd          |d	           Y dS w xY w)
a  Return True for journals that may become visible on a later read.

    `read_run_events()` deliberately collapses missing files and empty files
    into an empty event list, so the lazy retry path needs a small filesystem
    visibility check to avoid burning all retry attempts while WSL2 / network
    filesystems are still surfacing the journal.  Non-empty journals are treated
    as sealed enough for retry-budget accounting; if they contain no visible
    output, the normal capped give-up path handles them.
    Fr   )	_run_pathlatest_run_summaryterminalTz?Session %s: failed to classify journal visibility for stream %sr@   ?r/   )r  r  r  r@   rR   r9   r    st_sizer&   r'   r#   r$   r   )r   r  r  r  r   summarys         r*   _journal_is_still_arrivingr    s     uAAAAAAAAy+Y77$$W%7CC;;z"" 	5	%B$))++*=*BB 	 	 	44	   MG\3//	 	 	
 	
 	
 uus/   ?B 1A: :
BB BB 7CCdedupe_existingr  c                .
    sdS 	 ddl m}  | j                  }n># t          $ r1 t                              dt           dd          d           Y dS w xY wd	 |                    d
          pg D             }|sdS dg ddg }dt          dz  f fdd)dt          dz  dt          f fd}|D ] }t          |                    d          p|                    d          pd          }	t          |                    d          t                    r|                    d          ni }
t          |                    d          t          t          f          r|                    d          nd}|	dk    rWt          |
                    d          pd          }|ss|pt          j                                        |           |	dk    r|
                    d          r              Et          |
                    d          pd                                          }|ss|pt          j                    r=d                                                  d          s                    d                               |                         |	dk    r             }| ||          }t          |
                    d          pd          }t          |
                    d          pd          }rt%           ||          r|v|                    |||d|                    d          pt'          |          d z    |t)          |
                    d!          pi           ddd"	           d||	d#k    r&t          |
                    d          pd          }t+          |          D ]}|                    d$          r|r|                    d          |k    rd|d$<   |
                    d          rNt          |
                    d          pd          |d<   t          |
                    d          pd          |d%<   |
                    d&          |
                    d&          |d&<   t-          |
                    d'd                    |d'<    n|	d(v r
              "              |r t/           j        pg           |z    _        dS )*a]  Recover already-emitted visible output from a dead stream journal.

    This repair path is intentionally conservative: it restores user-visible
    assistant text and tool-card metadata that had already been emitted over
    SSE before the WebUI process died. It does not restore hidden reasoning and
    it does not try to continue execution.
    Fr   r  z4Session %s: failed to read run journal for stream %sr@   r  Tr/   c                 <    g | ]}t          |t                    |S r2   )r   r   )rg   r  s     r*   rm   z4_append_journaled_partial_output.<locals>.<listcomp>  s(    XXX
5RV@W@WXeXXXr3   r  Nr   c                  P   d                                                               } g | sS rt          |           }||d |S t          pt	          j                              }j                            d| |d	d           t          j                  dz
  d dS )Nr   r  Tr   r   r   _recovered_from_run_journalr  r   )r   r   r  r   r   r   r~   r   )
r   existing_idxr   appended_anyassistant_partsassistant_started_atcurrent_assistant_idxr  r   r  s
      r*   flush_assistantz9_append_journaled_partial_output.<locals>.flush_assistant  s    ''/**0022 	)(( 	$GQXYYL'(4%'+$##,;	<<	"+/$-!
 !
 	 	 	 !$G$4 5 5 9#$$r3   r&  c                                  }||S j                             ddt          | pt          j                              dd           t	          j                   dz
  dS )Nr  r   Tr  r   )r   r~   r   r   r   )r&  r  r  r  r  r   r  s     r*   ensure_assistant_anchorzA_append_journaled_partial_output.<locals>.ensure_assistant_anchor  s    o?J 	Z649;;77+/$-!
 !
 	 	 	 !$G$4 5 5 9$$r3   r  r  r   rY  r  r   r  r  )
r   z

r   r%   r  r  zjournal-seqr   r  )	r%   r  r  tidassistant_msg_idxr  r  r  r  tool_completer  r  durationr  >   r  errorcancelapperror
stream_endr   )r  r  r@   r'   r#   r$   r   rR   r   r   r   r   r   r   r~   r   r   r   r  r   r  r   r   r|   r%  )r   r  r  r  r  r  recovered_tool_callsr  r  r  rY  r&  r   
anchor_idxr%   r  r  r  r  r  r  r  s   ```              @@@@@r*    _append_journaled_partial_outputr    s5     u
333333!/'"4i@@   BG\3//	 	 	
 	
 	
 uu YXX!6!6!<"XXXF uL!#O)-(,')%S4Z % % % % % % % % % % % %2% %EDL %C % % % % % % % % % %&  C C7++Fuyy/@/@FBGG
*4UYYy5I5I4*P*PX%))I&&&VX0:599\;R;RUXZ_T`0a0akUYY|,,,gk
  w{{6**0b11D " A';'C'1'@TY[[$""4(((,,,{{-.. !!!w{{6**0b117799D " A';'C'1'@TY[[$ /rww'?'?'H'H'U'U /&&v...""4(((O(**J!44Z@@
w{{6**4f55D'++i006B77G #@w)$ $ $  )3% ''""U%))E"2"2"Sc:N6O6ORS6SUU%/3GKK4G4G4M2NN/3(1
) 
) 
 
 
  L$.!((w{{6**0b11D%&:;;  	==((  y}}V44<<(,If%{{9-- Q/27;;y3I3I3OR/P/P	),/27;;y3I3I3OR/P/P	),{{:..:07J0G0G	*-,0Z1O1O,P,PIj)E  = NNNOO !'"4":;;>RRs   & 7A! A!r  iQ _JOURNAL_RETRY_LOCKSr   c                     t           5  t                              t          |           t	          j                              cd d d            S # 1 swxY w Y   d S r   )_JOURNAL_RETRY_LOCKS_GUARDr  
setdefaultr   r<   Lockr   s    r*   _journal_retry_lock_for_sidr	  b  s    	# K K#..s3xx9I9IJJK K K K K K K K K K K K K K K K K Ks   9AAA)r*  c                     | rt          d||          S |st          d|          S t          d||          }t          |          |d<   d|d<   t          t          j                              |d	<   |S )
zBuild an interrupted-turn marker, arming the lazy-retry hook when
    visible output was not recovered yet but a stream id is available.Tr  r  r*  F)r  r*  )r  r  r*  _journal_retry_stream_idr   _journal_retry_attempts_journal_retry_first_seen_ts)r  r   r   r   )r  r  r*  r  s       r*   &_build_recovery_marker_with_retry_hookr  g  s    
  
+!1
 
 
 	

  
+"1
 
 
 	
 *-  F
 *-YF%&()F$%-0-=-=F)*Mr3   c                    t          | dd          pg }t          |          D ]a}t          |t                    s|                    d          r dS |                    d          dk    r|                    d          s dS bdS )	zCheap short-circuit: scan from the tail until the most recent normal
    assistant turn. Any `_pending_journal_recovery` flag found before then
    means a retry is queued.
    r   Nr  Tr   r  r  F)r   r   r   r   rR   )r   r   rK  s      r*   "_session_has_pending_journal_retryr    s    
 w
D117RH!!  #t$$ 	77.// 	44776??k))#''(2C2C) 555r3   r  c                     |                      dd            |                      dd            |                      dd            |                      dd            d S )Nr  r  r  r  pop)r  s    r*   _strip_journal_retry_metar    s\    
JJ*D111
JJ)4000
JJ($///
JJ-t44444r3   
marker_idxc                    | j         }|dk     s|t          |          k    rdS ||dz   d         }|sdS d |D             }|sdS d |D             }||         }|d|         |z   |gz   |z   }|dz   }|}	|	|z
  }
| j        pg D ]f}t          |t                    s|                    d          }t          |t                    r$||k    r||t          |          z   k     r||
z   |d<   g|| _         dS )zMove `_recovered_from_run_journal=True` rows appended *after*
    ``marker_idx`` to sit immediately above the marker so chronological
    order is preserved (journaled output happened during the turn, marker
    annotates its end).
    r   Nr   c                 f    g | ].}t          |t                    |                    d           ,|/S r  r   r   rR   rg   ms     r*   rm   z6_reorder_journal_tail_above_marker.<locals>.<listcomp>  sM       a#$55)F#G#G	  r3   c                 f    g | ].}t          |t                    r|                    d           ,|/S r  r  r  s     r*   rm   z6_reorder_journal_tail_above_marker.<locals>.<listcomp>  sM       1d##().K(L(L	  r3   r  )r   r   r%  r   r   rR   r   )r   r  r   tail	journaledrestr  new_messagesold_journaled_idx_basenew_journaled_idx_baseshiftr  r  s                r*   "_reorder_journal_tail_above_markerr%    s|    HA~~s8}}44JN$$%D    I     D j!F*
	(	 	  (!^'"%;;E'-2 9 9	)T** 	mm/00c3 	9C+A$A$A03y>>AAA-05[I)*#Gr3   c                    t          t          | dd          pd          }t          |          }|                    d          st                              d|           dS 	 t          | d          |                                 t          5  t          
                    |          |u rt                              |d            d d d            S # 1 swxY w Y   S # |                                 t          5  t          
                    |          |u rt                              |d            d d d            w # 1 swxY w Y   w xY w)Nr@   r   Fblockingz1lazy journal-retry already running for session %sTpreserve_arriving_budget)r   r   r	  acquirer#   r$    _retry_journal_recovery_in_placereleaser  r  rR   r  )r   r   locks      r*   $_try_retry_journal_recovery_in_placer/    s   
gg|R006B
7
7C&s++D<<<'' H#NNNu4/d
 
 
 	' 	4 	4#'',,44$((d333	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	' 	4 	4#'',,44$((d333	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4sB   $C 8CC	C	E;8D?3E?EEEEr)  r*  c          
         	 | j         pg }t          t          |          dz
  dd          D ]}||         }t          |t                    s!|                    d          dk    r-|                    d          s|                    d          s dS |                    d          d	k    r|                    d          s|                    d
          }|                    d          pd}t          |                    d          pd          }t          j                    }|t          k    p|o|t          |          z
  t          k    }	|spt          |d<   t          |           	 |                     d           n<# t          $ r/ t                              dt#          | dd          d           Y nw xY w dS |	rpt          |d<   t          |           	 |                     d           n<# t          $ r/ t                              dt#          | dd          d           Y nw xY w dS t          | j                   }
t%          | |d          }|rt&          |d<   t          |           |
}t)          | |           	 |                     d           n<# t          $ r/ t                              dt#          | dd          d           Y nw xY wt                              dt#          | dd          ||            dS |r>t-          | |          r.t                              dt#          | dd          |            dS |dz   }|t          k    rt          |d<   t          |           n||d<   	 |                     d           n<# t          $ r/ t                              dt#          | dd          d           Y nw xY w dS dS # t          $ r. t                              dt#          | dd                     Y dS w xY w)u   Re-attempt run-journal recovery for the most recent pending marker.

    Returns True if the marker was promoted to the recovered-output wording.
    Never raises — caller is best-effort.
    r   r   r   r  r  r  Fr  r  r  r  r   r  r   rM  z2save() failed while demoting marker for session %sr@   r  Tr/   r  z3save() failed while promoting marker for session %szQSession %s: lazy journal-recovery promoted marker for stream %s after %d attemptszISession %s: journal for stream %s still arriving; preserving retry budgetz9save() failed while updating retry counter for session %sz6_retry_journal_recovery_in_place failed for session %s)r   ranger   r   r   rR   r   r   _JOURNAL_RETRY_MAX_ATTEMPTSr   _JOURNAL_RETRY_GIVEUP_SECONDS_INTERRUPTED_NEUTRAL_WORDINGr  r`  r'   r#   r$   r   r  _INTERRUPTED_RECOVERED_WORDINGr%  infor  	exception)r   r*  r   r  rK  r  
first_seenattemptsnowgive_uptail_len_beforeokrK   next_attemptss                 r*   r,  r,    s   o#)rX*B33 f	 f	C3-Cc4(( wwv+--cggh6G6G-GG$?@@ . uu=00GG788 1  :;;I!?@@EAJ377#<==BCCH)++C77  PeJ///2OO	   !=I)#...LL%L8888    LLLs;;!% !      uu !=I)#...LL%L8888    LLLs;;!% !      uu!'"233O1D  B  !?I)#... $27C@@@LL%L8888    LLMs;;!% !      2G\377   tt(
.w	BB
 .G\377	   uu$qLM ;;;!=I)#....1>-.e4444   OG\377!       55u   DG\3//	
 	
 	
 uus   BO CO )F ?O  6F96O 8F99O ?O G21O 26H+(O *H++O 1AO JO 6KO K0O >O /O 7NO 6OO OO 4PPT)require_stream_deadrM  c                   | j         }| j        sdS |&| j        |k    rdS |r| j        t                      v rdS t	          | j                  dk    rd                    t          | j        pd                                                    }d}|r| j        r| j        d         }t          |t                    rh|                    d          dk    rOd                    t          |                    d	          pd                                                    }	|	|k    }t          t          j                              }
t          | j        t          t          f          r| j        dk    rt          | j                  }
|st!          | |

           n9d| j        dd}| j        rt%          | j                  |d<   t'          | |           t)          | |p| j                  }|p| j        }| j        }d| _        d| _        g | _        d| _        | j                            t-          |||                     |                     |           t0                              d|           dS |                                rlt7          |d          5 }t9          j        |          }ddd           n# 1 swxY w Y   |                    dg           }|r|p| j        }|| _        |                    dg           | _        dD ].}|                    |          t?          | |||                    /tA          | j                  }d}|rv| j        rotC          | j                  D ]Z}t          |t                    rC|                    d          dk    r*tA          |                    d	                    }	|	|k    } n[|r|stE          | |          rqt          t          j                              }
t          | j        t          t          f          r| j        dk    rt          | j                  }
t!          | |

           t)          | |d          }| j        }d| _        d| _        g | _        d| _        |r*| j                            tG          d||                     |                     |           t0                              d|t	          |          |rdnd           dS | j        rqt          t          j                              }
t          | j        t          t          f          r| j        dk    rt          | j                  }
t!          | |

           t)          | |p| j                  }|p| j        }| j        }d| _        d| _        g | _        d| _        | j                            t-          |||                     |                     |           t0                              d|           dS )u0  Inner repair logic. Must be called with the per-session lock already held.

    Re-checks session state under the lock, then either syncs messages from the
    core transcript (if present and non-empty) or restores the pending user
    message as a recovered user turn and appends an error marker.

    stream_id_for_recheck: when provided, repair bails if session.active_stream_id
    changed (e.g. context compression rotated it).  The cache-miss repair path
    also requires the stream to be absent from active streams; the streaming
    thread's final fallback passes require_stream_dead=False because it runs
    before its own stream is removed from STREAMS.

    Returns True if repair was applied, False if the re-check bailed out.
    Must never raise — caller is responsible for exception handling.
    FNr   r   r   r   r   r   r   r   T)r   r   r   r   r  r1  zPSession %s: recovered pending user turn (messages non-empty), added error markerr^   r_   r   r%  )r  r  r(  r  z5Session %s: synced %d messages from core transcript%sz and recovered journaled outputz8Session %s: no core transcript found, added error marker)$r@   r   r   r   r   r   r   r   r   r   r   rR   r   r   r*  r   r   r   r|   r   r  r~   r  r`  r#   r7  r9   r   r   r{   r%  setattrr  r   r  r  )r   	core_pathstream_id_for_recheckr@  rM  r   _pending_text_already_checkpointed	_last_msg
_last_text_recovered_tsr   r  
_stream_id_pending_started_atr   corecore_messagesfields                      r*    _apply_core_sync_or_error_markerrO  T  s   . 
C' u(
 #'<<<5 	7#;?Q?S?S#S#S5 7!!W%A%GR!H!H!N!N!P!PQQ % 	DW- 	D(,I)T** Dy}}V/D/D/N/N XXc)--	*B*B*Hb&I&I&O&O&Q&QRR
(2m(C%DIKK((g03,?? 	<GD^abDbDb :;;M$ 
	B*7mLLLLL "7" I
 * M+/0K+L+L	-(-gyAAA;!=W%=
 
 +Fg.F
%8#' '+$&(#%)"2!1$#6  	
 	
 	
 	&6777^	
 	
 	
 t  @)g... 	 !9Q<<D	  	  	  	  	  	  	  	  	  	  	  	  	  	  	 R00 <	.J'2JJ,G!%,!;!;GL 9 988E??.GUDK888<W=YZZM$)! !1 !)'*:!;!;  I!)T22 y}}V7L7LPV7V7V%EimmT]F^F^%_%_
0:m0K-Q-Q 4GZHHQ
 !$DIKK 0 0g83,GG DGLfijLjLj$'(B$C$CM.w-PPPP? $     
 #*"<'+G$+/G(*,G')-G&  ''0)-",+>    $ LL*:L;;;KKGM""5EM112	   4 # I DIKK((g03,?? 	<GD^abDbDb :;;M&w-HHHH79!9  'B'*BJ!4#G#'G "$G!%G.- 2	
 	
 	
   LL"2L333
KKJCPPP4s   J66J:=J:   c                    | j         }| j        r|r|t                      v rdS t          | dd          }|r	 t	          j                    t          |          z
  }n&# t          t          f$ r t          d          }Y nw xY w|t          k     r)t          
                    d| j        |t                     dS nt          d          }| j        }|rt          d |D                       sdS 	 t          | j                  }|dz  d| d	z  }t          |          }|                    d
          st          
                    d|           dS 	 d}|t          d          k    rdn|dd}	||k     rt          j        nt          j
        }
 |
d|||	           t%          | ||          |                                 S # |                                 w xY w# t(          $ r t                              d|           Y dS w xY w)uX  Recover a sidecar stuck with messages=[] and stale pending state.

    Fires only when messages is empty, pending_user_message is set,
    active_stream_id is set, the stream is no longer alive, AND the turn is
    older than _REPAIR_STALE_PENDING_GRACE_SECONDS (#1624).

    Uses a non-blocking lock acquire so a caller that already holds the
    per-session lock (e.g. retry_last, undo_last, cancel_stream) cannot
    deadlock when get_session() triggers this on a cache miss.

    Returns True if repair was applied, False otherwise.
    Must never raise — all errors are caught and logged.
    Fr*  Ninfui   _repair_stale_pending: skipping repair for session %s — pending_started_at age=%.1fs < %ds grace windowc              3      K   | ]}|d v V  	dS rc  r2   re  s     r*   rf  z(_repair_stale_pending.<locals>.<genexpr>N  s(      TT1!FFTTTTTTr3   sessionssession_rB   r'  zE_repair_stale_pending: lock contended, skipping repair for session %si,  z.1frT   zD_repair_stale_pending firing: session=%s stream_id=%s pending_age=%s)rD  z+_repair_stale_pending failed for session %s)r   r   r   r   r   r   r   r   #_REPAIR_STALE_PENDING_GRACE_SECONDSr#   r$   r@   rh  r  r'  r   r+  warningrO  r-  r'   r8  )r   _seen_stream_id_started_ager   profile_homerC  r.  _DIAG_WARN_WINDOW_SECONDS_age_str_logs              r*   _repair_stale_pendingr_  "  sp   " .O( ""4"6"666u
 w 4d;;H 	 9;;x0DD:& 	  	  	 <<DDD	 555LLB"D*M  
 5 6 U||

C cTTPSTTTTT u#(99 :-0E30E0E0EE	&s++ ||U|++ 	LLWY\   5	 ),%!%u!5!5d>>>>H%),E%E%E6>>6<DDV_h   4/   LLNNNNDLLNNNN   FLLLuus=   #A  A>=A>*A G AF< 'G <GG %G>=G>c                     t          | t                    sdS t          |           D ]}t          |          }|r
|dk    r|c S dS )Nr   r   r   r|   r   r   r   r   r   s      r*   _last_non_tool_rolerc  w  s\    h%% rH%%  W%% 	DFNNKKK2r3   c                     t          | t                    sd S t          |           D ]}t          |          }|r
|dk    r|c S d S )Nr   ra  rb  s      r*   _last_non_tool_messagere    s\    h%% tH%%  W%% 	DFNNNNN4r3   c                    t          | t                    sdS |                     d          }t          |t                    r|S t          |t                    rg }|D ]}t          |t                    r|                    |           -t          |t                    rCt          |                    d          t                    r|                    |d                    d                    |          S dS )Nr   r   r   )r   r   rR   r   r|   r~   r   )r   r   partsitems       r*   _message_content_textri    s    gt$$ rkk)$$G'3 '4    	+ 	+D$$$ +T""""D$'' +Jtxx7G7G,M,M +T&\***wwu~~2r3   c                     | dS t          | dd           st          | dd           rdS t          t          | dd           pg           dk    S )NFr   r   r   r   )r   rc  )cacheds    r*   %_inactive_cache_tail_needs_disk_checkrl    s_    ~uv)400 GFDZ\`4a4a uwvz4@@FBGG6QQr3   c                    | |dS t          | dd          st          | dd          rdS t          | dd          pg }t          |dd          pg }t          |          t          |          k    rdS t          |          dk    rdS t          |          dk    rdS t          |          }d}t	          |          D ]}t          |          dk    r|} n|dS t          |          t          |          k    S )a  Return True when an inactive cached session has an unsaved user tail.

    A completed turn is saved to the sidecar before the browser reloads it.  In
    rare compaction/reconnect paths the in-process cache can retain a recovered
    or optimistic user row after the saved assistant tail even though the row was
    never persisted.  If /api/session serves that cache entry, the visible
    transcript appears to end on the old prompt and the saved assistant answer
    looks missing until a fork/reload resets the cache.
    NFr   r   r   r   r  )r   r   rc  re  r   r   ri  )rk  disk_sessioncached_messagesdisk_messagescached_tailprevious_disk_userr   s          r*   "_cache_has_stale_unsaved_user_tailrs    s7    ~-uv)400 GFDZ\`4a4a ufj$77=2OL*d;;ArM
?s=1111u?++v55u=))[88u(99KM**  !!V++!(E , !u
 !--1FGY1Z1ZZZr3   c                 4   t           5  t          j        |           }|t          j        |            ddd           n# 1 swxY w Y   ||st	          |          r	 t
                              |           }t          ||          r?t           5  |t          | <   t          j        |            ddd           n# 1 swxY w Y   |}n-# t          $ r  t          
                    d| d           Y nw xY w|sMt          |          r>	 t          |           n-# t          $ r  t          
                    d| d           Y nw xY w|S |rt
                              |           }|r|S nt
                              |           }|rt           5  |t          | <   t          j        |            t          t                    t          k    r2t          j        d           t          t                    t          k    2ddd           n# 1 swxY w Y   |s	 t#          |          }|sMt          |          r>	 t          |           n-# t          $ r  t          
                    d| d           Y nw xY w|st          |j                  d	k    rm|j        rf|j        r_|j        t+                      vrJt           5  t          j        |           |u rt          j        | d           ddd           n# 1 swxY w Y   n# t          $ r Y nw xY w|S t/          |           )
ay  Load a session, optionally with metadata only (skipping the messages array).

    Metadata-only loads intentionally do not populate the full-session cache.
    Otherwise a later full load could return a compact object with an empty
    messages list. Use this when you only need compact() metadata and not the
    actual message history (e.g., for fast sidebar switching).
    Nz2stale cached user-tail check failed for session %sTr/   z5lazy journal-retry failed on cache hit for session %sFlastz5lazy journal-retry failed on cold load for session %sr   )r	   r   rR   move_to_endrl  rz   r{   rs  r'   r#   r$   r  r/  ru  r   r   popitemr_  r   r   r   r   r  KeyError)r   metadata_onlyrk  rn  rT   repaireds         r*   get_sessionr|    s    
 & &c"" %%%& & & & & & & & & & & & & & &  	!Fv!N!N 	&||C005flKK * 2 2(4 ,S1112 2 2 2 2 2 2 2 2 2 2 2 2 2 2 *F   H$      
  	!CF!K!K 	4V<<<<   K$      
  &&s++ 	H	 LL " 	- 	-HSM %%%h--,.. e,,,, h--,..	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	-
  	033
   $Fq$I$I <Q????$   S$ %        4S__%9%92 &:. &: .6H6J6JJJ 4 4#<,,11$Ld3334 4 4 4 4 4 4 4 4 4 4 4 4 4 4    
3--s   +?AA1C B:.C :B>>C B>C 'C21C2D 'E EA.HH	H	 K9 4I K9 'I.+K9 -I..AK9 5,K-!K9 -K11K9 4K15K9 9
LLc                 t   d}d}	 ddl m} t           ||                     dz  }t          j        |          }n# t
          $ r i }Y nw xY wt          |t                    r|                    di           ni }t          |t                    r|
                                }nt          |t                    rnt          |                    d          pd          
                                }t          |                    d          pd          
                                pd}|pt                      |fS )	z;Return the default model/provider configured for *profile*.r   Nr   r  zconfig.yamlr#  r  provider)r  r  r   r   _load_yaml_config_filer'   r   r   rR   r   r   r   )r'  default_modeldefault_providerr  config_pathconfig_data	model_cfgs          r*   _profile_default_model_stater    sK   M<<<<<<66w??@@=P1+>>    1;;0M0MU,,,SUI)S!! P!))	It	$	$ PIMM)44:;;AACCy}}Z88>B??EEGGO49799;KKKs   5< A
Ac                    |$	 ddl m}  |            }n# t          $ r d}Y nw xY w|r|}|}nt          |          \  }}|r|}t	          |t
                    r|nd}	|	r.|	r*|	                    d          r|	                    d          n| n| }
t          |
pt                      ||||d|	r|	                    d          nd|	r|	                    d          nd|	r|	                    d          nd|	r|	                    d          nd
  
        }t          5  |t          |j        <   t          j        |j                   t          t                    t          k    r2t          j        d	
           t          t                    t          k    2ddd           n# 1 swxY w Y   |	r|                                 |S )u  Create a new in-memory session.

    The session lives in the SESSIONS dict only — no disk write happens until
    the first message is appended (#1171 follow-up).  This avoids the
    "ghost Untitled session on disk" pile-up that occurred when users clicked
    New Conversation, reloaded the page, or completed onboarding without ever
    sending a message.  Subsequent code paths that populate state immediately
    (btw / background agent at api/routes.py) call ``s.save()`` themselves
    after setting title/messages, and ``_handle_chat_start`` saves the
    session as soon as the user actually sends a message — both are the
    natural first-write moments for a real session.

    Crash-safety: if the process exits between session creation and first
    message, the session is lost.  Since it had no messages, there is
    nothing to lose.  Worktree-backed sessions are the exception: they are
    saved immediately because creating the session also creates real
    filesystem state that must remain discoverable after restart.

    *profile* — when supplied by the caller (e.g. from the request body sent
    by the active browser tab), it is used directly so that concurrent clients
    on different profiles don't fight over a shared process-global.  If not
    supplied, we fall back to the process-level active profile (the pre-#798
    behaviour, preserved for calls that originate outside a request context).
    Nr   get_active_profile_namer   branch	repo_rootr&  )
r"  r#  r$  r'  r  r)  r9  r:  r;  r<  Fru  )r  r  r  r  r   r   rR   rz   r   r	   r   r@   rw  r   r   rx  r`  )r"  r#  r'  r$  r  worktree_infor  effective_modeleffective_model_providerwtworkspace_pathrT   s               r*   new_sessionr  -  s8   2 	<<<<<<--//GG 	 	 	GGG	 6#1  4PQX4Y4Y11 	6'5$$]D99	CtBQSbLrvvf~~LbffVnnn99YbN 8$6$8$8/(*4bffVnnn,.8x(((D24>266+...$46@BFF<000D	 	 	A 
 ) )!"Q\***(mml**%(((( (mml**) ) ) ) ) ) ) ) ) ) ) ) ) ) )
 
 	Hs    $$A8FF!$F!r   c                 f   t          |                     d          pd          }|                     d          p|                     d          }|dk    s|                    d          rdS t          |                     d                    r#t          |                     d	                     S d
S )zJReturn True for internal/background sessions hidden from the default list.r@   r   r  sourcecroncron_Tr  _show_pre_compression_snapshotF)r   rR   rd   r   )r   r   r  s      r*   _hide_from_default_sidebarr  l  s    
gkk,''-2
.
.C[[&&?'++h*?*?F3>>'22tGKK23344 G$DEEFFFF5r3   c                     dD ]K}	 t          |                     |          pd          }n# t          t          f$ r d}Y nw xY w|dk    r|c S LdS )N)rL   actual_message_countr   )r   rR   r   r   )r   rW   r  s      r*   _sidebar_message_countr  w  s{    8  	C((-A..EE:& 	 	 	EEE	199LLL 1s   $+A Asessions_by_idc                 D   t          |                     d          pd          }|}|                     d          }|h}|r_||vr[||v rWt          |          }|                    |           |                    |i                               d          }|r||vr||v W|S )Nr@   r   r  )r   rR   add)r   r  r   rootparentseens         r*   _sidebar_lineage_root_idr    s    
gkk,''-2
.
.CD[[,--F5D
 GV4''Fn,D,D6{{##D"--112EFF  GV4''Fn,D,D Kr3   c                     t          |                     d          p)|                     d          p|                     d                    S )Nr   rz  r   )r   rR   r   s    r*   _has_live_sidebar_stater    sN    &'' 	/;;122	/;;-..  r3   rT  c                 2   d | D             }i }| D ]}t          |                    d          pd          }|                    d          p|                    d          }|dk    s|                    d          rlt          ||          }|                    |g                               |           t                      }t                      }|                                D ]}	d |	D             }
d	 |	D             }|
r|st          d
 |
D                       r9t          d |
D                       }t          |d           }t          |          |k    rx|                    t          |                    d                               |                    d |
D                        |s|s| S g }| D ]X}t          |                    d          pd          }||v r+||v rt          |          }d|d<   |                    |           Y|S )a  Expose a hidden snapshot when it is the fuller transcript for a lineage.

    Pre-compression snapshots are normally hidden so archived compression
    segments do not duplicate the current continuation in the sidebar. If a
    snapshot row has more messages than the visible continuation for the same
    lineage, hiding it makes the conversation look truncated. In that case,
    show the fuller snapshot and suppress the shorter inactive continuation.
    c                 ~    i | ]:}|                     d           t          |                     d                     |;S rj   rR   r   rg   r   s     r*   rq   z8_prefer_fuller_snapshots_for_sidebar.<locals>.<dictcomp>  sP       ;;|$$GKK%%&&  r3   r@   r   r  r  r  r  c                 <    g | ]}|                     d           |S r  rQ   r  s     r*   rm   z8_prefer_fuller_snapshots_for_sidebar.<locals>.<listcomp>  s*    ___ww{{C]7^7^_7___r3   c                 <    g | ]}|                     d           |S r  rQ   r  s     r*   rm   z8_prefer_fuller_snapshots_for_sidebar.<locals>.<listcomp>  s*    ]]]W[[A[5\5\]W]]]r3   c              3   4   K   | ]}t          |          V  d S r   )r  r  s     r*   rf  z7_prefer_fuller_snapshots_for_sidebar.<locals>.<genexpr>  s+      GGG&w//GGGGGGr3   c              3   4   K   | ]}t          |          V  d S r   )r  r  s     r*   rf  z7_prefer_fuller_snapshots_for_sidebar.<locals>.<genexpr>  s+       X XW!7!@!@ X X X X X Xr3   c                 >    t          |           t          |           fS r   )r  r   r   s    r*   rU   z6_prefer_fuller_snapshots_for_sidebar.<locals>.<lambda>  s    !7!@!@BYZaBbBb c r3   rW   c              3      K   | ];}|                     d           t          |                     d                     V  <dS )r@   Nr  r  s     r*   rf  z7_prefer_fuller_snapshots_for_sidebar.<locals>.<genexpr>  s\       (
 (
{{<(((
L))**(
 (
 (
 (
 (
 (
r3   Tr  )r   rR   rd   r  r  r~   rC   r}   anyrH  r  r  r   r   )rT  r  groupsr   r   r  r  snapshot_ids_to_showcontinuation_ids_to_hidegroupvisible	snapshotsbest_visible_countbest_snapshotr  s                  r*   $_prefer_fuller_snapshots_for_sidebarr    s      N
 %'F 4 4'++l++1r22\**Cgkk(.C.CVs~~g66'@@$##**73333%(UU), 
 
__%___]]E]]]	 	i 	GGwGGGGG 	  X XPW X X XXXcc
 
 
 "-004FFF  ]%6%6|%D%D!E!EFFF '' (
 (
"(
 (
 (
 	
 	
 	
 	
   (@ 
C  '++l++1r22***&&&7mmG8<G45

7Jr3   c                 <    | D ]}|                     dd            d S )Nr  r  )rT  r   s     r*   _strip_sidebar_internal_flagsr    s3     < <4d;;;;< <r3   c            
      h   	 ddl m}  t           |                                                                                       }nk# t
          $ r^ t          t          j        dt          t          dz                                                                                                }Y nw xY w|dz  S )zHReturn state.db for the active Hermes profile, degrading to HERMES_HOME.r   get_active_hermes_homer  .hermesstate.db)
r  r  r   r   r!  r'   ru   getenvr   r   )r  hermes_homes     r*   _active_state_db_pathr    s    c777777113344??AAIIKK c c c29]Cy8H4I4IJJKKVVXX``bbc##s   AA A%B,+B,r
  c                     d                     t          | pd                                                    }|dk    rdS d}|                    |          o(|t	          |          d                                          S )Nr   r   zHermes WebUITzHermes WebUI #)r   r   r   rd   r   isdigit)r
  r   r   s      r*   _sidebar_title_is_generic_webuir    sr    88C$$**,,--D~tF??6""CtCKKLL'9'A'A'C'CCr3   c                    	 t          t                      d | D                       }n# t          $ r Y dS w xY w| D ]}|                    d          }||v rvt	          ||                   }|                    dd          }|                    |           |                    d          }|r||k    rt          |          r
||d<   ||d<   dS )zFAttach state.db compression lineage metadata used by sidebar collapse.c                 |    h | ]9}|                     d           t          |                     d                     :S rj   r  rp   s     r*   rh   z3_enrich_sidebar_lineage_metadata.<locals>.<setcomp>  s=    OOO!155;N;NOS|$$%%OOOr3   Nr@   _state_db_titler
  display_title)r   r  r'   rR   r   r  r   r  )rT  metadatar   r   r   state_db_titler
  s          r*    _enrich_sidebar_lineage_metadatar    s   0!##OOxOOO
 
     : :kk,''(??#''E"YY'8$??NNN5!!!KK((E:"e++3E:: , .<)*+9(: :s   &) 
77c                 \    | )	 |                      |           d S # t          $ r Y d S w xY wd S r   )stager'   )diagr%   s     r*   _diag_stager  	  sO    	JJt 	 	 	DD	 s    
))c                 b
  	
 t          | d           t                      	t          | d           t          j                    st          | d           t	                       t          j                    r	 t          | d           t          j        t          j        d                    }t          | d           t          5  t          t          j                              
d d d            n# 1 swxY w Y   	 d t          j        d	          D             n# t          $ r d Y nw xY w
fd
|D             }g }t          |          D ]t\  }dvrkt          | d           t                                                   d                    }|r,|                                ||<   |                    |           u|rL	 t          | d           t+          |           n*# t          $ r t,                              d           Y nw xY wt          | d           |D ](t1                              d          	          d<   )t          | d           d |D             }t          5  t          j                    D ]!                    d	          |j        <   "	 d d d            n# 1 swxY w Y   t          | d           t7          |                                d d          }d |D             }t9          |          }d |D             }t;          |           |D ]                    d          sdd<   t          | d           t=          |           |S # t          $ r t,                              d            Y nw xY wt          | d!           g }t          j        d	          D ]}|j                             d"          r	 t                               |j!                  r|                               U# t          $ r t,                              d#|           Y |w xY wt          | d$           t          j                    D ]2tE          fd%|D                       r|                               3t          | d&           |#                    d' d           	fd(|D             }t9          |          }d) |D             }t;          |           |D ]                    d          sdd<   t          | d           t=          |           |S )*Nzall_sessions.active_streamszall_sessions.index_existsz all_sessions.start_index_rebuildzall_sessions.read_indexr^   r_   zall_sessions.prune_indexc                 P    h | ]#}|j                             d           |j        $S rb   rc   rf   s     r*   rh   zall_sessions.<locals>.<setcomp>	  s@     ! ! !6,,S11!F! ! !r3   rJ   c                     g | ]x}t          |                    d           pd          v sN&t          |                    d           pd          v s&Rt          |                    d                     v|yS )r@   r   N)rE   )r   rR   rF   )rg   rT   rE   persisted_idss     r*   rm   z all_sessions.<locals>.<listcomp>$	  s       l++1r22mCC%1l 3 3 9r::mKK &-/l0C0CS`aaa .  .--r3   r   zall_sessions.backfill_loadr@   zall_sessions.backfill_writer-   z*Failed to persist last_message_at backfillzall_sessions.mark_streamingr   r{  zall_sessions.overlay_lockc                      i | ]}|d          |S rj   r2   rp   s     r*   rq   z all_sessions.<locals>.<dictcomp>H	  s    ;;;<!;;;r3   Tr}  r   zall_sessions.sort_filterc                 L    |                      dd          t          |           fS Nr  F)rR   r   rS   s    r*   rU   zall_sessions.<locals>.<lambda>P	  s#    quuXu?U?UWnopWqWq>r r3   rV   c                     g | ]w}|                     d d          dk    rY|                     dd          dk    r?|                     d          s*|                     d          s|                     d          u|xS )r
  r	  rL   r   r   rz  r9  rQ   rp   s     r*   rm   z all_sessions.<locals>.<listcomp>\	  s       Agz**j88EE/1--22011 3899 3 o.. 3 222r3   c                 0    g | ]}t          |          |S r2   r  rp   s     r*   rm   z all_sessions.<locals>.<listcomp>d	  s&    MMMA/I!/L/LMaMMMr3   r'  r  zall_sessions.lineage_metadataz7Failed to load session index, falling back to full scanzall_sessions.full_scanrK   rM   zall_sessions.full_scan_overlayc              3   8   K   | ]}j         |j         k    V  d S r   rj   )rg   xrT   s     r*   rf  zall_sessions.<locals>.<genexpr>|	  s,      99q|q|+999999r3   z"all_sessions.full_scan_sort_filterc                 B    t          | dd          t          |           fS r  )r   r   rS   s    r*   rU   zall_sessions.<locals>.<lambda>~	  s!    GAx779PQR9S9ST r3   c                     g | ][}|j         d k    r7t          |j                  dk    r|j        s|j        st          |dd          D|                    d          \S )r	  r   r9  NTr  )r
  r   r   r   r   r   ro   )rg   rT   r   s     r*   rm   z all_sessions.<locals>.<listcomp>	  s       WX	:
OOq  " !& ! ?D11 ! ii@QiRR   r3   c                 0    g | ]}t          |          |S r2   r  rp   s     r*   rm   z all_sessions.<locals>.<listcomp>	  s&    EEEA'A!'D'DEaEEEr3   )$r  r   r   r9   r?   r   r   r   r	   rC   r   rD   r   r   r'   r   rz   r{   rR   ro   r~   r1   r#   r$   r   r}   r@   sortedr  r  r  r%   rd   re   rh  r   )r  index
backfilledr   full	index_mapresultr  r)   r   rE   r  rT   s            @@@@r*   all_sessionsr  	  s   3444*,,1222$&& .D<===+--- "" ZTY	T7888J1;WMMMNNE8999 5 5 #HMOO 4 45 5 5 5 5 5 5 5 5 5 5 5 5 5 5%! !(-h77! ! !
  % % % $%       E J!%(( 0 01$A--&BCCC"<<l(;(;<<D 0#'<<>>a"))$/// OO&CDDD(<<<<<  O O OLL!MNNNNNO;<<<  $9EE,--%% %.!!
 9:::;;U;;;I  !**  A./ii(,*; /8 / /Ial++               8999I,,..4r4r  }A  B  B  BF    F :&AAFMMMMMF)&111  - -uuY'' -#,AiL=>>>,V444M 	T 	T 	TLLRSSSSS	T .///
Ch'' > >6S!!+8	>QV$$A#**Q--- 	> 	> 	>LL91=====	>6777_ I I9999S99999H3::a===:;;;HHTT^bHccc   \_   F 2&99FEEEEEF!&))) % %uuY 	%$AiL5666$V,,,Ms   9AM+ !C4(M+ 4C88M+ ;C8<M+  D M+ D.+M+ -D..BM+ 	 G* )M+ *$HM+ HA!M+ 26J5)M+ 5J99M+ <J9=B-M+ +$NN6P%P87P8r	  fallbackc                 4   | D ]}|                     d          dk    ry|                     dd          }t          |t                    rd                    d |D                       }t	          |                                          }|r|dd         c S |S )	z3Derive a session title from the first user message.r   r   r   r   r   c              3      K   | ]H}t          |t                    |                    d           dk    0|                    dd          V  IdS )r  r   r   Nr  rf   s     r*   rf  ztitle_from.<locals>.<genexpr>	  sZ      mm1Z4=P=PmUVUZUZ[aUbUbflUlUlQUU62..UlUlUlUlmmr3   N@   )rR   r   r|   r   r   r   )r   r  r  r   r   s        r*   
title_fromr  	  s     ! !55==F""i$$A!T"" nHHmmmmmmmq66<<>>D !CRCy   Or3   projectsc                    d | D             }|sdS i }t          j                    r	 t          j        t          j        d                    }d |D             }|D ]L}|                    d          }||v r1|                    d          r|                    ||d                    Mn*# t          $ r t          	                    d           Y nw xY wd}|D ]2}|                    |                    d          d	          }	|	|d<   d
}3|S )u  Tag any legacy untagged projects (`profile` missing) with a sensible default.

    Strategy:
      1. For each untagged project, look at the sessions assigned to it via
         the session index. If any session carries a profile, take that
         profile.  Most installs are single-profile so this picks up the
         right answer for everyone.
      2. Otherwise default to 'default'.

    Returns True if any project was mutated. Safe to call repeatedly — once
    every project is tagged, this is a no-op. Runs at most once per process
    (cached via the module-level _projects_migrated flag) but the result is
    persisted so it's a one-time write.
    c                 <    g | ]}|                     d           |S r'  rQ   rf   s     r*   rm   z8_backfill_project_profiles_if_needed.<locals>.<listcomp>	  s)    <<<a155+;+;<<<<r3   Fr^   r_   c                 H    h | ]}|                     d           |d           S )r  rQ   rf   s     r*   rh   z7_backfill_project_profiles_if_needed.<locals>.<setcomp>	  s-    UUU|ATATUAlOUUUr3   r  r'  z9Failed to read session index for project profile backfillr  T)
r   r9   r   r   r   rR   r  r'   r#   r$   )
r  untaggedsession_profile_by_projectr   untagged_idsrk   pidmutatedr)   inferreds
             r*   $_backfill_project_profiles_if_neededr  	  sZ    =<8<<<H u 24 "" 
V		Vj!3!=w!O!O!OPPGUUXUUUL M MeeL)),&&155+;+;&.99#q|LLL	M
  	V 	V 	VLLTUUUUU	V G  -11!%%2E2EyQQ)Ns   BB* *$CC)_migrater  c                 D   t          j                    sg S 	 t          j        t          j        d                    }n# t
          $ r g cY S w xY w| rt          st          5  t          rR	 t          j        t          j        d                    cddd           S # t
          $ r |cY cddd           S w xY wt          |          r=	 t          |           dan,# t
          $ r t                              d           Y nw xY wdaddd           n# 1 swxY w Y   |S )a  Load project list from disk. Returns list of project dicts.

    On first call, runs a one-time migration to back-fill the `profile` field
    on legacy untagged projects (#1614). Disable via `_migrate=False` for
    callsites that want the raw on-disk shape (test fixtures, e.g.).
    r^   r_   NTz*Failed to persist project profile backfill)r   r9   r   r   r   r'   _projects_migrated_PROJECTS_MIGRATION_LOCKr  save_projectsr#   r$   )r  r  s     r*   load_projectsr  	  s    !! 	:m5wGGGHH   			 ** *% 	* 	*! 
$$:m&=w&O&O&OPP	* 	* 	* 	* 	* 	* 	* 	* ! $ $ $#OO	* 	* 	* 	* 	* 	* 	* 	*$3H== 	*O!(+++)-&&  O O OLL!MNNNNNO
 &*"-	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	*. Osc   '? AA!D*&BB8)D7B88DCD$DDDDDDc                 \    t          j        t          j        | dd          d           dS )zWrite project list to disk.FrY   rZ   r^   r_   N)r   
write_textr   r   )r  s    r*   r  r  	  s0    TZuQOOOZabbbbbbr3   z	Cron Jobsc            	         ddl m} m}  |             pd}t          5  t	                      }|D ]}|                    d          t          k    r!|                    d          }||k    r|d         c cddd           S  ||pd          r! ||          r|d         c cddd           S |D ]_}|                    d          t          k    r?|                    d          s*||d<   t          |           |d         c cddd           S `t          j	                    j
        dd         }|                    |t          d	|t          j                    d
           t          |           |cddd           S # 1 swxY w Y   dS )u   Return the project_id of the system "Cron Jobs" project for the active profile.

    Each profile gets its own "Cron Jobs" project so cron-spawned sessions in
    profile A don't surface under the cron chip of profile B (#1614). Lookup
    keys on (name, profile) — a legacy untagged "Cron Jobs" project (no
    `profile` field) is treated as belonging to whichever profile first calls
    this in a given install, then re-tagged.

    Thread-safe and idempotent.  Returns a 12-char hex project_id string.
    r   )r  _is_root_profiler  r%   r'  r  Nr  z#6366f1)r  r%   colorr'  r&  )r  r  r  _CRON_PROJECT_LOCKr  rR   CRON_PROJECT_NAMEr  r  r  r  r~   r   )r  r  activer  r)   row_profiler  s          r*   ensure_cron_projectr  
  sg    GFFFFFFF$$&&3)F	   ??
  	' 	'AuuV}} 111%%	**Kf$$&&          8y99 '>N>Nv>V>V '&&         	' 	'AuuV}} 111!%%	:J:J1%)h'''&&'       * Z\\%crc*
$%)++
 
 	 	 	 	h=                 s&   AE>?!E>-AE>A"E>>FFr  c                 ^    |dk    rdS t          | pd          }|                    d          S )z4Return True if a session originates from a cron job.r  Tr   r  )r   rd   )r@   r  r   s      r*   is_cron_sessionr
  0
  s6    Vt
jB

C>>'"""r3   r  r#  c                 z    t          | |t                      ||||||	  	        }|                    d           |S )zCreate a new WebUI session populated with CLI/agent messages.

    Preserve parent_session_id from state.db so imported continuation segments
    keep their lineage in the WebUI store and sidebar instead of reappearing as
    detached orphan chats.
    )	r@   r
  r"  r#  r   r'  r&  rP   r  Fr1  )rz   r   r`  )	r@   r
  r   r#  r'  r&  rP   r  rT   s	            r*   import_cli_sessionr  9
  sT      	$&&+
	 
	 
	A FFEF"""Hr3   claude_codezClaude Code   i   i  i@ c                      t          j        d          } | r!t          |                                           S t          j        d          rdS t          j                    dz  dz  S )zOResolve the Claude Code projects directory without touching real home in tests. HERMES_WEBUI_CLAUDE_PROJECTS_DIRHERMES_WEBUI_TEST_STATE_DIRNz.clauder  )ru   r  r   r   home)overrides    r*   !_default_claude_code_projects_dirr  b
  s`    y;<<H +H~~((***	y.// t9;;"Z//r3   r   c                     t          j        t          |                                                                                               d                                                    d d         }t           d| S )Nr^   r  rK   )hashlibsha256r   r   r!  r   	hexdigestCLAUDE_CODE_SOURCE)r   digests     r*   _claude_code_session_idr  l
  si    ^C 1 1 9 9 ; ;<<CCGLLMMWWYYZ][]Z]^F ++6+++r3   c                    | d S t          | t          t          f          rt          |           S t          |                                           }|sd S 	 t          |          S # t
          $ r Y nw xY w	 t          j                            |                    dd                    	                                S # t          $ r Y d S w xY w)NZ+00:00)r   r   r   r   r   r   datetimefromisoformatr   r   r'   r  r   s     r*   _parse_claude_code_timestampr"  q
  s    }t%#u&& U||u::D tT{{    ..t||C/J/JKKUUWWW   tts%   A% %
A21A26AB; ;
C	C	c                    | dS t          | t                    r| d t                   S t          | t                    rg }d}| D ]}d}t          |t                    r|}nAt          |t                    r,|                    d          p|                    d          pd}|s`t          |          }t          |z
  }|dk    r n6|                    |d |                    |t          |d                   z  }d                    |          S t          | t                    r7t          |                     d          p|                     d                    S t          |           d t                   S )Nr   r   r   r   r   r  )
r   r   CLAUDE_CODE_MAX_CONTENT_CHARSr|   r   rR   r~   r   r   _extract_claude_code_text)r   rg  usedrh  r   	remainings         r*   r%  r%  
  s{   r'3 755566'4     	# 	#DD$$$ ED$'' Exx''D488I+>+>D" t99D5<IA~~LLjyj)***Cb	NN"DDyy'4   X(V)<)<)VI@V@VWWWw<<66677r3   )max_messagesr(  c                   g }d }d }d }	 |                      ddd          5 }|D ]u}t          |          |k    r n^|                                }|s0	 t          j        |          }n# t
          $ r Y Rw xY wt          |t                    sl|s|                    d          p|                    d          }	t          |	t                    rC|	                                r/d
                    |	                                          d d         }t          |                    d	          t                    r|                    d	          nd }
|
@t          |                    d
          t                    r|                    d
          n|g}
|
D ] }t          |          |k    r nt          |t                    s/t          |                    d
          t                    r|                    d
          n|}t          |                    d          p@|                    d          p+|                    d          p|                    d          pd                                                                          }|dk    rd}|dvrt          d|v r|                    d          n|                    d                    }|                                sTt          |                    d          p>|                    d          p)|                    d          p|                    d                    }|(||nt!          ||          }||nt#          ||          }||d}|||d<   |                    |           wd d d            n# 1 swxY w Y   n# t
          $ r	 g d d d fcY S w xY w||||fS )Nr   r^   r   )r`   errorsr  r
  r   P   r   r   r   r  r   humanr   >   r   r   systemr  r   r   r&  )r   r   )r   r   r   r   r   r'   r   r   rR   r   r   r   r|   r   r%  r"  minrH  r~   )r   r(  r   summary_titlefirst_tslast_tsfhliner   r  recordsrecordrK  r   r   r   rh  s                    r*   _parse_claude_code_jsonlr6  
  s:   HMHG1$YYsWYY?? .	*2 -* -*x==L00Ezz|| *T**CC    H!#t,, $ G!ggi00DCGGG4D4DG!'3// GGMMOO G(+(A(A#2#(F1;CGGJ<O<OQU1V1V`#''*---\`?5?	@R@RTX5Y5Ybswwy111_bcG% * *F8}}44%fd33 ! 3=fjj>S>SUY3Z3Zf&**Y///`fCswwvp&**V2D2DpPVp[^[b[bci[j[jpnpqqwwyy  B  BDw%#JJJ 7i[^N^N^	8J8J8JdjdndnoxdydyzzG"==?? ! 5,, 1!::k22177;//1 77<00	 B ~)1)922s8R?P?P(/""S"=M=M$(W==D~,.[)OOD))))].	* .	* .	* .	* .	* .	* .	* .	* .	* .	* .	* .	* .	* .	* .	*^  $ $ $4t####$]Hg55sY   N4 3N(A+*N(+
A85N(7A88L$N(N4 (N,,N4 /N,0N4 4OO	max_filesmax_file_bytesprojects_dirr8  r9  c             #   |  K   | !t          |                                           nt                      }|d S 	 |                                rd S |                    d          }|                                r|                                sd S d}t          |                                d           D ]}||k    r d S 	 |                                s|                                s5t          |                                d           D ]}||k    r  d S |                                s1|	                                r|j
                                        dk    rR	 |                                j        |k    rqn# t          $ r Y ~w xY w|dz  }|V  # t          $ r Y w xY wd S # t          $ r Y d S w xY w)	NFstrictr   c                     | j         S r   r%   r)   s    r*   rU   z/_iter_claude_code_jsonl_files.<locals>.<lambda>
  s     r3   r  c                     | j         S r   r?  r@  s    r*   rU   z/_iter_claude_code_jsonl_files.<locals>.<lambda>
  s     r3   z.jsonlr   )r   r   r  
is_symlinkr!  r9   is_dirr  iterdiris_filesuffixr   r    r  r&   )r:  r8  r9  r  yieldedproject_dirr   s          r*   _iter_claude_code_jsonl_filesrI  
  s%     .:.F4((***LmLoLoD|?? 	F||5|)){{}} 	DKKMM 	F!$,,..6F6FGGG 	 	K)##))++ ;3E3E3G3G ";#6#6#8#8>N>NOOO  D)++(( ! !$+BSBSBUBUYaBaBa !99;;.??$ @" ! ! ! !qLGJJJJ    %	 	(    s~   F- >F- /F- (F)F- *-FF- AF!F >F 
F
FFFF- 
F(%F- 'F((F- -
F;:F;r   r/  c                     |r|S | D ]r}|                     d          dk    rWd                    t          |                     d          pd                                                    }|r|d d         c S sdS )Nr   r   r   r   r   r+  zClaude Code Session)rR   r   r   r   )r   r/  rK  r   s       r*   _claude_code_titlerK  
  s      ! !776??f$$88C	 2 2 8b99??AABBD !CRCy     r3   c          	      x   g }t          | ||          pg D ]	}t          |          \  }}}}|st          |          }	|                    i d|	dt	          ||          dt          t                                dddt          |          d|p|p|                                j	        d	|p|p|                                j	        d
|p|p|                                j	        dddddddddt          dt          dddt          ddddi           |                    d d           |S )aS  Read Claude Code JSONL sessions as read-only external-agent rows.

    The bridge is additive and defensive: it skips symlinks, oversized files,
    malformed lines, and per-file errors rather than crashing WebUI session
    listing. Tests pass ``projects_dir`` fixtures so Michael's real ~/.claude is
    never read during test runs.
    r7  r@   r
  r"  r#  zclaude-coderL   r&  rP   r   r  Fr  r  Nr'  r  r  r  external_agentr  r  Tr  c                 Z    |                      d          p|                      d          pdS )Nr   rP   r   rQ   rS   s    r*   rU   z*get_claude_code_sessions.<locals>.<lambda>)  s*    &7 8 8 TAEE,<O<O TST r3   rV   )rI  r6  r  r~   rK  r   r   r   r    r!   r  CLAUDE_CODE_SOURCE_LABELr   )
r:  r8  r9  rT  r   r   r/  r0  r1  r   s
             r*   get_claude_code_sessionsrP    s    H-li`nooousu  5Md5S5S2-7 	%d++ 
#
'-@@
 /1122
 ]	

 S]]
 (EgE1E
 'EXE1E
 wJ(Jdiikk6J
 e
 
 $
 t
 ,
 ,
 .
  4!
" d#
$ %
 
 	 	 	 	( MMTT^bMcccOr3   c                     t          | pd          } |                     t           d          sg S t          |          pg D ]-}t	          |          | k    rt          |          \  }}}}|c S g S )z<Return messages for one read-only Claude Code JSONL session.r   rK   )r   rd   r  rI  r  r6  )r   r:  r   r   _summary_title	_first_ts_last_tss          r*    get_claude_code_session_messagesrU  -  s    
ciR..C>>/22233 	-l;;Ar  "4((C//8PQU8V8V5.)XIr3   c                  x    t           5  t                                           d d d            d S # 1 swxY w Y   d S r   )_CLI_SESSIONS_CACHE_LOCK_CLI_SESSIONS_CACHEclearr2   r3   r*   clear_cli_sessions_cacherZ  :  s~    	! $ $!!###$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $s   /33c                 *    t          j        |           S r   )copydeepcopy)rT  s    r*   _copy_cli_sessionsr^  ?  s    ="""r3   c                  x    	 t          dt          t                              S # t          t          f$ r Y dS w xY w)Nr  r   )rH  r   _CLI_SESSIONS_CACHE_TTL_SECONDSr   r   r2   r3   r*   _cli_sessions_cache_ttl_secondsra  C  sG    3=>>???z"   sss   !$ 99c                     | d S 	 t          t          |                                                               d                    S # t          $ r t          |           cY S w xY w)NFr<  )r   r   r   r!  r'   )r   s    r*   _path_cache_keyrc  J  so    |t4::((**22%2@@AAA   4yys   AA A$#A$c                     | d S 	 t          |                                           }|j        |j        fS # t          $ r Y d S w xY wr   )r   r    st_mtime_nsr  r&   )r   sts     r*   _path_stat_cache_keyrg  S  sW    |t$ZZ__
++   tts   .5 
AAdb_pathc                     t          |           t          t          |  d                    t          t          |  d                    fS )zAReturn a cheap invalidation key for a SQLite DB and WAL sidecars.z-walz-shm)rg  r   )rh  s    r*   _sqlite_file_stat_cache_keyrj  ]  sQ     	W%%TW"2"2"23344TW"2"2"23344 r3   c            
         	 ddl m}  t           |                                                                                       }nk# t
          $ r^ t          t          j        dt          t          dz                                                                                                }Y nw xY w	 ddl m
}  |            }n# t
          $ r d }Y nw xY w|dz  }t                      }t          |          t          |pd          t          |          t          |          t          |          t          |          t          t                    f}||||fS )Nr   r  r  r  r  r  r   )r  r  r   r   r!  r'   ru   r  r   r   r  r  rj  rc  rg  r   )r  r  r  cli_profilerh  r:  	cache_keys          r*   _resolve_cli_sessions_contextrn  f  sw   c777777113344??AAIIKK c c c29]Cy8H4I4IJJKKVVXX``bbc888888--//    J&G466LKK2G#G,,%%\**/00I i77s%   AA A%B,+B,0C CCr  c           	         g }	 |                     t                                 n,# t          $ r t                              dd           Y nw xY w|                                s|S d gfd}t          |t          t          d           D ]}|d         }|d         p|d         }|}|d	         pd
}	|d         }
|
s|	dk    r|                    d          r|	                    d          }t          |          dk    r|d         }	 | dz  dz  }|                                rvdd l}|                    |                                          }|                    dg           D ]4}|                    d          |k    r|                    d          p|
}
 n5n# t          $ r Y nw xY w	 t                              |          }|rt#          |dd           r|j        }
n# t          $ r Y nw xY w|
p|	                                 d}|                    i d|d|dt)          t+                                d|d         pd d|d         p	|d         pdd|d         d|ddddd t-          ||	          r
 |            nd d!|d"|	d#|                    d#          d$|                    d$          d%|                    d%          p|                    d&          d'|                    d'          d(|                    d(          i d)|                    d)          d*|                    d*          d+|                    d+          d,|                    d,          d-|                    d-          d.|                    d.          d/|                    d/          d0|                    d0          d1|                    d1          d2|                    d2          d|                    d          d3|                    d4          d5|                    d5          d6|                    d6          d7|                    d7          d8d           |S )9NzClaude Code session scan failedTr/   c                  F     d         t                       d<    d         S r  )r  )_cron_pid_caches   r*   	_cron_pidz._load_cli_sessions_uncached.<locals>._cron_pid  s)    1%!4!6!6OAq!!r3   )r  logexclude_sourcesidlast_activity
started_atr  clir
  r  r  rK      r   z	jobs.jsonr   jobsr%   z Sessionr@   r"  r#  rL   r  r&  rP   r  Fr  r  r'  r  r  user_idchat_idorigin_chat_id	chat_type	thread_idsession_keyplatformr  r  r  parent_titleparent_sourcerelationship_type_parent_lineage_root_id
end_reasonry  actual_user_message_count_lineage_root_id_lineage_tip_id_compression_segment_countr  )extendrP  r'   r#   r$   r9   r   CLI_VISIBLE_SESSION_LIMITrd   r   r   r   r   r   rR   rz   ru  r   r
  r~   r   r   r
  )r  rh  _cli_profilecli_sessionsrr  rowr   raw_tsr'  _source_titlerg  _job_id
_jobs_path_json
_jobs_data_j_webui_meta_display_titlerq  s                      @r*   _load_cli_sessions_uncachedr    s   LG4667777 G G G6FFFFFG >> 
 fO" " " " "
 2'	   L L $i_%:\): h-(5W 	'V++w0G0G+ IIcNNE5zzQ(
!,v!5!CJ!((** &,,,,%*[[1E1E1G1G%H%H
",.."<"< & &B!vvd||w66)+)A6 %  7 !   D	!44S99K +w{GTBB +$* 	 	 	D	?gmmoo#?#?#? "
#"
^"
 /1122"
 S\)T	"

 S1US9O5PUTU"
 #l+"
 &"
 e"
 "
 g)F)FP))+++D"
 w"
 '"
 #'',//"
 swwy))"
 swwy))FSWW5E-F-F"
  --!"
" --#"
 "
$ 377=11%"
& 
++'"
( cgg&677)"
* CGGN33+"
,  )<!=!=-"
. CGGN33/"
0 SWW_551"
2  )<!=!=3"
4 &sww/H'I'I5"
6 #'',//7"
8 #CGG,B$C$C9"
: !#''*E"F"F;"
< (: ; ;="
> sww'899?"
@ )#''2N*O*OA"
B dC"
 "
 "	 "	 "	 "	H s4   !' &AABF
F+*F+/4G$$
G10G1c                     t                      \  } }}}t                      }t          j                    }|dk    rt          5  t
                              |          }|rA|\  }}||k    rt          |          cddd           S t
                              |d           	 t          | ||          }	nA# t          $ r4}
t                              d||
           g cY d}
~
cddd           S d}
~
ww xY wt          j                    |z   t          |	          ft
          |<   t          |	          cddd           S # 1 swxY w Y   	 t          | ||          S # t          $ r(}
t                              d||
           g cY d}
~
S d}
~
ww xY w)a  Read CLI sessions from the agent's SQLite store and return them as
    dicts in a format the WebUI sidebar can render alongside local sessions.

    Returns empty list if the SQLite DB is missing or any error occurs -- the
    bridge is purely additive and never crashes the WebUI.
    r   NuD   get_cli_sessions() failed — check state.db schema or path (%s): %s)rn  ra  r   	monotonicrW  rX  rR   r^  r  r  r'   r#   rW  )r  rh  rl  rm  ttlr;  rk  
expires_atcached_sessionsrT  _cli_errs              r*   get_cli_sessionsr    s=    4Q3R3R0K+y
)
+
+C
.

C
Qww% 	0 	0(,,Y77F 9.4+
O##-o>>	0 	0 	0 	0 	0 	0 	0 	0 $''	48886{G[YY   ZX   						0 	0 	0 	0 	0 	0 	0 	0   3&"8,,.	* &h//'	0 	0 	0 	0 	0 	0 	0 	0 	0 	0 	0 	0 	0 	0 	0 	0**;MMM   RX	
 	
 	
 						sf   6D=D="B43D=4
C2>C-C2D=-C22>D==EE	E 
F$FFFc                     t          | t                    s| S |                                 }|sd S 	 t          j        |          S # t
          $ r | cY S w xY wr   )r   r   r   r   r   r'   r!  s     r*   _json_loads_if_stringr    sl    eS!! ;;==D tz$   s   A AA)stitch_continuationsr'  r  c          	         	 ddl }n# t          $ r g cY S w xY wt          |t                    r7|r5t	          |          dz  }|                                st                      }nt                      }|                                sg S 	 t          |                    t          |                              5 }|j	        |_
        |                                }|                    d           d |                                D             h d}|                              sg cddd           S g d}dv rdgng }	|	g d	z   fd
|D             z   }
t          |           g}|r|                    d           d |                                D             }h d                    |          rp|                    d| f           i }|                                }|r@t!          |          |t          |d                   <   t          |d                   }|h}t#          d          D ]}|                    |          }|r|                    d          nd}|r||v r n|                    d|f           |                                }|s nt!          |          }||t          |d                   <   t'          ||          s nT|                    dt          |d                              t          |d                   }|                    |           d                    d |D                       }|                    dd                    |
           d| d|           g }|                                D ]}|d         |d         |d         d	}|D ]>}||                                vr||         }|dv r&|dv rt1          |          }|||<   ?|                    d          dk    r5|                    d          r |                    d          s|d         |d<   |                    |           	 ddd           n# 1 swxY w Y   n# t4          $ r g cY S w xY w|S )a$  Read messages for a Hermes session from state.db.

    When *profile* is supplied, reads from that profile's state.db; otherwise
    falls back to the active profile's state.db.  This generic reader works for
    any session source, including WebUI-origin sessions that were later updated
    through another Hermes surface such as the Gateway API Server.  When
    ``stitch_continuations`` is true it preserves the historical CLI/external-agent
    behavior of walking compatible compression/close parent segments before reading
    messages.
    r   Nr  PRAGMA table_info(messages)c                 8    h | ]}t          |d                    S r?  r   rg   r  s     r*   rh   z0get_state_db_session_messages.<locals>.<setcomp>B  $    DDDcS[))DDDr3   >   r   r   r   )tool_call_idr%  	tool_namer  reasoning_detailscodex_reasoning_itemsreasoning_contentcodex_message_itemsru  )r   r   r   c                     g | ]}|v |	S r2   r2   )rg   r   	availables     r*   rm   z1get_state_db_session_messages.<locals>.<listcomp>Q  s%    CjCjCj![\`i[i[iA[i[i[ir3   zPRAGMA table_info(sessions)c                 8    h | ]}t          |d                    S r?  r  r  s     r*   rh   z0get_state_db_session_messages.<locals>.<setcomp>V  s$    KKKSCK 0 0KKKr3   >   r  r  rw  r  z
                        SELECT id, source, started_at, parent_session_id, ended_at, end_reason
                        FROM sessions
                        WHERE id = ?
                        r   r  z
                                SELECT id, source, started_at, parent_session_id, ended_at, end_reason
                                FROM sessions
                                WHERE id = ?
                                z, c              3      K   | ]}d V  dS )r  Nr2   )rg   rK   s     r*   rf  z0get_state_db_session_messages.<locals>.<genexpr>~  s"      $@$@QS$@$@$@$@$@$@r3   z
                SELECT zP, session_id
                FROM messages
                WHERE session_id IN (z=)
                ORDER BY timestamp ASC, id ASC
            r   r   r   Nr   >   r%  r  r  r  r   r  r%   )sqlite3r  r   r   r  r9   r  r   connectRowrow_factorycursorexecutefetchallrp  fetchoner   r2  rR   r   insertr  r   rD   r  r~   r'   )r   r  r'  r  rh  conncurrequiredoptionalid_colselectedsession_chainsession_cols
rows_by_idr  
current_idr  rK   current	parent_id
parent_rowparent_dictplaceholdersmsgsrK  colr  r  s                              @r*   get_state_db_session_messagesr  $  s      			 '3 *G *#G,,z9~~ 	.+--G'))>> 	\W__S\\2233 Y	!t&{D++--CKK5666DDS\\^^DDDI777H$$Y// Y	! Y	! Y	! Y	! Y	! Y	! Y	! Y	!	 	 	H  $y00dVVbF @ @ @@CjCjCjCjxCjCjCjjH XXJM# (19:::KKCLLNNKKKNNNWWXdee %1KK
    "$J,,..C 159#YY
3s4y>>2%(T^^
 *|!&r 1 1A&0nnZ&@&@GLS(]4G(H(H(HY]I#, &	T0A0A %KK!$
 "+   *-J#- & %*.z*:*:K@KJs:d+;'<'<=#;K#Q#Q & %)00C
48H4I4IJJJ),Z-=)>)>J HHZ000099$@$@-$@$@$@@@LKK 		(++  '3  
       D||~~ ! !K"9~!$[!1 
 $ % %C#((**,, HE
** qqq 5e < <$CHH776??f,,1E1E,cggV\oo,"%k"2CKC    #!QY	! Y	! Y	! Y	! Y	! Y	! Y	! Y	! Y	! Y	! Y	! Y	! Y	! Y	! Y	!t    			KsP    /Q# :A.Q(Q# 5LQQ# QQ# QQ# #Q21Q2r  c                   	 ddl }n# t          $ r dddcY S w xY wt          |t                    r7|r5t	          |          dz  }|                                st                      }nt                      }| r|                                sdddS 	 t          |                    t          |                              5 }|j	        |_
        |                                }|                    d           d |                                D             }d|vrdddcddd           S d	|v r|                    d
t          |           f           |                                }|sdddcddd           S t          dt!          |d         pd                    |d         t#          |d         pd          nddcddd           S |                    dt          |           f           |                                }|r%t          dt!          |d         pd                    ndddcddd           S # 1 swxY w Y   dS # t$          $ r dddcY S w xY w)zHReturn a cheap message count/timestamp summary for one state.db session.r   Nr  )rL   r   r  r  c                 8    h | ]}t          |d                    S r?  r  r  s     r*   rh   z/get_state_db_session_summary.<locals>.<setcomp>  r  r3   r@   r   zfSELECT COUNT(*) AS message_count, MAX(timestamp) AS last_message_at FROM messages WHERE session_id = ?rL   r   zCSELECT COUNT(*) AS message_count FROM messages WHERE session_id = ?)r  r  r   r   r  r9   r  r   r  r  r  r  r  r  r  rH  r   r   r'   )r   r'  r  rh  r  r  r  r  s           r*   get_state_db_session_summaryr    sX   < < < <!"s;;;;;< '3 *G *#G,,z9~~ 	.+--G')) <gnn&& <!"s;;;<W__S\\2233 	t&{D++--CKK5666DDS\\^^DDDI9,,)*sCC	 	 	 	 	 	 	 	 i''9XXK  
 llnn H-.3GG	 	 	 	 	 	 	 	" &)CO0D0I,J,J%K%KMPQbMcMouS1B-C-Hq'I'I'Iux !	 	 	 	 	 	 	 	( KK]`cdg`h`h_jkkk,,..CKN!UQC,@,EA(F(F!G!G!GTU#& -	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	4  < < <!"s;;;;;<sj    /I	 AH<I	 *AH<,I	 9AH<?I	 A#H</I	 <I  I	 I I	 	IIc                     | | dk    rdS 	 t          |           }n&# t          t          f$ r t          |           cY S w xY wt          t	          |                    S r  )r   r   r   r   r   )r  r   s     r*   %_normalized_message_timestamp_for_keyr    sn    }r%LL		z"   5zz
 s9~~s     ??c                     t          | t                    sd S |                     d          }||dk    rd S 	 t          |          S # t          t
          f$ r Y d S w xY w)Nr   r   r   )rK  r  s     r*   rG  rG    st    c4   tGGK  E}tU||z"   tts   A AArK  c           
      R   t          | t                    sdt          |           fS |                     d          p|                     d          }|rdt	          |          fS dt	          |                     d          pd          t	          |                     d          pd          t          |                     d                    t	          |                     d	          pd          t	          |                     d
          p|                     d          pd          fS )Nnon_dictru  
message_idlegacyr   r   r   r   r  r  r%   )r   r   reprrR   r   r  )rK  message_identitys     r*   _session_message_merge_keyr    s   c4   'DII&&wwt}}=(=(= 5c"23344CGGFOO!r""CGGI$"%%-cggk.B.BCCCGGN##)r**CGGK  9CGGFOO9r:: r3   c                     t          | t                    st          |           S d                    t	          |                     d          pd                                                    S )Nr   r   r   )r   r   r  r   r   rR   r   rK  s    r*   #_normalized_session_message_contentr    sW    c4   Cyy88C	**0b117799:::r3   r  c                     d                     t          j        dt          | pd                                                              S )Nr   z\w+r   )r   refindallr   casefoldr  s    r*   _loose_session_message_contentr    s8    88BJvs5;B'7'7'@'@'B'BCCDDDr3   c                 j   t          | t                    sdt          |           fS t          |                     d          pd          t          |           t          |                     d          pd          t          |                     d          p|                     d          pd          fS )Nr  r   r   r  r  r%   r   r   r  r   rR   r  r  s    r*   _session_message_content_keyr    s    c4   'DII&&CGGFOO!r""+C00CGGN##)r**CGGK  9CGGFOO9r::	 r3   c                     t          | t                    sdt          |           fS t          |                     d          pd          t          |           fS )Nr  r   r   r  r  s    r*   _session_message_visible_keyr  
  sT    c4   'DII&&CGGFOO!r""+C00 r3   visible_keysc                     i }i }| D ]^}	 |\  }}n# t           t          f$ r Y w xY w|s#|                    |g                               |           t	          |          ||<   _| ||dS )N)rD   by_roleloose_by_key)r   r   r  r~   r  )r  r  r  rW   r   r   s         r*   _build_visible_duplicate_lookupr    s    GL D D	MD'':& 	 	 	H	 	4$$++C000:7CCS WlSSSs   $$visible_keylookupc                 v   | |v r| S | \  }}|sd S |t          |          }d }|                    di                               |g           D ]k\  }}||k    s|s||v s||v r||fc S |t          |          }|                    di                               ||fd          }|r|r||v s||v r||fc S ld S )Nr  r  r   )r  rR   r  )	r  r  r  r   r   loose_contentexisting_roleexisting_contentloose_existings	            r*   _matching_visible_duplicater  "  s)   l""MD' t~0>>M+1::i+D+D+H+Hr+R+R 5 5''=  (8 &&&*:g*E*E!#34444 :7CCMNB77;;]L\<]_abb 	5^ 	5^++~/N/N!#344444r3   c                 &    t          | |          d uS r   )r  )r  r  s     r*   _has_visible_duplicater  :  s    &{LAAMMr3   sidecar_contextstate_messagesc                 J   t          | pg           } t          |pg           }| r|s|S d | D             }d |D             }t          t          |          t          |                    }d}t          |          D ]}d}||z   t          |          k     rk|t          |          k     rX|||z            ||         k    rC|dz  }||z   t          |          k     r(|t          |          k     r|||z            ||         k    C||k    r|}|dk     r|S |}|}	|t          |          k     rU|	t          |          k     rB||	         ||         k    r|dz  }|	dz  }	|t          |          k     r|	t          |          k     B|t          |          k    r
||	d         S ||d         S )a}  Return only state.db rows that are newer than model-facing context.

    `context_messages` is the authoritative model-facing prefix. state.db may
    contain a mirrored copy of that prefix with fresh timestamps, especially for
    LCM/continuation sessions. Appending the whole state transcript to a clean
    sidecar context replays old context into the next runtime prompt.
    c                 ,    g | ]}t          |          S r2   r  r  s     r*   rm   z0state_db_delta_after_context.<locals>.<listcomp>K  s!    MMM033MMMr3   c                 ,    g | ]}t          |          S r2   r  r  s     r*   rm   z0state_db_delta_after_context.<locals>.<listcomp>L  s!    JJJa.q11JJJr3   r   r   rY   N)r|   r.  r   r2  )
r  r  sidecar_keys
state_keys
max_offsetbest_lenoffsetlengthsidecar_indexstate_indexs
             r*   state_db_delta_after_contextr  >  s    ?0b11O..B//N . MM_MMMLJJ>JJJJS&&J88JH
## 	 	VOc,////Z((Vf_-F1CCCaKF	 VOc,////Z((Vf_-F1CCC HH !|| MK
#l++
+
+c*oo0M0Mk"l=&AAAQMq #l++
+
+c*oo0M0M L))))kll++())$$r3   r6  sidecar_messagesc                   t          | pg           } t          |pg           }t          d|i          |s| S | sfd|D             S |S g }t                      }t                      }t                      }g }t                      }i }	d}
| D ]}t          |          |
nt          |
          }
t	          |          }|                    |           |                    t          |                     t          |          }|                    |           |                    |           |	                    |d          dz   |	|<   |	                    |           |	                    |           t          |          }d}i }|D ]}t          |          t	          |          }t          |          }d}|t          |          k     r&||         }||k    st          ||h          rd}|dz  }|r0t          |||          }||                    |d          dz   ||<   k    r||vr|
1/|
k    r)||v rt          |t                    r|dd         dk    s||v rt          |||          }|<|                    |d          }|	                    |d          }||k     r
|dz   ||<   3|d         d	k    r|

|
k    rK|d         d	k    r|                    |           |                    t          |                     |                    |           |	                    |           |S )
zHMerge sidecar/context and state.db messages without deleting local rows.r   Nc                 B    g | ]}t          |          xk    |S r   )rG  )rg   rK  r   watermark_timestamps     r*   rm   z6merge_session_messages_append_only.<locals>.<listcomp>~  sD       "=c"B"BBYO!%888  988r3   r   r   FT)r  r  )r|   rG  rC   rH  r  r  r  r  rR   r~   r  r   r  r  r   r  )r
  r  r6  merged_messagesseen_message_keysseen_content_keysseen_visible_keyssidecar_visible_sequencesidecar_visible_keyssidecar_visible_countsmax_sidecar_timestamprK  rW   r  sidecar_visible_lookupstate_replay_idxskipped_state_visible_countsreplays_sidecar_prefixexpected_visible_keymatched_visible_keyskipped_countsidecar_countr   r  s                         @@r*   "merge_session_messages_append_onlyr  p  sa    ,233..B//N5{DX6YZZ   	*    -    O!55  $ $/44	 1F1NIITWXmoxTyTy!(--c""":3??@@@2377k***  ---.D.H.HVW.X.X[\.\{+ ''444s####<=QRR#%  E$ E$/44	(--2377!&c":;;;;#;<L#M 2226L237 72 *.& A% ! 
	"=$&# #
 #.0445H!LLqP --@A +%///,,, ,1F9XmKmKm'''sE** s2A2w//I/I###9 "
 

 *8<<=PRSTTM2667JANNM},,DQTUDU,-@A Fl""%1%222q6\!!!!#&&&:3??@@@k***s####r3   )prefer_contextr  r  c          	      :   | g S g }|r*t          | dd          }t          |t                    r|r|}|st          | dd          pg }|t          t          | dd                    }|r|rt	          ||          }t          ||t          | dd                    S )zIReturn append-only messages reconciled with state.db for a WebUI session.Nr   r   r@   r6  r	  )r   r   r|   r  r  r  )r   r  r  local_messagesr   s        r*   (reconciled_state_db_messages_for_sessionr"    s     	N ."7,>EE&-- 	.2B 	.-N B *d;;Ar6wwVZ7[7[\\ V. V5nnUU-$W.DdKK   r3   c                     t          | pd                              t           d          rt          |           S t	          | d          S )a  Read messages for a single CLI/external-agent session.

    Preserve tool-call/result and reasoning metadata from the agent state.db so
    CLI-origin transcripts render with the same tool cards as WebUI-native
    sessions. When the requested session is the tip of a compression/CLI-close
    continuation chain, return the stitched full transcript across all segments
    in chronological order. Returns empty list on any error.
    r   rK   T)r  )r   rd   r  rU  r  r  s    r*   get_cli_session_messagesr$     sO     39"~~  $6!9!9!9:: 5/444(4HHHHr3   sincec           
         ddl }ddl}ddl}	 ddlm} t           |                                                                                      }ng# t          $ rZ t           |j	        dt          t          dz                                                                                                }Y nw xY w|dz  }|                                sdS 	 t          |                    t          |                              5 }|j        |_        |                                }	|	                    d| f           |	                                }
ddd           n# 1 swxY w Y   n# t          $ r Y dS w xY wd}d}d}|
D ]}|d	         pd
                                                                }|d         }||	 t-          |t.          t0          f          rt1          |          }nM|j                            t          |                              dd                                                    }||k    rn# t          $ r Y nw xY w|dk    r|r|sn|r	|r|dz  }d}d}|dk    r|rd}|r|r|dz  }|S )a  Count conversation rounds for a session from state.db.

    A "round" = one user message + one agent reply.  Consecutive user
    messages are merged into a single round so that multi-part questions
    don't inflate the count.

    Parameters
    ----------
    sid : str
        Gateway session ID (e.g. ``20260430_151231_7209a0``).
    since : float | None
        Unix timestamp.  If provided, only messages **after** this
        timestamp are counted.

    Returns
    -------
    int
        Number of complete conversation rounds.
    r   Nr  r  r  r  zPSELECT role, timestamp FROM messages WHERE session_id = ? ORDER BY timestamp ASCFr   r   r   r  r  r   r   Tr  )ru   r  r  r  r  r   r   r!  r'   r  r   r   r9   r   r  r  r  r  r  r  r   r   r   r   r   r   r   r   )r   r%  ru   r  r  r  r  rh  r  r  rowsrounds	seen_userseen_agent_after_userr  r   ts_rawts_vals                     r*   count_conversation_roundsr-    sK   ( !           c777777113344??AAIIKK c c c929]Cy8H4I4IJJKKVVXX``bbcJ&G>> q
W__S\\2233 	"t&{D++--CKKb   <<>>D	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	"    qq FI! - -F!r((**0022[! !3fsEl33 ""6]]FF &.<<F++C:: ikk  U?? #    6>> .!6 . .4 .!(-%II[   -(,%  * !Ms]   AA A!B43B4/E& AEE& EE& !E"E& &
E43E4<A?H==
I
	I

   c           
      :   ddl }	 ddl}n# t          $ r Y dS w xY w	 ddlm} t           |                                                                                      }ng# t          $ rZ t           |j	        dt          t          dz                                                                                                }Y nw xY w|dz  }|                                sdS 	 t          |                    t          |                              5 }|                                }|                    d| f           |                    d	| f           |                                 |j        dk    cddd           S # 1 swxY w Y   dS # t          $ r Y dS w xY w)
z{Delete a CLI session from state.db (messages + session row).
    Returns True if deleted, False if not found or error.
    r   NFr  r  r  r  z)DELETE FROM messages WHERE session_id = ?z!DELETE FROM sessions WHERE id = ?)ru   r  r  r  r  r   r   r!  r'   r  r   r   r9   r   r  r  r  commitrowcount)r   ru   r  r  r  rh  r  r  s           r*   delete_cli_sessionr2  g  s    III   uuc777777113344??AAIIKK c c c929]Cy8H4I4IJJKKVVXX``bbcJ&G>> uW__S\\2233 	$t++--CKKCcVLLLKK;cVDDDKKMMM<!#	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$    uusV    
AA A!CC"/F A!E?2F ?FF FF 
FFr~  r   )r   )r  )F)NNNNNN)r	  )r  NNNN)__doc__collectionsr\  r  r  r   loggingru   r  r<   r   r  
contextlibr   pathlibr   
api.configconfigr   r   r   r   r   r	   r
   r   r   r   r   r   r   r   api.workspacer   	api.usager   api.agent_sessionsr   r   r   	getLoggerr  r#   r  r`  r  rW  rX  r   RLockry   r8   r:   r+   r4   r?   r   r   rF   r1   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  rz   r  r6  _INTERRUPTED_NO_OUTPUT_WORDING"_INTERRUPTED_PENDING_RETRY_WORDINGr5  r  r  r  r  r  r  r  r  r|   ri  r  r  r  r  r  r3  r4  r  __annotations__r  r	  r  r  r  r%  r/  r,  rO  rV  r_  rc  re  ri  rl  rs  r|  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r
  r  r  rO  CLAUDE_CODE_MAX_FILESCLAUDE_CODE_MAX_FILE_BYTES!CLAUDE_CODE_MAX_MESSAGES_PER_FILEr$  r  r  r"  r%  r   r6  rI  rK  rP  rU  rZ  r^  ra  rc  rg  rj  rn  r  r  r  r  r  r  rG  r  r  r  r  r  rC   r  r  r  r  r  r"  r$  r-  CONVERSATION_ROUND_THRESHOLDr2  r2   r3   r*   <module>rF     sC   A A A          				 				                                                     
 - , , , , , . . . . . .          
	8	$	$ "% )9>++     $IO%% ,in..  $    *O O O O. . . .& C     &s+ s+ s+ s+l#+ #+ #+ #+ #+ #+L  +$ +4 + + + +  HL   #* PTW[P[    (L L Lq q q  	 	 	8 8 8% % %P   &  ,+ + +w
 w
 w
 w
 w
 w
 w
 w
r
O$ 
O 
O 
O 
O< O $ #) 	6	N	/
	'  ,  $  *   8.2HK   8 # * * ** * Tz	* 
* * * *Z S     .s . . . .     >uT4Z7H    (3 3QU:    0 !. . .
. .
 Tz. 
. . . .bd
 t    23: $    J "	[ [ [Tz[ 	[
 
[ [ [ [x !  ) 24 d3	./ 4 4 4+Y^-- KS KY^ K K K K JN  *-*	   64    $5d 5t 5 5 5 5)$C )$D )$ )$ )$ )$X4T 4 4 4 4( &+y y y #y 
	y y y y~ w
 w w w 
w w w wV ') #Rd R R R RjS      c    "RT R R R R#[ #[ #[ #[ #[LL L L L\L L L L*= = = =~     D S    	d 	DdO 	PS 	 	 	 	T d    <4: <$t* < < < <~<DJ <4 < < < <
$t $ $ $ $D3: D$ D D D D:tDz :d : : : :2C D    D D D DN
 
3 
 
 
 
 *9>++  &4 &D & & & &R '+ & & &t &t & & & &Pc c c c
   #Y^%% ,S , , , ,^# # # # # # # #   	   B # (  - $( ! ' 04$; 0 0 0 0,$ ,3 , , , ,
  $8# 8 8 8 88 Ab 76 76 764 76# 76fklpqulvx{  C  yC  EJ  MQ  EQ  SX  [_  S_  m_  g` 76 76 76 76t ^s  Lf      s
T0A  X[    FI        F!d !C$J !3 ! ! ! !#Yn  Ga # # #4#:+< #SV #  AD #  fj # # # #L
 
s
T8I 
UY 
 
 
 
$ $ $ $
# #$ # # # #    S4Z           8  8  8FaT aD aSW a a a aH($ ( ( ( (V	 	 	 HMVZ v v v v_c v v v vr 26 ,< ,< ,<$ ,< ,< ,< ,<^
 
 
	 	 	D     ;T ;c ; ; ; ;E# E# E E E Ed    d    T#e* T T T T T U #e* VZ]aVa    0N NSZ ND N N N N/%$ /% /%QU /% /% /% /%l 	u u uuu
 
u u u ur (-D   $>BTk	   0IT I I I IS S3 Sut| Ss S S S Sl  " t      r3   