Ë
    âß²j=…  ã                   óN  — d Z ddlZddlZddlZddlmZ ddlmZmZ ddlm	Z	m
Z
mZmZ ddlmZ ddlmZ ddlmZ dd	lmZ dd
lmZmZmZmZ  ej4                  d«      ZdZdZdZdZdZ dZ!dZ"dde#de#de#dee#   de#f
d„Z$dZ%d dee
e#e	f      dee#   de
e#e	f   fd„Z& G d„ d«      Z'y)!zñSession listing/rich rows, export, and import (portability) for SessionDB.

Plain mixin for ``hermes_state.SessionDB`` (no ``__init__``/state of its own).
Must never import hermes_state (cycle); shared constants live in hermes_state_common.
é    N)ÚCounter)ÚdatetimeÚtimezone)ÚAnyÚDictÚListÚOptional)ÚSKILL_SCAFFOLD_SQL_LIKE)Úsafe_json_loads)Úcoerce_epoch)Únew_session_id)Ú
SCHEMA_SQLÚ_PREVIEW_RAW_SUBQUERY_SQLÚ_shape_previewÚ_sql_session_last_activeÚhermes_state)ÚsourceÚuser_idÚmodelÚsystem_promptÚ
end_reasonÚcwdÚ
git_branchÚgit_repo_rootÚbilling_providerÚbilling_base_urlÚbilling_modeÚcost_statusÚcost_sourceÚpricing_versionÚtitle)Útool_call_idÚ	tool_nameÚeffect_dispositionÚfinish_reasonÚ	reasoningÚreasoning_contentÚplatform_message_idÚ
message_id)Úreasoning_detailsÚcodex_reasoning_itemsÚcodex_message_itemsa®  INSERT INTO sessions (
                           id, source, user_id, model, model_config, system_prompt,
                           system_prompt_hash,
                           parent_session_id, started_at, ended_at, end_reason,
                           message_count, tool_call_count, input_tokens, output_tokens,
                           cache_read_tokens, cache_write_tokens, reasoning_tokens,
                           cwd, git_branch, git_repo_root,
                           billing_provider, billing_base_url, billing_mode,
                           estimated_cost_usd, actual_cost_usd, cost_status, cost_source,
                           pricing_version, title, api_call_count, archived
                       )
                       VALUES (
                           :id, :source, :user_id, :model, :model_config,
                           NULL, :system_prompt_hash, NULL, :started_at, :ended_at,
                           :end_reason, 0, 0, :input_tokens, :output_tokens,
                           :cache_read_tokens, :cache_write_tokens,
                           :reasoning_tokens, :cwd, :git_branch, :git_repo_root,
                           :billing_provider, :billing_base_url, :billing_mode,
                           :estimated_cost_usd, :actual_cost_usd, :cost_status,
                           :cost_source, :pricing_version, :title,
                           :api_call_count, :archived
                       ))r   r   Úmodel_configr   r   r   r   r   r   r   r   r   r    r!   )Úinput_tokensÚoutput_tokensÚcache_read_tokensÚcache_write_tokensÚreasoning_tokensÚapi_call_count)Úended_atÚestimated_cost_usdÚactual_cost_usdÚselect_colsÚwhereÚtailÚprompt_selectÚreturnc                 ó\   — |€dnd}d| › |xs d› dt         › dt        d«      › d|› d|› |› d�S )	zà``list_sessions_rich``-shaped SELECT: resolved prompt (``prompt_select`` fragment;
    None omits prompt columns AND the join), preview, last_active. Whitespace matches
    the historical inline queries (SQL text is pinned).Ú z=LEFT JOIN system_prompts sp ON sp.hash = s.system_prompt_hashz
            SELECT ú,
                Úsz8 AS last_active
            FROM sessions s
            z
            WHERE z	
        )r   r   )r7   r8   r9   r:   Úprompt_joins        ú=/home/ubuntu/.hermes/hermes-agent/hermes_state_portability.pyÚ_rich_selectrB   B   sg   € ð &Ð-‘"Ð3r€KðØ�= Ò!4°"Ð 5ð 6Ü*Ð+ð ,Ü)¨#Ó.Ð/ð 0àˆMð Ø�'˜$˜ð  	ðð ó    z?COALESCE(sp.prompt, s.system_prompt) AS _system_prompt_resolvedÚmessagesÚ
session_idc                 ól  — | D �cg c]$  }t        |j                  d«      |¬«      x}�||f‘Œ& }}t        d„ | D «       «      }t        d„ d„ | D «       D «       «      }t	        ||dd «      D ���	�
cg c]q  \  \  }}\  }	}
|j                  d«      |	j                  d«      |j                  d	«      |	j                  d	«      t        d
t        t        |
|z
  dz  «      «      «      dœ‘Œs }}	}}}
d„ }|r|d
   d   |d   d   fnd\  }}dt        |«      d|rdndt        |«      t        | «      t        |«      z
  dœ|� ||«      nd|� ||«      nd|r$t        d
t        t        ||z
  dz  «      «      «      nd|rt        d„ |D «       «      ndt        |«      |j                  dd
«      ||dœS c c}w c c}
}	}}w )uR  Text-free timing evidence for a session export.

    Exports get attached to bug reports; a reader should not have to infer from raw
    timestamps whether a slow turn was one long model gap or many small tool
    intervals. Hermes persists no model/tool stopwatch samples, so message
    timestamps are the durable floor (``complete`` is therefore always False).
    Ids, roles, counts and durations only â€” never prompt text, arguments or results.
    Corrupt timestamp cells go through ``coerce_epoch`` like every other reader: they
    count as ``missing`` and never abort the export.
    Ú	timestamp)rE   Nc              3   óX   K  — | ]"  }t        |j                  d «      xs d«      –— Œ$ y­w)ÚroleÚunknownN)ÚstrÚget©Ú.0Úmsgs     rA   ú	<genexpr>z"_export_timings.<locals>.<genexpr>a   s"   è ø€ ÒPÀœ#˜cŸg™g f›oÒ:°×;ÑPùs   ‚(*c              3   ó\   K  — | ]$  }|sŒt        |t        «      rt        |«      nd –— Œ& y­w)é   N)Ú
