Ë
    ãß²jà   ã                   ó  — U d Z ddlZddlZddlZ G d„ d«      Z ej
                  «       Zi Zee	ef   e
d<   dZdZdefd	„Zd
edededefd„Zde	defd„Zde	dz  de	fd„Zde	fd„Zej&                  dde	dz  fd„«       Zdde	dz  defd„Zy)u&  Human-wait accounting for :mod:`tools.approval` (per session).

Tracks wall-clock time the agent spends verifiably blocked on a HUMAN prompt
(CLI approval prompt, gateway approval round-trip). The concurrent tool batch
deadline in agent/tool_executor.py excludes this time so a slow human answer
never times a batch out â€” but ONLY this time. Measuring at the source (rather
than residency in the authorization gate, which is arbitrary code) is what keeps
a wedged pre_tool_call plugin or a dead approval client from growing the
exclusion 1:1 with wall clock and defeating the deadline entirely. Keyed by
session so one gateway session's pending approval cannot extend a different
session's batch deadline; state is process-global like the rest of the approval
state, bounded by _HUMAN_WAIT_MAX_SESSIONS.
é    Nc                   ó   — e Zd ZdZdd„Zy)Ú_HumanWaitState©ÚpendingÚwindow_startedÚcompleted_secondsNc                 ó.   — d| _         d | _        d| _        y )Nr   ç        r   )Úselfs    ú>/home/ubuntu/.hermes/hermes-agent/tools/approval_human_wait.pyÚ__init__z_HumanWaitState.__init__!   s   € ØˆŒØ,0ˆÔØ!$ˆÕó    )ÚreturnN)Ú__name__Ú
__module__Ú__qualname__Ú	__slots__r   © r   r   r   r      s   „ ØB€Iô%r   r   Ú_human_wait_statesé   g      N@r   c                  óN   — ddl m}  t        | j                  «       «      t        z   S )uÁ  Max seconds a single window may contribute: approvals.timeout + margin.
    Every legitimate human wait self-terminates at ``approvals.timeout`` (the CLI
    prompt join and the gateway poll loop both enforce it), so a window that
    overstays this ceiling is itself wedged and must not keep extending a batch
    deadline. Also the bound on the authorization gate's serialization-lock
    acquire in agent/tool_executor.py, so the two cannot drift. Never call while
    holding ``_human_wait_lock`` â€” it reads the config cache.
    ``_get_approval_timeout`` caps at ``agent.deadline.MAX_SAFE_TIMEOUT_S`` so the
    value is always safe for ``Lock.acquire(timeout=...)`` / ``Thread.join(timeout=...)``.r   ©Úapproval_context)Útoolsr   ÚfloatÚ_get_approval_timeoutÚHUMAN_WAIT_MARGIN_Sr   s    r   Úhuman_wait_ceilingr   0   s"   € õ 'ÜÐ!×7Ñ7Ó9Ó:Ô=PÑPÐPr   ÚstartedÚnowÚceilingc                 ó4   — t        t        d|| z
  «      |«      S )z¶Seconds an open window contributes: elapsed, floored at 0, capped. Shared
    by the close-time accrual and the open-window read so the two clamps stay
    identical by construction.r
   )ÚminÚmax)r   r    r!   s      r   Ú_clamped_window_secondsr%   >   s   € ô Œs�3˜˜g™Ó&¨Ó0Ð0r   Úsession_keyc                 óî   — t         j                  | «      }|€]t        t         «      D ]8  }t        t         «      t        k  r n t         |   j
                  dk(  sŒ2t         |= Œ: t        «       x}t         | <   |S )a´  Return (creating if needed) the wait state for *session_key*. Caller must
    hold ``_human_wait_lock``. Evicts idle entries (no pending waiter)
    insertion-order-first until the table is under the cap so an army of
    short-lived session keys cannot grow it without bound. Entries with an open
    window are never evicted (that would corrupt live accounting), so the cap is
    best-effort under 256+ concurrently-pending sessions.r   )r   ÚgetÚlistÚlenÚ_HUMAN_WAIT_MAX_SESSIONSr   r   )r&   ÚstateÚkeys      r   Ú_human_wait_stater.   E   sv   € ô ×"Ñ" ;Ó/€EØ€}ÜÔ*Ó+ò 	,ˆCÜÔ%Ó&Ô)AÒAÙÜ! #Ñ&×.Ñ.°!Ó3Ü& sÑ+ð		,ô
 3BÓ2CÐCˆÔ" ;Ñ/Ø€Lr   c                 ó6   — | �| S ddl m} |j                  «       S )Nr   r   )r   r   Úget_current_session_key)r&   r   s     r   Ú_resolve_keyr1   W   s    € ØÐØÐÝ&Ø×3Ñ3Ó5Ð5r   Úlabelc                 óz   ‡ ‡‡— 	 ddl mŠ t        j                  «       }||dœŠˆ ˆˆfd„S # t        $ r d„ cY S w xY w)zéCallable that pings the agent's inactivity tracker (at most every ~10s)
    while a human wait is parked, so the gateway watchdog does not kill the agent
    while the user is still answering. No-op in minimal tool-only environments.r   )Útouch_activity_if_duec                   ó   — y ©Nr   r   r   r   ú<lambda>z$activity_heartbeat.<locals>.<lambda>e   s   � r   )Ú
