Ë
    áß²jCF  ã                   ó  — d Z ddlZddlZddlZddlmZ ddlmZ ddlm	Z
 ddlmZ  ej                  d«      ZdZd	Zd
Z eeeeh«      Zdee   fd„Zdededdfd„Zdefd„Zdedefd„Zdefd„Zdededdfd„Zdeddfd„Z G d„ d«      Zy)zûInterrupt / steer / redirect control surface for ``AIAgent``.

Soft/hard interrupt requests, tool-thread interrupt propagation, pending steer/redirect queues.
Extracted from ``run_agent.py``; every method resolves through ``AIAgent``'s MRO unchanged.
é    N)ÚOptional)Úrequest_hard_interrupt)Úrequest_yield)Úset_interruptÚ	run_agentzexplicit stop requestedzuser sent a new messagezuser interruptÚreturnc                 ó„   — t        | dd«      }|r|t        v ryt        |«      j                  «       j	                  dd«      S )zWSlug of the system producer behind the pending interrupt, or ``None`` for a human stop.Ú_tool_interrupt_reasonNÚ Ú_)ÚgetattrÚUSER_INTERRUPT_REASONSÚstrÚstripÚreplace)ÚagentÚreasons     ú</home/ubuntu/.hermes/hermes-agent/agent/interrupt_control.pyÚinterrupt_issuerr      s?   € ä�UÐ4°dÓ;€FÙ�VÔ5Ñ5ØÜˆv‹;×ÑÓ×&Ñ& s¨CÓ0Ð0ó    Úwhen_in_flightÚfailure_logc                óÜ   — | �t        t        | dd«      «      |uryt        t        | «      dd«      }t        |«      r
	  || «       yy# t        $ r t
        j                  |d¬«       Y yw xY w)u9  Call ``type(fence).cancel_before_commit(fence)`` when ``commit_in_flight`` matches.

    Hard-cancel admission has two halves (#99758 P1). BEFORE the generation claim is
    consumed only a commit already in flight is waited out (the call blocks on the fence
    lock and returns False WITHOUT setting ``_cancelled``) â€” cancelling a still-pending
    fence there would be irreversible for an abort that may yet be declined. AFTER the
    claim survived, only a still-pending commit is cancelled; one that started meanwhile
    owns the fence and completes on its own.NÚcommit_in_flightFÚcancel_before_commitT©Úexc_info)Úboolr   ÚtypeÚcallableÚ	ExceptionÚloggerÚdebug)Úfencer   r   r   s       r   Ú_fence_cancel_before_commitr%   #   sr   € ð €}œœW UÐ,>ÀÓFÓGÈ~Ñ]ØÜ"¤4¨£;Ð0FÈÓMÐÜÐ$Ô%ð	5Ù  Õ'ð &øô ò 	5Ü�L‰L˜¨tˆLÖ4ð	5ús   ¾A Á A+Á*A+Úattrc                 óL   — t        | |d«      }|€t        j                  «       S |S )zY``with`` the lock stored at ``attr`` when present; __init__-less test stubs run unlocked.N)r   Ú
