+
    jHàjÅX  ã                  óÜ   € R t ^ RIHt ^ RIt^ RIHtHtHtHtH	t	H
t
Ht ^ RIHtHtHt ]P"                  ! ]4      tR R ltR R ltR	 R
 lt]! RR04      t ! R R]4      t ! R R4      tR# )u§  Crossed-profile durable state in ONE ``state.db`` â€” finders and fixers for
``hermes sessions repair-profiles`` (#88715 PR-6).

Every ``state.db`` belongs to exactly one profile (``<root>/state.db`` â†’ ``default``,
``<root>/profiles/<name>/state.db`` â†’ ``name``) and every gateway session key encodes the profile
that owns the conversation (``agent:main:â€¦`` for the default, ``agent:<name>:â€¦`` for a named one).
The per-profile store model (#88734) is forward-only: it routes NEW writes to the right store but
never touched rows that had already landed in the wrong one, and the identity fences
(``_INHERIT_PARENT_META_SQL``, ``_recovered_row_allowed_for_active_profile``) only refuse to
*widen* damage that already exists. This module is the backward-looking half: it names each crossed
row and can settle it.

Store-level only. Which profiles exist, which store owns which home, whether a live gateway holds the
routing index in memory, and the JSON files outside ``state.db`` are the orchestrator's concern
(``hermes_cli/sessions_repair_profiles.py``).

Moving a session between two stores is two single-store transactions â€” copy into the target, then
delete from the source â€” because a crash between them leaves a *duplicate*, which the next run
settles idempotently (:meth:`import_moved_session` reports ``present``; the delete only proceeds when
the target holds at least as many messages). The reverse order would lose the row.
)ÚannotationsN)ÚAnyÚDictÚIterableÚListÚOptionalÚSetÚTuple)Ú
_id_chunksÚ_placeholdersÚemit_session_deletedc               ó    € V ^8„  d   QhRRRR/# )é   Úsession_keyr   ÚreturnúOptional[str]© )Úformats   "Ú@/home/ubuntu/.hermes/hermes-agent/hermes_state_profile_repair.pyÚ__annotate__r   $   s   € ÷ 	8ñ 	8 Sð 	8¨]ñ 	8ó    c                óÜ   € \        V \        4      '       g   R# V P                  R4      p\        V4      ^8  g   V^ ,          R8w  g   V^,          '       g   R# ^ RIHp V! V^,          4      # )u¹   Profile encoded in an ``agent:<ns>:â€¦`` gateway key (``default`` for ``main``), or None for a
keyless row (CLI/subagent lineage) or a key another producer minted (hosted rooms, tests).NÚ:Úagent)Ú"profile_from_session_key_namespace)Ú
isinstanceÚstrÚsplitÚlenÚhermes_state_keysr   )r   Úpartsr   s   &  r   Úsession_key_profiler!   $   sW   € ô �k¤3×'Ò'ÙØ×Ñ˜cÓ"€EÜ
ˆ5ƒz�A„~˜˜q� WÔ,°E¸!·H´HÙÝDÙ-¨e°A­hÓ7Ð7r   c               ó    € V ^8„  d   QhRRRR/# )r   Úvaluer   r   r   r   )r   s   "r   r   r   0   s   € ÷ ñ ˜3ð  =ñ r   c                ó`   € \        T ;'       g    R4      P                  4       pT;'       g    R# )zY``sessions.profile_name`` as a comparable label; NULL/blank is "unowned", not a crossing.Ú N)r   Ústrip)r#   Únames   & r   Ú_stored_profiler(   0   s'   € äˆu�{ˆ{˜Ó×!Ñ!Ó#€DØ�<ˆ<�4Ðr   c               ó    € V ^8„  d   QhRRRR/# )r   Útabler   r   z	list[str]r   )r   s   "r   r   r   6   s   € ÷ Mñ M ð M¨	ñ Mr   c                ób   € V P                  R V R24       Uu. uF  q"^,          NK  	  up# u upi )zPRAGMA table_info('z'))Úexecute)Úconnr*   Úrows   && r   Ú_table_columnsr/   6   s3   € Ø"Ÿl™lÐ-@ÀÀÀrÐ+JÔKÓLÑK�s��FˆFÑKÑLÐLùÒLs   ˜,ÚidÚdisplay_orderc                  ó2   a € ] tR t^AtRtR V 3R lltRtV ;t# )ÚSessionLedgeredErrorzOA session with gateway admission/worker history cannot be moved between stores.c               ó    € V ^8„  d   QhRRRR/# )r   Ú
session_idr   r   ÚNoner   )r   s   "r   r   Ú!SessionLedgeredError.__annotate__D   s   € ÷ Zñ Z 3ð Z¨4ñ Zr   c                	ó,   <€ \         SV `  V R 24       R# )z0 has gateway admission/worker history; not movedN)ÚsuperÚ__init__)Úselfr5   Ú	__class__s   &&€r   r:   ÚSessionLedgeredError.__init__D   s   ø€ Ü‰Ñ˜J˜<Ð'WÐXÖYr   r   )Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__r:   Ú__static_attributes__Ú__classcell__)r<   s   @r   r3   r3   A   s   ø† ÙY÷Zö Zr   r3   c                  óä   € ] tR t^HtR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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"tR## )$ÚSessionProfileRepairMixinz^Per-store crossed-profile identity: find, relabel, sever, move, and settle routing/topic rows.c               ó    € V ^8„  d   QhRRRR/# )r   Úownerr   r   zdict[str, list[dict[str, Any]]]r   )r   s   "r   r   Ú&SessionProfileRepairMixin.__annotate__M   s   € ÷ $ñ $°3ð $Ð;Zñ $r   c                ó”  € V P                  R4      pV P                  4       pR. R. R. /pV EF  p\        VR,          4      pVf   K  \        VR,          4      pVe5   Wv8w  d/   VR,          P	                  RVR,          RVR,          RVRV/4       Wa8w  d^   VR,          P	                  RVR,          RVR,          RTR	\        VR	,          ;'       g    ^ 4      R
VR
,          RVR,          V9   /4       \        VR,          4      pVf   Ká  W†8w  g   Ké  VR,          P	                  RVR,          RVR
VR
,          RV/4       EK  	  V# )uV  Keyed session rows whose identity disagrees with itself or with this store.

