Ë
    ãß²jú<  ã                  óÖ  — U d Z ddlmZ ddlZddlZddlZddlZddlmZm	Z	m
Z
mZ ddlmZ ddlmZ  ej                   d«      Z ej$                  «       Zdad	ed
<    ej$                  «       Zi Zded<   dZi Zded<   d&d„Zd'd„Zd(d„Zd)d„Zddœd*d„Zd+d„Zd,d„Z d(d„Z!ddddœ	 	 	 	 	 	 	 	 	 	 	 d-d„Z"d.d„Z# e$h d£«      Z%d/d„Z&d0d1d„Z' e$h d£«      Z(d2d„Z)d3d „Z*d4d!„Z+d5d"„Z,d#d$d%œZ-y)6zXLive-subagent registry + model-facing control plane (list/steer/stop) for delegate_task.é    )ÚannotationsN)ÚAnyÚDictÚListÚOptional)Úrequest_hard_interrupt)Ú
tool_errorztools.delegate_toolFÚboolÚ_spawn_pausedzDict[str, Dict[str, Any]]Ú_active_subagentséÈ   Ú_recent_subagentsc                ó  — | rt        | t        «      syt        5  t        j	                  | «      xs t
        j	                  | «      }ddd«       €y| |j	                  d«      |j	                  d«      dœS # 1 sw Y   Œ0xY w)zÆ``{subagent_id, goal, delegation_id}`` for a process task_id that belongs to a live or recently-finished child
    (children run their terminal sessions under ``task_id == subagent_id``), else None.NÚgoalÚdelegation_id)Úsubagent_idr   r   )Ú
isinstanceÚstrÚ_active_subagents_lockr   Úgetr   )Útask_idÚrecords     úA/home/ubuntu/.hermes/hermes-agent/tools/delegate_tool_registry.pyÚget_subagent_attributionr      s{   € ñ œ* W¬cÔ2ØÜ	ñ RÜ"×&Ñ& wÓ/ÒQÔ3D×3HÑ3HÈÓ3Qˆ÷Rà€~ØØ"¨F¯J©J°vÓ,>ÐQW×Q[ÑQ[Ð\kÓQlÑmÐm÷	Rð Rús   š-A6Á6A?c                ó\   — t         5  t        | «      at        cddd«       S # 1 sw Y   yxY w)zfGlobally block/unblock NEW delegate_task spawns (active children keep running). Returns the new state.N)Ú_spawn_pause_lockr
   r   )Úpauseds    r   Úset_spawn_pausedr   '   s(   € ô 
ñ Ü˜V›ˆÜ÷÷ ò ús   ‡"¢+c                 óF   — t         5  t        cd d d «       S # 1 sw Y   y xY w)N)r   r   © ó    r   Úis_spawn_pausedr"   .   s   € Ü	ñ Ü÷÷ ò ús   ‡— c                ó˜   — | j                  d«      }|sy | j                  dd«       t        5  | t        |<   d d d «       y # 1 sw Y   y xY w)Nr   Úaccepting_steerT)r   Ú
setdefaultr   r   )r   Úsids     r   Ú_register_subagentr'   2   sJ   € Ø
�*‰*�]Ó
#€CÙØØ
×ÑÐ'¨Ô.Ü	ñ (Ø!'Ô˜#Ñ÷(÷ (ñ (ús   ­
A Á A	)Úagentc               ó  — t         5  t        j                  | «      }|�|�|j                  d«      |u s
	 ddd«       yt        j                  | d«       |j                  d«      }|s
	 ddd«       ydD �ci c]  }||j                  |«      “Œ c}t        |<   t        t        «      t        kD  rCt        j                  t        t        t        «      «      d«       t        t        «      t        kD  rŒCddd«       yc c}w # 1 sw Y   yxY w)z[Drop the live record (exact agent identity when given) and keep a bounded attribution stub.Nr(   r   )r   r   Úowner_agent_session_id)	r   r   r   Úpopr   ÚlenÚ_RECENT_SUBAGENTS_CAPÚnextÚiter)r   r(   r   r&   Úks        r   Ú_unregister_subagentr1   :   sõ   € ä	ñ 