contextlibÚnullcontext)r   r&   Úlocks      r   Ú_ic_lockr+   6   s(   € ä�5˜$ Ó%€DØ'+ |Œ:×!Ñ!Ó#Ð=¸Ð=r   Ú	lock_attrÚslotc                 óN   — t        | |d«      €t        | |d«      S t        | |«      S )a  Read the pending-text ``slot`` guarded by ``lock_attr``. An initialized agent always has both
    attributes, so under the lock the slot is read directly and a missing one fails loud (a real bug);
    only ``__init__``-less test stubs (no lock) get the ``getattr`` fallback.N)r   )r   r,   r-   s      r   Ú_ic_slotr/   <   s/   € ô ˆu�i Ó&Ð.Ü�u˜d DÓ)Ð)Ü�5˜$ÓÐr   Únamec                 ór   — t        | dd«      dk7  ryt        t        | dd«      |d«      }t        |«      r|S dS )zLCodex app-server owns its model/tool loop; return its ``name`` hook or None.Úapi_modeNÚcodex_app_serverÚ_codex_session)r   r    )r   r0   Úmethods      r   Ú_ic_codex_methodr6   E   sA   € äˆu�j $Ó'Ð+=Ò=ØÜ”W˜UÐ$4°dÓ;¸TÀ4ÓH€FÜ˜fÔ%ˆ6Ð/¨4Ð/r   r   c                 ó”   — t        | dd«      }t        |«      r
	  ||«       yy# t        $ r t        j	                  |d¬«       Y yw xY w)zXShut the registered in-flight request's sockets (cron turns register their client here).Ú_active_request_abortNTr   )r   r    r!   r"   r#   )r   r   r   Úaborts       r   Ú_ic_abort_active_requestr:   M   sJ   € ä�EÐ2°DÓ9€EÜ�„ð	5Ù�&�Mð øô ò 	5Ü�L‰L˜¨tˆLÖ4ð	5ús   š$ ¤ AÁAÚactivec                 óÎ   — t        | dd«      }t        | dd«      }|�|€y|5  t        |«      }ddd«       D ]  }	 t        ||fi |¤Ž Œ y# 1 sw Y   ŒxY w# t        $ r Y Œ+w xY w)a-  Fan the tool interrupt bit out to concurrent-tool worker tids.

    ``is_interrupted()`` inside a tool only sees its own tid, so without this a hung
    concurrent tool runs to its own timeout (and a stale bit could survive a turn
    boundary onto a recycled tid). getattr covers __init__-less stubs.Ú_tool_worker_threadsNÚ_tool_worker_threads_lock)r   ÚlistÚ_set_interruptr!   )r   r;   ÚkwÚtrackerÚtracker_lockÚworker_tidsÚtids          r   Ú_ic_signal_tool_workersrF   W   s†   € ô �eÐ3°TÓ:€GÜ˜5Ð"=¸tÓD€LØ€˜,Ð.ØØ	ñ $Ü˜7“mˆ÷$àò ˆð	Ü˜6 3Ñ-¨"Ó-ñ÷$ð $ûô
 ò 	Ùð	ús   ¢A¼AÁAÁ	A$Á#A$c                   óà   — e Zd ZdZ	 dddddœdee   dedee   dee   d	ef
d
„Zdddœdee   dee   d	dfd„Z	dddœ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fd„Zd	ee   fd„Zd	ee   fd„Zy)ÚInterruptControlMixinzYinterrupt()/hard_interrupt()/clear_interrupt()/steer()/redirect() (see module docstring).NF)Úhard_cancelÚtool_reasonÚrequire_generationÚmessagerI   rJ   rK   r   c                óò  ‡ ‡‡‡— |�:‰ j                  «       5  t        ‰ dd«      |k7  r
	 ddd«       y|‰ _        ddd«       ‰r
|xs t        n‰rt        nt
        Šdˆˆˆ ˆfd„}ˆˆ fd„}t        ‰ d«      5  t         |«       dd	¬
«       |€ |«        nI‰ j                  «       5  t        ‰ dd«      |k7  r	 ddd«       ddd«       yd‰ _         |«        ddd«       t         |«       dd¬
«       d‰ _        ddd«       t        ‰ d«      }|�	  |«        t        ‰ dd«       ‰ j                  � t        d‰ j                  ‰¬«       d‰ _        nd‰ _        t#        ‰ d‰¬«       ‰ j$                  5  t'        ‰ j(                  «      }ddd«       D ]%  }		 ‰rt+        |	‰‰¬«       n|	j-                  ‰«       Œ' ‰ j.                  s/t1        d‰rt3        ‰«      dkD  r	d‰dd › d�n	‰rd‰› d�ndz   «       y# 1 sw Y   �Œ¼xY w# 1 sw Y   �Œ-xY w# 1 sw Y   �ŒxY w# t        $ r t        j                  dd¬«       Y �Œ(w xY w# 1 sw Y   Œ½xY w# t        $ r }
t        j                  d|
«       Y d}
~
Œâd}
~
ww xY w)uÚ  Request the agent to interrupt its current tool-calling loop (call from another thread).

        ``hard_cancel``: explicit stop; compression may honor it even while ordinary interrupts are masked.
        ``tool_reason``: trusted fixed category safe for tool output. ``require_generation``: activity-
        generation claim â€” published only if the turn's generation still matches at the final mutation edge;
        returns False if the turn resumed meanwhile.
        NÚ"_turn_liveness_activity_generationr   Fc                  ó®   •— d‰_         ‰‰_        ‰‰_        t        j	                  d‰rdnd‰«       ‰rt        ‰dd «      nd } | �| j                  «        y y )NTzInterrupt requested (%s): %sÚhardÚsoftÚ_hard_interrupt_requested)Ú_interrupt_requestedÚ_interrupt_messager
   r"   Úinfor   Úset)Ú_hard_eventrI   rL   ÚselfÚtool_interrupt_reasons    €€€€r   Ú_publish_interrupt_statezAInterruptControlMixin.interrupt.<locals>._publish_interrupt_state‡   s[   ø€ Ø(,ˆDÔ%Ø&-ˆDÔ#Ø*?ˆDÔ'ä�K‰KÐ6Á+¹ÐSYÐ[pÔqÙNYœ' $Ð(CÀTÔJÐ_cˆKØÐ&Ø—‘Õ!ð 'r   c                  ó@   •— ‰ rt        ‰«      j                  d«      S d S )NÚ _active_compression_commit_fence)ÚvarsÚget)rI   rX   s   €€r   Ú_fencez/InterruptControlMixin.interrupt.<locals>._fence‘   s   ø€ ÙIT”4˜“:—>‘>Ð"DÓEÐ^ÐZ^Ð^r   Ú_pending_redirect_lockTz)Compression hard-cancel fence wait failed)r   r   Ú_turn_liveness_abort_claimz.Compression hard-cancel fence admission failedÚrequest_interruptz)Failed to interrupt Codex app-server turnr   Úinterrupt_abortz%Failed to abort active inline request)r   ©rJ   z0Failed to propagate interrupt to child agent: %su   