``mislabelled``: ``profile_name`` names a profile other than the one in the row's own key
(the key is what routing, the agent cache and ``_db_for_key`` consult; the label is stamped
after the fact). ``foreign``: the key's profile is not *owner* â€” the row sits in a store that
the routed profile never reads. ``crossed_parents``: child and parent are both keyed and name
different profiles, so the NULL-fill inheritance fence would have been the only thing standing
between them. A row can appear in more than one list.
zþSELECT s.id, s.session_key, s.profile_name, s.parent_session_id, s.message_count,        p.session_key AS parent_session_key FROM sessions s LEFT JOIN sessions p ON p.id = s.parent_session_id WHERE s.session_key LIKE 'agent:%' ORDER BY s.started_at, s.idÚmislabelledÚforeignÚcrossed_parentsr   Úprofile_namer0   Úkey_profileÚmessage_countÚparent_session_idÚledgeredÚparent_session_keyÚparent_profile)Ú	_read_allÚ_ledgered_session_idsr!   r(   ÚappendÚint)	r;   rH   ÚrowsrR   Úfoundr.   rO   ÚlabelrT   s	   &&       r   Úfind_crossed_profile_sessionsÚ7SessionProfileRepairMixin.find_crossed_profile_sessionsM   sv  € ð �~‰~ðMóNˆð
 ×-Ñ-Ó/ˆØ2?ÀÀYÐPRÐTeÐgiÐ1jˆÜˆCÜ-¨c°-Õ.@ÓAˆKØÒ"ÙÜ# C¨Õ$7Ó8ˆEØÒ  UÔ%9Ø�mÕ$×+Ñ+Ø˜#˜d�) ]°C¸Õ4FØ" E¨=¸+ð-Gô Hð Ô#Ø�iÕ ×'Ñ'Ø˜#˜d�) ]°C¸Õ4FÈÐWbØ#¤S¨¨_Õ)=×)BÐ)BÀÓ%CØ'¨Ð-@Õ)AÀ:ÈsÐSWÍyÐ\dÑOdð)fô gô 1°Ð5IÕ1JÓKˆNØÔ)¨nÖ.KØÐ'Õ(×/Ñ/Ø˜#˜d�) ]°KØ'¨Ð-@Õ)AÐCSÐUcð1e÷ fñ! ð& ˆr   c               ó   € V ^8„  d   QhRR/# )r   r   úset[str]r   )r   s   "r   r   rI   s   s   € ÷ 0ñ 0 xñ 0r   c                ó*   € R pV P                  V4      # )a"  Sessions the gateway's durable ledger references (``session_admissions`` /
