
    sj                    |   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
dh          ZdZdZdZdZd<dZ ed           G d d                      ZdZ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d=d$Zd>d(Z  G d) d*          Z!d?d,Z"d@d-Z#dAd0Z$dBd2Z%dCd3Z&dDd7Z'dEd8Z(dEd9Z)dFd:Z*dGd;Z+dS )Ha"  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_navigater   bfl_flux3_get_result)_get_result_poll   i   x   	tool_namestrreturnboolc                L    | t           v rdS |                     t                    S )z=Whether a tool is exempt from the identical-call loop notice.T)STALL_GUARD_REPEATABLE_TOOLSendswith _STALL_GUARD_REPEATABLE_SUFFIXES)r/   s    9/home/agent/.hermes/hermes-agent/agent/tool_guardrails.pyis_stall_guard_repeatabler8   f   s(    000t>???    T)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           Zded<   ed!d            Zd S )"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.
    Tr2   warnings_enabledFhard_stop_enabled   intexact_failure_warn_after   exact_failure_block_afterr-   same_tool_failure_warn_after   same_tool_failure_halt_afterno_progress_warn_afterno_progress_block_afterc                     t           S N)IDEMPOTENT_TOOL_NAMES r9   r7   <lambda>z ToolCallGuardrailConfig.<lambda>~   s    EZ r9   )default_factoryzfrozenset[str]idempotent_toolsc                     t           S rJ   )MUTATING_TOOL_NAMESrL   r9   r7   rM   z ToolCallGuardrailConfig.<lambda>   s    CV r9   mutating_toolsc                     t                      S rJ   )LoopCapConfigrL   r9   r7   rM   z ToolCallGuardrailConfig.<lambda>   s
    } r9   'LoopCapConfig'	loop_capsdataMapping[str, Any] | Noner1   'ToolCallGuardrailConfig'c                   t          |t                    s
 |             S |                    d          }t          |t                    si }|                    d          }t          |t                    si } |             } | t          |                    d          |j                  t          |                    d          |j                  t          |                    d|                    d                    |j                  t          |                    d|                    d                    |j                  t          |                    d	|                    d
                    |j	                  t          |                    d|                    d                    |j
                  t          |                    d|                    d                    |j                  t          |                    d	|                    d                    |j                  t                              |                    d                    	  	        S )zABuild config from the `tool_loop_guardrails` config.yaml section.
warn_afterhard_stop_afterr=   r>   exact_failurerA   same_tool_failurerD   idempotent_no_progressrG   rC   rF   rH   rV   )	r=   r>   rA   rD   rG   rC   rF   rH   rV   )
isinstancer   get_as_boolr=   r>   _positive_intrA   rD   rG   rC   rF   rH   rT   from_mapping)clsrW   r[   r\   defaultss        r7   rd   z$ToolCallGuardrailConfig.from_mapping   s)    $(( 	355LXXl++
*g.. 	J((#455/733 	! O355s%dhh/A&B&BHD]^^&txx0C'D'DhF`aa%29S0T0TUU1& & *72DHH=[4\4\]]5* * $17BZ9[9[\\/$ $ '4##OTXX>Y5Z5Z[[2' ' *7##$7B`9a9abb5* * %2##$<dhhG`>a>abb0% % $00+1F1FGG7
 
 
 	
r9   N)rW   rX   r1   rY   )__name__
__module____qualname____doc__r=   __annotations__r>   rA   rC   rD   rF   rG   rH   r   rO   rR   rV   classmethodrd   rL   r9   r7   r<   r<   m   s7          "!!!!#####$%%%%%%&&&&&() ))))() ))))"######$$$$$',u=Z=Z'['['[[[[[%*U;V;V%W%W%WNWWWW!&7N7N!O!O!OIOOOO)
 )
 )
 [)
 )
 )
