+
    „WÝjº2  ã                  ó  € R t ^ RIHt ^ RIt^ RIt^ RIHtHtHt ^ RI	H
t
 ^ RIHtHt ]P                  ! R4      t]P"                  ! R4      t]P"                  ! R4      t]P"                  ! R	4      tR
 R lt ! R R4      tR R ltR# )zgSession title mixin for SessionDB: sanitizing, auto/user provenance ranking, and
lineage-aware lookups.)ÚannotationsN)ÚAnyÚDictÚOptional)Ú_sanitize_surrogates)Ú_COMPRESSION_CHILD_SQLÚescape_likeÚhermes_statez [\x00-\x08\x0b\x0c\x0e-\x1f\x7f]zB[\u200b-\u200f\u2028-\u202e\u2060-\u2069\ufeff\ufffc\ufff9-\ufffb]z^(.*?) #(\d+)$c               ó    € V ^8„  d   QhRRRR/# ©é   Ú
base_titleÚstrÚreturn© )Úformats   "ÚGC:\Users\johnn\AppData\Local\hermes\hermes-agent\hermes_state_titles.pyÚ__annotate__r      s   € ÷ /ñ /¨Cð /°Cñ /ó    c                ó‚  € \         P                  V4      pV'       d   VP                  ^4      MTpV P                  RV\	        V4       R234      P                  4       pV'       g   V# R V 4        Uu. uF&  qU'       g   K  \        VP                  ^4      4      NK(  	  ppV R\        ^.VO4      ^,            2# u upi )z�Next title in a lineage ("my session" -> "my session #2") as seen by *conn*: strip any
" #N" suffix, then increment the highest existing number.zESELECT title FROM sessions WHERE title = ? OR title LIKE ? ESCAPE '\'ú #%c              3  óZ   "  € T F!  p\         P                  V^ ,          4      x € K#  	  R# 5i)é    N)Ú_NUMBERED_TITLE_REÚmatch)Ú.0Úrows   & r   Ú	<genexpr>Ú(next_title_in_lineage.<locals>.<genexpr>"   s%   é € Ð(ZÑUYÈcÔ);×)AÑ)AÀ#ÀaÅ&×)IÐ)IÓUYùs   ‚)+z #)r   r   ÚgroupÚexecuteÚ_escape_likeÚfetchallÚintÚmax)Úconnr   r   ÚbaseÚrowsÚmÚnumberss   &&     r   Únext_title_in_lineager*      s´   € ô ×$Ñ$ ZÓ0€Eß"ˆ5�;‰;�qŒ>¨
€DØ�<‰<ØPØ	”,˜tÓ$Ð% SÐ)Ð*ó,ç,4©H«Jð 	÷ Øˆá(ZÑUYÔ(ZÓ`Ñ(Z 1Ó^_ŒŒs�1—7‘7˜1“:ŽÑ(Z€GÐ`ØˆV�2”c˜1˜-˜w˜-Ó(¨1Õ,Ð-Ð.Ð.ùò as   Á4	B<ÂB<c                  óÖ   € ] tR t^&tRt]R R l4       t]R R l4       tR R lt	R R	 lt
R
 R ltR R ltR R ltR R ltR R ltR R ltR R ltR R ltR R ltR R ltRtR# ) ÚSessionTitlesMixinz<Sanitizing, ranking auto/user titles, lineage-aware lookups.c               ó    € V ^8„  d   QhRRRR/# )r   ÚsourceúOptional[str]r   r#   r   )r   s   "r   r   ÚSessionTitlesMixin.__annotate__*   s   € ÷ [ñ [ ð [°3ñ [r   c                ó|   € V P                   pVf   W P                  ,          # VP                  \        V4      ^ 4      # )zŠRank a stored title_source. NULL (pre-provenance rows) is indistinguishable from a