``worker_executions``, both ``ON DELETE RESTRICT``). A move copies only the transcript, so
such a row could never leave the source: report it instead of copying it. A store whose
schema predates the ledger has none.c                ó(  € V P                  R 4       Uu0 uF  q^ ,          kK  	  pp\        4       pRV9   d(   VP                  R V P                  R4       4       4       RV9   d(   VP                  R V P                  R4       4       4       V# u upi )ziSELECT name FROM sqlite_master WHERE type='table' AND name IN ('session_admissions', 'worker_executions')Úsession_admissionsc              3  ó2   "  € T F  q^ ,          x € K  	  R# 5i©é    Nr   ©Ú.0Úrs   & r   Ú	<genexpr>ÚQSessionProfileRepairMixin._ledgered_session_ids.<locals>._read.<locals>.<genexpr>~   s   é € ÐsÑ)r A˜QŸ4š4Ó)rùó   ‚z9SELECT DISTINCT target_session_id FROM session_admissionsÚworker_executionsc              3  ó2   "  € T F  q^ ,          x € K  	  R# 5ird   r   rf   s   & r   ri   rj   €   s   é € ÐkÑ)j A˜QŸ4š4Ó)jùrk   z1SELECT DISTINCT session_id FROM worker_executions)r,   ÚsetÚupdate)r-   r.   ÚtablesÚidss   &   r   Ú_readÚ>SessionProfileRepairMixin._ledgered_session_ids.<locals>._readx   s”   € Ø(,¯©ðJô)Kó Lñ )K ˜!—f�fñ )KˆFð Lô  ›EˆCØ# vÔ-Ø—
‘
Ñs¨¯©Ð6qÔ)rÓsÔsØ" fÔ,Ø—
‘
Ñk¨¯©Ð6iÔ)jÓkÔkØˆJùòLs   ”B)Ú_read_retrying_ioerr)r;   rr   s   & r   rV   Ú/SessionProfileRepairMixin._ledgered_session_idss   s   € ò
		ð ×(Ñ(¨Ó/Ð/r   c               ó   € V ^8„  d   QhRR/# ©r   r   zlist[dict[str, Any]]r   )r   s   "r   r   rI   „   s   € ÷ ,ñ ,Ð+?ñ ,r   c                	ó`   € V P                  R 4       Uu. uF  p\        V4      NK  	  up# u upi )zbSELECT scope, session_key, entry_json, updated_at FROM gateway_routing ORDER BY scope, session_key)rU   Údict)r;   r.   s   & r   Úlist_gateway_routing_rowsÚ3SessionProfileRepairMixin.list_gateway_routing_rows„   s:   € Ø%)§^¡^ð*ô&+ó ,ñ &+˜c”�S–	ñ &+ñ ,ð 	,ùò ,s   ”+c               ó   € V ^8„  d   QhRR/# rw   r   )r   s   "r   r   rI   ‰   s   € ÷ ñ Ð7Kñ r   c                ó¤   € R p. pV P                  V4       F5  p\        VR,          4      pVR9  g   K  VP                  / VCRV/C4       K7  	  V# )a	  ``telegram_dm_topic_bindings`` rows labelled ``default`` whose ``session_key`` names a named
profile: written by a multiplexer that had not yet learned to stamp the routed profile
(#76423). Stores whose topic tables predate ``profile_name`` have nothing to relabel.c                óì   € V P                  R 4       Uu0 uF  q^ ,          kK  	  ppV'       d   R\        V R4      9  d   . # V P                  R4       Uu. uF  p\        V4      NK  	  up# u upi u upi )zYSELECT name FROM sqlite_master WHERE type='table' AND name = 'telegram_dm_topic_bindings'rN   Útelegram_dm_topic_bindingsz�SELECT chat_id, thread_id, session_key FROM telegram_dm_topic_bindings WHERE profile_name = 'default' ORDER BY chat_id, thread_id)r,   r/   ry   )r-   r.   Úexistings   &  r   rr   ÚNSessionProfileRepairMixin.find_profile_less_telegram_topic_rows.<locals>._read�   s’   € Ø*.¯,©,Økô+mó nñ +m 3˜AŸ˜ñ +mˆHð nç˜~´^ÀDÐJfÓ5gÔgØ�	Ø)-¯©ðMô*Nó Oñ *N #”D˜–Iñ *Nñ Oð Oùò	nùòOs   ”A,ÁA1r   rO   ©NÚdefault)rt   r!   rW   )r;   rr   Úoutr.   rO   s   &    r   Ú%find_profile_less_telegram_topic_rowsÚ?SessionProfileRepairMixin.find_profile_less_telegram_topic_rows‰   s\   € ò	Oð ˆØ×,Ñ,¨UÖ3ˆCÜ-¨c°-Õ.@ÓAˆKØÐ"3Ö3Ø—
‘
Ð>˜cÐ> =°+Ñ>Ö?ñ 4ð ˆ
r   c               ó    € V ^8„  d   QhRRRR/# )r   r5   r   r   rX   r   )r   s   "r   r   rI   œ   s   € ÷ )ñ )¨Sð )°Sñ )r   c                	ó`   € V P                  R V34      pV'       d   \        V^ ,          4      # ^ # )z2SELECT COUNT(*) FROM messages WHERE session_id = ?)Ú	_read_onerX   )r;   r5   r.   s   && r   Úcount_messages_allÚ,SessionProfileRepairMixin.count_messages_allœ   s,   € Ø�n‰nÐQÐT^ÐS`Óaˆß!Œs�3�q•6‹{Ð( qÐ(r   c               ó    € V ^8„  d   QhRRRR/# ©r   rq   úIterable[str]r   rX   r   )r   s   "r   r   rI   ¢   s   € ÷ (ñ (°=ð (ÀSñ (r   c                ób   a€ \        V4      oS'       g   ^ # R V3R llpV P                  V4      # )z†Set ``profile_name`` to the profile in each row's own key. Re-derives the target inside the
transaction (never trusts a stale report).c               ó   € V ^8„  d   QhRR/# ©r   r   rX   r   )r   s   "r   r   ÚOSessionProfileRepairMixin.relabel_sessions_to_key_profile.<locals>.__annotate__©   s   € ÷ 	ñ 	˜ñ 	r   c           	     ó
  <€ ^ p\        S4       Fp  pV P                  R\        V4       R2V4      P                  4       pV F:  w  rE\	        V4      pVf   K  WP                  RWdV34      P
                  ,          pK<  	  Kr  	  V# )re   z2SELECT id, session_key FROM sessions WHERE id IN (Ú)zKUPDATE sessions SET profile_name = ? WHERE id = ? AND profile_name IS NOT ?©r
   r,   Ú_session_ids_placeholdersÚfetchallr!   Úrowcount)r-   ÚchangedÚchunkrY   r5   r   ÚtargetÚwanteds   &      €r   Ú_doÚFSessionProfileRepairMixin.relabel_sessions_to_key_profile.<locals>._do©   s’   ø€ ØˆGÜ# FÖ+�Ø—|‘|ØHÔIbÐchÓIiÐHjÐjkÐlØóç#™8›:ð ó 04Ñ+�JÜ0°Ó=�FØ’~Ù ØŸ|™|ØeØ¨VÐ4ó 6ç6>±hõ?’Gó	 04ñ	 ,ð ˆNr   ©ÚlistÚ_execute_write©r;   rq   r�   rœ   s   && @r   Úrelabel_sessions_to_key_profileÚ9SessionProfileRepairMixin.relabel_sessions_to_key_profile¢   s1   ø€ ô �c“ˆßÙ÷	ð 	ð ×"Ñ" 3Ó'Ð'r   c               ó    € V ^8„  d   QhRRRR/# r�   r   )r   s   "r   r   rI   ¹   s   € ÷ (ñ (¨ð (¸3ñ (r   c                ób   a€ \        V4      oS'       g   ^ # R V3R llpV P                  V4      # )uß   Detach children from a parent keyed under another profile. Only ``parent_session_id`` is
cleared â€” ``profile_name`` stays the row's own (relabelled separately when it is wrong) â€” and
only while the crossing still holds.c               ó   € V ^8„  d   QhRR/# r‘   r   )r   s   "r   r   ÚESessionProfileRepairMixin.sever_crossed_parents.<locals>.__annotate__Á   s   € ÷ 	ñ 	˜ñ 	r   c                ó:  <€ ^ p\        S	4       Fˆ  pV P                  R\        V4       R2V4      P                  4       pV FR  w  rEp\	        V4      \	        V4      r‡Vf   K!  Vf   K'  Wx8w  g   K/  WP                  RV34      P
                  ,          pKT  	  KŠ  	  V# )re   zwSELECT s.id, s.session_key, p.session_key FROM sessions s JOIN sessions p ON p.id = s.parent_session_id WHERE s.id IN (r”   z9UPDATE sessions SET parent_session_id = NULL WHERE id = ?r•   )
r-   Úseveredrš   rY   r5   ÚkeyÚ
parent_keyÚmineÚtheirsrœ   s
   &        €r   r�   Ú<SessionProfileRepairMixin.sever_crossed_parents.<locals>._doÁ   s¯   ø€ ØˆGÜ# FÖ+�Ø—|‘|ð&ä&?ÀÓ&FÐ%GÀqðJàKPóR÷ S[ÑRZÓR\ð ó 48Ñ/�J ZÜ#6°sÓ#;Ô=PÐQ[Ó=\˜&ØÔ'¨FÔ,>À4Æ>Ø§<¡<ØWÐZdÐYfó$hßhpÑhpõqšó 48ñ ,ð ˆNr   rŸ   r¢   s   && @r   Úsever_crossed_parentsÚ/SessionProfileRepairMixin.sever_crossed_parents¹   s1   ø€ ô �c“ˆßÙ÷	ð 	ð ×"Ñ" 3Ó'Ð'r   c               ó    € V ^8„  d   QhRRRR/# )r   r5   r   r   zOptional[dict[str, Any]]r   )r   s   "r   r   rI   Ò   s   € ÷ ñ °#ð Ð:Rñ r   c                ó~   a€ V3R lpV P                  V4      pVe!   SV P                  4       9   d   \        S4      hV# )uæ   Everything the target store needs to hold *session_id* as its own: the row (every column),
the resolved system prompt, EVERY message row (inactive and compacted generations included â€”
a move is not an export) and its usage rows.c           
     óÊ  <a € S P                  R S34      P                  4       pVf   R# V 3R lpVR,          '       d   V! VR,          4      MRpVR,          '       d   V! VR,          4      MRpS P                  RS34       Uu. uF  p\        V4      NK  	  ppS P                  RS34       Uu. uF  p\        V4      NK  	  ppR\        V4      RVR	VR
VRV/# u upi u upi )z#SELECT * FROM sessions WHERE id = ?Nc                ól   <€ SP                  R V 34      P                  4       pV'       d
   V^ ,          # R# )z0SELECT prompt FROM system_prompts WHERE hash = ?N)r,   Úfetchone)Úprompt_hashr.   r-   s   & €r   Ú_storedÚQSessionProfileRepairMixin.export_session_for_move.<locals>._read.<locals>._storedÚ   s2   ø€ Ø—l‘lÐ#UÐXcÐWeÓf×oÑoÓq�ß!$�s˜1•vÐ.¨$Ð.r   Úsystem_prompt_hashÚ
tool_namesz7SELECT * FROM messages WHERE session_id = ? ORDER BY idz6SELECT * FROM session_model_usage WHERE session_id = ?ÚsessionÚsystem_promptÚtool_pinÚmessagesÚusage)r,   r¶   ry   )	r-   r¼   r¸   Úpromptr¾   rh   r¿   rÀ   r5   s	   f       €r   rr   Ú@SessionProfileRepairMixin.export_session_for_move.<locals>._readÖ   s  ù€ Ø—l‘lÐ#HÈ:È-ÓX×aÑaÓcˆGØŠÙõ/ð @GÐG[×?\Ô?\‘W˜WÐ%9Õ:Ô;ÐbfˆFà9@À×9NÔ9N‘w˜w |Õ4Ô5ÐTXˆHØ)-¯©ØIÈJÈ=ô*Zó [ñ *Z Aœ˜Qžñ *ZˆHð [à&*§l¡lØHÈ:È-ô'Yó Zñ 'Y ”T˜!–Wñ 'YˆEð Zàœt G›}¨o¸vÀzÐS[Ð]gÐiqØ˜Uð$ð $ùò	[ùòZs   ÂCÂ0C )rt   rV   r3   )r;   r5   rr   Úpayloads   &f  r   Úexport_session_for_moveÚ1SessionProfileRepairMixin.export_session_for_moveÒ   sB   ø€ õ	$ð  ×+Ñ+¨EÓ2ˆØÒ :°×1KÑ1KÓ1MÔ#Mô ' zÓ2Ð2Øˆr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   rÃ   údict[str, Any]rN   r   r   r   )r   s   "r   r   rI   í   s"   € ÷  (ñ  (¨Nð  (ÈSð  (ÐUXñ  (r   c               ó~   a aaaa€ \        SR,          4      oSR,          oR VVV VV3R llpS P                  V4      # )uh  Insert a moved session into THIS store as *profile_name*'s. ``present`` when the id already
exists (an earlier run copied but did not delete), else ``imported``. The parent link survives
only when the parent is already here â€” a moved row must never point across stores or at a
row of another profile. Columns the target schema lacks are dropped, never invented. Titles
are unique per store only, so a title an unrelated row here already holds gets the moved
row's id tail (the :meth:`import_foreign_history` convention); the resident row keeps its
name, since it is the one this profile's clients resolve by title.r¼   r0   c               ó   € V ^8„  d   QhRR/# )r   r   r   r   )r   s   "r   r   ÚDSessionProfileRepairMixin.import_moved_session.<locals>.__annotate__ø   s   € ÷ 	ñ 	˜ñ 	r   c                ó  <€ V P                  R S
34      P                  4       '       d   R# SS	R&   S	P                  R4      pV'       d)   V P                  R V34      P                  4       f   RS	R&   S	P                  R4      pVeZ   V P                  RV34      P                  4       '       d3   RS
RR  R2pVRSP                  \	        V4      ,
           V,           S	R&   SP                  V SP                  R	4      4      S	R