last_touchÚstartc                  ó   •—  ‰‰‰ «      S r6   r   )r2   r,   r4   s   €€€r   r7   z$activity_heartbeat.<locals>.<lambda>h   s   ø€ Ñ(¨°Ó6€ r   )Útools.environments.baser4   Ú	ExceptionÚtimeÚ	monotonic)r2   r    r,   r4   s   ` @@r   Úactivity_heartbeatr?   ^   s@   ú€ ðÝAô �.‰.Ó
€CØ¨Ñ-€EÝ6Ð6øô	 ò ÙÒðús   …+ «:¹:c           
   #   óÄ  K  — t        | «      }t        j                  «       }t        5  t	        |«      }|j
                  dk(  r||_        |xj
                  dz  c_        ddd«       	 d–— t        j                  «       }t        «       }t        5  t        j                  |«      }|�a|xj
                  dz  c_        |j
                  dk(  r=|j                  �*|xj                  t        |j                  ||«      z  c_
        d|_        ddd«       y# 1 sw Y   Œ´xY w# 1 sw Y   yxY w# t        j                  «       }t        «       }t        5  t        j                  |«      }|�a|xj
                  dz  c_        |j
                  dk(  r=|j                  �*|xj                  t        |j                  ||«      z  c_
        d|_        ddd«       w # 1 sw Y   w xY wxY w­w)a  Mark the enclosed block as time spent blocked on a human prompt. Wrap ONLY
    code that is genuinely parked waiting for a user's answer (the CLI approval
    prompt, the gateway approval poll loop). The concurrent tool batch deadline
    excludes this time; wrapping anything else re-creates the hang where
    arbitrary wedged code pushes the deadline out forever. Overlapping windows
    for the same session coalesce (pending counter), so two serialized approval
    prompts don't double-count the same wall clock.

    See #79719.
    r   é   N)r1   r=   r>   Ú_human_wait_lockr.   r   r   r   r   r(   r   r%   )r&   r-   r    r,   r!   s        r   Úhuman_wait_windowrC   k   sš  è ø€ ô �{Ó
#€CÜ
�.‰.Ó
€CÜ	ñ Ü! #Ó&ˆØ�=‰=˜AÒØ#&ˆEÔ Ø�Š˜Ñ�÷	ð
0Ûä�n‰nÓˆô %Ó&ˆÜñ 	0Ü&×*Ñ*¨3Ó/ˆEØÐ Ø—’ Ñ"•Ø—=‘= AÒ%Ø×+Ñ+Ð7Ø×/Ò/Ô3JÈ5×K_ÑK_ÐadÐfmÓ3nÑnÕ/Ø+/�EÔ(÷	0ð 	0÷ð ú÷	0ð 	0ûô	 �n‰nÓˆô %Ó&ˆÜñ 	0Ü&×*Ñ*¨3Ó/ˆEØÐ Ø—’ Ñ"•Ø—=‘= AÒ%Ø×+Ñ+Ð7Ø×/Ò/Ô3JÈ5×K_ÑK_ÐadÐfmÓ3nÑnÕ/Ø+/�EÔ(÷	0÷ 	0ñ 	0ÿsf   ‚&G ¨7DÁG Á(D* Á,$G ÂA9DÄ		G ÄDÄG ÄD'Ä#G Ä*%GÅA9GÇ	GÇGÇGÇG c                 ó6  — t        | «      }t        j                  «       }t        «       }t        5  t
        j                  |«      }|€
	 ddd«       y|j                  }|j                  �|t        |j                  ||«      z  }|cddd«       S # 1 sw Y   yxY w)uš  Return total human-wait seconds recorded for the session: completed windows
    plus the currently open one (if any). Monotonically non-decreasing for the
    life of the process â€” except when an idle session's entry is evicted under
    cap pressure, which can only shrink a consumer's baseline delta to zero (the
    safe direction: the deadline fires sooner). Deadline consumers snapshot a
    baseline at batch start and use the delta. Each window's contribution is
    clamped to :func:`human_wait_ceiling` (belt-and-braces against the
    wedged-window hang).

    Each window's contribution is clamped to :func:`human_wait_ceiling`: every legitimate human wait
    self-terminates at ``approvals.timeout`` (both the CLI prompt join and the gateway poll loop enforce
    it), so a window that overstays that bound is itself wedged and must not keep extending a batch deadline
    (belt-and-braces for #79719).
    Nr
   )
r1   r=   r>   r   rB   r   r(   r   r   r%   )r&   r-   r    r!   r,   Útotals         r   Úhuman_wait_secondsrF   �   s’   € ô �{Ó
#€CÜ
�.‰.Ó
€Cä Ó"€GÜ	ñ Ü"×&Ñ& sÓ+ˆØˆ=Ø÷ð ð ×'Ñ'ˆØ×ÑÐ+ØÔ,¨U×-AÑ-AÀ3ÈÓPÑPˆEØ÷÷ ò ús   °BÁ3BÂBr6   )Ú__doc__Ú
contextlibÚ	threadingr=   r   ÚLockrB   r   ÚdictÚstrÚ__annotations__r+   r   r   r   r%   r.   r1   r?   ÚcontextmanagerrC   rF   r   r   r   ú<module>rO      sõ   ðòó Û Û ÷%ñ %ð "�9—>‘>Ó#Ð Ø13Ð �D˜˜oÐ-Ñ.Ó 3ØÐ ð Ð ðQ˜Eó Qð1 Uð 1°ð 1Àð 1È5ó 1ð 3ð ¨?ó ð$6˜c D™jð 6¨Só 6ð
7˜có 
7ð ×Ññ 0 3¨¡:ò  0ó ð 0ñF C¨$¡Jð ¸%ô r   