Ë
    áß²jŸ‚  ã            
      ó,  — U d Z ddlmZ ddlZddlZddlmZ ddlmZm	Z	m
Z
mZ ddlmZmZ ddlmZ ddlmZmZ  eh d	£«      Z eh d
£«      Z edh«      ZdZdZdZdZdZdZ eh d£«      Z eh d£«      ZdZ dddddddœZ!de"d<   dZ#dZ$ eh d£«      Z%dSd „Z&dTd!„Z' e	d"¬#«       G d$„ d%«      «       Z( e	d"¬#«       G d&„ d'«      «       Z) e	d"¬#«       G d(„ d)«      «       Z* e	d"¬#«       G d*„ d+«      «       Z+ e	d"¬#«       G d,„ d-«      «       Z,dUd.„Z-dVd/„Z.dWd0„Z/d1d2d3d4d5d6d7d8d9d:œ	Z0d;e"d<<   d=Z1d>Z2d?d@dAœZ3dBe"dC<    G dD„ dE«      Z4dXdF„Z5dYdG„Z6dZdH„Z7d[dI„Z8d\dJ„Z9d]dK„Z:dLD � ci c]  } | d"“Œ c} dMD � ci c]  } | dN“Œ c} z  Z;d^dO„Z<d_dP„Z=d`dQ„Z>dadR„Z?yc c} w c c} w )ba  Pure tool-call loop guardrail primitives.