manual ``/title`` of that era, so it ranks as ``user``.)Ú_TITLE_SOURCE_RANKÚTITLE_SOURCE_USERÚgetr   )Úclsr.   Úranks   && r   Ú_title_rankÚSessionTitlesMixin._title_rank)   s7   € ð ×%Ñ%ˆØ.4ªnˆt×)Ñ)Õ*ÐZÀ$Ç(Á(Ì3ÈvË;ÐXYÓBZÐZr   c               ó    € V ^8„  d   QhRRRR/# )r   Útitler/   r   r   )r   s   "r   r   r0   1   s   € ÷ ñ ˜mð °ñ r   c           	     ój  € ^ RI Hp V '       g   R# \        P                  R\        P                  R\        V 4      4      4      p\        P                  ! RRV4      P                  4       pV'       g   R# \        V4      VP                  8”  d&   \        R\        V4       RVP                   R24      hV# )	zžStrip control/zero-width/bidi chars (and lone surrogates sqlite3 cannot bind),
collapse whitespace, normalize empty to None. ValueError past MAX_TITLE_LENGTH.)Ú	SessionDBNÚ z\s+Ú zTitle too long (z chars, max Ú))r	   r<   Ú_TITLE_INVISIBLE_REÚsubÚ_TITLE_CONTROL_REr   ÚreÚstripÚlenÚMAX_TITLE_LENGTHÚ
ValueError)r:   r<   Úcleaneds   &  r   Úsanitize_titleÚ!SessionTitlesMixin.sanitize_title0   s“   € õ 	+ßÙÜ%×)Ñ)¨"Ô.?×.CÑ.CÀBÔH\Ð]bÓHcÓ.dÓeˆÜ—&’&˜  gÓ.×4Ñ4Ó6ˆßÙÜˆw‹<˜)×4Ñ4Ô4ÜÐ/´°G³¨~¸\È)×JdÑJdÐIeÐefÐgÓhÐhØˆr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   Úancestor_idr   Údescendant_idr   Úboolr   )r   s   "r   r   r0   ?   s(   € ÷ Uñ U¸Sð UÐQTð UÐY]ñ Ur   c               ó°   € V'       d   V'       d   W#8X  d   R# \         P                  ! RR7      pVP                  RV R2W2V34      P                  4       RJ# )zÐTrue if *ancestor_id* is a compression predecessor of *descendant_id*, via the
canonical continuation edge ``_COMPRESSION_CHILD_SQL`` (excludes delegate/branch
children that also carry ``parent_session_id``).FÚchild)Úaa9  
            WITH RECURSIVE ancestors(id) AS (
                SELECT ?
                UNION
                SELECT parent.id
                FROM ancestors a
                JOIN sessions child ON child.id = a.id
                JOIN sessions parent ON parent.id = child.parent_session_id
                WHERE z`
            )
            SELECT 1 FROM ancestors WHERE id = ? AND id != ? LIMIT 1
            N)r   r   r    Úfetchone)Úselfr%   rL   rM   Úedges   &&$$ r   Ú_is_compression_ancestorÚ+SessionTitlesMixin._is_compression_ancestor?   sf   € ÷ §-°;Ô3OÙÜ%×,Ò,¨wÔ7ˆØ�|‰|ð !ð �fð ðð  ¨mÐ<ó>÷ ?G¹h»jÐPTðUð 	Ur   c               ó(   € V ^8„  d   QhRRRRRRRR/# ©r   Ú
session_idr   r:   r.   r   rN   r   )r   s   "r   r   r0   S   s)   € ÷ 	ñ 	¨Sð 	¸ð 	Èð 	ÐQUñ 	r   c               óL   a aaa€ \        S P                  V VVV3R l4      4      # )aÚ  Write a title, enforcing provenance precedence. A ``user`` write always lands;
``derived``/``llm`` land only when the row is untitled or holds strictly lower
authority (derived upgrades to llm exactly once, nothing overwrites a user name,
re-running the titler on an llm row is a no-op). No writer may move a hidden
canonical Bot Chat off its title. Read and write are one compare-and-swap
transaction, so a manual ``/title`` racing an in-flight generation is not clobbered.c                ó.   <€ SP                  V SSSR 7      # )©r.   )Ú!_set_session_title_in_transaction)r%   rS   rY   r.   r:   s   &€€€€r   Ú<lambda>Ú7SessionTitlesMixin._set_session_title.<locals>.<lambda>[   s   ø€ ˜×?Ñ?ÀÀjÐRWÐ`fÐ?Ôgr   )rN   Ú_execute_write©rS   rY   r:   r.   s   fffdr   Ú_set_session_titleÚ%SessionTitlesMixin._set_session_titleS   s"   û€ ô �D×'Ñ'ßgó
ó ð 	r   c               ó$   € V ^8„  d   QhRRRRRR/# )r   rY   r   r:   r.   r   )r   s   "r   r   r0   ^   s'   € ÷ #@ñ #@À#ð #@Ècð #@Ð^añ #@r   c          	     óÞ  € V P                  V4      pW@P                  8H  pV'       g   V P                  V4      MRp. pVP                  RV34      P	                  4       pVf   . # VR,          ;'       g    RV P
                  8X  d>   \        VR,          4      '       d&   W0P
                  8w  d   V'       d   \        R4      h. # V'       g+   VR,          e    V P                  VR,          4      V8¼  d   . # V'       d,   V P                  WV4      p	V	'       d   VP                  V	4       TP                  RY3'       d   TMRW(R,          VR,          34      P                  p