GÜ"×&Ñ& {Ó3ˆØˆ> % -°6·:±:¸gÓ3FÈ%Ñ3OØ÷
Gð 
Gô 	×Ñ˜k¨4Ô0Ø�j‰j˜Ó'ˆÙØ÷
Gð 
Gð =pÖ!p°q ! V§Z¡Z°£]Ñ"2Ò!pÔ˜#ÑÜÔ#Ó$Ô'<Ò<Ü×!Ñ!¤$¤tÔ,=Ó'>Ó"?ÀÔFô Ô#Ó$Ô'<Ó<÷
Gð 
Gùò "q÷
Gð 
Gús)   ‡.C?¾*C?Á1C?Á5C:ÂA"C?Ã:C?Ã?Dc                ó²  — t         5  t        j                  | «      }|�|j                  d«      |ur
	 ddd«       yd|d<   t        |dd«      }t	        |«      s
	 ddd«       y	  |«       }t        |t        «      r|j                  «       r|ndcddd«       S # t
        $ r)}t        j                  d| |«       Y d}~ddd«       yd}~ww xY w# 1 sw Y   yxY w)an  Atomically close steer acceptance and drain its final durable artifact. ``steer_subagent`` holds the same
    registry lock through ``agent.steer``, so either acceptance wins and this drain sees its exact text, or closure
    wins and the caller is rejected. Exact agent identity prevents a finishing child with a recycled public id from
    closing its replacement.Nr(   Fr$   Ú_drain_pending_steerz#final steer drain for %s failed: %s)r   r   r   ÚgetattrÚcallableÚ	ExceptionÚloggerÚdebugr   r   Ústrip)r   r(   r   ÚdrainÚpendingÚexcs         r   Ú_close_subagent_steeringr=   H   sâ   € ô
 
 ñ QÜ"×&Ñ& {Ó3ˆØˆ>˜VŸZ™Z¨Ó0¸Ñ=Ø÷Qð Qð %*ˆÐ Ñ!Ü˜Ð5°tÓ<ˆÜ˜ŒØ÷Qð Qð	Ù“gˆGô % W¬cÔ2°w·}±}´‰wÈD÷Qñ Qøô ò 	Ü�L‰LÐ>ÀÈSÔQÛ÷Qð Qûð	ú÷Qð Qús:   ‡,C¼CÁ$BÁ+#CÂ	C
Â!CÂ8CÃC
Ã
CÃCc                ó   — t         5  t        j                  | «      }ddd«       r|j                  d«      nd}|€y	 t        t	        |d| › d�«      «      S # 1 sw Y   Œ;xY w# t
        $ r!}t        j                  d| |«       Y d}~yd}~ww xY w)zäRequest that one running subagent stop at its next iteration boundary
    (cooperative: the flag propagates to in-flight tools and recurses into
    grandchildren via AIAgent.interrupt()). True iff a matching subagent was found.Nr(   FzInterrupted via TUI (ú)z!interrupt_subagent(%s) failed: %s)r   r   r   r
   r   r6   r7   r8   )r   r   r(   r<   s       r   Úinterrupt_subagentr@   \   sŽ   € ô 
 ñ 4Ü"×&Ñ& {Ó3ˆ÷4á#)ˆF�J‰J�wÔ¨t€EØ€}ØðÜÔ*¨5Ð4IÈ+ÈÐVWÐ2XÓYÓZÐZ÷4ð 4ûô ò Ü�‰Ð8¸+ÀsÔKÜûðús"   ‡A¾A# ÁA Á#	BÁ,BÂBc                ó  — ddl m} | j                  d«      €y| j                  d«      }t        |t        «      r|j                  d«      n| j                  d«      }||u xs t        ||«      xr |j                  |«      S )a1  Authority follows the owning session's LIVE transport slot, read at check time.

    ``owner_transport`` on the record is only the capture-time marker that a gateway session
    commissioned the child (``None`` = no RPC authority ever). The slot is authoritative because
    every reattach path (prompt.submit, queued drain, resume, activate, viewer failover) already
    mutates it; a per-record copy needed a matching registry sync at each of those sites and two
    were missed (#106663). Records whose owner is not a session dict keep the exact-object rule.r   )ÚFanoutTransportÚowner_transportFÚowner_session_recordÚ	transport)Útui_gateway.transportrB   r   r   ÚdictÚcontains)r   rE   rB   ÚownerÚbounds        r   Ú_subagent_transport_matchesrK   k   sr   € õ 6à‡z�zÐ#Ó$Ð,ØØ�J‰JÐ-Ó.€EÜ&0°¼Ô&=ˆE�I‰I�kÔ"À6Ç:Á:ÐN_ÓC`€EØ�IÐÒc¤*¨U°OÓ"DÒ"bÈÏÉÐXaÓIbÐcr!   )Úowner_session_idrC   rD   c               ó&  — |r|j                  «       syt        5  t        j                  | «      }|r|j                  dd«      s
	 ddd«       y|�A|j                  d«      |k7  s#|�!t	        ||«      r|�|j                  d«      |ur
	 ddd«       y|j                  d«      }|€
	 ddd«       y	 t        |j                  |«      «      cddd«       S # t        $ r)}t        j                  d| |«       Y d}~ddd«       yd}~ww xY w# 1 sw Y   yxY w)uƒ  Queue steering text into a running subagent without stopping it.

    AIAgent.steer() appends the text to the child's last tool result at its next iteration boundary â€” the current tool
    call is never cut. True iff the text was QUEUED while the child still accepted work; False for unknown/closed id,
    ownership mismatch, no live agent, or empty text. ``owner_session_id=None`` keeps the in-process helper contract;
    gateway callers must pass exact authority. Acceptance and completion are linearized by the registry lock: if
    acceptance wins but no delivery boundary remains, the text lands in the entry as ``missed_steer``.
    Fr$   NrL   rD   r(   zsteer_subagent(%s) failed: %s)
r9   r   r   r   rK   r
   Ústeerr6   r7   r8   )r   ÚtextrL   rC   rD   r   r(   r<   s           r   Ústeer_subagentrP   |   s  € ñ �t—z‘z”|ØÜ	ñ Ü"×&Ñ& {Ó3ˆÙ˜VŸZ™ZÐ(9¸5ÔAØ÷ð ð Ð'Ø�J‰JÐ)Ó*Ð.>Ò>ØÐ&Ü.¨v°ÔGØ#Ð+Ø�z‰zÐ0Ó1Ð9MÑMà÷ð ð —
‘
˜7Ó#ˆØˆ=Ø÷ð ð	Ü˜Ÿ™ DÓ)Ó*÷!ñ øô" ò 	Ü�L‰LÐ8¸+ÀsÔKÛ÷'ð ûð"	ú÷#ð ús;   š+DÁ:DÂDÂ/CÃ	DÃC?Ã2DÃ?DÄDÄDc                óD   — | sy	 ddl m}  || «      S # t        $ r Y yw xY w)už   Exact request transport + live session generation, if any â€” an in-process
    bridge, not a serializable capability. Non-gateway hosts get ``(None, None)``.)NNr   )Ú _current_session_steer_authority)Útui_gateway.serverrR   r6   )rL   rR   s     r   Ú _capture_gateway_steer_authorityrT   Ÿ   s0   € ñ ØðÝGÙ/Ð0@ÓAÐAøÜò Ùðús   … “	ž>   r(   r$   rC   rL   rD   c                 óö   — t         5  t        j                  «       D � ��cg c]/  } | j                  «       D ��ci c]  \  }}|t        vsŒ||“Œ c}}‘Œ1 c}}} cddd«       S c c}}w c c}}} w # 1 sw Y   yxY w)z�Copy of the running subagent tree ({subagent_id, parent_id, depth, goal, model,
    started_at, tool_count, status, ...}); safe from any thread.N)r   r   ÚvaluesÚitemsÚ_PRIVATE_RECORD_KEYS)Úrr0   Úvs      r   Úlist_active_subagentsr[   ­   sh   € ô 
 ñ sÜWh×WoÑWoÓWq×rÐrÐRS !§'¡'£)×M™$˜!˜Q¨qÔ8LÒ/L��A‘ÕMÔr÷sñ sùÛMùÔr÷sð sús3   ‡A/ A(
¸A"Á	A"ÁA(
ÁA/Á"A(
Á(A/Á/A8c                óŒ   — | �|€y| }t        |«      D ]/  }t        |dd«      }t        |«      r |«       nd}|€ y||u r y|}Œ1 y)u  True when *child_agent* sits below *parent_agent* in the spawn tree (walks the ``_delegate_parent_ref`` weakref
    chain stamped at build time). Identity only â€” a parent may steer/stop its own children and grandchildren, never
    a sibling tree owned by another conversation.NFÚ_delegate_parent_refT)Úranger4   r5   )Úchild_agentÚparent_agentÚmax_hopsÚcurÚ_ÚrefÚancestors          r   Ú_is_descendant_ofrf   ³   sf   € ð Ð˜lÐ2ØØ