The controller is side-effect free: it tracks per-turn tool-call observations
and returns decisions. Runtime code decides whether a decision becomes warning
guidance, a synthetic tool result, or a controlled turn halt.
é    )ÚannotationsN)Údeque)ÚasdictÚ	dataclassÚfieldÚfields)ÚAnyÚMapping)Úsafe_json_loads)Úfile_mutation_result_landedÚis_guardrail_refusal>   Ú	read_fileÚ
skill_viewÚ
web_searchÚskills_listÚweb_extractÚsearch_filesÚsession_searchÚbrowser_consoleÚbrowser_snapshotÚbrowser_get_imagesÚmcp_filesystem_read_fileÚmcp_filesystem_search_filesÚmcp_filesystem_get_file_infoÚmcp_filesystem_directory_treeÚmcp_filesystem_list_directoryÚmcp_filesystem_read_text_fileÚ"mcp_filesystem_read_multiple_filesÚ(mcp_filesystem_list_directory_with_sizes>   ÚpatchÚmemoryÚterminalÚ	todo_listÚ
write_fileÚbrowser_typeÚexecute_codeÚsend_messageÚskill_manageÚbrowser_clickÚbrowser_pressÚdelegate_taskÚbrowser_scrollÚcronjob_manageÚprocess_manageÚbrowser_navigater.   )Ú_get_resultÚ_pollé   é   é@   i   éx   >   Úprocessr"   r   r&   r.   r/   >   Útodor    r!   Úcronjobr6   r"   r#   r$   r%   r&   r'   r(   r)   r*   r+   r-   r.   r/   )Úwarnings_enabledÚhard_stop_enabledÚ!non_interactive_hard_stop_enabled)Ú
warn_afterÚexact_failure)r<   Úsame_tool_failure)r<   Úidempotent_no_progress)Úhard_stop_afterr=   )r@   r>   )r@   r?   )Úexact_failure_warn_afterÚsame_tool_failure_warn_afterÚno_progress_warn_afterÚexact_failure_block_afterÚsame_tool_failure_halt_afterÚno_progress_block_afterzdict[str, tuple[str, str]]Ú_THRESHOLD_SOURCESé2   >   ÚacpÚcliÚtuiÚdesktopÚsubagentÚ
api_serverc                ó@   — | t         v xs | j                  t        «      S )z=Whether a tool is exempt from the identical-call loop notice.)ÚSTALL_GUARD_REPEATABLE_TOOLSÚendswithÚ _STALL_GUARD_REPEATABLE_SUFFIXES)Ú	tool_names    ú:/home/ubuntu/.hermes/hermes-agent/agent/tool_guardrails.pyÚis_stall_guard_repeatablerU   X   s   € àÔ4Ð4Òl¸	×8JÑ8JÔKkÓ8lÐló    c                óŒ   — t        | t        «      r| j                  «       sy| j                  «       j                  «       t        vS )z?True for gateway/cron sessions where tool loops are unattended.F)Ú
isinstanceÚstrÚstripÚlowerÚ_ATTENDED_PLATFORMS©Úplatforms    rT   Ú_is_non_interactive_platformr_   ]   s4   € ä�h¤Ô$¨H¯N©NÔ,<ØØ�>‰>Ó×!Ñ!Ó#Ô+>Ð>Ð>rV   T)Úfrozenc                  ó@   — e Zd ZU dZeZded<   eZded<   e	dd„«       Z
y)ÚLoopCapConfigz¢Per-turn hard ceilings on web_search calls / subagent spawns; count total calls (not
    repeats), fire regardless of ``hard_stop_enabled``; ``0`` disables a cap.ÚintÚmax_web_searchesÚmax_subagentsc                óê   — t        |t        «      s | «       S  | di t        | «      D �ci c]=  }|j                  t	        |j                  |j                  «      |j                  d«      “Œ? c}¤ŽS c c}w )zABuild config from the ``tool_loop_guardrails.loop_caps`` section.r   © )rX   r
   r   ÚnameÚ_int_at_leastÚgetÚdefault)ÚclsÚdataÚfs      rT   Úfrom_mappingzLoopCapConfig.from_mappingl   s\   € ô ˜$¤Ô(Ù“5ˆLÙÑbÔU[Ð\_ÓU`ÖaÐPQ�a—f‘fœm¨D¯H©H°Q·V±VÓ,<¸a¿i¹iÈÓKÑKÒaÑbÐbùÒas   ©AA0N)rm   úMapping[str, Any] | NoneÚreturnz'LoopCapConfig')Ú__name__Ú
__module__Ú__qualname__Ú__doc__Ú"_DEFAULT_MAX_WEB_SEARCHES_PER_TURNrd   Ú__annotations__Ú_DEFAULT_MAX_SUBAGENTS_PER_TURNre   Úclassmethodro   rg   rV   rT   rb   rb   d   s3   … ñQð ?Ð�cÓ>Ø8€M�3Ó8àòcó ñcrV   rb   c                  ó
  — e Zd ZU dZdZded<   dZded<   dZded<   dZd	ed
<   dZ	d	ed<   dZ
d	ed<   dZd	ed<   dZd	ed<   dZd	ed<    ed„ ¬«      Zded<    ed„ ¬«      Zded<    ee¬«      Zded<   eddœ	 	 	 	 	 dd„«       Zy)ÚToolCallGuardrailConfigz¹Thresholds for per-turn tool-call loop detection. Warnings never prevent execution; hard
    stops are opt-in on interactive platforms, default on for unattended gateway/cron platforms.TÚboolr9   Fr:   r;   é   rc   rA   é   rD   r2   rB   é   rE   rC   rF   c                 ó   — t         S ©N)ÚIDEMPOTENT_TOOL_NAMESrg   rV   rT   ú<lambda>z ToolCallGuardrailConfig.<lambda>‚   s   € ÔEZ€ rV   )Údefault_factoryzfrozenset[str]Úidempotent_toolsc                 ó   — t         S r�   )ÚMUTATING_TOOL_NAMESrg   rV   rT   rƒ   z ToolCallGuardrailConfig.<lambda>ƒ   s   € ÔCV€ rV   Úmutating_toolsrb   Ú	loop_capsNr]   c               ó¬  ‡‡— t        ‰t        «      si Š | «       Št        D �ci c](  }|t        ‰j	                  |«      t        ‰|«      «      “Œ* }}|d   rt        |«      rd|d<   dˆˆfd„}t        j                  «       D ��ci c]  \  }}| ||g|¢­Ž “Œ }}} | ddt        j                  ‰j	                  d«      «      i|¤|¤ŽS c c}w c c}}w )zpBuild config from `tool_loop_guardrails`; nested ``warn_after`` / ``hard_stop_after`` win over flat legacy keys.r;   Tr:   c                óØ   •— ‰j                  |«      }t        |t        «      r!|j                  |‰j                  | «      «      n‰j                  | «      }t        |t	        ‰| «      d«      S )Né   )rj   rX   r
   ri   Úgetattr)rh   Úsection_nameÚkeyÚsectionÚnestedÚdrm   s        €€rT   Ú	thresholdz7ToolCallGuardrailConfig.from_mapping.<locals>.threshold’   sW   ø€ Ø—h‘h˜|Ó,ˆGÜ9CÀGÌWÔ9U�W—[‘[  d§h¡h¨t£nÔ5Ð[_×[cÑ[cÐdhÓ[iˆFÜ  ¬°°DÓ)9¸1Ó=Ð=rV   r‰   )rh   rY   rŽ   rY   r�   rY   rq   rc   rg   )rX   r
   Ú_BOOL_FIELDSÚ_as_boolrj   r�   r_   rG   Úitemsrb   ro   )	rl   rm   r^   rh   Úflagsr“   ÚsrcÚ
thresholdsr’   s	    `      @rT   ro   z$ToolCallGuardrailConfig.from_mapping†   sØ   ù€ ô
 ˜$¤Ô(ØˆDÙ‹EˆÜNZÖ[Àd�”x §¡¨£´¸¸4Ó0@ÓAÑAÐ[ˆÐ[ØÐ4Ò5Ô:VÐW_Ô:`Ø)-ˆEÐ%Ñ&ö	>ô
 DV×C[ÑC[ÓC]×^±i°d¸C�d™I dÐ1¨SÒ1Ñ1Ð^ˆ
Ñ^ÙÑfœ]×7Ñ7¸¿¹ÀÓ8MÓNÐfÐRWÐfÐ[eÑfÐfùò \ùó _s   ¤-CÂC)rm   rp   r^   ú
str | Nonerq   z'ToolCallGuardrailConfig')rr   rs   rt   ru   r9   rw   r:   r;   rA   rD   rB   rE   rC   rF   r   r…   rˆ   rb   r‰   ry   ro   rg   rV   rT   r{   r{   t   sÏ   … ñdð "Ð�dÓ!Ø#Ð�tÓ#Ø.2Ð% tÓ2Ø$%Ð˜cÓ%Ø%&Ð˜sÓ&Ø()Ð  #Ó)Ø()Ð  #Ó)Ø"#Ð˜CÓ#Ø#$Ð˜SÓ$Ù',Ñ=ZÔ'[Ð�nÓ[Ù%*Ñ;VÔ%W€N�NÓWÙ$°]ÔC€Iˆ}ÓCààGKñgØ+ðgØ:Dðgà	"ògó ñgrV   r{   c                  ó.   — e Zd ZU dZdZded<   dZded<   y)ÚIdenticalCallObservationz]``notice`` is appended after the result, ``stub`` replaces a byte-identical duplicate result.Nrš   ÚnoticeÚstub)rr   rs   rt   ru   r�   rw   rž   rg   rV   rT   rœ   rœ   ›   s   … ágà€FˆJÓØ€Dˆ*ÔrV   rœ   c                  ó@   — e Zd ZU dZded<   ded<   edd„«       Zd	d„Zy)
ÚToolCallSignaturezDStable, non-reversible identity for a tool name plus canonical args.rY   rS   Ú	args_hashc           	     óB   —  | |t        t        |xs i «      «      ¬«      S )N)rS   r¡   )Ú_sha256Úcanonical_tool_args)rl   rS   Úargss      rT   Ú	from_callzToolCallSignature.from_callª   s   € á˜Y´'Ô:MÈdÊjÐVXÓ:YÓ2ZÔ[Ð[rV   c                ó   — t        | «      S )z3Return public metadata without raw argument values.©r   ©Úselfs    rT   Úto_metadatazToolCallSignature.to_metadata®   s   € ä�d‹|ÐrV   N)rS   rY   r¥   rp   rq   z'ToolCallSignature')rq   údict[str, str])rr   rs   rt   ru   rw   ry   r¦   r«   rg   rV   rT   r    r    £   s(   … áNàƒNØƒNàò\ó ð\ôrV   r    c                  ó’   — e Zd ZU dZdZded<   dZded<   dZded<   dZded<   d	Z	d
ed<   dZ
ded<   edd„«       Zedd„«       Zdd„Zy)ÚToolGuardrailDecisionz8Decision returned by the tool-call guardrail controller.ÚallowrY   ÚactionÚcodeÚ ÚmessagerS   r   rc   ÚcountNzToolCallSignature | NoneÚ	signaturec                ó   — | j                   dv S )N>   Úwarnr¯   ©r°   r©   s    rT   Úallows_executionz&ToolGuardrailDecision.allows_execution¾   ó   € à�{‰{Ð/Ð/Ð/rV   c                ó   — | j                   dv S )N>   ÚhaltÚblockr¸   r©   s    rT   Úshould_haltz!ToolGuardrailDecision.should_haltÂ   rº   rV   c                ó,   — t        | «      }|d   €|d= |S )Nrµ   r¨   )rª   rm   s     rT   r«   z!ToolGuardrailDecision.to_metadataÆ   s#   € Ü�d‹|ˆØ�ÑÐ$Ø�[Ð!ØˆrV   )rq   r|   )rq   zdict[str, Any])rr   rs   rt   ru   r°   rw   r±   r³   rS   r´   rµ   Úpropertyr¹   r¾   r«   rg   rV   rT   r®   r®   ³   sl   … áBà€FˆCÓØ€Dˆ#ÓØ€GˆSÓØ€IˆsÓØ€Eˆ3ƒNØ*.€IÐ'Ó.àò0ó ð0ð ò0ó ð0ôrV   r®   c                ó>   — t        j                  | dddt        ¬«      S )NFT)ú,ú:)Úensure_asciiÚ	sort_keysÚ
separatorsrk   )ÚjsonÚdumpsrY   ©Úvalues    rT   Ú_canonical_jsonrË   Í   s   € Ü�:‰:�e¨%¸4ÈJÔ`cÔdÐdrV   c                óz   — t        | t        «      s!t        dt        | «      j                  › �«      ‚t        | «      S )z5Return sorted compact JSON for parsed tool arguments.z!tool args must be a mapping, got )rX   r
   Ú	TypeErrorÚtyperr   rË   ©r¥   s    rT   r¤   r¤   Ñ   s5   € ä�dœGÔ$ÜÐ;¼DÀ»J×<OÑ<OÐ;PÐQÓRÐRÜ˜4Ó Ð rV   c                ó°  — |�t        | |«      ryt        |«      ry| dk(  r?t        |«      }t        |t        «      r|j                  d«      nd}|�|dk7  rdd|› d�fS dS | d	k(  rCt        |«      }t        |t        «      r(|j                  d
«      du rd|j                  dd«      v ry|dd j                  «       }d|v sd|v s|j                  d«      rdS dS )zµFallback classifier used only when callers don't pass ``failed``; mirrors
    ``agent.display._detect_tool_failure`` so the guardrail never disagrees with the CLI's ``[error]`` tag.N)Fr²   r"   Ú	exit_coder   Tz [exit ú]r!   ÚsuccessFzexceed the limitÚerrorr²   )Tz [full]iô  z"error"z"failed"ÚError)Tz [error])r   r   r   rX   Údictrj   r[   Ú
startswith)rS   Úresultrm   rÑ   r[   s        rT   Úclassify_tool_failurerÙ   Ø   sø   € ð €~Ô4°YÀÔGØô ˜FÔ#Øà�JÒÜ˜vÓ&ˆÜ-7¸¼dÔ-C�D—H‘H˜[Ô)Èˆ	Ø1:Ð1FÈ9ÐXYÊ>�˜ 	˜{¨!Ð,Ð-ÐjÐ_jÐjà�HÒÜ˜vÓ&ˆÜ�dœDÔ! d§h¡h¨yÓ&9¸UÑ&BÐGYÐ]a×]eÑ]eÐfmÐoqÓ]rÑGrØ"Ø�4�CˆL×ÑÓ €EØ!*¨eÑ!3°zÀUÑ7JÈf×N_ÑN_Ð`gÔNhÐÐyÐnyÐyrV   zšBlocked {tool_name}: the same tool call failed {count} times with identical arguments. Stop retrying it unchanged; change strategy or explain the blocker.z§Blocked {tool_name}: this read-only call returned the same result {count} times. Stop repeating it unchanged; use the result already provided or try a different query.z�Stopped {tool_name}: it failed {count} times this turn. Stop retrying the same failing tool path and choose a different approach.zž{tool_name} has failed {count} times with identical arguments. This looks like a loop; inspect the error and change strategy instead of retrying it unchanged.zŠ{tool_name} returned the same result {count} times. Use the result already provided or change the query instead of repeating it unchanged.z½Stopped {tool_name}: the same call with identical arguments returned the same result {count} times in a row. Stop repeating it unchanged; use the result already provided or change strategy.z÷Stopped {tool_name}: the same repeating cycle of tool calls (period {period}) with identical arguments and identical results has run {count} times. Repeating the batch unchanged is not progress; use the results already provided or change strategy.zÃBlocked web_search: this turn has already made {cap} web searches, the per-turn limit. This looks like a runaway search loop. Work with the results you already have and give the user your answer.z¿Blocked delegate_task: this turn has already spawned {count} subagents (limit {cap}). This looks like a runaway delegation loop. Finish the work with the results you have and answer the user.)	Úrepeated_exact_failure_blockÚidempotent_no_progress_blockÚsame_tool_failure_haltÚrepeated_exact_failure_warningÚidempotent_no_progress_warningÚidentical_call_streak_haltÚidentical_cycle_haltÚloop_web_search_capÚloop_subagent_capr¬   Ú_DECISION_MESSAGESuÞ   [hermes note: this is the {ordinal} consecutive identical call to {tool_name} with identical arguments returning the same result. Do not repeat it â€” change arguments, use a different tool, or proceed with what you have.]u  [hermes note: the last {count} rounds repeated the same cycle of {period} tool calls (ending with {tool_name}) with identical arguments and identical results. Do not repeat the batch â€” change arguments, use a different tool, or proceed with what you have.])rd   Ú_turn_web_search_countrá   )re   Ú_turn_subagent_countrâ   )r   r+   zdict[str, tuple[str, str, str]]Ú
_LOOP_CAPSc                  óæ   — e Zd ZdZddd„Zdd„Zedd„«       Zddœ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dd„Zdd„Z	dd	œ	 	 	 	 	 	 	 	 	 dd
„Z
dd„Zdddœ	 	 	 	 	 	 	 	 	 	 	 dd„Zdd„Zdd„Zdd„Z	 	 	 	 	 	 	 	 d d„Zy)!ÚToolCallGuardrailControllerzCPer-turn controller for repeated failed/non-progressing tool calls.Nc                óJ   — |xs
 t        «       | _        | j                  «        y r�   )r{   ÚconfigÚreset_for_turn)rª   rê   s     rT   Ú__init__z$ToolCallGuardrailController.__init__4  s   € ØÒ9Ô 7Ó 9ˆŒØ×ÑÕrV   c                óÖ   — i | _         i | _        i | _        i | _        d | _        d | _        d| _        d| _        d| _        t        t        ¬«      | _        i | _        d| _        d| _        y )Nr²   r   )Úmaxlen)Ú_exact_failure_countsÚ_same_tool_failure_countsÚ_progress_since_failureÚ_no_progressÚ_halt_decisionÚ_identical_streak_sigÚ_identical_streak_result_hashÚ_identical_streak_countÚ_identical_streak_first_call_idr   Ú_STALL_GUARD_CYCLE_HISTORYÚ_call_historyÚ_persisted_result_pathsrä   rå   r©   s    rT   rë   z*ToolCallGuardrailController.reset_for_turn8  sx   € ØCEˆÔ"Ø9;ˆÔ&àFHˆÔ$ØFHˆÔØ<@ˆÔð @DˆÔ"Ø24ˆÔ*Ø,-ˆÔ$Ø46ˆÔ,ô JOÔVpÔIqˆÔà79ˆÔ$Ø&'ˆÔ#Ø$%ˆÕ!rV   c                ó   — | j                   S r�   )ró   r©   s    rT   Úhalt_decisionz)ToolCallGuardrailController.halt_decisionS  s   € à×"Ñ"Ð"rV   ©r³   c               ó†   — |€t        |   j                  d||dœ|¤Ž}t        ||||||«      }|j                  r|| _        |S )zZBuild a warn/block/halt decision; block/halt is also recorded as the turn's halt decision.)rS   r´   rg   )rã   Úformatr®   r¾   ró   )	rª   r°   r±   rS   r´   rµ   r³   ÚfmtÚdecisions	            rT   Ú_decidez#ToolCallGuardrailController._decideW  sU   € ð
 ˆ?Ü(¨Ñ.×5Ñ5Ð^À	ÐQVÑ^ÐZ]Ñ^ˆGÜ(¨°°wÀ	È5ÐR[Ó\ˆØ×ÒØ"*ˆDÔØˆrV   c                óZ  — t        |«      }t        j                  ||«      }t        ||¬«      }| j	                  |||«      }|€| j
                  j                  s|xs |S | j                  j                  |«      rdn| j                  j                  |d«      }|| j
                  j                  k\  r| j                  dd|||«      S | j                  |«      r| j                  j                  |«      nd }|�4|d   | j
                  j                  k\  r| j                  dd||d   |«      S |S )N©rS   rµ   r   r½   rÚ   rŒ   rÛ   )Ú_coerce_argsr    r¦   r®   Ú_check_loop_caprê   r:   rñ   rj   rï   rD   r  Ú_is_idempotentrò   rF   )rª   rS   r¥   rµ   r¯   Ú	cap_blockÚexact_countÚrecords           rT   Úbefore_callz'ToolCallGuardrailController.before_callc  s  € Ü˜DÓ!ˆÜ%×/Ñ/°	¸4Ó@ˆ	Ü%°	ÀYÔOˆð ×(Ñ(¨°D¸)ÓDˆ	ØÐ ¨¯©×(EÒ(EØÒ% Ð%à×7Ñ7×;Ñ;¸IÔF‘aÈD×LfÑLf×LjÑLjÐktÐvwÓLxˆØ˜$Ÿ+™+×?Ñ?Ò?Ø—<‘< Ð)GÈÐT_ÐajÓkÐkØ59×5HÑ5HÈÔ5S�×"Ñ"×&Ñ& yÔ1ÐY]ˆØÐ &¨¡)¨t¯{©{×/RÑ/RÒ"RØ—<‘< Ð)GÈÐTZÐ[\ÑT]Ð_hÓiÐiØˆrV   )Úfailedc               óH  — t        |«      }t        j                  ||«      }|€t        ||«      \  }}| j                  j
                  }|�rv| j                  j                  |d«      r| j                  j                  |d «       | j                  j                  |d«      dz   x}| j                  |<   | j                  j                  |d«      dz   x}	| j                  |<   | j                  j                  |d «       | j                  j                  r6|t        vr.|	| j                  j                  k\  r| j                  dd||	|«      S |r.|| j                  j                   k\  r| j                  dd|||«      S |r:|	| j                  j"                  k\  r!| j                  dd||	|t%        ||	«      ¬	«      S t'        |||¬