r9   r<   2   c                  H    e Zd ZU dZeZded<   eZded<   e	dd	            Z
d
S )rT   a'  Per-turn caps on runaway-prone tool calls.

    Inspired by Claude Code v2.1.212 (Week 29, July 2026), which added caps on
    WebSearch calls and subagent spawns to stop runaway search / delegation
    loops. Here the caps count *within a single agent loop* (one turn): the
    counters reset in ``reset_for_turn`` at the start of every
    ``run_conversation``, so a legitimate multi-turn session is never starved,
    but a single turn that spirals into an unbounded search / delegation loop
    is stopped.

    Semantics differ from the per-turn loop *detector* above (which keys on
    repeated identical/failing calls): these caps are a hard ceiling on the
    total count of a tool within the turn and fire regardless of
    ``hard_stop_enabled``. A value of ``0`` disables the cap (unlimited).
    r@   max_web_searchesmax_subagentsrW   rX   r1   rU   c                   t          |t                    s
 |             S  |             } | t          |                    d          |j                  t          |                    d          |j                            S )zABuild config from the ``tool_loop_guardrails.loop_caps`` section.ro   rp   )ro   rp   )r`   r   _non_negative_intra   ro   rp   )re   rW   rf   s      r7   rd   zLoopCapConfig.from_mapping   s     $(( 	355L355s.+,,h.G  ,))8+A 	
 
 
 	
r9   N)rW   rX   r1   rU   )rg   rh   ri   rj   "_DEFAULT_MAX_WEB_SEARCHES_PER_TURNro   rk   _DEFAULT_MAX_SUBAGENTS_PER_TURNrp   rl   rd   rL   r9   r7   rT   rT      sc            ?>>>>8M8888
 
 
 [
 
 
r9   rT   c                  0    e Zd ZU dZdZded<   dZded<   dS )IdenticalCallObservationa  Outcome of observing one completed tool call for the stall guards.

    ``notice`` is the identical-call loop-breaker notice (appended after the
    result). ``stub`` is the result-reference replacement for a byte-identical
    duplicate result (replaces the result content). Both may be set on the
    same call (3rd+ identical call): the stub replaces the payload and the
    notice is appended after it.
    N
str | Nonenoticestub)rg   rh   ri   rj   rx   rk   ry   rL   r9   r7   rv   rv      s@           FDr9   rv   c                  H    e Zd ZU dZded<   ded<   edd	            ZddZdS )ToolCallSignaturezDStable, non-reversible identity for a tool name plus canonical args.r0   r/   	args_hashargsrX   r1   'ToolCallSignature'c                X    t          |pi           } | |t          |                    S )Nr/   r|   )canonical_tool_args_sha256)re   r/   r}   	canonicals       r7   	from_callzToolCallSignature.from_call   s0    '
33	sY')2D2DEEEEr9   dict[str, str]c                     | j         | j        dS )z3Return public metadata without raw argument values.r   r   selfs    r7   to_metadatazToolCallSignature.to_metadata   s    !^$.IIIr9   N)r/   r0   r}   rX   r1   r~   )r1   r   )rg   rh   ri   rj   rk   rl   r   r   rL   r9   r7   r{   r{      sj         NNNNNNNNF F F [FJ J J J J Jr9   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dS )ToolGuardrailDecisionz8Decision returned by the tool-call guardrail controller.allowr0   actioncode messager/   r   r@   countNzToolCallSignature | None	signaturer1   r2   c                    | j         dv S )N>   warnr   r   r   s    r7   allows_executionz&ToolGuardrailDecision.allows_execution	      {///r9   c                    | j         dv S )N>   haltblockr   r   s    r7   should_haltz!ToolGuardrailDecision.should_halt  r   r9   dict[str, Any]c                    | j         | j        | j        | j        | j        d}| j        | j                                        |d<   |S )N)r   r   r   r/   r   r   )r   r   r   r/   r   r   r   )r   rW   s     r7   r   z!ToolGuardrailDecision.to_metadata  sN    kI|Z 
  
 >% $ : : < <Dr9   )r1   r2   )r1   r   )rg   rh   ri   rj   r   rk   r   r   r/   r   r   propertyr   r   r   rL   r9   r7   r   r      s         BBFDGIENNNN*.I....0 0 0 X0 0 0 0 X0
 
 
 
 
 
r9   r   r}   Mapping[str, Any]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)r`   r   	TypeErrortyperg   jsondumpsr0   r}   s    r7   r   r     s]    dG$$ SQDJJ<OQQRRR:   r9   resultrw   tuple[bool, str]c                    |dS t          | |          rdS | dk    rKt          |          }t          |t                    r%|                    d          }||dk    rdd| dfS dS | d	k    rUt          |          }t          |t                    r1|                    d
          du rd|                    dd          v rdS |dd                                         }d|v sd|v s|                    d          rdS dS )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)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`   dictra   lower
startswith)r/   r   rW   r   r   s        r7   classify_tool_failurer   +  s6    ~y"9f55 yJv&&dD!! 	4--I$a3y33333yHv&&dD!! 	'xx	""e++0BdhhwXZF[F[0[0[&4C4L  EEZ500F4E4Eg4N4N09r9   c                      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
d.dZdddd/dZd0d!Zd1d"Zd2d&ZdS )3ToolCallGuardrailControllerzCPer-turn controller for repeated failed/non-progressing tool calls.NconfigToolCallGuardrailConfig | Nonec                X    |pt                      | _        |                                  d S rJ   )r<   r   reset_for_turn)r   r   s     r7   __init__z$ToolCallGuardrailController.__init__Q  s-    9 7 9 9r9   r1   Nonec                    i | _         i | _        i | _        d | _        d | _        d| _        d| _        d| _        i | _        d| _	        d| _
        d S )Nr   r   )_exact_failure_counts_same_tool_failure_counts_no_progress_halt_decision_identical_streak_sig_identical_streak_result_hash_identical_streak_count_identical_streak_first_call_id_persisted_result_paths_turn_web_search_count_turn_subagent_countr   s    r7   r   z*ToolCallGuardrailController.reset_for_turnU  sj    CE"9;&FH<@ @D"24*,-$ 57,
 8:$ '(#$%!!!r9   ToolGuardrailDecision | Nonec                    | j         S rJ   )r   r   s    r7   halt_decisionz)ToolCallGuardrailController.halt_decisiont  s    ""r9   r/   r0   r}   rX   r   c           	     t   t                               |t          |                    }|                     |t          |          |          }||S | j        j        st          ||          S | j                            |d          }|| j        j	        k    r%t          ddd| d| d|||          }|| _
        |S |                     |          rV| j                            |          }|:|\  }}	|	| j        j        k    r%t          dd	d| d
|	 d||	|          }|| _
        |S t          ||          S )Nr/   r   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.r   r   r   r/   r   r   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{   r   _coerce_args_check_loop_capr   r>   r   r   ra   rC   r   _is_idempotentr   rH   )
r   r/   r}   r   	cap_blockexact_countdecisionrecord_result_hashrepeat_counts
             r7   before_callz'ToolCallGuardrailController.before_callx  s   %//	<;M;MNN	 ((L4F4F	RR	 {, 	S(9	RRRR044YBB$+???,3>y > >k > > > $!#  H #+DOy)) 	$&**955F!-3*l4;#FFF4&;Xy X X&2X X X #,*"+     H +3D'#O$yINNNNr9   )failedr   rw   r   bool | Nonec          	        t          |          }t                              ||          }|t          ||          \  }}|r4| j                            |d          dz   }|| j        |<   | j                            |d            | j                            |d          dz   }|| j        |<   | j	        j
        r5|| j	        j        k    r%t          ddd| d| d|||          }	|	| _        |	S | j	        j        r+|| j	        j        k    rt          d	d
| d| d|||          S | j	        j        r3|| j	        j        k    r#t          d	dt#          ||          |||          S t          |||          S | j                            |d            | j                            |d            |                     |          s,| j                            |d            t          ||          S t'          |          }
| j                            |          }d}||d         |
k    r|d         dz   }|
|f| j        |<   | j	        j        r+|| 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   r   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/   r   r   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{   r   r   r   ra   r   popr   r   r>   rF   r   r   r=   rA   rD   _tool_failure_recovery_hintr   r   rG   )r   r/   r}   r   r   r   _r   
same_countr   result_hashpreviousr   s                r7   
after_callz&ToolCallGuardrailController.after_call  sA    D!!%//	4@@	>-i@@IFA /	f488AFFJK4?D&y1!!)T2227;;IqIIAMJ8BD*95{,  t{?g1g1g0!1d9 d d* d d d ($'
 
 
 '/#{+ t{?c0c0c,!9$ < <+ < < < (%'    {+ 
dk>f0f0f,!47	:NN'$'    )9K[deeee"&&y$777&**9d;;;""9-- 	S!!)T222(9	RRRR"6**$((33HQK;$>$>#A;?L(3\'B)$;' 	LDK<^,^,^(5  . .L . . . $"#    %yXabbbbr9   r2   c                >    || j         j        v rdS || j         j        v S )NF)r   rR   rO   )r   r/   s     r7   r   z*ToolCallGuardrailController._is_idempotent  s'    2225DK888r9   c                :    |                      |||          j        S )zTrack consecutive identical calls; return a loop-breaker notice or None.

        Back-compat wrapper around :meth:`observe_call` for callers that only
        care about the loop-breaker notice.
        )observe_callrx   )r   r/   r}   r   s       r7   observe_identical_callz2ToolCallGuardrailController.observe_identical_call  s       D&99@@r9   r   F)tool_call_idr   r   'IdenticalCallObservation'c                  t          |t                    }t                              |t	          |                    }|rt          |          nd}|r'| j        |k    r| j        |k    r| xj        dz  c_        n&|r|nd| _        || _        |rdnd| _        |pd| _	        | j        }	d}
t          |          sJ|	t          k    r?|	 d|	dz  cxk    rdk    rn ndnd	d
dd                    |	dz  d           }d| d| d}
d}|r6|	dk    r0|s.t          |          t          k    r|                     ||          }t!          |
|          S )u@  Track consecutive identical calls; return notice + dedupe stub info.

        Two independent outputs from the same consecutive-streak tracker:

        - ``notice``: the compact loop-breaker notice, fired when the SAME
          tool is called with identical canonical arguments AND returns an
          identical result for the ``STALL_GUARD_IDENTICAL_CALL_THRESHOLD``-th
          (and every subsequent) consecutive time within the turn. Purely
          observational — never blocks the call. Allowlisted pollers
          (``is_stall_guard_repeatable``) are exempt from the NOTICE.
        - ``stub``: a short reference replacement for the CURRENT result,
          produced from the 2nd consecutive identical call whose fresh result
          is byte-identical to the previous one. The tool still executed —
          only the context representation is deduplicated, so polling
          semantics are preserved (a changed result flows through whole and
          resets the streak). Pollers are NOT exempt from stubbing: for a
          poller, an identical result means nothing changed, which is exactly
          when the stub saves the most context and loses nothing. Results
          under ``IDENTICAL_RESULT_STUB_MIN_CHARS`` and failed/error results
          are never stubbed, and only plain-string results are considered.

        Any intervening different call or changed result resets the streak.
        Callers substitute/append at tool RESULT construction time, which is
        cache-safe: tool results are append-only and never mutate
        already-sent context.
        r   r   Nr      d      thstndrd)r   r?   r-   
   z[hermes note: this is the z consecutive identical call to u    with identical arguments returning the same result. Do not repeat it — change arguments, use a different tool, or proceed with what you have.]r?   )rx   ry   )r`   r0   r{   r   r   r   r   r   r   r   r8   $STALL_GUARD_IDENTICAL_CALL_THRESHOLDra   lenIDENTICAL_RESULT_STUB_MIN_CHARS_build_result_reference_stubrv   )r   r/   r}   r   r   r   is_plain_strr   r   r   rx   ordinalry   s                r7   r   z(ToolCallGuardrailController.observe_call  s   F "&#..%//	<;M;MNN	.:Bl6*** 	F*i772kAA((A-((( 7C)LD&1<D.0<+C11!D(3?3E2D0,))44
	===xeck(?(?(?(?R(?(?(?(?(?RV[_E`E`EdEdejmoeoquEvEvxxG/W / // / /  	F

 F>>>44YEED'vDAAAAr9   	file_pathc                *    |r|r|| j         |<   dS dS dS )a  Remember the spillover path a persisted result was saved to.

        When the first occurrence of a result entered context as a
        persisted-output preview, a later reference stub must carry the
        spillover file path so the reference can't dangle.
        N)r   )r   r   r  s      r7   record_persisted_resultz3ToolCallGuardrailController.record_persisted_resultl  s@      	CI 	C9BD(666	C 	C 	C 	Cr9   c                F   	 t          t          |                    }n# t          $ r d}Y nw xY wt          |          t          k    r|dt                   dz   }| j        }|rd| dnd}d| d| d	| d
}|r| j                            |          nd}|r	|d| dz  }|S )a/  Build the reference stub replacing a byte-identical duplicate result.

        Carries the tool name + a canonical-args preview so that even if
        context compression later evicts the referenced result, the model
        still knows WHAT the call was (cheap dangling-reference mitigation).
        z{}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   r   _RESULT_STUB_ARGS_PREVIEW_CHARSr   r   ra   )r   r/   r}   args_previewfirst_idrefry   
spill_paths           r7   r  z8ToolCallGuardrailController._build_result_reference_stubv  s   	 .|D/A/ABBLL 	  	  	 LLL	 |>>>'(H)H(HIEQL7/7?+++++R.) . .'*. .*. . . 	
 DLUT155h???QU
 	Pj P P PD s    ..r   r   r{   c           	        | j         j        }|dk    rM|j        }|r2| j        |k    r't	          ddd| d|| j        |          }|| _        |S | xj        dz  c_        dS |d	k    rn|j        }|sdS t          |          }|d
k    rdS | j        |k    r/t	          ddd| j         d| d|| j        |          }|| _        |S | xj        |z  c_        dS dS )aN  Enforce and advance the per-turn runaway-loop counters.

        Returns a ``block`` decision when the cap is already reached, otherwise
        increments the relevant counter for the allowed call and returns
        ``None``. A cap of 0 disables that limit entirely. Counters reset each
        turn via ``reset_for_turn``.
        r   r   loop_web_search_capz/Blocked web_search: this turn has already made z 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.r   r   Nr'   r   loop_subagent_capz5Blocked delegate_task: this turn has already spawned z subagents (limit zl). This looks like a runaway delegation loop. Finish the work with the results you have and answer the user.)	r   rV   ro   r   r   r   rp   _subagent_spawn_countr   )r   r/   r}   r   capscapr   spawn_counts           r7   r   z+ToolCallGuardrailController._check_loop_cap  sk    {$$$'C  t2c990".># > > >
 (5'   '/#''1,''4''$C t/55Ka t(C//0",N4N NHKN N N
 (3'   '/#%%4%%4tr9   rJ   )r   r   )r1   r   )r1   r   )r/   r0   r}   rX   r1   r   )
r/   r0   r}   rX   r   rw   r   r   r1   r   r/   r0   r1   r2   )r/   r0   r}   rX   r   rw   r1   rw   )r/   r0   r}   rX   r   rw   r   r0   r   r2   r1   r   )r   r0   r  r0   r1   r   )r/   r0   r}   rX   r1   r0   )r/   r0   r}   r   r   r{   r1   r   )rg   rh   ri   rj   r   r   r   r   r   r   r   r   r   r  r  r   rL   r9   r7   r   r   N  sU       MM    & & & &> # # # X#5O 5O 5O 5Oz #Zc Zc Zc Zc Zc Zcx9 9 9 9
A A A A& MB MB MB MB MB MB^C C C C   <A A A A A Ar9   r   r   c                b    t          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    r7   toolguard_synthetic_resultr    s?    :%!--//	
 	
    r9   c           	         |j         dvs|j        s| S |j         dk    rdnd}d| d|j         d|j         d|j         d		}| pd
