+
    jHàjØ/ ã                   óv  € R t ^ RIt^ RIt^ RIt^ RIt^ RIt^ RIHtHtH	t	H
t
HtHtHt ^ RIHt ^ RIHt ^ RIHtHtHtHtHtHtHtHtHtHtHtHtH t H!t! ^ RI"H#t#H$t$ ]PJ                  ! R4      t&R R	 lt'R
t(]PR                  ! R]PT                  ! ](4       R24      t+],! 0 R3m4      t-]-R0,          t.]PR                  ! R4      t/]PR                  ! R4      t0Rt1Rt2Rt3Rt4RRRR/t5Rt6R4t7R R lt8R R lt9R R lt:R R  lt;R! R" lt<R# R$ lt=R% R& lt>R' R( lt?R) R* lt@R+ R, ltAR-RR.R/R/ R0 lltB ! R1 R24      tCR# )5zòFull-text / trigram / CJK message search and FTS maintenance for SessionDB.

Plain mixin for ``hermes_state.SessionDB`` (no ``__init__``/state of its own).
Must never import hermes_state (cycle); shared constants live in hermes_state_common.
N)ÚAnyÚCallableÚ
CollectionÚDictÚListÚOptionalÚTuple)Údescribe_skill_invocation)Úis_malformed_db_error)ÚFTS_CJK_STALE_KEYÚFTS_SQLÚFTS_STALE_KEYÚFTS_STORAGE_VERSIONÚFTS_TOOL_CONTENT_PREFIX_CHARSÚFTS_TRIGRAM_EXCLUDED_SOURCESÚFTS_TRIGRAM_SQLÚMAX_FTS5_QUERY_CHARSÚSCHEMA_VERSIONÚ_FTS_CJK_TRIGGERSÚescape_likeÚfts_rebuild_admissionÚfts_trigram_session_sqlÚrouted_sessions_setting)Úfts_layout_currentÚfts_layout_driftÚhermes_statec                ó$   € V ^8„  d   QhR\         /# ©é   Úreturn)Úfloat)Úformats   "Ú8/home/ubuntu/.hermes/hermes-agent/hermes_state_search.pyÚ__annotate__r#      s   € ÷ ñ œñ ó    c                 óª   € \        RR4      p  V e   \        V 4      P                  4       R8X  d   R# \        V 4      #   \        \
        3 d     R# i ; i)zU``sessions.search_slow_ms`` for the served profile (default 1000; 0 logs every call).Úsearch_slow_msÚHERMES_SEARCH_SLOW_MSÚ ç     @�@)r   ÚstrÚstripr    Ú	TypeErrorÚ
ValueError)Úvalues    r"   Ú_search_slow_msr/      sS   € ä#Ð$4Ð6MÓN€EðØš¬#¨e«*×*:Ñ*:Ó*<ÀÔ*BˆvÐTÌÈeËÐTøÜ”zÐ"ô Úðús   Ž#= ²
= ½AÁAz+{}():"^@/#&|~[]<>,;!?$=\'Ú[Ú]ÚNEARz"[^"]+"|\S+z"[^"]*"zLm.timestamp, m.tool_name, s.source, s.model, s.started_at AS session_startedzCsubstr(m.content, max(1, instr(m.content, ?) - 40), 120) AS snippetz`(m.content LIKE ? ESCAPE '\' OR m.tool_name LIKE ? ESCAPE '\' OR m.tool_calls LIKE ? ESCAPE '\')zŠ(COALESCE(m.content, '') LIKE ? ESCAPE '\' OR COALESCE(m.tool_name, '') LIKE ? ESCAPE '\' OR COALESCE(m.tool_calls, '') LIKE ? ESCAPE '\')ÚnewestzORDER BY m.timestamp DESC, rankÚoldestzORDER BY m.timestamp ASC, ranka%  WITH target AS (
    SELECT session_id, timestamp, id FROM messages WHERE id IN ({ids})
)
SELECT t.id AS match_id, m.role, m.content
FROM target t JOIN messages m ON m.id IN (
    t.id,
    (SELECT p.id FROM messages p
     WHERE p.session_id = t.session_id AND (p.timestamp, p.id) < (t.timestamp, t.id)
     ORDER BY p.timestamp DESC, p.id DESC LIMIT 1),
    (SELECT n.id FROM messages n
     WHERE n.session_id = t.session_id AND (n.timestamp, n.id) > (t.timestamp, t.id)
     ORDER BY n.timestamp, n.id LIMIT 1)
)
ORDER BY t.id, m.timestamp, m.idc                óZ   € V ^8„  d   QhR\         R\        \        P                  ,          /# )r   Úkeyr   )r*   r   Úsqlite3ÚRow)r!   s   "r"   r#   r#   R   s'   € ÷ Yñ Yœð Y¤¬'¯+©+Õ!6ñ Yr$   c                óD   € V P                  RV34      P                  4       # )z9Point-read one ``state_meta`` row (``None`` when absent).z*SELECT value FROM state_meta WHERE key = ?)ÚexecuteÚfetchone)Úconnr6   s   &&r"   Ú	_meta_rowr=   R   s   € à�<‰<ÐDÀsÀfÓM×VÑVÓXÐXr$   c                ó(   € V ^8„  d   QhR\         RR/# )r   Úkeysr   N©r*   )r!   s   "r"   r#   r#   W   s   € ÷ _ñ _œcð _ dñ _r$   c                 ó^   € V P                  R RP                  R V 4       4       R2V4       R# )z%DELETE FROM state_meta WHERE key IN (Ú,c              3   ó&   "  € T F  pR x € K	  	  R# 5i©Ú?N© ©Ú.0Ú_s   & r"   Ú	<genexpr>Ú_delete_meta.<locals>.<genexpr>X   s   é € ÐATÉtÈ!Å#Ëtùó   ‚Ú)N)r:   Újoin)r<   r?   s   &*r"   Ú_delete_metarO   W   s,   € Ø‡L�LÐ8¸¿¹ÑATÉtÓATÓ9TÐ8UÐUVÐWÐY]Ö^r$   c                ó0   € V ^8„  d   QhR\         R\        /# )r   Úcpr   )ÚintÚbool)r!   s   "r"   r#   r#   [   s   € ÷ 9ñ 9”ð 9œñ 9r$   c                 óˆ   a € \         ;QJ d#    V 3R  l\         4       F  '       g   K   R# 	  R# ! V 3R  l\         4       4      # )c              3   óT   <"  € T F  w  rVSu;8*  ;'       d    V8*  Mu x € K  	  R # 5i©NrF   )rH   ÚloÚhirQ   s   &  €r"   rJ   Ú_is_cjk.<locals>.<genexpr>\   s   øé € Ð8©K¡& "ˆr�R�~Œ~˜2�~‰~«Kùs   ƒ%(TF)ÚanyÚ_CJK_RANGES)rQ   s   fr"   Ú_is_cjkr\   [   s,   ø€ ß‹3Ô8­KÓ8�3Œ3Ð8Š3Ð8ˆ3Ô8­KÓ8Ó8Ð8r$   c                óF   € V ^8„  d   QhR\         R\        \         ,          /# ©r   Ú	raw_queryr   ©r*   Úlist)r!   s   "r"   r#   r#   _   s"   € ÷ Mñ M¤Cð M¬D´­Iñ Mr$   c                 ó€   € V P                  4        Uu. uF  qP                  4       \        9  g   K  VNK!  	  up# u upi rV   )ÚsplitÚupperÚ_FTS_OPERATORS)r_   Úts   & r"   Ú_non_operator_tokensrg   _   s0   € Ø —‘Ô(ÓLÑ(�!¯G©G«I¼^Ñ,K�AˆAÑ(ÑLÐLùÒLs   “;²;c                ó0   € V ^8„  d   QhR\         R\         /# r^   r@   )r!   s   "r"   r#   r#   c   s   € ÷ ñ ¤ð ¬ñ r$   c                óN   € RP                  R V P                  4        4       4      # )zYQuote each non-operator token (neutralising FTS5 special characters), keeping AND/OR/NOT.Ú c              3   ó–   "  € T F?  qP                  4       \        9   d   TMR VP                  R R4      ,           R ,           x € KA  	  R# 5i)Ú"z""N)rd   re   Úreplace)rH   Útoks   & r"   rJ   Ú$_quote_fts_tokens.<locals>.<genexpr>e   s;   é € ð Ù`qÐY\�y‰y‹{œnÔ,‰°#¸¿¹ÀCÈÓ8NÕ2NÐQTÕ2TÔTÓ`qùs   ‚AA	)rN   rc   )r_   s   &r"   Ú_quote_fts_tokensrp   c   s)   € à�8‰8ñ Ø`i×`oÑ`oÔ`qóó ð r$   c                óF   € V ^8„  d   QhR\         R\        \         ,          /# )r   Útermr   r`   )r!   s   "r"   r#   r#   j   s   € ÷ +ñ +”sð +œt¤C�yñ +r$   c                ó0   € R\        V 4       R2.^,          # )z;One ``%term%`` bind per column of ``_LIKE_ANY_COLUMN_SQL``.Ú%)Ú_escape_like)rr   s   &r"   Ú_like_paramsrv   j   s   € à”˜TÓ"Ð# 1Ð%Ð&¨Õ*Ð*r$   c                ó0   € V ^8„  d   QhR\         R\         /# r^   r@   )r!   s   "r"   r#   r#   o   s   € ÷ +ñ +¤Cð +¬Cñ +r$   c                ó(  € RV 9  d   V # . pV P                  4        FY  pVP                  4       \        9   d   VP                  V4       K/  TP                  VP	                  R4      ;'       g    T4       K[  	  RP                  V4      ;'       g    T # )uÈ  Drop the trailing prefix wildcard callers append for ASCII ("nimb" -> "nimb*").

None of the CJK routes can honour that star: the bigram and trigram routes
quote every token before MATCH (so ``*`` matches a literal asterisk) and
LIKE has no ``*`` wildcard at all (only ``%``/``_``). Left in place, every
CJK search arriving from the web/desktop search box â€” which appends the
star to each unquoted token so partial English words match â€” searches for
a term ending in a literal ``*`` and returns nothing (#90636). Only
TRAILING stars go: a star written inside a quoted phrase is the user's
own text, and a token that is ALL stars keeps its original form so it
cannot degrade to a match-everything empty term.
Ú*rj   )rc   rd   re   ÚappendÚrstriprN   )r_   ÚstrippedÚtokens   &  r"   Ú_strip_cjk_wildcardsr~   o   sx   € ð �)ÔØÐØ€HØ—‘Ö"ˆØ�;‰;‹=œNÔ*Ø�O‰O˜EÖ"à�O‰O˜EŸL™L¨Ó-×6Ð6°Ö7ñ	 #ð
 �8‰8�HÓ×*Ð* Ð*r$   c                ó0   € V ^8„  d   QhR\         R\        /# )r   Údecodedr   )r   r*   )r!   s   "r"   r#   r#   ‡   s   € ÷ 7ñ 7œ3ð 7¤3ñ 7r$   c                ón  € \        V \        4      '       d‚   V  Uu. uFE  p\        V\        4      '       g   K  VP                  R4      R8X  g   K3  VP                  RR4      NKG  	  ppRP	                  R V 4       4      P                  4       ;'       g    R# \        V \        4      '       d   V # R# u upi )zVMultimodal part list -> joined text (or the placeholder); str passes through; else ''.ÚtypeÚtextr(   rj   c              3   ó8   "  € T F  q'       g   K  Vx € K  	  R # 5irV   rF   ©rH   rf   s   & r"   rJ   Ú _flatten_text.<locals>.<genexpr>‹   s   é € Ð.¡5˜a«AŸš£5ùs   ‚	�
z[multimodal content])Ú
isinstancera   ÚdictÚgetrN   r+   r*   )r€   ÚpÚpartss   &  r"   Ú_flatten_textrŒ   ‡   s’   € ä�'œ4× Ò Ù,3Óg©G q´zÀ!ÄT×7JÔ"ÈqÏuÉuÐU[Ë}Ð`fÑOfÔ"�—‘�v˜rÖ"©GˆÐgØ�x‰xÑ.¡5Ó.Ó.×4Ñ4Ó6×PÐPÐ:PÐPÜ  ¬#×.Ò.ˆ7Ð6°BÐ6ùò hs   ›B2¸B2ÁB2c                ó4   € V ^8„  d   QhR\         R\        RR/# )r   Únamer.   r   N)r*   r   )r!   s   "r"   r#   r#   �   s!   € ÷ >ñ >œð >¤Cð >¨Dñ >r$   c                 ó¤   € \        V\        4      '       g   \        V\        4      '       g   \        V  R 24      hV^ 8:  d   \	        V  R24      hR# )z must be an integerz must be greater than zeroN)r‡   rS   rR   r,   r-   )rŽ   r.   s   &&r"   Ú_positive_intr�   �   sM   € Ü�%œ×Ò¤j°¼×&<Ò&<Ü˜4˜&Ð 3Ð4Ó5Ð5Ø�„zÜ˜D˜6Ð!;Ð<Ó=Ð=ñ r$   c                óv   € V ^8„  d   QhR\         R\         R\        \         ,          R\         R\         R\         /# )r   Úsnippet_sqlÚfrom_sqlÚwhereÚorder_byÚ	limit_sqlr   r`   )r!   s   "r"   r#   r#   –   s=   € ÷ ñ ¤Cð ´3ð ¼tÄC½yð ÔTWð Ôdgð Ôloñ r$   c                óR   € RV  R\          RV RRP                  V4       RV RV R2# )zIResult-row SELECT shared by the FTS and LIKE routes (SQL text is pinned).zC
            SELECT m.id, m.session_id, m.role,
                   z,
                   z
            FROM zF
            JOIN sessions s ON s.id = m.session_id
            WHERE ú AND z
            z	
        )Ú_SEARCH_SELECT_TAILrN   )r’   r“   r”   r•   r–   s   &&&&&r"   Ú_search_select_sqlrš   –   sX   € ðà�=ð !Ü'Ð(ð )Ø�ð à—<‘< Ó&Ð'ð (ØˆJð ØˆKð 	ð	ð 	r$   Úafter_tsÚ	before_tsc                óB  € V ^8„  d   QhR\         \        ,          R\         R\        R\        \         \        ,          ,          R\        \         \        ,          ,          R\        \         \        ,          ,          R\        \        ,          R\        \        ,          R	R
/	# )r   r”   ÚparamsÚinclude_inactiveÚsource_filterÚexclude_sourcesÚrole_filterr›   rœ   r   N)ra   r*   rS   r   rR   )r!   s   "r"   r#   r#   ¤   s†   € ÷ &ñ &Ü”�9ð&Ü"ð&Ü9=ð&ÜNVÔW[Ô\_ÕW`ÕNað&äœd¤3�iÕ(ð&ä7?ÄÄSÅ	Õ7Jð&ô ”s�mð&ô 08¼­}ð&ð IMñ&r$   c               ó¢  € V'       g   V P                  R4       V P                  R4       Ve=   V P                  RRP                  R V 4       4       R24       VP                  V4       Ve=   V P                  RRP                  R	 V 4       4       R24       VP                  V4       V'       d=   V P                  R
RP                  R V 4       4       R24       VP                  V4       Ve,   V P                  R4       VP                  \        V4      4       Ve.   V P                  R4       VP                  \        V4      4       R# R# )at  Append the visibility/source/role/session-start predicates every search route shares. Live
rows (active=1) AND compaction-archived rows (compacted=1) are discoverable; only
rewind/undo rows (active=0, compacted=0) are hidden. ``after_ts``/``before_ts`` bound
``sessions.started_at`` (inclusive / exclusive) inside the query so LIMIT cannot be
filled by out-of-window hits.z!(m.active = 1 OR m.compacted = 1)z(COALESCE(m.display_kind, '') <> 'hidden'Nzs.source IN (rB   c              3   ó&   "  € T F  pR x € K	  	  R# 5irD   rF   rG   s   & r"   rJ   Ú)_search_filter_clauses.<locals>.<genexpr>²   s   é € Ð-I¹=°a­c»=ùrL   rM   zs.source NOT IN (c              3   ó&   "  € T F  pR x € K	  	  R# 5irD   rF   rG   s   & r"   rJ   r¥   µ   s   é € Ð1O¹¸!µ#»ùrL   zm.role IN (c              3   ó&   "  € T F  pR x € K	  	  R# 5irD   rF   rG   s   & r"   rJ   r¥   ¸   s   é € Ð+E¹°A­C»ùrL   zs.started_at >= ?zs.started_at < ?)rz   rN   ÚextendrR   )r”   rž   rŸ   r    r¡   r¢   r›   rœ   s   &&$$$$$$r"   Ú_search_filter_clausesr©   ¤   s  € ÷ Ø�‰Ð8Ô9à	‡L�LÐ;Ô<ØÒ Ø�‰�} S§X¡XÑ-I¹=Ó-IÓ%IÐ$JÈ!ÐLÔMØ�‰�mÔ$ØÒ"Ø�‰Ð(¨¯©Ñ1O¹Ó1OÓ)OÐ(PÐPQÐRÔSØ�‰�oÔ&ßØ�‰�{ 3§8¡8Ñ+E¹Ó+EÓ#EÐ"FÀaÐHÔIØ�‰�kÔ"ØÒØ�‰Ð(Ô)Ø�‰”c˜(“mÔ$ØÒØ�‰Ð'Ô(Ø�‰”c˜)“nÖ%ñ r$   c                   óö  a € ] tR t^Ât o RtR€t]V 3R lR l4       tV 3R lR ltV 3R lR lt	V 3R lR	 lt
V 3R
 lR ltRtRtR]! R4       R2tR] R2tR]! R4       2tV 3R lR ltV 3R lR ltV 3R lR ltV 3R lR ltV 3R lR ltRR/V 3R lR  lltV 3R! lR" ltV 3R# lR$ ltV 3R% lR& ltV 3R' lR( ltV 3R) lR* lt]V 3R+ lR, l4       t]V 3R- lR. l4       t R/R/V 3R0 lR1 llt!V 3R2 lR3 lt"V 3R4 lR5 lt#V 3R6 lR7 lt$V 3R8 lR9 lt%V 3R: lR; lt&V 3R< lR= lt'R>R?R@RA/V 3RB lRC llt(R�V 3RD lRE llt)R‚V 3RF lRG llt*]V 3RH lRI l4       t+]V 3RJ lRK l4       t,]V 3RL lRM l4       t-]V 3RN lRO l4       t.]V 3RP lRQ l4       t/]V 3RR lRS l4       t0]V 3RT lRU l4       t1V 3RV lRW lt2V 3RX lRY lt3]V 3RZ lR[ l4       t4R\R?R]R?/V 3R^ lR_ llt5V 3R` lRa lt6]V 3Rb lRc l4       t7V 3Rd lRe lt8V 3Rf lRg lt9RƒV 3Rh lRi llt:R„V 3Rj lRk llt;R„V 3Rl lRm llt<V 3Rn lRo lt=V 3Rp lRq lt>R…V 3Rr lRs llt?V 3Rt lRu lt@V 3Rv lRw ltAV 3Rx lRy ltBV 3Rz lR{ ltCR|R?/V 3R} lR~ lltDRtEV tFR?# )†ÚSessionSearchMixinu>   See module docstring â€” mixin for SessionDB (Search cluster).c                ój   <€ V ^8„  d   QhRS[ S[S[,          ,          RS[ S[S[R3,          ,          /# )r   Úfieldsr   .)r   r   r*   Útuple)r!   Ú__classdict__s   "€r"   r#   ÚSessionSearchMixin.__annotate__Ê   s:   ø€ ÷ 