isinstanceÚlistÚlen)rN   Útcs     rA   rP   z"_export_timings.<locals>.<genexpr>b   s,   è ø€ ò lØ35Òhj”:˜b¤$Ô'ŒˆBŒ¨QÓ.ñlùs   ‚,Š",c              3   ó>   K  — | ]  }|j                  d «      –— Œ y­w)Ú
tool_callsN)rL   rM   s     rA   rP   z"_export_timings.<locals>.<genexpr>c   s   è ø€ Ò9dÐTW¸#¿'¹'À,×:OÑ9dùó   ‚rR   ÚidrI   r   iè  )Úfrom_message_idÚto_message_idÚ	from_roleÚto_roleÚgap_msc                 óh   — t        j                  | t        j                  ¬«      j	                  «       S )N)Útz)r   Úfromtimestampr   ÚutcÚ	isoformat)Útss    rA   ú<lambda>z!_export_timings.<locals>.<lambda>i   s    € ”X×+Ñ+¨B´8·<±<Ô@×JÑJÓL€ rC   éÿÿÿÿ)NNÚmessage_timestampsFÚno_timestamped_messages)Ú	availableÚmissingc              3   ó&   K  — | ]	  }|d    –— Œ y­w)r_   N© )rN   Úis     rA   rP   z"_export_timings.<locals>.<genexpr>t   s   è ø€ Ò=¨a˜a �kÑ=ùs   ‚Útool)r   rj   ÚcompleteÚunavailable_reasonrh   Úfirst_message_atÚlast_message_atÚwall_clock_msÚlargest_gap_msÚrole_countsÚtool_result_countÚtool_calls_emittedÚ	intervals)r   rL   r   ÚsumÚzipÚmaxÚintÚroundÚboolrU   Údict)rD   rE   rO   re   Útimestampedrv   rx   ÚprevÚprev_tsÚnxtÚnxt_tsry   ÚisoÚfirst_tsÚlast_tss                  rA   Ú_export_timingsr‰   T   sæ  € ð )1ö e Ü)¨#¯'©'°+Ó*>È:ÔVÐV�rÐcð ˜’9ð e€Kð eäÑPÀxÔPÓP€KÜñ lÙ9dÐ[cÔ9dôló lÐô -0°¸[ÈÈ¸_Ó,M÷	Oñ Oñ )‰/ˆ4�™=˜C ð  Ÿ8™8 D›>¸C¿G¹GÀD»MØ—X‘X˜fÓ%°#·'±'¸&³/Ü�aœœU F¨WÑ$4¸Ñ#<Ó=Ó>Ó?óð O€Ió Oñ
 M€CÙCN˜ Q™¨Ñ*¨K¸©O¸AÑ,>Ñ?ÐT`Ñ€Hˆgà&Ü˜+Ó&ØÙ&1™dÐ7PÜ,/°Ó,<ÌÈXËÔY\Ð]hÓYiÑIiÑjØ-5Ð-A™C œMÀtØ+2Ð+>™3˜wœ<ÀDÙLWœ˜Q¤¤E¨7°XÑ+=ÀÑ*EÓ$FÓ GÔHÐ]aÙAJœ#Ñ=°9Ô=Ô=ÐPTÜ˜KÓ(Ø(Ÿ_™_¨V°QÓ7Ø0Øñð ùòeùõ
Os   …)F)Á/A6F.
c                   ón  — e Zd ZdZed„ «       Zd„ Zd„ Zede	fd„«       Z
edee	ef   fd„«       Zd5de	defd	„Zd6d
edeee	ef      fd„Zd7de	dededeee	ef      fd„Zd6de	dedeee	ef      fd„ZeZd6dedee	ee	ef   f   fd„Zd8dedeee	ef      fd„Zdee	ef   dee	ef   fd„Zde	deee	ef      fd„Zde	deee	ef      fd„Zd9de	deee	ef      fd„Zddœdede	dedee	ef   fd „Zded!e	defd"„Zed#ed$e	dee	   fd%„«       Z ed#ed$e	dee   fd&„«       Z!ed#ed$e	dee	   fd'„«       Z"ed#efd(„«       Z#d)ee	ef   de	d*edee	ef   fd+„Z$d,eee	ef      de%fd-„Z&d)ede	d.e'd/ee	ef   dee	ef   f
d0„Z(d)ee	ef   d*eee	ef      de	ddfd1„Z)ed2ee%   defd3„«       Z*d,eee	ef      dee	ef   fd4„Z+y):ÚSessionPortabilityMixinu<   See module docstring â€” mixin for SessionDB (Port cluster).c                 ób  — | j                  d|d   f«      j                  «       }|D ]†  }t        |d   i ¬«      xs i j                  di «      }|j                  d«      |d   k7  rŒ?|j                  d«      }|r||j                  d«      k(  s|rŒi|j                  d«      |d   k(  sŒ�|d   c S  y )	NzQSELECT id, origin_json FROM sessions WHERE source = ? AND origin_json IS NOT NULLro   Úorigin_json©ÚdefaultÚimported_fromÚforeign_session_idÚpathrZ   )ÚexecuteÚfetchallr   rL   )ÚconnÚoriginÚrowsÚrowÚimportedÚ
foreign_ids         rA   Ú_find_foreign_import_on_connz4SessionPortabilityMixin._find_foreign_import_on_conn   s¼   € à�|‰|ÐoØ# F™^Ð-ó/ß/7©x«zð 	àò 	!ˆCÜ'¨¨MÑ(:ÀBÔGÒMÈ2×RÑRÐSbÐdfÓgˆHØ�|‰|˜FÓ# v¨f¡~Ò5ØØŸ™Ð$8Ó9ˆJÙ 
¨h¯l©lÐ;OÓ.PÒ PÚ&¨8¯<©<¸Ó+?À6È&Á>Ó+QØ˜4‘yÒ ð	!ð rC   c                 ór   — | j                  «       5 }| j                  ||«      cd d d «       S # 1 sw Y   y xY w©N)Ú	_read_ctxr›   )Úselfr–   r•   s      rA   Úfind_foreign_importz+SessionPortabilityMixin.find_foreign_import�   s7   € Ø�^‰^Óð 	C Ø×4Ñ4°T¸6ÓB÷	C÷ 	Cò 	Cús   ‘-­6c                ó¾   ‡ ‡‡‡‡‡	— t        d¬«      Š	‰ j                  ‰	‰d   ‰||dœg«      \  Š}|rt        |d   d   «      ‚ˆˆˆˆ ˆ	ˆfd„}‰ j                  |«      S )a8  Adopt or mint a foreign snapshot in one transaction, including its provenance.

        BEGIN IMMEDIATE serializes duplicate clicks across connections/processes.
        Reuse the portability validator and message writer so counters and FTS
        obey the same contract as ordinary transcript imports.
        é   )Úhex_lenro   )rZ   r   r!   r   rD   r   Úerrorc                 ó6  •— ‰j                  | ‰«      }|r|ddœS ‰d   }| j                  d‰f«      j                  «       r‰› d‰dd  › d�|d   d	<   ‰j                  | |d   |d
   ‰«       | j                  dt	        j
                  d‰i«      ‰‰f«       ‰ddœS )NT)rE   Úalready_importedr   z&SELECT 1 FROM sessions WHERE title = ?z (iôÿÿÿú)Úsessionr!   rD   zBUPDATE sessions SET origin_json = ?, profile_name = ? WHERE id = ?r�   F)r›   r“   ÚfetchoneÚ_import_session_rowÚjsonÚdumps)	r•   ÚexistingÚitemÚ
normalizedr–   ÚprofilerŸ   rE   r!   s	      €€€€€€rA   Ú_doz;SessionPortabilityMixin.import_foreign_history.<locals>._doŸ   s»   ø€ Ø×8Ñ8¸¸vÓFˆHÙØ&.ÀDÑIÐIð ˜a‘=ˆDØ�|‰|ÐDÀuÀhÓO×XÑXÔZØ.3¨W°B°zÀ#À$Ð7GÐ6HÈÐ+J��Y‘ Ñ(Ø×$Ñ$ T¨4°	©?¸DÀÑ<LÈjÔYØ�L‰LÐ]ÜŸ*™* o°vÐ%>Ó?ÀÈ*ÐUôWà",À%ÑHÐHrC   )r   Ú_validate_import_payloadÚ
ValueErrorÚ_execute_write)
rŸ   r–   rD   r!   r   r°   Úerrorsr±   r¯   rE   s
   `` ` `  @@rA   Úimport_foreign_historyz.SessionPortabilityMixin.import_foreign_history‘   sv   ý€ ô $¨BÔ/ˆ
Ø!×:Ñ:Ø¨°©À%Ø Xñ/ð<0ó 1Ñˆ
�Fñ Ü˜V A™Y wÑ/Ó0Ð0÷	Iñ 	Ið ×"Ñ" 3Ó'Ð'rC   r;   c                 ó¢   ‡ — ‰ j                   €7‰ j                  t        «      d   }dj                  ˆ fd„|D «       «      ‰ _         ‰ j                   S )zŠ``s.``-prefixed SELECT list of every SCHEMA_SQL ``sessions`` column except
        prompt storage internals (the compact_rows projection).Úsessionsú, c              3   óF   •K  — | ]  }|‰j                   vsŒd |› �–— Œ y­w)zs.N)Ú_SESSION_COMPACT_EXCLUDED)rN   ÚnameÚclss     €rA   rP   z@SessionPortabilityMixin._compact_session_cols.<locals>.<genexpr>µ   s)   øè ø€ ò 6Ø $°DÀ×@]Ñ@]Ò4]�"�T�F”ñ6ùs   ƒ!—
!)Ú_session_compact_cols_sqlÚ_parse_schema_columnsr   Újoin)r½   Údeclareds   ` rA   Ú_compact_session_colsz-SessionPortabilityMixin._compact_session_cols¯   sR   ø€ ð ×(Ñ(Ð0Ø×0Ñ0´Ó<¸ZÑHˆHØ,0¯I©Ió 6Ø(0ô6ó -ˆCÔ)ð ×,Ñ,Ð,rC   c                 ód   — | j                  |«      }t        |j                  dd«      «      |d<   |S )z?Session row dict with ``_preview_raw`` shaped into ``preview``.Ú_preview_rawr=   Úpreview)Ú_session_row_dictr   Úpop)r½   r˜   r?   s      rA   Ú	_rich_rowz!SessionPortabilityMixin._rich_rowº   s2   € ð ×!Ñ! #Ó&ˆÜ% a§e¡e¨N¸BÓ&?Ó@ˆˆ)‰ØˆrC   Úsqlc                 óŽ   — | j                  «       5 }|j                  ||«      j                  «       cddd«       S # 1 sw Y   yxY w)z^Pure-read query via ``_read_ctx()`` (never the writer lock: turn persistence must not convoy).N)rž   r“   r”   )rŸ   rÉ   Úparamsr•   s       rA   Ú
_read_rowsz"SessionPortabilityMixin._read_rowsÁ   s:   € à�^‰^Óð 	8 Ø—<‘<  VÓ,×5Ñ5Ó7÷	8÷ 	8ò 	8ús	   ‘ ;»AÚinclude_archivedc           	      ó¶   — d}|s|dz  }| j                  d|› d�«      }|D �cg c]+  }|d   t        |d   xs d«      t        |d   xs d«      d	œ‘Œ- c}S c c}w )
z½Distinct non-empty session cwds with usage stats, for repo discovery. Aggregates
        across ALL history; children/branches count (a worktree session is a real
        workspace signal).z#cwd IS NOT NULL AND TRIM(cwd) != ''z AND archived = 0zsSELECT cwd AS cwd, COUNT(*) AS sessions, MAX(COALESCE(ended_at, started_at, 0)) AS last_active FROM sessions WHERE z GROUP BY cwdr   r¸   r   Úlast_active)r   r¸   rÏ   )rÌ   r}   Úfloat)rŸ   rÍ   r8   r—   Úrs        rA   Údistinct_session_cwdsz-SessionPortabilityMixin.distinct_session_cwdsÆ   s„   € ð 6ˆÙØÐ(Ñ(ˆEØ�‰ð#Ø#( '¨ð8ó
