+
    „WÝj.# ã                   óp  € R t ^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RIH	t	 ^ RI
HtHt ^ RIHt ^ RIHtHt ^ RIHtHtHtHt ^ RIHtHtHtHtHt RtR	tR
t^?t Rt!^<t"R R lt#R R lt$Rt%R] R] R2t&Rt'R R lt(Rt)R R lt*R R lt+R R lt,R R lt-R  R! lt.]P^                  ! R"^4      ^ ,          t0]0]3t1]-! R#]14      t2].! ]4      t3R$](! ]4       R%]-! ]3]14       R&2t4],! R'](! ]4       R(24      t5]+! ]54      t6R)]6 R*]7! ]4       R+](! ]4       R,]+! R-]6 R.]7! ]4      ^,            R&24       R/]5 R02t8].! ]4      t9R1]2 R2]4 R3]2 R4](! ]4       R5],! ]94       R6]4 R7],! ]84       R82t:R9Pw                  . R:N]2 NR,N]9 NR;N]4 NR,N]8 NR;N]& NR<N]!^,           NR=N]% NR*N]! NR>N] NR?N]% NR@N]! NRAN]& NR=N]% NR*N]!^,           NRBN]% NR*N]  NRCN4      t<RD RE lt=RµRF RG llt>]*! RHRI4       RJ2t?RKt@R¶tAR.Pw                  RL ]A 4       4      tB]C! ]A4      RM0,          tDR·tER.Pw                  RN ]E 4       4      tF]C! ]E4      0 R¸m,          tGRO RP ltHRQ RR ltIR¹RS RT lltJ]*! RHRU4       RV2]I! RW]B4      ,           tKRX]?P™                  RYRZ7       R[]KP™                  RYRZ7       R&2tMRºR\ R] lltNR^]: R_]: 2tOR` Ra ltPRb Rc ltQRºRd Re lltRRf Rg ltS^ tTRhtU^tVRitWRj Rk ltX]X! Rl4      tY]X! Rm4      tZRnt[Ro Rp lt\Rqt]Rrt^Rs Rt lt_Ru Rv lt`RwtaRx Ry ltbR»Rz R{ lltcR|td]d3R} R~ llteRRR€R/R� ltfR¼tgR‚],           Rƒ,           thR„tiR…]W R†]Y R‡]Z Rˆ]Z R‰]Y RŠ2tjR½tkR¾R‹ RŒ lltl]l! 4       tmR�]l! RY4       RŽ]m R�]m R�]m R‘]m R’2tnR¿toR“tpR”tqR•trR–]Y R—]Y R˜2tsR™]Y Rš]Y R˜2tt]Pê                  ! R›4      tvRœtwR�tx]Pò                  Rž8H  tzRŸt{]Pø                  ]Pú                  ]Pü                  ]Pþ                  0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¬ lt‡R­ tˆR® R¯ lt‰R° tŠ]EP                  R±R/R² l4       tŒR³ R´ lt�R# )Àz‘Shared constants and helpers for the SessionDB family of modules.  Lives outside hermes_state so
the mixin modules can import it without a cycle.N)ÚAny)Ú	EPOCH_MAXÚ	EPOCH_MIN)ÚCRON_PRINCIPAL_ID)Ú!persistent_record_pidns_checkableÚpid_namespace_id)ÚAUTO_LOAD_SCAFFOLD_SQL_LIKEÚSKILL_EXCERPT_JOINTÚSKILL_SCAFFOLD_SQL_LIKEÚdescribe_skill_invocation)ÚLEGACY_SUMMARY_PREFIXÚSUMMARY_PREFIXÚ_MERGED_PRIOR_CONTEXT_HEADERÚ_MERGED_SUMMARY_DELIMITERÚ_SUMMARY_END_MARKERÚderivedÚllmÚuseri�  c                ó<   € V ^8„  d   QhR\         R\         R\        /# )é   ÚkeyÚenv_varÚreturn)Ústrr   )Úformats   "ÚGC:\Users\johnn\AppData\Local\hermes\hermes-agent\hermes_state_common.pyÚ__annotate__r   #   s!   € ÷ #ñ #¤ð #¬sð #´sñ #ó    c                óö   € ^ RI Hp V! 4       '       d7    ^ RIHp V! 4       P	                  R4      ;'       g    / P	                  V 4      # \        P                  P	                  V4      #   \
         d     R# i ; i)aû  ``sessions.<key>`` for the profile whose state.db this process is touching.

``gateway/run.py`` bridges the LAUNCH profile's ``sessions.*`` into ``env_var`` (the cross-process
carrier CLI/cron children read). Under a multiplexer a routed turn runs with a HERMES_HOME override
and that env slot holds the default profile's value, so a served profile with different
``sessions.*`` settings must read its own config.yaml. Unscoped: the env bridge, as before.
Returns ``None`` when neither source sets the key.
)Úget_hermes_home_override)Úload_config_readonlyÚsessionsN)Úhermes_constantsr   Úhermes_cli.configr    ÚgetÚ	ExceptionÚosÚenviron)r   r   r   r    s   &&  r   Úrouted_sessions_settingr(   #   sh   € õ :á×!Ò!ð	Ý>Ù(Ó*×.Ñ.¨zÓ:×@Ð@¸b×EÑEÀcÓJÐJô �:‰:�>‰>˜'Ó"Ð"øô ô 	Úð	ús   •"A) ¸A) Á)A8Á7A8c                ó0   € V ^8„  d   QhR\         R\         /# ©r   Útextr   ©r   )r   s   "r   r   r   7   s   € ÷ Nñ N”cð Nœcñ Nr   c                óf   € V P                  RR4      P                  RR4      P                  RR4      # )z½Escape LIKE wildcards (``%``, ``_``) so derived text matches literally; pair with ``ESCAPE '\'``.
``_`` is common in branch names/titles/paths and a substring match must not silently widen.Ú\z\\Ú%z\%Ú_z\_©Úreplace©r+   s   &r   Úescape_liker4   7   s/   € ð �<‰<˜˜fÓ%×-Ñ-¨c°5Ó9×AÑAÀ#ÀuÓMÐMr   z3REPLACE(REPLACE(m.content, X'0A', ' '), X'0D', ' ')z(m.content LIKE 'z' OR m.content LIKE 'z')z+CHAR(9) || CHAR(10) || CHAR(13) || CHAR(32)c                ó0   € V ^8„  d   QhR\         R\         /# r*   r,   )r   s   "r   r   r   G   s   € ÷ /ñ /”sð /œsñ /r   c                 óB   € R V P                  R R4      ,           R ,           # )Ú'z''r1   r3   s   &r   Ú_sql_literalr8   G   s   € Ø�—‘˜c 4Ó(Õ(¨3Õ.Ð.r   Ú_queued_promptc                ó<   € V ^8„  d   QhR\         R\         R\         /# )r   Ú
expressionÚpathr   r,   )r   s   "r   r   r   T   s!   € ÷ >ñ >¤#ð >¬Sð >´Sñ >r   c                ó8   € RV  RV  R2pRV R\        V4       R2# )z?Build a non-throwing JSON marker lookup for a JSON TEXT column.z(CASE WHEN json_valid(z) THEN z ELSE json_object() END)zjson_extract(ú, Ú))r8   )r;   r<   Ú	safe_jsons   && r   Ú_sql_json_extractrA   T   s?   € ð !  ð -Øˆ|Ð3ð	5ð ð ˜9˜+ R¬°TÓ(:Ð';¸1Ð=Ð=r   c                ó0   € V ^8„  d   QhR\         R\         /# ©r   r;   r   r,   )r   s   "r   r   r   ^   s   € ÷ 5ñ 5¤cð 5¬cñ 5r   c                 ó   € R V  R\          R2# )zLTRIM(r>   r?   ©Ú_SQL_WHITESPACE©r;   s   &r   Ú_sql_ltrim_whitespacerH   ^   s   € Ø�J�<˜r¤/Ð!2°!Ð4Ð4r   c                ó0   € V ^8„  d   QhR\         R\         /# rC   r,   )r   s   "r   r   r   b   s   € ÷ 4ñ 4¤Sð 4¬Sñ 4r   c                 ó   € R V  R\          R2# )zTRIM(r>   r?   rE   rG   s   &r   Ú_sql_trim_whitespacerK   b   s   € Ø�:�,˜b¤Ð 1°Ð3Ð3r   c                óV   € V ^8„  d   QhR\         R\        \         R3,          R\         /# )r   r;   Úprefixes.r   )r   Útuple)r   s   "r   r   r   f   s.   € ÷ lñ l¤ð l´´c¸3°hµð lÄCñ lr   c                 ól   a€ \        V 4      oR RP                  V3R lV 4       4      ,           R,           # )Ú(ú OR c           	   3   ó^   <"  € T F"  pR S R\        V4       R\        V4       2x € K$  	  R# 5i)úSUBSTR(ú, 1, ú) = N)Úlenr8   )Ú.0ÚpÚtrimmeds   & €r   Ú	<genexpr>Ú#_sql_starts_with.<locals>.<genexpr>h   s0   øé € ÐeÑ\dÐWX˜w w i¨u´S¸³V°H¸DÄÈaÃÐ@QÕRÓ\dùs   ƒ*-r?   )rH   Újoin)r;   rM   rY   s   &&@r   Ú_sql_starts_withr]   f   s.   ø€ Ü# JÓ/€GØ�—‘ÔeÑ\dÓeÓeÕeÐhkÕkÐkr   c                ó0   € V ^8„  d   QhR\         R\         /# )r   Úmarkerr   r,   )r   s   "r   r   r   k   s   € ÷ Zñ Zœcð Z¤cñ Zr   c                ó8   € R\        V 4       R\        V 4       R2# )z5``m.content`` after the first occurrence of *marker*.z#SUBSTR(m.content, INSTR(m.content, z) + r?   )r8   rV   )r_   s   &r   Ú_sql_after_markerra   k   s"   € à0´¸fÓ1EÐ0FÀdÌ3ÈvË;È-ÐWXÐYÐYr   zDo NOT answerz	m.contentz(INSTR(m.content, z
) > 0 AND r?   z&SUBSTR(m.content, 1, INSTR(m.content, z) - 1)zCASE WHEN SUBSTR(rT   rU   z THEN rS   r>   z ELSE z ENDz4(COALESCE(m.display_kind, '') <> 'hidden' AND ((NOT z	 AND NOT z) OR (z AND INSTR(m.content, z) > 0 AND LENGTH(z) > 0) OR (z AND LENGTH(z) > 0)))Ú ú
CASE WHEN z WHEN z AND LENGTH(m.content) > z THEN SUBSTR(z) || 'z' || SUBSTR(z, -z) WHEN z) ELSE SUBSTR(z) ENDc                ó0   € V ^8„  d   QhR\         R\        /# )r   Úrawr   ©r   r   )r   s   "r   r   r   •   s   € ÷ Yñ Yœð Y¤ñ Yr   c                óF  € \        T ;'       g    R4      P                  4       pV'       g   R# VP                  RR4      P                  RR4      p\        V4      pVe   TMVP	                  \
        4      ^ ,          p\        V4      \        8”  d   VR\         R,           # T# )zCTurn a ``_preview_raw`` column into the short preview callers show.rb   Ú
