+
    jHàjåo  ã                   óš  a € R* t.0 t R t^ RIt^ RIt^ RIt^ RIt^ RIt^ RI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 ^ RIHtHt ]P0                  ! R4      tRtR	tR
 R ltR R ltR R ltR+tR R lt R R lt!R R lt"RR,RR-/t#] ^ k 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t*R.t+R& R' lt, ! R( R)4      t-R# )/zãFTS5 index setup for SessionDB: the CJK-bigram (cjk_unicode61) index DDL and
tokenizer loader, per-table schema ensure with tokenizer-capability fallback,
FTS-scoped corruption detection and the atomic fail-open trigger detach.N©ÚPath)ÚSequence)Úget_hermes_home)ÚFTS_CJK_STALE_KEYÚFTS_SQLÚFTS_STALE_KEYÚFTS_TRIGRAM_SQLÚ_FTS_CJK_TRIGGERSÚ_FTS_TRIGGERSÚrouted_sessions_setting)Úis_fts_scoped_corruption_errorÚis_sqlite_lock_errorÚhermes_statea\  
CREATE VIEW IF NOT EXISTS messages_fts_cjk_src AS
    SELECT id, role, content, tool_name, tool_calls
    FROM messages
    WHERE role <> 'tool';

CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts_cjk USING fts5(
    content,
    tool_name,
    tool_calls,
    content='messages_fts_cjk_src',
    content_rowid='id',
    tokenize='cjk_unicode61'
);
a	  
CREATE TRIGGER IF NOT EXISTS messages_fts_cjk_insert AFTER INSERT ON messages
WHEN new.role <> 'tool'
   AND (new.id > COALESCE((SELECT CAST(value AS INTEGER) FROM state_meta
                           WHERE key = 'fts_cjk_rebuild_high_water'), -1)
     OR new.id <= COALESCE((SELECT CAST(value AS INTEGER) FROM state_meta
                            WHERE key = 'fts_cjk_rebuild_progress'), -1))
BEGIN
    INSERT INTO messages_fts_cjk(rowid, content, tool_name, tool_calls)
    VALUES (new.id, new.content, new.tool_name, new.tool_calls);
END;

CREATE TRIGGER IF NOT EXISTS messages_fts_cjk_delete AFTER DELETE ON messages
WHEN old.role <> 'tool'
   AND (old.id > COALESCE((SELECT CAST(value AS INTEGER) FROM state_meta
                           WHERE key = 'fts_cjk_rebuild_high_water'), -1)
     OR old.id <= COALESCE((SELECT CAST(value AS INTEGER) FROM state_meta
                            WHERE key = 'fts_cjk_rebuild_progress'), -1))
BEGIN
    INSERT INTO messages_fts_cjk(messages_fts_cjk, rowid, content, tool_name, tool_calls)
    VALUES ('delete', old.id, old.content, old.tool_name, old.tool_calls);
END;

CREATE TRIGGER IF NOT EXISTS messages_fts_cjk_update
AFTER UPDATE OF content, tool_name, tool_calls, role ON messages
WHEN (old.content IS NOT new.content
    OR old.tool_name IS NOT new.tool_name
    OR old.tool_calls IS NOT new.tool_calls
    OR old.role IS NOT new.role)
   AND (old.id > COALESCE((SELECT CAST(value AS INTEGER) FROM state_meta
                           WHERE key = 'fts_cjk_rebuild_high_water'), -1)
     OR old.id <= COALESCE((SELECT CAST(value AS INTEGER) FROM state_meta
                            WHERE key = 'fts_cjk_rebuild_progress'), -1))
BEGIN
    INSERT INTO messages_fts_cjk(messages_fts_cjk, rowid, content, tool_name, tool_calls)
    SELECT 'delete', old.id, old.content, old.tool_name, old.tool_calls
    WHERE old.role <> 'tool';
    INSERT INTO messages_fts_cjk(rowid, content, tool_name, tool_calls)
    SELECT new.id, new.content, new.tool_name, new.tool_calls
    WHERE new.role <> 'tool';