|z   S )z;Append runtime guidance to the current tool result content.>   r   r   r   zTool loop hard stopzTool loop warningz

[z: z; count=z; r   r   )r   r   r   r   )r   r   labelsuffixs       r7   append_toolguard_guidancer    s    ...h6F.%-_%>%>!!DWE	H 	H 	H=	H 	H"*.	H 	H4<4D	H 	H 	H  LbF""r9   r   r@   c                2    |  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.rL   )r/   r   commons      r7   r   r     sa      	N 	N% 	N 	N 	N 
 JY
 	

 	_ r9   rX   c                4    t          | t                    r| ni S rJ   )r`   r   r   s    r7   r   r     s    dG,,444"4r9   c                    t          | pd          }|?	 t          j        |dddt                    }n## t          $ r t          |          }Y nw xY w| pd}t          |          S )Nr   FTr   r   )r   r   r   r0   r   r   )r   parsedr   s      r7   r   r     s    V\r**F		$
"%  II  	$ 	$ 	$FIII	$ Lb	9s   4 AAvaluer   r   c                    | |S t          | t                    r| S t          | t          t          f          rt          |           S t          | t                    r2|                                                                 }|dv rdS |dv rdS |S )N>   1onyestrueenabledT>   0noofffalsedisabledF)r`   r2   r@   floatr0   stripr   )r#  r   lowereds      r7   rb   rb     s    }% %#u&& E{{% ++--%%'';;;4===5Nr9   c                r    | |S 	 t          |           }n# t          t          f$ r |cY S w xY w|dk    r|n|S )Nr   r@   r   
ValueErrorr#  r   r"  s      r7   rc   rc   )  sY    }Uz"   q[[66g-    ,,c                r    | |S 	 t          |           }n# t          t          f$ r |cY S w xY w|dk    r|n|S )ziParse a session-cap value. 0 is a valid (disable) value; negatives and
    junk fall back to the default.Nr   r3  r5  s      r7   rr   rr   3  s[     }Uz"   q[[66g-r6  c                p   t          | t                    rNt          |                     d          pd                                                                          }|dv rdS t          | t                    r|                     d          nd}t          |t                    r|rt          |          S dS )a  How many subagents a single delegate_task call spawns.

    delegate_task runs in one of two modes: a batch (``tasks`` is a non-empty
    list, one child per item) or a single task (``goal``). Count the batch size
    when present, otherwise 1, so the session subagent cap reflects real spawns
    rather than delegate_task invocations. Control actions (list/steer/stop)
    spawn nothing and must not consume the cap.
    r   r   )liststeerstopr   tasksNr   )r`   r   r0   ra   r0  r   r9  r   )r}   r   r<  s      r7   r  r  ?  s     $   TXXh''-2..4466<<>>...1!+D'!:!:DDHHWE% 5 5zz1r9   c                v    t          j        |                     dd                                                    S )Nzutf-8surrogatepass)hashlibsha256encode	hexdigest)r#  s    r7   r   r   R  s.    
 >%,,w@@AAKKMMMr9   r  )r}   r   r1   r0   )r/   r0   r   rw   r1   r   )r   r   r1   r0   )r   r0   r   r   r1   r0   )r/   r0   r   r@   r1   r0   )r}   rX   r1   r   )r   rw   r1   r0   )r#  r   r   r2   r1   r2   )r#  r   r   r@   r1   r@   )r}   r   r1   r@   )r#  r0   r1   r0   ),rj   
__future__r   r?  r   dataclassesr   r   typingr   r   utilsr    agent.tool_result_classificationr	   	frozensetrK   rQ   r4   r6   r   r  r
  r8   r<   rs   rt   rT   rv   r{   r   r   r   r   r  r  r   r   r   rb   rc   rr   r  r   rL   r9   r7   <module>rI     s    # " " " " "   ( ( ( ( ( ( ( (         ! ! ! ! ! ! H H H H H H "	    *  i    6  )y    $   () $ #& 
 #& @ @ @ @ $>
 >
 >
 >
 >
 >
 >
 >
N &( ""$  $!
 !
 !
 !
 !
 !
 !
 !
H $        $J J J J J J J J  $       >
 
 
 
       FG G G G G G G GT   	# 	# 	# 	#   (5 5 5 5   $    . . . .	. 	. 	. 	.   &N N N N N Nr9   