Ú ÚNz...)r   Ústripr2   r   Úsplitr	   rV   Ú_PREVIEW_MAX_CHARS)re   r+   Ú	describeds   &  r   Ú_shape_previewro   •   s‰   € äˆs�yˆy�b‹>×ÑÓ!€DßÙØ�<‰<˜˜cÓ"×*Ñ*¨4°Ó5€DÜ)¨$Ó/€IØ!Ò-‰9°4·:±:Ô>QÓ3RÐSTÕ3U€DÜ03°D³	Ô<NÔ0Nˆ4Ð#Ô#Ð$ uÕ,ÐXÐTXÐXr   c                ó0   € V ^8„  d   QhR\         R\         /# ©r   Úsession_id_exprr   r,   )r   s   "r   r   r       s   € ÷ bñ b¤cð b´sñ br   c                ó*   € R\          RV  R\         R2# )a'  Correlated ``_preview_raw`` column: the first eligible user message of *session_id_expr*.
``INDEXED BY`` walks the session in timestamp order and stops at the first eligible row; with planner
stats older than idx_messages_session_id, SQLite otherwise reads and sorts the whole session (#119403).zCOALESCE((SELECT zF FROM messages m INDEXED BY idx_messages_session WHERE m.session_id = z3 AND m.role = 'user' AND m.content IS NOT NULL AND z9 ORDER BY m.timestamp, m.id LIMIT 1), '') AS _preview_raw)Ú_PREVIEW_RAW_SELECTÚ_PREVIEW_ELIGIBLE_SQL©rr   s   &r   Ú_sql_preview_rawrw       s2   € ð  Ô 3Ð4ð 5!Ø!0Ð 1ð 2Ü%Ð&Ð&_ðað br   z{a}.model_configz$._branched_fromz´ IS NOT NULL OR EXISTS (SELECT 1 FROM sessions p            WHERE p.id = {a}.parent_session_id            AND p.end_reason = 'branched'            AND {a}.started_at >= p.ended_at)zsEXISTS (SELECT 1 FROM sessions p        WHERE p.id = {a}.parent_session_id        AND p.end_reason = 'compression')c              #   ó.   "  € T F  pR V R 2x € K  	  R# 5i©r7   N© ©rW   Úreasons   & r   rZ   rZ   ´   s   é € Ð"RÑ?Q°V Q v h¨a¥=Ó?Qùó   ‚Únew_sessionc              #   ó.   "  € T F  pR V R 2x € K  	  R# 5iry   rz   r{   s   & r   rZ   rZ   Á   s   é € Ð(^ÑE]¸6¨1¨V¨H°A­ÓE]ùr}   c                ó$   € V ^8„  d   QhR\         /# ©r   r   ©Úbool)r   s   "r   r   r   Í   s   € ÷ Hñ H¤tñ Hr   c                óD   € \        V \        4      ;'       d
    V \        9   # )u#  True when *reason* is an automatic-cleanup end stamp; compression-liveness sites must call this.

Single owner of the "accidental vs deliberate end" predicate â€” every compression-liveness site must call
this instead of re-implementing the reason taxonomy (#88197, never-patch-predicates).
)Ú
isinstancer   Ú_AUTOMATIC_END_REASONS)r|   s   &r   Úis_automatic_end_reasonr‡   Í   s   € ô �fœcÓ"×GÐG vÔ1GÑ'GÐGr   c                ó<   € V ^8„  d   QhR\         R\         R\         /# )r   ÚaliasÚreasons_sqlr   r,   )r   s   "r   r   r   Ö   s&   € ÷ jñ j¤3ð j´Sð j¼Sñ jr   c                ó&   € RV  RV RV  RV  RV  R2# )a  Pre-marker reset-continuation heuristic: child rides its parent's exact non-empty routing key and the
parent ended at a reset boundary.  Shared by ``_RESET_CHILD_SQL`` and ``reopen_session()`` so the two
cannot drift; ``reasons_sql`` is a literal or placeholder list.z9EXISTS (SELECT 1 FROM sessions p            WHERE p.id = z3.parent_session_id            AND p.end_reason IN (z)            AND z(.session_key IS NOT NULL            AND z".session_key != ''            AND z.session_key = p.session_key)rz   )r‰   rŠ   s   &&r   Ú_legacy_reset_child_sqlrŒ   Ö   s?   € ð HÈÀwð O,Ø,7¨=Ð8IÈ%Èð QØ ˜'Ð!CÀEÀ7ÐJgðið jr   c                ó<   € V ^8„  d   QhR\         R\         R\         /# )r   ÚchildÚparentr   r,   )r   s   "r   r   r   ß   s!   € ÷ 9ñ 9¤sð 9¼ð 9Äsñ 9r   c                óT   a a€ RP                  V V3R lR 4       4      RS  R2,           # )ax  ``  AND ...`` clauses rejecting children that are NOT compression continuations of *parent*
(branch/delegate/reset forks, tool sessions).  Markers are bound to the parent id: continuations
inherit ``model_config`` verbatim, so a marker naming another row is inherited, not a fork.
``child`` is the column prefix (``""``, ``"c."``); single owner so prune and compression agree.rb   c              3   óV   <"  € T F  pR \        S R2RV 24       RS R2x € K   	  R# 5i)ú  AND COALESCE(Úmodel_configz$.z	, '') != rh   N)rA   )rW   r_   rŽ   r�   s   & €€r   rZ   Ú._non_continuation_child_sql.<locals>.<genexpr>ä   sA   øé € ð áIˆFð Ô+¨u¨g°\Ð,BÀbÈÈÀMÓRÐSÐS\Ð]cÐ\dÐdfÕgÛIùs   ƒ&)r’   zsource, '') != 'tool'
)Ú_branched_fromÚ_delegate_fromÚ_reset_from)r\   )rŽ   r�   s   ffr   Ú_non_continuation_child_sqlr˜   ß   s7   ù€ ð
 �7‰7õ áIóó ð ˜%˜Ð 7Ð8õ9ð 9r   z$._reset_fromz IS NOT NULL OR z{a}z (s.parent_session_id IS NULL OR Ús©ÚarQ   c                ó0   € V ^8„  d   QhR\         R\         /# ©r   r‰   r   r,   )r   s   "r   r   r   ô   s   € ÷ nñ n¤ð n¬cñ nr   c                ó˜   € RV  R\         P                  V R7       R\        P                  V R7       R\        P                  V R7       R2	# )z:Subagent runs, not branch, reset, or compression children.rP   z(.parent_session_id IS NOT NULL AND NOT (rš   z) AND NOT (ú)))Ú_BRANCH_CHILD_SQLr   Ú_COMPRESSION_CHILD_SQLÚ_RESET_CHILD_SQL©r‰   s   &r   Ú_ephemeral_child_sqlr¤   ô   sa   € à�ˆwÐ>Ô?P×?WÑ?WÐZ_Ð?WÓ?`Ð>að bÜ+×2Ñ2°UÐ2Ó;Ð<¸KÔHX×H_ÑH_ÐbgÐH_ÓHhÐGiÐikðmð nr   zBETWEEN z AND c                ó0   € V ^8„  d   QhR\         R\         /# )r   Úexprr   r,   )r   s   "r   r   r   ý   s   € ÷ [ñ [œð [¤ñ [r   c                ó   € RV  R\          R2# )z@*expr* when it is inside the ``coerce_epoch`` window, else NULL.z(SELECT _win.v FROM (SELECT z AS v) _win WHERE _win.v r?   )Ú_SQL_IN_WINDOW)r¦   s   &r   Ú_sql_in_windowr©   ý   s   € à)¨$¨Ð/HÌÐHXÐXYÐZÐZr   c                óH   € V ^8„  d   QhR\         R\         R\         R\         /# )r   Úactivityrr   Ústartedr   r,   )r   s   "r   r   r     s.   € ÷ Jñ Jœsð J´Sð JÄ3ð JÌ3ñ Jr   c           
     óT   € RV R\          R2pRV  RV R\          R\        V4       R2	# )a¶  Freshest of *activity* and the latest message timestamp for *session_id_expr*, else *started*.
Heartbeats are rate-limited (~60s) so ``last_activity_at`` can lag a newer message; never use it alone.
Cells outside the ``coerce_epoch`` window (garbage doubles salvaged from a damaged page, TEXT) are
skipped, fallback included, or one bad row pins the session's recency to ``5e+246`` (#91536); a
session with no trusted cell at all is NULL.zm(SELECT MAX(_act_m.timestamp) FROM messages _act_m INDEXED BY idx_messages_session WHERE _act_m.session_id = z AND _act_m.timestamp r?   z,COALESCE((SELECT MAX(_act_v.v) FROM (SELECT z AS v UNION ALL SELECT z) _act_v WHERE _act_v.v z), )r¨   r©   )r«   rr   r¬   Úmsg_maxs   &&& r   Ú_sql_freshest_ofr¯     sV   € ð&Ø&5Ð%6Ø
 ¤Ð 0°ð3€Gð ;¸8¸*ÐD[Ð\cÐ[dð eÜ)Ð*¨#¬n¸WÓ.EÐ-FÀaðIð Jr   c                ó0   € V ^8„  d   QhR\         R\         /# r�   r,   )r   s   "r   r   r     s   € ÷ _ñ _¤Cð _´#ñ _r   c                ó.   € \        V  R2V  R2V  R24      # )z:Session recency expression for a ``sessions {alias}`` row.z.last_activity_atz.idz.started_at©r¯   r£   s   &r   Ú_sql_session_last_activer³     s'   € ä˜u˜gÐ%6Ð7¸E¸7À#¸È5È'ÐQ\ÐH]Ó^Ð^r   c                ó0   € V ^8„  d   QhR\         R\         /# rq   r,   )r   s   "r   r   r     s   € ÷ Yñ Y´Cð Y¼Cñ Yr   c                ó,   € \        RV  R2V RV  R24      # )zASame freshest-of expression keyed by a session-id SQL expression.z@(SELECT last_activity_at FROM sessions _act_s WHERE _act_s.id = r?   z:(SELECT started_at FROM sessions _act_s WHERE _act_s.id = r²   rv   s   &r   Ú_sql_session_last_active_by_idr¶     s0   € äØ
JÈ?ÐJ[Ð[\Ð]Ð_nØ
DÀ_ÐDUÐUVÐWóYð Yr   g      Ð?i    c                ó0   € V ^8„  d   QhR\         R\         /# r�   r,   )r   s   "r   r   r   E  s   € ÷ %ñ %¤Cð %¬Cñ %r   c           	      ó(   € R V  RV  R\          RV  R2	# )rc   z-.role = 'tool'
         THEN substr(COALESCE(z.content, ''), 1, z)
         ELSE z.content END)ÚFTS_TOOL_CONTENT_PREFIX_CHARSr£   s   &r   Ú_fts_indexed_content_sqlrº   E  s3   € Ø˜%˜ð !Ø$˜gÐ%7Ô8UÐ7Vð WØˆg�\ð%ð %r   ÚnewÚoldi   c                óT   € V ^8„  d   QhR\         \        \        3,          R,          /# ©r   r   N)rN   Úint)r   s   "r   r   r   R  s#   € ÷ Gñ G¤5¬¬c¨¥?°TÕ#9ñ Gr   c                óÐ   €  \         P                  ! V 4      pTP                  '       d+   TP                  '       d   TP                  TP                  3# R#   \         d     R# i ; i)z—``(st_dev, st_ino)`` for *path*, or None.  st_ino=0 (Windows, some network FS) would false-positive
every replaced-file check, so it counts as unknown.N)r&   ÚstatÚOSErrorÚst_devÚst_ino)r<   Ústs   & r   Ústat_db_file_identityrÆ   R  sR   € ðÜ�WŠW�T‹]ˆð &(§Y§Y Y°2·9·9°9ˆB�I‰I�r—y‘yÐ!ÐFÀ$ÐFøô ô Úðús   ‚A ÁA%Á$A%z6SELECT ended_at, end_reason FROM sessions WHERE id = ?zESELECT holder, expires_at FROM compression_locks WHERE session_id = ?c                ó$   € V ^8„  d   QhR\         /# r�   r‚   )r   s   "r   r   r   a  s   € ÷ bñ b¤$ñ br   c                 óX   € V R J;'       d     V R,          R J;'       d    V R,          R8H  # )NÚended_atÚ
end_reasonÚcompressionrz   )Úrows   &r   Ú_ended_by_compressionrÍ   a  s0   € Ø�dˆ?×aÐa˜s :�°dÐ:×aÐa¸sÀ<Õ?PÐTaÑ?aÐar   c                ó$   € V ^8„  d   QhR\         /# r�   r,   )r   s   "r   r   r   e  s   € ÷ Yñ YœCñ Yr   c           	     ó†   € RP                  R \        \        V \        4      '       d   T M
\	        V 4      4       4       4      # )zV``?,?,?`` for one bound parameter per element of *items* (a sequence or an int count).Ú,c              3   ó&   "  € T F  pR x € K	  	  R# 5i)Ú?Nrz   )rW   r0   s   & r   rZ   Ú _placeholders.<locals>.<genexpr>g  s   é € ÐXÑ!W˜A•CÓ!Wùs   ‚)r\   Úranger…   r¿   rV   )Úitemss   &r   Ú_placeholdersrÖ   e  s/   € à�8‰8ÑX¤´
¸5Ä#×0FÒ0F¡uÌCÐPUËJÔ!WÓXÓXÐXr   ze(s.message_count > 0 OR EXISTS (SELECT 1 FROM session_admissions a WHERE a.target_session_id = s.id))c                ó$   € V ^8„  d   QhR\         /# r�   r,   )r   s   "r   r   r   q  s   € ÷ !ñ !¤#ñ !r   c                ó@   € V P                  R4      '       d   \        # R# )zý``sessions s`` predicate that drops empty launch stubs (the CLI crashed or a resume failed before
the first turn). A read-only open does not migrate, so a store from before the runtime tables may
lack ``session_admissions``; messages alone decide there.zPSELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'session_admissions'zs.message_count > 0)Ú	_read_allÚ_NOT_A_LAUNCH_STUB_SQL)Údbs   &r   Úlaunch_stub_filter_sqlrÜ   q  s   € ð 
‡|�|Ðf×gÒgÜ%Ð%Ù r   c                óF   € V ^8„  d   QhR\         \        \        3,          /# r�   )rN   r   Úlist)r   s   "r   r   r   z  s   € ÷ ñ ÄÄsÌDÀyÕAQñ r   c                óT  € \        T ;'       g    . 4      pV'       g   R. 3# \        T;'       g    R	4      pV'       g   R\        V4       R2V3# V Uu. uF  qDR8w  g   K  VNK  	  ppR\        V4       R2pV'       g   V\        V4      3# R\        V4       RV R2. VOVO3# u upi )
a  ``sessions s`` source predicate. With ``local_created_sources``, a ``source='local'`` row
matches only when ``created_source`` is one of those surfaces; the placeholder ``'unknown'``
is not a surface and stays unmatched until an upsert stamps the real creator.rb   zs.source IN (r?   Úlocalz-(s.source = 'local' AND s.created_source IN (rŸ   z(s.source IN (z) OR rz   )rÞ   rÖ   )ÚsourcesÚlocal_created_sourcesÚlabelsÚsurfacesÚitemÚ	non_localÚ	local_sqls   &&     r   Ú_source_match_clauserè   z  sÅ   € ô �'—-�-˜RÓ €FßØ�2ˆvˆÜÐ)×/Ð/¨RÓ0€HßØœ}¨VÓ4Ð5°QÐ7¸Ð?Ð?Ù"(Ó<¡&˜$°G©O—�¡&€IÐ<Ø?ÄÈhÓ@WÐ?XÐXZÐ[€IßØœ$˜x›.Ð(Ð(à
œ yÓ1Ð2°%¸	°{À!ÐDØˆ)Ð�hÐðð ùò	 =s   ÁB%Á!B%i„  c                ó$   € V ^8„  d   QhR\         /# )r   Úsize©r¿   )r   s   "r   r   r   ”  s   € ÷ &ñ &œ#ñ &r   c              #  óv   "  € \        V 4      p \        ^ \        V 4      V4       F  pWW!,            x € K  	  R# 5i)z?Yield *ids* (any iterable) as lists of at most *size* elements.N)rÞ   rÔ   rV   )Úidsrê   Ústarts   && r   Ú
_id_chunksrï   ”  s2   é € ä
ˆs‹)€CÜ�qœ#˜c›( DÖ)ˆØ˜�Ð%Ô%ó *ùs   ‚79ÚprimaryÚsessions_dirc          	     ó~  € \        T;'       g    R Uu0 uF'  p\        V\        4      '       g   K  V'       g   K%  VkK)  	  up4      pV'       g   R#  ^ RIHp ^ RIHpHp	Hp
H	p \        P                  P                  \        P                  P                  \        P                  ! V P                  4      4      4      pV	! V4      pRW69   d   TMV^ ,          RVRV/p\!        V4      ^8”  d   WnR&   Ve   \        V4      VR&   VRJ;'       dE    \        P                  P#                  V4      \        P                  P#                  V! 4       4      8g  pV'       d	   V! V4      MRp V! R/ VB  Ve   V
! V4       R# R# u upi   Te
   T
! T4       i i ; i  \$         d    \&        P)                  R	R
R7        R# i ; i)aV  Fire ``on_session_delete`` after a committed delete or deliberate archive (#124511).