END;
c                ó$   € V ^8„  d   QhR\         /# ©é   Úreturnr   )Úformats   "Ú5/home/ubuntu/.hermes/hermes-agent/hermes_state_fts.pyÚ__annotate__r   e   s   € ÷ [ñ [œ$ñ [ó    c                 ó    € \         P                  ! R4      p V '       d   \        V 4      P                  4       # \	        4       R,          R,          # )z1Location of the cjk_unicode61 loadable extension.ÚHERMES_FTS5_CJK_SOÚlibzlibfts5_cjk.so)ÚosÚgetenvr   Ú
expanduserr   )Úenvs    r   Úfts5_cjk_so_pathr   e   s9   € ä
�)Š)Ð(Ó
)€Cß%(Œ4�‹9×ÑÓ!ÐZ¬oÓ.?À%Õ.GÐJZÕ.ZÐZr   c                ó$   € V ^8„  d   QhR\         /# r   ©Úbool)r   s   "r   r   r   k   s   € ÷ Zñ Z¤ñ Zr   c                 ó†   € \        RR4      p V RJ ;'       g+    \        V 4      P                  4       P                  4       R9  # )zKconfig.yaml ``sessions.cjk_fts`` (default on) for the profile being served.Úcjk_ftsÚHERMES_CJK_FTSN)Ú0ÚfalseÚoffÚno)r   ÚstrÚstripÚlower)Úvalues    r   Ú_cjk_fts_config_enabledr.   k   s=   € ä# IÐ/?Ó@€EØ�Dˆ=×YÐYœC ›J×,Ñ,Ó.×4Ñ4Ó6Ð>YÑYÐYr   c                óD   € V ^8„  d   QhR\         P                  R\        /# )r   Úconnr   )Úsqlite3Ú
Connectionr"   )r   s   "r   r   r   q   s    € ÷ ñ ¤'×"4Ñ"4ð ¼ñ r   c                óf  € \        4       p\        4       '       d   VP                  4       '       g   R#  V P                  R4        V P	                  \        V4      4       V P                  R4       R#   T P                  R4       i ; i  \         d    \        P                  RTRR7        R# i ; i)z–Best-effort load of the cjk_unicode61 tokenizer; False (never raises) when
the .so is absent, ``sessions.cjk_fts`` is off, or loading is compiled out.FTz#fts5_cjk extension load failed (%s)©Úexc_info)	r   r.   ÚexistsÚenable_load_extensionÚload_extensionr*   Ú	ExceptionÚloggerÚwarning)r0   Úpaths   & r   Úload_fts5_cjk_extensionr=   q   s“   € ô Ó€DÜ"×$Ò$¨D¯K©K¯MªMÙð	Ø×"Ñ" 4Ô(ð	.Ø×Ñ¤ D£	Ô*à×&Ñ& uÔ-Ùøð ×&Ñ& uÕ-ûäô Ü�‰Ð<¸dÈTˆÔRÚðús)   ´B	 ÁA3 Á B	 Á3BÂB	 Â	#B0Â/B0Úmessages_ftsÚmessages_fts_trigramc                ó0   € V ^8„  d   QhR\         R\         /# )r   Úsqlr   ©r*   )r   s   "r   r   r   †   s   € ÷ Yñ Y”cð Yœcñ Yr   c                ó°   € \         P                  ! RRRP                  T ;'       g    RP                  4       4      4      p\         P                  ! RRV4      # )a#  ``sqlite_master.sql`` as compared here: whitespace collapsed, none kept around parentheses and commas (the
same definition written with different spacing is the same layout), and the object name unquoted (an ALTER
TABLE RENAME round trip stores it as ``"name"`` without changing the layout).z\s*([(),])\s*z\1Ú Ú z&^CREATE (VIRTUAL TABLE|VIEW) "([^"]+)"zCREATE \1 \2)ÚreÚsubÚjoinÚsplit)rA   Ú	collapseds   & r   Ú_stored_formrK   †   sC   € ô —’Ð'¨°·±¸#¿)¸)À×9JÑ9JÓ9LÓ0MÓN€IÜ�6Š6Ð;¸_ÈiÓXÐXr   c                óH   € V ^8„  d   QhR\         R\         R\         R\         /# )r   ÚddlÚkindÚnamer   rB   )r   s   "r   r   r   Ž   s.   € ÷ Iñ Iœcð I¬ð I´Cð I¼Cñ Ir   c                ó’   € V P                  RV RV R24      pV P                  RV4      p\        WV P                  RR^4      4      # )z³The stored form of the ``CREATE <kind> IF NOT EXISTS <name>`` statement in *ddl* (``IF NOT EXISTS``
and ``;`` dropped), i.e. what ``sqlite_master.sql`` holds once the DDL has run.zCREATE z IF NOT EXISTS rD   Ú;z IF NOT EXISTSrE   )ÚindexrK   Úreplace)rM   rN   rO   ÚstartÚends   &&&  r   Ú_expected_stored_sqlrV   Ž   sP   € ð �I‰I˜ ˜v _°T°F¸!Ð<Ó=€EØ
�)‰)�C˜Ó
€CÜ˜ #˜×.Ñ.Ð/?ÀÀQÓGÓHÐHr   c                ó<   € V ^8„  d   QhR\         R\         R\         /# )r   rM   Útabler   rB   )r   s   "r   r   r   –   s!   € ÷ =ñ =œcð =¬#ð =´#ñ =r   c                 ó   € \        V R V4      # )zVIRTUAL TABLE)rV   )rM   rX   s   &&r   Ú_expected_vtable_sqlrZ   –   s   € Ü  _°eÓ<Ð<r   c                óf   € V ^8„  d   QhR\         P                  R\        \        \        3,          /# ©r   Úcursorr   )r1   ÚCursorÚdictr*   )r   s   "r   r   r   ´   s&   € ÷ ;ñ ;œGŸN™Nð ;¬t´C¼°H­~ñ ;r   c                 ó–   € V P                  R \        4      P                  4       pV UUu/ uF  w  r#V\        V4      bK  	  upp# u uppi )zKSELECT name, sql FROM sqlite_master WHERE type = 'table' AND name IN (?, ?))ÚexecuteÚ_FTS_DDL_TABLESÚfetchallrK   )r]   ÚrowsrO   rA   s   &   r   Ú_stored_vtablesre   ´   sG   € Ø�>‰>ØUÔWfóhßhpÑhpÓhrð 	á59Ô:±T©	¨ˆD”,˜sÓ#Ò#±TÒ:Ð:ùÓ:s   ªAc                ó<   € V ^8„  d   QhR\         R\         R\        /# )r   rX   Ústoredr   ©r*   r"   )r   s   "r   r   r   º   s!   € ÷ *ñ *œCð *¬ð *´ñ *r   c                ón   € V\         V ,          RR 9   d   R# V R8X  d   RV9  # RVP                  4       9   # )zÏA definition an earlier release created. The two shape checks keep every store the pre-registry code
treated as old (any base index without ``tool_name``, any trigram with ``tool_calls``) on the opt-in path.NTr>   Ú	tool_nameÚ
tool_callséÿÿÿÿ)ÚFTS_VTABLE_LAYOUTSr,   )rX   rg   s   &&r   Ú_is_older_layoutrn   º   s@   € ð Ô# EÕ*¨3¨BÐ/Ô/ÙØ�ÔØ &Ñ(Ð(Ø˜6Ÿ<™<›>Ñ)Ð)r   c                óD   € V ^8„  d   QhR\         P                  R\        /# r\   ©r1   r^   r"   )r   s   "r   r   r   Ä   s"   € ÷ ]ñ ]¤'§.¡.ð ]´Tñ ]r   c                óR   € \        V 4      P                  R4      \        R,          9   # )zy``messages_fts`` holds a definition this code or an earlier release created; the open-time realign
replaces nothing else.r>   )re   Úgetrm   )r]   s   &r   Úbase_index_recognisedrs   Ä   s%   € ô ˜6Ó"×&Ñ& ~Ó6Ô:LÈ^Õ:\Ñ\Ð\r   c                ó0   € V ^8„  d   QhR\         R\        /# )r   rg   r   rh   )r   s   "r   r   r   Ê   s   € ÷ Fñ F¬ð F´ñ Fr   c                ó&   € RV 9   ;'       d    RV 9  # )z¹External-content base index not reading ``messages_fts_src``: the next read-write open rebuilds it from
the current DDL (``_migrate_misaligned_fts_source``), so it is never opt-in work.rj   Úmessages_fts_src© )rg   s   &r   Úbase_index_realigned_on_openrx   Ê   s   € ð ˜&Ñ ×EÐEÐ%7¸vÑ%EÐEr   c                óZ   € V ^8„  d   QhR\         P                  R\        \        ,          /# r\   )r1   r^   Úlistr*   )r   s   "r   r   r   Ð   s&   € ÷ 	Xñ 	XœWŸ^™^ð 	X´´Sµ	ñ 	Xr   c                óÂ   € \        V 4      P                  4        UUu. uF4  w  r\        W4      '       g   K  VR8X  d   \        V4      '       d   K2  VNK6  	  upp# u uppi )a“  FTS tables on an older release's definition that only ``optimize-storage`` replaces; empty otherwise.