€CÜ�8‹_ò ˆÜ�cÐ1°4Ó8ˆÜ$ SœM‘3”5¨tˆØÐÙØ�|Ñ#ÙØ‰ðð r!   >   ÚlistÚstoprN   c                óª   — t        | xs d«      }t        |dd«      }|r|€|S 	 |j                  |«      }|rt        |«      S |S # t        $ r |cY S w xY w)zØTip of a session id's compression lineage via the parent's live SessionDB (best-effort; input unchanged when
    unavailable) so a delegation dispatched before a compression rotation still matches the rotated parent.Ú Ú_session_dbN)r   r4   Úresolve_resume_session_idr6   )Ú
session_idr`   r&   ÚdbÚresolveds        r   Ú_resolve_session_lineagerp   È   sg   € ô ˆjÒ˜BÓ
€CÜ	�˜}¨dÓ	3€BÙ�"�*Øˆ
ðØ×/Ñ/°Ó4ˆÙ (Œs�8‹}Ð1¨cÐ1øÜò ØŠ
ðús   ¤A ÁA ÁAÁAc                óô   — t        | j                  d«      |«      ryt        | j                  d«      xs d«      }t        t        |dd«      xs d«      }|r|sy||k(  ryt	        ||«      |t	        ||«      hv S )u  True when *parent_agent*'s conversation owns this live-child record.

    Tier 1: identity â€” the ``_delegate_parent_ref`` weakref chain reaches
    *parent_agent* (fast path while the parent AIAgent survives the run). Tier 2:
    durable lineage â€” the record's ``owner_agent_session_id`` matches the caller's
    ``session_id`` after resolving compression-rotation lineage on both sides.
    Tier 2 exists because the identity chain is BRITTLE across parent rebuilds:
    the CLI sets ``self.agent = None`` mid-session (route change, credential
    refresh, /model, MoA one-shots) and builds a NEW AIAgent while the child keeps
    a weakref to the old one. Delivery routes by durable session id; control must
    use the same spine or running children go invisible/unsteerable.
    r(   Tr*   rj   rm   F)rf   r   r   r4   rp   )r   r`   Ú	owner_sidÚ