ˆð
 öØð ˜%™¬c°!°J±-Ò2DÀ1Ó.EÔV[Ð\]Ð^kÑ\lÒ\qÐpqÓVrÓsò ð 	ùò s   £0AÚjob_idÚlimitÚoffsetc                 óê   — d|› d�}|dd t        t        |d   «      dz   «      z   }t        dddd	t        › �¬
«      }| j	                  |||||f«      D �cg c]  }| j                  |«      ‘Œ c}S c c}w )a§  Run sessions of one cron job, newest first, in the ``list_sessions_rich`` row shape.
        Cron runs are flat ``cron_{job_id}_{timestamp}`` sessions that never compress or
        branch, so this skips ``list_sessions_rich``'s compression-chain CTE /
        leading-wildcard ``id_query`` path (which seeds from EVERY ``source='cron'`` row)
        for a ``[prefix, prefix_hi)`` id range scan that scales with the window.Úcron_Ú_Nrg   rR   ús.*z,s.source = 'cron' AND s.id >= ? AND s.id < ?zO
            ORDER BY s.started_at DESC, s.id DESC
            LIMIT ? OFFSET ?r>   ©r:   )ÚchrÚordrB   Ú_PROMPT_RESOLVED_SQLrÌ   rÈ   )rŸ   rÓ   rÔ   rÕ   ÚprefixÚ	prefix_hiÚqueryr˜   s           rA   Úlist_cron_job_runsz*SessionPortabilityMixin.list_cron_job_runsÔ   sˆ   € ð ˜˜ Ð"ˆà˜3˜B�K¤#¤c¨&°©*£o¸Ñ&9Ó":Ñ:ˆ	ÜØÐAØ_Ø/Ô0DÐ/EÐFô
ˆð
 04¯©¸uÀvÈyÐZ_ÐagÐFhÓ/iÖj¨�—‘˜sÕ#ÒjÐjùÒjs   ÁA0rE   Úcompact_rowsc                 óH   — | j                  |g|¬«      j                  |«      S )zÑOne session with the ``list_sessions_rich`` enriched columns, or None.
        ``compact_rows=True`` omits the ``system_prompt`` blob. Public alias:
        :meth:`get_session_rich_row` (web server hydration).©râ   )Ú_get_session_rich_rows_batchrL   )rŸ   rE   râ   s      rA   Ú_get_session_rich_rowz-SessionPortabilityMixin._get_session_rich_rowä   s(   € ð ×0Ñ0°*°ÈLÐ0ÓY×]Ñ]Ð^hÓiÐirC   c           	      ó
  — |D �cg c]  }|sŒ|‘Œ	 }}|si S d}t        |«      |kD  rGi }t        dt        |«      |«      D ]*  }|j                  | j                  ||||z    |¬«      «       Œ, |S | j	                  «        t        |r| j                  «       ndddj                  d„ |D «       «      › d�|rd	nd