&   SP                  R4      f*   \	        S	P                  R4      ;'       g    R4      ^@8X  d%   SP                  V SP                  R4      4      S	R&   SP                  V RS	\        4       R7       SP                  R4      ;'       g    .  F"  pSP                  V R/ VCRS
/C\        R7       K$  	  SP                  R4      ;'       g    .  F&  pSP                  V R/ VCRS
/C\        4       R7       K(  	  R# )ú#SELECT 1 FROM sessions WHERE id = ?ÚpresentrN   rQ   NÚtitlez&SELECT 1 FROM sessions WHERE title = ?ú (r”   r½   rº   r¾   r»   r%   Úsessions)Úskipr¿   r5   rÀ   Úsession_model_usageÚimportediôÿÿÿ)	r,   r¶   ÚgetÚMAX_TITLE_LENGTHr   Ú_store_system_promptÚ_insert_rowÚ	frozensetÚ_MESSAGE_MOVE_SKIP)r-   Ú	parent_idrÎ   ÚsuffixÚmessagerÀ   rÃ   rN   r;   r¼   r5   s   &     €€€€€r   r�   Ú;SessionProfileRepairMixin.import_moved_session.<locals>._doø   sï  ø€ Ø�|‰|ÐAÀJÀ=ÓQ×ZÑZ×\Ò\Ù Ø&2ˆG�NÑ#ØŸ™Ð$7Ó8ˆIß˜TŸ\™\Ð*OÐR[ÐQ]Ó^×gÑgÓiÒqØ/3�Ð+Ñ,Ø—K‘K Ó(ˆEØÒ  T§\¡\Ð2ZÐ]bÐ\dÓ%e×%nÑ%n×%pÒ%pØ˜j¨¨Ð.Ð/¨qÐ1�Ø#(Ð)M¨$×*?Ñ*?Ä#ÀfÃ+Õ*MÐ#NÐQWÕ#W�˜Ñ Ø,0×,EÑ,EÀdÈGÏKÉKÐXgÓLhÓ,iˆGÐ(Ñ)Ø�{‰{˜:Ó&Ò2´c¸'¿+¹+ÀlÓ:S×:YÐ:YÐWYÓ6ZÐ^`Ô6`à(,×(AÑ(AÀ$ÈÏÉÐT^ÓH_Ó(`�˜Ñ%Ø×Ñ˜T :¨w¼Y»[ÐÔIØ"Ÿ;™; zÓ2×8Ð8°bÒ8�Ø× Ñ   zÐ3X°gÐ3X¸|ÈZÑ3XÔ_qÐ Örñ 9à Ÿ™ WÓ-×3Ð3°Ò3�Ø× Ñ  Ð'<Ð>aÀÐ>aÈÐV`Ñ>aÔhqÓhsÐ Ötñ 4ár   )ry   r¡   )r;   rÃ   rN   r�   r¼   r5   s   ffd @@r   Úimport_moved_sessionÚ.SessionProfileRepairMixin.import_moved_sessioní   s:   ü€ ô �w˜yÕ)Ó*ˆØ˜T•]ˆ
÷	ô 	ð* ×"Ñ" 3Ó'Ð'r   c               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   r*   r   ÚvaluesrÇ   rÑ   rØ   r   r6   r   )r   s   "r   r   rI     s)   € ÷ *ñ * ð *¨nð *Àyð *ÐUYñ *r   c               	ó  € \        W4       Uu. uF  qDV9   g   K  WC9  g   K  VNK  	  ppT P                  R V RRP                  V4       RRP                  R V 4       4       R2V Uu. uF  qBV,          NK  	  up4       R# u upi u upi )zINSERT INTO rÏ   z, z
) VALUES (c              3  ó&   "  € T F  pR x € K	  	  R# 5i)Ú?Nr   )rg   Ú_s   & r   ri   Ú8SessionProfileRepairMixin._insert_row.<locals>.<genexpr>  s   é € ÐKaÑY`ÐTUÍCÓY`ùs   ‚r”   N)r/   r,   Újoin)r-   r*   rá   rÑ   ÚcÚcolumnss   &&&$  r   r×   Ú%SessionProfileRepairMixin._insert_row  sŠ   € ä,¨TÔ9Ó[Ñ9˜À&¹[”1ÈQÉ]—1�1Ñ9ˆÐ[Ø�‰Ø˜5˜'  D§I¡I¨gÓ$6Ð#7°zÀ$Ç)Á)ÑKaÑY`ÓKaÓBaÐAbÐbcÐdÙ 'Ó(¡˜1�A�YˆY¡Ñ(ö	*ùò \ùò )s   ŽB›B£BÁ*B	
c               ó    € V ^8„  d   QhRRRR/# )r   r5   r   r   Úboolr   )r   s   "r   r   rI     s   € ÷ ñ ¨sð °tñ r   c                ó€   a a€ R V V3R llpS P                  V4      pV'       d   \        S S.RR7       \        V4      # )zÀRemove a session this store no longer owns after the target confirmed it. Children left
behind are detached (same FK rule as :meth:`delete_session`); topic bindings on the row
cascade with it.c               ó   € V ^8„  d   QhRR/# )r   r   rì   r   )r   s   "r   r   ÚDSessionProfileRepairMixin.delete_moved_session.<locals>.__annotate__  s   € ÷ 	ñ 	˜ñ 	r   c                ó  <€ V P                  R S34      P                  4       f   R# V P                  RS34       V P                  RS34       V P                  RS34       V P                  RS34       SP                  V 4       R# )rÌ   FzHUPDATE sessions SET parent_session_id = NULL WHERE parent_session_id = ?z)DELETE FROM messages WHERE session_id = ?z4DELETE FROM session_model_usage WHERE session_id = ?z!DELETE FROM sessions WHERE id = ?T)r,   r¶   Ú#_delete_unreferenced_system_prompts)r-   r;   r5   s   &€€r   r�   Ú;SessionProfileRepairMixin.delete_moved_session.<locals>._do  s|   ø€ Ø�|‰|ÐAÀJÀ=ÓQ×ZÑZÓ\ÒdÙØ�L‰LÐcÐfpÐerÔsØ�L‰LÐDÀzÀmÔTØ�L‰LÐOÐR\ÐQ^Ô_Ø�L‰LÐ<¸z¸mÔLØ×4Ñ4°TÔ:Ùr   Úprofile_repair)Úreason)r¡   r   rì   )r;   r5   r�   Údeleteds   ff  r   Údelete_moved_sessionÚ.SessionProfileRepairMixin.delete_moved_session  s;   ù€ ÷	ñ 	ð ×%Ñ% cÓ*ˆßÜ  ¨
 |Ð<LÕMÜ�G‹}Ðr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   rq   rŽ   Úprofiler   r   rX   r   )r   s   "r   r   rI   *  s!   € ÷ (ñ (¨mð (Àcð (Ècñ (r   c                ó”   aaa€ ^ RI Hp \        V4      oS'       g   ^ # V! S4      R,           oR VVV3R llpV P                  V4      # )u	  ``agent:main:â€¦`` â†’ ``agent:<profile>:â€¦`` (+ ``profile_name``) for rows a standalone