añ 
a©H±ZÁµ_Õ,Eð 
aÉ(ÑSXÑY\Ð^aÐYaÕSbÕJcñ 
ar$   c                ó†  a€ Vf   R# \        V\        4      '       d   \        R4      h\        V4      oSP	                  V P
                  4      pV'       d'   \        RRP                  \        V4      4       24      h\        ;QJ d!    . V3R lV P
                   4       F  NK  	  5# ! V3R lV P
                   4       4      # )z=Validate and canonically order an optional result projection.Nz?search fields must be a collection of field names, not a stringz unknown search result field(s): ú, c              3   ó8   <"  € T F  qS9   g   K  Vx € K  	  R # 5irV   rF   )rH   ÚfieldÚ	requesteds   & €r"   rJ   Ú<SessionSearchMixin._search_message_fields.<locals>.<genexpr>Ô   s   øé € Ð`Ñ(I˜uÐV_ÑM_—U’UÓ(Iùs   ƒ�
)
r‡   r*   r,   ÚsetÚ
differenceÚ_SEARCH_MESSAGE_RESULT_FIELDSr-   rN   Úsortedr®   )Úclsr­   Úunknownrµ   s   && @r"   Ú_search_message_fieldsÚ)SessionSearchMixin._search_message_fieldsÉ   s˜   ø€ ð Š>ÙÜ�fœc×"Ò"ÜÐ]Ó^Ð^Ü˜“Kˆ	Ø×&Ñ& s×'HÑ'HÓIˆßÜÐ?ÀÇ	Á	Ì&ÐQXË/Ó@ZÐ?[Ð\Ó]Ð]ßŒuÔ`¨×(IÒ(IÓ`�uÐ`ˆuÔ`¨×(IÒ(IÓ`Ó`Ð`r$   c                ó   <€ V ^8„  d   QhRR/# ©r   r   NrF   )r!   r¯   s   "€r"   r#   r°   Ö   s   ø€ ÷ Qñ Q¨Dñ Qr$   c                óÆ   € V P                   '       g   R#  V P                  V P                  R7       R#   \         d"   p\        P                  RT4        Rp?R# Rp?ii ; i)uó   One bounded FTS5 merge pass that never fails the already-committed write (a caller
must never replay an ambiguous, possibly-durable write â€” even on the bare
SystemError CPython's sqlite3 layer can raise under cross-thread errmsg scrambling).N)Ú	max_pagesz-FTS incremental merge failed after commit: %s)Ú_fts_enabledÚ_merge_fts_incrementallyÚ_FTS_MERGE_MAX_PAGES_PER_INDEXÚ	ExceptionÚloggerÚwarning)ÚselfÚexcs   & r"   Ú_try_incremental_merge_ftsÚ-SessionSearchMixin._try_incremental_merge_ftsÖ   sV   € ð × × Ð Ùð	QØ×)Ñ)°D×4WÑ4WÐ)ÖXøÜô 	Qô
 �N‰NÐJÈC×PÒPûð	Qús   –4 ´A ¿AÁA c                óF   <€ V ^8„  d   QhRS[ S[S[S[3,          ,          /# r   ©r   rˆ   r*   r   )r!   r¯   s   "€r"   r#   r°   ç   s!   ø€ ÷ 3ñ 3¡H©T±#±s°(­^Õ$<ñ 3r$   c                ó$   € V P                  R4      # )z­Deferred-rebuild progress ``{"pending", "total", "indexed", "percent"}`` or None. Reads
via the pooled reader (not get_meta/self._lock) so search never blocks on the writer.Úfts_rebuild©Ú_rebuild_status©rÉ   s   &r"   Úfts_rebuild_statusÚ%SessionSearchMixin.fts_rebuild_statusç   s   € ð ×#Ñ# MÓ2Ð2r$   c                óF   <€ V ^8„  d   QhRS[ S[S[S[3,          ,          /# r   rÎ   )r!   r¯   s   "€r"   r#   r°   ì   s!   ø€ ÷ 7ñ 7©±±c¹3°hµÕ(@ñ 7r$   c                ó$   € V P                  R4      # )z:CJK-index backfill progress, or None when none is pending.Úfts_cjk_rebuildrÑ   rÓ   s   &r"   Úfts_cjk_rebuild_statusÚ)SessionSearchMixin.fts_cjk_rebuild_statusì   s   € à×#Ñ#Ð$5Ó6Ð6r$   c                óL   <€ V ^8„  d   QhRS[ RS[S[S[ S[3,          ,          /# )r   Úprefixr   )r*   r   rˆ   r   )r!   r¯   s   "€r"   r#   r°   ð   s,   ø€ ÷ xñ x¡cð x©h±t¹CÁ¸Hµ~Õ.Fñ xr$   c                óŠ  € V P                  R V R2V R234      pV Uu/ uF  q3R,          VR,          bK  	  ppVP                  V R24      pVe   \        V4      ^ 8:  d   R# \        V4      \        VP                  V R24      ;'       g    ^ 4      rvRRRVR	VR
\        ^d\        ^dV,          V,          4      4      /# u upi )z5SELECT key, value FROM state_meta WHERE key IN (?, ?)Ú_high_waterÚ	_progressr6   r.   NÚpendingTÚtotalÚindexedÚpercent)Ú	_read_allr‰   rR   Úmin)rÉ   rÜ   ÚrowsÚrÚmetaÚ
high_waterrá   Úprogresss   &&      r"   rÒ   Ú"SessionSearchMixin._rebuild_statusð   sÓ   € Ø�~‰~ÐUØ"( ¨Ð5¸&¸ÀÐ7KÐLóNˆá.2Ó3©d¨�%•˜!˜G�*Ò$©dˆÐ3Ø—X‘X  ¨Ð4Ó5ˆ
ØÒ¤ Z£°AÔ!5ÙÜ˜j›/¬3¨t¯x©x¸6¸(À)Ð8LÓ/M×/RÐ/RÐQRÓ+SˆxØ˜4 ¨%°¸HÀiÔQTÐUXÔZ]Ð^aÐdlÕ^lÐotÕ^tÓZuÓQvÐwÐwùò 4s   ŸC zãINSERT INTO {table}(rowid, content, tool_name, tool_calls) SELECT m.id, m.content, m.tool_name, m.tool_calls FROM messages m WHERE m.id > ? AND m.id <= ? {extra}AND NOT EXISTS (SELECT 1 FROM {table}_docsize d WHERE d.id = m.id)a4  INSERT INTO messages_fts(rowid, content, tool_name, tool_calls) SELECT m.id, CASE WHEN m.role = 'tool' THEN substr(COALESCE(m.content, ''), 1, ?) ELSE m.content END, m.tool_name, m.tool_calls FROM messages m WHERE m.id > ? AND m.id <= ? AND NOT EXISTS (SELECT 1 FROM messages_fts_docsize d WHERE d.id = m.id)zÍINSERT INTO messages_fts_trigram(rowid, content, tool_name) SELECT m.id, m.content, m.tool_name FROM messages m JOIN sessions s ON s.id = m.session_id WHERE m.id > ? AND m.id <= ? AND m.role <> 'tool' AND ÚszP AND NOT EXISTS (SELECT 1 FROM messages_fts_trigram_docsize d WHERE d.id = m.id)z„INSERT INTO {table}(rowid, content, tool_name, tool_calls) SELECT id, CASE WHEN role = 'tool' THEN substr(COALESCE(content, ''), 1, zW) ELSE content END, tool_name, tool_calls FROM messages WHERE id > ? AND id <= ?{extra}c                ó   <€ V ^8„  d   QhRR/# rÀ   rF   )r!   r¯   s   "€r"   r#   r°     s   ø€ ÷ 	Oñ 	O Tñ 	Or$   c                óÐ   € V P                   R3.pV P                  '       d   VP                  V P                  R34       V P	                  RV4       \
        P                  R4       R# )a9  Finalize the deferred rebuild: boundary sweep + clear markers. The sweep is cheap
insurance against a write that slipped between high_water capture and trigger
activation. The trigram half is gated on ``_trigram_available``: without the
tokenizer/table an unconditional INSERT raises and aborts the whole rebuild.TFrÐ   u7   Deferred FTS rebuild complete â€” all messages indexed.N)Ú_BASE_BOUNDARY_SWEEP_SQLÚ_trigram_availablerz   Ú_TRIGRAM_BOUNDARY_SWEEP_SQLÚ_rebuild_finishrÇ   Úinfo)rÉ   Úsweepss   & r"   Ú_fts_rebuild_finishÚ&SessionSearchMixin._fts_rebuild_finish  sW   € ð
 ×0Ñ0°$Ð7Ð8ˆØ×"×"Ð"Ø�M‰M˜4×;Ñ;¸UÐCÔDØ×Ñ˜]¨FÔ3Ü�‰ÐMÖNr$   c                ó   <€ V ^8„  d   QhRR/# rÀ   rF   )r!   r¯   s   "€r"   r#   r°   *  s   ø€ ÷ Oñ O¨ñ Or$   c                ó¢   € V P                   P                  RRR7      pV P                  RVR3.4       RV n        \        P                  R4       R# )	z?Boundary sweep + clear the cjk markers; index becomes servable.Úmessages_fts_cjkzAND m.role <> 'tool' ©ÚtableÚextrarØ   FTu7   CJK FTS index backfill complete â€” serving CJK search.N)Ú_BOUNDARY_SWEEP_SQLr!   rò   Ú_fts_cjk_availablerÇ   ró   )rÉ   Úsweeps   & r"   Ú_fts_cjk_rebuild_finishÚ*SessionSearchMixin._fts_cjk_rebuild_finish*  sL   € à×(Ñ(×/Ñ/Ð6HÐPgÐ/ÓhˆØ×ÑÐ.°%¸°Ð0@ÔAØ"&ˆÔÜ�‰ÐMÖNr$   c                óP   <€ V ^8„  d   QhRS[ RS[S[S[ S[3,          ,          RR/# )r   rÜ   Ú
sweep_sqlsr   N)r*   ra   r®   rS   )r!   r¯   s   "€r"   r#   r°   1  s1   ø€ ÷ !ñ !¡cð !±t¹EÁ#ÁtÀ)Õ<LÕ7Mð !ÐRVñ !r$   c                ó:   aa€ VV3R lpV P                  V4       R# )z¤Sweep a generous window around the high-water boundary, then clear the markers.
``(sql, bounded)``: a bounded sweep takes the tool-content prefix_chars param first.c                 óþ   <€ \        V S R 24      pVeV   \        V^ ,          4      pS F=  w  r4V'       d   \        3MRpV P                  V. VOVR,
          NVR,           N54       K?  	  \	        V S R 2S R24       R# )rÞ   Niè  rß   rF   )r=   rR   r   r:   rO   )r<   Úhw_rowÚhwÚsqlÚboundedrž   rÜ   r  s   &     €€r"   Ú_doÚ/SessionSearchMixin._rebuild_finish.<locals>._do4  s„   ø€ Ü˜t¨ x¨{Ð%;Ó<ˆFØÒ!Ü˜ �“^�Û$.‘L�CßAHÔ;Ñ=Èb�FØ—L‘L Ð&E¨Ð&E°°Tµ	Ð&E¸2À½9Ñ&EÖFñ %/ô ˜ & ¨Ð5¸&¸ÀÐ7KÖLr$   N©Ú_execute_write)rÉ   rÜ   r  r
  s   &ff r"   rò   Ú"SessionSearchMixin._rebuild_finish1  s   ù€ ö	Mð 	×Ñ˜CÖ r$   c                ó    <€ V ^8„  d   QhRS[ /# r   ©rS   )r!   r¯   s   "€r"   r#   r°   >  s   ø€ ÷ 
[ñ 
[¡$ñ 
[r$   c                ó   € V P                   '       g   R# V P                  P                  RRR7      .pV P                  '       d   VP	                  V P
                  4       V P                  RVRV P                  RR7      # )	zÄBackfill one chunk of the deferred FTS rebuild; True while work remains. Chunks are
claimed atomically inside the write transaction, so concurrent processes interleave
instead of duplicating rows.FÚmessages_ftsr(   rú   rÐ   z)FTS rebuild chunk failed (will retry): %sT)Úfail_msgÚfinishÚfinish_when_empty)rÃ   Ú_CHUNK_INSERT_SQLr!   rð   rz   Ú_TRIGRAM_CHUNK_INSERT_SQLÚ_rebuild_steprõ   )rÉ   Úinsertss   & r"   Úfts_rebuild_stepÚ#SessionSearchMixin.fts_rebuild_step>  s|   € ð × × Ð ÙØ×)Ñ)×0Ñ0°~ÈRÐ0ÓPÐQˆØ×"×"Ð"Ø�N‰N˜4×9Ñ9Ô:Ø×!Ñ! -°ÐCnØ)-×)AÑ)AÐUYð "ó [ð 	[r$   c                ó    <€ V ^8„  d   QhRS[ /# r   r  )r!   r¯   s   "€r"   r#   r°   J  s   ø€ ÷ \ñ \¡dñ \r$   c                óÈ   € V P                   '       d   V P                  '       g   R# V P                  P                  RRR7      pV P	                  RV.V P
                  RR7      # )z=Backfill one chunk of the CJK index. True while work remains.Frù   z AND role <> 'tool'rú   rØ   z-CJK FTS rebuild chunk failed (will retry): %s)r  r  )rÃ   Ú_fts_cjk_loadedr  r!   r  r   )rÉ   Úinserts   & r"   Úfts_cjk_rebuild_stepÚ'SessionSearchMixin.fts_cjk_rebuild_stepJ  se   € à× × Ð ¨×(<×(<Ð(<ÙØ×'Ñ'×.Ñ.Ð5GÐOdÐ.ÓeˆØ×!Ñ!Ð"3°f°XÀd×FbÑFbØ+Zð "ó \ð 	\r$   r  Fc          
      óH   <€ V ^8„  d   QhRS[ RS[S[ ,          RS[ RS[RS[/# )r   rÜ   Úinsert_sqlsr  r  r   )r*   ra   rS   )r!   r¯   s   "€r"   r#   r°   R  s6   ø€ ÷ 'ñ '¡Cð '±d¹3µið 'Écð 'Ù)-ð'Ù:>ñ'r$   c               ó¸  aaaa€ V P                  S R24      pVf   R# \        V4      oV P                  oVVVV3R lp V P                  V4      pTRJ dC   T P                  S4      p
T'       d   S^ 8:  g   T
e   T
R,          T
R,          8¼  d   T! 4        R# \        T4      #   \        P
                   d!   p	\        P                  Y94        Rp	?	R# Rp	?	ii ; i)z«Shared chunk engine for the base and CJK deferred backfills. ``finish_when_empty``
finalizes a high_water <= 0 marker (empty messages table) instead of leaving it pending.rÞ   NFc                 ó  <€ \        V S R 24      pVf   R# \        V^ ,          4      pVS8¼  d   R# \        VS,           S4      pS F  pV P                  WBV34       K  	  V P                  R\	        V4      S R 234       VS8  # )rß   Fz-UPDATE state_meta SET value = ? WHERE key = ?)r=   rR   rå   r:   r*   )	r<   Úrowrê   rd   r  Úchunkré   r#  rÜ   s	   &    €€€€r"   r
  Ú-SessionSearchMixin._rebuild_step.<locals>._do\  s“   ø€ ô ˜D V H¨IÐ"6Ó7ˆCØŠ{ÙÜ˜3˜q�6“{ˆHØ˜:Ô%Ùä˜ 5Õ(¨*Ó5ˆEÛ"�Ø—‘˜S¨UÐ"3Ö4ñ #ð �L‰LÐHÌ3ÈuË:ÐZ`ÐYaÐajÐWkÐJlÔmØ˜:Ñ%Ð%r$   Trâ   rá   )
Úget_metarR   Ú_FTS_REBUILD_CHUNK_ROWSr  r7   ÚOperationalErrorrÇ   ÚdebugrÒ   rS   )rÉ   rÜ   r#  r  r  r  Úhigh_water_rawr
  ÚmorerÊ   Ústatusr'  ré   s   &ff$$$     @@r"   r  Ú SessionSearchMixin._rebuild_stepR  sÈ   û€ ð Ÿ™¨&¨°Ð'=Ó>ˆØÒ!ÙÜ˜Ó(ˆ
Ø×,Ñ,ˆ÷	&ð 	&ð"	Ø×&Ñ& sÓ+ˆDð �5‹=Ø×)Ñ)¨&Ó1ˆFß! j°A¤oØÒ" v¨iÕ'8¸FÀ7½OÔ'Ká”ÙÜ�D‹zÐøô ×'Ñ'ô 	Ü�L‰L˜Ô'Ýûð	ús   Á B$ Â$CÂ9CÃCc                ó    <€ V ^8„  d   QhRS[ /# r   r  )r!   r¯   s   "€r"   r#   r°   {  s   ø€ ÷ >ñ >©$ñ >r$   c           	     ó  a aa€ S P                  4       ;_uu_ 4       pVP                  RS P                  P                  RR4      R,           34      P	                  4        Uu. uF  q"^ ,          NK  	  ppRRR4       X'       g   R# V^ ,          oVV V3R lpRR V3R	 lllo \        S P                  V4      4      # u upi   + '       g   i     LV; i  \        P                   d"   p\        P                  R
T4        Rp?R# Rp?ii ; i)u¸  Tear down one chunk of a demoted v22 FTS shadow table (a PLAIN table now); True while
work remains. INTEGER single-column-key tables drain with a high-water marker so
each chunk's scan is bounded (restarting the scan was O(nÂ²)); compound-key tables
keep the chunked ``LIMIT`` delete â€” they are small by construction.

Single-column-key trash tables (the common shape â€” FTS shadow tables carry a rowid/integer PK) are
drained with a high-water marker mirroring :meth:`fts_rebuild_step`: each chunk deletes only rows
after the previously-drained key, so the per-chunk scan is bounded instead of re-scanning from the
start of the table every chunk (O(nÂ²) total on large trash tables, #79324).
zNSELECT name FROM sqlite_master WHERE type = 'table' AND name LIKE ? ESCAPE '\'rI   z\_rt   NFc                 ó~  <€ V P                  R S R24       Uu. uF<  q^,          ^ 8”  g   K  V^,          V^,          ;'       g    RP                  4       3NK>  	  ppV'       d   RP                  R V 4       4      MRp\        V4      ^8X  dæ   V^ ,          ^,          R8X  dÑ   RS R2p\	        W4      pVe   \        V^ ,          4      M^ pV P                  R	V R
S RV RV RSP                   2
V34      P                  4       pV'       g	   S
! W4      # VR,          ^ ,          pV P                  RS RV RV R2Wh34      p	V	P                  ^ 8”  d   SP                  V\        V4      V R7       R# V P                  RS RV RV R
S RSP                   R24      p	V	P                  ^ 8X  d	   S
! V 4      # R# u upi )zPRAGMA table_info(rM   r(   r²   c              3   ó*   "  € T F	  w  rVx € K  	  R # 5irV   rF   )rH   rŽ   Ú_typs   &  r"   rJ   ÚKSessionSearchMixin._fts_teardown_trash_step.<locals>._do.<locals>.<genexpr>‘  s   é € Ð;±7¡Z T�D³7ùs   ‚ÚrowidÚINTEGERÚfts_teardown_rß   zSELECT z FROM z WHERE z > ? ORDER BY z LIMIT zDELETE FROM z	 > ? AND z <= ?©ÚcursorTz WHERE (z) IN (SELECT éÿÿÿÿ)r:   rd   rN   Úlenr=   rR   r*  ÚfetchallÚrowcountÚset_metar*   )r<   rç   Úpk_infor6   Ú
marker_keyr&  ré   Ú
upper_rowsrd   ÚcurÚ_droprÉ   Útbls   &         €€€r"   r
  Ú8SessionSearchMixin._fts_teardown_trash_step.<locals>._do�  sÏ  ø€ Ø=A¿\¹\ÐL^Ð_bÐ^cÐcdÐJeÔ=fÓsÑ=f¸ÐlmÕjnÐqrÑjrÔ3˜˜!�˜q �tŸz˜z r×0Ñ0Ó2Ó3Ñ=fˆGÐsß?F�$—)‘)Ñ;±7Ó;Ô;ÈGˆCÜ�7‹|˜qÔ  W¨Q¥Z°¥]°iÔ%?ð  -¨S¨E°Ð;�
Ü Ó1�Ø,/ªOœS  Q¥œ[À�
à!Ÿ\™\Ø˜c˜U &¨¨¨W°S°E¸ÈÀuÈGÐTX×TpÑTpÐSqÐrØ�Mó÷ ‘(“*ð ÷ "Ù  Ó2Ð2Ø" 2� qÕ)�Ø—l‘l \°#°°g¸c¸UÀ)ÈCÈ5ÐPUÐ#VÐYcÐXkÓl�Ø—<‘< !Ô#Ø—M‘M *¬c°%«jÀ�MÔFÙð —,‘,Ø˜s˜e 8¨C¨5°¸c¸UÀ&ÈÈÈWÐUY×UqÑUqÐTrÐrsÐtóˆCð #&§,¡,°!Ô"3‘5˜“;Ð=¸Ð=ùò7 ts   ™F:­F:ÁF:c                óF   € V ^8„  d   QhR\         \        ,          R\        /# )r   rB  r   )r   r*   rS   )r!   s   "r"   r#   ÚASessionSearchMixin._fts_teardown_trash_step.<locals>.__annotate__­  s   € ÷ 	ñ 	¤H¬S¥Mð 	¼Tñ 	r$   c                óz   <€ V P                  RS 24       Ve   \        W4       \        P                  RS4       R# )uA   Drained â€” the DROP is cheap now. True: re-check for more trash.zDROP TABLE IF EXISTS z"Old FTS shadow table %s torn down.T)r:   rO   rÇ   ró   )r<   rB  rF  s   &&€r"   rE  Ú:SessionSearchMixin._fts_teardown_trash_step.<locals>._drop­  s7   ø€ à�L‰LÐ0°°Ð6Ô7ØÒ%Ü˜TÔ.Ü�K‰KÐ<¸cÔBÙr$   z0FTS trash teardown chunk failed (will retry): %sTrV   )Ú	_read_ctxr:   Ú_FTS_TRASH_PREFIXrm   r>  rS   r  r7   r+  rÇ   r,  )rÉ   r<   rç   Útrashr
  rÊ   rE  rF  s   f     @@r"   Ú_fts_teardown_trash_stepÚ+SessionSearchMixin._fts_teardown_trash_step{  sç   ú€ ð �^‰^×Ô Ø#'§<¡<ØaØ×'Ñ'×/Ñ/°°UÓ;¸cÕAÐCó$÷ ‰h‹jð$ó ñ $˜a�q—T�Tñ $ˆEð ÷ ÷
 ÙØ�A�hˆ÷	>÷<	ò 	ð	Ü˜×+Ñ+¨CÓ0Ó1Ð1ùò_÷ ×ûôb ×'Ñ'ô 	Ü�L‰LÐKÈSÔQÝûð	ús6   œACÁ!B=Á2CÂ#C Â=CÃC	ÃDÃ*DÄDc                ó   <€ V ^8„  d   QhRR/# rÀ   rF   )r!   r¯   s   "€r"   r#   r°   »  s   ø€ ÷ 0ñ 0¨ñ 0r$   c                ó†   € V P                   '       g   R# R pV P                  V4      '       d   V P                  4        R# R# )z¦From-scratch rebuild of a stale cjk index (triggers were dropped, gap extent unknown):
drop table + triggers, clear the breadcrumb, recreate (fresh backfill markers).Nc                 óØ   € \        V \        4      f   R# \         F  pV P                  RV 24       K  	  V P                  R4       V P                  R4       \	        V \        RR4       R# )NFzDROP TRIGGER IF EXISTS z%DROP TABLE IF EXISTS messages_fts_cjkz(DROP VIEW IF EXISTS messages_fts_cjk_srcÚfts_cjk_rebuild_high_waterÚfts_cjk_rebuild_progressT)r=   r   r   r:   rO   )r<   Útrigs   & r"   r
  Ú7SessionSearchMixin._fts_cjk_reset_if_stale.<locals>._doÁ  s_   € Ü˜Ô0Ó1Ò9Ùß)�Ø—‘Ð6°t°fÐ=Ö>ñ *à�L‰LÐ@ÔAØ�L‰LÐCÔDÜ˜Ô0Ð2NÐPjÔkÙr$   )r  r  Ú_ensure_cjk_schema_committed©rÉ   r
  s   & r"   Ú_fts_cjk_reset_if_staleÚ*SessionSearchMixin._fts_cjk_reset_if_stale»  s<   € ð ×#×#Ð#Ùò	ð ×Ñ˜s×#Ò#à×-Ñ-Ö/ñ $r$   c                ó   <€ V ^8„  d   QhRR/# rÀ   rF   )r!   r¯   s   "€r"   r#   r°   Î  s   ø€ ÷  ñ  ¨dñ  r$   c                óÒ   € V P                   ;_uu_ 4        V P                  V P                  4       V P                  P                  4        R R R 4       R #   + '       g   i     R # ; irV   )Ú_lockÚ_ensure_fts_cjk_schemaÚ_connÚcommitrÓ   s   &r"   rX  Ú/SessionSearchMixin._ensure_cjk_schema_committedÎ  s9   € Ø�Z�Z‹ZØ×'Ñ'¨¯
©
Ô3Ø�J‰J×ÑÔ÷ �Z�ZŠZús   •6AÁA&	c                ó    <€ V ^8„  d   QhRS[ /# r   r  )r!   r¯   s   "€r"   r#   r°   Ó  s   ø€ ÷ 	ñ 	¹tñ 	r$   c                óî   €  VP                  R4      P                  4       ^ ,          '       g   R# VP                  R4      P                  4       ^ ,          '       * #   \        P                   d     R# i ; i)a  True when the base FTS table indexes nothing while ``messages`` has rows (post-demote
empty-index shape). Caller holds ``self._lock``. docsize is the authoritative "is this
rowid indexed" surface; EXISTS not COUNT(*) because this runs on every writable open.z%SELECT EXISTS(SELECT 1 FROM messages)Fz1SELECT EXISTS(SELECT 1 FROM messages_fts_docsize))r:   r;   r7   r+  ©rÉ   r<   s   &&r"   Ú'_fts_external_index_empty_with_messagesÚ:SessionSearchMixin._fts_external_index_empty_with_messagesÓ  sc   € ð	Ø—<‘<Ð GÓH×QÑQÓSÐTU×VÔVÙØ—|‘|Ð$WÓX×aÑaÓcÐdeÕfÔfÐføÜ×'Ñ'ô 	Úð	ús   ‚+A °*A ÁA4Á3A4c                ó   <€ V ^8„  d   QhRR/# rÀ   rF   )r!   r¯   s   "€r"   r#   r°   Þ  s   ø€ ÷ 
Dñ 
D°ñ 
Dr$   c                óž   € \        VR4      f?   V P                  V4      '       g   V P                  V4       V P                  RRVR7       R# R# )a°  high_water without progress: fts_rebuild_step reads missing progress as "done by
another process" and optimize would no-op then stamp. Reset to known-empty, re-seed.
Truncation goes through FTS5 ``'delete-all'`` (a plain DELETE is O(rows) and corrupts
the index when indexed rows diverged from ``messages``); the backfill worker replays
without an anti-join, so it needs a known-empty index. A missing docsize table counts
as empty.Úfts_rebuild_progressNÚ0r:  )r=   Ú_fts_index_known_emptyÚ_reset_fts_index_to_emptyr@  re  s   &&r"   Ú_reseed_missing_progressÚ+SessionSearchMixin._reseed_missing_progressÞ  sI   € ô �TÐ1Ó2Ò:Ø×.Ñ.¨t×4Ò4Ø×.Ñ.¨tÔ4Ø�M‰MÐ0°#¸dˆMÖCñ ;r$   c                ó    <€ V ^8„  d   QhRS[ /# r   r  )r!   r¯   s   "€r"   r#   r°   ë  s   ø€ ÷ ñ ©ñ r$   c                ó    €  \        V P                  R4      P                  4       ^ ,          4      ^ 8H  #   \        P                   d     R# i ; i)zZTrue when the base external-content index holds no rows (a missing table counts as empty).z)SELECT COUNT(*) FROM messages_fts_docsizeT)rR   r:   r;   r7   r+  )r<   s   &r"   rl  Ú)SessionSearchMixin._fts_index_known_emptyê  sI   € ð	Ü�t—|‘|Ð$OÓP×YÑYÓ[Ð\]Õ^Ó_ÐcdÑdÐdøÜ×'Ñ'ô 	Úð	ús   ‚14 ´AÁAc                ó   <€ V ^8„  d   QhRR/# rÀ   rF   )r!   r¯   s   "€r"   r#   r°   ó  s   ø€ ÷ Oñ O¨4ñ Or$   c           
     óÚ   € R FP  p\         P                  ! \        P                  4      ;_uu_ 4        V P	                  RV RV R24       RRR4       KR  	  R#   + '       g   i     Kg  ; i)z©Truncate the v23 external-content tables via FTS5 ``'delete-all'`` (O(1); a plain DELETE is
O(rows) and corrupts the index when indexed rows diverged from ``messages``).úINSERT INTO Ú(z) VALUES('delete-all')N)r  Úmessages_fts_trigram)Ú
contextlibÚsuppressr7   r+  r:   )r<   rF  s   & r"   rm  Ú,SessionSearchMixin._reset_fts_index_to_emptyò  sS   € ó <ˆCÜ×$Ò$¤W×%=Ñ%=×>Õ>Ø—‘˜|¨C¨5°°#°Ð6LÐMÔN÷ ?Ñ>ó <ß>×>Ð>ús   ²AÁA*Úforcec                ó&   <€ V ^8„  d   QhRS[ RS[/# )r   r{  r   )rS   rR   )r!   r¯   s   "€r"   r#   r°   ú  s   ø€ ÷ ñ ¹ð Éñ r$   c               ó<  € \        VR4      pVe,   V'       g$   V P                  V4       \        V^ ,          4      # VP                  R4      P	                  4       ^ ,          pV P                  R\        V4      VR7       V P                  RRVR7       \        V4      # )zãWrite ``fts_rebuild_high_water`` / ``fts_rebuild_progress`` for a full backfill; returns
the high-water id. Without ``force`` an existing high_water only gets a missing
progress key repaired. Caller holds the write transaction.Úfts_rebuild_high_waterz)SELECT COALESCE(MAX(id), 0) FROM messagesr:  rj  rk  )r=   rn  rR   r:   r;   r@  r*   )rÉ   r<   r{  Úexisting_hwr  s   &&$  r"   Ú_seed_fts_rebuild_markersÚ,SessionSearchMixin._seed_fts_rebuild_markersú  s‡   € ô   Ð&>Ó?ˆØÒ"¯5Ø×)Ñ)¨$Ô/Ü�{ 1•~Ó&Ð&Ø�\‰\ÐEÓF×OÑOÓQÐRSÕTˆØ�‰Ð.´°B³ÀˆÔEØ�‰Ð,¨c¸$ˆÔ?Ü�2‹wˆr$   c                ó   <€ V ^8„  d   QhRR/# rÀ   rF   )r!   r¯   s   "€r"   r#   r°     s   ø€ ÷ !ñ !¨dñ !r$   c                ó6   a € V 3R lpS P                  V4       R# )uk  Heal interrupted demote/backfill bookkeeping before optimize runs: orphan high_water
gets progress re-seeded; an empty external index with messages and no markers gets
a full backfill claim. Never invents markers on a still-legacy inline DB â€” optimize
would then skip demote and INSERT against the inline table forever.

Covers two post-#65798 failure classes:
c                 óì   <€ \        V R 4      e   SP                  V 4       R# SP                  V 4      '       d   R# SP                  V 4      '       d"   \	        V R4       SP                  V RR7       R# R# )r~  NÚfts_storage_versionT©r{  )r=   rn  Ú_db_has_legacy_inline_ftsrf  rO   r€  )r<   rÉ   s   &€r"   r
  Ú<SessionSearchMixin._repair_optimize_bookkeeping.<locals>._do  sk   ø€ Ü˜Ð7Ó8ÒDØ×-Ñ-¨dÔ3ÙØ×-Ñ-¨d×3Ò3ÙØ×;Ñ;¸D×AÒAÜ˜TÐ#8Ô9Ø×.Ñ.¨t¸4Ð.Ö@ñ Br$   Nr  rY  s   f r"   Ú_repair_optimize_bookkeepingÚ/SessionSearchMixin._repair_optimize_bookkeeping  s   ø€ õ	Að 	×Ñ˜CÖ r$   c                ó    <€ V ^8„  d   QhRS[ /# r   r  )r!   r¯   s   "€r"   r#   r°     s   ø€ ÷ ñ ©ñ r$   c                óø  € V P                   '       d   V P                  '       d   R# V P                  4       ;_uu_ 4       pV P                  V4      ;'       g…    \	        VR4      RJ;'       go    V P
                  ;'       d1    \	        VR4      RJ;'       gE    \	        V\        4      RJ;'       g+    V P                  V4      ;'       g    V P                  V4       uuRRR4       #   + '       g   i     R# ; i)a6  True when `optimize_fts_storage()` has work: legacy inline FTS or a v23 trigram still
carrying ``tool_calls`` (``_db_needs_fts_storage_upgrade``), an interrupted optimize
(markers/trash), a CJK backfill on this tokenizer-capable host, or an empty external
index without markers. False when FTS5 is unavailable.Fr~  NrT  )	rÃ   Ú	read_onlyrL  Ú_db_needs_fts_storage_upgrader=   r  r   Ú_has_fts_trashrf  re  s   & r"   Úfts_optimize_availableÚ)SessionSearchMixin.fts_optimize_available  sí   € ð
 × × Ð  D§N§N NÙØ�^‰^×Ô à×2Ñ2°4Ó8÷ Fð FÜ˜TÐ#;Ó<ÀDÐH÷Fð Fð ×(Ñ(÷ ð Ü˜dÐ$@ÓAÈÐM÷	FðFÜ  Ô'8Ó9ÀÐE÷Fð Fð ×&Ñ& tÓ,÷Fð Fð ×?Ñ?ÀÓEð