V
'       d   . VOVN# T# )	zKReuse title rules within an authority-owned transaction; never commit here.Nz=SELECT title, title_source, hidden FROM sessions WHERE id = ?r:   r=   Úhiddenu›   This is the bot's canonical Bot Chat â€” its name is its identity, and renaming it would orphan the conversation. To start fresh, create a new bot instead.Útitle_sourcezaUPDATE sessions SET title = ?, title_source = ? WHERE id = ? AND title IS ? AND title_source IS ?)rI   r3   r7   r    rR   ÚCANONICAL_BOT_CHAT_TITLErN   rG   Ú_resolve_title_conflictÚappendÚrowcount)rS   r%   rY   r:   r.   Úis_userÚnew_rankÚaffectedÚcurrentÚclearedÚchangeds   &&&&$      r   r]   Ú4SessionTitlesMixin._set_session_title_in_transaction^   s]  € à×#Ñ# EÓ*ˆØ×2Ñ2Ñ2ˆß3:�4×#Ñ# FÔ+ÀˆØˆà—,‘,ØKÈjÈ]ó
ç
‰(‹*ð 	ð Š?ØˆIð
 �WÕ×#Ð# ¨×(EÑ(EÔEÌ$ÈwÐW_ÕO`×JaÒJaØ×:Ñ:Ô:ßÜ ð "Mó Nð Nð ˆIß˜7 7Õ+Ò7¸D×<LÑ<LÈWÐUcÕMdÓ<eÐiqÔ<qØˆIßØ×2Ñ2°4ÀUÓKˆGßØ—‘ Ô(ð —,‘,ØoØže‘F¨¨zÀ7Õ;KÈWÐUcÕMdÐeó
÷ ‰(ð 	÷ +2Ð&�Ð&˜:Ð&Ð?°xÐ?r   c               ó$   € V ^8„  d   QhRRRRRR/# )r   rY   r   r:   r   r/   r   )r   s   "r   r   r0   ƒ   s&   € ÷  Xñ  X¸ð  XÀCð  XÈMñ  Xr   c                óê  € VP                  RV P                   R2W234      P                  4       pV'       g   R# VR,          pV P                  WVR7      '       d   VP                  RV34       V# W0P                  8X  d1   \        VR,          4      '       d   \        VR,          4      '       g   VR	,          '       d%   VR,          '       g   VP                  R
V34       V# \        RV RV 24      h)aF  Free ``title`` for ``session_id`` inside the caller's write transaction, or raise
ValueError when another session legitimately holds it. The one place the uniqueness
rule lives, shared by renames and the API server's create-with-title. Returns the id
whose title was cleared (an affected row for mutation receipts), else None.zSELECT id, archived, hidden, z3 AS ghost FROM sessions WHERE title = ? AND id != ?NÚid)rL   rM   z-UPDATE sessions SET title = NULL WHERE id = ?Úarchivedrf   ÚghostzBUPDATE sessions SET title = NULL, title_source = NULL WHERE id = ?zTitle 'z' is already in use by session )r    Ú_EMPTY_SESSION_WHERErR   rU   rh   rN   rG   )rS   r%   rY   r:   ÚconflictÚconflict_ids   &&&&  r   ri   Ú*SessionTitlesMixin._resolve_title_conflictƒ   sõ   € ð
 —<‘<Ø+¨D×,EÑ,EÐ+Fð G8ð 8Ø:?Ð9Ló