Call right after the write transaction commits (before follow-up cleanup that can raise), with
the ids it removed or, for ``reason="archive"``, hid. *primary* is the session the caller asked
for; without it the first id stands in (bulk sweeps). ``deleted_ids`` rides along when more
than one row went. Dispatch runs under the store's own profile home, so a process touching
another profile's store (``sessions repair-profiles``) reaches THAT profile's plugins.
Late-bound and fail-open: never breaks the delete.
N)Úinvoke_hook)Úget_hermes_homeÚprofile_name_for_homeÚreset_hermes_home_overrideÚset_hermes_home_overrideÚ
session_idr|   ÚprofileÚdeleted_idsrñ   z!on_session_delete dispatch failedT)Úexc_inforz   )Úon_session_delete)Úsortedr…   r   Úhermes_cli.lifecycleró   r"   rô   rõ   rö   r÷   r&   r<   ÚdirnameÚabspathÚfspathÚdb_pathrV   Úrealpathr%   ÚloggerÚwarning)rÛ   rú   r|   rð   rñ   Úsidrí   ró   rô   rõ   rö   r÷   Úhomerù   ÚpayloadÚrebindÚtokens   &&$$$            r   Úemit_session_deletedr  ›  s{  € ô  +×"3Ð"3°Ð"3ÓUÑ"3˜#¼
À3Ì×8L”#ÔQT—#�#Ñ"3ÑUÓ
V€CßÙðKÝ4÷	
ó 	
ô �w‰w�‰œrŸw™wŸ™¬r¯yªy¸¿¹Ó/DÓEÓFˆÙ'¨Ó-ˆØ%°'´.¡wÀcÈ!ÅfÈhÐX^Ð`iÐkrÐsˆÜˆs‹8�aŒ<Ø%(�MÑ"ØÒ#Ü&)¨,Ó&7ˆG�NÑ#Ø Ð$×fÐf¬¯©×)9Ñ)9¸$Ó)?Ä2Ç7Á7×CSÑCSÑTcÓTeÓCfÑ)fˆß28Ñ(¨Ô.¸dˆð	2ÙÑ7¨wÒ7àÒ Ù*¨5Ö1ñ !ùò+ Vøð* Ò Ù*¨5Õ1ð !ûäô KÜ�‰Ð:ÀTˆ×JðKúsG   ”E?±E?»E?ÁB;F ÄAF Å
F Å'F Å/F ÆFÆF Æ"F<Æ;F<a/  
CREATE TABLE IF NOT EXISTS schema_version (
    version INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS system_prompts (
    hash TEXT PRIMARY KEY,
    prompt TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS sessions (
    id TEXT PRIMARY KEY,
    runtime_revision INTEGER NOT NULL DEFAULT 0,
    runtime_generation INTEGER NOT NULL DEFAULT 0,
    source TEXT NOT NULL,
    created_source TEXT,
    user_id TEXT,
    session_key TEXT,
    chat_id TEXT,
    chat_type TEXT,
    thread_id TEXT,
    display_name TEXT,
    origin_json TEXT,
    expiry_finalized INTEGER DEFAULT 0,
    model TEXT,
    model_config TEXT,
    system_prompt TEXT,
    system_prompt_hash TEXT,
    parent_session_id TEXT,
    started_at REAL NOT NULL,
    ended_at REAL,
    end_reason TEXT,
    message_count INTEGER DEFAULT 0,
    tool_call_count INTEGER DEFAULT 0,
    input_tokens INTEGER DEFAULT 0,
    output_tokens INTEGER DEFAULT 0,
    cache_read_tokens INTEGER DEFAULT 0,
    cache_write_tokens INTEGER DEFAULT 0,
    reasoning_tokens INTEGER DEFAULT 0,
    cwd TEXT,
    git_branch TEXT,
    git_repo_root TEXT,
    git_metadata_generation INTEGER NOT NULL DEFAULT 0,
    billing_provider TEXT,
    billing_base_url TEXT,
    billing_mode TEXT,
    estimated_cost_usd REAL,
    actual_cost_usd REAL,
    cost_status TEXT,
    cost_source TEXT,
    pricing_version TEXT,
    title TEXT,
    title_source TEXT,
    last_activity_at REAL,
    last_activity_description TEXT,
    last_activity_provenance TEXT,
    api_call_count INTEGER DEFAULT 0,
    handoff_state TEXT,
    handoff_platform TEXT,
    handoff_error TEXT,
    compression_failure_cooldown_until REAL,
    compression_failure_error TEXT,
    compression_fallback_streak INTEGER NOT NULL DEFAULT 0,
    compression_ineffective_count INTEGER NOT NULL DEFAULT 0,
    compression_recovery_deadline REAL,
    compression_overload_streak INTEGER NOT NULL DEFAULT 0,
    profile_name TEXT,
    transport_profile TEXT,
    rewind_count INTEGER NOT NULL DEFAULT 0,
    archived INTEGER NOT NULL DEFAULT 0,
    auto_archived INTEGER NOT NULL DEFAULT 0,
    pinned INTEGER NOT NULL DEFAULT 0,
    hidden INTEGER NOT NULL DEFAULT 0,
    last_read_at REAL,
    tool_names TEXT,
    transcript_revision INTEGER NOT NULL DEFAULT 0,
    FOREIGN KEY (parent_session_id) REFERENCES sessions(id),
    FOREIGN KEY (system_prompt_hash) REFERENCES system_prompts(hash)
);

CREATE TABLE IF NOT EXISTS messages (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    session_id TEXT NOT NULL REFERENCES sessions(id),
    role TEXT NOT NULL,
    content TEXT,
    tool_call_id TEXT,
    tool_calls TEXT,
    tool_name TEXT,
    effect_disposition TEXT,
    timestamp REAL NOT NULL,
    token_count INTEGER,
    finish_reason TEXT,
    reasoning TEXT,
    reasoning_content TEXT,
    reasoning_details TEXT,
    codex_reasoning_items TEXT,
    codex_message_items TEXT,
    platform_message_id TEXT,
    observed INTEGER DEFAULT 0,
    _compressed_summary INTEGER NOT NULL DEFAULT 0,
    active INTEGER NOT NULL DEFAULT 1,
    compacted INTEGER NOT NULL DEFAULT 0,
    api_content TEXT,
    display_kind TEXT,
    display_metadata TEXT,
    display_identity BLOB,
    display_order INTEGER,
    message_uid TEXT,
    absorbed_message_uids TEXT,
    tool_call_uids TEXT,
    tool_call_uid TEXT
);

CREATE TABLE IF NOT EXISTS session_model_usage (
    session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
    model TEXT NOT NULL,
    billing_provider TEXT NOT NULL DEFAULT '',
    billing_base_url TEXT NOT NULL DEFAULT '',
    billing_mode TEXT NOT NULL DEFAULT '',
    task TEXT NOT NULL DEFAULT '',
    api_call_count INTEGER NOT NULL DEFAULT 0,
    input_tokens INTEGER NOT NULL DEFAULT 0,
    output_tokens INTEGER NOT NULL DEFAULT 0,
    cache_read_tokens INTEGER NOT NULL DEFAULT 0,
    cache_write_tokens INTEGER NOT NULL DEFAULT 0,
    reasoning_tokens INTEGER NOT NULL DEFAULT 0,
    estimated_cost_usd REAL NOT NULL DEFAULT 0,
    actual_cost_usd REAL NOT NULL DEFAULT 0,
    cost_status TEXT,
    cost_source TEXT,
    first_seen REAL,
    last_seen REAL,
    PRIMARY KEY (session_id, model, billing_provider, billing_base_url, billing_mode, task)
);

CREATE TABLE IF NOT EXISTS runtime_epoch (
    singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
    epoch INTEGER NOT NULL CHECK (epoch > 0),
    instance_id TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS session_admissions (
    seq INTEGER PRIMARY KEY AUTOINCREMENT,
    admission_id TEXT NOT NULL UNIQUE,
    request_id TEXT NOT NULL,
    principal_id TEXT NOT NULL,
    target_session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE RESTRICT,
    lineage_json TEXT NOT NULL,
    payload_json TEXT NOT NULL,
    payload_digest TEXT NOT NULL,
    intent TEXT NOT NULL CHECK (intent IN ('queue','steer','redirect')),
    status TEXT NOT NULL CHECK (status IN ('queued','started','unknown','terminal')),
    outcome TEXT,
    owner_epoch INTEGER,
    generation INTEGER,
    UNIQUE (principal_id, target_session_id, request_id),
    CHECK (status != 'started' OR (owner_epoch IS NOT NULL AND generation IS NOT NULL))
);
CREATE INDEX IF NOT EXISTS session_admissions_pending
    ON session_admissions(target_session_id, status, seq);
-- Cron run history finds a job's unified-runtime runs by the scheduler principal's request ids
-- (``cron:<job>:<execution>``); without this a lookup walked
-- every cron admission of every job. Partial: interactive turns, the bulk of admissions, pay
-- nothing for it. The constant leading column gives the planner equality + range over a covering
-- index, so it beats the UNIQUE autoindex's bare principal_id prefix even without ANALYZE stats.
-- The WHERE is built from CRON_PRINCIPAL_ID, the literal the run queries inline, so they cannot drift.
CREATE INDEX IF NOT EXISTS idx_session_admissions_cron_request
    ON session_admissions(principal_id, request_id, target_session_id) WHERE principal_id = 'u^  ';
CREATE TABLE IF NOT EXISTS worker_executions (
    execution_id TEXT PRIMARY KEY,
    session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE RESTRICT,
    kind TEXT NOT NULL CHECK (kind IN ('cron','child','compute','kanban')),
    owner_epoch INTEGER NOT NULL,
    generation INTEGER NOT NULL,
    status TEXT NOT NULL CHECK (status IN ('registered','running','unknown','terminal')),
    adoption_digest TEXT NOT NULL,
    last_sequence INTEGER NOT NULL DEFAULT 0 CHECK (last_sequence >= 0)
);
-- Lineage-wide worker exclusion (claim/register/reset) probes every row of a conversation's lineage
-- inside the write transaction; without this each probe scanned the whole (only-growing) table.
CREATE INDEX IF NOT EXISTS idx_worker_executions_session_status
    ON worker_executions(session_id, status);
CREATE TABLE IF NOT EXISTS worker_receipts (
    execution_id TEXT NOT NULL REFERENCES worker_executions(execution_id) ON DELETE RESTRICT,
    sequence INTEGER NOT NULL CHECK (sequence > 0),
    payload_digest TEXT NOT NULL,
    result_json TEXT NOT NULL,
    PRIMARY KEY (execution_id, sequence)
);

CREATE TABLE IF NOT EXISTS state_meta (
    key TEXT PRIMARY KEY,
    value TEXT
);

CREATE TABLE IF NOT EXISTS gateway_routing (
    scope TEXT NOT NULL DEFAULT '',
    session_key TEXT NOT NULL,
    entry_json TEXT NOT NULL,
    updated_at REAL NOT NULL,
    PRIMARY KEY (scope, session_key)
);

CREATE TABLE IF NOT EXISTS gateway_hygiene_state (
    session_key TEXT PRIMARY KEY,
    failure_streak INTEGER NOT NULL DEFAULT 0
);

-- Monotonic conversation generation per routing peer (#96811).
--
-- A host-declared conversation key (X-Hermes-Session-Key / build_session_key)
-- is per-CHAT and outlives any single conversation on it, so the prompt-cache
-- affinity scope derived from it must be qualified by which conversation is
-- currently live. Deriving that from the session rows themselves
-- (COUNT/MAX over _RESET_END_REASONS boundaries) cannot prove non-reuse:
-- delete_session() and bulk pruning remove ended rows, so an aggregate can
-- return a pair it already emitted and hand a new conversation a retired
-- affinity identity.
--
-- This counter lives outside prunable session history and only ever
-- increments, once per boundary actually written, so a generation can never
-- be reused for a peer even if every session row behind it is deleted.
--
-- These rows are deliberately NEVER garbage-collected, including when every
-- session row for the peer is gone. Collecting one resets that peer to "no
-- generation", so its next boundary writes generation = 1 again and re-issues
-- a gwk_ scope a retired conversation already used â€” exactly the ABA this
-- table exists to close. Do not add it to delete_session()'s cascade or to any
-- prune sweep. One (TEXT, TEXT, INTEGER) row per routing peer is the intended,
-- bounded cost.
CREATE TABLE IF NOT EXISTS conversation_generations (
    source TEXT NOT NULL,
    session_key TEXT NOT NULL,
    generation INTEGER NOT NULL DEFAULT 0,
    PRIMARY KEY (source, session_key)
);

-- Per-backend liveness heartbeat (#94895). Each serve / tui_gateway process
-- registers a row at startup and refreshes ``last_heartbeat`` periodically.
-- The startup orphan sweep (sessions.startup_orphan_reap) consults this
-- table to avoid reaping rows whose owning backend is still alive but
-- just idle (multi-backend state.db shared by isolated serve processes).
-- A backend whose ``last_heartbeat`` is older than the heartbeat staleness
-- window is treated as dead; rows without ANY matching heartbeat fall back
-- to the original staleness predicate so legacy deployments keep working.
CREATE TABLE IF NOT EXISTS gateway_heartbeats (
    backend_id TEXT PRIMARY KEY,
    pid INTEGER NOT NULL,
    started_at REAL NOT NULL,
    last_heartbeat REAL NOT NULL,
    profile TEXT NOT NULL DEFAULT '',
    host TEXT NOT NULL DEFAULT ''
);

CREATE TABLE IF NOT EXISTS compression_locks (
    session_id TEXT PRIMARY KEY,
    holder TEXT NOT NULL,
    acquired_at REAL NOT NULL,
    expires_at REAL NOT NULL
);

CREATE TABLE IF NOT EXISTS session_turn_leases (
    conversation_id TEXT PRIMARY KEY,
    holder TEXT NOT NULL,
    acquired_at REAL NOT NULL,
    expires_at REAL NOT NULL
);

CREATE TABLE IF NOT EXISTS async_delegations (
    delegation_id TEXT PRIMARY KEY,
    origin_session TEXT NOT NULL,
    origin_ui_session_id TEXT NOT NULL DEFAULT '',
    parent_session_id TEXT,
    state TEXT NOT NULL,
    dispatched_at REAL NOT NULL,
    completed_at REAL,
    updated_at REAL NOT NULL,
    event_json TEXT,
    result_json TEXT,
    delivery_state TEXT NOT NULL DEFAULT 'pending',
    delivery_attempts INTEGER NOT NULL DEFAULT 0,
    delivered_at REAL,
    owner_pid INTEGER,
    owner_started_at INTEGER,
    task_json TEXT,
    -- Owner-side execution fence (unified runtime).
    owner_execution_id TEXT,
    delivery_claim TEXT,
    delivery_claimed_at REAL,
    -- Mirrors the delegation tool's own CREATE TABLE (tools/async_delegation.py
    -- _initialize_schema). Keeping the canonical fresh-install shape identical
    -- to the tool's avoids a silent schema drift: the tool's lazy
    -- ALTER TABLE ADD COLUMN used to be the only source of this column, so two
    -- databases at the same schema_version had different
    -- async_delegations shapes depending on whether the delegation tool had
    -- ever run, breaking rebuild/replay pipelines that reconstruct state.db
    -- from the canonical schema (#94691).
    origin_session_id TEXT NOT NULL DEFAULT ''
);

CREATE INDEX IF NOT EXISTS idx_sessions_source ON sessions(source);
CREATE INDEX IF NOT EXISTS idx_sessions_source_id ON sessions(source, id);
CREATE INDEX IF NOT EXISTS idx_sessions_parent ON sessions(parent_session_id);
CREATE INDEX IF NOT EXISTS idx_sessions_started ON sessions(started_at DESC);
CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id, timestamp);
CREATE INDEX IF NOT EXISTS idx_messages_session_id ON messages(session_id, id);
-- Partial index for the Insights assistant tool-call scan
-- (agent/insights.py _get_tool_usage / _get_skill_usage): those queries filter
-- messages by role='assistant' AND tool_calls IS NOT NULL, a small fraction of
-- rows on a large state.db. role and tool_calls are base columns, so this can
-- live in SCHEMA_SQL rather than DEFERRED_INDEX_SQL.
CREATE INDEX IF NOT EXISTS idx_messages_assistant_calls_by_session
    ON messages(session_id)
    WHERE role = 'assistant' AND tool_calls IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_compression_locks_expires ON compression_locks(expires_at);
CREATE INDEX IF NOT EXISTS idx_session_turn_leases_expires ON session_turn_leases(expires_at);
CREATE INDEX IF NOT EXISTS idx_session_model_usage_session ON session_model_usage(session_id);
CREATE INDEX IF NOT EXISTS idx_session_model_usage_model ON session_model_usage(model);
CREATE INDEX IF NOT EXISTS idx_async_delegations_delivery
    ON async_delegations(delivery_state, completed_at);
a  
DROP TRIGGER IF EXISTS sessions_runtime_ledger_delete_guard;
CREATE TRIGGER IF NOT EXISTS sessions_runtime_ledger_delete_guard
BEFORE DELETE ON sessions
WHEN EXISTS (SELECT 1 FROM session_admissions WHERE target_session_id = old.id)
  OR EXISTS (SELECT 1 FROM worker_executions WHERE session_id = old.id)
BEGIN
    SELECT RAISE(ABORT, 'Refused to delete a session that has gateway runtime history: state.db was upgraded by a newer Hermes, and this older Hermes cannot delete such sessions. Downgrading is unsupported. Run hermes update to go back to the newer release, or restore the pre-update snapshot: https://hermes-agent.nousresearch.com/docs/getting-started/updating#rolling-back-to-an-older-release');
END;
DROP TRIGGER IF EXISTS messages_transcript_revision_update;
CREATE TRIGGER IF NOT EXISTS messages_transcript_revision_update
AFTER UPDATE OF role, content, api_content, tool_calls, tool_call_id, tool_name, reasoning, reasoning_content,
    reasoning_details ON messages
BEGIN
    -- An in-place rewrite (prompt expansion, multimodal context, a redaction) moves neither the
    -- active row count nor the highest id; this write-side revision is what a transcript-derived
    -- cache keys on (conversation_revision). Whoever wrote the row, old builds included.
    UPDATE sessions SET transcript_revision = transcript_revision + 1 WHERE id = new.session_id;
END;
CREATE INDEX IF NOT EXISTS idx_messages_session_active
    ON messages(session_id, active, timestamp);
CREATE INDEX IF NOT EXISTS idx_messages_display_page
    ON messages(session_id, display_order, active DESC, id DESC)
    WHERE active = 1 OR compacted = 1;
CREATE INDEX IF NOT EXISTS idx_messages_display_backfill
    ON messages(session_id) WHERE (display_order IS NULL OR display_identity IS NULL)
    AND (active = 1 OR compacted = 1);
CREATE INDEX IF NOT EXISTS idx_messages_display_identity
    ON messages(session_id, display_identity, display_order)
    WHERE display_identity IS NOT NULL AND (active = 1 OR compacted = 1);
DROP TRIGGER IF EXISTS messages_message_uid_insert;
CREATE TRIGGER IF NOT EXISTS messages_message_uid_insert
AFTER INSERT ON messages WHEN new.message_uid IS NULL
BEGIN
    -- Every row carries a message_uid, whoever wrote it: a build that predates the column binds NULL, so
    -- the store mints one on its behalf (the current build always binds a uid; this never fires for it).
    -- The note lives inside the body: a comment before DROP/CREATE would defeat the settled-trigger skip.
    UPDATE messages SET message_uid = lower(hex(randomblob(16))) WHERE id = new.id;
END;
DROP TRIGGER IF EXISTS messages_display_order_insert;
CREATE TRIGGER IF NOT EXISTS messages_display_order_insert
AFTER INSERT ON messages WHEN new.display_order IS NULL
BEGIN
    UPDATE messages SET display_order = COALESCE((
        SELECT display_order FROM messages
        WHERE session_id = new.session_id AND id <> new.id
          AND (active = 1 OR compacted = 1)
          AND display_identity = new.display_identity AND display_order IS NOT NULL
        ORDER BY display_order LIMIT 1
    ), new.id) WHERE id = new.id;
END;
DROP TRIGGER IF EXISTS messages_display_visibility_update;
CREATE TRIGGER IF NOT EXISTS messages_display_visibility_update
AFTER UPDATE OF active, compacted ON messages
WHEN (new.active = 1 OR new.compacted = 1) <> (old.active = 1 OR old.compacted = 1)
BEGIN
    UPDATE messages SET display_order = MIN(new.id, COALESCE((
        SELECT display_order FROM messages
        WHERE session_id = new.session_id AND id <> new.id
          AND (active = 1 OR compacted = 1)
          AND display_identity = new.display_identity AND display_order IS NOT NULL
        ORDER BY display_order LIMIT 1
    ), new.id)) WHERE id = new.id
      AND (new.active = 1 OR new.compacted = 1);
    UPDATE messages SET display_order = (SELECT display_order FROM messages WHERE id = new.id)
    WHERE session_id = new.session_id AND id <> new.id AND (active = 1 OR compacted = 1)
      AND display_identity = new.display_identity
      AND (new.active = 1 OR new.compacted = 1);
    UPDATE messages SET display_order = (
        SELECT MIN(peer.id) FROM messages AS peer
        WHERE peer.session_id = old.session_id AND (peer.active = 1 OR peer.compacted = 1)
          AND peer.display_identity = old.display_identity
    ) WHERE session_id = old.session_id AND (active = 1 OR compacted = 1)
      AND display_identity = old.display_identity
      AND NOT (new.active = 1 OR new.compacted = 1);
END;
DROP TRIGGER IF EXISTS messages_display_identity_update;
CREATE TRIGGER IF NOT EXISTS messages_display_identity_update
AFTER UPDATE OF role, content, timestamp, tool_call_id, tool_calls, tool_name,
                display_kind ON messages
WHEN new.role IS NOT old.role
  OR new.content IS NOT old.content
  OR new.timestamp IS NOT old.timestamp
  OR new.tool_call_id IS NOT old.tool_call_id
  OR new.tool_calls IS NOT old.tool_calls
  OR new.tool_name IS NOT old.tool_name
  OR new.display_kind IS NOT old.display_kind
BEGIN
    UPDATE messages SET display_identity = NULL, display_order = NULL
    WHERE id = new.id OR (
        session_id = old.session_id AND display_identity = old.display_identity
        AND (active = 1 OR compacted = 1)
    );
END;
DROP TRIGGER IF EXISTS messages_display_identity_delete;
CREATE TRIGGER IF NOT EXISTS messages_display_identity_delete
AFTER DELETE ON messages WHEN old.active = 1 OR old.compacted = 1
BEGIN
    UPDATE messages SET display_order = (
        SELECT MIN(peer.id) FROM messages AS peer
        WHERE peer.session_id = old.session_id AND (peer.active = 1 OR peer.compacted = 1)
          AND peer.display_identity = old.display_identity
    ) WHERE session_id = old.session_id AND (active = 1 OR compacted = 1)
      AND display_identity = old.display_identity;
END;
CREATE INDEX IF NOT EXISTS idx_messages_active_null
    ON messages(active) WHERE active IS NULL;
-- Logical-identity recovery (agent.transcript_repair._active_logical_message_row) runs inside the write lock.
CREATE INDEX IF NOT EXISTS idx_messages_session_uid
    ON messages(session_id, message_uid) WHERE active = 1;
CREATE INDEX IF NOT EXISTS idx_sessions_session_key
    ON sessions(session_key, started_at DESC);
CREATE INDEX IF NOT EXISTS idx_sessions_gateway_peer
    ON sessions(source, user_id, chat_id, chat_type, thread_id, started_at DESC);
CREATE INDEX IF NOT EXISTS idx_sessions_handoff_state
    ON sessions(handoff_state, started_at);
CREATE INDEX IF NOT EXISTS idx_sessions_system_prompt_hash
    ON sessions(system_prompt_hash);
CREATE INDEX IF NOT EXISTS idx_sessions_tool_names
    ON sessions(tool_names);
-- Recent-session browsing must never derive recency by scanning messages.
-- This expression is the durable, indexable approximation used to preselect
-- a small candidate set before compression-chain and preview hydration.
CREATE INDEX IF NOT EXISTS idx_sessions_effective_activity
    ON sessions(COALESCE(last_activity_at, started_at) DESC, started_at DESC);
-- CLI/TUI listings match gateway rows stored as source='local' by created_source,
-- before LIMIT. created_source is reconciled onto legacy DBs, so this index lives
-- here rather than in SCHEMA_SQL. A source-only index would walk every local row
-- (Desktop and TUI included) to find one surface.
CREATE INDEX IF NOT EXISTS idx_sessions_local_created_started
    ON sessions(created_source, started_at DESC)
    WHERE source = 'local';
ah  
-- Stable projection the base word index reads and writes through: the view
-- computes EXACTLY what the triggers/backfill insert, so 'rebuild' and the
-- integrity checker always agree with the stored index.
CREATE VIEW IF NOT EXISTS messages_fts_src AS
    SELECT id,
           CASE WHEN role = 'tool'
                THEN substr(COALESCE(content, ''), 1, a×  )
                ELSE content END AS content,
           tool_name, tool_calls
    FROM messages;

CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
    content,
    tool_name,
    tool_calls,
    content='messages_fts_src',
    content_rowid='id'
);

CREATE TRIGGER IF NOT EXISTS messages_fts_insert AFTER INSERT ON messages
WHEN (new.id > COALESCE((SELECT CAST(value AS INTEGER) FROM state_meta
                         WHERE key = 'fts_rebuild_high_water'), -1)
   OR new.id <= COALESCE((SELECT CAST(value AS INTEGER) FROM state_meta
                          WHERE key = 'fts_rebuild_progress'), -1))
BEGIN
    INSERT INTO messages_fts(rowid, content, tool_name, tool_calls)
    VALUES (
        new.id,
        a-  ,
        new.tool_name,
        new.tool_calls
    );
END;

CREATE TRIGGER IF NOT EXISTS messages_fts_delete AFTER DELETE ON messages
WHEN (old.id > COALESCE((SELECT CAST(value AS INTEGER) FROM state_meta
                         WHERE key = 'fts_rebuild_high_water'), -1)
   OR old.id <= COALESCE((SELECT CAST(value AS INTEGER) FROM state_meta
                          WHERE key = 'fts_rebuild_progress'), -1))
BEGIN
    INSERT INTO messages_fts(messages_fts, rowid, content, tool_name, tool_calls)
    VALUES (
        'delete',
        old.id,
        aÕ  ,
        old.tool_name,
        old.tool_calls
    );
END;

-- UPDATE OF skips the trigger entirely for non-content column writes
-- (status/compacted/observed/etc.), which is stronger than the WHEN gate
-- alone and avoids FTS I/O saturation on large state.db (#68858 / #73639).
CREATE TRIGGER IF NOT EXISTS messages_fts_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_rebuild_high_water'), -1)
     OR old.id <= COALESCE((SELECT CAST(value AS INTEGER) FROM state_meta
                            WHERE key = 'fts_rebuild_progress'), -1))
BEGIN
    INSERT INTO messages_fts(messages_fts, rowid, content, tool_name, tool_calls)
    VALUES (
        'delete',
        old.id,
        z ,
        old.tool_name,
        old.tool_calls
    );
    INSERT INTO messages_fts(rowid, content, tool_name, tool_calls)
    VALUES (
        new.id,
        z<,
        new.tool_name,
        new.tool_calls
    );
END;
c                ó0   € V ^8„  d   QhR\         R\         /# r�   r,   )r   s   "r   r   r     s   € ÷ 
ñ 
¤3ð 
´ñ 
r   c                ó¤   € V '       d   V  R2MRpV R2RP                  R \         4       4      ,           R\        VR,           R4       R	2,           # )
a  Predicate over a ``sessions`` row selecting sessions whose rows belong in
the trigram index; ``alias`` qualifies every column for joins. Shared by the
view, the sync triggers, and the deferred-backfill INSERT ... SELECTs so they
can never disagree about the index boundary.Ú.rb   zsource NOT IN (r>   c              3   ó.   "  € T F  pR V R 2x € K  	  R# 5iry   rz   )rW   Úsrcs   & r   rZ   Ú*fts_trigram_session_sql.<locals>.<genexpr>"  s   é € ÐGÑ*F 3�a˜�u˜A•JÓ*Fùr}   z) AND r“   z$._delegate_fromz IS NULL)r\   ÚFTS_TRIGRAM_EXCLUDED_SOURCESrA   )r‰   Úqs   & r   Úfts_trigram_session_sqlr    s\   € ÷
 ˆ5ˆ'�‰ "€Aàˆ#ˆ_ÐØ
�)‰)ÑGÕ*FÓGÓ
Gõ	HàÔ$ Q¨Õ%7Ð9KÓLÐMÈXÐ
Võ	Wðr   zË
CREATE VIEW IF NOT EXISTS messages_fts_trigram_src AS
    SELECT m.id, m.role, m.content, m.tool_name
    FROM messages AS m
    JOIN sessions AS s ON s.id = m.session_id
    WHERE m.role <> 'tool' AND a{  ;

CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts_trigram USING fts5(
    content,
    tool_name,
    content='messages_fts_trigram_src',
    content_rowid='id',
    tokenize='trigram'
);

CREATE TRIGGER IF NOT EXISTS messages_fts_trigram_insert AFTER INSERT ON messages
WHEN new.role <> 'tool'
   AND EXISTS (SELECT 1 FROM sessions
               WHERE id = new.session_id AND a[  )
   AND (new.id > COALESCE((SELECT CAST(value AS INTEGER) FROM state_meta
                           WHERE key = 'fts_rebuild_high_water'), -1)
     OR new.id <= COALESCE((SELECT CAST(value AS INTEGER) FROM state_meta
                            WHERE key = 'fts_rebuild_progress'), -1))
BEGIN
    INSERT INTO messages_fts_trigram(rowid, content, tool_name)
    VALUES (new.id, new.content, new.tool_name);
END;

CREATE TRIGGER IF NOT EXISTS messages_fts_trigram_delete AFTER DELETE ON messages
WHEN old.role <> 'tool'
   AND EXISTS (SELECT 1 FROM sessions
               WHERE id = old.session_id AND aÅ  )
   AND (old.id > COALESCE((SELECT CAST(value AS INTEGER) FROM state_meta
                           WHERE key = 'fts_rebuild_high_water'), -1)
     OR old.id <= COALESCE((SELECT CAST(value AS INTEGER) FROM state_meta
                            WHERE key = 'fts_rebuild_progress'), -1))
BEGIN
    INSERT INTO messages_fts_trigram(messages_fts_trigram, rowid, content, tool_name)
    VALUES ('delete', old.id, old.content, old.tool_name);
END;

CREATE TRIGGER IF NOT EXISTS messages_fts_trigram_update
AFTER UPDATE OF content, tool_name, role ON messages
WHEN (old.content IS NOT new.content
    OR old.tool_name IS NOT new.tool_name
    OR old.role IS NOT new.role)
   AND (old.id > COALESCE((SELECT CAST(value AS INTEGER) FROM state_meta
                           WHERE key = 'fts_rebuild_high_water'), -1)
     OR old.id <= COALESCE((SELECT CAST(value AS INTEGER) FROM state_meta
                            WHERE key = 'fts_rebuild_progress'), -1))
BEGIN
    INSERT INTO messages_fts_trigram(messages_fts_trigram, rowid, content, tool_name)
    SELECT 'delete', old.id, old.content, old.tool_name
    WHERE old.role <> 'tool'
      AND EXISTS (SELECT 1 FROM sessions
                  WHERE id = old.session_id AND zç);
    INSERT INTO messages_fts_trigram(rowid, content, tool_name)
    SELECT new.id, new.content, new.tool_name
    WHERE new.role <> 'tool'
      AND EXISTS (SELECT 1 FROM sessions
                  WHERE id = new.session_id AND z);
END;
Úfts_cjk_staleÚ	fts_staleÚfts_rebuild_deferralzô
CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
    content
);

CREATE TRIGGER IF NOT EXISTS messages_fts_insert AFTER INSERT ON messages BEGIN
    INSERT INTO messages_fts(rowid, content) VALUES (
        new.id,
        COALESCE(aó  , '')
        || ' ' || COALESCE(new.tool_name, '') || ' ' || COALESCE(new.tool_calls, '')
    );
END;

CREATE TRIGGER IF NOT EXISTS messages_fts_delete AFTER DELETE ON messages BEGIN
    DELETE FROM messages_fts WHERE rowid = old.id;
END;

CREATE TRIGGER IF NOT EXISTS messages_fts_update
AFTER UPDATE OF content, tool_name, tool_calls, role ON messages BEGIN
    DELETE FROM messages_fts WHERE rowid = old.id;
    INSERT INTO messages_fts(rowid, content) VALUES (
        new.id,
        COALESCE(zg, '')
        || ' ' || COALESCE(new.tool_name, '') || ' ' || COALESCE(new.tool_calls, '')
    );
END;
a$  
CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts_trigram USING fts5(
    content,
    tokenize='trigram'
);

CREATE TRIGGER IF NOT EXISTS messages_fts_trigram_insert AFTER INSERT ON messages BEGIN
    INSERT INTO messages_fts_trigram(rowid, content) VALUES (
        new.id,
        COALESCE(a  , '')
        || ' ' || COALESCE(new.tool_name, '') || ' ' || COALESCE(new.tool_calls, '')
    );
END;

CREATE TRIGGER IF NOT EXISTS messages_fts_trigram_delete AFTER DELETE ON messages BEGIN
    DELETE FROM messages_fts_trigram WHERE rowid = old.id;
END;

CREATE TRIGGER IF NOT EXISTS messages_fts_trigram_update
AFTER UPDATE OF content, tool_name, tool_calls, role ON messages BEGIN
    DELETE FROM messages_fts_trigram WHERE rowid = old.id;
    INSERT INTO messages_fts_trigram(rowid, content) VALUES (
        new.id,
        COALESCE(Úhermes_stateg      ^@gš™™™™™¹?Úwin32g      @c                ó0   € V ^8„  d   QhR\         R\        /# )r   Úexcr   )ÚBaseExceptionrƒ   )r   s   "r   r   r   é  s   € ÷ sñ s¤]ð s´tñ sr   c                óˆ   € \        V \        4      ;'       g,    \        V \        4      ;'       d    V P                  \        9   # )zcTrue when *exc* means another process holds the lock; on any other ``OSError`` fail closed at once.)r…   ÚBlockingIOErrorrÂ   ÚerrnoÚ_LOCK_CONTENTION_ERRNOS)r  s   &r   Úis_advisory_lock_contentionr!  é  s0   € ä�cœ?Ó+×rÐr´
¸3ÄÓ0H×0qÐ0qÈSÏYÉYÔZqÑMqÐrr   c                ó$   € V ^8„  d   QhR\         /# )r   Úpidrë   )r   s   "r   r   r   î  s   € ÷ ñ œ3ñ r   c                ó6  €  \        RV  R2R4      ;_uu_ 4       p\        VP                  4       P                  R^4      ^,          P	                  4       ^,          4      uuRRR4       #   + '       g   i     R# ; i  \
        \        \        3 d     R# i ; i)uÊ   Kernel start time of *pid* (field 22 of ``/proc/<pid>/stat``; with the PID it identifies a process
uniquely).  None off Linux or on any failure â€” callers must treat None as unknowable and FAIL CLOSED.z/proc/z/statÚrbó   )N)Úopenr¿   ÚreadÚrsplitrl   rÂ   Ú
ValueErrorÚ
IndexError)r#  Úfhs   & r   Ú_proc_start_ticksr-  î  st   € ðä�F˜3˜%˜uÐ% t×,Ô,°Ü�r—w‘w“y×'Ñ'¨¨aÓ0°Õ3×9Ñ9Ó;¸BÕ?Ó@÷ -×,×,Ó,ûä”Z¤Ð,ô Úðús/   ‚A> šAA*Á
A> Á*A;	Á5A> Á;A> Á>BÂBc                ó  €  V P                  ^ 4       V P                  R4      pV'       d'   \        P                  ! VP	                  RR4      4      MRp\        T\        4      '       d   T# R#   \
        \        \        3 d     R# i ; i)z=Best-effort parse of the holder metadata JSON in a lock file.i   úutf-8r2   N)
Úseekr(  ÚjsonÚloadsÚdecoderÂ   r*  ÚUnicodeDecodeErrorr…   Údict)Úhandlere   Úrecords   &  r   Ú_read_lock_holder_recordr8  ù  sr   € ðØ�‰�AŒØ�k‰k˜$Óˆß?B”—’˜CŸJ™J w°	Ó:Ô;Èˆô   ¬×-Ò-ˆ6Ð7°4Ð7øô ”ZÔ!3Ð4ô Úðús   ‚)A. ¬(A. Á.BÂBc                ó(   € V ^8„  d   QhR\         RR/# )r   r  r   N)Úbytes)r   s   "r   r   r     s   € ÷ ñ ¬ð °$ñ r   c                ó"  € \         P                  ! \        \        4      ;_uu_ 4        V P	                  ^ 4       V P                  4        V'       d   V P                  V4       V P                  4        RRR4       R#   + '       g   i     R# ; i)z8Best-effort truncate-and-write of *payload* at offset 0.N)Ú
contextlibÚsuppressrÂ   r*  r0  ÚtruncateÚwriteÚflush)r6  r  s   &&r   Ú_rewrite_lock_filerA    sQ   € ä	×	Ò	œW¤j×	1Õ	1Ø�‰�AŒØ�‰ÔßØ�L‰L˜Ô!Ø�‰Œ÷ 
2×	1×	1Ò	1ús   ¨AA=Á=B	c                ó   € V ^8„  d   QhRR/# r¾   rz   )r   s   "r   r   r     s   € ÷ 
Sñ 
S¨ñ 
Sr   c           	     ó  € R\         P                  ! 4       R\        4       R\        \         P                  ! 4       4      R\        P                  ! 4       /p\        V \        P                  ! VRR7      P                  R4      4       R# )	u_  Record this process as holder (best effort) so timed-out contenders can tell an orphaned-fd holder
from a live wedged one.

Written under the flock so contenders that time out can tell an orphaned-fd holder (recorded process
dead, flock inherited by a forked child â€” issue #100108) from a live wedged holder.  ``pidns``: see
``hermes_state_pidns``.
r#  ÚpidnsÚstart_ticksÚacquired_atT)Ú	sort_keysr/  N)	r&   Úgetpidr   r-  ÚtimerA  r1  ÚdumpsÚencode)r6  r7  s   & r   Ú_write_lock_holder_recordrL    sZ   € ð ”R—Y’Y“[ 'Ô+;Ó+=ØÔ.¬r¯yªy«{Ó;¸]ÌDÏIÊIËKðY€Fä�vœtŸzšz¨&¸DÔA×HÑHÈÓQÖRr   c                ó   € V ^8„  d   QhRR/# r¾   rz   )r   s   "r   r   r     s   € ÷ $ñ $¨ñ $r   c                ó   € \        V R4       R# )zfErase holder metadata before a normal release: a surviving record means ABNORMAL exit (break allowed).r   N)rA  )r6  s   &r   Ú_clear_lock_holder_recordrO    s   € ä�v˜sÖ#r   c                ó$   € V ^8„  d   QhR\         /# r�   r‚   )r   s   "r   r   r      s   € ÷ Iñ I¬$ñ Ir   c                óŠ  €  \        V R,          4      pT^ 8:  d   R# \	        T P                  R4      4      '       g   R#  \        P                  ! T^ 4       T P                  R4      pTf   R# \        T4      pTRJ;'       d    Y28g  #   \        \        \        3 d     R# i ; i  \         d     R# \         d     R# i ; i)a  True ONLY when the recorded holder is provably dead or PID-recycled.  Anything indeterminate
(no/malformed record, PID owned by another user, /proc unavailable, foreign PID namespace)
is False: FAIL CLOSED and defer.  Unstamped records keep probing: see ``hermes_state_pidns``.r#  FrD  TrE  N)r¿   ÚKeyErrorÚ	TypeErrorr*  r   r$   r&   ÚkillÚProcessLookupErrorrÂ   r-  )r7  r#  Úrecorded_ticksÚcurrent_tickss   &   r   Ú_lock_holder_provably_deadrX     s¼   € ðÜ�&˜•-Ó ˆð ˆa„xÙÜ,¨V¯Z©Z¸Ó-@×AÒAÙðÜ
�Š��QŒð
 —Z‘Z Ó.€NØÒÙÜ% cÓ*€MØ Ð$×HÐH¨Ñ)HÐHøô! ”i¤Ð,ô Úðûô ô ÚÜô Úðús/   ‚B	 Á B& Â	B#Â"B#Â&CÂ4CÂ=CÃCc           	     óž  € ^ RI p\        P                  ! 4       V,           pRp  VP                  VP	                  4       VP
                  VP                  ,          4        \         P*                  ! TP	                  4       4      \         P,                  ! T 4      rºT
P.                  TP.                  8H  ;'       d    T
P0                  TP0                  8H  pT'       d   \3        T4       RT3#  TP%                  4        \'        T R4      p\        P                  ! 4       T8¼  g   EK  RT3#   \        \        3 Edi   p\        T4      '       g!   \        P                  RY@Y‚4       RT3u Rp?# \        P                  ! 4       T8  d   \        P                  ! T4        Rp?EK€  T'       d
   RT3u Rp?# \        T4      p	\        T	4      '       g
   RT3u Rp?# \        P                  RY@T	;'       g    / P                  R4      4        \         P"                  ! T 4       TP%                  4        \'        T R4      pM8  \         d+   p\        P                  RY@T4       RT3u Rp?u Rp?# Rp?ii ; iRp\        P                  ! 4       \(        ,           p Rp?EK{  Rp?ii ; i  \         d    Rp ELâi ; i  \         d    RT3u # i ; i)	uë  Bounded POSIX flock acquire with orphaned-holder break.  Returns ``(acquired, handle)``; *handle* may
have been re-opened and the caller closes whichever comes back.  *acquired*: True, False (a holder kept
the lock past the deadline) or None (non-contention ``OSError``, already logged: treat as not acquired
without the held-by-another-process warning).  ``flock`` rides the open file DESCRIPTION, which ``fork()``
duplicates, so a holder that forks then dies leaves the lock held forever; when the acquirer is provably
dead the file is unlinked and retaken on a fresh inode (the orphan's flock excludes nobody).  Every
acquire verifies its inode still names *lock_path*, so a racer on a dead inode retries.

A holder that forks (multiprocessing worker, daemonized helper) and then dies leaves the flock held by a
child that will never release it â€” the kernel's holder-death release never triggers, and every contender
defers forever. Indeterminate liveness always defers (fail closed). See #100108.
NFTuv   Could not acquire %s %s (%s) â€” deferring rather than waiting out the %.0fs holder timeout on a non-contention error.u°   %s %s is held by an orphaned file descriptor (recorded holder pid %s is dead â€” a forked child inherited the lock fd); breaking the stale lock and retaking it on a fresh file.r#  úa+bu/   Could not break stale %s %s (%s) â€” deferring.)ÚfcntlrI  Ú	monotonicÚflockÚfilenoÚLOCK_EXÚLOCK_NBr  rÂ   r!  r  r  Úsleepr8  rX  r$   r&   ÚunlinkÚcloser'  Ú_LOCK_BREAK_REACQUIRE_SECONDSÚfstatrÁ   rÃ   rÄ   rL  )Ú	lock_pathr6  Útimeout_secondsÚpoll_secondsÚdescriptionr[  ÚdeadlineÚ
broke_lockr  r7  Úfd_statÚ	path_statÚ	same_files   &&&&&        r   Ú_acquire_db_flockro  9  s=  € ó Ü�~Š~Ó /Õ1€HØ€JØ
ð	Ø�K‰K˜Ÿ™›¨¯©¸¿¹Õ)FÔGð:	Ü!#§¢¨&¯-©-«/Ó!:¼B¿GºGÀIÓ<N�YØŸ™¨)×*:Ñ*:Ñ:×aÐa¸w¿~¹~ÐQZ×QaÑQaÑ?aˆI÷ Ü% fÔ-Ø˜�<Ðð	!Ø�L‰LŒNÜ˜) UÓ+ˆFô �>Š>Ó˜x×'Ø˜&�=Ð øôU  ¤Ð)õ 	Ü.¨s×3Ò3Ü—‘ð  aà*°sôMð ˜V�|Õ#Ü�~Š~Ó (Ô*Ü—
’
˜<Ô(ÞßØ˜f�}Õ$Ü-¨fÓ5ˆFÜ-¨f×5Ò5Ø˜f�}Õ$Ü�N‰Nð )à*5À6Ç<À<ÈR×BTÑBTÐUZÓB[ô]ð%Ü—	’	˜)Ô$Ø—‘”Ü˜i¨Ó/‘øÜô %Ü—‘ÐPÐR]ÐjmÔnØ˜f�}×$Ò$ûð%úð ˆJÜ—~’~Ó'Ô*GÕGˆHÞûð5	ûô> ô 	Ø‹Ið	ûô ô 	!Ø˜&�=Ò ð	!ús¨   ¥;D) Á!A3J& Ã,J: Ä)J#Ä;+JÅ&J#Å,0JÆ#JÆ+JÆ.J#Æ4JÇJ#ÇJÇ3JÈ
2H=È<JÈ=I2ÉI-É"I2É#JÉ'J#É-I2É2%JÊJ#Ê&J7Ê6J7Ê:KËKc                ó$   € V ^8„  d   QhR\         /# r�   r,   )r   s   "r   r   r   y  s   € ÷ +ñ +¤Sñ +r   c                ó~  € \        V \        4      '       d   RV 9  d   R# Rp\        P                  ! \        \
        4      ;_uu_ 4        V P                  R4      e2   R\        P                  ! 4       \        V R,          4      ,
          R R2pRRR4       R	V P                  R4       V 2#   + '       g   i     L&; i)
z5Human-readable holder identity for deferral warnings.r#  z8unknown (no holder record; pre-fix writer or non-Hermes)rb   rF  Nz, acquired z.0fzs agozpid )	r…   r5  r<  r=  rS  r*  r$   rI  Úfloat)r7  Úages   & r   Ú_describe_lock_holderrt  y  s‘   € ä�fœd×#Ò# u°FÔ':ÙIØ
€CÜ	×	Ò	œY¬
×	3Õ	3Ø�:‰:�mÓ$Ò0Ø¤§	¢	£¬e°F¸=Õ4IÓ.JÕ JÈ3ÐOÈuÐUˆC÷ 
4ð �&—*‘*˜UÓ#Ð$ S EÐ*Ð*÷ 
4×	3ús   Á	AB,Â,B<	c                óÎ  € ^ RI p\        P                  ! 4       V,           p  VP                  ^ 4       VP	                  VP                  4       VP                  ^4       R#   \        \        3 ds   p\        T4      '       g   \        P                  RY4        Rp?R# \        P                  ! 4       T8¼  d    Rp?R# \        P                  ! \        4        Rp?K¾  Rp?ii ; i)zbWindows counterpart of ``_acquire_db_flock`` (no orphan break); same True / False / None contract.NTuS   Could not acquire FTS rebuild lock %s (%s) â€” deferring on a non-contention error.F)ÚmsvcrtrI  r\  r0  Úlockingr^  ÚLK_NBLCKr  rÂ   r!  r  r  ra  Ú_FTS_REBUILD_LOCK_POLL_SECONDS)rf  r6  Útimeoutrv  rj  r  s   &&&   r   Ú_acquire_msvcrt_lockr{  „  s�   € ãÜ�~Š~Ó 'Õ)€HØ
ð	7Ø�K‰K˜ŒNØ�N‰N˜6Ÿ=™=›?¨F¯O©O¸QÔ?ÙøÜ¤Ð)ô 	7Ü.¨s×3Ò3Ü—‘ÐtØ(ô/åÜ�~Š~Ó 8Ô+ÝÜ�JŠJÔ5×6Ò6ûð	7ús#   £<A! Á!C$Á2'CÂCÂ?CÃC$rg  c          	   #  ó&  "  € V f   Rx € R# Vf   \         M\        \        V4      R4      pV  R2p \        VR4      pRp \        '       d   \        Y4T4      pM\        Y4T\        R4      w  rdTf   RpMiT'       gb   \        '       d   RM
\        T4      pT^ 8:  d"   \
        P                  R	T\        T4      4       M \
        P                  R
Y2\        T4      4       Tx €  \        P                  ! \        4      ;_uu_ 4        T'       dN   \        '       dB   ^ RIpTP#                  ^ 4       TP%                  TP'                  4       TP(                  ^4       MAT'       d:   ^ RIp	\-        T4       T	P/                  TP'                  4       T	P0                  4       RRR4       TP3                  4        R#   \         d&   p\
        P                  RY54       Rx €  Rp?R# Rp?ii ; i  + '       g   i     LU; i  TP3                  4        i ; i   \        P                  ! \        4      ;_uu_ 4        T'       dN   \        '       dB   ^ RIpTP#                  ^ 4       TP%                  TP'                  4       TP(                  ^4       MAT'       d:   ^ RIp	\-        T4       T	P/                  TP'                  4       T	P0                  4       RRR4       M  + '       g   i     M; iTP3                  4        i   TP3                  4        i ; i; i5i)aÉ  Serialize full structural FTS rebuilds on *db_path* across processes.  Yields True when this process
holds the authority, False when the bounded acquire timed out or the lock file could not be opened: the
caller must NOT rebuild (fail closed; the stale breadcrumb guarantees a retry).  ``db_path`` None
(in-memory) yields True.  In-process retries pass ``timeout_seconds=0`` so a live holder never stalls a
long-lived writer; the orphan break still applies.NTg        z.fts_rebuild.lockrZ  uz   Could not open FTS rebuild lock %s (%s) â€” deferring this rebuild rather than running it without cross-process authority.FzFTS rebuild lockux   FTS rebuild lock %s is busy â€” deferring this retry (the stale-FTS breadcrumb keeps it retryable). Recorded holder: %s.u¹   FTS rebuild lock %s held by another process for more than %.0fs â€” deferring this rebuild to avoid racing the holder (the stale-FTS breadcrumb keeps it retryable). Recorded holder: %s.)Ú!_FTS_REBUILD_LOCK_TIMEOUT_SECONDSÚmaxrr  r'  rÂ   r  r  Ú_IS_WINDOWSr{  ro  ry  r8  Úinfort  r<  r=  rv  r0  rw  r^  ÚLK_UNLCKr[  rO  r]  ÚLOCK_UNrc  )
r  rg  rz  rf  r6  r  Úacquiredr7  rv  r[  s
   &$        r   Úfts_rebuild_admissionr„  —  sC  é € ð ‚ØŠ
ÙØ3BÒ3JÕ/ÔPSÔTYÐZiÓTjÐloÓPp€GØ�)Ð,Ð-€Ið	Ü�i Ó'ˆð €Hð!ß‹;Ü+¨I¸wÓG‰Hä0Ø 7Ô,JÐL^ó `ÑˆHàÒà‰Hßß(›[‘TÔ.FÀvÓ.NˆFØ˜!Œ|ä—‘ð bà%Ô'<¸VÓ'DõFô —‘ð  BàCLÔWlÐmsÓWtôvð Šð	Ü×$Ò$¤W×-Õ-ß§£Û!Ø—K‘K ”NØ—N‘N 6§=¡=£?°F·O±OÀQÕGßÛ Ü-¨fÔ5Ø—K‘K §¡£°·±Ô?÷ .ð �L‰LŽNøôU ô ô 	�‰ð QØR[ô	bàŠÝûðú÷@ .×-ûð �L‰L�Nûð	Ü×$Ò$¤W×-Õ-ß§£Û!Ø—K‘K ”NØ—N‘N 6§=¡=£?°F·O±OÀQÕGßÛ Ü-¨fÔ5Ø—K‘K §¡£°·±Ô?÷ .×-×-Ð-úð �L‰L�NøˆF�L‰L�NÿsÌ   ‚/L²F6 ¾LÁH Á/H Á=H Â	AH Ã#"G< ÄG)ÄAG)Å#9G)ÆG< Æ$LÆ6G&ÇG!ÇLÇ!G&Ç&LÇ)G9	Ç4G< Ç<HÈLÈLÈ"K9È5K	É
AK	Ê9K	Ë	K9ËK%Ë K9Ë(LË9LÌLÌLc                óH   € V ^8„  d   QhR\         R\         R\        R\         /# )r   re   Úfallbackr  r   rf   )r   s   "r   r   r   Ò  s(   € ÷ ñ ”#ð ¤ð ¬sð ´sñ r   c                ó    €  \         P                  ! V 4      #   \         P                  \        3 d    \        P                  T4       Tu # i ; i)zD``json.loads(raw)``; on failure log *warning* and return *fallback*.)r1  r2  ÚJSONDecodeErrorrS  r  r  )re   r†  r  s   &&&r   Ú_json_orr‰  Ò  s@   € ðÜ�zŠz˜#‹ÐøÜ× Ñ ¤)Ð,ô Ü�‰�wÔØŠðús   ‚ ˜2AÁA)zs.id)Úsession_resetÚsession_switchÚidleÚdailyÚ	suspendedÚresume_pending_expired)Úagent_closeÚws_orphan_reapÚsuperseded_by_resumeÚstartup_orphan_reap>   Ú	lru_evictÚidle_timeoutÚtui_shutdownÚws_disconnect)rb   rÒ   )r™   )N)Úmessages_fts_insertÚmessages_fts_deleteÚmessages_fts_updateÚmessages_fts_trigram_insertÚmessages_fts_trigram_deleteÚmessages_fts_trigram_update)ÚcronÚsubagent)rb   )Úmessages_fts_cjk_insertÚmessages_fts_cjk_deleteÚmessages_fts_cjk_update)ŽÚ__doc__r<  r  r1  Úloggingr&   ÚsysrI  Útypingr   Úhermes_cli.timefmtr   r   Úhermes_state_keysr   Úhermes_state_pidnsr   r   Úagent.skill_commandsr   r	   r
   r   Úagent.context_compressorr   r   r   r   r   ÚTITLE_SOURCE_DERIVEDÚTITLE_SOURCE_LLMÚTITLE_SOURCE_USERÚ_PREVIEW_HEAD_CHARSÚ_PREVIEW_SCAFFOLD_WINDOWrm   r(   r4   Ú_PREVIEW_CONTENT_SQLÚ_PREVIEW_SCAFFOLDED_SQLrF   r8   ÚQUEUED_PROMPT_METADATA_KEYrA   rH   rK   r]   ra   rl   Ú_PREVIEW_LONG_FORM_PREFIXÚ_PREVIEW_SUMMARY_PREFIXESÚ_PREVIEW_STANDALONE_SUMMARY_SQLÚ_PREVIEW_MERGED_AFTER_SQLÚ_PREVIEW_MERGED_SUMMARY_SQLÚ_PREVIEW_MERGED_PRIOR_SQLÚ"_PREVIEW_MERGED_PRIOR_LTRIMMED_SQLrV   Ú#_PREVIEW_MERGED_PRIOR_UNWRAPPED_SQLÚ!_PREVIEW_FORCE_USER_REMAINDER_SQLru   r\   rt   ro   rw   r    r¡   Ú_RESET_END_REASONSÚ_RESET_END_REASONS_SQLÚ	frozensetÚ_BOUNDARY_END_REASONSÚ_RECOVERABLE_END_REASONSÚ_RECOVERABLE_END_REASONS_SQLr†   r‡   rŒ   r˜   r¢   r   Ú_LISTABLE_CHILD_SQLr¤   r¨   r©   r¯   r³   r¶   ÚSCHEMA_VERSIONÚAUTO_VACUUM_MIN_FREELIST_RATIOÚFTS_STORAGE_VERSIONr¹   rº   Ú_FTS_NEW_INDEXED_CONTENT_SQLÚ_FTS_OLD_INDEXED_CONTENT_SQLÚMAX_FTS5_QUERY_CHARSrÆ   Ú_ENDED_ROW_SQLÚ_COMPRESSION_LOCK_ROW_SQLrÍ   rÖ   rÚ   rÜ   rè   Ú_SQL_IN_CHUNKrï   r  Ú_FTS_TRIGGERSÚ
SCHEMA_SQLÚDEFERRED_INDEX_SQLÚFTS_SQLr  r  ÚFTS_TRIGRAM_SESSION_SQLÚFTS_TRIGRAM_SQLÚ_FTS_CJK_TRIGGERSÚFTS_CJK_STALE_KEYÚFTS_STALE_KEYÚFTS_REBUILD_DEFERRAL_KEYÚLEGACY_FTS_SQLÚLEGACY_FTS_TRIGRAM_SQLÚ	getLoggerr  r}  ry  Úplatformr  rd  ÚEAGAINÚEACCESÚEWOULDBLOCKÚEDEADLKr   r!  r-  r8  rA  rL  rO  rX  ro  rt  r{  Úcontextmanagerr„  r‰  rz   r   r   Ú<module>rà     s  ðñ4ó Û Û Û Û 	Û 
Û Ý ç 3Ý /ß R÷ Fó  F÷4õ 4ð
 !Ð ØÐ ØÐ ð Ð ØÐ ØÐ õ#õ(Nð MÐ ð
 Ð/Ð0ð 1Ø6Ð7°rð;ð ð @€õ/ð .Ð õ>õ5õ4õlõ
Zð +×0Ò0°À!ÓDÀQÕGÐ Ø6Ð8MÐNÐ Ù"2°;Ð@YÓ"ZÐ Ù-Ð.GÓHÐ Ø!3±LÐAZÓ4[Ð3\ð ]ÙÐ6Ð8QÓRÐSÐSTð VÐ á0Ø,©\Ð:SÓ-TÐ,UÐU[Ð\ó^Ð á%:Ð;TÓ%UÐ "Ø):Ð;]Ð:^ð _ÙÐ(Ó)Ð*¨$©|Ð<XÓ/YÐ.ZØÑ" WÐ-OÐ,PÐPRÑSVÐWsÓStÐwxÕSxÐRyÐyzÐ#{Ó|Ð}ØÐ&Ð' tð(-Ð #ñ %6Ð6IÓ$JÐ !ðØ1Ð2°)Ð<WÐ;Xð YØ+Ð,Ð,BÁ<ÐPcÓCdÐBeð fÙ'Ð(IÓJÐKð LØ'Ð(ØÑ'Ð(KÓLÐMÈXðWÐ ÷Jó J€jð JÐ0Ð1ð J°ð JÐ8YÐ7Zð JØðJØ(Ð)ðJØ)/ðJØ0SÐ/TðJàðJà$Ð%ðJà%>ðJà?WÐZ[Õ?[Ð>\ðJð ðJð )Ð)ðJð */ðJð 0HÐ.HðJð IOðJð PcÐNcðJðdðJð 'Ð'ð	Jð (+ð	Jð ,DÐ*Dð	JðEð	Jð
 %Ð%ðJð
 &3ðJð
 4HÐ2HðJð
 INðJð
 OgÐijÕNjÐMkðJð
lðJð )Ð)ðJð */ðJð 0CÐ.CðJð DIôJð õY÷bñ *Ð*<Ð>PÓQÐRð S]ð ]Ð ð0Ð ð
 qÐ ØŸ™Ñ"RÑ?QÓ"RÓRÐ ñ "Ð"4Ó5¸¸ÕGÐ ð lÐ ð  $Ÿy™yÑ(^ÑE]Ó(^Ó^Ð ñ #Ð#;Ó<ò @Bõ BÐ õHõj÷9ñ )Ð);¸_ÓMÐNð Oð Ù$ UÐ,BÓCõDÐ ð :Ð:K×:RÑ:RÐUXÐ:RÓ:YÐ9ZØ
Ð×"Ñ" SÐ"Ó)Ð
*¨!ð-Ð ÷nð ˜I™=¨¨i©]Ð;€õ[õ
J÷_õ
Yð €ð "&Ð ð  Ð ð !&Ð õ%ñ  8¸Ó>Ð Ù7¸Ó>Ð ð Ð õGð J€ØcÐ õbõYð lð õ!÷ð. €ð !.÷ &ð"K¸Tð "KÐPTô "KðJn€ðfaðL duõMfuðLYxõM€
ðN
DÐ ðF7ð 8UÐ6Uð V	ð, 
&Ð&ð '	ð  
&Ð&ð '	ð0 
&Ð&ð '	ð 
&Ð&ð 'ðYQ€ðV  4Ð ÷
ñ 2Ó3Ð ð ñ
 !8¸Ó <Ð=ð >.ð /FÐ-Fð G.ð /FÐ-Fð G1ð0 2IÐ0Ið J1ð
 2IÐ0Ið Jðy>€ð@ fÐ ð $Ð ð €ð 2Ð ðð /Ð/ð 0ð /Ð/ð 0ð-€ð:	ð /Ð/ð 0ð /Ð/ð 0ð/Ð ðx 
×	Ò	˜>Ó	*€à$)Ð !Ø!$Ð Ø�l‰l˜gÑ%€à #Ð ð !Ÿ<™<¨¯©°u×7HÑ7HÈ%Ï-É-ÐXÐ õsõ
ò8õõ
Sõ$õ
Iò2=!õ@+ò7ð& ×Òð7°dó 7ó ð7÷tr   