Ë
    âß²jBë  ã                   óÈ  — d Z ddlZddlZddlZddl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mZ ddlmZmZmZmZmZ dZdZdZd	Zd
ZdZdedede	fd„Zdedefd„ZdZde› d�ZdZdedefd„Z dededefd„Z!dedefd„Z"dedefd„Z#dede$edf   defd„Z%dedefd „Z& ejN                  d!d"«      d   Z(e(efZ) e%d#e)«      Z* e&e«      Z+d$ e e«      › d% e%e+e)«      › d&�Z, e#d' e e«      › d(�«      Z- e"e-«      Z.d)e.› d* e/e«      › d+ e e«      › d, e"d-e.› d. e/e«      d"z   › d&�«      › d/e-› d0�Z0 e&e«      Z1d1e*› d2e,› d3e*› d4 e e«      › d5 e#e1«      › d6e,› d7 e#e0«      › d8�Z2d9jg                  g d:‘e*› ‘d,‘e1› ‘d;‘e,› ‘d,‘e0› ‘d;‘e› ‘d<‘ed=z  › ‘d>‘e› ‘d*‘e› ‘d?‘e› ‘d@‘e› ‘dA‘e› ‘dB‘e› ‘d>‘e› ‘d*‘ed=z  › ‘dC‘e› ‘d*‘e› ‘dD‘«      Z4dEe	defdF„Z5dGe4› dHe2› dI�Z6 e!dJdK«      › dL�Z7dMZ8dNZ9d.jg                  dO„ e9D «       «      Z: e;e9«      dPhz  Z<dQZ=d.jg                  dR„ e=D «       «      Z> e;e=«      h dS£z  Z?de@fdT„ZAdUedVedefdW„ZB e!dJdX«      › dY� eBdZe:«      z   ZCd[e7j‰                  d\¬]«      › d^eCj‰                  d\¬]«      › d&�ZEd¤dUedefd_„ZFd`edaedbedefdc„ZGd¤dUedefdd„ZHdaedefde„ZIdfZJdgZKdhZLdiZMdUedefdj„ZN eNdk«      ZO eNdl«      ZPdmZQd¥dn„ZRdoZSdpZTde@fdq„ZUdefdr„ZVdsZWeWfdteXfdu„ZYdvZZdwZ[dxZ\dyeM› dzeO› d{eP› d|eP› d}eO› d~�Z]dZ^d¦dUedefd€„Z_ e_«       Z`d� e_d\«      › d‚e`› dƒe`› d„e`› d…e`› d†�Zad‡ZbdˆZcd‰ZddŠZed‹eO› dŒeO› d��ZfdŽeO› d�eO› d��Zg ejÐ                  d�«      Zid‘Zjd’ZkejØ                  d“k(  Zmd”ZnejÞ                  ejà                  ejâ                  ejä                  hZsd•etde@fd–„Zud—eXfd˜„Zvd™„ Zwdšexddfd›„Zyd§dœ„Zzd§d�„Z{de@fdž„Z|dŸ„ Z}defd „Z~d¡„ Ze�j                   dd¢œd£„«       Z�y)¨z‘Shared constants and helpers for the SessionDB family of modules.  Lives outside hermes_state so
the mixin modules can import it without a cycle.é    N)ÚAny)ÚSKILL_EXCERPT_JOINTÚSKILL_SCAFFOLD_SQL_LIKEÚdescribe_skill_invocation)ÚLEGACY_SUMMARY_PREFIXÚSUMMARY_PREFIXÚ_MERGED_PRIOR_CONTEXT_HEADERÚ_MERGED_SUMMARY_DELIMITERÚ_SUMMARY_END_MARKERÚderivedÚllmÚuseré?   i�  é<   ÚkeyÚenv_varÚreturnc                 óØ   — ddl m}  |«       r0	 ddlm}  |«       j	                  d«      xs i j	                  | «      S t        j                  j	                  |«      S # t
        $ r Y yw xY w)a  ``sessions.<key>`` for the profile whose state.db this process is touching.

    ``gateway/run.py`` bridges the LAUNCH profile's ``sessions.*`` into ``env_var`` (the cross-process
    carrier CLI/cron children read). Under a multiplexer a routed turn runs with a HERMES_HOME override
    and that env slot holds the default profile's value, so a served profile with different
    ``sessions.*`` settings must read its own config.yaml. Unscoped: the env bridge, as before.
    Returns ``None`` when neither source sets the key.
    r   )Úget_hermes_home_override)Úload_config_readonlyÚsessionsN)Úhermes_constantsr   Úhermes_cli.configr   ÚgetÚ	ExceptionÚosÚenviron)r   r   r   r   s       ú8/home/ubuntu/.hermes/hermes-agent/hermes_state_common.pyÚrouted_sessions_settingr       sb   € õ :áÔ!ð	Ý>Ù(Ó*×.Ñ.¨zÓ:Ò@¸b×EÑEÀcÓJÐJô �:‰:�>‰>˜'Ó"Ð"øô ò 	Ùð	ús   �.A Á	A)Á(A)Útextc                 óf   — | j                  dd«      j                  dd«      j                  dd«      S )zÁEscape LIKE wildcards (``%``, ``_``) so derived text matches literally; pair with ``ESCAPE '\'``.
    ``_`` is common in branch names/titles/paths and a substring match must not silently widen.ú\z\\ú%z\%Ú_z\_©Úreplace©r    s    r   Úescape_liker(   4   s/   € ð �<‰<˜˜fÓ%×-Ñ-¨c°5Ó9×AÑAÀ#ÀuÓMÐMó    z3REPLACE(REPLACE(m.content, X'0A', ' '), X'0D', ' ')zm.content LIKE 'ú'z+CHAR(9) || CHAR(10) || CHAR(13) || CHAR(32)c                 ó2   — d| j                  dd«      z   dz   S )Nr*   z''r%   r'   s    r   Ú_sql_literalr,   ?   s   € Ø�—‘˜c 4Ó(Ñ(¨3Ñ.Ð.r)   Ú
expressionÚpathc                 ó8   — d| › d| › d�}d|› dt        |«      › d�S )z?Build a non-throwing JSON marker lookup for a JSON TEXT column.z(CASE WHEN json_valid(z) THEN z ELSE json_object() END)zjson_extract(ú, ú))r,   )r-   r.   Ú	safe_jsons      r   Ú_sql_json_extractr3   C   s?   € ð !  ð -Øˆ|Ð3ð	5ð ð ˜9˜+ R¬°TÓ(:Ð';¸1Ð=Ð=r)   c                 ó   — d| › dt         › d�S )NzLTRIM(r0   r1   ©Ú_SQL_WHITESPACE©r-   s    r   Ú_sql_ltrim_whitespacer8   M   s   € Ø�J�<˜r¤/Ð!2°!Ð4Ð4r)   c                 ó   — d| › dt         › d�S )NzTRIM(r0   r1   r5   r7   s    r   Ú_sql_trim_whitespacer:   Q   s   € Ø�:�,˜b¤Ð 1°Ð3Ð3r)   Úprefixes.c                 óZ   ‡— t        | «      Šddj                  ˆfd„|D «       «      z   dz   S )Nú(ú OR c           	   3   óX   •K  — | ]!  }d ‰› dt        |«      › dt        |«      › �–— Œ# y­w)úSUBSTR(ú, 1, ú) = N)Úlenr,   )Ú.0ÚpÚtrimmeds     €r   ú	<genexpr>z#_sql_starts_with.<locals>.<genexpr>W   s.   øè ø€ ÒeÐWX˜w w i¨u´S¸³V°H¸DÄÈaÃÐ@QÔRÑeùs   ƒ'*r1   )r8   Újoin)r-   r;   rF   s     @r   Ú_sql_starts_withrI   U   s.   ø€ Ü# JÓ/€GØ�—‘ÓeÐ\dÔeÓeÑeÐhkÑkÐkr)   Úmarkerc                 ó8   — dt        | «      › dt        | «      › d�S )z5``m.content`` after the first occurrence of *marker*.z#SUBSTR(m.content, INSTR(m.content, z) + r1   )r,   rC   )rJ   s    r   Ú_sql_after_markerrL   Z   s"   € à0´¸fÓ1EÐ0FÀdÌ3ÈvË;È-ÐWXÐYÐYr)   zDo NOT answeré   z	m.contentz(INSTR(m.content, z
) > 0 AND r1   z&SUBSTR(m.content, 1, INSTR(m.content, z) - 1)zCASE WHEN SUBSTR(rA   rB   z THEN r@   r0   z ELSE z ENDz4(COALESCE(m.display_kind, '') <> 'hidden' AND ((NOT z	 AND NOT z) OR (z AND INSTR(m.content, z) > 0 AND LENGTH(z) > 0) OR (z AND LENGTH(z) > 0)))Ú ú
CASE WHEN z WHEN z AND LENGTH(m.content) > é   z THEN SUBSTR(z) || 'z' || SUBSTR(z, -z) WHEN z) ELSE SUBSTR(z) ENDÚrawc                 ó  — t        | xs d«      j                  «       }|sy|j                  dd«      j                  dd«      }t        |«      }|�|n|j	                  t
        «      d   }t        |«      t        kD  r|dt         dz   S |S )zCTurn a ``_preview_raw`` column into the short preview callers show.rN   Ú