gateway wrote before this store's profile was multiplexed (#113884). Only rows still under
the legacy namespace change; a ``main``-named profile is written in its marked form.)Ú_session_key_namespacer   c               ó   € V ^8„  d   QhRR/# r‘   r   )r   s   "r   r   ÚJSessionProfileRepairMixin.rekey_legacy_main_sessions.<locals>.__annotate__4  s   € ÷ 	ñ 	˜ñ 	r   c                ó˜   <€ ^ p\        S4       F7  pWP                  R\        V4       R2SS.VO54      P                  ,          pK9  	  V# )re   z^UPDATE sessions SET session_key = ? || substr(session_key, 12), profile_name = ? WHERE id IN (z0) AND substr(session_key, 1, 11) = 'agent:main:')r
   r,   r–   r˜   )r-   r™   rš   Únew_nsrù   rœ   s   &  €€€r   r�   ÚASessionProfileRepairMixin.rekey_legacy_main_sessions.<locals>._do4  s^   ø€ ØˆGÜ# FÖ+�ØŸ<™<ð$Ü$=¸eÓ$DÐ#EÐEuðwà˜WÐ- uÑ-ó/÷ 08©xõ8’ñ ,ð
 ˆNr   )Úgateway.sessionrû   r    r¡   )r;   rq   rù   rû   r�   rÿ   rœ   s   &&f  @@r   Úrekey_legacy_main_sessionsÚ4SessionProfileRepairMixin.rekey_legacy_main_sessions*  sC   ú€ õ 	;Ü�c“ˆßÙÙ'¨Ó0°3Õ6ˆ÷	ò 	ð ×"Ñ" 3Ó'Ð'r   c               ó    € V ^8„  d   QhRRRR/# )r   rY   zIterable[tuple[str, str]]r   rX   r   )r   s   "r   r   rI   >  s   € ÷ 	(ñ 	(Ð0Ið 	(Ècñ 	(r   c                	ób   a€ \        V4      oS'       g   ^ # R V3R llpV P                  V4      # )re   c               ó   € V ^8„  d   QhRR/# r‘   r   )r   s   "r   r   ÚKSessionProfileRepairMixin.delete_gateway_routing_rows.<locals>.__annotate__C  s   € ÷ 	*ñ 	*˜ñ 	*r   c                ó0   <a € \        V 3R  lS 4       4      # )c              3  ób   <"  € T F$  w  rSP                  R W34      P                  x € K&  	  R# 5i)z?DELETE FROM gateway_routing WHERE scope = ? AND session_key = ?N©r,   r˜   )rg   Úscoper«   r-   s   &  €r   ri   ÚUSessionProfileRepairMixin.delete_gateway_routing_rows.<locals>._do.<locals>.<genexpr>D  s9   øé € ð *á"(‘J�Eð —|‘|ØQÐTYÐS_óaßaiÑaiôjã"(ùs   ƒ,/©Úsum©r-   rœ   s   f€r   r�   ÚBSessionProfileRepairMixin.delete_gateway_routing_rows.<locals>._doC  s   ù€ Üô *á"(ó*ó *ð *r   rŸ   ©r;   rY   r�   rœ   s   && @r   Údelete_gateway_routing_rowsÚ5SessionProfileRepairMixin.delete_gateway_routing_rows>  s/   ø€ Ü�d“ˆßÙ÷	*ð 	*ð ×"Ñ" 3Ó'Ð'r   c               ó    € V ^8„  d   QhRRRR/# )r   rY   z%Iterable[tuple[str, str, str, float]]r   rX   r   )r   s   "r   r   rI   I  s   € ÷ (ñ (Ð:_ð (Ðdgñ (r   c                ób   a€ \        V4      oS'       g   ^ # R V3R llpV P                  V4      # )u¯   Adopt ``(scope, session_key, entry_json, updated_at)`` rows another store held for this