t        › �¬«      }t        | j                  | j                  ||«      «      D �	ci c]  }	|	d   |	“Œ
 c}	S c c}w c c}	w )z’Enriched rows for many sessions in one query, keyed by id; missing ids are absent
        (a page of compression tips resolves in one round trip).é„  r   rä   rÙ   z	s.id IN (ú,c              3   ó    K  — | ]  }d –— Œ y­w©ú?Nrm   ©rN   rØ   s     rA   rP   zGSessionPortabilityMixin._get_session_rich_rows_batch.<locals>.<genexpr>ý   s   è ø€ ÒYkÐbcÔZ]ÑYkùó   ‚r§   Nr¹   rÚ   rZ   )rU   ÚrangeÚupdaterå   Úflush_token_countsrB   rÂ   rÀ   rÝ   ÚmaprÈ   rÌ   )
rŸ   Úsession_idsrâ   ÚsidÚidsÚ_CHUNKÚresultÚstartrà   r?   s
             rA   rå   z4SessionPortabilityMixin._get_session_rich_rows_batchì   s  € ð *Ö1�sªSŠsÐ1ˆÐ1ÙØˆIð ˆÜˆs‹8�fÒØ02ˆFÜ˜q¤# c£(¨FÓ3ò w�Ø—‘˜d×?Ñ?ÀÀEÈ%ÐRXÉ.Ð@YÐhtÐ?ÓuÕvðwàˆMà×ÑÔ!ÜÙ,8ˆD×&Ñ&Ô(¸eÀyÐQT×QYÑQYÑYkÐgjÔYkÓQkÐPlÐlmÐEnÙ".™$°bÔ9MÐ8NÐ4Oô
ˆô %(¨¯©¸¿¹ÈÈsÓ8SÓ$TÖU˜q��$‘˜‘
ÒUÐUùò# 2ùò" Vs   …C;�C;Ã+D c                 ó€   — | j                  dt        t        |«      f«      }|D �cg c]  }t        |«      ‘Œ c}S c c}w )a  Titled sessions whose first user turn was a ``/skill`` invocation (their titles
        describe the expanded skill body, not the request). Returns ``id``, ``title`` and
        the first-turn ``content`` so callers can re-derive what was typed. Newest first.a  
                SELECT s.id, s.title, m.content
                FROM sessions s
                JOIN messages m ON m.id = (
                    SELECT m2.id FROM messages m2
                    WHERE m2.session_id = s.id AND m2.role = 'user'
                      AND m2.content IS NOT NULL
                    ORDER BY m2.timestamp, m2.id LIMIT 1
                )
                WHERE s.title IS NOT NULL AND m.content LIKE ?
                ORDER BY s.started_at DESC
                LIMIT ?
                )rÌ   r
   r}   r€   )rŸ   rÔ   r—   r˜   s       rA   Úlist_skill_scaffolded_sessionsz6SessionPortabilityMixin.list_skill_scaffolded_sessions  s?   € ð �‰ð  ô .¬s°5«zÐ:ó<ˆð &*Ö*˜c”�S•	Ò*Ð*ùÒ*s   ¦;r¨   c                 óV   — | j                  |d   «      }i |¥|t        ||d   «      dœ¥S )NrZ   ©rD   Útimings)Úget_messagesr‰   )rŸ   r¨   rD   s      rA   Ú_with_messagesz&SessionPortabilityMixin._with_messages  s6   € Ø×$Ñ$ W¨T¡]Ó3ˆØe�'Ðe x¼OÈHÐV]Ð^bÑVcÓ<dÒeÐerC   c                 óN   — | j                  |«      }|r| j                  |«      S dS )z8Export a single session with all its messages as a dict.N)Úget_sessionrÿ   )rŸ   rE   r¨   s      rA   Úexport_sessionz&SessionPortabilityMixin.export_session  s*   € à×"Ñ" :Ó.ˆÙ/6ˆt×"Ñ" 7Ó+Ð@¸DÐ@rC   c           	      óX  — | j                  |«      }|syt        | j                  |«      D �cg c]  }|sŒ|‘Œ	 }}|sy|D ��cg c]  }|j                  d«      xs g D ]  }|‘Œ Œ  }}}i |d   ¥||D �cg c]  }|d   ‘Œ	 c}t	        |«      |t        ||«      dœ¥S c c}w c c}}w c c}w )z9Export a compression lineage as one logical session dict.NrD   rg   rZ   )ÚsegmentsÚlineage_session_idsÚmessage_countrD   rý   )Úget_compression_lineagerò   r  rL   rU   r‰   )rŸ   rE   Úlineage_idsÚsegr  rO   rD   s          rA   Úexport_session_lineagez.SessionPortabilityMixin.export_session_lineage   sÄ   € à×2Ñ2°:Ó>ˆÙØÜ#& t×':Ñ':¸KÓ#HÖP˜CÊC’CÐPˆÐPÙØØ#+×S˜C¸¿¹À
Ó8KÒ8QÈrÒS°’CÐS�CÐSˆÑSð
Ø�r‰lð
Ø(0Ø9AÖ#B°# C¨£IÒ#BÔUXÐYaÓUbØ ¬_¸XÀzÓ-Rò
ð 	
ùò	 Qùó Tùò $Cs   ­BµBÁ#B!Á4B'Nr   c           
      óÖ  — | j                  |d¬«      }|D �ci c]  }|d   g “Œ
 }}t        |«      }t        dt        |«      d«      D ]f  }|||dz    }| j	                  ddj                  d„ |D «       «      › d	�|«      }|D ]+  }	||	d
      j                  | j                  |	dd¬«      «       Œ- Œh |D �cg c]$  }i |¥||d      t        ||d      |d   «      dœ¥‘Œ& c}S c c}w c c}w )zDExport all sessions (with messages) as dicts, e.g. for JSONL backup.i † )r   rÔ   rZ   r   rè   z,SELECT * FROM messages WHERE session_id IN (ré   c              3   ó    K  — | ]  }d –— Œ y­wrë   rm   rí   s     rA   rP   z5SessionPortabilityMixin.export_all.<locals>.<genexpr>8  s   è ø€ ÒG[ÐPQÌÑG[ùrî   z() AND active = 1 ORDER BY session_id, idrE   rþ   T)Úwarn_contextÚsummary_flagrü   )	Úsearch_sessionsrT   rï   rU   Ú	_read_allrÀ   ÚappendÚ_row_to_message_dictr‰   )
rŸ   r   r¸   r¨   Úmessages_by_sessionró   rø   Úchunkr—   r˜   s
             rA   Ú