÷ ××Óús)   ¿.C(Á.C(ÂC(ÂC(Â2C(ÃC(Ã(C9	c                ó    <€ V ^8„  d   QhRS[ /# r   ©rR   )r!   r¯   s   "€r"   r#   r°   .  s   ø€ ÷ (ñ (©Sñ (r$   c                ój   a € V 3R lp\        S P                  V4      4      pS P                  R4       V# )u�  Demote upgrade-eligible FTS vtables and stage their shadow tables for chunked
teardown; returns MAX(messages.id) as the rebuild high water. O(1) schema surgery
â€” the heavy delete is deferred. Markers land in the same BEGIN IMMEDIATE, BEFORE
the empty v23 schema is created (``executescript`` implicitly COMMITs), closing
the crash window where trash + empty v23 tables exist with no backfill claim.c                 óÐ  <€ SP                  V 4       V P                  R 4       V P                  R4      P                  4       '       d   V P                  R4       V P                  R4       V P                  R4       V P                  R4      P                  4        F(  pV P                  RV^ ,           RV^ ,           24       K*  	  SP	                  V RR	7      p\        V R
4       V# )z,DROP VIEW IF EXISTS messages_fts_trigram_srcz’SELECT 1 FROM sqlite_master WHERE type = 'table' AND name IN ('messages_fts', 'messages_fts_trigram') AND sql LIKE 'CREATE VIRTUAL TABLE%' LIMIT 1zPRAGMA writable_schema=ONzˆDELETE FROM sqlite_master WHERE type = 'table' AND name IN ('messages_fts', 'messages_fts_trigram') AND sql LIKE 'CREATE VIRTUAL TABLE%'zPRAGMA writable_schema=RESETzÃSELECT name FROM sqlite_master WHERE type = 'table' AND (name LIKE 'messages_fts_%' ESCAPE '\' OR name LIKE 'messages_fts_trigram_%' ESCAPE '\') AND name NOT LIKE 'messages\_fts\_cjk%' ESCAPE '\'zALTER TABLE z RENAME TO fts_v22_trash_Tr†  r�  )Ú_drop_fts_triggersr:   r;   r>  r€  rO   )r<   r&  r  rÉ   s   &  €r"   Ú_stageÚ>SessionSearchMixin._demote_legacy_fts_to_trash.<locals>._stage4  sã   ø€ Ø×#Ñ# DÔ)Ø�L‰LÐGÔHØ�|‰|ð?ó÷ ‰h‹jöð —‘Ð8Ô9Ø—‘ðpôð —‘Ð;Ô<ØŸ<™<ðLó
÷ ‘(“*ò
�Cð —L‘L <°°Aµ¨xÐ7PÐQTÐUVÕQWÐPXÐ!YÖZñ
ð ×/Ñ/°¸DÐ/ÓAˆBÜ˜Ð7Ô8ØˆIr$   z@failed to create v23 messages_fts during optimize-storage demote)rR   r  Ú_ensure_v23_fts_tables)rÉ   r—  r  s   f  r"   Ú_demote_legacy_fts_to_trashÚ.SessionSearchMixin._demote_legacy_fts_to_trash.  s3   ø€ õ	ô> �×$Ñ$ VÓ,Ó-ˆà×#Ñ#Ð$fÔgØˆ	r$   c                ó$   <€ V ^8„  d   QhRS[ RR/# )r   Úfailure_messager   Nr@   )r!   r¯   s   "€r"   r#   r°   X  s   ø€ ÷ 	 ñ 	 ±cð 	 ¸dñ 	 r$   c                ó|  € V P                   ;_uu_ 4        V P                  V P                  R\        4      pV P                  V P                  R\        4      p\        V4      V n        V'       g   \        P                  ! V4      hV P                  P                  4        RRR4       R#   + '       g   i     R# ; i)z£Ensure the v23 base + trigram tables under the lock (IF NOT EXISTS, cheap); raise
*failure_message* without the base table (the backfill loop would retry forever).r  rw  N)
r^  Ú_ensure_fts_schemar`  r   r   rS   rð   r7   r+  ra  )rÉ   r�  Úbase_okÚ
trigram_oks   &&  r"   r™  Ú)SessionSearchMixin._ensure_v23_fts_tablesX  s|   € ð �Z�Z‹ZØ×-Ñ-¨d¯j©j¸.Ì'ÓRˆGØ×0Ñ0°·±Ð=SÔUdÓeˆJÜ&*¨:Ó&6ˆDÔ#ßÜ×.Ò.¨Ó?Ð?Ø�J‰J×ÑÔ÷ �Z�ZŠZús   •BB*Â*B;	c                ó    <€ V ^8„  d   QhRS[ /# r   r  )r!   r¯   s   "€r"   r#   r°   c  s   ø€ ÷ ñ ¡$ñ r$   c                ó  €  V P                   ;_uu_ 4        V P                  P                  R4       RRR4       Rp V P                   ;_uu_ 4        V P                  P                  R4       RRR4       V#   + '       g   i     LM; i  \        P                   d#   p\
        P                  RT4       Rp Rp?L€Rp?ii ; i  + '       g   i     T# ; i  \         d"   p\
        P                  RT4        Rp?T# Rp?ii ; i)zƒPhase 3: reclaim freed pages to the OS. False when VACUUM failed (usually no free disk
for its temp copy; a later VACUUM reclaims).ÚVACUUMNTz$VACUUM after FTS optimize failed: %sFzPRAGMA wal_checkpoint(PASSIVE)z9WAL checkpoint (PASSIVE) after optimize VACUUM failed: %s)	r^  r`  r:   r7   r+  rÇ   rÈ   rÆ   r,  )rÉ   Ú	vacuum_okrÊ   s   &  r"   Ú_optimize_vacuumÚ#SessionSearchMixin._optimize_vacuumc  sÌ   € ð	Ø——“Ø—
‘
×"Ñ" 8Ô,÷ àˆIð	[Ø——“ð
 —
‘
×"Ñ"Ð#CÔD÷ ð Ð÷% —ûô ×'Ñ'ô 	Ü�N‰NÐAÀ3ÔGØ�Iûð	ú÷ –ð Ðûô ô 	[Ü�L‰LÐTÐVY×ZÐZØÐûð	[úsb   ‚B
 –A7²
B
 ½C ÁCÁ-C Á7B	ÂB
 Â
CÂB<Â<CÃC	ÃC ÃC ÃDÃ#C?Ã?Dc                ó0   <€ V ^8„  d   QhRS[ S[,          /# r   )r   r*   )r!   r¯   s   "€r"   r#   r°   {  s   ø€ ÷ ñ ©±­ñ r$   c                óx  € \        VR4      e   R# V P                  V4      '       d   R# V P                  V4      '       d   R# \        V4      pV'       g   \	        V4      '       d   R# V'       d"   V P                  R\        \        4      VR7       \        VR4       VP                  R	\        \        34       R# )
a•  Phase 4 (inside the write transaction, so a concurrent writer cannot race a stamp past
incomplete work): stamp the FTS layout (source of truth for "optimized"), clear the
"available" flag, advance a lagging schema_version. Returns a refusal reason or None.
Refuses while optimize work remains; an empty base index against non-empty messages
also refuses (settling there meant permanent search-index loss).r~  NÚbackfill_incompleteÚteardown_incompleteÚlayout_driftr…  r:  r�  z7UPDATE schema_version SET version = ? WHERE version < ?)r=   r�  rf  r   r   r@  r*   r   rO   r:   r   )rÉ   r<   Úcurrents   && r"   Ú_optimize_settleÚ#SessionSearchMixin._optimize_settle{  s›   € ô �TÐ3Ó4Ò@Ù(Ø×Ñ˜t×$Ò$Ù(Ø×7Ñ7¸×=Ò=Ù(Ü$ TÓ*ˆßÔ+¨D×1Ò1Ù!÷ Ø�M‰MÐ/´Ô5HÓ1IÐRVˆMÔWÜ�TÐ3Ô4Ø�‰ÐNÔQ_ÔaoÐPpÔqÙr$   Úprogress_cbNÚvacuumTc                ó~   <€ V ^8„  d   QhRS[ S[S[S[S[3,          .R3,          ,          RS[RS[S[S[3,          /# )r   r±  Nr²  r   )r   r   rˆ   r*   r   rS   )r!   r¯   s   "€r"   r#   r°   ’  sL   ø€ ÷ M3ñ M3Ù&¡x±±c¹3°hµÐ0@À$Ð0FÕ'GÕHðM3ÙY]ðM3á	‰c‘3ˆh�ñM3r$   c               óf  a aa€ S P                   '       g   RRRR/# S P                  '       d   RRRR/# S P                  4        S P                  ;_uu_ 4        S P	                  S P
                  4      pRRR4       S P                  R4      RJpX'       d   V'       g   S P                  4        M!V'       d   V'       g   S P                  R4       S P                  4        S P                  '       d   S P                  4        R	 VV 3R
 lloR VV 3R llpS! R4       V! RS P                  4       S! R4       V! RS P                  4       S! R4       V! RS P                  4       S P                  4       ;_uu_ 4       p\!        VR4      RJpS P#                  V4      pS P%                  V4      p	RRR4       X'       g   X'       g	   X	'       d5   V'       g	   X	'       d   RMRp
\&        P)                  RW§XX	4       RRRV
RR/# RpV'       d   S! R4       S P+                  4       pS P-                  S P.                  4      pVe   \&        P)                  RV4       RRRVRV/# S! R4       \&        P1                  R\2        4       RRRV/#   + '       g   i     EL!; i  + '       g   i     L÷; i)aY  Repair an older FTS layout into the current v23 shape, foreground and to completion:
legacy-v22 inline -> external-content, or a v23 ``messages_fts_trigram`` that still stores
``tool_calls``. Re-running resumes. ``progress_cb`` receives {"phase", "percent",
"indexed", "total"}. A missing trigram tokenizer is not fatal (CJK falls back to LIKE).ÚokFÚreasonÚfts5_unavailabler�  Nr~  z?failed to re-create v23 messages_fts on optimize-storage resumec                ó(   € V ^8„  d   QhR\         RR/# ©r   Úphaser   Nr@   )r!   s   "r"   r#   Ú=SessionSearchMixin.optimize_fts_storage.<locals>.__annotate__°  s   € ÷ 	dñ 	dœð 	d ñ 	dr$   c                 óê   <€ Sf   R # SP                  4       ;'       g    SP                  4       pS! RT RV'       d
   VR,          M^dRV'       d
   VR,          M^ RV'       d
   VR,          M^ /4       R # )Nrº  rã   râ   rá   )rÔ   rÙ   )rº  Ústr±  rÉ   s   & €€r"   Ú_emitÚ6SessionSearchMixin.optimize_fts_storage.<locals>._emit°  se   ø€ ØÒ"ÙØ×(Ñ(Ó*×KÐK¨d×.IÑ.IÓ.KˆBÙ˜ %¨ÇR°B°y¶MÈSØ"·R B y¦M¸QÀ×XZÈÈGÎÐ`aðcö dr$   c                ó(   € V ^8„  d   QhR\         RR/# r¹  r@   )r!   s   "r"   r#   r»  ·  s   € ÷ 		wñ 		wœ#ð 		w¨ñ 		wr$   c                ó  <€  \         P                  ! 4       pV! 4       '       g   R# S! V 4       \         P                  ! \        SP                  \         P                  ! 4       V,
          SP
                  ,          4      4       K„  )u¹   Run *step* to completion; the inter-chunk sleep is the single place the duty
cycle is enforced â€” back-to-back BEGIN IMMEDIATE chunks starve a live
gateway/CLI out of its lock retries.N)ÚtimeÚ	monotonicÚsleepÚmaxÚ_FTS_REBUILD_MIN_PAUSEÚ_FTS_REBUILD_DUTY_FACTOR)rº  ÚstepÚ_t0r¾  rÉ   s   && €€r"   Ú_driveÚ7SessionSearchMixin.optimize_fts_storage.<locals>._drive·  sW   ø€ ð Ü—n’nÓ&�Ù—v’vÙÙ�e”Ü—
’
œ3˜t×:Ñ:¼T¿^º^Ó=MÐPSÕ=SÐW[×WtÑWtÕ<tÓuÖvr$   ÚbackfillÚteardownr«  r¬  zPFTS storage optimization did not settle (%s): pending=%s trash=%s empty_index=%sÚvacuumedr²  z,FTS storage optimization settle refused (%s)Údonez/FTS storage optimization complete (layout v%d).T)rÃ   r�  r‰  r^  rŽ  r`  r)  rš  r™  rZ  r  rX  r  r   rO  rL  r=   r�  rf  rÇ   rÈ   r§  r  r¯  ró   r   )rÉ   r±  r²  Úneeds_storage_upgraderà   rÊ  r<   Ústill_pendingÚstill_trashÚempty_indexr¶  r¦  Úrefusalr¾  s   fd$          @r"   Úoptimize_fts_storageÚ'SessionSearchMixin.optimize_fts_storage’  sJ  ú€ ð × × Ð Ø˜% Ð+=Ð>Ð>Ø�>�>ˆ>Ø˜% ¨;Ð7Ð7ð 	×)Ñ)Ô+Ø�Z�Z‹ZØ$(×$FÑ$FÀtÇzÁzÓ$RÐ!÷ à—-‘-Ð 8Ó9ÀÐEˆß ¯Ø×,Ñ,Õ.ß×2ð ×'Ñ'Ð(iÔjð 	×$Ñ$Ô&Ø××ÐØ×-Ñ-Ô/÷	dñ 	d÷		wñ 		wñ 	ˆjÔÙˆz˜4×0Ñ0Ô1ÙˆjÔÙˆz˜4×4Ñ4Ô5áˆjÔÙˆz˜4×8Ñ8Ô9Ø�^‰^×Ô Ü% dÐ,DÓEÈTÐQˆMØ×-Ñ-¨dÓ3ˆKØ×FÑFÀtÓLˆK÷ ÷ ŸK¯;ß.;¿{Ñ*ÐPeˆFÜ�N‰NÐmØ!°+¸{ôLà˜% ¨6°:¸tÐDÐDàˆ	ßÙ�(ŒOØ×-Ñ-Ó/ˆIØ×%Ñ% d×&;Ñ&;Ó<ˆØÒä�N‰NÐIÈ7ÔSØ˜% ¨7°JÀ	ÐJÐJÙˆfŒÜ�‰ÐEÔGZÔ[Ø�d˜J¨	Ð2Ð2÷ �ZˆZú÷T ×ús   ÁJÆ 1J ÊJ	Ê J0	c                óx   <€ V ^8„  d   QhRS[ RS[RS[RS[RS[S[S[ R3,          ,          RS[S[ S[3,          /# )r   Ú
session_idÚaround_message_idÚwindowÚbookendÚ
keep_roles.r   )r*   rR   r   r®   rˆ   r   )r!   r¯   s   "€r"   r#   r°   ã  sS   ø€ ÷ ,
ñ ,
Ùð,
Ù25ð,
Ù?Bð,
ÙQTð,
á™U¡3¨ 8�_Õ-ð,
áJNÉsÑTWÈxÍ.ñ,
r$   c                ó^  a aaaaa€ \        S^ 4      oS P                  SW#R7      pVR,          pV'       g   R. R^ R^ R. R. /# TpVeJ   \        V4      p	V U
u. uF2  qªP                  R4      V8X  g   V
P                  R	4      V	9   g   K0  V
NK4  	  pp
. p. pS^ 8”  d¤   Vf   R
MRRP	                  R V 4       4       R2oVf   . M
\        V4      oS P                  4       ;_uu_ 4       oR VVVVV3R llpV! RV^ ,          R,          R4      p\        \        V! RVR,          R,          R4      4      4      pRRR4       R V 3R llpRTRVR,          RVR,          RV Uu. uF
  qþ! V4      NK  	  upRV Uu. uF
  qþ! V4      NK  	  up/# u up
i   + '       g   i     Lb; iu upi u upi )a¬  Anchored window (``get_messages_around``) plus session bookends, so one call yields the
goal and the resolution of a long session. ``window`` is filtered to ``keep_roles``
(None disables) EXCEPT the anchor; ``bookend_start`` / ``bookend_end`` are the
first/last ``bookend`` non-empty-content messages with ids strictly outside the
window (empty when it overlaps the head/tail). Empty result when the anchor isn't
in the session.)rÚ  rÚ  Úmessages_beforeÚmessages_afterÚbookend_startÚbookend_endNÚidÚroler(   z AND role IN (rB   c              3   ó&   "  € T F  pR x € K	  	  R# 5irD   rF   rG   s   & r"   rJ   Ú7SessionSearchMixin.get_anchored_view.<locals>.<genexpr>ù  s   é € ÐQjÑ_iÐZ[ÕRUÓ_iùrL   rM   c                ó<   € V ^8„  d   QhR\         R\        R\         /# )r   ÚopÚboundary_idÚorder©r*   rR   )r!   s   "r"   r#   Ú:SessionSearchMixin.get_anchored_view.<locals>.__annotate__ü  s!   € ÷ !ñ !¤ð !´3ð !¼sñ !r$   c           	      óf   <€ SP                  R V  RS RV R2SV.SOSN54      P                  4       # )z3SELECT * FROM messages WHERE session_id = ? AND id z ?z% AND length(content) > 0 ORDER BY id z LIMIT ?)r:   r>  )rç  rè  ré  rÛ  r<   Úrole_clauseÚrole_paramsrØ  s   &&&€€€€€r"   Ú_bookendÚ6SessionSearchMixin.get_anchored_view.<locals>._bookendü  sS   ø€ ØŸ<™<ð7Ø79°d¸"¸[¸Mð J'à', g¨Xð7ð $ [ÐH°;ÐHÀÑHó÷ ‘h“jð!r$   Ú<ÚASCÚ>ÚDESCc                óF   € V ^8„  d   QhR\         \        \        3,          /# r   )rˆ   r*   r   )r!   s   "r"   r#   rë    s   € ÷ 	hñ 	hœT¤#¤s (�^ñ 	hr$   c                 ó,   <€ SP                  V R RR7      # )Úget_anchored_viewF)Úwarn_contextÚsummary_flag)Ú_row_to_message_dict)r&  rÉ   s   &€r"   Ú_hydrateÚ6SessionSearchMixin.get_anchored_view.<locals>._hydrate  s   ø€ Ø×,Ñ,¨SÐ?RÐafÐ,ÓgÐgr$   r<  )rÅ  Úget_messages_aroundr·   r‰   rN   ra   rL  Úreversed)rÉ   rØ  rÙ  rÚ  rÛ  rÜ  Ú	primitiveÚwindow_rowsÚfiltered_windowÚkeep_setÚmÚbookend_start_rowsÚbookend_end_rowsrï  rû  rç   r<   rí  rî  s   ff&&f&          @@@r"   r÷  Ú$SessionSearchMixin.get_anchored_viewã  sÆ  ý€ ô �g˜q“/ˆØ×,Ñ,¨ZÐ9JÐ,ÓZˆ	Ø Õ)ˆßØ˜bÐ"3°QÐ8HÈ!È_Ð^`ÐboÐqsÐtÐtà%ˆØÒ!Ü˜:“ˆHÙ*5Ów©+ Q¿¹¸t»ÐHYÔ9YÐ]^×]bÑ]bÐciÓ]jÐnvÑ]vŸq˜q©+ˆOÐwØ(*ÐØ&(ÐØ�QŒ;Ø *Ò 2™"¸.ÈÏÉÑQjÑ_iÓQjÓIjÐHkÐklÐ8mˆKØ *Ò 2™"¼¸ZÓ8HˆKØ—‘×!Ô! T÷!ô !ñ &.¨c°;¸qµ>À$Õ3GÈÓ%OÐ"ä#'¬±¸#¸{È2½ÈtÕ?TÐV\Ó1]Ó(^Ó#_Ð ÷ "÷	hð 	hð �oÐ'8¸)ÐDUÕ:VØ˜iÐ(8Õ9ØÑ3EÓFÑ3E¨a˜h qžkÑ3EÑFØÑ1AÓBÑ1A¨A˜H QžKÑ1AÑBð	
ð 	
ùò+ x÷ "×!üò$ GùÚBs%   Á-FÂ
FÃ(AFÅ"F%Å9F*ÆF"	c                óX   <€ V ^8„  d   QhRS[ RS[RS[RS[S[S[ S[3,          ,          /# )r   rØ  ÚlimitrŸ   r   ©r*   rR   rS   ra   rˆ   r   )r!   r¯   s   "€r"   r#   r°     s?   ø€ ÷ #ñ #Ùð#Ù&)ð#ÙBFð#ÙSWÑX\Ñ]`ÑbeÐ]eÕXfÕSgñ#r$   c           	     óü  € V'       d   RMRpRpV P                  4       ;_uu_ 4       pVP                  RV V R2V\        V4      ^,          ^,           34      P                  4       pRRR4       ^ RIHp . p	X Fë  p
\        V	4      \        V4      8¼  d    V	# V P                  V
R,          4      pVP                  V4      '       d   KP  \        V\        4      '       d   \        V4      ;'       g    TpM\        V4      pR	P                  VP                  4       4      p\        V4      ^P8”  d   VR
,          R,           pV	P                  RV
R,          RV
R,          RV/4       Kí  	  V	#   + '       g   i     EL; i)uÑ  The *limit* most-recent real user turns, newest first, as ``{id, timestamp, preview}``
(80 chars, whitespace collapsed); used by /rewind and ``/undo [N]``. Bookkeeping rows
(``display_kind`` set) are excluded. Legacy compaction handoffs are role='user' rows
with NO display_kind â€” invisible to SQL â€” so fetch with headroom and drop them in the
decode loop; otherwise ``/undo N`` pairs an in-memory count that excludes handoffs
with a DB pick that includes them.r(   z AND active = 1zJ AND (display_kind IS NULL OR display_kind = '' OR display_kind = 'steer')zRSELECT id, timestamp, content FROM messages WHERE session_id = ? AND role = 'user'z ORDER BY id DESC LIMIT ?N)ÚContextCompressorÚcontentrj   :NéM   Nz...râ  Ú	timestampÚpreview)rL  r:   rR   r>  Úagent.context_compressorr  r=  Ú_decode_contentÚ_is_context_summary_contentr‡   r*   r	   rŒ   rN   rc   rz   )rÉ   rØ  r  rŸ   Úactive_clauseÚdisplay_clauser<   ræ   r  Úresultr&  r€   r  s   &&&&         r"   Úlist_recent_user_messagesÚ,SessionSearchMixin.list_recent_user_messages  sW  € ÷ /™Ð4Eˆð fˆØ�^‰^×Ô Ø—<‘<ØdØ �/ .Ð!1ð 2+ð+ð œS ›Z¨!�^¨aÕ/Ð0ó	÷
 ‰h‹jð ÷ õ 	?Ø')ˆÛˆCÜ�6‹{œc %›jÔ(Øð ˆð ×*Ñ*¨3¨y­>Ó:ˆGØ ×<Ñ<¸W×EÒEÙÜ˜'¤3×'Ò'Ü3°GÓ<×GÐGÀ‘ä'¨Ó0�Ø—h‘h˜wŸ}™}›Ó/ˆGÜ�7‹|˜bÔ Ø! #�,¨Õ.�Ø�M‰M˜4  T¥¨K¸¸[Õ9IÈ9ÐV]Ð^Ö_ñ ð ˆ÷/ ×Ðús   §A E*Å*E;	c                ó&   <€ V ^8„  d   QhRS[ RS[ /# ©r   Úqueryr   r@   )r!   r¯   s   "€r"   r#   r°   9  s   ø€ ÷ "!ñ "!¡Cð "!©Cñ "!r$   c                ó¦  a€ V R\          p . oR V3R llp\        P                  W4      P                  RR4      p\        P                  RV4      pRV9   d.   \
        P                  V4      '       g   VP                  RR4      p\        P                  ! RRV4      p\        P                  ! R	R
V4      p\        P                  ! RRVP                  4       4      p\        P                  ! RRVP                  4       4      p\        P                  ! RRV4      p\        S4       F  w  r4VP                  RV R2V4      pK  	  VP                  4       # )zùSanitize user input for FTS5 MATCH (raw special characters raise): preserve paired
quoted phrases, strip unmatched special characters, and quote hyphenated/dotted
terms so FTS5 matches them as phrases (``chat-send``, ``P2.2``, ``my-app.config.ts``).Nc                óZ   € V ^8„  d   QhR\         P                  \        ,          R\        /# )r   r  r   )ÚreÚMatchr*   )r!   s   "r"   r#   Ú=SessionSearchMixin._sanitize_fts5_query.<locals>.__annotate__D  s"   € ÷ 	8ñ 	8”R—X‘Xœc•]ð 	8¤sñ 	8r$   c                 óp   <€ SP                  V P                  ^ 4      4       R\        S4      ^,
           R2# )é    ú QÚ )rz   Úgroupr=  )r  Ú_quoted_partss   &€r"   Ú_holdÚ6SessionSearchMixin._sanitize_fts5_query.<locals>._holdD  s3   ø€ Ø× Ñ  §¡¨£Ô,Øœ3˜}Ó-°Õ1Ð2°$Ð7Ð7r$   rl   rj   rt   z\*+ry   z(^|\s)\*z\1z(?i)^(AND|OR|NOT)\b\s*r(   z(?i)\s+(AND|OR|NOT)\s*$z\b(\w+(?:[._-]\w+)+)\bz"\1"r"  r#  )
r   Ú_QUOTED_PHRASE_REÚsubrm   Ú_FTS5_SPECIAL_REr«   Ú_contains_cjkr  r+   Ú	enumerate)r  r&  Ú	sanitizedÚiÚquotedr%  s   &    @r"   Ú_sanitize_fts5_queryÚ'SessionSearchMixin._sanitize_fts5_query8  s'  ø€ ð Ð+Ô+Ð,ˆð !ˆ÷	8ð 	8ô &×)Ñ)¨%Ó7×?Ñ?ÀÀSÓIˆ	ô %×(Ñ(¨¨iÓ8ˆ	Ø�)ÔÔ$6×$DÑ$DÀY×$OÒ$OØ!×)Ñ)¨#¨sÓ3ˆIä—F’F˜6 3¨	Ó2ˆ	Ü—F’F˜;¨¨yÓ9ˆ	ä—F’FÐ4°b¸)¿/¹/Ó:KÓLˆ	Ü—F’FÐ5°r¸9¿?¹?Ó;LÓMˆ	ô —F’FÐ4°g¸yÓIˆ	ä" =Ö1‰IˆAØ!×)Ñ)¨E°!°°D¨/¸6ÓBŠIñ 2à�‰Ó Ð r$   c                ó&   <€ V ^8„  d   QhRS[ RS[/# ©r   rƒ   r   ©r*   rS   )r!   r¯   s   "€r"   r#   r°   ^  s   ø€ ÷ 4ñ 4™Cð 4¡Dñ 4r$   c                ój   € \         ;QJ d    R  V  4       F  '       g   K   R# 	  R# ! R  V  4       4      # )c              3   óJ   "  € T F  p\        \        V4      4      x € K  	  R # 5irV   ©r\   Úord©rH   Úchs   & r"   rJ   Ú3SessionSearchMixin._contains_cjk.<locals>.<genexpr>_  s   é € Ð3©d¨”7œ3˜r›7×#Ð#«dùs   ‚!#TF)rZ   ©rƒ   s   &r"   r+  Ú SessionSearchMixin._contains_cjk]  s+   € ç‹sÑ3©dÓ3�sŒsÐ3ŠsÐ3ˆsÑ3©dÓ3Ó3Ð3r$   c                ó&   <€ V ^8„  d   QhRS[ RS[/# r3  rê  )r!   r¯   s   "€r"   r#   r°   b  s   ø€ ÷ 9ñ 9™ð 9¡ñ 9r$   c                ó&   € \        R  V  4       4      # )c              3   ó^   "  € T F#  p\        \        V4      4      '       g   K  ^x € K%  	  R# 5i)é   Nr7  r9  s   & r"   rJ   Ú0SessionSearchMixin._count_cjk.<locals>.<genexpr>c  s   é € Ð8™t˜¤w¬s°2«w×'7—1’1›tùs   ‚-£
-)Úsumr<  s   &r"   Ú
_count_cjkÚSessionSearchMixin._count_cjka  s   € äÑ8™tÓ8Ó8Ð8r$   c                ó&   <€ V ^8„  d   QhRS[ RS[/# r  r4  )r!   r¯   s   "€r"   r#   r°   f  s   ø€ ÷ ñ ¡ð ©ñ r$   c                ó„   € ^ pV  F4  p\        \        V4      4      '       d   V^,          pK(  V^8X  d    R# ^ pK6  	  V^8H  # )uª   True when any maximal CJK run is a single char: the cjk-bigram index stores unigrams
only for isolated chars, so such a term can't match inside longer runs â€” keep LIKE.Tr7  )r  Úrunr:  s   &  r"   Ú_has_lone_cjk_runÚ$SessionSearchMixin._has_lone_cjk_rune  sE   € ð ˆÛˆBÜ”s˜2“w×ÒØ�q•’à˜!”8ÚØ’ñ ð �a‰xˆr$   c                ó6   <€ V ^8„  d   QhRS[ RS[S[ ,          /# r  )r*   r   )r!   r¯   s   "€r"   r#   r°   t  s   ø€ ÷ ?ñ ?¡ð ?©±#­ñ ?r$   c                óì   € . p\         P                  V 4       F7  pVP                  4       pVR9   d    R# VR8w  g   K&  VP                  V4       K9  	  \	        V4      ^8¼  d   RP                  V4      # R# )aO  The sanitized implicit-AND query rewritten as an any-term OR query, or ``None`` when
relaxation does not apply: fewer than two searchable units (a single term cannot relax)
or explicit ``OR``/``NOT`` (the caller expressed exact semantics). Quoted phrases stay
whole units: ``"docker networking" tls`` -> ``"docker networking" OR tls``.NÚANDú OR >   ÚORÚNOT)Ú_LIKE_TOKEN_REÚfindallrd   rz   r=  rN   )r  ÚunitsÚ	raw_tokenrd   s   &   r"   Ú_or_relaxed_queryÚ$SessionSearchMixin._or_relaxed_querys  sh   € ð ˆÜ'×/Ñ/°Ö6ˆIØ—O‘OÓ%ˆEØ˜Ô%ÚØ˜Ž~Ø—‘˜YÖ'ñ 7ô &)¨£Z°1¤_ˆv�{‰{˜5Ó!Ð>¸$Ð>r$   c                ó&   <€ V ^8„  d   QhRS[ RS[/# r  r4  )r!   r¯   s   "€r"   r#   r°   ƒ  s   ø€ ÷ Añ A©ð A±ñ Ar$   c                óà   € \        V P                  R4      P                  4       4      p\        V4      ;'       d5    \        ;QJ d    R V 4       F  '       d   K   R# 	  R# ! R V 4       4      # )z›True when every non-operator token is >=3 chars: a shorter token produces no
trigrams, and with FTS5's implicit AND one such token empties the whole MATCH.rl   c              3   ó>   "  € T F  p\        V4      ^8¬  x € K  	  R# 5i©é   N)r=  r…   s   & r"   rJ   Ú>SessionSearchMixin._trigram_eligible_tokens.<locals>.<genexpr>‡  s   é € Ð#@¹°A¤C¨£F¨a¦K»ùs   ‚FT)rg   r+   rS   Úall)r  Útokenss   & r"   Ú_trigram_eligible_tokensÚ+SessionSearchMixin._trigram_eligible_tokens‚  sU   € ô & e§k¡k°#Ó&6×&<Ñ&<Ó&>Ó?ˆÜ�F‹|×@Ð@§£Ñ#@¹Ó#@§¤Ð@¢Ð@ Ñ#@¹Ó#@Ó @Ð@r$   c                ó&   <€ V ^8„  d   QhRS[ RS[/# r^   r4  )r!   r¯   s   "€r"   r#   r°   Š  s   ø€ ÷ kñ k©Sð k±Tñ kr$   c                óœ   a € \         ;QJ d(    V 3R l\        V4       4       F  '       g   K   R# 	  R# ! V 3R l\        V4       4       4      # )u˜   True when any non-operator CJK token has fewer than 3 CJK chars â€” the trigram
tokenizer needs >=3 per token, so such a query must take the LIKE route.c              3   ó~   <"  € T F2  pSP                  V4      '       g   K  SP                  V4      ^8  x € K4  	  R# 5irZ  )r+  rD  )rH   rf   r»   s   & €r"   rJ   Ú:SessionSearchMixin._has_short_cjk_token.<locals>.<genexpr>�  s3   øé € ÐjÑ2Q¨QÐUX×UfÑUfÐgh×UiÔ(�3—>‘> !Ó$ qÖ(Ó2Qùs   ƒ=¡=TF)rZ   rg   )r»   r_   s   f&r"   Ú_has_short_cjk_tokenÚ'SessionSearchMixin._has_short_cjk_token‰  s:   ø€ ÷ ‹sÔjÔ2FÀyÔ2QÓj�sŒsÐjŠsÐjˆsÔjÔ2FÀyÔ2QÓjÓjÐjr$   c                ó&   <€ V ^8„  d   QhRS[ RS[/# r^   r4  )r!   r¯   s   "€r"   r#   r°   �  s   ø€ ÷ -ñ -©3ð -±4ñ -r$   c                óŽ   € V P                  V4      ^8¬  ;'       d+    V P                  V4      '       * ;'       d    V P                  # )uŒ   Per-token CJK length gate for the trigram index: ``å¹¿è¥¿ OR æ¡‚æž— OR æ¼“æ±Ÿ`` has 6
CJK chars total but 2 per token, so trigram returns 0.)rD  re  rð   )rÉ   r_   s   &&r"   Ú_trigram_route_okÚ$SessionSearchMixin._trigram_route_ok�  sH   € ð —‘ 	Ó*¨aÑ/÷ ,ð ,¸×8QÑ8QÐR[Ó8\Ô4\÷ ,ð ,Ø×+Ñ+ð	-r$   c                ó&   <€ V ^8„  d   QhRS[ RS[ /# r  r@   )r!   r¯   s   "€r"   r#   r°   •  s   ø€ ÷ ñ ©3ð ±3ñ r$   c                ó–  €  V P                   '       d   R# T P                  T;'       g    R4      pV'       g   R# V P                  V4      '       g   R# VP                  R4      P                  4       pV P                  '       d   V P                  V4      '       g   R# V P                  V4      '       d   R# R#   \         d     R	# i ; i)
z>Best-effort name of the routing path a query takes (log-only).Úlike_scan_fts_staler(   ÚemptyÚfts5rl   Úfts_cjkÚtrigramÚ	like_scanr¼   )Ú
_fts_staler0  r+  r+   rþ   rI  ri  rÆ   )rÉ   r  r-  Úraws   &&  r"   Ú_describe_search_pathÚ(SessionSearchMixin._describe_search_path•  s¦   € ð	Ø��ˆÙ,Ø×1Ñ1°%·+°+¸2Ó>ˆIßÙØ×%Ñ% i×0Ò0ÙØ—/‘/ #Ó&×,Ñ,Ó.ˆCØ×&×&Ð&¨t×/EÑ/EÀc×/JÒ/JÙ Ø $× 6Ñ 6°s× ;Ò ;�9ÐLÀÐLøÜô 	Úð	ús>   ‚B9 –B9 ªB9 »B9 Á0B9 ÂB9 ÂB9 Â5B9 Â7B9 Â9CÃCc                óT   <€ V ^8„  d   QhRS[ RS[ RS[ RS[RS[RS[S[ S[3,          /# )r   rû   Úmatch_queryÚorder_by_sqlr  Úoffsetr   )r*   rR   r®   ra   )r!   r¯   s   "€r"   r#   r°   ©  sC   ø€ ÷ ñ ™cð ±ð Á3ð ÑRUð Ñ_bð Ù%*©3±¨9Õ%5ñr$   c               ó†   € V  R2.pV.p\        Wg3/ VB  VP                  W4.4       \        RV  R2V  RV  R2WbR4      pW‡3# )zHMATCH query + params against one FTS5 index joined to messages/sessions.z MATCH ?zsnippet(z), -1, '>>>', '<<<', '...', 40) AS snippetz'
            JOIN messages m ON m.id = z.rowidúLIMIT ? OFFSET ?)r©   r¨   rš   )	rû   rx  ry  r  rz  Úfiltersr”   rž   r  s	   &&&$$,   r"   Ú_fts_match_sqlÚ!SessionSearchMixin._fts_match_sql¨  sl   € ð �7˜(Ð#Ð$ˆØ#�}ˆÜ˜uÑ8°Ò8Ø�‰�u�oÔ&Ü Ø�u�gÐFÐGØˆgÐ=¸e¸WÀFÐKÈUÐbtó
ˆð ˆ{Ðr$   Ú	fail_openÚoperational_debugc                ó”   <€ V ^8„  d   QhRS[ RS[ RS[ RS[S[ ,          RS[S[ ,          RS[S[S[S[ S[3,          ,          ,          /# )r   rû   rx  ry  r€  r�  r   )r*   r   ra   rˆ   r   )r!   r¯   s   "€r"   r#   r°   ¶  s\   ø€ ÷ ñ ¡ð ±3ð Ácð ÑYaÑbeÕYfð Ù'/±¥}ðÙJRÑSWÑX\Ñ]`ÑbeÐ]eÕXfÕSgÕJhñr$   c               ó¤  € V P                   ! WV3/ VB w  rx V P                  Wx4       U	u. uF  p	\        V	4      NK  	  up	# u up	i   \        P                   d$    T'       d   \
        P                  TRR7        R# \        P                   d>   p
Te   T P                  T
4      '       g   h \
        P                  RYJ4        Rp
?
R# Rp
?
ii ; i)u�  Run one MATCH against *table*; ``None`` when the query cannot execute (tokenizer /
syntax) so the caller falls back. *fail_open* names the index for the
substring-capable routes: a corruption-class ``DatabaseError`` there detaches the
derived indexes (``_enter_fts_fail_open``) and answers from canonical rows â€” a live
search never runs the unbounded rebuild. Other ``DatabaseError``s propagate.T)Úexc_infoNz[%s FTS search hit a corruption error (%s); detached FTS and falling back to canonical LIKE.)
r~  rä   rˆ   r7   r+  rÇ   r,  ÚDatabaseErrorÚ_enter_fts_fail_openrÈ   )rÉ   rû   rx  ry  r€  r�  Úkwargsr  rž   r&  rÊ   s   &&&&$$,    r"   Ú_match_rowsÚSessionSearchMixin._match_rows¶  s´   € ð ×)Ò)¨%¸lÑUÈfÑU‰ˆð	Ø)-¯©¸Ô)DÓEÑ)D #”D˜–IÑ)DÑEÐEùÒEøÜ×'Ñ'ô 	ß Ü—‘Ð.¸�Ô>ÚÜ×$Ñ$ô 	ØÒ ¨×(AÑ(AÀ#×(FÒ(FØÜ�N‰NØmØô õ ûð	ús?   ˜A «A¿A ÁA ÁCÁ$CÁ>CÂCÂC
Â-C
Ã
Cc                ón   <€ V ^8„  d   QhRS[ S[,          RS[ RS[RS[RS[ S[S[S[3,          ,          /# )r   r”   rž   r•   r–   r   )ra   r*   rˆ   r   )r!   r¯   s   "€r"   r#   r°   Ì  sO   ø€ ÷ Bñ B¡¡S¥	ð B±4ð BÁcð BÑVYð BÑ^bÑcgÑhkÑmpÐhpÕcqÕ^rñ Br$   c               ó„   € \        \        RWV4      pV P                  WR4       Uu. uF  p\        V4      NK  	  up# u upi )zDCanonical-table LIKE scan; ``params[0]`` is the snippet anchor term.z
messages m)rš   Ú_LIKE_SNIPPET_SQLrä   rˆ   )rÉ   r”   rž   r•   r–   r  r&  s   &&&$$  r"   Ú
_like_rowsÚSessionSearchMixin._like_rowsÌ  s;   € ä Ô!2°LÀ%ÐS\Ó]ˆØ%)§^¡^°CÔ%@ÓAÑ%@˜c”�S–	Ñ%@ÑAÐAùÒAs   ¦=c                ó`   <€ V ^8„  d   QhRS[ RS[S[ S[S[,          S[S[ ,          3,          /# r  )r*   r®   ra   r   r   )r!   r¯   s   "€r"   r#   r°   Ò  s4   ø€ ÷ #Bñ #B©3ð #B±5¹¹dÁ3½iÉÑRUÍÐ9VÕ3Wñ #Br$   c                óZ  € . .pRp\         P                  V 4       F¥  pVP                  4       pVR8X  d%   VR,          '       d   VP                  . 4       RpK>  VR9   d   KG  VR8X  d   RpKR  VP	                  R4      P	                  R4      P	                  4       pV'       g   KŠ  VR,          P                  WR34       RpK§  	  . p. pRpV FÊ  p	V	'       d;   \
        ;QJ d    R V	 4       F  '       g   K   RM	  RM! R V	 4       4      '       g   KG  . p
V	 FW  w  r[T
P                  V'       d
   R	\         2M\        4       VP                  \        V4      4       Ve   KK  V'       d   KU  TpKY  	  VP                  R
RP                  V
4       R24       KÌ  	  RP                  V4      Wx3# )z¢Compile the supported FTS boolean subset into LIKE predicates: terms within an OR
group are ANDed (FTS5's implicit conjunction) and ``NOT`` negates the next term.FrO  rP  Trl   ry   Nc              3   ó4   "  € T F  w  rV'       * x € K  	  R # 5irV   rF   )rH   rI   Únegateds   &  r"   rJ   ÚASessionSearchMixin._compile_like_boolean_query.<locals>.<genexpr>ì  s   é € Ð#HÁ%±J°A¨§K¡KÃ%ùs   ‚zNOT rv  r˜   rM   rN  r<  >   rM  r2   )
rQ  rR  rd   rz   r+   rZ   Ú_LIKE_COALESCED_COLUMN_SQLr¨   rv   rN   )r  ÚgroupsÚnegate_nextrT  Úoperatorrr   Úcompiled_groupsrž   Úsnippet_termr$  Úclausesr’  s   &           r"   Ú_compile_like_boolean_queryÚ.SessionSearchMixin._compile_like_boolean_queryÑ  sw  € ð 13¨tˆØˆÜ'×/Ñ/°Ö6ˆIØ —‘Ó(ˆHØ˜4ÔØ˜"—:”:Ø—M‘M "Ô%Ø#�ÙØ˜?Ô*ÙØ˜5Ô Ø"�ÙØ—?‘? 3Ó'×-Ñ-¨cÓ2×8Ñ8Ó:ˆDß‰tØ�r•
×!Ñ! 4Ð"5Ô6Ø#’ñ 7ð" &(ˆØˆØ&*ˆÛˆEß§£Ñ#HÁ%Ó#H§§¢Ñ#HÁ%Ó#H× HÒ HÙØ!#ˆGÛ!&‘�Ø—‘Çg Ô&@Ð%AÑBÔSmÔnØ—‘œl¨4Ó0Ô1ØÔ'·±Ø#'’Lñ	 "'ð
 ×"Ñ" Q w§|¡|°GÓ'<Ð&=¸QÐ#?Ö@ñ ð �{‰{˜?Ó+¨VÐAÐAr$   c                ón   <€ V ^8„  d   QhRS[ RS[RS[RS[S[ ,          RS[S[S[ S[3,          ,          /# )r   r  r  rz  Úsortr   )r*   rR   r   ra   rˆ   r   )r!   r¯   s   "€r"   r#   r°   ÷  sR   ø€ ÷ 
uñ 
uÙð
uÙ$'ð
uÙ14ð
uÙ<DÁS½Mð
uÙY]Ñ^bÑcfÑhkÐckÕ^lÕYmñ
ur$   c               ó(  € V P                  V4      w  rgpV'       d   Vf   . # RV R2.p	\        W—3/ VB  \        V\        4      '       d&   VP	                  4       P                  4       R8X  d   RMRp
V P                  W˜.VOVNVNRV
 RV
 2RR	7      # )
z;Search canonical messages while derived FTS state is stale.rv  rM   r4   rò  rô  zORDER BY m.timestamp z, m.id r|  ©r•   r–   )r›  r©   r‡   r*   r+   Úlowerr�  )rÉ   r  r  rz  rž  r}  Ú	predicaterž   r™  r”   ré  s   &&$$$,     r"   Ú_search_messages_like_fallbackÚ1SessionSearchMixin._search_messages_like_fallback÷  s­   € ð +/×*JÑ*JÈ5Ó*QÑ'ˆ	˜<ß˜LÒ0ØˆIØ�Y�K˜qÐ!Ð"ˆÜ˜uÑ8°Ò8Ü# D¬#×.Ò.°4·:±:³<×3EÑ3EÓ3GÈ8Ô3S‘ÐY_ˆØ�‰˜uÐ&L°fÐ&L¸eÐ&LÀVÐ&LØ*?À¸wÀgÈeÈWÐ(UÐasð ó uð 	ur$   c                ó   <€ V ^8„  d   QhRR/# rÀ   rF   )r!   r¯   s   "€r"   r#   r°     s   ø€ ÷ 
Zñ 
Z¨$ñ 
Zr$   c                óþ   € V P                   '       g   V P                  '       g   R#  V P                  R\        34      pTe   RT n         R;T n        ;T n        T n        R# R#   \        P
                   d     R# i ; i)z@Observe fail-open initiated by another process sharing state.db.Nz.SELECT 1 FROM state_meta WHERE key = ? LIMIT 1TF)rs  rÃ   Ú	_read_oner   r7   ÚErrorrð   rþ   )rÉ   Ústales   & r"   Ú_refresh_fts_stale_stateÚ+SessionSearchMixin._refresh_fts_stale_state  sw   € à�?�?ˆ? $×"3×"3Ð"3Ùð	Ø—N‘NÐ#SÔVcÐUeÓfˆEð ÒØ"ˆDŒOØTYÐYˆDÔÐY Ô 7¸$Ö:Qñ øô �}‰}ô 	Úð	ús   ¨A# Á#A<Á;A<c          	      ó˜   <€ V ^8„  d   QhRS[ S[S[S[3,          ,          RS[S[S[,          ,          RS[ S[S[S[3,          ,          /# )r   ÚmatchesÚresult_fieldsr   )ra   rˆ   r*   r   r   r   )r!   r¯   s   "€r"   r#   r°     sN   ø€ ÷ ñ Ù™D¡¡c �NÕ+ðÙ<DÁZÑPSÅ_Õ<UðÙaeÑfjÑknÑpsÐksÕftÕauñr$   c                óð  € Ve	   RV9   EdH   \        ^ \        V4      R4       EF,  pWVR,            pV Uu/ uF  qUR,          . bK  	  pp \        P                  RP	                  R V 4       4      R7      pV P                  4       ;_uu_ 4       pVP                  V\        V4      4      P                  4       p	RRR4       X	 F!  p
WjR,          ,          P                  V
4       K#  	  V Fa  p VP                  VR,          . 4       U
u. uF6  p
R	V
R	,          R
\        V P                  V
R
,          4      4      R,          /NK8  	  up
VR&   Kc  	  EK/  	  V F  pVP                  R
R4       K  	  Ve2   V UUu. uF#  qR Uu/ uF  q»V9   g   K  WµV,          bK  	  upNK%  	  pppV# u upi   + '       g   i     Lú; i  \         d    / p Låi ; iu up
i   \         d
    . TR&    Kÿ  i ; iu upi u uppi )zOAttach neighboring messages in bounded batches, only when context is requested.NÚcontextiô  râ  rB   c              3   ó&   "  € T F  pR x € K	  	  R# 5irD   rF   rG   s   & r"   rJ   Ú>SessionSearchMixin._finalize_search_matches.<locals>.<genexpr>  s   é € ÐAXÉxÈ!Å#ËxùrL   )ÚidsÚmatch_idrã  r  ºNéÈ   N)Úranger=  Ú_CONTEXT_WINDOW_SQLr!   rN   rL  r:   ra   r>  rz   rÆ   r‰   rŒ   r  Úpop)rÉ   r­  r®  ÚstartÚbatchÚmatchÚcontextsr  r<   ræ   r&  r´   s   &&&         r"   Ú_finalize_search_matchesÚ+SessionSearchMixin._finalize_search_matches  sÝ  € ð Ò  I°Õ$>Ü˜q¤# g£,°×4�Ø e¨c¥kÐ2�Ù9>Ó?¹° $�K¨šO¹�Ð?ð"Ü-×4Ñ4¸¿¹ÑAXÉxÓAXÓ9XÐ4ÓY�CØŸ™×)Ô)¨TØ#Ÿ|™|¨C´°h³Ó@×IÑIÓK˜÷ *ã#˜Ø  Z¥Õ1×8Ñ8¸Ö=ñ  $ó #�Eð.ð (0§|¡|°E¸$µKÀÔ'Dó,Fá'D ð $ S¨¥[°)¼]È4×K_ÑK_Ð`cÐdmÕ`nÓKoÓ=pÐquÕ=vÓwÙ'Dñ,F˜˜iÓ(ô #ñ 5ó& ˆEØ�I‰I�i Ö&ñ àÒ$ÙgnÔoÑgnÐ^c¸ÓY¹°ÐSXÉ.Ô+˜ U�|Ò+¹ÔYÑgnˆGÑoØˆùò+ @÷ *×)ûô !ô "Ø!’Hð"üò,Føô %ô .Ø+-˜˜iÔ(ð.üò ZùÓosr   ¶F&ÁAF>Â*F+Â9/F>Ã.GÄ	<GÅGÅ8G2Æ G-ÆG-ÆG2Æ+F;Æ6F>Æ>GÇGÇGÇG*Ç)G*Ç-G2c                ó0  <€ V ^8„  d   QhRS[ RS[S[ ,          R,          RS[S[ ,          R,          RS[S[ ,          R,          RS[RS[RS[ R,          R	S[R
S[S[S[ ,          ,          RS[S[,          RS[S[,          RS[S[S[ S[3,          ,          /# ©r   r  r    Nr¡   r¢   r  rz  rž  rŸ   r­   r›   rœ   r   ©r*   ra   rR   rS   r   r   rˆ   r   )r!   r¯   s   "€r"   r#   r°   .  s¹   ø€ ÷ *ñ *Ùð*Ù)-©c­°TÕ)9ð*ÙSWÑX[ÕS\Ð_cÕScð*á™#•Y Õ%ð*á58ð*áGJð*áVYÐ\`ÕV`ð*ñ ð*ñ 19¹ÁC½Õ0Ið*ñ ™3•-ð	*ñ 4<¹Cµ=ð	*ñ
 
‰d‘3™�8�nÕ	ñ*r$   c                ó.  € \         P                   ! 4       pRp V P                  WW4WVWxV	W«R7      pT\         P                   ! 4       V,
          R,          pV\        4       8¼  d?   \        P	                  RV P                  V4      Yíe   \        V4      MRVR,          4       # #   \         P                   ! 4       T,
          R,          pT\        4       8¼  d?   \        P	                  RT P                  T4      Yíe   \        T4      MRTR,          4       i i ; i)zŸ:meth:`_search_messages_impl` plus one log line per slow search with the routing
path taken. Threshold HERMES_SEARCH_SLOW_MS (default 1000; 0 logs every call).N)
r    r¡   r¢   r  rz  rž  rŸ   r­   r›   rœ   r)   z<slow session search: path=%s elapsed=%.0fms rows=%s query=%rÚerrrµ  )rÂ  Ú_search_messages_implr/   rÇ   ró   ru  r=  )rÉ   r  r    r¡   r¢   r  rz  rž  rŸ   r­   r›   rœ   Ústartedræ   Ú
elapsed_mss   &&&&&&&&&&&&   r"   Úsearch_messagesÚ"SessionSearchMixin.search_messages.  sõ   € ô —)’)“+ˆØˆð	*Ø×-Ñ-ØÀOØ°ÐagØ!ð .ó 8ˆDð äŸ)š)›+¨Õ/°6Õ9ˆJØœ_Ó.Ô.Ü—‘ÐZØ ×6Ñ6°uÓ=¸zÒXhÌ3ÈtÌ9ÐnsØ! %�Lõ*ð /øô Ÿ)š)›+¨Õ/°6Õ9ˆJØœ_Ó.Ô.Ü—‘ÐZØ ×6Ñ6°uÓ=¸zÒXhÌ3ÈtÌ9ÐnsØ! %�Lõ*ð /ús   ™B" Â"A2Dc                ó0  <€ V ^8„  d   QhRS[ RS[S[ ,          R,          RS[S[ ,          R,          RS[S[ ,          R,          RS[RS[RS[ R,          R	S[R
S[S[S[ ,          ,          RS[S[,          RS[S[,          RS[S[S[ S[3,          ,          /# rÁ  rÂ  )r!   r¯   s   "€r"   r#   r°   E  sÕ   ø€ ÷ hSñ hSÙðhSÙ)-©c­°TÕ)9ðhSÙSWÑX[ÕS\Ð_cÕScðhSá™#•Y Õ%ðhSá58ðhSáGJðhSáVYÐ\`ÕV`ðhSñ ðhSñ 19¹ÁC½Õ0IðhSñ ™3•-ð	hSñ 4<¹Cµ=ð	hSñ
 
‰d‘3™�8�nÕ	ñhSr$   c           
     ó	  a€ V P                  V	4      pV'       d   VP                  4       '       g   . # V P                  V4      pV'       g   . # \        W‚W4W«R7      pV'       d3   RV9   d,   V P                  ! V3RVRVRV/VB pV P                  WìR7      # V P                  4        V P                  '       d,   V P                  ! V3RVRVRV/VB pV P                  WìR7      # V P                  '       g   . # \        P                  \        V\        4      '       d   VP                  4       P                  4       MRR4      p\        RR	VRVRV/VB p\        V4      ;'       d    RV9   ;'       gH    \        V4      ;'       d5    \        ;QJ d    R
 V 4       F  '       g   K   RM	  RM! R
 V 4       4      pV P!                  V4      pV'       d   V P#                  VVV4      pMBV P$                  ! RV3/ VB w  pp V P'                  VV4       Uu. uF  p\        V4      NK  	  ppV P1                  4       ei   \3        V4      V8  dY    V P4                  ! W\3        V4      ,
          3/ VB pV Uu0 uF  pVR,          kK  	  upoVP7                  V3R lV 4       4       V'       gÈ   V'       gÀ   \        V4      '       d   RV9   g¨   \=        VP                  R4      P                  4       4      pV P>                  '       d   V P@                  ! RV3/ VB ;'       g    TpV'       gH   V PB                  '       d6   V PE                  V4      '       d   V P@                  ! RV3/ VB ;'       g    TpV'       gR   V'       gJ   V P                  '       g8   V PG                  V4      pVe#   V P@                  ! RV3RRRR/VB ;'       g    TpV P                  WìR7      # u upi   \(        P*                   d    . u # \(        P,                   d=   pT P/                  T4      '       g   h T P                  ! T3RTRTRT/TB p Rp?ELRp?ii ; iu upi   \(        P*                   d"   p\8        P;                  RT4        Rp?ELáRp?ii ; i)a2  FTS5 search across session messages (keywords, ``"phrases"``, AND/OR/NOT, ``prefix*``).
Returns snippet + session metadata + 1-message context per hit; ``fields`` selects a
projection. ``sort``: None = BM25 rank; "newest"/"oldest" = timestamp then rank (the
CJK LIKE fallback ignores it). Rewound rows (``active=0, compacted=0``) are excluded
by default; compaction-archived rows ARE included; ``include_inactive`` = every row.
``after_ts``/``before_ts`` bound ``sessions.started_at`` on every route (FTS5, CJK,
trigram, LIKE fallback, unindexed-gap supplement).©rŸ   r    r¡   r¢   r›   rœ   Útoolr  rz  rž  )r®  NzORDER BY rankry  c              3   ó2   "  € T F  q\         9   x € K  	  R # 5irV   )r   )rH   Úsrcs   & r"   rJ   Ú;SessionSearchMixin._search_messages_impl.<locals>.<genexpr>l  s   é € Ð'eÑWdÐPSÔ/KÖ(KÓWdùs   ‚TFr  râ  c              3   óF   <"  € T F  qR ,          S9  g   K  Vx € K  	  R# 5i)râ  NrF   )rH   r  Úseen_idss   & €r"   rJ   rÐ  ˆ  s   øé € ÐQ©+ Q¸4½ÈÑ9PŸqšq«+ùs   ƒ!—
!z$Unindexed-gap supplement skipped: %srl   rù   rw  r€  z
OR-relaxedr�  z1OR-relaxed FTS retry failed; keeping empty resultrF   )$r½   r+   r0  rˆ   r£  r¾  rª  rs  rÃ   Ú_FTS_ORDER_BYr‰   r‡   r*   r¡  rS   rZ   r+  Ú_search_cjkr~  rä   r7   r+  r…  r†  rÔ   r=  Ú_search_unindexed_gapr¨   rÇ   r,  rp   rþ   rˆ  rð   r_  rU  )rÉ   r  r    r¡   r¢   r  rz  rž  rŸ   r­   r›   rœ   r®  r}  r­  ry  ÚrouteÚwants_unindexed_rowsÚis_cjkr  rž   r&  rÊ   Úgap_matchesr  Úfb_queryÚrelaxedrÒ  s   &&&&&&&&&&&&               @r"   rÅ  Ú(SessionSearchMixin._search_messages_implE  s.  ø€ ð ×3Ñ3°FÓ;ˆß˜EŸK™KŸMšMØˆIØ×)Ñ)¨%Ó0ˆßØˆIÜÐ(8Ø'6Ø (ô?ˆ÷
 ˜6 [Ô0Ø×9Ò9¸%ÑrÀuÐrÐU[ÐrÐbfÐrÐjqÑrˆGØ×0Ñ0°Ð0ÓVÐVØ×%Ñ%Ô'Ø�?�?ˆ?Ø×9Ò9¸%ÑrÀuÐrÐU[ÐrÐbfÐrÐjqÑrˆGØ×0Ñ0°Ð0ÓVÐVØ× × Ð ØˆIä$×(Ñ(ÄÈDÔRU×AVÒAV¨¯©«×);Ñ);Ô)=Ð\`ÐbqÓrˆÜÑV ,ÐV°eÐVÀFÐVÈgÑVˆô !% [Ó 1× KÐ K°fÀÑ6K÷  gð  gÜ�Ó×eÐe§C£CÑ'eÑWdÓ'e§C§C¢CÑ'eÑWdÓ'eÓ$eð 	à×#Ñ# EÓ*ˆßØ×&Ñ& uÐ.BÀEÓJ‰Gà×-Ò-¨n¸eÑMÀuÑM‰KˆC�ðwØ04·±¸sÀFÔ0KÓLÑ0K¨œ4 ž9Ñ0K�ÐLð" ×"Ñ"Ó$Ò0´S¸³\ÀEÔ5IðJØ"×8Ò8¸ÌÈGËÕ@TÑ`ÐX_Ñ`�Ù-4Ó5©W¨˜A˜dŸG˜G©WÑ5�Ø—‘ÔQ©+ÓQÔQ÷ Ÿv¬t°K×/@Ò/@ÀVÈ{ÔEZÜ(¨¯©°SÓ)9×)?Ñ)?Ó)AÓBˆHð ×&×&Ð&Ø×*Ò*Ð+=¸xÑQÈ5ÑQ×\Ð\ÐU\�ß˜t×6×6Ð6¸4×;XÑ;XÐY^×;_Ò;_Ø×*Ò*Ð+AÀ8ÑUÈuÑU×`Ð`ÐY`�÷ Ÿv¨d¯o¯o¨oØ×,Ñ,¨UÓ3ˆGØÒ"Ø×*Ò*¨>¸7ñ 4Èlð 4Ø=pð4à-2ñ4÷ ?ð ?à7>ð ð ×,Ñ,¨WÐ,ÓRÐRùòu MøÜ×+Ñ+ô Ø’	Ü×(Ñ(ô 	wð ×0Ñ0°×5Ò5ØØ×=Ò=¸eÑvÈ5ÐvÐY_ÐvÐfjÐvÐnuÑv–ûð	wüò" 6øä×+Ñ+ô JÜ—‘ÐCÀS×IÒIûðJús`   Ç>O! ÈOÈ&O! É'Q É2QÊQ ÏO! Ï!Q
Ï:Q
ÐQ
Ð1QÑQ
ÑQ ÑRÑ'RÒRc                ón   <€ V ^8„  d   QhRS[ RS[RS[S[ S[3,          RS[S[S[ S[3,          ,          /# )r   r  r×  rÖ  r   )r*   rS   rˆ   r   ra   )r!   r¯   s   "€r"   r#   r°   ¯  sK   ø€ ÷ cñ c¡ð c¹Dð cÉÉcÑSVÈhÍð cÑ\`ÑaeÑfiÑknÐfnÕaoÕ\pñ cr$   c                ó  € \        V4      P                  R4      P                  4       p\        V4      pV P                  '       d>   V'       g6   V P	                  V4      '       g   V P
                  ! RV3RRRR/VB pVe   V# V P                  V4      '       d%   V'       g   V P
                  ! RV3RR/VB pVe   V# \        V4      ;'       g    V.pV UU	u. uF  p\        V4       F  q™NK  	  K  	  p
pp	R	R
P                  \        .\        V4      ,          4       R2.pR Uu/ uF  qÌW<,          bK  	  pp\        Wº3/ VB  V P                  W·^ ,          .V
OVR,          NVR,          NRRR7      # u up	pi u upi )uÏ  CJK routing: the unicode61 table splits CJK into single characters (false positives,
missed phrases). cjk-bigram serves every shape except queries wanting rows the
substring indexes exclude (role='tool', cron/subagent sources) and LONE
1-char CJK runs (bigrams only exist for runs >=2 â€” LIKE is broader); then trigram
(>=3 CJK chars per token); then a LIKE substring scan with one clause per
non-operator token so "å¹¿è¥¿ OR æ¡‚æž— OR æ¼“æ±Ÿ" matches each term.rl   rù   r€  z
CJK-bigramr�  z;messages_fts_cjk query failed; falling back to trigram/LIKErw  ÚTrigramrv  rN  rM   r  rz  úORDER BY m.timestamp DESCr|  r   rÌ  )r~   r+   rp   rþ   rI  rˆ  ri  rg   rv   rN   Ú_LIKE_ANY_COLUMN_SQLr=  r©   r�  )rÉ   r  r×  rÖ  r_   rx  r­  Únon_op_tokensrn   rŠ   Úlike_paramsÚ
like_whereÚkr}  s   &&&&          r"   rÔ  ÚSessionSearchMixin._search_cjk¯  s­  € ô )¨Ó/×5Ñ5°cÓ:×@Ñ@ÓBˆ	Ü'¨	Ó2ˆØ×"×"Ð"×+?È×H^ÑH^Ð_h×HiÒHiØ×&Ò&Ø" KñjØ;Gðjà"_ðjàchñjˆGð Ò"Ø�Ø×!Ñ! )×,Ò,×5IØ×&Ò&Ð'=¸{ÑiÐV_ÐiÐchÑiˆGØÒ"Ø�Ü,¨YÓ7×FÐF¸I¸;ˆÙ*7ÔS©- 3ÄÈcÖAR¸AšQÑAR™Q©-ˆÑSØ˜&Ÿ+™+Ô';Ð&<¼sÀ=Ó?QÕ&QÓRÐSÐSTÐUÐVˆ
ñ)Bó Cñ )B 1�e•h’;ñ )Bˆð Cä˜zÑB¸'ÒBà�‰˜z¸!Õ,<Ð+l¸{Ð+lÈEÐRYÍNÐ+lÐ\aÐbjÕ\kÐ+lØ(CÐOað ó cð 	cùó TùòCs   ÃE6Ä'E<c          	      óR   <€ V ^8„  d   QhRS[ RS[RS[S[S[ S[3,          ,          /# )r   Ú	fts_queryr  r   )r*   rR   ra   rˆ   r   )r!   r¯   s   "€r"   r#   r°   Ì  s4   ø€ ÷ 4ñ 4©sð 4¹3ð 4ÉdÑSWÑX[Ñ]`ÐX`ÕSaÕNbñ 4r$   c                ó¼  € V P                  4       pVe   V^ 8:  d   . # R \        P                  V4       4        Uu. uF*  pV'       g   K  VP                  4       \        9  g   K(  VNK,  	  ppV'       g   . # R.\
        .\        V4      ,          OpVR,          VR,          .R V 4       Op\        Wx3/ VB  V P                  Wv^ ,          .VOVNRRR7      # u upi )	uæ   LIKE-scan ids in (fts_rebuild_progress, fts_rebuild_high_water] â€” rows the deferred
rebuild hasn't indexed yet. The FTS query degrades to AND-joined substring terms
(quoted phrases kept whole): recall-over-precision mid-rebuild.c              3   ó|   "  € T F2  qP                  R 4      P                  R4      P                  4       x € K4  	  R# 5i)rl   ry   N)r+   r…   s   & r"   rJ   Ú;SessionSearchMixin._search_unindexed_gap.<locals>.<genexpr>Ó  s/   é € Ð lÑJkÀQ§¡¨£×!3Ñ!3°CÓ!8×!>Ñ!>×!@Ð!@ÓJkùs   ‚:<zm.id > ? AND m.id <= ?râ   rá   c              3   óH   "  € T F  p\        V4       F  q"x € K  	  K  	  R # 5irV   )rv   )rH   rr   rŠ   s   &  r"   rJ   rë  Ø  s!   é € Ð=nÉEÀDÔ[gÐhlÖ[mÐVW¼aÑ[m¹aËEùs   ‚ "rà  zLIMIT ?r   )	rÔ   rQ  rR  rd   Ú_LIKE_SKIP_TOKENSrá  r=  r©   r�  )	rÉ   rè  r  r}  r/  rn   Útermsr”   rž   s	   &&&,     r"   rÕ  Ú(SessionSearchMixin._search_unindexed_gapÌ  sí   € ð ×(Ñ(Ó*ˆØŠ>˜U aœZØˆIÙ lÌ.×J`ÑJ`ÐajÔJkÔ ló BÑ l˜Üô ØŸI™I›KÔ/@Ñ@÷ �Ñ lˆð BçØˆIØ)ÐRÔ.BÐ-CÄcÈ%ÃjÕ-PÐRˆØ˜yÕ)¨6°'­?ÐoÑ=nÉEÓ=nÐoˆÜ˜uÑ8°Ò8Ø�‰˜u¨Q¥xÐ&@°&Ð&@¸%Ð&@ÐKfØ)2ð ó 4ð 	4ùòBs   ¼
CÁCÁ&Cc                ó´   <€ V ^8„  d   QhRS[ RS[RS[RS[ R,          RS[S[ ,          R,          RS[S[ ,          R,          RS[S[S[ S[3,          ,          /# )	r   r  r  Úinclude_archivedÚsourceNÚsourcesr¡   r   r	  )r!   r¯   s   "€r"   r#   r°   Ý  so   ø€ ÷ 2ñ 2Ùð2Ù!$ð2Ù=Að2ÙRUÐX\ÕR\ð2á‘c•˜TÕ!ð2á;?Á½9ÀtÕ;Kð2áW[Ñ\`ÑadÑfiÐaiÕ\jÕWkñ2r$   c                óN  aa€ T;'       g    RP                  4       P                  4       oS'       d   V^ 8:  d   . # V P                  WEV\        V^,          V4      ^ VRSR7      pR V3R llo\	        \        V4      V3R lR7      pVRV  U	U
u. uF  w  ršV
NK	  	  up
p	# u up
p	i )	z¡Search surfaced sessions by exact/prefix/substring session id. Also matches
``_lineage_root_id`` so an old compression root id resolves to the live continuation.r(   T)rò  ró  r¡   r  rz  rñ  Úorder_by_last_activeÚid_queryc                óR   € V ^8„  d   QhR\         \        \        3,          R\        /# )r   r&  r   )rˆ   r*   r   rR   )r!   s   "r"   r#   Ú>SessionSearchMixin.search_sessions_by_id.<locals>.__annotate__ë  s&   € ÷ 	Uñ 	U”tœC¤˜H•~ð 	U¬#ñ 	Ur$   c                 óö  <€ \        V P                  R 4      ;'       g    R4      \        V P                  R4      ;'       g    R4      3 Uu. uF  q'       g   K  VP                  4       NK  	  pp\        ;QJ d    V3R lV 4       F  '       g   K   RM	  RM! V3R lV 4       4      '       d   ^ # \        ;QJ d    V3R lV 4       F  '       g   K   RM	  RM! V3R lV 4       4      '       d   ^# ^# u upi )râ  r(   Ú_lineage_root_idc              3   ó,   <"  € T F	  qS8H  x € K  	  R # 5irV   rF   ©rH   r.   Úneedles   & €r"   rJ   ÚJSessionSearchMixin.search_sessions_by_id.<locals>.score.<locals>.<genexpr>í  s   øé € Ð;±
 u˜F–?³
ùs   ƒTFc              3   óD   <"  € T F  qP                  S4      x € K  	  R # 5irV   )Ú
startswithrü  s   & €r"   rJ   rþ  ï  s   øé € ÐMÁ*¸×,Ñ,¨V×4Ð4Ã*ùs   ƒ )r*   r‰   r¡  rZ   )r&  ÚvÚ
normalizedrý  s   &  €r"   ÚscoreÚ7SessionSearchMixin.search_sessions_by_id.<locals>.scoreë  s¬   ø€ Ü.1°#·'±'¸$³-×2EÐ2EÀ2Ó.FÌÈCÏGÉGÐTfÓLg×LmÐLmÐkmÓHnÑ-oÓuÑ-o¨Óstœ)˜!Ÿ'™'ž)Ñ-oˆJÐuß‹sÔ;±
Ó;�s�sŠsÔ;±
Ó;×;Ò;Ùß›ÔMÁ*ÓMŸŸšÔMÁ*ÓM×MÒM�1ÐTÐSTÐTùò vs   Á	C6ÁC6c                 ó4   <€ S! V ^,          4      V ^ ,          3# )rA  rF   )Úitemr  s   &€r"   Ú<lambda>Ú:SessionSearchMixin.search_sessions_by_id.<locals>.<lambda>ð  s   ø€ ÁÀtÈAÅwÃÐQUÐVWÕQXÑ@Yr$   )r6   N)r+   r¡  Úlist_sessions_richrÅ  rº   r,  )rÉ   r  r  rñ  rò  ró  r¡   Ú
candidatesÚrankedrI   r&  rý  r  s   &&&&&&&    @@r"   Úsearch_sessions_by_idÚ(SessionSearchMixin.search_sessions_by_idÝ  sª   ù€ ð
 —+�+˜2×$Ñ$Ó&×,Ñ,Ó.ˆß˜ !œØˆIð ×,Ñ,Ø¸OÔSVÐW\Ð_`ÕW`ÐbgÓShØÐ'7ÈdÐ]cð -ó eˆ
÷	Uð 	Uô
 œ	 *Ó-Ô3YÔZˆØ"(¨¨%¡.Ô1¡.™˜“¡.Ò1Ð1ùÓ1s   ÂB!c                ó&   <€ V ^8„  d   QhRS[ RS[/# )r   rŽ   r   r4  )r!   r¯   s   "€r"   r#   r°   õ  s   ø€ ÷ ñ ¡cð ©dñ r$   c                ó~   €  V P                   P                  RV R24       R#   \        P                   d     R# i ; i)u˜   True if an FTS5 virtual table is queryable ("no such table" and "vtable
constructor failed" â€” missing tokenizer / mid-teardown â€” both count as not).zSELECT 1 FROM z LIMIT 0TF)r`  r:   r7   r…  )rÉ   rŽ   s   &&r"   Ú_fts_table_existsÚ$SessionSearchMixin._fts_table_existsõ  s=   € ð	Ø�J‰J×Ñ °¨v°XÐ>Ô?ÙøÜ×$Ñ$ô 	Úð	ús   ‚# £<»<c                ó0   <€ V ^8„  d   QhRS[ S[,          /# r   )ra   r*   )r!   r¯   s   "€r"   r#   r°   þ  s   ø€ ÷ Pñ P¡T©#¥Yñ Pr$   c                ót   € V P                    Uu. uF  qP                  V4      '       g   K  VNK  	  up# u upi )z3Queryable FTS tables (caller holds ``self._lock``).)Ú_FTS_TABLESr  )rÉ   rF  s   & r"   Ú_present_fts_tablesÚ&SessionSearchMixin._present_fts_tablesþ  s/   € à#×/Ò/ÓOÑ/˜×3IÑ3IÈ#×3N—�Ñ/ÑOÐOùÒOs   �5¬5c                ó    <€ V ^8„  d   QhRS[ /# r   r“  )r!   r¯   s   "€r"   r#   r°     s   ø€ ÷ ñ ™cñ r$   c           
     óä  € V P                  4        ^ pV P                  ;_uu_ 4        V P                  4        V P                  f   V P	                  RR7       V P                  4        F/  p V P                  P                  RV RV R24       V^,          pK1  	  RRR4       V#   \        P                   d"   p\        P                  RY#4        Rp?Kn  Rp?ii ; i  + '       g   i     T# ; i)a›  Merge fragmented FTS5 segments into one per index (``'optimize'``). Pure
maintenance: changes neither results nor ``snippet()`` output, only layout and
speed; VACUUM then returns the freed pages. Returns the number optimized. A quarantined
handle never issues ``'optimize'``: it rewrites index segments in place and would compound
structural damage (or a split WAL generation) instead of leaving it diagnosable.NÚwrite©r°  ru  rv  z) VALUES('optimize')zFTS optimize failed for %s: %s)Ú_raise_if_db_corruptr^  Ú_raise_if_db_replacedr`  Ú_reopen_after_close_lockedr  r:   r7   r…  rÇ   rÈ   )rÉ   Ú	optimizedrF  rÊ   s   &   r"   Úoptimize_ftsÚSessionSearchMixin.optimize_fts  sÒ   € ð 	×!Ñ!Ô#Øˆ	Ø�Z�Z‹ZØ×&Ñ&Ô(Ø�z‰zÒ!Ø×/Ñ/¸Ð/Ô@Ø×/Ñ/Ö1�ðOØ—J‘J×&Ñ&¨°c°U¸!¸C¸5Ð@TÐ'UÔVØ •N’Iñ 2÷	 ð Ðøô ×,Ñ,ô OÜ—N‘NÐ#CÀS×NÒNûðOú÷ ŽZð Ðús6   §ACÁ,+B%ÂCÂ%C	Â:C	ÃCÃC	ÃCÃC/	c                ó    <€ V ^8„  d   QhRS[ /# r   r“  )r!   r¯   s   "€r"   r#   r°     s   ø€ ÷ ,ñ ,™Sñ ,r$   c                óh  € V P                  4        ^ p\        V P                  4      ;_uu_ 4       pV'       g!   \        P	                  R4        RRR4       ^ # V P
                  ;_uu_ 4        V P                  4        V P                  f   V P                  RR7       V P                  4        FI  p V P                  P                  RV RV R24       V P                  P                  4        V^,          pKK  	  RRR4       RRR4       V#   \        P                   di   pT P                  P                  4        \        T4      '       d   \        P!                  RY44        Rp?K»  \        P	                  R	Y44        Rp?K×  Rp?ii ; i  + '       g   i     Lš; i  + '       g   i     T# ; i)
u)  Rebuild FTS5 indexes from ``messages`` (``'rebuild'``) â€” the recovery for a corrupt index
that rejects writes while reads succeed. Two processes rebuilding one state.db
concurrently corrupted production DBs, so this admits through
``fts_rebuild_admission`` and FAILS CLOSED, returning 0 on deferral (callers treat 0
as "no progress" and use the stale-FTS breadcrumb path). Returns indexes rebuilt.

Uses the FTS5 ``'rebuild'`` command, which rewrites the internal b-tree segments from the content
rows. Unlike ``optimize_fts`` (which merges existing segments), ``rebuild`` discards and recreates
the index data entirely â€” the more destructive of the two, so it is quarantined the same way. See
#50502.
A full structural rebuild must never run concurrently in two processes sharing one state.db â€” that
interleaving has structurally corrupted the database in production (PR #93200) â€” so this admits
through the cross-process ``fts_rebuild_admission`` authority and FAILS CLOSED: if another process
holds the rebuild lock beyond the bounded wait, this call defers (returns 0) rather than racing it.
Callers already treat 0 as "rebuild made no progress" and fall back to the stale-FTS breadcrumb
path, which retries in-process from the gateway housekeeping tick (``retry_deferred_fts_recovery``)
and at next startup.
z]Deferred in-place FTS rebuild: another process holds the rebuild authority for this state.db.Nr  r  ru  rv  z) VALUES('rebuild')z‘FTS rebuild failed with a corruption-class error for %s: %s; run 'hermes sessions repair' (or 'hermes doctor --fix') to rebuild the index offlinezFTS rebuild failed for %s: %s)r  r   Údb_pathrÇ   rÈ   r^  r  r`  r  r  r:   ra  r7   r…  Úrollbackr
   Úerror)rÉ   ÚrebuiltÚadmittedrF  rÊ   s   &    r"   Úrebuild_ftsÚSessionSearchMixin.rebuild_fts  sV  € ð& 	×!Ñ!Ô#ØˆÜ" 4§<¡<×0Ô0°HßÜ—‘Øsôuà÷	 1Ñ0ð
 ——“Ø×*Ñ*Ô,Ø—:‘:Ò%Ø×3Ñ3¸GÐ3ÔDØ×3Ñ3Ö5�CðVØŸ
™
×*Ñ*¨\¸#¸¸aÀ¸uÐDWÐ+XÔYØŸ
™
×)Ñ)Ô+Ø 1�šñ	 6÷	 ÷ 1ð. ˆøô #×0Ñ0ô VØŸ
™
×+Ñ+Ô-Ü0°×5Ò5Ü"ŸL™Lð!wà #÷*ò *ô
 #ŸN™NÐ+JÈC×UÒUûðVú÷ —ú÷ 1Ö0ð. ˆúsb   °F ÁF Á-AF	Â2AD
Ã7F	Ã;F ÄF
Ä"AFÅ#F	Å)FÅ?F	ÆF
Æ
F	ÆFÆF Æ F1	Úmax_commandsc                ó<   <€ V ^8„  d   QhRS[ RS[S[ ,          RS[ /# )r   rÂ   r*  r   )rR   r   )r!   r¯   s   "€r"   r#   r°   D  s(   ø€ ÷ ñ ±Sð ÉÑQTÍð Ñadñ r$   c               óT  € \        RV4       Vf   V P                  p\        RV4       ^ pV P                  ;_uu_ 4        V P                  4        F±  pV P                  '       g#   V P
                  P                  RV RV R24       \        V4       Fk  pV P
                  P                  pV P
                  P                  RV RV R2V34       V^,          pV P
                  P                  V,
          ^8  g   Kj   K¯  	  K³  	  RV n        RRR4       V#   + '       g   i     T# ; i)	a  Run bounded FTS5 ``'merge'`` commands against each present index. A positive merge rank
stops after ~that many output pages, so each command holds the write lock for
milliseconds regardless of index size (``'optimize'`` takes 9-18 s per index on a
10 GB DB). ``usermerge`` is lowered to its minimum of 2 (persisted in ``%_config``,
once per instance) so a merge acts on ANY level with >= 2 segments; at the default
4 a fragmented index cannot converge. Up to *max_commands* per index, stopping on
the no-progress signal ``total_changes`` delta < 2 (the INSERT itself is 1). Each
command is its own implicit transaction, so processes interleave mid-pass. Missing
tables are skipped (optimize_fts_storage drops + backfills them live); other SQLite
errors propagate. Returns commands executed.rÂ   Nr*  ru  rv  z, rank) VALUES('usermerge', 2)z, rank) VALUES('merge', ?)T)	r�   Ú_FTS_MERGE_COMMANDS_PER_PASSr^  r  Ú_fts_usermerge_floor_appliedr`  r:   r·  Útotal_changes)rÉ   rÂ   r*  ÚexecutedrF  rI   Úbefores   &$$    r"   rÄ   Ú+SessionSearchMixin._merge_fts_incrementallyD  s  € ô 	�k 9Ô-ØÒØ×<Ñ<ˆLÜ�n lÔ3ØˆØ�Z�Z‹ZØ×/Ñ/Ö1�Ø×8×8Ð8Ø—J‘J×&Ñ&¨°c°U¸!¸C¸5Ð@^Ð'_Ô`Ü˜|Ö,�AØ!ŸZ™Z×5Ñ5�FØ—J‘J×&Ñ&¨°c°U¸!¸C¸5Ð@ZÐ'[Ð^gÐ]iÔjØ •M�HØ—z‘z×/Ñ/°&Õ8¸1Ö<Úó -ñ 2ð 15ˆDÔ-÷ ð ˆ÷ ŽZð ˆús   ¿B8DÃ<DÄD'	)rþ   rÃ   rs  r.  rð   )
râ  rØ  rã  Úsnippetr  Ú	tool_namerò  ÚmodelÚsession_startedr°  )é   r[  )ÚuserÚ	assistant)é   FrV   )
NNNr:  r!  NFNNN)r:  TNNN)GÚ__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__r¹   Úclassmethodr½   rË   rÔ   rÙ   rÒ   rý   rï   r   rñ   r   r  r  rõ   r   rò   r  r   r  rO  rZ  rX  rf  rn  Ústaticmethodrl  rm  r€  r‰  r�  rš  r™  r§  r¯  rÕ  r÷  r  r0  r+  rD  rI  rU  r_  re  ri  ru  r~  rˆ  r�  r›  r£  rª  r¾  rÈ  rÅ  rÔ  rÕ  r  r  r  r  r(  rÄ   Ú__static_attributes__Ú__classdictcell__)r¯   s   @r"   r«   r«   Â   sˆ  ø‡ € ÙHð%Ð!ð ÷
aó ð
a÷Qð Q÷"3ð 3÷
7ð 7÷xð xð$	Mð ð	Rð ð	AáAXÐY\ÓA]Ð@^ð _Zð	Zð  ð	1à1NÐ0Oð P`ð	`ð ð	AáAXÐY\ÓA]Ð@^ð	`ð ÷	Oð 	O÷Oð O÷!ð !÷
[ð 
[÷\ð \ð'Ø05÷'ò '÷R>ð >÷@0ð 0÷& ð  ÷
	ð 	÷
Dð 
Dð ÷ó ðð ÷Oó ðOð¸u÷ ò ÷!ð !÷&ð ÷((ð (÷T	 ð 	 ÷ð ÷0ð ð.M3ØKOðM3Ø`d÷M3ò M3÷b,
ò ,
÷\#ò #ðN ÷"!ó ð"!ðH ÷4ó ð4ð ÷9ó ð9ð ÷ó ðð ÷?ó ð?ð ÷Aó ðAð ÷kó ðk÷
-ð -÷ð ð& ÷ó ððÐimð Ø7;÷ò ÷,Bð Bð
 ÷#Bó ð#B÷J
uð 
u÷
Zð 
Z÷ò ÷>*ò *÷.hSò hS÷Tcð c÷:4ð 4÷"2ò 2÷0ð ÷Pð P÷ð ÷(,ð ,ð\ÐX\÷ ÷ ð r$   r«   >   rO  rM  rP  ))i N  iÿŸ  )i 4  i¿M  )i   iß¦ )i 0  i?0  )i@0  iŸ0  )i 0  iÿ0  )i ¬  i¯×  )Dr?  rx  Úloggingr  r7   rÂ  Útypingr   r   r   r   r   r   r   Úagent.skill_commandsr	   Úhermes_state_errorsr
   Úhermes_state_commonr   r   r   r   r   r   r   r   r   r   r   ru   r   r   r   Úhermes_state_ftsr   r   Ú	getLoggerrÇ   r/   Ú_FTS5_SPECIAL_CHARSÚcompileÚescaper*  Ú	frozensetre   rí  rQ  r(  r™   rŒ  rá  r”  rÓ  r¸  r[   r=   rO   r\   rg   rp   rv   r~   rŒ   r�   rš   r©   r«   rF   r$   r"   Ú<module>rO     sY  ðñó Û Û 	Û Û ß I× IÑ Iå :Ý 5÷÷ ÷ ÷ ÷ Bð 
×	Ò	˜>Ó	*€õð 5Ð Ø—:’:  "§)¢)Ð,?Ó"@Ð!AÀÐCÓDÐ áÒ/Ó0€Ø" f XÕ-Ð Ø—’˜NÓ+€Ø—J’J˜zÓ*Ð ð eÐ ØYÐ àið ð5ð ð Ð<¸hÐHhÐi€ð$Ð ð€õYõ
_õ9õMõõ+õ
+õ07õ>õð&ð #ð&ð @D÷&÷<]ó ]r$   