Shared by the update notice, doctor and optimize-storage's demote decision. An absent table is not drift
(the trigram index is optional; the base index is created on open), nor is a base index the next open
realigns, nor a definition this code does not recognise. The cjk index owns its own markers.
r>   )re   Úitemsrn   rx   )r]   rX   rg   s   &  r   Úfts_layout_driftr}   Ð   s\   € ô (7°vÓ'>×'DÑ'DÔ'Fô XÑ'F‘m�eÜ ×.ô à˜nÔ,Ô1MÈf×1U÷ ˆEÑ'Fò Xð Xùó Xs   �A·AÁAc                óD   € V ^8„  d   QhR\         P                  R\        /# r\   rp   )r   s   "r   r   r   Ü   s"   € ÷ 	qñ 	qœwŸ~™~ð 	q´$ñ 	qr   c                óx  € \        V 4      p\        ;QJ d*    R VP                  4        4       F  '       g   K   RM	  RM! R VP                  4        4       4      '       d   R# RV9  d   R# V P                  R4      P	                  4       pVRJ;'       d%    \        V^ ,          4      \        \        RR4      8H  # )	z­True when every present FTS table, and the base index's source view, is exactly what this code creates:
the only state the ``fts_storage_version`` stamp may be written over.c              3   óT   "  € T F  w  rV\         V,          R,          8g  x € K   	  R# 5i)é   Nrl   )rm   )Ú.0rX   rA   s   &  r   Ú	<genexpr>Ú%fts_layout_current.<locals>.<genexpr>à   s"   é € Ð
QÁ.±J°Eˆ3Ô$ UÕ+¨BÕ/Ö/Ã.ùs   ‚&(TFr>   úOSELECT sql FROM sqlite_master WHERE type = 'view' AND name = 'messages_fts_src'NÚVIEWrv   )re   Úanyr|   ra   ÚfetchonerK   rV   r   )r]   rg   Úrows   &  r   Úfts_layout_currentrŠ   Ü   s�   € ô ˜VÓ$€Fß
ƒsÑ
QÀ&Ç,Á,Ä.Ó
Q‡s‡s‚sÑ
QÀ&Ç,Á,Ä.Ó
Q×QÒQÙØ˜VÔ#ÙØ
�.‰.ÐjÓ
k×
tÑ
tÓ
v€CØ�dˆ?×pÐpœ|¨C°­FÓ3Ô7KÌGÐU[Ð]oÓ7pÑpÐpr   c                óD   € V ^8„  d   QhR\         P                  R\        /# r\   rp   )r   s   "r   r   r   è   s"   € ÷ qñ q¤g§n¡nð q¼ñ qr   c                ó    € V P                  R4      P                  4       pVRJ;'       d%    \        V^ ,          4      \        \        RR4      8g  # )zv``messages_fts_src`` exists but differs from what this code creates (``CREATE VIEW IF NOT EXISTS`` never
replaces it).r…   Nr†   rv   )ra   rˆ   rK   rV   r   ©r]   r‰   s   & r   Úbase_source_view_is_stalerŽ   è   sH   € ð �.‰.ÐjÓ
k×
tÑ
tÓ
v€CØ�dˆ?×pÐpœ|¨C°­FÓ3Ô7KÌGÐU[Ð]oÓ7pÑpÐpr   c                ó|   € V ^8„  d   QhR\         P                  R\        \        ,          R\        \        ,          /# )r   r]   Úfamiliesr   )r1   r^   r   r*   rz   )r   s   "r   r   r   ö   s0   € ÷ ñ ¬7¯>©>ð ÄXÌcÅ]ð ÔW[Ô\_ÕW`ñ r   c                ó  € . pV Fõ  pV P                  RV34      P                  4       pV'       d   K.  \         Uu. uF	  qS RV 2NK  	  ppV P                  RRP                  R V 4       4       R2V4      P	                  4        Uu. uF  qw^ ,          NK  	  ppV'       g   K   V F  p	V P                  RV	 R24       K  	  \
        P                  R	VR
