
    `gj3G                    l   d Z ddlmZ ddlZddlZddlmZmZ ddlm	Z	m
Z
 ddlmZ ddlmZ  eh d      Z eh d	      Z ed
       G d d             Z ed
       G d d             Z ed
       G d d             ZddZddZ G d d      Zd dZd!dZd"dZd#dZd$dZd%dZd&dZd'dZy)(a"  Pure tool-call loop guardrail primitives.

The controller in this module is intentionally side-effect free: it tracks
per-turn tool-call observations and returns decisions. Runtime code owns whether
those decisions become warning guidance, synthetic tool results, or controlled
turn halts.
    )annotationsN)	dataclassfield)AnyMapping)safe_json_loads)file_mutation_result_landed>   	read_file
web_search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>   todopatchmemorycronjobprocessterminal
write_filebrowser_typeexecute_codesend_messageskill_managebrowser_clickbrowser_pressdelegate_taskbrowser_scrollbrowser_navigateT)frozenc                      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<    ed       Zded<    ed       Zded<   edd       Zy)ToolCallGuardrailConfiga  Thresholds for per-turn tool-call loop detection.

    Warnings are enabled by default and never prevent tool execution. Hard stops
    are explicit opt-in so interactive CLI/TUI sessions get a gentle nudge unless
    the user enables circuit-breaker behavior in config.yaml.
    Tboolwarnings_enabledFhard_stop_enabled   intexact_failure_warn_after   exact_failure_block_after   same_tool_failure_warn_after   same_tool_failure_halt_afterno_progress_warn_afterno_progress_block_afterc                     t         S N)IDEMPOTENT_TOOL_NAMES     H/root/.hermes/venv/lib/python3.12/site-packages/agent/tool_guardrails.py<lambda>z ToolCallGuardrailConfig.<lambda>P   s    EZ r?   )default_factoryzfrozenset[str]idempotent_toolsc                     t         S r<   )MUTATING_TOOL_NAMESr>   r?   r@   rA   z ToolCallGuardrailConfig.<lambda>Q   s    CV r?   mutating_toolsc                   t        |t              s |        S |j                  d      }t        |t              si }|j                  d      }t        |t              si } |        } | t        |j                  d      |j                        t        |j                  d      |j
                        t        |j                  d|j                  d            |j                        t        |j                  d|j                  d            |j                        t        |j                  d	|j                  d
            |j                        t        |j                  d|j                  d            |j                        t        |j                  d|j                  d            |j                        t        |j                  d	|j                  d            |j                              S )zABuild config from the `tool_loop_guardrails` config.yaml section.
warn_afterhard_stop_afterr.   r/   exact_failurer2   same_tool_failurer6   idempotent_no_progressr9   r4   r8   r:   )r.   r/   r2   r6   r9   r4   r8   r:   )
isinstancer   get_as_boolr.   r/   _positive_intr2   r6   r9   r4   r8   r:   )clsdatarH   rI   defaultss        r@   from_mappingz$ToolCallGuardrailConfig.from_mappingS   s    $(5LXXl+
*g.J((#45/73 O5%dhh/A&BHD]D]^&txx0C'DhF`F`a%29S0TU11& *72DHH=[4\]55* $17BZ9[\//$ '4##OTXX>Y5Z[22' *7##$7B`9ab55* %2##$<dhhG`>ab00%/
 	