âš¡ Interrupt requestedé(   z: 'z...'ú'Ú )r   N)Ú_liveness_activity_lockr   ra   Ú_REASON_HARD_STOPÚ_REASON_NEW_MESSAGEÚ_REASON_USER_INTERRUPTr+   r%   Ú_pending_redirectr6   r!   r"   r#   r:   Ú_execution_thread_idr@   Ú _interrupt_thread_signal_pendingrF   Ú_active_children_lockr?   Ú_active_childrenr   Ú	interruptÚ
quiet_modeÚprintÚlen)rX   rL   rI   rJ   rK   rZ   r_   Ú_request_interruptÚchildren_copyÚchildÚerY   s   ```        @r   rq   zInterruptControlMixin.interruptm   sÃ  û€ ð Ð)ð ×-Ñ-Ó/ñ EÜ˜4Ð!EÀqÓIÐM_Ò_Ø ÷Eð Eð 3E�Ô/÷Eñ 3>ˆ[Ò-Õ-Ù)0Õ%Ô6Lð 	÷
	"ð 	"õ	_ô �dÐ4Ó5ñ 	*Ü'Ù“¨Ð;fõð "Ð)ñ )Õ*ð
 ×1Ñ1Ó3ñ /Ü˜tÐ%AÀ4ÓHÐL^Ò^Ø$÷/÷	*ð 	*ð 7;�DÔ3Ù,Ô.÷	/ô
 (Ù“¨Ð<lõð &*ˆDÔ"÷)	*ô. .¨dÐ4GÓHÐØÐ)ðYÙ"Ô$ô 	! Ð'8Ð:aÔbà×$Ñ$Ð0Ü˜4 ×!:Ñ!:ÐCXÕYØ49ˆDÕ1ð 59ˆDÔ1Ü  dÐ3HÕIà×'Ñ'ñ 	8Ü  ×!6Ñ!6Ó7ˆM÷	8à"ò 	TˆEðTÙÜ*¨5°'ÐG\Ö]à—O‘O GÔ,øð	Tð �ŠÜÐ-ÉWÔY\Ð]dÓYeÐhjÒYj°3°w¸sÀ°|°nÀDÑ1Iñ  ELÐruÐv}Ðu~Ð~ñ  qAð  RTñ  Uô  VØ÷iEñ Eú÷R/ñ /ú÷	*ñ 	*ûô6 ò YÜ—‘ÐHÐSW�×XðYú÷ 	8ð 	8ûô ò TÜ—‘ÐOÐQR×SÑSûðTúsv   —G3²G3Á6.HÂ$H Â6HÃH Ã"HÄH Å/IÆ"IÇ3G=È H
	ÈHÈHÈ H>È=H>ÉI
É	I6ÉI1É1I6rd   c                ó6   — t         j                  | |d|¬«       y)a  Explicit stop preserving the ``interrupt()`` ABI (frontends feature-detect this and fall back to
        legacy ``interrupt()`` for third-party agents). Bypasses dynamic dispatch: legacy subclasses may
        override interrupt(message=None) without hard_cancel.T)rI   rJ   N)rH   rq   )rX   rL   rJ   s      r   Úhard_interruptz$InterruptControlMixin.hard_interruptÑ   s   € ô 	×'Ñ'¨¨gÀ4ÐU`Ð'Õar   )Úpreserve_redirectrI   r{   c                óÎ  — t        | d«      5  |rt        | dd«      s
	 ddd«       yd| _        dx| _        | _        t        | dt        j                  «       «      j                  «        |sd| _	        ddd«       d| _
        | j                  �t        d| j                  «       t        | d«       |rt        | d«      5  d| _        ddd«       yy# 1 sw Y   Œ^xY w# 1 sw Y   yxY w)u  Clear the interrupt request and per-thread tool signal. ``preserve_redirect`` is only for the
        conversation loop rebuilding the same logical turn after cancelling a model request.
        ``hard_cancel`` also drops an unconsumed pending steer: the aborted turn's next tool iteration
        will no longer happen, and re-injecting the note into the post-stop turn would surprise the
        user. Every other caller (redirect rebuild, error recovery, turn-boundary hygiene) continues
        this session, so the already-accepted steer must survive: it stays buffered for the existing
        drains â€” the pre-API inject, the post-batch append, or the finalizer's leftover handoff â€”
        instead of silently vanishing after the surface was told it was delivered.r`   rl   NFrR   Ú_pending_steer_lockT)r+   r/   rS   rT   r
   r   Ú	threadingÚEventÚclearrl   rn   rm   r@   rF   Ú_pending_steer)rX   r{   rI   s      r   Úclear_interruptz%InterruptControlMixin.clear_interrupt×   sá   € ô �dÐ4Ó5ñ 	.Ù ¬°$Ð8PÐReÔ)fØ÷	.ð 	.ð ).ˆDÔ%ØDHÐHˆDÔ# dÔ&AÜ�DÐ5´y·±Ó7HÓI×OÑOÔQÙ$Ø)-�Ô&÷	.ð 16ˆÔ-Ø×$Ñ$Ð0Ü˜5 $×";Ñ";Ô<Ü  eÔ,Ùô ˜$Ð 5Ó6ñ +Ø&*�Ô#÷+á÷%	.ð 	.ú÷ +àús   �C§ACÂ=CÃCÃC$Útextc                 óÈ   — |r|j                  «       sy|j                  «       }t        | d«      5  t        | dd«      }|r|dz   |z   n|| _        ddd«       y# 1 sw Y   yxY w)z¶Queue user text for delivery as its own user row after the current tool batch finishes (no
        interrupt); multiple calls concatenate with newlines. Returns False for empty text.Fr}   r�   Ú
NT)r   r+   r/   r�   )rX   rƒ   ÚcleanedÚexistings       r   ÚsteerzInterruptControlMixin.steerô   sj   € ñ ˜4Ÿ:™:œ<ØØ—*‘*“,ˆÜ�dÐ1Ó2ñ 	WÜ Ð&;Ð=MÓNˆHÙAI 8¨d¡?°WÒ#<ÈwˆDÔ÷	Wð ÷	Wð ús   °AÁA!c                 óº  — |r|j                  «       sy|j                  «       }t        | d«      }|�>t        | d«      5  | j                  r
	 ddd«       y	 ddd«       	 t	         ||«      «      S t        | dd«      r[| j                  |«      }|rFt        | d	d«      }t        | d
d«      }|�*|�(|5  t        |«      }ddd«       D ]  }t        |«       Œ |S t        | dd«      }	t        | d«      5  |	�|	j                  «       s
	 ddd«       yt        | dd«      }
| j                  r|
s
	 ddd«       y|
r|
› d|› �n|| _        d| _        d| _        ddd«       t        | dd«      }|�t!        d|«       d| _        nd| _        t%        | dd«       y# 1 sw Y   �Œ=xY w# t
        $ r t        j                  dd¬«       Y yw xY w# 1 sw Y   �Œ
xY w# 1 sw Y   Œ�xY w)aŸ  Redirect the active turn without converting it into a new task: during a model request only that
        request is cancelled (completed messages kept, partial reasoning becomes assistant context, the
        correction is appended as a real user message, the loop retries); during tool execution it degrades
        to ``steer()``; Codex app-server uses native ``turn/steer``. False when no live turn / empty text.FÚrequest_steerNr`   z"Codex app-server turn/steer failedTr   Ú_executing_toolsr=   r>   Ú_model_request_activerl   z