export_allz"SessionPortabilityMixin.export_all/  sC  € à×'Ñ'¨v¸VÐ'ÓDˆØ@HÖI°W˜w t™}¨bÑ0ÐIÐÐIÜÐ.Ó/ˆä˜1œc +Ó.°Ó4ò 
	ˆEØ  e¨c¡kÐ2ˆEØ—>‘>Ø>¸s¿x¹xÑG[ÐUZÔG[Ó?[Ð>\ð ]9ð 9àóˆDð
 ò �Ø# C¨Ñ$5Ñ6×=Ñ=Ø×-Ñ-¨cÀÐ]aÐ-Óbõñð
	ð pxöyØdkð`�7ð `Ð(;¸GÀD¹MÑ(JÜ+Ð,?ÀÈÁÑ,NÐPWÐX\ÑP]Ó^ô`ò yð 	yùò Jùòys   ˜C!Â5)C&T)Úretire_donorÚdonor_dbr  c          
      óJ  ‡ ‡— ‰j                  |«      }|sdddd|›d�dœS |j                  d«      xs |g}d}|D ]  }|j                  d«      }|r‰ j                  |«      €Œ(t        |j                  d«      xs g «      }	t        ‰ j	                  |«      «      }
|	|
kD  sŒfd}t
        j                  d	||	|
«       Œ� ‰ j                  |D �cg c]  }t        |«      ‘Œ c}«      }t        |j                  d
«      xs d«      }t        |j                  d«      xs d«      }|j                  dd«      xr ||z   t        |«      k(  }|s2t
        j                  d|||t        |«      |j                  d«      «       d}|r|r|st        ˆˆ fd„|D «       «      }i |¥||dœ¥S c c}w )u  Adopt *session_id*'s full compression lineage from *donor_db* (stranded-bot-session
        heal: a profile bot's rows accumulated in the DEFAULT profile's state.db before the
        desktop routed session RPCs by target session). Pure composition
        ``donor_db.export_session_lineage()`` -> ``self.import_sessions()``: runtime
        fields reset, already-present ids skipped (idempotent). With ``retire_donor`` and
        a complete adoption, donor rows are ARCHIVED (never deleted) with
        ``end_reason='adopted_by_profile'`` â€” deliberately NOT in the recoverable set, so
        resurrection cannot undo an adoption. Returns the ``import_sessions`` dict plus
        ``adopted`` and ``donor_retired`` (True only when EVERY segment retired).

        Once routing was fixed, the profile backend correctly received the RPCs but had no such session, so
        the same chat 4001'd for the opposite reason. This method moves the conversation to where routing
        now looks for it. See #93091, #93296.
        Fzsession z not found in donor store)ÚokÚadoptedÚdonor_retiredr¤   r  rZ   rD   Tuf   adoption divergence: donor segment %s has %d messages, local copy has %d â€” donor will NOT be retiredr™   r   Úskippedr  zSadoption of %s did not complete: imported=%s skipped=%s of %s segment(s); errors=%srµ   c              3   ól   •K  — | ]+  }|j                  d «      sŒ‰j                  ‰|d    «      –— Œ- y­w)rZ   N)rL   Ú_retire_donor_segment)rN   r	  r  rŸ   s     €€rA   rP   zESessionPortabilityMixin.adopt_session_lineage_from.<locals>.<genexpr>q  s2   øè ø€ ÒuÐTWÐgj×gnÑgnÐosÕgt × :Ñ :¸8ÀSÈÁY× OÑuùs   ƒ4š4)r  r  )r
  rL   r  rU   rþ   ÚloggerÚwarningÚimport_sessionsr€   r}   Úall)rŸ   r  rE   r  Úpayloadr  Údonor_aheadr	  Úseg_idÚdonor_countÚlocal_countr÷   r™   r  r  r  s   ``              rA   Úadopt_session_lineage_fromz2SessionPortabilityMixin.adopt_session_lineage_fromC  sµ  ù€ ð ×1Ñ1°*Ó=ˆÙØ¨EÀEØ'¨
 ~Ð5NÐOñQð Qà—;‘;˜zÓ*Ò7¨w¨iˆð
 ˆØò 		tˆCØ—W‘W˜T“]ˆFÙ˜T×-Ñ-¨fÓ5Ð=ØÜ˜cŸg™g jÓ1Ò7°RÓ8ˆKÜ˜d×/Ñ/°Ó7Ó8ˆKØ˜[Ó(Ø"�Ü—‘ð  QØRXÐZeÐgrõtð		tð ×%Ñ%¸HÖ&E°S¤t¨C¥yÒ&EÓFˆÜ�v—z‘z *Ó-Ò2°Ó3ˆÜ�f—j‘j Ó+Ò0¨qÓ1ˆØ—*‘*˜T 5Ó)ÒS¨x¸'Ñ/AÄcÈ(ÃmÑ.SˆÙÜ�N‰NÐpØ% x°¼#¸h»-ÈÏÉÐT\ÓI]ô_ð ˆÙ‘|©KÜÔuÐ[cÔuÓuˆMØM�&ÐM W¸}ÒMÐMùò 'Fs   ÃF r%  c                 ób  — 	 t        |j                  |«      «      }t        | j                  |«      «      }||kD  rt        j                  d|||«       y|j	                  |«       |j                  |d«       |j                  |d«       y# t        $ r t        j                  d|d¬«       Y yw xY w)uä  Archive one adopted donor segment; False when skipped or failed. TOCTOU close-out:
        the divergence guard used EXPORT-TIME counts; re-read both stores right before
        stamping so donor growth never lands behind a non-recoverable archive
        (equal-count CONTENT divergence is accepted â€” bytes stay in the donor either way).
        A retirement failure must not fail the adoption (a later resume retries
        idempotently), but never claims success it didn't have.uo   adoption divergence at retire time: donor segment %s grew to %d messages (local %d) â€” leaving donor unretiredFÚadopted_by_profileTz0failed to retire donor segment %s after adoption)Úexc_info)rU   rþ   r  r   Úreopen_sessionÚend_sessionÚset_session_archivedÚ	Exception)rŸ   r  r%  Ú	donor_nowÚ	local_nows        rA   r  z-SessionPortabilityMixin._retire_donor_segmentt  s°   € ð	Ü˜H×1Ñ1°&Ó9Ó:ˆIÜ˜D×-Ñ-¨fÓ5Ó6ˆIØ˜9Ò$Ü—‘ð.Ø/5°yÀ)ôð ð ×#Ñ# FÔ+Ø× Ñ  Ð)=Ô>Ø×)Ñ)¨&°$Ô7ØøÜò 	Ü�N‰NÐMÈvÐ`dˆNÔeÙð	ús   ‚AB
 Á5B
 Â
!B.Â-B.ÚvalueÚfieldc                 óF   — | �t        | t        «      r| S t        |› d�«      ‚)Nz must be a string)rS   rK   r³   )r2  r3  s     rA   Ú_import_text_or_nonez,SessionPortabilityMixin._import_text_or_none�  s(   € àˆ=œJ u¬cÔ2ØˆLÜ˜E˜7Ð"3Ð4Ó5Ð5rC   c                 ón   — 	 | €d S t        | «      S # t        t        f$ r}t        |› d�«      |‚d }~ww xY w)Nz must be an integer)r}   Ú	TypeErrorr³   )r2  r3  Úexcs      rA   Ú_import_int_or_nonez+SessionPortabilityMixin._import_int_or_none–  sG   € ð	EØ ˜=�4Ð8¬c°%«jÐ8øÜœ:Ð&ò 	EÜ ˜wÐ&9Ð:Ó;ÀÐDûð	Eús   ‚ †
 ‘4 /¯4c                 óœ  — | €y t        | t        «      r6	 t        j                  | «      }t        |t        «      st        |› d�«      ‚| S t        | t        «      st        |› d�«      ‚	 t        j                  | «      S # t        j                  $ r}t        |› d�«      |‚d }~ww xY w# t        t
        f$ r}t        |› d�«      |‚d }~ww xY w)Nz must be valid JSONz must be a JSON objectz must be JSON serializable)	rS   rK   r«   ÚloadsÚJSONDecodeErrorr³   r€   r¬   r7  )r2  r3  Úparsedr8  s       rA   Ú_import_json_object_or_nonez3SessionPortabilityMixin._import_json_object_or_none�  sÚ   € àˆ=ØÜ�eœSÔ!ðIÜŸ™ EÓ*�ô ˜f¤dÔ+Ü  E 7Ð*@Ð!AÓBÐBØˆLÜ˜%¤Ô&Ü ˜wÐ&<Ð=Ó>Ð>ð	LÜ—:‘:˜eÓ$Ð$øô ×'Ñ'ò IÜ  E 7Ð*=Ð!>Ó?ÀSÐHûðIûô œ:Ð&ò 	LÜ ˜wÐ&@ÐAÓBÈÐKûð	Lús/   •A> Á)B( Á>B%ÂB Â B%Â(CÂ7CÃCc                 óJ   — 	 | €|S  || «      S # t         t        f$ r |cY S w xY w)z;``cast(value)``; *default* for None or an unparsable value.)r7  r³   )r2  Úcastr�   s      rA   Ú
_coerce_orz"SessionPortabilityMixin._coerce_or°  s3   € ð	Ø#˜m�7Ð<±°e³Ð<øÜœ:Ð&ò 	ØŠNð	ús   ‚ † Ž"¡"ÚrawrD   c                 óF  — t        |«      }||d<   | j                  |j                  d«      d«      |d<   dgt        ¢­D ]&  }| j	                  |j                  |«      |«      ||<   Œ( g }t        |«      D ]¦  \  }}t        |«      }	|	j                  d«      }
t        |
t        «      r|
st        d|› d�«      ‚t        D ]&  }| j	                  |	j                  |«      |«      |	|<   Œ( | j                  |	j                  d«      d«      |	d<   |j                  |	«       Œ¨ ||dœS )	zAType-check one payload session + its messages; raises ValueError.rZ   r-   Úparent_session_idrI   z	messages[z!].role must be a non-empty stringÚtoken_count)r¨   rD   )r€   r>  rL   Ú_IMPORT_SESSION_TEXT_FIELDSr5  Ú	enumeraterS   rK   r³   Ú_IMPORT_MESSAGE_TEXT_FIELDSr9  r  )rŸ   rB  rE   rD   Úclean_sessionr3  Úclean_messagesÚmessage_indexÚmessageÚclean_messagerI   s              rA   Ú_normalize_import_sessionz1SessionPortabilityMixin._normalize_import_session¸  sB  € ä˜S›	ˆØ(ˆ�dÑØ(,×(HÑ(HÈ×IZÑIZÐ[iÓIjÐlzÓ({ˆ�nÑ%Ø)ÐHÔ,GÑHò 	^ˆEØ#'×#<Ñ#<¸]×=NÑ=NÈuÓ=UÐW\Ó#]ˆM˜%Ò ð	^à/1ˆÜ&/°Ó&9ò 	1Ñ"ˆM˜7Ü  ›MˆMØ ×$Ñ$ VÓ,ˆDÜ˜d¤CÔ(±Ü  9¨]¨OÐ;\Ð!]Ó^Ð^Ü4ò b�Ø'+×'@Ñ'@À×ARÑARÐSXÓAYÐ[`Ó'a�˜eÒ$ðbà+/×+CÑ+CÀM×DUÑDUÐVcÓDdÐfsÓ+tˆM˜-Ñ(Ø×!Ñ! -Õ0ð	1ð )°nÑEÐErC   r¸   c                 óÀ  — g }g }t        «       }dddœ}t        |«      D ]~  \  }}t        |t        «      r,t	        |j                  d«      xs d«      j                  «       nd}	 | j                  ||||«      }	|j                  |«       |j                  d|i|	¥«       Œ€ ||fS # t        $ r0}
|t	        |
«      dœ}	|r||	d<   |j                  |	«       Y d}
~
Œ¹d}
~
ww xY w)	z‚Size/shape/type validation of the whole payload; returns ``(normalized_items,
        errors)``. Every rejected entry is reported.r   )rD   ÚbytesrZ   r=   )Úindexr¤   rE   NrQ  )ÚsetrG  rS   r€   rK   rL   ÚstripÚ_validate_import_sessionr³   r  Úadd)rŸ   r¸   r¯   rµ   Úseen_idsÚtotalsrQ  rB  rE   r®   r8  s              rA   r²   z0SessionPortabilityMixin._validate_import_payloadË  sî   € ð ,.ˆ