one's routing index. An existing key wins â€” the row the gateway actually loads stays.c               ó   € V ^8„  d   QhRR/# r‘   r   )r   s   "r   r   ÚUSessionProfileRepairMixin.insert_gateway_routing_rows_if_absent.<locals>.__annotate__P  s   € ÷ 	Hñ 	H˜ñ 	Hr   c                ó0   <a € \        V 3R  lS 4       4      # )c              3  ó\   <"  € T F!  pSP                  R V4      P                  x € K#  	  R# 5i)zfINSERT OR IGNORE INTO gateway_routing (scope, session_key, entry_json, updated_at) VALUES (?, ?, ?, ?)Nr
  )rg   r.   r-   s   & €r   ri   Ú_SessionProfileRepairMixin.insert_gateway_routing_rows_if_absent.<locals>._do.<locals>.<genexpr>Q  s4   øé € ð Há@F¸ð —|‘|ð&Ø'*ó,ç,4©Hô5ã@Fùs   ƒ),r  r  s   f€r   r�   ÚLSessionProfileRepairMixin.insert_gateway_routing_rows_if_absent.<locals>._doP  s!   ù€ Üô Há@FóHó Hð Hr   rŸ   r  s   && @r   Ú%insert_gateway_routing_rows_if_absentÚ?SessionProfileRepairMixin.insert_gateway_routing_rows_if_absentI  s3   ø€ ô �d“ˆßÙ÷	Hð 	Hð ×"Ñ" 3Ó'Ð'r   c               ó    € V ^8„  d   QhRRRR/# )r   rY   zIterable[dict[str, Any]]r   údict[str, int]r   )r   s   "r   r   rI   X  s   € ÷ "(ñ "(Ð0Hð "(È^ñ "(r   c                óv   aa€ \        V4      oR^ R^ R^ /oS'       g   S# R VV3R llpV P                  V4      # )zãStamp the key's profile onto ``default``-labelled bindings (and the chat's mode row when