r?   N)rR   Mapping[str, Any] | Nonereturnz'ToolCallGuardrailConfig')__name__
__module____qualname____doc__r.   __annotations__r/   r2   r4   r6   r8   r9   r:   r   rC   rF   classmethodrT   r>   r?   r@   r,   r,   ?   s     "d!#t#$%c%%&s&() #)() #)"#C##$S$',=Z'[n[%*;V%WNNW(
 (
r?   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.str	tool_name	args_hashc                F    t        |xs i       } | |t        |            S )Nr`   ra   )canonical_tool_args_sha256)rQ   r`   args	canonicals       r@   	from_callzToolCallSignature.from_call   s"    '
3	Y')2DEEr?   c                4    | j                   | j                  dS )z3Return public metadata without raw argument values.rc   rc   selfs    r@   to_metadatazToolCallSignature.to_metadata   s    !^^$..IIr?   N)r`   r_   rf   rU   rV   z'ToolCallSignature')rV   zdict[str, str])rW   rX   rY   rZ   r[   r\   rh   rl   r>   r?   r@   r^   r^      s)    NNNF FJr?   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.allowr_   actioncode messager`   r   r1   countNzToolCallSignature | None	signaturec                    | j                   dv S )N>   warnro   rp   rj   s    r@   allows_executionz&ToolGuardrailDecision.allows_execution       {{///r?   c                    | j                   dv S )N>   haltblockrx   rj   s    r@   should_haltz!ToolGuardrailDecision.should_halt   rz   r?   c                    | j                   | j                  | j                  | j                  | j                  d}| j
                  | j
                  j                         |d<   |S )N)rp   rq   rs   r`   rt   ru   )rp   rq   rs   r`   rt   ru   rl   )rk   rR   s     r@   rl   z!ToolGuardrailDecision.to_metadata   sT    kkII||ZZ 
 >>% $ : : <Dr?   )rV   r-   )rV   zdict[str, Any])rW   rX   rY   rZ   rp   r[   rq   rs   r`   rt   ru   propertyry   r~   rl   r>   r?   r@   rn   rn      sl    BFCD#GSIsE3N*.I'.0 0 0 0
r?   rn   c                    t        | t              s!t        dt        |       j                         t        j                  | dddt              S )z5Return sorted compact JSON for parsed tool arguments.z!tool args must be a mapping, got FT,:ensure_ascii	sort_keys
separatorsdefault)rM   r   	TypeErrortyperW   jsondumpsr_   rf   s    r@   rd   rd      sJ    dG$;DJ<O<O;PQRR:: r?   c                   |yt        | |      ry| dk(  r<t        |      }t        |t              r |j	                  d      }||dk7  rdd| dfS y| 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yy)a  Safety-fallback classifier used only when callers don't pass ``failed``.

    Mirrors ``agent.display._detect_tool_failure`` exactly so the guardrail
    never disagrees with the CLI's user-visible ``[error]`` tag. Production
    callers in ``run_agent.py`` always pass an explicit ``failed=`` derived
    from ``_detect_tool_failure``; this function exists so standalone callers
    (tests, tooling) still get consistent behavior.
    N)Frr   r   	exit_coder   Tz [exit ]r   successFzexceed the limiterrorrr   )Tz [full]i  z"error"z"failed"Error)Tz [error])r	   r   rM   dictrN   lower
startswith)r`   resultrR   r   r   s        r@   classify_tool_failurer      s     ~"9f5Jv&dD!-I$awyk333Hv&dD!xx	"e+0BdhhwXZF[0[&4CL EEZ50F4E4Eg4Nr?   c                  d    e Zd ZdZd
ddZddZedd       ZddZdd	 	 	 	 	 	 	 	 	 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)rk   r   s     r@   __init__z$ToolCallGuardrailController.__init__   s    9 7 9r?   c                <    i | _         i | _        i | _        d | _        y r<   )_exact_failure_counts_same_tool_failure_counts_no_progress_halt_decisionrj   s    r@   r   z*ToolCallGuardrailController.reset_for_turn   s"    CE"9;&FH<@r?   c                    | j                   S r<   )r   rj   s    r@   halt_decisionz)ToolCallGuardrailController.halt_decision   s    """r?   c           	     &   t         j                  |t        |            }| j                  j                  st        ||      S | j                  j                  |d      }|| j                  j                  k\  r!t        ddd| d| d|||      }|| _	        |S | j                  |      r\| j                  j                  |      }|?|\  }}|| j                  j                  k\  r!t        dd	d| d
| d|||      }|| _	        |S t        ||      S )Nr`   ru   r   r}   repeated_exact_failure_blockzBlocked z: the same tool call failed zd times with identical arguments. Stop retrying it unchanged; change strategy or explain the blocker.rp   rq   rs   r`   rt   ru   idempotent_no_progress_blockz/: this read-only call returned the same result z^ times. Stop repeating it unchanged; use the result already provided or try a different query.)r^   rh   _coerce_argsr   r/   rn   r   rN   r4   r   _is_idempotentr   r:   )	rk   r`   rf   ru   exact_countdecisionrecord_result_hashrepeat_counts	            r@   before_callz'ToolCallGuardrailController.before_call   s:   %//	<;MN	{{,,(9	RR0044YB$++???,3yk)Ek] S> > $!#H #+DOy)&&**95F!-3*l4;;#F#FF4&;&yk 2&&2^ 4XX #,*"+ H +3D'#O$yINNr?   )failedc          	     Z   t        |      }t        j                  ||      }|t        ||      \  }}|rf| j                  j                  |d      dz   }|| j                  |<   | j                  j                  |d        | j                  j                  |d      dz   }|| j                  |<   | j                  j                  r:|| j                  j                  k\  r!t        ddd| d| d|||      }	|	| _        |	S | j                  j                  r0|| j                  j                  k\  rt        d	d
| d| d|||      S | j                  j                  r4|| j                  j                   k\  rt        d	dt#        ||      |||      S t        |||      S | j                  j                  |d        | j                  j                  |d        | j%                  |      s)| j                  j                  |d        t        ||      S t'        |      }
| j                  j                  |      }d}||d   |
k(  r|d   dz   }|
|f| j                  |<   | j                  j                  r0|| j                  j(                  k\  rt        d	d| d| d|||      S t        |||      S )Nr      r|   same_tool_failure_haltzStopped z: it failed z[ times this turn. Stop retrying the same failing tool path and choose a different approach.r   rw   repeated_exact_failure_warning has failed z times with identical arguments. This looks like a loop; inspect the error and change strategy instead of retrying it unchanged.same_tool_failure_warning)r`   rt   ru   r   idempotent_no_progress_warningz returned the same result z^ times. Use the result already provided or change the query instead of repeating it unchanged.)r   r^   rh   r   r   rN   r   popr   r   r/   r8   rn   r   r.   r2   r6   _tool_failure_recovery_hintr   r   r9   )rk   r`   rf   r   r   ru   _r   
same_countr   result_hashpreviousr   s                r@   
after_callz&ToolCallGuardrailController.after_call  s    D!%//	4@	>-i@IFA4488AFJK4?D&&y1!!)T277;;IqIAMJ8BD**95{{,,t{{?g?g1g0!1"9+\* Fd d ($'
 '/#{{++t{{?c?c0c,!9$+\+ ?< < (%'  {{++
dkk>f>f0f,!47	:N'$'  )9K[dee""&&y$7&&**9d;""9-!!)T2(9	RR"6*$$((3HQK;$>#A;?L(3\'B)$;;''LDKK<^<^,^(5 k!;L> J. . $"#  %yXabbr?   c                d    || j                   j                  v ry|| j                   j                  v S )NF)r   rF   rC   )rk   r`   s     r@   r   z*ToolCallGuardrailController._is_idempotenty  s,    222DKK8888r?   r<   )r   zToolCallGuardrailConfig | None)rV   None)rV   zToolGuardrailDecision | None)r`   r_   rf   rU   rV   rn   )
r`   r_   rf   rU   r   
str | Noner   zbool | NonerV   rn   )r`   r_   rV   r-   )rW   rX   rY   rZ   r   r   r   r   r   r   r   r>   r?   r@   r   r      sw    MA # #*Od #ZcZc 'Zc 	Zc Zc 
Zcx9r?   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   rs   rl   )r   s    r@   toolguard_synthetic_resultr     s2    ::%%!--/	
  r?   c           	         |j                   dvs|j                  s| S |j                   dk(  rdnd}d| d|j                   d|j                   d|j                   d		}| xs d
|z   S )z;Append runtime guidance to the current tool result content.>   r|   rw   r|   zTool loop hard stopzTool loop warningz

[z: z; count=z; r   rr   )rp   rs   rq   rt   )r   r   labelsuffixs       r@   append_toolguard_guidancer     sx    ..h6F6F%-__%>!DWE
wb==/(..!1H4D4D3EQ	H  LbF""r?   c                0    |  d| d}| dk(  r|dz   S |dz   S )zDAction-oriented guidance for recovering from repeated tool failures.r   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   zFor 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.zTry 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.r>   )r`   rt   commons      r@   r   r     sR     +\% )N 	N 
 JY
 	

 	_ r?   c                *    t        | t              r| S i S r<   )rM   r   r   s    r@   r   r     s    dG,44"4r?   c                    t        | xs d      }|*	 t        j                  |dddt              }t        |      S | xs d}t        |      S # t        $ r t        |      }Y t        |      S w xY w)Nrr   FTr   r   )r   r   r   r_   r   re   )r   parsedrg   s      r@   r   r     s    V\r*F		$

"%I 9 Lb	9	  	$FI 9		$s   A A.-A.c                    | |S t        | t              r| S t        | t        t        f      rt        |       S t        | t              r(| j                         j                         }|dv ry|dv ry|S )N>   1onyestrueenabledT>   0noofffalsedisabledF)rM   r-   r1   floatr_   stripr   )valuer   lowereds      r@   rO   rO     sn    }%%#u&E{%++-%%';;==Nr?   c                b    | |S 	 t        |       }|dk\  r|S |S # t        t        f$ r |cY S w xY w)Nr   )r1   r   
ValueError)r   r   r   s      r@   rP   rP     sH    }U q[6-g- z" s    ..c                h    t        j                  | j                  dd            j                         S )Nzutf-8surrogatepass)hashlibsha256encode	hexdigest)r   s    r@   re   re     s'    
 >>%,,w@AKKMMr?   )rf   Mapping[str, Any]rV   r_   )r`   r_   r   r   rV   ztuple[bool, str])r   rn   rV   r_   )r   r_   r   rn   rV   r_   )r`   r_   rt   r1   rV   r_   )rf   rU   rV   r   )r   r   rV   r_   )r   r   r   r-   rV   r-   )r   r   r   r1   rV   r1   )r   r_   rV   r_   ) rZ   
__future__r   r   r   dataclassesr   r   typingr   r   utilsr    agent.tool_result_classificationr	   	frozensetr=   rE   r,   r^   rn   rd   r   r   r   r   r   r   r   rO   rP   re   r>   r?   r@   <module>r      s    #   (  ! H " *   , $<
 <
 <
~ $J J J  $  >
 F\9 \9~	#(5$ .Nr?   