P                  V4      4       VP                  V4       K÷  	  V# u upi u upi )aƒ  Drop, per family, shadow tables whose virtual table row is absent from sqlite_master.

Matches exact shadow names only (never a prefix LIKE, so the base family cannot reach
``messages_fts_trigram_*``) and leaves a family alone whenever its vtable is live. The
shadows are derived index state; the caller recreates and rebuilds from ``messages``.
Returns the families that were repaired.
zbSELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ? AND sql LIKE 'CREATE VIRTUAL TABLE%'Ú_zASELECT name FROM sqlite_master WHERE type = 'table' AND name IN (Ú,c              3   ó&   "  € T F  pR x € K	  	  R# 5i©Ú?Nrw   ©r‚   r’   s   & r   rƒ   Ú1_drop_orphan_fts_shadow_tables.<locals>.<genexpr>	  s   é € ÐXnÑfmÐabÕY\Ófmùó   ‚Ú)zDROP TABLE "Ú"zRDropped orphan FTS5 shadow tables of %s (%s); the index is recreated from messagesz, )ra   rˆ   Ú_FTS5_SHADOW_SUFFIXESrH   rc   r:   r;   Úappend)
r]   r�   ÚrepairedÚfamilyÚvtable_liveÚsuffixÚshadowsr‰   ÚorphansrO   s
   &&        r   Ú_drop_orphan_fts_shadow_tablesr¤   ö   s  € ð €HÛˆØ—n‘nð3àˆIó