«      S | j                  j                  |d «       | j                  j                  |d «       |t(        v st+        ||«      rS| j                  j-                  t.        j1                  | j                  d«      «       | j                  j3                  «        | j5                  |«      s)| j                  j                  |d «       t'        ||¬«      S t7        |«      }
| j                  j                  |«      }|�|d   |
k(  r|d   dz   nd}|
|f| j                  |<   |r.|| j                  j8                  k\  r| j                  dd|||«      S t'        |||¬
«      S )NFr   rŒ   r¼   rÜ   r·   rÝ   Úsame_tool_failure_warningrý   )rS   r´   rµ   Tr  rÞ   )r  r    r¦   rÙ   rê   r9   rñ   Úpoprï   rj   rð   rò   r:   ÚFAILURE_TOLERANT_TOOL_NAMESrE   r  rA   rB   Ú_tool_failure_recovery_hintr®   ÚPROGRESS_RESET_TOOL_NAMESr   ÚupdaterÖ   ÚfromkeysÚclearr  Ú_result_hashrC   )rª   rS   r¥   rØ   r  rµ   Ú_Úwarningsr	  Ú
same_countÚresult_hashÚpreviousÚrepeat_counts                rT   Ú
after_callz&ToolCallGuardrailController.after_callu  sð  € ô ˜DÓ!ˆÜ%×/Ñ/°	¸4Ó@ˆ	Øˆ>Ü-¨i¸Ó@‰IˆF�AØ—;‘;×/Ñ/ˆâð ×+Ñ+×/Ñ/°	¸5ÔAØ×*Ñ*×.Ñ.¨y¸$Ô?ØBF×B\ÑB\×B`ÑB`ÐajÐlmÓBnÐqrÑBrÐrˆK˜$×4Ñ4°YÑ?ØEI×EcÑEc×EgÑEgÐhqÐstÓEuÐxyÑEyÐyˆJ˜×7Ñ7¸	ÑBØ×Ñ×!Ñ! )¨TÔ2ð —‘×-Ò-ØÔ%@Ñ@Ø $§+¡+×"JÑ"JÒJà—|‘| FÐ,DÀiÐQ[Ð]fÓgÐgÙ˜K¨4¯;©;×+OÑ+OÒOØ—|‘| FÐ,LÈiÐYdÐfoÓpÐpÙ˜J¨$¯+©+×*RÑ*RÒRØ—|‘|ØÐ7¸ÀJÐPYÜ7¸	À:ÓNð $ó ð ô )°9ÀKÐ[dÔeÐeà×"Ñ"×&Ñ& y°$Ô7Ø×&Ñ&×*Ñ*¨9°dÔ;ð Ô1Ñ1Ô5PÐQZÐ\bÔ5cØ×(Ñ(×/Ñ/´·±¸d×>XÑ>XÐZ^Ó0_Ô`Ø×*Ñ*×0Ñ0Ô2Ø×"Ñ" 9Ô-Ø×Ñ×!Ñ! )¨TÔ2Ü(°9È	ÔRÐRä" 6Ó*ˆØ×$Ñ$×(Ñ(¨Ó3ˆØ*2Ð*>À8ÈAÁ;ÐR]ÒC]�x ‘{ Q’ÐcdˆØ(3°\Ð'Bˆ×Ñ˜)Ñ$Ù˜¨¯©×(JÑ(JÒJØ—<‘< Ð(HÈ)ÐUaÐclÓmÐmÜ$¨yÀÐXaÔbÐbrV   c                óf   — || j                   j                  vxr || j                   j                  v S r�   )rê   rˆ   r…   )rª   rS   s     rT   r  z*ToolCallGuardrailController._is_idempotent±  s+   € Ø §¡× :Ñ :Ð:Òh¸yÈDÏKÉK×LhÑLhÐ?hÐhrV   r²   F)Útool_call_idr  c               óf  — t        |t        «      }t        j                  |t	        |«      «      }|rt        |«      nd}|r3||f| j                  | j                  fk(  r| xj                  dz  c_        n(|r|nd| _        || _        |rdnd| _        |xs d| _	        | j                  }	d}
t        |«      sy|	t        k\  rpt        j                  t        |	«      |¬«      }
| j                  j                   r:|	| j                  j"                  k\  r!| j$                  €| j'                  dd||	|«       |r(| j(                  j+                  ||t        |«      f«       n| j(                  j-                  «        |
€ƒ|r�| j/                  «       }|�o|\  }}t0        j                  |||¬«      }
| j                  j                   r<|| j                  j"                  k\  r#| j$                  €| j'                  dd	||||¬
«       d}|r+|	dk\  r&|s$t3        |«      t4        k\  r| j7                  ||«      }t9        |
|¬«      S )uæ  Track consecutive identical calls; return notice + dedupe stub info.

        ``notice`` fires from the threshold-th consecutive identical (tool, args, result) call
        (observational, pollers exempt). ``stub`` replaces the CURRENT result from the 2nd byte-identical
        repeat â€” the tool still executed, only the context representation is deduplicated, so polling
        semantics survive; pollers are NOT exempt here since an unchanged poll is where it saves most.
        r²   rŒ   Nr   )ÚordinalrS   r¼   rß   )r´   ÚperiodrS   rà   )r"  r}   )r�   rž   )rX   rY   r    r¦   r  r  rô   rõ   rö   r÷   rU   Ú$STALL_GUARD_IDENTICAL_CALL_THRESHOLDÚ_IDENTICAL_CALL_NOTICErÿ   Ú_ordinalrê   r:   rF   ró   r  rù   Úappendr  Ú_detect_identical_cycleÚ_IDENTICAL_CYCLE_NOTICEÚlenÚIDENTICAL_RESULT_STUB_MIN_CHARSÚ_build_result_reference_stubrœ   )rª   rS   r¥   rØ   r  r  Úis_plain_strrµ   r  r´   r�   Úcycler"  Úlapsrž   s                  rT   Úobserve_callz(ToolCallGuardrailController.observe_call´  s  € ô " &¬#Ó.ˆÜ%×/Ñ/°	¼<ÈÓ;MÓNˆ	Ù.:”l 6Ô*Àˆá˜Y¨Ð4¸×9SÑ9SÐUY×UwÑUwÐ8xÒxØ×(Ò(¨AÑ-Ö(ñ 7C©ÈˆDÔ&Ø1<ˆDÔ.Ù0<©1À!ˆDÔ(Ø3?Ò3EÀ2ˆDÔ0Ø×,Ñ,ˆàˆÜ(¨Ô3¸ÔAeÒ8eÜ+×2Ñ2¼8ÀE»?ÐV_Ð2Ó`ˆFð �{‰{×,Ò,°¸$¿+¹+×:]Ñ:]Ò1]Ðbf×buÑbuÐb}Ø—‘˜VÐ%AÀ9ÈeÐU^Ô_ñ Ø×Ñ×%Ñ% y°+Ô?XÐYbÓ?cÐ&dÕeà×Ñ×$Ñ$Ô&Øˆ>™lØ×0Ñ0Ó2ˆEØÐ Ø$‘�˜Ü0×7Ñ7¸dÈ6Ð]fÐ7Óg�Ø—;‘;×0Ò0°T¸T¿[¹[×=`Ñ=`Ò5`Ðei×exÑexð  fAØ—L‘L Ð)?ÀÈDÐR[Ðdj�LÔkàˆÙ˜E QšJ©v¼#¸f»+ÔIhÒ:hØ×4Ñ4°YÀÓEˆDÜ'¨v¸DÔAÐArV   c                óª  ‡‡‡— | j                   Št        dt        dz   «      D ]©  Št        ‰«      ‰t        z  k  rŒd}	 t        ‰«      ‰|dz   z  z
  Š‰dk  rn(t        ˆˆˆfd„t        ‰«      D «       «      }|sn|dz  }ŒB|t        k\  sŒht        ‰«      D �cg c]  }‰t        ‰«      ‰z
  |z      ‘Œ }}t        d„ |D «       «      rŒ¥‰|fc S  yc c}w )a  Detect a repeating identical-call cycle ending at the latest observed call.

        Returns ``(period, laps)`` for the smallest period 2..max whose trailing laps
        (identical signature AND result per position) reach the notice threshold, else None.
        Period 1 is the consecutive streak's job. A cycle made ONLY of poller-exempt tools
        is exempt (an unchanged poll loop is legitimate waiting); one non-exempt call in
        the cycle keeps the guard armed, matching the single-call exemption semantics.
        r}   rŒ   r   c              3  ód   •K  — | ]'  }‰‰|z      d d ‰t        ‰«      ‰z
  |z      d d k(  –— Œ) y ­w)Nr}   )r)  )Ú.0ÚiÚbaseÚhistoryr"  s     €€€rT   ú	<genexpr>zFToolCallGuardrailController._detect_identical_cycle.<locals>.<genexpr>ü  sJ   øè ø€ ò  àð ˜D 1™HÑ% b qÐ)¨W´S¸³\ÀFÑ5JÈQÑ5NÑ-OÐPRÐQRÐ-SÕSñ ùs   ƒ-0c              3  ó(   K  — | ]
  \  }}}|–— Œ y ­wr�   rg   )r2  r  Ú
repeatables      rT   r6  zFToolCallGuardrailController._detect_identical_cycle.<locals>.<genexpr>  s   è ø€ Ò?Ñ&6 a¨¨J”zÑ?ùs   ‚N)rù   ÚrangeÚ_STALL_GUARD_MAX_CYCLE_PERIODr)  r#  Úall)rª   r.  Ú	lap_equalr3  Útailr4  r5  r"  s        @@@rT   r'  z3ToolCallGuardrailController._detect_identical_cycleé  sö   ú€ ð ×$Ñ$ˆÜ˜AÔ<¸qÑ@ÓAò 	$ˆFÜ�7‹|˜fÔ'KÑKÒKØØˆDàÜ˜7“| f°°q±Ñ&9Ñ9�Ø˜!’8ØÜõ  ä" 6›]ô ó �	ñ !ØØ˜‘	�ð ð Ô;Ó;ÜDIÈ&ÃMÖR¸q˜¤ G£¨vÑ 5¸Ñ 9Ó:ÐR�ÐRÜÑ?¸$Ô?Ô?Øà˜t�|Ò#ð+	$ð, ùò Ss   ÂCc                ó.   — |r|r|| j                   |<   yyy)z<Remember the spillover path a persisted result was saved to.N)rú   )rª   r  Ú	file_paths      rT   Úrecord_persisted_resultz3ToolCallGuardrailController.record_persisted_result  s   € á™IØ9BˆD×(Ñ(¨Ò6ð &ˆ<rV   c                ó  — t        t        |«      «      }t        |«      t        kD  r|dt         dz   }| j                  }|rd|› d�nd}d|› d|› d|› d	�}|r| j
                  j                  |«      nd}|r	|d
|› d�z  }|S )zKReference stub for a byte-identical duplicate result (tool + args preview).Nu   â€¦z (tool_call_id ú)r²   z3[hermes note: this result is byte-identical to the z result earlier this turnz2. Refer to that result; it has not changed. Args: rÒ   z*
[The referenced result was persisted to: uB    â€” page through it with read_file if you need the full content.])r¤   r  r)  Ú_RESULT_STUB_ARGS_PREVIEW_CHARSr÷   rú   rj   )rª   rS   r¥   Úargs_previewÚfirst_idÚrefrž   Ú
spill_paths           rT   r+  z8ToolCallGuardrailController._build_result_reference_stub  s½   € ä*¬<¸Ó+=Ó>ˆÜˆ|ÓÔ>Ò>Ø'Ð(HÔ)HÐIÈEÑQˆLØ×7Ñ7ˆÙ/7� ˜z¨Ñ+¸RˆàAÀ)Àð M'Ø'* eð ,Ø*˜^¨1ð.ð 	ñ
 DL�T×1Ñ1×5Ñ5°hÔ?ÐQUˆ
ÙØÐAÀ*Àð  NPð  Qñ  QˆDØˆrV   c                ó$  — t         j                  |«      }|€y|\  }}}t        | j                  j                  |«      t        | |«      }	}|dk(  rdn|rt        |«      nd}
|
r|r|	|k\  r| j                  d|||	||¬«      S t        | ||	|
z   «       y)zÓBlock once a per-turn cap is reached (BEFORE the call, so the (cap+1)-th is refused), else advance
        the counter and return None. delegate_task control actions spawn nothing and keep working after the cap.Nr   rŒ   r   r½   )Úcap)ræ   rj   r�   rê   r‰   Ú_subagent_spawn_countr  Úsetattr)rª   rS   r¥   rµ   ÚspecÚ	cap_fieldÚ
count_attrr±   rI  r´   Ú	increments              rT   r  z+ToolCallGuardrailController._check_loop_cap!  sœ   € ô
 �~‰~˜iÓ(ˆØˆ<ØØ&*Ñ#ˆ	�:˜tÜ˜TŸ[™[×2Ñ2°IÓ>ÄÈÈjÓ@YˆUˆØ" lÒ2‘AÑX[Ô9NÈtÔ9TÐabˆ	Ù™ ¨#¢Ø—<‘< ¨¨y¸%ÀÐPS�<ÓTÐTÜ��j %¨)Ñ"3Ô4ØrV   r�   )rê   zToolCallGuardrailConfig | None)rq   ÚNone)rq   úToolGuardrailDecision | None)r°   rY   r±   rY   rS   rY   r´   rc   rµ   r    r³   rš   r   r	   rq   r®   )rS   rY   r¥   rp   rq   r®   )
rS   rY   r¥   rp   rØ   rš   r  zbool | Nonerq   r®   ©rS   rY   rq   r|   )rS   rY   r¥   rp   rØ   rš   r  rY   r  r|   rq   rœ   )rq   ztuple[int, int] | None)r  rY   r?  rY   rq   rP  )rS   rY   r¥   rp   rq   rY   )rS   rY   r¥   úMapping[str, Any]rµ   r    rq   rQ  )rr   rs   rt   ru   rì   rë   rÀ   rü   r  r  r  r  r/  r'  r@  r+  r  rg   rV   rT   rè   rè   1  s1  „ ÙMôó&ð6 ò#ó ð#ð
 "&ñ
Øð
Ø!$ð
Ø14ð
Ø=@ð
ØM^ð
àð
à.1ð
ð 
ó
óð( "&ñ:cØð:cØ$<ð:cØFPð:càð:cð 
ó:cóxið
  "°%ñ3BØð3BØ$<ð3BØFPð3Bàð3Bà+/ð3Bð 
"ó3Bój óDCó
ð"ØðØ$5ðØBSðà	%ôrV   rè   c                óf   — t        j                  | j                  | j                  «       dœd¬«      S )zCBuild a synthetic role=tool content string for a blocked tool call.)rÔ   Ú	guardrailF)rÄ   )rÇ   rÈ   r³   r«   )r  s    rT   Útoolguard_synthetic_resultrV  2  s(   € ä�:‰: × 0Ñ 0¸x×?SÑ?SÓ?UÑVÐejÔkÐkrV   c           
     óÈ   — |j                   dvs|j                  s| S |j                   dk(  rdnd}| xs dd|› d|j                  › d|j                  › d	|j                  › d
�	z   S )z;Append runtime guidance to the current tool result content.>   r¼   r·   r¼   zTool loop hard stopzTool loop warningr²   z

[z: z; count=z; rÒ   )r°   r³   r±   r´   )rØ   r  Úlabels      rT   Úappend_toolguard_guidancerY  7  sm   € à‡�Ð.Ñ.°h×6FÒ6FØˆØ%-§_¡_¸Ò%>Ñ!ÐDW€EØŠL�b˜e E 7¨"¨X¯]©]¨O¸8ÀHÇNÁNÐCSÐSUÐV^×VfÑVfÐUgÐghÐiÑiÐirV   c                ó0   — | › d|› d�}| dk(  r|dz   S |dz   S )zDAction-oriented guidance for recovering from repeated tool failures.z has failed zÁ times this turn. This looks like a loop. Do not switch to text-only replies; keep using tools, but diagnose before retrying. First inspect the latest error/output and verify your assumptions. r"   zÜFor terminal failures, run a small diagnostic such as `pwd && ls -la` in the same tool, then try an absolute path, a simpler command, a different working directory, or a different tool such as read_file/write_file/patch.zóTry different arguments, a narrower query/path, an absolute path when relevant, or a different tool that can make progress. If the blocker is external, report the blocker after one diagnostic attempt instead of repeating the same failing path.rg   )rS   r´   Úcommons      rT   r  r  ?  sR   € ð ˆ+�\ % ð )Nð 	Nð ð
 �JÒØðYñ
ð 	
ð
 ð	_ñð rV   c                ób   — | › d| dz  cxk  rdk  rd› �S  ddddœj                  | d	z  d«      › �S )
Né   éd   é   ÚthÚstÚndÚrd)rŒ   r}   r2   é
   )rj   )r´   s    rT   r%  r%  S  sM   € ØˆW˜R 5¨3¡;Ô4°"Ò4�TÐlÐmÐmÐ4¸dÀtÐPTÑ:U×:YÑ:YÐZ_ÐbdÑZdÐfjÓ:kÐlÐmÐmrV   c                ó*   — t        | t        «      r| S i S r�   )rX   r
   rÏ   s    rT   r  r  W  s   € Ü˜d¤GÔ,ˆ4Ð4°"Ð4rV   c                ó`   — t        | xs d«      }t        |�t        |«      «      S | xs d«      S )Nr²   )r   r£   rË   )rØ   Úparseds     rT   r  r  [  s2   € Ü˜Vš\ rÓ*€FÜ¨fÐ.@”? 6Ó*ÓUÐUÀvÂ|ÐQSÓUÐUrV   )Ú1ÚtrueÚyesÚonÚenabled)Ú0ÚfalseÚnoÚoffÚdisabledFc                óÖ   — t        | t        t        t        f«      rt        | «      S t        | t        «      r2t
        j                  | j                  «       j                  «       |«      S |S r�   )	rX   r|   rc   ÚfloatrY   Ú_BOOL_WORDSrj   rZ   r[   )rÊ   rk   s     rT   r•   r•   c  sL   € Ü�%œ$¤¤UÐ+Ô,Ü�E‹{ÐÜ�%œÔÜ�‰˜uŸ{™{›}×2Ñ2Ó4°gÓ>Ð>Ø€NrV   c                óZ   — 	 t        | «      }||k\  r|S |S # t        t        f$ r |cY S w xY w)zRjunk/None/below-minimum fall back to default (caps use minimum 0 so 0 = disabled).)rc   rÍ   Ú
ValueError)rÊ   rk   Úminimumrg  s       rT   ri   ri   k  s?   € ðÜ�U“ˆð ˜wÒ&ˆ6Ð3¨GÐ3øô ”zÐ"ò ØŠðús   ‚ –*©*c                óÜ   — t        | j                  d«      xs d«      j                  «       j                  «       dv ry| j                  d«      }t	        |t
        «      r|rt        |«      S dS )ziSubagents one delegate_task call spawns: ``len(tasks)`` for a non-empty batch, else 1; control actions 0.r°   r²   )ÚlistÚsteerÚstopr   ÚtasksrŒ   )rY   rj   rZ   r[   rX   ry  r)  )r¥   r|  s     rT   rJ  rJ  t  s[   € ä
ˆ4�8‰8�HÓÒ# Ó$×*Ñ*Ó,×2Ñ2Ó4Ð8QÑQØØ�H‰H�WÓ€EÜ# E¬4Ô0±UŒ3ˆu‹:ÐAÀÐArV   c                óh   — t        j                  | j                  dd«      «      j                  «       S )Nzutf-8Úsurrogatepass)ÚhashlibÚsha256ÚencodeÚ	hexdigestrÉ   s    rT   r£   r£   |  s'   € ô �>‰>˜%Ÿ,™, w°Ó@ÓA×KÑKÓMÐMrV   rR  )r^   rš   rq   r|   )rÊ   r	   rq   rY   )r¥   rS  rq   rY   )rS   rY   rØ   rš   rq   ztuple[bool, str])r  r®   rq   rY   )rØ   rY   r  r®   rq   rY   )rS   rY   r´   rc   rq   rY   )r´   rc   rq   rY   )r¥   rp   rq   rS  )rØ   rš   rq   rY   )rÊ   r	   rk   r|   rq   r|   )rÊ   r	   rk   rc   rw  rc   rq   rc   )r¥   rS  rq   rc   )rÊ   rY   rq   rY   )@ru   Ú
__future__r   r  rÇ   Úcollectionsr   Údataclassesr   r   r   r   Útypingr	   r
   Úutilsr   Ú agent.tool_result_classificationr   r   Ú	frozensetr‚   r‡   rP   rR   r#  r:  rø   r*  rC  r  r  r”   rG   rw   rv   rx   r\   rU   r_   rb   r{   rœ   r    r®   rË   r¤   rÙ   rã   r$  r(  ræ   rè   rV  rY  r  r%  r  r  rt  r•   ri   rJ  r£   )Úws   0rT   ú<module>r‹     s«  ðòõ #ã Û Ý ß 8Ó 8ß å !ß ^ñ "ò #ó Ð ñ  ò !ó Ð ñ  )Ð*:Ð);Ó<Ð Ø#;Ð  à'(Ð $ð !"Ð àÐ ð #&Ð Ø"%Ð ñ
 (ò )ó Ð ñ &ò 'ó Ð ð ^€ð !@Ø$GØFØ!EØ$LØLñ2Ð Ð.ó ð &(Ð "Ø"$Ð ñ  Ò ZÓ[Ð ómó
?ñ �$Ô÷cð có ðcñ �$Ô÷#gð #gó ð#gñL �$Ô÷ð ó ðñ �$Ô÷ð ó ðñ �$Ô÷ð ó ðó2eó!ózð<	Nð	að	Tð	Rð	Að	sð	Ið
	wð	tñG&&Ð �Nó &ðR#ð ð#ð ð XØSñ/€
Ð+ó ÷~ñ ~óBló
jóó(nó5óVð
 !FÖF˜1ˆq�$‰wÒFð  ]Dö  JEÐWXÈ!ÈUÉ(ò  JEñ  E€óó4óBôNùò9 Gùò  JEs   Å
FÅ,
F