only the default one exists). A binding that would collide with one the named profile already
wrote is the stale duplicate and is removed.Úbindings_relabelledÚbindings_duplicates_removedÚmode_rows_relabelledc               ó   € V ^8„  d   QhRR/# )r   r   r  r   )r   s   "r   r   ÚKSessionProfileRepairMixin.relabel_telegram_topic_rows.<locals>.__annotate__a  s   € ÷ 	ñ 	˜ñ 	r   c           
     ój  <€ R \        V R4      9   pS EF  p\        VR,          4      pVR9   d   K  VR,          VR,          rTV P                  RW4V34      P                  4       pV'       d2   SR;;,          V P                  RWE34      P                  ,          uu&   M1SR;;,          V P                  R	W4V34      P                  ,          uu&   V'       g   KÆ  V P                  R
W434      P                  4       e   Kë  SR;;,          V P                  RW434      P                  ,          uu&   EK  	  S# )rN   Útelegram_dm_topic_moder   Úchat_idÚ	thread_idzaSELECT 1 FROM telegram_dm_topic_bindings WHERE profile_name = ? AND chat_id = ? AND thread_id = ?r"  zgDELETE FROM telegram_dm_topic_bindings WHERE profile_name = 'default' AND chat_id = ? AND thread_id = ?r!  zwUPDATE telegram_dm_topic_bindings SET profile_name = ? WHERE profile_name = 'default' AND chat_id = ? AND thread_id = ?zKSELECT 1 FROM telegram_dm_topic_mode WHERE profile_name = ? AND chat_id = ?r#  zaUPDATE telegram_dm_topic_mode SET profile_name = ? WHERE profile_name = 'default' AND chat_id = ?r‚   )r/   r!   r,   r¶   r˜   )	r-   Úmode_has_profiler.   r›   r(  r)  ÚcollidesÚcountsrœ   s	   &      €€r   r�   ÚBSessionProfileRepairMixin.relabel_telegram_topic_rows.<locals>._doa  s6  ø€ Ø-´ÀÐF^Ó1_Ñ_ÐÜ�Ü,¨S°Õ-?Ó@�ØÐ.Ô.ÙØ%(¨¥^°S¸Õ5E˜ØŸ<™<ð(Ø*0¸9Ð)EóGçGOÁxÃzð ÷ ØÐ8×9¸T¿\¹\ð<Ø>EÐ=Qó>SçS[ÑS[õ\Ô9ð Ð0×1°T·\±\ð<Ø>DÈyÐ=Yó6[ç[cÑ[cõdÓ1÷ $Ñ#¨¯©ØeØÐ)ó)+ç+3©8«:ô)>ð Ð1×2°d·l±lð*Ø,2Ð+<ó7>ç>F¹hõGÖ2ñ' ð, ˆMr   rŸ   )r;   rY   r�   r,  rœ   s   && @@r   Úrelabel_telegram_topic_rowsÚ5SessionProfileRepairMixin.relabel_telegram_topic_rowsX  sH   ù€ ô �d“ˆØ'¨Ð,IÈ1ÐNdÐfgÐhˆßØˆM÷	ñ 	ð2 ×"Ñ" 3Ó'Ð'r   c               ó$   € V ^8„  d   QhRRRRRR/# )r   Úplatformr   r(  r   r_   r   )r   s   "r   r   rI   ~  s&   € ÷ cñ c¨cð c¸Cð cÀHñ cr   c                ó‚   € V P                  RV\        V4      34      pR V 4        Uu0 uF
  qDf   K  VkK  	  up# u upi )u–   Profiles whose keyed sessions hold *(platform, chat_id)* in this store â€” the evidence for