÷ ‰(‹*ð	 	÷
 ÙÝ6KÓLÑ6K¨F�X˜Q˜v˜hÓ'Ñ6KˆÐLØ%+§^¡^ØOÐPS×PXÑPXÑXnÑfmÓXnÓPnÐOoÐopÐqØó&
÷ ‰(‹*ð&ó ñ &˜c�q—6�6ñ &ˆð ÷ ÙÛˆDØ�N‰N˜\¨$¨¨qÐ1Ö2ñ ä�‰Ø`Ø�D—I‘I˜gÓ&ô	
ð 	�‰˜Öñ+ ð, €Oùò Mùòs   »D ÂDc                   ót  a € ] tR tRt o Rt]V 3R lR l4       t]V 3R lR l4       t]V 3R lR l4       t]V 3R	 lR
 l4       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4       tV 3R lR lt]V 3R lR l4       tRRRR/V 3R lR lltRtRtR tR!tV 3R" lR# ltR%tR$tV tR# )&ÚSessionFtsSetupMixini  zOFTS table/trigger lifecycle shared by schema init, optimize and the write path.c                ó:   <€ V ^8„  d   QhRS[ P                  RS[/# ©r   Úexcr   ©r1   ÚOperationalErrorr"   )r   Ú__classdict__s   "€r   r   Ú!SessionFtsSetupMixin.__annotate__  s%   ø€ ÷ vñ v©×(@Ñ(@ð vÁTñ vr   c                ó’   € \        V 4      P                  4       pRV9   ;'       d    RV9   ;'       g    \        P                  V 4      # )zONo FTS5 module, or an optional tokenizer missing (same capability-error shape).zno such moduleÚfts5)r*   r,   r¦   Ú_is_trigram_unavailable_error©r©   Úerrs   & r   Ú_is_fts5_unavailable_errorÚ/SessionFtsSetupMixin._is_fts5_unavailable_error  s@   € ô �#‹h�n‰nÓˆØ  CÑ'×9Ð9¨F°c©M×uÐuÔ>R×>pÑ>pÐqtÓ>uÐur   c                ó:   <€ V ^8„  d   QhRS[ P                  RS[/# r¨   rª   )r   r¬   s   "€r   r   r­   "  s%   ø€ ÷ `ñ `©7×+CÑ+Cð `Éñ `r   c                óX   € \        V 4      P                  4       pRV9   ;'       g    RV9   # )z—Only an optional tokenizer is missing (trigram needs SQLite >= 3.34;
cjk_unicode61 is loadable): "this one index can't be served", never "disable FTS".zno such tokenizer: trigramz no such tokenizer: cjk_unicode61)r*   r,   r±   s   & r   r°   Ú2SessionFtsSetupMixin._is_trigram_unavailable_error!  s.   € ô �#‹h�n‰nÓˆØ+¨sÑ2×_Ð_Ð6XÐ\_Ñ6_Ð_r   c                ó:   <€ V ^8„  d   QhRS[ P                  RS[/# r\   rp   )r   r¬   s   "€r   r   r­   )  s#   ø€ ÷ Eñ E©'¯.©.ð E¹Tñ Er   c                ó„   € V P                  R4      P                  4       pVRJ;'       d    RV^ ,          ;'       g    R9  # )u]  messages_fts exists in ANY pre-v23 shape: every legacy shape lacks
tool_name, so "stored CREATE lacks tool_name" catches them all. False when absent.

v23's messages_fts is external-content over THREE real columns (content, tool_name, tool_calls).
Every pre-v23 shape lacks the tool_name/tool_calls columns â€” whether the old inline single-column
form (v11..v22) or the even older external-content single-column form (v10-era, pre-#16751). We
therefore detect "needs optimize" as "the stored CREATE lacks the tool_name column", which is the
precise v23 marker and correctly catches BOTH legacy variants.
zLSELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'messages_fts'Nrj   rE   )ra   rˆ   r�   s   & r   Ú_db_has_legacy_inline_ftsÚ.SessionFtsSetupMixin._db_has_legacy_inline_fts(  sB   € ð �n‰nØZó
ç
‰(‹*ð 	ð ˜$ˆ×DÐD ;°s¸1µv·|°|ÀÑ#DÐDr   c                ó:   <€ V ^8„  d   QhRS[ P                  RS[/# r\   rp   )r   r¬   s   "€r   r   r­   9  s#   ø€ ÷ Jñ J±g·n±nð JÉñ Jr   c                ó    € V P                  R4      P                  4       pVRJ;'       d%    RV^ ,          ;'       g    RP                  4       9   # )z[True when the trigram vtable still includes the tool_calls payload (FTS_STORAGE_VERSION 1).zTSELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'messages_fts_trigram'Nrk   rE   )ra   rˆ   r,   r�   s   & r   Ú%_db_has_trigram_tool_calls_projectionÚ:SessionFtsSetupMixin._db_has_trigram_tool_calls_projection8  sK   € ð �n‰nØbó
ç
‰(‹*ð 	ð ˜$ˆ×IÐI <°C¸µF·L°L¸b×3GÑ3GÓ3IÑ#IÐIr   c                ó:   <€ V ^8„  d   QhRS[ P                  RS[/# r\   rp   )r   r¬   s   "€r   r   r­   A  s   ø€ ÷ .ñ .±7·>±>ð .Ádñ .r   c                ó*   € \        \        V4      4      # )zdTrue when optimize-storage has layout work: a stored FTS vtable differs from what this code creates.)r"   r}   )Úclsr]   s   &&r   Ú_db_needs_fts_storage_upgradeÚ2SessionFtsSetupMixin._db_needs_fts_storage_upgrade@  s   € ô Ô$ VÓ,Ó-Ð-r   c                ó8   <€ V ^8„  d   QhRS[ P                  RR/# ©r   r©   r   N©r1   r«   )r   r¬   s   "€r   r   r­   E  s!   ø€ ÷ 
ñ 
©W×-EÑ-Eð 
È$ñ 
r   c                óž   € \        V RR4      '       d   R# RV n        \        P                  RV P                  \
        P                  V4       R# )zHLog once that the trigram tokenizer is missing; base FTS5 stays enabled.Ú_trigram_unavailable_warnedFNTzˆSQLite trigram tokenizer unavailable for %s (requires SQLite >= 3.34, this build is %s); CJK/substring search will fall back to LIKE: %s)ÚgetattrrÉ   r:   ÚinfoÚdb_pathr1   Úsqlite_version©Úselfr©   s   &&r   Ú_warn_trigram_unavailableÚ.SessionFtsSetupMixin._warn_trigram_unavailableE  sC   € ä�4Ð6¸×>Ò>ÙØ+/ˆÔ(Ü�‰ð>ð �L‰LÜ×"Ñ"Øö	
r   c                ó8   <€ V ^8„  d   QhRS[ P                  RR/# rÆ   rÇ   )r   r¬   s   "€r   r   r­   S  s!   ø€ ÷ 
ñ 
©'×*BÑ*Bð 
Àtñ 
r   c                óŒ   € R V n         V P                  '       d   R# RV n        \        P                  RV P                  V4       R# )FNTzµSQLite FTS5 unavailable for %s; full-text session search disabled. Run `hermes update` to rebuild the venv with a current Python (managed uv guarantees FTS5). (underlying error: %s))Ú_fts_enabledÚ_fts_unavailable_warnedr:   r;   rÌ   rÎ   s   &&r   Ú_warn_fts5_unavailableÚ+SessionFtsSetupMixin._warn_fts5_unavailableS  s?   € Ø!ˆÔØ×'×'Ð'ÙØ'+ˆÔ$Ü�‰ðRð �L‰LØö	
r   c                ó   <€ V ^8„  d   QhRR/# )r   r   Nrw   )r   r¬   s   "€r   r   r­   `  s   ø€ ÷ K,ñ K,°ñ K,r   c                ó>  €  \        VP                  R4      P                  4       4      pV P                  '       gÃ   V'       d²   VP                  RRP	                  R \
         4       4       R2\
        4      P                  4        Uu. uF  q3^ ,          NK  	  ppV'       dS   \        P                  R\        4       4       VP                  R\        34       V F  pVP                  RV 24       K  	  R	V n        R
#   TP                  \        4       T'       g”   TP                  R\        34       TP                  R4      P                  4       ^ ,          ^ 8”  dR   TP                  R4      P                  4       ^ ,          pR\!        T4      3R3 F  w  rxTP                  RYx34       K  	  TP                  R\        34      P                  4       '       d
   R	T n        R
# TP                  \"        4       TP                  R4      P                  4       p	T	'       * T n        R
# u upi   \        P                   d#    \        P                  RRR7       R	T n         R
# i ; i  \        P                   d#    \        P                  RRR7       R	T n         R
# i ; i)u€  Create / repair / self-heal the CJK-bigram index (see the module comment).
Sets ``_fts_cjk_available``; never raises. Loaded + absent â†’ create (a
populated DB gets backfill markers and is NOT served until optimize-storage
backfills); loaded + present â†’ ensure triggers, honour the stale breadcrumb;
NOT loaded + live triggers â†’ drop them (INSERTs must not fail at trigger time).zNSELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'messages_fts_cjk'zCSELECT name FROM sqlite_master WHERE type = 'trigger' AND name IN (r“   c              3   ó&   "  € T F  pR x € K	  	  R# 5ir•   rw   r—   s   & r   rƒ   Ú>SessionFtsSetupMixin._ensure_fts_cjk_schema.<locals>.<genexpr>n  s   é € Ð0PÑ>O¸µÓ>Oùr™   rš   u
  messages_fts_cjk triggers present but the cjk_unicode61 tokenizer is unavailable (%s) â€” dropping the cjk triggers so message writes keep working. CJK search falls back to trigram/LIKE; run `hermes sessions optimize-storage` on a host with the extension to rebuild.z^INSERT INTO state_meta (key, value) VALUES (?, '1') ON CONFLICT(key) DO UPDATE SET value = '1'úDROP TRIGGER IF EXISTS FNzHmessages_fts_cjk presence check failed; CJK search stays on trigram/LIKETr4   z$DELETE FROM state_meta WHERE key = ?z2SELECT COUNT(*) FROM messages WHERE role <> 'tool'z)SELECT COALESCE(MAX(id), 0) FROM messagesÚfts_cjk_rebuild_high_waterzgINSERT INTO state_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.valuez&SELECT 1 FROM state_meta WHERE key = ?zISELECT 1 FROM state_meta WHERE key = 'fts_cjk_rebuild_high_water' LIMIT 1z@messages_fts_cjk ensure failed; CJK search stays on trigram/LIKE)Úfts_cjk_rebuild_progressr&   )r"   ra   rˆ   Ú_fts_cjk_loadedrH   r
   rc   r:   r;   r   r   Ú_fts_cjk_availabler1   r«   ÚexecutescriptÚFTS_CJK_TABLE_SQLr*   ÚFTS_CJK_TRIGGER_SQL)