Ø')ˆÜ ›UˆØ¨!Ñ,ˆÜ# HÓ-ò 	8‰JˆE�3Ü=GÈÌTÔ=Rœ˜SŸW™W T›]Ò0¨bÓ1×7Ñ7Ô9ÐXZˆJðØ×4Ñ4°S¸*ÀhÐPVÓW�ð �L‰L˜Ô$Ø×Ñ˜w¨Ð6°Ð6Õ7ð	8ð ˜6Ð!Ð!øô ò Ø!&´°S³Ñ:�ÙØ)3�D˜Ñ&Ø—‘˜dÔ#Üûðús   Á$B$Â$	CÂ-&CÃCrV  rW  c                 ól  — t        |t        «      st        d«      ‚|st        d«      ‚||v rt        d«      ‚|j                  d«      xs g }t        |t        «      st        d«      ‚t        |«      | j                  kD  rt        d«      ‚t        d„ |D «       «      rt        d«      ‚	 |j                  «       D ��ci c]  \  }}|d	k7  sŒ||“Œ }}}t        t        j                  |d
d¬«      j                  d«      «      }	|	| j                  kD  rt        d«      ‚|dxx   |	z  cc<   |d   | j                  kD  rt        d«      ‚| j                  |||«      }
|dxx   t        |
d   «      z  cc<   |d   | j                   kD  rt        d«      ‚|
S c c}}w # t        t        f$ r t        d«      d‚w xY w)z¨One payload session -> normalized item; ValueError(message) on rejection. *totals*
        accumulate before their limit check (a rejected oversize entry still counts).zsession must be an objectzsession id is requiredzduplicate session idrD   zmessages must be a listz-messages exceeds the per-session import limitc              3   ó>   K  — | ]  }t        |t        «       –— Œ y ­wr�   )rS   r€   rM   s     rA   rP   zCSessionPortabilityMixin._validate_import_session.<locals>.<genexpr>î  s   è ø€ Ò=¨S”:˜c¤4Ó(Ô(Ñ=ùrY   z"messages must contain only objectsrý   F)ré   ú:)Úensure_asciiÚ
separatorszutf-8z!session must be JSON serializableNz%session exceeds the import size limitrP  z#import exceeds the total size limitz'messages exceeds the total import limit)rS   r€   r³   rL   rT   rU   Ú _IMPORT_MAX_MESSAGES_PER_SESSIONÚanyÚitemsr«   r¬   Úencoder7  Ú_IMPORT_MAX_SESSION_BYTESÚ_IMPORT_MAX_TOTAL_BYTESrN  Ú_IMPORT_MAX_TOTAL_MESSAGES)rŸ   rB  rE   rV  rW  rD   ÚkÚvÚmeasuredÚsession_bytesr®   s              rA   rT  z0SessionPortabilityMixin._validate_import_sessionà  sµ  € ô ˜#œtÔ$ÜÐ8Ó9Ð9ÙÜÐ5Ó6Ð6Ø˜Ñ!ÜÐ3Ó4Ð4Ø—7‘7˜:Ó&Ò,¨"ˆÜ˜(¤DÔ)ÜÐ6Ó7Ð7Üˆx‹=˜4×@Ñ@Ò@ÜÐLÓMÐMÜÑ=°HÔ=Ô=ÜÐAÓBÐBð	Lð *-¯©«×G¡  A¸¸Y»˜˜1™ÐGˆHÑGÜ¤§
¡
¨8À%ÐT^Ô _× fÑ fÐgnÓ oÓpˆMð ˜4×9Ñ9Ò9ÜÐDÓEÐEØˆw‹˜=Ñ(‹Ø�'‰?˜T×9Ñ9Ò9ÜÐBÓCÐCØ×-Ñ-¨c°:¸xÓHˆØˆzÓœc $ zÑ"2Ó3Ñ3ÓØ�*Ñ × ?Ñ ?Ò?ÜÐFÓGÐGØˆùó Høäœ:Ð&ò 	LÜÐ@ÓAÀtÐKð	Lús$   Â)F Â<FÃ
FÃ3F ÆF ÆF3c                 ó’  — t        |j                  d«      |d¬«      }|t        |j                  d«      xs d«      | j                  ||j                  d«      «      |€t	        j                  «       n||j                  d«      rdnd	d
œt
        D �ci c]  }||j                  |«      “Œ c}¥t        D �ci c])  }|| j                  |j                  |«      t        d«      “Œ+ c}¥t        D �ci c])  }|| j                  |j                  |«      t        d	«      “Œ+ c}¥}|j                  t        |«       dt        dt        fd„}|D �	�
cg c]0  }	i |	¥t        D �
ci c]  }
|
 ||	j                  |
«      «      “Œ c}
¥‘Œ2 }}	}
| j                  |||«      \  }}|j                  d|||f«       yc c}w c c}w c c}w c c}
w c c}
}	w )zDINSERT one normalized session + its messages; counts fixed up after.Ú
started_at)rE   r3  r   Úimportr   NÚarchivedrR   r   )rZ   r   Úsystem_prompt_hashri  rk  r2  r;   c                 ó@   — t        | t        «      rt        | | ¬«      S | S )NrŽ   )rS   rK   r   )r2  s    rA   Ú_json_valuez@SessionPortabilityMixin._import_session_row.<locals>._json_value  s   € Ü<FÀuÌcÔ<R”? 5°%Ô8Ð]ÐX]Ð]rC   zGUPDATE sessions SET message_count = ?, tool_call_count = ? WHERE id = ?)r   rL   rK   Ú_store_system_promptÚtimeÚ_IMPORT_PASSTHROUGH_COLSÚ_IMPORT_FLOAT_COLSrA  rÐ   Ú_IMPORT_INT_COLSr}   r“   Ú_IMPORT_SESSION_INSERT_SQLr   Ú_IMPORT_MESSAGE_JSON_FIELDSÚ_insert_message_rows)rŸ   r•   rB  rD   rE   ri  ÚcolrË   rn  rO   ÚkeyÚsanitized_messagesÚtotal_messagesÚtotal_tool_callss                 rA   rª   z+SessionPortabilityMixin._import_session_row  sµ  € ä! #§'¡'¨,Ó"7ÀJÐVbÔcˆ
à¬¨C¯G©G°HÓ,=Ò,IÀÓ(JØ"&×";Ñ";¸DÀ#Ç'Á'È/ÓBZÓ"[Ø)3Ð);œ$Ÿ)™)œ+ÀØ ŸW™W ZÔ0™°añ	
ô
 -EÖE Sˆs�C—G‘G˜C“LÑ ÒEð
ô K]Ö]À3ˆs�D—O‘O C§G¡G¨C£L´%¸Ó>Ñ>Ò]ð
ô FVÖV¸cˆs�D—O‘O C§G¡G¨C£L´#°qÓ9Ñ9ÒVð
ˆð 	�‰Ô/°Ô8ð	^œsð 	^¤só 	^ð ks÷
ØcfÐ^ˆsÐ^ÔA\Ö]¸#�s™K¨¯©°«Ó5Ñ5Ò]Ò^ð
Ðñ 
ð ,0×+DÑ+DÀTÈ:ÐWiÓ+jÑ(ˆÐ(Ø�‰Ð^Ø$Ð&6¸
ÐCõ	Eùò FùÚ]ùÚVùò ^ùó
s*   ÂF/Â5.F4Ã..F9ÅGÅF>Å:GÆ>GÚparent_updatesc                 ó  ‡ ‡— t        |«      Šdt        dt        dt        fˆ ˆfd„}d}|D ]]  \  }}‰ j                  d|f«      j	                  «       }|r |||«      s‰ j                  d||f«       ŒG‰j                  |d«       |d	z  }Œ_ |S )
a   Re-attach imported children whose parent exists (in the store or the same payload)
        without creating a cycle; returns the detached count. Only the closing edge of a
        cycle is dropped, so later entries can still attach to the now-root session.rE   Ú	parent_idr;   c                 ó¬   •— | h}|}|rL||v ry|j                  |«       |‰v r‰|   }Œ"‰j                  d|f«      j                  «       }|€y|d   }|rŒLy)NTz;SELECT parent_session_id FROM sessions WHERE id = ? LIMIT 1FrD  )rU  r“   r©   )rE   r~  ÚseenÚcurrentr˜   r•   Úparent_by_childs        €€rA   Ú_would_create_cyclezKSessionPortabilityMixin._attach_import_parents.<locals>._would_create_cycle  s}   ø€ Ø�<ˆDØˆGÙØ˜d‘?ØØ—‘˜Ô!Ø˜oÑ-Ø-¨gÑ6�GØØ—l‘lØQÐT[ÐS]óç‘(“*ð ð �;Ø ØÐ1Ñ2�ò ð rC   r   ú+SELECT 1 FROM sessions WHERE id = ? LIMIT 1z6UPDATE sessions SET parent_session_id = ? WHERE id = ?NrR   )r€   rK   r   r“   r©   rÇ   )r•   r|  rƒ  ÚdetachedrE   r~  Úparent_existsr‚  s   `      @rA   Ú_attach_import_parentsz.SessionPortabilityMixin._attach_import_parents  s¥   ù€ ô
 ˜~Ó.ˆð	¬Cð 	¼Cð 	ÄDö 	ð$ ˆØ%3ò 	Ñ!ˆJ˜	Ø ŸL™LÐ)VÐYbÐXdÓe×nÑnÓpˆMÙÑ%8¸ÀYÔ%OØ—‘ÐUÐXaÐcmÐWnÕoà×#Ñ# J°Ô5Ø˜A‘‘ð	ð ˆrC   c                 ó  ‡ ‡— t        |t        «      st        d«      ‚t        |«      ‰ j                  kD  rt        d‰ j                  › d�«      ‚‰ j                  |«      \  Š}|rdddd|dœS ˆˆ fd„}‰ j                  |«      S )u  Import sessions exported by :meth:`export_session` or ``export_all``. Existing ids
        are skipped. A child keeps its parent only when the parent exists or is in the
        same payload; otherwise it is detached so partial imports pass FK validation.
        Gateway routing, handoff, rewind and other live runtime state are reset: this
        restores history, not ownership of a live channel or process. Export INCLUDES
        ``last_activity_*`` but import RESETS them to NULL â€” resurrecting a stale
        "working ..." label would fabricate activity the watchdog acts on (pinned).

        Activity contract (#76354 review S4): export INCLUDES the live activity fields (``last_activity_at``
        / ``last_activity_description`` / ``last_activity_provenance``) because they are part of the durable
        row, but import deliberately RESETS them to NULL. This asymmetry is intentional and covered by
        regression
        (tests/gateway/test_watchdog_review.py::test_s4_export_includes_activity_import_resets_it).
        zsessions must be a listzsessions must contain at most z entriesFr   )r  r™   r  r…  rµ   c                 ó  •— g }g }g }‰D ]Ï  }|d   }t        |j                  d«      xs d«      j                  «       }| j                  d|f«      j	                  «       r|j                  |«       Œg‰	j                  | ||d   |«       t        |j                  d«      xs d«      j                  «       }|r|j                  ||f«       |j                  |«       ŒÑ dt        |«      t        |«      ‰	j                  | |«      ||g dœS )	Nr¨   rZ   r=   r„  rD   rD  T)r  r™   r  r…  Úimported_idsÚskipped_idsrµ   )	rK   rL   rS  r“   r©   r  rª   rU   r‡  )