parent_sids       r   Ú_owns_subagent_recordrt   Õ   s‰   € ô ˜Ÿ™ GÓ,¨lÔ;ØÜ�F—J‘JÐ7Ó8Ò>¸BÓ?€IÜ”W˜\¨<¸Ó<ÒBÀÓC€JÙ™JØØ�JÒØä# I¨|Ó<ÀÔMeÐfpÐr~ÓMð  AAð  Að  Ar!   c                ó�  — t         5  t        t        j                  «       «      }d d d «       g }D ]ï  }t	        || «      sŒ|j                  d«      }|j                  |j                  d«      |j                  d«      |j                  d«      |j                  d«      |j                  d«      t        |t        t        f«      r!t        t        j                  «       |z
  d«      nd t        |j                  dd	«      «      t        |j                  d
«      dd «      dœ«       Œñ dt        |«      |dœ}|sd|d<   |S # 1 sw Y   �ŒxY w)NÚ
started_atr   Ú	parent_idr   ÚmodelÚstatusé   r$   Fr(   Ú_live_transcript_path)r   rw   r   rx   ry   Úrunning_secondsr$   Úlive_transcriptrg   )ÚactionÚcountÚ	subagentsu¯   No live subagents right now. Children that already finished have delivered (or will deliver) their results as normal completion messages â€” there is nothing to steer or stop.Únote)r   rg   r   rV   rt   r   Úappendr   ÚintÚfloatÚroundÚtimer
   r4   r,   )r`   ÚrecordsÚentriesrY   ÚstartedÚpayloads         r   Ú_list_payloadr‹   í   s%  € Ü	ñ 3ÜÔ(×/Ñ/Ó1Ó2ˆ÷3à€GØò ˆÜ$ Q¨Ô5ØØ—%‘%˜Ó%ˆØ�‰ØŸ5™5 Ó/ØŸ™˜{Ó+Ø—E‘E˜&“MØ—U‘U˜7“^Ø—e‘e˜H“oÜBLÈWÔWZÔ\aÐVbÔBcœu¤T§Y¡Y£[°7Ñ%:¸AÔ>ÐimÜ# A§E¡EÐ*;¸UÓ$CÓDÜ& q§u¡u¨W£~Ð7NÐPTÓUñ	
õ 		ð	ð *0¼#¸g»,ÐU\Ñ]€GÙðIð 	�‰ð
 €N÷13ñ 3ús   ‡D;Ä;Ec                ó–  — | dk(  r t        j                  t        |«      d¬«      S |xs dj                  «       }|st	        d| › d�«      S t
        5  t        j                  |«      }ddd«       �t        ||«      st	        d|› d	�«      S | d
k(  r|xs dj                  «       st	        d«      S t        j                  | «      }|€t	        d| › d�«      S |\  }}}	| dk(  rt        |«      nt        ||j                  «       «      }
|
rt        j                  | |||dœd¬«      S t	        |	j                  |¬«      «      S # 1 sw Y   ŒÙxY w)zÐSynchronous control plane for delegate_task: list/steer/stop. Runs in-turn (never backgrounded) over the same
    registry the TUI overlay drives, scoped so a conversation can only control its own spawn tree.rg   F)Úensure_asciirj   zaction='zK' requires subagent_id (from the spawn dispatch response or action='list').NzNo live subagent 'zž' in this conversation's spawn tree. It may have already finished (its result arrives as a normal completion message). Use action='list' to see live children.rN   zOaction='steer' requires a non-empty 'message' describing the course correction.zUnknown action 'z#'. Use spawn, list, steer, or stop.rh   )r~   r   ry   r�   )r&   )ÚjsonÚdumpsr‹   r9   r	   r   r   r   rt   Ú_CONTROL_OUTCOMESr@   rP   Úformat)r~   r   Úmessager`   r&   r   Úoutcomery   r�   ÚfailureÚoks              r   Ú_handle_control_actionr–     sX  € ð �ÒÜ�z‰zœ-¨Ó5ÀEÔJÐJð Ò˜"×