rÏ   r]   Úcjk_presentÚrÚliveÚtrigÚhwÚkÚvÚbackfill_pendings
   &&        r   Ú_ensure_fts_cjk_schemaÚ+SessionFtsSetupMixin._ensure_fts_cjk_schema`  s‚  € ð%	Ü˜vŸ~™~Ø`ó ç‰h‹jóˆKð ×'×'Ð'ßØ*0¯.©.ð(Ø(+¯©Ñ0PÕ>OÓ0PÓ(PÐ'QÐQRðTä)ó+÷ ‘h“jð	+!ó "ñ +! Q˜aŸD˜Dñ +!�Dð "÷
 äŸ™ð=ô -Ó.ôð Ÿ™ðIä.Ð0ôó
 %)˜DØ"ŸN™NÐ-DÀTÀFÐ+KÖLñ %)à*/�Ô'Ùð5 (ðD	,Ø× Ñ Ô!2Ô3ßà—‘ÐEÔHYÐG[Ô\ð —>‘>Ð"VÓW×`Ñ`ÓbÐcdÕeÐhiÔiØŸ™Ð(SÓT×]Ñ]Ó_Ð`aÕb�Bà5´s¸2³wÐ?ÐAbó!™˜ð Ÿ™ðTà˜Föñ!ð �~‰~ÐFÔIZÐH\Ó]×fÑf×hÒhà*/�Ô'ÙØ× Ñ Ô!4Ô5Ø%Ÿ~™~Ø[ó ç‰h‹jð ð +;Ô&:ˆDÖ#ùòs"øô2 ×'Ñ'ô 	Ü�N‰NðØ)-ð ô ð ',ˆDÔ#Úð	ûôB ×'Ñ'ô 	,Ü�N‰NðØ)-ð ô ð ',ˆD×#ð	,úsR   ‚AH+ ÁAH+ ÂH&ÂA#H+ Ä I% ÄB=I% ÇI% Ç$A I% È&H+ È+3I"É!I"É%3JÊJc                ó8   <€ V ^8„  d   QhRS[ P                  RR/# )r   r]   r   N)r1   r^   )r   r¬   s   "€r   r   r­   ®  s   ø€ ÷ ñ ¡7§>¡>ð °dñ r   c                ó‚   € \          F  p V P                  R V 24       K  	  R#   \        P                   d     K6  i ; i)rÜ   N)r   ra   r1   r«   )r]   Útriggers   & r   Ú_drop_fts_triggersÚ'SessionFtsSetupMixin._drop_fts_triggers­  s?   € ç$ˆGðØ—‘Ð!8¸¸	ÐBÖCó %øô ×+Ñ+ô Úðús   ‹%¥>½>c                óF   <€ V ^8„  d   QhRS[ P                  RS[RS[RS[/# )r   r]   Ú
table_namerM   r   )r1   r^   r*   r"   )r   r¬   s   "€r   r   r­   µ  s.   ø€ ÷ ñ ©¯©ð ÁSð Ésð ÑW[ñ r   c                óB  € V P                  W4      pVf   R#  VP                  V4       R#   \        P                   d^   pT P	                  T4      '       g   h T P                  T4      '       d   T P                  T4       MT P                  T4        R p?R# R p?ii ; i)NFT)Ú_fts_table_proberá   r1   r«   r³   r°   rÐ   rÖ   )rÏ   r]   rô   rM   Ústatusr©   s   &&&&  r   Ú_ensure_fts_schemaÚ'SessionFtsSetupMixin._ensure_fts_schemaµ  sŠ   € Ø×&Ñ& vÓ:ˆØŠ>Ùð	à× Ñ  Ô%ÙøÜ×'Ñ'ô 	Ø×2Ñ2°3×7Ò7Øà×1Ñ1°#×6Ò6Ø×.Ñ.¨sÕ3à×+Ñ+¨CÔ0Ýûð	ús   ™, ¬BÁ.BÁ0#BÂBc                ó:   <€ V ^8„  d   QhRS[ P                  RS[/# r¨   )r1   ÚDatabaseErrorr"   )r   r¬   s   "€r   r   r­   È  s!   ø€ ÷ 3ñ 3©G×,AÑ,Að 3Ádñ 3r   c                ó   € \        V 4      # )aN  Corruption SQLite identifies as FTS-scoped (SQLITE_CORRUPT_VTAB, or an ``fts5:``
report naming ``messages_fts*`` on builds without result codes); a bare malformed
image is structural. One rule, shared with ``classify_persistence_error`` and the
gateway transcript retry: see :func:`hermes_state_errors.is_fts_scoped_corruption_error`.)r   )r©   s   &r   Ú_is_fts_write_corruption_errorÚ3SessionFtsSetupMixin._is_fts_write_corruption_errorÇ  s   € ô .¨cÓ2Ð2r   ÚdeadlineNÚ
patience_sc                ób   <€ V ^8„  d   QhRS[ P                  RS[R,          RS[R,          RS[/# )r   r©   rÿ   Nr   r   )r1   rû   Úfloatr"   )r   r¬   s   "€r   r   r­   Ï  sC   ø€ ÷ Hñ HÙ×(Ñ(ðHÙ7<¸tµ|ðHÙX]Ð`dÕXdðHá	ñHr   c          	     óZ  € V P                   '       d   V P                  V4      '       g   R# Vf   V P                  pVf   \        P                  ! 4       V,           p V P                  RR7        V P                  ;_uu_ 4        V P                  4        V P                  f   V P                  RR7       V P                  P                  R4        V P                  P                  R\        34       V P                  P                  R	R
P                  R \         4       4       R2\        4      P                  4       pV'       d"   V P                  P                  R\        34       V P!                  V P                  P#                  4       4       V P                  P%                  4         RRR4        RT n        RT n         RT n        RT n        \6        P9                  RT4       R#   \&         d    T P                  P)                  4        h i ; i  + '       g   i     Lo; i  \*        P,                   dq   p\/        T\*        P0                  4      '       d0   \3        T4      '       d   T P5                  Y#4      '       d    Rp?EK-  \6        P9                  RT4        Rp?R# Rp?ii ; i)u  Detach corrupt FTS indexes so canonical writes can continue. Breadcrumb +
trigger drop commit atomically: once triggers are absent the index has a
gap of unknown extent, so nobody may reinstall them without a full rebuild.