[Additional user correction]
rm   Úredirect_abortz$Failed to abort request for redirect)r   r6   r+   rS   r   r!   r"   r#   r   rˆ   r?   Ú_request_yieldÚis_setr/   rl   rT   r@   rn   r:   )rX   rƒ   r†   Ú_native_steerÚacceptedrB   rC   rD   rE   Ú_model_activer‡   rm   s               r   ÚredirectzInterruptControlMixin.redirectÿ   s  € ñ
 ˜4Ÿ:™:œ<ØØ—*‘*“,ˆä(¨¨Ó?ˆØÐ$Ü˜$Ð 8Ó9ñ !Ø×,Ò,Ø ÷!ð !Ø,÷!ðÜ™M¨'Ó2Ó3Ð3ô �4Ð+¨UÔ3Ø—z‘z 'Ó*ˆHÙÜ! $Ð(>ÀÓE�Ü& tÐ-HÈ$ÓO�ØÐ&¨<Ð+CØ%ñ 4Ü&*¨7£m˜÷4à*ò ,˜Ü& sÕ+ð,àˆOä Ð&=¸tÓDˆÜ�dÐ4Ó5ñ 
	+ØÐ$¨M×,@Ñ,@Ô,BØ÷
	+ð 
	+ô   Ð&>Ð@SÓTˆHØ×(Ò(±Ø÷
	+ð 
	+ñ MU�8�*Ð>¸w¸iÑHÐZað Ô"ð )-ˆDÔ%Ø&*ˆDÔ#÷
	+ô  ' tÐ-CÀTÓJÐØÐ+Ü˜4Ð!5Ô6Ø49ˆDÕ1à48ˆDÔ1Ü  Ð'7Ð9_Ô`Ø÷]!ñ !ûô
 ò Ü—‘ÐAÈD�ÔQÙðú÷4ñ 4ú÷
	+ð 
	+úsA   ¾FÁF Â0GÃ1GÄGÄ3GÆFÆ GÇ GÇGÇGc                 ór   — t        | d«      5  t        t        | dd«      «      cddd«       S # 1 sw Y   yxY w)z@Return whether an active-turn redirect is waiting to be applied.r`   rl   N)r+   r   r/   )rX   s    r   Ú_has_pending_redirectz+InterruptControlMixin._has_pending_redirect:  s9   € ä�dÐ4Ó5ñ 	WÜœ Ð'?ÐATÓUÓV÷	W÷ 	Wò 	Wús   �-­6c                 ór   — t        | d«      5  t        | dd«      }d| _        ddd«       |S # 1 sw Y   S xY w)z5Return and clear pending active-turn correction text.r`   rl   N)r+   r/   rl   ©rX   rƒ   s     r   Ú_drain_pending_redirectz-InterruptControlMixin._drain_pending_redirect?  sC   € ä�dÐ4Ó5ñ 	*Ü˜DÐ":Ð<OÓPˆDØ%)ˆDÔ"÷	*ð ˆ÷	*ð ˆúó   �,¬6c                 ór   — t        | d«      5  t        | dd«      }d| _        ddd«       |S # 1 sw Y   S xY w)zXReturn the pending steer text (if any) and clear the slot; None when nothing is pending.r}   r�   N)r+   r/   r�   r—   s     r   Ú_drain_pending_steerz*InterruptControlMixin._drain_pending_steerF  sC   € ä�dÐ1Ó2ñ 	'Ü˜DÐ"7Ð9IÓJˆDØ"&ˆDÔ÷	'ð ˆ÷	'ð ˆúr™   )N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   r   r   Úintrq   rz   r‚   rˆ   r“   r•   r˜   r›   © r   r   rH   rH   j   s  „ Ùcð (,ðbØDIØ%)ÈtòbØ ‘}ðbØ=Aðbà˜c‘]ðbà?GÈ¹}ðbð 
óbðHbÐ\`ò b h¨s¡mð bÈHÐUXÉMð bÐeió bð <AÐV[ò °Dð Ètð Ð`dó ð:	˜#ð 	 $ó 	ð9˜Sð 9 Tó 9ðvW tó Wð
¨°#©ó ð h¨s¡mô r   rH   )rŸ   r(   Úloggingr~   Útypingr   Úagent.interrupt_compatr   Útools.interruptr   rŽ   r   r@   Ú	getLoggerr"   ri   rj   rk   Ú	frozensetr   r   r   r   r%   r+   r/   r6   r:   rF   rH   r¡   r   r   ú<module>r¨      sí   ðñó
 Û Û Ý å 9Ý ;Ý ;ð 
ˆ×	Ñ	˜;Ó	'€ð
 .Ð Ø/Ð Ø)Ð Ù"Ð$5Ð7JÐLbÐ#cÓdÐ ð1˜x¨™}ó 1ð5¸$ð 5ÈSð 5ÐUYó 5ð&>˜#ó >ð ˜sð  ¨#ó  ð0 #ó 0ð5¨Cð 5¸cð 5Àdó 5ð¨4ð ¸$ó ÷&aò ar   