Ú úNr   z...)ÚstrÚstripr&   r   Úsplitr   rC   Ú_PREVIEW_MAX_CHARS)rQ   r    Ú	describeds      r   Ú_shape_previewr[   „   s‡   € äˆsŠy�b‹>×ÑÓ!€DÙØØ�<‰<˜˜cÓ"×*Ñ*¨4°Ó5€DÜ)¨$Ó/€IØ!Ð-‰9°4·:±:Ô>QÓ3RÐSTÑ3U€DÜ03°D³	Ô<NÒ0Nˆ4Ð#Ô#Ð$ uÑ,ÐXÐTXÐXr)   zCOALESCE((SELECT z] FROM messages m WHERE m.session_id = s.id AND m.role = 'user' AND m.content IS NOT NULL AND z9 ORDER BY m.timestamp, m.id LIMIT 1), '') AS _preview_rawz{a}.model_configz$._branched_fromz´ IS NOT NULL OR EXISTS (SELECT 1 FROM sessions p            WHERE p.id = {a}.parent_session_id            AND p.end_reason = 'branched'            AND {a}.started_at >= p.ended_at)zsEXISTS (SELECT 1 FROM sessions p        WHERE p.id = {a}.parent_session_id        AND p.end_reason = 'compression'))Úsession_resetÚsession_switchÚidleÚdailyÚ	suspendedÚresume_pending_expiredc              #   ó(   K  — | ]
  }d |› d �–— Œ y­w©r*   N© ©rD   Úreasons     r   rG   rG       s   è ø€ Ò"R°V Q v h¨a¤=Ñ"Rùó   ‚Únew_session)Úagent_closeÚws_orphan_reapÚsuperseded_by_resumeÚstartup_orphan_reapc              #   ó(   K  — | ]
  }d |› d �–— Œ y­wrc   rd   re   s     r   rG   rG   ­   s   è ø€ Ò(^¸6¨1¨V¨H°A¬Ñ(^ùrg   >   Ú	lru_evictÚidle_timeoutÚtui_shutdownÚws_disconnectc                 ó6   — t        | t        «      xr | t        v S )u/  True when *reason* is an automatic-cleanup end stamp; compression-liveness sites must call this.

    Single owner of the "accidental vs deliberate end" predicate â€” every compression-liveness site must call
    this instead of re-implementing the reason taxonomy (#88197, never-patch-predicates).
    )Ú
isinstancerV   Ú_AUTOMATIC_END_REASONS)rf   s    r   Úis_automatic_end_reasonru   ¹   s   € ô �fœcÓ"ÒG vÔ1GÐ'GÐGr)   ÚaliasÚreasons_sqlc                 ó&   — d| › d|› d| › d| › d| › d�S )a  Pre-marker reset-continuation heuristic: child rides its parent's exact non-empty routing key and the
    parent ended at a reset boundary.  Shared by ``_RESET_CHILD_SQL`` and ``reopen_session()`` so the two
    cannot drift; ``reasons_sql`` is a literal or placeholder list.z9EXISTS (SELECT 1 FROM sessions p            WHERE p.id = z3.parent_session_id            AND p.end_reason IN (z)            AND z(.session_key IS NOT NULL            AND z".session_key != ''            AND z.session_key = p.session_key)rd   )rv   rw   s     r   Ú_legacy_reset_child_sqlry   Â   s?   € ð HÈÀwð O,Ø,7¨=Ð8IÈ%Èð QØ ˜'Ð!CÀEÀ7ÐJgðið jr)   z$._reset_fromz IS NOT NULL OR z{a}z (s.parent_session_id IS NULL OR Ús©Úar>   c           
      ó˜   — d| › dt         j                  | ¬«      › dt        j                  | ¬«      › dt        j                  | ¬«      › d�	S )z:Subagent runs, not branch, reset, or compression children.r=   z(.parent_session_id IS NOT NULL AND NOT (r{   z) AND NOT (z)))Ú_BRANCH_CHILD_SQLÚformatÚ_COMPRESSION_CHILD_SQLÚ_RESET_CHILD_SQL©rv   s    r   Ú_ephemeral_child_sqlrƒ   Õ   sa   € à�ˆwÐ>Ô?P×?WÑ?WÐZ_Ð?WÓ?`Ð>að bÜ+×2Ñ2°UÐ2Ó;Ð<¸KÔHX×H_ÑH_ÐbgÐH_ÓHhÐGiÐikðmð nr)   ÚactivityÚsession_id_exprÚstartedc                 ó&   — d|› d�}d| › d|› d|› d�S )zÊFreshest of *activity* and the latest message timestamp for *session_id_expr*, else *started*.
    Heartbeats are rate-limited (~60s) so ``last_activity_at`` can lag a newer message; never use it alone.zM(SELECT MAX(_act_m.timestamp) FROM messages _act_m WHERE _act_m.session_id = r1   z,COALESCE((SELECT MAX(_act_v.v) FROM (SELECT z AS v UNION ALL SELECT z) _act_v), rd   )r„   r…   r†   Úmsg_maxs       r   Ú_sql_freshest_ofr‰   Û   s:   € ð ^Ð^mÐ]nÐnoÐp€GØ:¸8¸*ÐD[Ð\cÐ[dÐdoØˆ)�1ðð r)   c                 ó.   — t        | › d�| › d�| › d�«      S )z:Session recency expression for a ``sessions {alias}`` row.z.last_activity_atz.idz.started_at©r‰   r‚   s    r   Ú_sql_session_last_activerŒ   ã   s'   € ä˜u˜gÐ%6Ð7¸E¸7À#¸È5È'ÐQ\ÐH]Ó^Ð^r)   c                 ó,   — t        d| › d�| d| › d�«      S )zASame freshest-of expression keyed by a session-id SQL expression.z@(SELECT last_activity_at FROM sessions _act_s WHERE _act_s.id = r1   z:(SELECT started_at FROM sessions _act_s WHERE _act_s.id = r‹   )r…   s    r   Ú_sql_session_last_active_by_idrŽ   è   s0   € äØ
JÈ?ÐJ[Ð[\Ð]Ð_nØ
DÀ_ÐDUÐUVÐWóYð Yr)   é   g      Ð?é   i    c           	      ó(   — d| › d| › dt         › d| › d�	S )NrO   z-.role = 'tool'
         THEN substr(COALESCE(z.content, ''), 1, z)
         ELSE z.content END)ÚFTS_TOOL_CONTENT_PREFIX_CHARSr‚   s    r   Ú_fts_indexed_content_sqlr“     s3   € Ø˜%˜ð !Ø$˜gÐ%7Ô8UÐ7Vð WØˆg�\ð%ð %r)   ÚnewÚoldi   c                 ó°   — 	 t        j                  | «      }|j                  r$|j                  r|j                  |j                  fS dS # t        $ r Y yw xY w)z›``(st_dev, st_ino)`` for *path*, or None.  st_ino=0 (Windows, some network FS) would false-positive
    every replaced-file check, so it counts as unknown.N)r   ÚstatÚOSErrorÚst_devÚst_ino)r.   Ústs     r   Ústat_db_file_identityrœ      sN   € ðÜ�W‰W�T‹]ˆð &(§Y¢Y°2·9²9ˆB�I‰I�r—y‘yÐ!ÐFÀ$ÐFøô ò Ùðús   ‚A	 Á		AÁAz6SELECT ended_at, end_reason FROM sessions WHERE id = ?zESELECT holder, expires_at FROM compression_locks WHERE session_id = ?c                 ó0   — | d uxr | d   d uxr | d   dk(  S )NÚended_atÚ
end_reasonÚcompressionrd   )Úrows    r   Ú_ended_by_compressionr¢   /  s*   € Ø�dˆ?Òa˜s :™°dÐ:Òa¸sÀ<Ñ?PÐTaÑ?aÐar)   c           	      óz   — dj                  d„ t        t        | t        «      r| n
t	        | «      «      D «       «      S )zV``?,?,?`` for one bound parameter per element of *items* (a sequence or an int count).ú,c              3   ó    K  — | ]  }d –— Œ y­w)ú?Nrd   )rD   r$   s     r   rG   z _placeholders.<locals>.<genexpr>5  s   è ø€ ÒX˜A”CÑXùs   ‚)rH   Úrangers   ÚintrC   )Úitemss    r   Ú_placeholdersrª   3  s,   € à�8‰8ÑX¤´
¸5Ä#Ô0F¡uÌCÐPUËJÓ!WÔXÓXÐXr)   i„  Úsizec              #   ól   K  — t        | «      } t        dt        | «      |«      D ]  }| |||z    –— Œ y­w)z?Yield *ids* (any iterable) as lists of at most *size* elements.r   N)Úlistr§   rC   )Úidsr«   Ústarts      r   Ú
_id_chunksr°   >  s<   è ø€ ä
ˆs‹)€CÜ�qœ#˜c›( DÓ)ò &ˆØ�%˜ ™Ð%Ó%ñ&ùs   ‚24)Úmessages_fts_insertÚmessages_fts_deleteÚmessages_fts_updateÚmessages_fts_trigram_insertÚmessages_fts_trigram_deleteÚmessages_fts_trigram_updateu’%  
CREATE TABLE IF NOT EXISTS schema_version (
    version INTEGER NOT NULL
);

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

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

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

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

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

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

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

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

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

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

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

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

CREATE INDEX IF NOT EXISTS idx_sessions_source ON sessions(source);
CREATE INDEX IF NOT EXISTS idx_sessions_source_id ON sessions(source, id);
CREATE INDEX IF NOT EXISTS idx_sessions_parent ON sessions(parent_session_id);
CREATE INDEX IF NOT EXISTS idx_sessions_started ON sessions(started_at DESC);
CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id, timestamp);
CREATE INDEX IF NOT EXISTS idx_messages_session_id ON messages(session_id, id);
-- Partial index for the Insights assistant tool-call scan
-- (agent/insights.py _get_tool_usage / _get_skill_usage): those queries filter
-- messages by role='assistant' AND tool_calls IS NOT NULL, a small fraction of
-- rows on a large state.db. role and tool_calls are base columns, so this can
-- live in SCHEMA_SQL rather than DEFERRED_INDEX_SQL.
CREATE INDEX IF NOT EXISTS idx_messages_assistant_calls_by_session
    ON messages(session_id)
    WHERE role = 'assistant' AND tool_calls IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_compression_locks_expires ON compression_locks(expires_at);
CREATE INDEX IF NOT EXISTS idx_session_turn_leases_expires ON session_turn_leases(expires_at);
CREATE INDEX IF NOT EXISTS idx_session_model_usage_session ON session_model_usage(session_id);
CREATE INDEX IF NOT EXISTS idx_session_model_usage_model ON session_model_usage(model);
CREATE INDEX IF NOT EXISTS idx_async_delegations_delivery
    ON async_delegations(delivery_state, completed_at);
a–  
CREATE INDEX IF NOT EXISTS idx_messages_session_active
    ON messages(session_id, active, timestamp);
CREATE INDEX IF NOT EXISTS idx_messages_display_page
    ON messages(session_id, display_order, active DESC, id DESC)
    WHERE active = 1 OR compacted = 1;
CREATE INDEX IF NOT EXISTS idx_messages_display_backfill
    ON messages(session_id) WHERE (display_order IS NULL OR display_identity IS NULL)
    AND (active = 1 OR compacted = 1);
CREATE INDEX IF NOT EXISTS idx_messages_display_identity
    ON messages(session_id, display_identity, display_order)
    WHERE display_identity IS NOT NULL AND (active = 1 OR compacted = 1);
DROP TRIGGER IF EXISTS messages_display_order_insert;
CREATE TRIGGER IF NOT EXISTS messages_display_order_insert
AFTER INSERT ON messages WHEN new.display_order IS NULL
BEGIN
    UPDATE messages SET display_order = COALESCE((
        SELECT display_order FROM messages
        WHERE session_id = new.session_id AND id <> new.id
          AND (active = 1 OR compacted = 1)
          AND display_identity = new.display_identity AND display_order IS NOT NULL
        ORDER BY display_order LIMIT 1
    ), new.id) WHERE id = new.id;
END;
DROP TRIGGER IF EXISTS messages_display_visibility_update;
CREATE TRIGGER IF NOT EXISTS messages_display_visibility_update
AFTER UPDATE OF active, compacted ON messages
WHEN (new.active = 1 OR new.compacted = 1) <> (old.active = 1 OR old.compacted = 1)
BEGIN
    UPDATE messages SET display_order = MIN(new.id, COALESCE((
        SELECT display_order FROM messages
        WHERE session_id = new.session_id AND id <> new.id
          AND (active = 1 OR compacted = 1)
          AND display_identity = new.display_identity AND display_order IS NOT NULL
        ORDER BY display_order LIMIT 1
    ), new.id)) WHERE id = new.id
      AND (new.active = 1 OR new.compacted = 1);
    UPDATE messages SET display_order = (SELECT display_order FROM messages WHERE id = new.id)
    WHERE session_id = new.session_id AND id <> new.id AND (active = 1 OR compacted = 1)
      AND display_identity = new.display_identity
      AND (new.active = 1 OR new.compacted = 1);
    UPDATE messages SET display_order = (
        SELECT MIN(peer.id) FROM messages AS peer
        WHERE peer.session_id = old.session_id AND (peer.active = 1 OR peer.compacted = 1)
          AND peer.display_identity = old.display_identity
    ) WHERE session_id = old.session_id AND (active = 1 OR compacted = 1)
      AND display_identity = old.display_identity
      AND NOT (new.active = 1 OR new.compacted = 1);
END;
DROP TRIGGER IF EXISTS messages_display_identity_update;
CREATE TRIGGER IF NOT EXISTS messages_display_identity_update
AFTER UPDATE OF role, content, timestamp, tool_call_id, tool_calls, tool_name,
                display_kind ON messages
WHEN new.role IS NOT old.role
  OR new.content IS NOT old.content
  OR new.timestamp IS NOT old.timestamp
  OR new.tool_call_id IS NOT old.tool_call_id
  OR new.tool_calls IS NOT old.tool_calls
  OR new.tool_name IS NOT old.tool_name
  OR new.display_kind IS NOT old.display_kind
BEGIN
    UPDATE messages SET display_identity = NULL, display_order = NULL
    WHERE id = new.id OR (
        session_id = old.session_id AND display_identity = old.display_identity
        AND (active = 1 OR compacted = 1)
    );
END;
DROP TRIGGER IF EXISTS messages_display_identity_delete;
CREATE TRIGGER IF NOT EXISTS messages_display_identity_delete
AFTER DELETE ON messages WHEN old.active = 1 OR old.compacted = 1
BEGIN
    UPDATE messages SET display_order = (
        SELECT MIN(peer.id) FROM messages AS peer
        WHERE peer.session_id = old.session_id AND (peer.active = 1 OR peer.compacted = 1)
          AND peer.display_identity = old.display_identity
    ) WHERE session_id = old.session_id AND (active = 1 OR compacted = 1)
      AND display_identity = old.display_identity;
END;
CREATE INDEX IF NOT EXISTS idx_messages_active_null
    ON messages(active) WHERE active IS NULL;
CREATE INDEX IF NOT EXISTS idx_sessions_session_key
    ON sessions(session_key, started_at DESC);
CREATE INDEX IF NOT EXISTS idx_sessions_gateway_peer
    ON sessions(source, user_id, chat_id, chat_type, thread_id, started_at DESC);
CREATE INDEX IF NOT EXISTS idx_sessions_handoff_state
    ON sessions(handoff_state, started_at);
CREATE INDEX IF NOT EXISTS idx_sessions_system_prompt_hash
    ON sessions(system_prompt_hash);
-- Recent-session browsing must never derive recency by scanning messages.
-- This expression is the durable, indexable approximation used to preselect
-- a small candidate set before compression-chain and preview hydration.
CREATE INDEX IF NOT EXISTS idx_sessions_effective_activity
    ON sessions(COALESCE(last_activity_at, started_at) DESC, started_at DESC);
ah  
-- Stable projection the base word index reads and writes through: the view
-- computes EXACTLY what the triggers/backfill insert, so 'rebuild' and the
-- integrity checker always agree with the stored index.
CREATE VIEW IF NOT EXISTS messages_fts_src AS
    SELECT id,
           CASE WHEN role = 'tool'
                THEN substr(COALESCE(content, ''), 1, a×  )
                ELSE content END AS content,
           tool_name, tool_calls
    FROM messages;

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

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

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

-- UPDATE OF skips the trigger entirely for non-content column writes
-- (status/compacted/observed/etc.), which is stronger than the WHEN gate
-- alone and avoids FTS I/O saturation on large state.db (#68858 / #73639).
CREATE TRIGGER IF NOT EXISTS messages_fts_update
AFTER UPDATE OF content, tool_name, tool_calls, role ON messages
WHEN (old.content IS NOT new.content
    OR old.tool_name IS NOT new.tool_name
    OR old.tool_calls IS NOT new.tool_calls
    OR old.role IS NOT new.role)
   AND (old.id > COALESCE((SELECT CAST(value AS INTEGER) FROM state_meta
                           WHERE key = 'fts_rebuild_high_water'), -1)
     OR old.id <= COALESCE((SELECT CAST(value AS INTEGER) FROM state_meta
                            WHERE key = 'fts_rebuild_progress'), -1))
BEGIN
    INSERT INTO messages_fts(messages_fts, rowid, content, tool_name, tool_calls)
    VALUES (
        'delete',
        old.id,
        z ,
        old.tool_name,
        old.tool_calls
    );
    INSERT INTO messages_fts(rowid, content, tool_name, tool_calls)
    VALUES (
        new.id,
        z<,
        new.tool_name,
        new.tool_calls
    );
END;
)ÚcronÚsubagentc                 ó€   — | r| › d�nd}|› d�dj                  d„ t        D «       «      z   dt        |dz   d«      › d	�z   S )
a  Predicate over a ``sessions`` row selecting sessions whose rows belong in
    the trigram index; ``alias`` qualifies every column for joins. Shared by the
    view, the sync triggers, and the deferred-backfill INSERT ... SELECTs so they
    can never disagree about the index boundary.ú.rN   zsource NOT IN (r0   c              3   ó(   K  — | ]
  }d |› d �–— Œ y­wrc   rd   )rD   Úsrcs     r   rG   z*fts_trigram_session_sql.<locals>.<genexpr>9  s   è ø€ ÒG 3�a˜�u˜A”JÑGùrg   z) AND Úmodel_configz$._delegate_fromz IS NULL)rH   ÚFTS_TRIGRAM_EXCLUDED_SOURCESr3   )rv   Úqs     r   Úfts_trigram_session_sqlrÀ   1  s\   € ñ
 ˆ5ˆ'�‰ "€Aàˆ#ˆ_ÐØ
�)‰)ÑGÔ*FÔGÓ
Gñ	HàÔ$ Q¨Ñ%7Ð9KÓLÐMÈXÐ
Vñ	Wðr)   zË
CREATE VIEW IF NOT EXISTS messages_fts_trigram_src AS
    SELECT m.id, m.role, m.content, m.tool_name
    FROM messages AS m
    JOIN sessions AS s ON s.id = m.session_id
    WHERE m.role <> 'tool' AND a{  ;

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

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

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

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

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

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

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

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

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

CREATE TRIGGER IF NOT EXISTS messages_fts_trigram_update
AFTER UPDATE OF content, tool_name, tool_calls, role ON messages BEGIN
    DELETE FROM messages_fts_trigram WHERE rowid = old.id;
    INSERT INTO messages_fts_trigram(rowid, content) VALUES (
        new.id,
        COALESCE(Úhermes_stateg      ^@gš™™™™™¹?Úwin32g      @Úexcc                 ón   — t        | t        «      xs$ t        | t        «      xr | j                  t        v S )zcTrue when *exc* means another process holds the lock; on any other ``OSError`` fail closed at once.)rs   ÚBlockingIOErrorr˜   ÚerrnoÚ_LOCK_CONTENTION_ERRNOS)rÉ   s    r   Úis_advisory_lock_contentionrÎ      s*   € ä�cœ?Ó+Òr´
¸3ÄÓ0HÒ0qÈSÏYÉYÔZqÐMqÐrr)   Úpidc                 óþ   — 	 t        d| › d�d«      5 }t        |j                  «       j                  dd«      d   j	                  «       d   «      cddd«       S # 1 sw Y   yxY w# t
        t        t        f$ r Y yw xY w)uÎ   Kernel start time of *pid* (field 22 of ``/proc/<pid>/stat``; with the PID it identifies a process
    uniquely).  None off Linux or on any failure â€” callers must treat None as unknowable and FAIL CLOSED.z/proc/z/statÚrbó   )rM   é   N)Úopenr¨   ÚreadÚrsplitrX   r˜   Ú
ValueErrorÚ
IndexError)rÏ   Úfhs     r   Ú_proc_start_ticksrÚ     s{   € ðä�F˜3˜%˜uÐ% tÓ,ð 	A°Ü�r—w‘w“y×'Ñ'¨¨aÓ0°Ñ3×9Ñ9Ó;¸BÑ?Ó@÷	A÷ 	Aò 	Aûä”Z¤Ð,ò Ùðús.   ‚A% ’=AÁ	A% ÁA"ÁA% Á"A% Á%A<Á;A<c                 óö   — 	 | j                  d«       | j                  d«      }|r%t        j                  |j	                  dd«      «      nd}t        |t        «      r|S dS # t
        t        t        f$ r Y yw xY w)z=Best-effort parse of the holder metadata JSON in a lock file.r   i   úutf-8r&   N)
ÚseekrÕ   ÚjsonÚloadsÚdecoder˜   r×   ÚUnicodeDecodeErrorrs   Údict)ÚhandlerQ   Úrecords      r   Ú_read_lock_holder_recordrå     so   € ðØ�‰�AŒØ�k‰k˜$ÓˆÙ?B”—‘˜CŸJ™J w°	Ó:Ô;Èˆô   ¬Ô-ˆ6Ð7°4Ð7øô ”ZÔ!3Ð4ò Ùðús   ‚AA! Á!A8Á7A8Úpayloadc                 óò   — t        j                  t        t        «      5  | j	                  d«       | j                  «        |r| j                  |«       | j                  «        ddd«       y# 1 sw Y   yxY w)z8Best-effort truncate-and-write of *payload* at offset 0.r   N)Ú
contextlibÚsuppressr˜   r×   rÝ   ÚtruncateÚwriteÚflush)rã   ræ   s     r   Ú_rewrite_lock_filerí     sT   € ä	×	Ñ	œW¤jÓ	1ñ Ø�‰�AŒØ�‰ÔÙØ�L‰L˜Ô!Ø�‰Œ÷÷ ñ ús   ŸAA-Á-A6c                 óî   — t        j                  «       t        t        j                  «       «      t        j                  «       dœ}t	        | t        j                  |d¬«      j                  d«      «       y)uG  Record this process as holder (best effort) so timed-out contenders can tell an orphaned-fd holder
    from a live wedged one.

    Written under the flock so contenders that time out can tell an orphaned-fd holder (recorded process
    dead, flock inherited by a forked child â€” issue #100108) from a live wedged holder.
    )rÏ   Ústart_ticksÚacquired_atT)Ú	sort_keysrÜ   N)r   ÚgetpidrÚ   Útimerí   rÞ   ÚdumpsÚencode)rã   rä   s     r   Ú_write_lock_holder_recordrö   %  sL   € ô —Y‘Y“[Ô1BÄ2Ç9Á9Ã;Ó1OÔ`d×`iÑ`iÓ`kÑl€FÜ�vœtŸz™z¨&¸DÔA×HÑHÈÓQÕRr)   c                 ó   — t        | d«       y)zfErase holder metadata before a normal release: a surviving record means ABNORMAL exit (break allowed).r)   N)rí   )rã   s    r   Ú_clear_lock_holder_recordrø   0  s   € ä�v˜sÕ#r)   c                 ó  — 	 t        | d   «      }|dk  ry	 t	        j
                  |d«       | j                  d«      }|€yt        |«      }|duxr ||k7  S # t        t        t        f$ r Y yw xY w# t        $ r Y yt        $ r Y yw xY w)zÅTrue ONLY when the recorded holder is provably dead or PID-recycled.  Anything indeterminate
    (no/malformed record, PID owned by another user, /proc unavailable) is False: FAIL CLOSED and defer.rÏ   Fr   Trï   N)
r¨   ÚKeyErrorÚ	TypeErrorr×   r   ÚkillÚProcessLookupErrorr˜   r   rÚ   )rä   rÏ   Úrecorded_ticksÚcurrent_tickss       r   Ú_lock_holder_provably_deadr   5  s¤   € ðÜ�&˜‘-Ó ˆð ˆa‚xØðÜ
�‰��QŒð
 —Z‘Z Ó.€NØÐØÜ% cÓ*€MØ Ð$ÒH¨¸.Ñ)HÐHøô ”i¤Ð,ò Ùðûô ò ÙÜò Ùðús(   ‚A —A1 ÁA.Á-A.Á1	BÁ<BÂBc           	      ó  — ddl }t        j                  «       |z   }d}	 	 |j                  |j	                  «       |j
                  |j                  z  «       	 t!        j*                  |j	                  «       «      t!        j,                  | «      }}
|
j.                  |j.                  k(  xr |
j0                  |j0                  k(  }|rt3        |«       d|fS 	 |j%                  «        t'        | d«      }t        j                  «       |k\  rd|fS Œï# t        t        f$ �rF}t        |«      s"t        j                  d|| ||«       d|fcY d}~S t        j                  «       |k  rt        j                  |«       Y d}~�Œ_|r	d|fcY d}~S t        |«      }	t        |	«      s	d|fcY d}~S t        j                  d|| |	xs i j                  d«      «       	 t!        j"                  | «       |j%                  «        t'        | d«      }n7# t        $ r+}t        j                  d	|| |«       d|fcY d}~cY d}~S d}~ww xY wd}t        j                  «       t(        z   }Y d}~�ŒAd}~ww xY w# t        $ r d}Y �Œ±w xY w# t        $ r d|fcY S w xY w)
u  Bounded POSIX flock acquire with orphaned-holder break.  Returns ``(acquired, handle)``; *handle* may
    have been re-opened and the caller closes whichever comes back.  *acquired*: True, False (a holder kept
    the lock past the deadline) or None (non-contention ``OSError``, already logged: treat as not acquired
    without the held-by-another-process warning).  ``flock`` rides the open file DESCRIPTION, which ``fork()``
    duplicates, so a holder that forks then dies leaves the lock held forever; when the acquirer is provably
    dead the file is unlinked and retaken on a fresh inode (the orphan's flock excludes nobody).  Every
    acquire verifies its inode still names *lock_path*, so a racer on a dead inode retries.

    A holder that forks (multiprocessing worker, daemonized helper) and then dies leaves the flock held by a
    child that will never release it â€” the kernel's holder-death release never triggers, and every contender
    defers forever. Indeterminate liveness always defers (fail closed). See #100108.
    r   NFTuv   Could not acquire %s %s (%s) â€” deferring rather than waiting out the %.0fs holder timeout on a non-contention error.u°   %s %s is held by an orphaned file descriptor (recorded holder pid %s is dead â€” a forked child inherited the lock fd); breaking the stale lock and retaking it on a fresh file.rÏ   úa+bu/   Could not break stale %s %s (%s) â€” deferring.)Úfcntlró   Ú	monotonicÚflockÚfilenoÚLOCK_EXÚLOCK_NBrË   r˜   rÎ   ÚloggerÚwarningÚsleeprå   r   r   r   ÚunlinkÚcloserÔ   Ú_LOCK_BREAK_REACQUIRE_SECONDSÚfstatr—   r™   rš   rö   )Ú	lock_pathrã   Útimeout_secondsÚpoll_secondsÚdescriptionr  ÚdeadlineÚ
broke_lockrÉ   rä   Úfd_statÚ	path_statÚ	same_files                r   Ú_acquire_db_flockr  K  sB  € ó Ü�~‰~Ó /Ñ1€HØ€JØ
ð	Ø�K‰K˜Ÿ™›¨¯©¸¿¹Ñ)FÔGð:	Ü!#§¡¨&¯-©-«/Ó!:¼B¿G¹GÀIÓ<N�YˆGØŸ™¨)×*:Ñ*:Ñ:Òa¸w¿~¹~ÐQZ×QaÑQaÑ?aˆIñ Ü% fÔ-Ø˜�<Ðð	!Ø�L‰LŒNÜ˜) UÓ+ˆFô �>‰>Ó˜xÒ'Ø˜&�=Ð ð[ øô  ¤Ð)ó 	Ü.¨sÔ3Ü—‘ð  aà*¨I°s¸OôMð ˜V�|Õ#Ü�~‰~Ó (Ò*Ü—
‘
˜<Ô(ÝÙØ˜f�}Õ$Ü-¨fÓ5ˆFÜ-¨fÔ5Ø˜f�}Õ$Ü�N‰Nð )à*5°yÀ6Â<ÈR×BTÑBTÐUZÓB[ô]ð%Ü—	‘	˜)Ô$Ø—‘”Ü˜i¨Ó/‘øÜò %Ü—‘ÐPÐR]Ð_hÐjmÔnØ˜f�}×$Ò$ûð%úð ˆJÜ—~‘~Ó'Ô*GÑGˆHÝûð5	ûô> ò 	Ø‹Ið	ûô ò 	!Ø˜&�=Ò ð	!úsœ    7D ÁA,I' ÃI9 ÄI$Ä'IÅI$Å,IÅ=IÆI$ÆIÆ!I$Æ'+IÇ1HÈIÈ	H9ÈH4È)H9È*IÈ.I$È4H9È9 IÉI$É'I6É5I6É9J	ÊJ	c                 ó4  — t        | t        «      rd| vryd}t        j                  t        t
        «      5  | j                  d«      �(dt        j                  «       t        | d   «      z
  d›d�}ddd«       d	| j                  d«      › |› �S # 1 sw Y   ŒxY w)
z5Human-readable holder identity for deferral warnings.rÏ   z8unknown (no holder record; pre-fix writer or non-Hermes)rN   rð   Nz, acquired z.0fzs agozpid )	rs   râ   rè   ré   rû   r×   r   ró   Úfloat)rä   Úages     r   Ú_describe_lock_holderr  ‹  s–   € ä�fœdÔ# u°FÑ':ØIØ
€CÜ	×	Ñ	œY¬
Ó	3ñ VØ�:‰:�mÓ$Ð0Ø¤§	¡	£¬e°F¸=Ñ4IÓ.JÑ JÈ3ÐOÈuÐUˆC÷Vð �&—*‘*˜UÓ#Ð$ S EÐ*Ð*÷Vð Vús   ¶:BÂBc                 ó¨  — ddl }t        j                  «       |z   }	 	 |j                  d«       |j	                  |j                  «       |j                  d«       y# t        t        f$ rf}t        |«      st        j                  d| |«       Y d}~yt        j                  «       |k\  rY d}~yt        j                  t        «       Y d}~nd}~ww xY wŒ·)zbWindows counterpart of ``_acquire_db_flock`` (no orphan break); same True / False / None contract.r   NTrM   uS   Could not acquire FTS rebuild lock %s (%s) â€” deferring on a non-contention error.F)Úmsvcrtró   r  rÝ   Úlockingr  ÚLK_NBLCKrË   r˜   rÎ   r	  r
  r  Ú_FTS_REBUILD_LOCK_POLL_SECONDS)r  rã   Útimeoutr  r  rÉ   s         r   Ú_acquire_msvcrt_lockr$  –  s¡   € ãÜ�~‰~Ó 'Ñ)€HØ
ð	7Ø�K‰K˜ŒNØ�N‰N˜6Ÿ=™=›?¨F¯O©O¸QÔ?ØøÜ¤Ð)ò 	7Ü.¨sÔ3Ü—‘ÐtØ(¨#ô/äÜ�~‰~Ó 8Ò+ÜÜ�J‰JÔ5×6Ñ6ûð	7úð s#   ž<A ÁCÁ*"CÂCÂ-CÃC)r  c             #   ój  K  — | €d–— y|€t         nt        t        |«      d«      }| › d�}	 t        |d«      }d}	 t        rt        |||«      }nt        |||t        d«      \  }}|€d}n\|sZt        rdn
t        |«      }|d	k  r!t
        j                  d
|t        |«      «       n!t
        j                  d||t        |«      «       |–— 	 t        j                  t        «      5  |rGt        rAd	dl}|j#                  d	«       |j%                  |j'                  «       |j(                  d«       n;|r9d	dl}	t-        |«       |	j/                  |j'                  «       |	j0                  «       ddd«       |j3                  «        y# t        $ r%}t
        j                  d||«       d–— Y d}~yd}~ww xY w# 1 sw Y   ŒKxY w# |j3                  «        w xY w# 	 t        j                  t        «      5  |rGt        rAd	dl}|j#                  d	«       |j%                  |j'                  «       |j(                  d«       n;|r9d	dl}	t-        |«       |	j/                  |j'                  «       |	j0                  «       ddd«       n# 1 sw Y   nxY w|j3                  «        w # |j3                  «        w xY wxY w­w)aÙ  Serialize full structural FTS rebuilds on *db_path* across processes.  Yields True when this process
    holds the authority, False when the bounded acquire timed out or the lock file could not be opened: the
    caller must NOT rebuild (fail closed; the stale breadcrumb guarantees a retry).  ``db_path`` None
    (in-memory) yields True.  In-process retries pass ``timeout_seconds=0`` so a live holder never stalls a
    long-lived writer; the orphan break still applies.NTg        z.fts_rebuild.lockr  uz   Could not open FTS rebuild lock %s (%s) â€” deferring this rebuild rather than running it without cross-process authority.FzFTS rebuild lockr   ux   FTS rebuild lock %s is busy â€” deferring this retry (the stale-FTS breadcrumb keeps it retryable). Recorded holder: %s.u¹   FTS rebuild lock %s held by another process for more than %.0fs â€” deferring this rebuild to avoid racing the holder (the stale-FTS breadcrumb keeps it retryable). Recorded holder: %s.rM   )Ú!_FTS_REBUILD_LOCK_TIMEOUT_SECONDSÚmaxr  rÔ   r˜   r	  r
  Ú_IS_WINDOWSr$  r  r"  rå   Úinfor  rè   ré   r  rÝ   r   r  ÚLK_UNLCKr  rø   r  ÚLOCK_UNr  )
Údb_pathr  r#  r  rã   rÉ   Úacquiredrä   r  r  s
             r   Úfts_rebuild_admissionr.  ©  sV  è ø€ ð €ØŠ
ØØ3BÐ3JÕ/ÔPSÔTYÐZiÓTjÐloÓPp€GØ�)Ð,Ð-€Ið	Ü�i Ó'ˆð €Hð!ÝÜ+¨I°v¸wÓG‰Hä0Ø˜6 7Ô,JÐL^ó `ÑˆH�fàÐà‰HÙÝ(‘TÔ.FÀvÓ.NˆFØ˜!Š|ä—‘ð bà%Ô'<¸VÓ'DõFô —‘ð  BàCLÈgÔWlÐmsÓWtôvð Šð	Ü×$Ñ$¤WÓ-ñ @Ù¥Û!Ø—K‘K ”NØ—N‘N 6§=¡=£?°F·O±OÀQÕGÙÛ Ü-¨fÔ5Ø—K‘K §¡£°·±Ô?÷@ð �L‰L�NøôU ò ô 	�‰ð QØR[Ð]`ô	bàŠÜûðú÷@@ð @ûð �L‰L�Nûð	Ü×$Ñ$¤WÓ-ñ @Ù¥Û!Ø—K‘K ”NØ—N‘N 6§=¡=£?°F·O±OÀQÕGÙÛ Ü-¨fÔ5Ø—K‘K §¡£°·±Ô?÷@÷ @ñ @úð �L‰L�NøˆF�L‰L�Nÿs¡   ‚*J3­F ¹J3¼BG ÃG  Ã%BF4Å*G  Å2J3Æ	F1ÆF,Æ'J3Æ,F1Æ1J3Æ4F=Æ9G  Ç GÇJ3ÇJ0ÇJÇ0BI>É5	JÉ>JÊJÊ
J0ÊJ-Ê-J0Ê0J3)rz   )r   ztuple[int, int] | None)rN   )r   N)‚Ú__doc__rè   rÌ   rÞ   Úloggingr   Úsysró   Útypingr   Úagent.skill_commandsr   r   r   Úagent.context_compressorr   r   r	   r
   r   ÚTITLE_SOURCE_DERIVEDÚTITLE_SOURCE_LLMÚTITLE_SOURCE_USERÚ_PREVIEW_HEAD_CHARSÚ_PREVIEW_SCAFFOLD_WINDOWrY   rV   r   r(   Ú_PREVIEW_CONTENT_SQLÚ_PREVIEW_SCAFFOLDED_SQLr6   r,   r3   r8   r:   ÚtuplerI   rL   rX   Ú_PREVIEW_LONG_FORM_PREFIXÚ_PREVIEW_SUMMARY_PREFIXESÚ_PREVIEW_STANDALONE_SUMMARY_SQLÚ_PREVIEW_MERGED_AFTER_SQLÚ_PREVIEW_MERGED_SUMMARY_SQLÚ_PREVIEW_MERGED_PRIOR_SQLÚ"_PREVIEW_MERGED_PRIOR_LTRIMMED_SQLrC   Ú#_PREVIEW_MERGED_PRIOR_UNWRAPPED_SQLÚ!_PREVIEW_FORCE_USER_REMAINDER_SQLÚ_PREVIEW_ELIGIBLE_SQLrH   Ú_PREVIEW_RAW_SELECTr[   Ú_PREVIEW_RAW_SUBQUERY_SQLr~   r€   Ú_RESET_END_REASONSÚ_RESET_END_REASONS_SQLÚ	frozensetÚ_BOUNDARY_END_REASONSÚ_RECOVERABLE_END_REASONSÚ_RECOVERABLE_END_REASONS_SQLrt   Úboolru   ry   r�   r   Ú_LISTABLE_CHILD_SQLrƒ   r‰   rŒ   rŽ   ÚSCHEMA_VERSIONÚAUTO_VACUUM_MIN_FREELIST_RATIOÚFTS_STORAGE_VERSIONr’   r“   Ú_FTS_NEW_INDEXED_CONTENT_SQLÚ_FTS_OLD_INDEXED_CONTENT_SQLÚMAX_FTS5_QUERY_CHARSrœ   Ú_ENDED_ROW_SQLÚ_COMPRESSION_LOCK_ROW_SQLr¢   rª   Ú_SQL_IN_CHUNKr¨   r°   Ú_FTS_TRIGGERSÚ
SCHEMA_SQLÚDEFERRED_INDEX_SQLÚFTS_SQLr¾   rÀ   ÚFTS_TRIGRAM_SESSION_SQLÚFTS_TRIGRAM_SQLÚ_FTS_CJK_TRIGGERSÚFTS_CJK_STALE_KEYÚFTS_STALE_KEYÚFTS_REBUILD_DEFERRAL_KEYÚLEGACY_FTS_SQLÚLEGACY_FTS_TRIGRAM_SQLÚ	getLoggerr	  r&  r"  Úplatformr(  r  ÚEAGAINÚEACCESÚEWOULDBLOCKÚEDEADLKrÍ   ÚBaseExceptionrÎ   rÚ   rå   Úbytesrí   rö   rø   r   r  r  r$  Úcontextmanagerr.  rd   r)   r   ú<module>ro     s	  ðñ4ó Û Û Û Û 	Û 
Û Ý ç hÑ h÷4õ 4ð
 !Ð ØÐ ØÐ ð Ð ØÐ ØÐ ð# ð #¨sð #°só #ð(N�cð N˜có Nð MÐ Ø,Ð-DÐ,EÀQÐGÐ Ø?€ð/�sð /˜só /ð> #ð >¨Sð >°Só >ð5 cð 5¨có 5ð4 Sð 4¨Só 4ðl ð l°°c¸3°h±ð lÀCó lð
Z˜cð Z có Zð 1˜N×0Ñ0°À!ÓDÀQÑGÐ Ø6Ð8MÐNÐ Ù"2°;Ð@YÓ"ZÐ Ù-Ð.GÓHÐ Ø!3±LÐAZÓ4[Ð3\ð ]ÙÐ6Ð8QÓRÐSÐSTð VÐ á0Ø,©\Ð:SÓ-TÐ,UÐU[Ð\ó^Ð á%:Ð;TÓ%UÐ "Ø):Ð;]Ð:^ð _ÙÐ(Ó)Ð*¨$©|Ð<XÓ/YÐ.ZØÑ" WÐ-OÐ,PÐPRÑSVÐWsÓStÐwxÑSxÐRyÐyzÐ#{Ó|Ð}ØÐ&Ð' tð(-Ð #ñ %6Ð6IÓ$JÐ !ðØ1Ð2°)Ð<WÐ;Xð YØ+Ð,Ð,BÁ<ÐPcÓCdÐBeð fÙ'Ð(IÓJÐKð LØ'Ð(ØÑ'Ð(KÓLÐMÈXðWÐ ÷Jó J€jð JÐ0Ð1ð J°ð JÐ8YÐ7Zð JØðJØ(Ð)ðJØ)/ðJØ0SÐ/TðJàðJà$Ð%ðJà%>ðJà?WÐZ[Ñ?[Ð>\ðJð ðJð )Ð)ðJð */ðJð 0HÐ.HðJð IOðJð PcÐNcðJðdðJð 'Ð'ð	Jð (+ð	Jð ,DÐ*Dð	JðEð	Jð
 %Ð%ðJð
 &3ðJð
 4HÐ2HðJð
 INðJð
 OgÐijÑNjÐMkðJð
lðJð )Ð)ðJð */ðJð 0CÐ.CðJð DIôJð ðY˜ð Y ó Yð  1Ð1DÐ0Eð FTØTiÐSjØ?ðAÐ ñ *Ð*<Ð>PÓQÐRð S]ð ]Ð ð0Ð ð
 qÐ ØŸ™Ñ"RÐ?QÔ"RÓRÐ ñ "Ð"4Ó5¸¸ÑGÐ ð lÐ ð  $Ÿy™yÑ(^ÐE]Ô(^Ó^Ð ñ #Ð#;Ó<ò @Bñ BÐ ðH tó Hðj 3ð j°Sð j¸Só jñ )Ð);¸_ÓMÐNð Oð Ù$ UÐ,BÓCñDÐ ð :Ð:K×:RÑ:RÐUXÐ:RÓ:YÐ9ZØ
Ð×"Ñ" SÐ"Ó)Ð
*¨!ð-Ð ñn ð n¨có nð˜sð °Sð À3ð È3ó ñ_ Cð _°#ó _ð
Y°Cð Y¸Có Yð €ð "&Ð ð  Ð ð !&Ð ð% Cð %¨Có %ñ  8¸Ó>Ð Ù7¸Ó>Ð ð Ð óGð J€ØcÐ ðb $ó bðY˜Có Yð €ð !.ñ &˜#ó &ðn€ð}€
ð@]Ð ðx7ð 8UÐ6Uð V	ð, 
&Ð&ð '	ð  
&Ð&ð '	ð0 
&Ð&ð '	ð 
&Ð&ð 'ðYQ€ðV  4Ð ñ
 3ð 
°ó 
ñ 2Ó3Ð ð ñ
 !8¸Ó <Ð=ð >.ð /FÐ-Fð G.ð /FÐ-Fð G1ð0 2IÐ0Ið J1ð
 2IÐ0Ið Jðy>€ð@ fÐ ð $Ð ð €ð 2Ð ðð /Ð/ð 0ð /Ð/ð 0ð-€ð:	ð /Ð/ð 0ð /Ð/ð 0ð/Ð ðx 
ˆ×	Ñ	˜>Ó	*€à$)Ð !Ø!$Ð Ø�l‰l˜gÑ%€à #Ð ð !Ÿ<™<¨¯©°u×7HÑ7HÈ%Ï-É-ÐXÐ ðs ]ð s°tó sð
˜3ó ò8ð¨ð °$ó óSó$ð
I¨$ó Iò,=!ð@+ Só +ò7ð& ×ÒØ6:ó 7ó ñ7r)   