A busy write lock is waited out on the caller's write budget (default
``_WRITE_PATIENCE_S``), like ``_execute_write``: the writer connection's busy
timeout is only 1 s, and the usual holder is a sibling writer detaching the
same corrupt index â€” giving up after 1 s cost that turn's canonical write.FNT)ÚstorageÚwrite)ÚcontextzBEGIN IMMEDIATEziINSERT INTO state_meta (key, value) VALUES (?, '1') ON CONFLICT(key) DO UPDATE SET value = excluded.valuez@SELECT 1 FROM sqlite_master WHERE type = 'trigger' AND name IN (r“   c              3   ó&   "  € T F  pR x € K	  	  R# 5ir•   rw   r—   s   & r   rƒ   Ú<SessionFtsSetupMixin._enter_fts_fail_open.<locals>.<genexpr>ò  s   é € Ð4TÑBS¸QµSÓBSùr™   z	) LIMIT 1zNCould not detach corrupt FTS indexes; canonical write still cannot proceed: %sz­state.db FTS indexes remain corrupt (%s); disabled FTS sync and retrying the canonical write. Search temporarily uses LIKE until a later SessionDB open rebuilds the indexes.) rÔ   rý   Ú_WRITE_PATIENCE_SÚtimeÚ	monotonicÚ_raise_if_db_corruptÚ_lockÚ_raise_if_db_replacedÚ_connÚ_reopen_after_close_lockedra   r   rH   r
   rˆ   r   Ú_drop_all_fts_triggersr]   ÚcommitÚBaseExceptionÚrollbackr1   ÚErrorÚ