#Ñ
#Ó
%€CÙÜ˜H V HÐ,wÐxÓyÐyÜ	ñ ,Ü"×&Ñ& sÓ+ˆ÷,à€~Ô2°6¸<ÔHÜØ   ð &Kð Kó
ð 	
ð
 �Ò '¢-¨R×!6Ñ!6Ô!8ÜÐkÓlÐlÜ×#Ñ# FÓ+€GØ€ÜÐ,¨V¨HÐ4WÐXÓYÐYØ#Ñ€FˆD�'Ø$*¨fÒ$4Ô	˜CÔ	 ¼.ÈÈgÏmÉmËoÓ:^€BÙ	Ü�z‰z V¸CÈ6Ð[_Ñ`ÐotÔuÐuÜ�g—n‘n¨�nÓ-Ó.Ð.÷#,ð ,ús   ÁD?Ä?E)Úinterrupt_requesteduÂ   The subagent stops at its next iteration boundary (in-flight tool calls are asked to cancel). Its partial result still re-enters the conversation as a completion message â€” do not wait or poll.uy   Could not interrupt '{sid}' â€” it likely finished in the last moment. Its result arrives as a normal completion message.)Úqueueduð   Steering text queued. The subagent sees it appended to its next tool result â€” the current tool call is never cut. If the child finishes before a delivery boundary remains, the text is reported back as missed_steer in its completion entry.z¹Subagent '{sid}' is no longer accepting steering (finishing or already finished). Its result arrives as a normal completion message; re-delegate a follow-up task if more work is needed.)rh   rN   )r   úOptional[str]ÚreturnzOptional[Dict[str, Any]])r   r
   rš   r
   )rš   r
   )r   úDict[str, Any]rš   ÚNone)r   r   r(   r   rš   rœ   )r   r   r(   r   rš   r™   )r   r   rš   r
   )r   r   rO   r   rL   r™   rC   r   rD   r   rš   r
   )rL   r™   rš   ztuple[Any, Any])rš   zList[Dict[str, Any]])é   )r_   r   r`   r   ra   rƒ   rš   r
   )rm   r™   r`   r   rš   r   )r   r›   r`   r   rš   r
   )r`   r   rš   r›   )
r~   r   r   r™   r’   r™   r`   r   rš   r   ).Ú__doc__Ú
__future__r   ÚloggingrŽ   Ú	threadingr†   Útypingr   r   r   r   Úagent.interrupt_compatr   Útools.registryr	   Ú	getLoggerr7   ÚLockr   r   Ú__annotations__r   r   r-   r   r   r   r"   r'   r1   r=   r@   rK   rP   rT   Ú	frozensetrX   r[   rf   Ú_CONTROL_ACTIONSrp   rt   r‹   r–   r�   r    r!   r   ú<module>rª      sJ  ðÚ ^å "ã Û Û Û ß ,Ó ,Ý 9Ý %à	ˆ×	Ñ	Ð0Ó	1€à"�I—N‘NÓ$Ð Ø€ˆtÓ Ø'˜Ÿ™Ó)Ð ð 02Ð Ð,Ó 1ð
 Ð Ø/1Ð Ð,Ó 1ó	nóóó(ð <@õ GóQó(ódð$ GKÐcgØ $ñ!Øð!Øð!Ø6Cð!Ø]`ð!àð!ð 
ó!óF	ñ !Ò!|Ó}Ð ósôñ& Ò6Ó7Ð óóAó0ó6/ð>ðñÑ r!   