r•   rŠ  r‹  r|  r®   rB  rE   r~  r¯   rŸ   s
           €€rA   r±   z4SessionPortabilityMixin.import_sessions.<locals>._doR  s
  ø€ Ø&(ˆLØ%'ˆKØ46ˆNØ"ò 
0�Ø˜9‘o�Ü  §¡¨£Ò!4°"Ó5×;Ñ;Ó=�
Ø—<‘<Ð MÐPZÈ}Ó]×fÑfÔhØ×&Ñ& zÔ2ØØ×(Ñ(¨¨s°D¸Ñ4DÀjÔQÜ §¡Ð(;Ó <Ò BÀÓC×IÑIÓK�	ÙØ"×)Ñ)¨:°yÐ*AÔBØ×#Ñ# JÕ/ð
0ð ¬¨LÓ(9ÄcÈ+ÓFVØ ×7Ñ7¸¸nÓMØ ,¸[ÐTVñð rC   )rS   rT   r³   rU   Ú_IMPORT_MAX_SESSIONSr²   r´   )rŸ   r¸   rµ   r±   r¯   s   `   @rA   r!  z'SessionPortabilityMixin.import_sessions;  sŒ   ù€ ô ˜(¤DÔ)ÜÐ6Ó7Ð7Üˆx‹=˜4×4Ñ4Ò4ÜÐ=¸d×>WÑ>WÐ=XÐX`ÐaÓbÐbØ!×:Ñ:¸8ÓDÑˆ
�FÙØ¨Q¸1È!ÐW]Ñ^Ð^õ	ð* ×"Ñ" 3Ó'Ð'rC   )rm   )F)é   r   )éÈ   r�   ),Ú__name__Ú
__module__Ú__qualname__Ú__doc__Ústaticmethodr›   r    r¶   ÚclassmethodrK   rÂ   r   r   rÈ   rT   rÌ   r   r   rÒ   r}   rá   r	   ræ   Úget_session_rich_rowrå   rú   rÿ   r  r
  r  r(  r  r5  r9  r>  rA  rN  Útupler²   rR  rT  rª   r‡  r!  rm   rC   rA   r‹   r‹   |   s  „ ÙFàñó ðòCò(ð< ð- cò -ó ð-ð ð˜t C¨ H™~ò ó ðñ8˜cð 8°ó 8ñ
°dð ÀtÈDÐQTÐVYÐQYÉNÑG[ó ñk¨ð k°Sð kÀsð kÐSWÐX\Ð]`ÐbeÐ]eÑXfÑSgó kñ j°ð jÀ4ð jÐT\Ð]aÐbeÐgjÐbjÑ]kÑTló jð 1ÐñVÀdð VÐW[Ð\_ÐaeÐfiÐknÐfnÑaoÐ\oÑWpó Vñ,+°Cð +À$ÀtÈCÐQTÈHÁ~ÑBVó +ð*f d¨3°¨8¡nð f¸¸cÀ3¸h¹ó fðA¨ð A°¸$¸sÀC¸x¹.Ñ1Ió Að

°ð 
¸À$ÀsÈCÀxÁ.Ñ9Qó 
ñy ð y°°T¸#¸s¸(±^Ñ0Dó yð( bfò /N°3ð /NÀCð /NÐZ^ð /NÐjnÐorÐtwÐowÑjxó /Nðb¨cð ¸3ð À4ó ð8 ð6 Cð 6°ð 6¸À¹ò 6ó ð6ð
 ðE 3ð E¨sð E°xÀ±}ò Eó ðEð ðL¨3ð L°sð L¸xÈ¹}ò Ló ðLð$ ð˜#ò ó ððF¨T°#°s°(©^ð FÈð FÐX\ð FÐaeÐfiÐknÐfnÑaoó Fð&"°°d¸3À¸8±nÑ1Eð "È%ó "ð* ¨Cð  ¸Sð  ÈCð  ÐY]Ð^aÐcfÐ^fÑYgð  ÐlpÐqtÐvyÐqyÑlzó  ðDE¨T°#°s°(©^ð EÀtÈDÐQTÐVYÐQYÉNÑG[ð EÐilð EÐquó Eð, ð °T¸%±[ð  ÀSò  ó ð ðD,(¨¨T°#°s°(©^Ñ(<ð ,(ÀÀcÈ3ÀhÁô ,(rC   r‹   )r=   r=   r�   )(r’  Úloggingr«   rp  Úcollectionsr   r   r   Útypingr   r   r   r	   Úagent.skill_commandsr
   Úutilsr   Úhermes_cli.timefmtr   Úhermes_state_idsr   Úhermes_state_commonr   r   r   r   Ú	getLoggerr  rF  rH  ru  rt  rq  rs  rr  rK   rB   rÝ   r‰   r‹   rm   rC   rA   ú<module>r      sý   ðñó Û Û Ý ß 'ß ,Ó ,å 8Ý !Ý +Ý +ß oÓ oð 
ˆ×	Ñ	˜>Ó	*€ðÐ ð
Ð ð dÐ ðÐ ð.Ð ðÐ ð KÐ ñ˜cð ¨#ð °Sð ÈhÐWZÉmð Ðehó ð YÐ ñ%˜d 4¨¨S¨¡>Ñ2ð %ÀÈÁð %ÐY]Ð^aÐcfÐ^fÑYgó %÷Pk(ò k(rC   