isinstancer«   r   Ú_sleep_before_write_retryr:   ÚerrorÚ
_fts_staleÚ_trigram_availablerà   )rÏ   r©   rÿ   r   Úcjk_triggers_presentÚ
detach_excs   &&$$  r   Ú_enter_fts_fail_openÚ)SessionFtsSetupMixin._enter_fts_fail_openÏ  s:  € ð × × Ð ¨×(KÑ(KÈC×(PÒ(PÙØÒØ×/Ñ/ˆJØÒÜ—~’~Ó'¨*Õ4ˆHØð ×%Ñ%¨dÐ%Ô3ð(Ø—Z—Z“ZØ×.Ñ.Ô0Ø—z‘zÒ)Ø×7Ñ7ÀÐ7ÔHØ—J‘J×&Ñ&Ð'8Ô9ðØŸ
™
×*Ñ*ðTä*Ð,ôð
 04¯z©z×/AÑ/Að,Ø,/¯H©HÑ4TÕBSÓ4TÓ,TÐ+Uð V&ð&ô .ó	0÷
 #™(›*ð -÷ 0Ø ŸJ™J×.Ñ.ð!Xä!2Ð 4ôð
 ×3Ñ3°D·J±J×4EÑ4EÓ4GÔHØŸ
™
×)Ñ)Õ+÷1  ð8 ð ˆŒØ!ˆÔØ"'ˆÔØ"'ˆÔÜ�‰ð;ð ô		
ñ øô3 )ô ØŸ
™
×+Ñ+Ô-Øðú÷3  —Zûô: —=‘=ô 
ä˜z¬7×+CÑ+C×DÒDÔI]Ð^h×IiÒIiØ×6Ñ6°x×LÒLæÜ—‘ØdØôõ ûð
úsR   Á0H% ÂAHÃA4G(ÅA$G(Æ+H% Ç('HÈHÈH"	ÈH% È%J*È:AJ%Ê	J%Ê%J*iô  g      @gš™™™™™É?Úfts_v22_trash_c                ó    <€ V ^8„  d   QhRS[ /# r   r!   )r   r¬   s   "€r   r   r­   (  s   ø€ ÷ ñ ¡dñ r   c           	     ó˜   € \        VP                  RV P                  P                  RR4      R,           34      P	                  4       4      # )zƒTrue when demoted v22 shadow tables are still awaiting teardown.
Caller must hold ``self._lock`` (or pass a migration-time cursor).zSSELECT 1 FROM sqlite_master WHERE type = 'table' AND name LIKE ? ESCAPE '\' LIMIT 1r’   z\_Ú%)r"   ra   Ú_FTS_TRASH_PREFIXrS   rˆ   )rÏ   r0   s   &&r   Ú_has_fts_trashÚ#SessionFtsSetupMixin._has_fts_trash(  sF   € ô �D—L‘LØbØ×#Ñ#×+Ñ+¨C°Ó7¸#Õ=Ð?ó
÷ ‰(‹*óð 	r   )rà   rÔ   r  rÕ   r  rÉ   )r>   r?   Úmessages_fts_cjk)Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__Ústaticmethodr³   r°   rº   r¾   ÚclassmethodrÃ   rÐ   rÖ   rì   rñ   rø   rý   r  Ú_FTS_REBUILD_CHUNK_ROWSÚ_FTS_REBUILD_DUTY_FACTORÚ_FTS_REBUILD_MIN_PAUSEr#  r$  Ú_FTS_TABLESÚ__static_attributes__Ú__classdictcell__)r¬   s   @r   r¦   r¦     s  ø‡ € ÙYà÷vó ðvð
 ÷`ó ð`ð ÷Eó ðEð ÷Jó ðJð ÷.ó ð.÷
ð 
÷
ð 
÷K,ð K,ðZ ÷ó ð÷ð ð$ ÷3ó ð3ðHØFJðHØgk÷Hò Hð` "ÐØ"ÐØ Ðð
 )Ð÷ð ð O†Kr   r¦   c                ó|   € V ^8„  d   Qh/ ^ \         9   d(   \        \        \        \        R3,          3,          ;R&   # )r   .rm   )Ú__conditional_annotations__r_   r*   Útuple)r   s   "r   r   r      s9   € ÷ Lñ L÷|ò ”Dœœe¤C¨ H�oÐ-Õ.ñ ò}Lr   )r>   r?   )zWCREATE VIRTUAL TABLE messages_fts USING fts5(content,content=messages,content_rowid=id)z5CREATE VIRTUAL TABLE messages_fts USING fts5(content)zpCREATE VIRTUAL TABLE messages_fts USING fts5(content,tool_name,tool_calls,content='messages',content_rowid='id')zxCREATE VIRTUAL TABLE messages_fts USING fts5(content,tool_name,tool_calls,content='messages_fts_src',content_rowid='id'))zrCREATE VIRTUAL TABLE messages_fts_trigram USING fts5(content,content=messages,content_rowid=id,tokenize='trigram')zPCREATE VIRTUAL TABLE messages_fts_trigram USING fts5(content,tokenize='trigram')z›CREATE VIRTUAL TABLE messages_fts_trigram USING fts5(content,tool_name,tool_calls,content='messages_fts_trigram_src',content_rowid='id',tokenize='trigram')z�CREATE VIRTUAL TABLE messages_fts_trigram USING fts5(content,tool_name,content='messages_fts_trigram_src',content_rowid='id',tokenize='trigram'))ÚcontentÚdataÚdocsizeÚidxÚconfig)/r5  r+  Úloggingr   rF   r1   r
  Úpathlibr   Útypingr   Úhermes_constantsr   Úhermes_state_commonr   r   r   r	   r
   r   r   Úhermes_state_errorsr   r   Ú	getLoggerr:   râ   rã   r   r.   r=   rb   rK   rV   rZ   rm   re   rn   rs   rx   r}   rŠ   rŽ   rœ   r¤   r¦   r   )r5  s   @r   Ú<module>rC     sê   øðöLó Û 	Û 	Û Û Ý Ý å ,÷,÷ ,ñ ,ç Tð 
×	Ò	˜>Ó	*€ð,Ð ð )Ð õX[õZõð$ ;€õYõIõ=ð ð ð ð ð2Ð ó õ*;õ*õ]õFõ	Xõ	qõqð HÐ õ÷DZOó ZOr   