÷ ‰(‹*ð 	÷ ÙØ˜t•nˆð ×(Ñ(¨ÐV`Ð(×aÒaØ�L‰LÐHÈ;È.ÔYØÐð ×2Ñ2Ô2´t¸HÀZÕ<P×7QÒ7QÜ�x Õ)×*Ò*°¸×0AÔ0AÈ(ÐS[×J\ÔJ\Ø�L‰LØTØ�ôð ÐÜ˜7 5 'Ð)HÈÈÐVÓWÐWr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   rY   r   r:   r   rN   r   )r   s   "r   r   r0   ¥   s&   € ÷ Yñ Y¨Cð Y¸ð YÀñ Yr   c                ó<   € V P                  WV P                  R7      # )z}Set a title on the user's behalf (``user`` provenance). Empty clears it. Raises
ValueError on conflict or validation failure.r\   )rb   r3   )rS   rY   r:   s   &&&r   Úset_session_titleÚ$SessionTitlesMixin.set_session_title¥   s    € ð ×&Ñ& zÀ×AWÑAWÐ&ÓXÐXr   c               ó(   € V ^8„  d   QhRRRRRRRR/# rX   r   )r   s   "r   r   r0   ª   s.   € ÷ Iñ I¨ð I°Sð IÀSð IÈTñ Ir   c               ó~   € W0P                   V P                  39  d   \        RV: 24      hV P                  WVR7      # )zVSet an automatic title; False (untouched) when a higher-authority title holds the row.z invalid automatic title source: r\   )ÚTITLE_SOURCE_DERIVEDÚTITLE_SOURCE_LLMrG   rb   ra   s   &&&$r   Úset_auto_titleÚ!SessionTitlesMixin.set_auto_titleª   sB   € à×3Ñ3°T×5JÑ5JÐKÔKÜÐ?À¹zÐJÓKÐKØ×&Ñ& zÀÐ&ÓHÐHr   c               ó    € V ^8„  d   QhRRRR/# ©r   rY   r   r   r/   r   )r   s   "r   r   r0   °   s   € ÷ -ñ -¨Cð -°Mñ -r   c                óN   € V P                  RV34      pV'       d
   VR,          # R# )z%Get the title for a session, or None.z'SELECT title FROM sessions WHERE id = ?r:   N©Ú	_read_one©rS   rY   r   s   && r   Úget_session_titleÚ$SessionTitlesMixin.get_session_title°   s&   € à�n‰nÐFÈÈÓVˆß"ˆs�7�|Ð,¨Ð,r   c               ó    € V ^8„  d   QhRRRR/# r‡   r   )r   s   "r   r   r0   µ   s   € ÷ Qñ Q°3ð Q¸=ñ Qr   c                ód   € V P                  RV34      pV'       d   VR,          e
   VR,          # R# )z?Get the provenance of a session's title, or None when untitled.z5SELECT title, title_source FROM sessions WHERE id = ?r:   Nrg   r‰   r‹   s   && r   Úget_session_title_sourceÚ+SessionTitlesMixin.get_session_title_sourceµ   s2   € à�n‰nÐTÐWaÐVcÓdˆß&)¨c°'­lÒ.Fˆs�>Õ"ÐPÈDÐPr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   rY   r   r.   r   rN   r   )r   s   "r   r   r0   º   s!   € ÷ ñ °3ð Àð Èñ r   c                ól   € W P                   9  d   \        RV: 24      hV P                  RW!34      ^ 8„  # )z‡Overwrite a title's provenance without touching the text (a title copied across a
compression rotation keeps the original's authority).zinvalid title source: zGUPDATE sessions SET title_source = ? WHERE id = ? AND title IS NOT NULL)r2   rG   Ú_write_rowcount)rS   rY   r.   s   &&&r   Úset_session_title_sourceÚ+SessionTitlesMixin.set_session_title_sourceº   sG   € ð ×0Ñ0Ô0ÜÐ5°f±ZÐ@ÓAÐAØ×#Ñ#ØUÐX^ÐWkó
àñð 	r   c               ó    € V ^8„  d   QhRRRR/# )r   r:   r   r   zOptional[dict[str, Any]]r   )r   s   "r   r   r0   Ã   s   € ÷ <ñ <¨#ð <Ð2Jñ <r   c                ó^   € V P                  RV34      pV'       d   V P                  V4      # R# )z?Look up a session by exact title. Returns session dict or None.z«SELECT s.*, COALESCE(sp.prompt, s.system_prompt) AS _system_prompt_resolved FROM sessions s LEFT JOIN system_prompts sp ON sp.hash = s.system_prompt_hash WHERE s.title = ?N)rŠ   Ú_session_row_dict)rS   r:   r   s   && r   Úget_session_by_titleÚ'SessionTitlesMixin.get_session_by_titleÃ   s4   € à�n‰nð à"' ó+ˆ÷ /2ˆt×%Ñ% cÓ*Ð;°tÐ;r   c               ó    € V ^8„  d   QhRRRR/# )r   r:   r   r   r/   r   )r   s   "r   r   r0   Ë   s   € ÷ Sñ S¨cð S°mñ Sr   c                óò   € V P                  V4      pVe   WP                  8X  d
   VR,          # V P                  R\        V4       R234      pV'       d   V^ ,          R,          # V'       d
   VR,          # R# )zOResolve a title to a session ID, preferring the latest "title #N" continuation.Nru   zaSELECT id, title, started_at FROM sessions WHERE title LIKE ? ESCAPE '\' ORDER BY started_at DESCr   )rš   rh   Ú	_read_allr!   )rS   r:   ÚexactÚnumbereds   &&  r   Úresolve_session_by_titleÚ+SessionTitlesMixin.resolve_session_by_titleË   sy   € à×)Ñ)¨%Ó0ˆð Ò ×*GÑ*GÔ!GØ˜•;Ðà—>‘>ðFä˜UÓ#Ð$ CÐ(Ð*ó,ˆ÷ %-ˆx˜�{˜4Õ ÐRÇ%°5¸µ;ÐRÈTÐRr   c               ó    € V ^8„  d   QhRRRR/# r   r   )r   s   "r   r   r0   Þ   s   € ÷ _ñ _°Cð _¸Cñ _r   c                ó.   a€ V P                  V3R l4      # )z:Next title in a lineage ("my session" -> "my session #2").c                ó   <€ \        V S4      # )N©r*   ©r%   r   s   &€r   r^   Ú>SessionTitlesMixin.get_next_title_in_lineage.<locals>.<lambda>à   s   ø€ Ô6KÈDÐR\Ô6]r   )Ú_read_retrying_ioerr)rS   r   s   &fr   Úget_next_title_in_lineageÚ,SessionTitlesMixin.get_next_title_in_lineageÞ   s   ø€ à×(Ñ(Ô)]Ó^Ð^r   r   N)Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__Úclassmethodr7   ÚstaticmethodrI   rU   rb   r]   ri   r~   r„   rŒ   r�   r•   rš   r¡   rª   Ú__static_attributes__r   r   r   r,   r,   &   sy   † ÙFàô[ó ð[ð ôó ðõUõ(	õ#@õJ XõDYõ
Iõ-õ
Qõ
õ<õS÷&_ñ _r   r,   c               ó    € V ^8„  d   QhRRRR/# r   r   )r   s   "r   r   r   ã   s   € ÷ 3ñ 3¨Cð 3°Cñ 3r   c                ó   € \        W4      # )z”``get_next_title_in_lineage`` inside an authority-owned transaction (same connection, so a
sibling branch committed in this transaction is counted).r¦   r§   s   &&r   Úlineage_title_on_connr¶   ã   s   € ô ! Ó2Ð2r   )r°   Ú
__future__r   ÚloggingrC   Útypingr   r   r   Úagent.message_sanitizationr   Úhermes_state_commonr   r   r!   Ú	getLoggerÚloggerÚcompilerB   r@   r   r*   r,   r¶   r   r   r   Ú<module>r¿      s~   ðñõ #ã Û 	ß &Ñ &å ;ß Sð 
×	Ò	˜>Ó	*€ð —J’JÐBÓCÐ Ø—j’jÐ!fÓgÐ Ø—Z’ZÐ 1Ó2Ð õ/÷z_ñ z_÷z3r   