who owns a profile-less ``gateway_voice_mode.json`` entry.ziSELECT DISTINCT session_key FROM sessions WHERE source = ? AND chat_id = ? AND session_key LIKE 'agent:%'c              3  óF   "  € T F  p\        VR ,          4      x € K  	  R# 5i)r   N)r!   rf   s   & r   ri   ÚBSessionProfileRepairMixin.key_profiles_for_chat.<locals>.<genexpr>„  s   é € ÐPÉ4ÀaÔ/°°-Õ0@×AÐAË4ùs   ‚!)rU   r   )r;   r1  r(  rY   Úps   &&&  r   Úkey_profiles_for_chatÚ/SessionProfileRepairMixin.key_profiles_for_chat~  sH   € ð �~‰~ð-Ø/7¼¸W»Ð.FóHˆñ QÉ4ÔPÓbÑP�a—�ÑPÑbÐbùÒbs   ©<³<r   N)r>   r?   r@   rA   rB   r\   rV   rz   r…   rŠ   r£   r°   rÄ   rÞ   Ústaticmethodr×   rö   r  r  r  r.  r6  rC   r   r   r   rF   rF   H   sn   † Ùhõ$õL0õ",õ
õ&)õ(õ.(õ2õ6 (ðD ô*ó ð*õõ((õ(	(õ(õ"(÷Lcñ cr   rF   )rB   Ú
__future__r   ÚloggingÚtypingr   r   r   r   r   r   r	   Úhermes_state_commonr
   r   r–   r   Ú	getLoggerr>   Úloggerr!   r(   r/   rØ   rÙ   ÚRuntimeErrorr3   rF   r   r   r   Ú<module>r@     su   ðñõ* #ã ß B× BÑ B÷ñ ð 
×	Ò	˜8Ó	$€õ	8õõMñ   oÐ6Ó7Ð ôZ˜<ô Z÷|có |cr   