
    Jj                       U d Z ddlZddlZddlZddlZddlZddlZddlmZm	Z	 ddl
mZ ddlmZ ddlmZmZmZ ddlmZ ddlmZ  ej        e          Zej        ej        ej        hZd	ed
efdZdZda e!dz  e"d<   d
e!fdZ#dede!d
e$ee!e%f         fdZ&dZ' e(h d          Z)ddeded
ee	z  fdZ* e(h d          Z+ e(h d          Z,dded
efdZ-dded
e%fdZ.d	eez  e	z  d
e	fdZ/dedz  d
edz  fdZ0d
edz  fdZ1dded
edz  fd Z2ded
edz  fd!Z3dded
edz  fd"Z4dded
edz  fd#Z5	 ddd$ded%e%dz  d
ee	z  fd&Z6ddeded
ee	z  fd'Z7dded(eded
edz  fd)Z8d	ed
e%fd*Z9dded+eez  dz  d
e%fd,Z:dd	eded
edz  fd-Z;dded
e!fd.Z<d/Z=d0d1hZ>da?edz  e"d2<   d3a@d
edz  fd4ZAddeded
edz  fd5ZBdded
edz  fd6ZCddeded
edz  fd7ZDd8eEd
e%fd9ZF ejG                    ZHi ZIeJe"d:<   i ZKeJe"d;<   ded<edz  d
dfd=ZLdded
edz  fd>ZM ejG                    ZNi ZOeJe"d?<    ejG                    ZPi ZQeJe"d@<   dedAed
e!fdBZRdedCeSd
dfdDZTdEZUdFZVdFZWdGZXdHeJd
dfdIZYded
e%fdJZZded
e%fdKZ[ded
e%fdLZ\dded
efdMZ]ddefdNZ^dd	edPe!dQe!ded
ef
dRZ_ddefdSZ`ddefdTZadeded
dfdUZbdeded
dfdVZcdeded
edz  fdWZd	 ddedCeSe         dXedz  d
dfdYZe	 	 	 dd	edededZe%dXedz  d
efd[Zf	 	 	 	 dd]ed	ed^ed_ed`e%daededZe%dXedz  d
efdbZg	 	 	 	 ddeedfed	edgedQe!dPe!dhedie!ded
efdjZhddklimjZjmkZk dl ZldmdndodpdqdrdsdtdOdOdudsdvdEdwdxdyd	gdzd{Zmd|d}dodpd~drdpddrddd3ddd	dgdzd{Zndaddodpd\dagdd\ddpddrdpddrdpddrddd3ddpddrddd3ddd]gdzd{Zodddodpddrdpddgddddpddcddpddrdsdddddsddddpg dddddsdddddegdzd{Zpd Zqd Zrd Zsd Zt ejju        dmdemeqeldd            ejju        d|denereldd            ejju        dadeoeseldd            ejju        ddepeteldd           dS )z6File Tools Module - LLM agent file manipulation tools.    N)PathPurePosixPath)get_read_block_error)has_binary_extension)ShellFileOperationsnormalize_read_paginationnormalize_search_pagination)
file_state)redact_sensitive_textpathreturnc                 2   | rd| vr| S 	 ddl m}  |            }n# t          $ r d}Y nw xY w|rK| dk    s|                     d          r0| dk    r|n't          j                            || dd                   S t          j                            |           S )a  Expand ``~`` using the effective profile home when available.

    In-process file tools share the gateway process's HOME, which may differ
    from the profile-specific HOME that interactive CLI sessions use.  This
    mirrors ``hermes_constants.get_subprocess_home()`` so that ``~`` resolves
    consistently regardless of whether the tool runs interactively or inside a
    gateway-driven cron job (#48552).
    ~r   )get_subprocess_homeNz~/   )hermes_constantsr   	Exception
startswithosr   join
expanduser)r   r   homes      6/home/rurouni/.hermes/hermes-agent/tools/file_tools.py_expand_tilder      s      3d??888888""$$    E 5 5s{{ttT48(D(DD7d###s    **i _max_read_chars_cachedc                     t           t           S 	 ddlm}   |             }|                    d          }t	          |t
          t          f          r|dk    rt          |          a t           S n# t          $ r Y nw xY wt          a t           S )zReturn the configured max characters per file read.

    Reads ``file_read_max_chars`` from config.yaml on first call, caches
    the result for the lifetime of the process.  Falls back to the
    built-in default if the config is missing or invalid.
    Nr   )load_configfile_read_max_chars)	r   hermes_cli.configr   get
isinstanceintfloatr   _DEFAULT_MAX_READ_CHARS)r   cfgvals      r   _get_max_read_charsr'   ?   s     )%%111111kmmgg+,,cC<(( 	*S1WW%(XX"))   4!!s   AA. .
A;:A;content	max_charsc                    t          |           |k    r| | r|                     d          dz   nddfS |                     d          }g }d}|D ]=}t          |          |rdndz   }||z   |k    r n|                    |           ||z  }>|s#|                    |d         d|                    d                    |          t          |          dfS )a  Trim line-numbered ``read_file`` content to fit a char budget.

    Ported in spirit from nearai/ironclaw#5029 (dual line/byte cap on
    ``read_file``). Where hermes previously hard-rejected an oversized read
    (forcing the model to guess a smaller ``limit`` and burn a round-trip
    returning nothing), this trims the content to the last *complete line*
    that fits within ``max_chars`` and reports how many lines were kept so
    the caller can offer a ``next_offset`` continuation.

    ``content`` is the gutter-rendered text (``LINE_NUM|CONTENT`` joined by
    ``\n``). Individual lines are already clamped to ``get_max_line_length()``
    upstream, so a single line never blows the whole budget on its own; the
    overflow this handles is the *accumulation* of many lines under the
    line-count limit (logs, wide CSV rows, minified data).

    Returns ``(kept_text, lines_kept, truncated)``. When ``content`` already
    fits, returns it unchanged with ``truncated=False``. If not even the
    first line fits, that single line is clamped on a code-point boundary
    (Python ``str`` slicing never splits a code point) so the read never
    returns empty and the cursor can still advance.
    
   r   FNT)lencountsplitappendr   )r(   r)   lineskeptrunninglineadditions          r   _truncate_to_char_budgetr6   V   s    , 7||y  GBt,,q00UJJMM$EDG  t99T 0q1X	))ED8 * 	E!HZiZ()))99T??CIIt++    i  >   /dev/tty	/dev/fd/0	/dev/fd/1	/dev/fd/2	/dev/full	/dev/zero
/dev/stdin/dev/random/dev/stderr/dev/stdout/dev/console/dev/urandomdefaultfilepathtask_idc                 "    t          | |          S )zsResolve a path relative to TERMINAL_CWD (the worktree base directory)
    instead of the main repository root.
    )_resolve_path_for_task)rE   rF   s     r   _resolve_pathrI      s     "(G444r7   >   ./ cwdauto.>   modaldockerdaytonasingularityc                    	 ddl m}m}m}m} 	  ||           }n# t
          $ r | }Y nw xY w|5  |                    |          p|                    |           }ddd           n# 1 swxY w Y   |B|j        j        	                                }d|v rdS d|v rdS d|v rdS d|v rdS d|v rdS d	|v rd	S  |            }t          |                    d
          pt          j        d          pd          	                                S # t
          $ r8 t          t          j        d          pd          	                                cY S w xY w)z@Best-effort terminal backend type for path-resolution decisions.r   _active_environments	_env_lock_get_env_config_resolve_container_task_idNlocalsshrP   rR   rO   rQ   env_typeTERMINAL_ENV)tools.terminal_toolrU   rV   rW   rX   r   r    	__class____name__lowerstrr   getenv)	rF   rU   rV   rW   rX   container_keyenvnamer%   s	            r   _terminal_env_type_for_taskrf      s   A	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
	$66w??MM 	$ 	$ 	$#MMM	$ 	_ 	_&**=99^=Q=U=UV]=^=^C	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_?=)//11D$w}}u4x$$$}$wD   yo377:&&N")N*C*CNwOOUUWWW A A A29^,,788>>@@@@@Ast   D	  D	 *D	 *D	 +A&D	 &A**D	 -A*.'D	 D	 D	 #D	 )D	 /D	 5AD	 	?E
Ec                 f    	 ddl m} |}n# t          $ r
 t          }Y nw xY wt	          |           |v S )Nr   )_CONTAINER_BACKENDS)r]   rh   r   !_CONTAINER_PATH_BACKENDS_FALLBACKrf   )rF   rh   container_backendss      r   _uses_container_pathsrk      s]    ?;;;;;;0 ? ? ?>?&w//3EEEs    c                 ^    t          t          j        t          |                               S )a  Normalize path syntax without following host symlinks.

    Container backends use paths that are meaningful inside the sandbox. Calling
    ``Path.resolve()`` on the host can dereference a host-side symlink such as
    ``/workspace`` and rewrite the path before Docker sees it.
    )r   	posixpathnormpathra   )r   s    r   _normalize_without_host_derefro      s#     +CII66777r7   rawc                     t          | pd                                          } |                                 t          v rdS t	          |           }t
          j                            |          sdS |S )u  Normalize a cwd candidate to an absolute, sentinel-free anchor.

    Returns the expanded path only when *raw* is non-empty, not a sentinel (see
    ``_TERMINAL_CWD_SENTINELS``), and absolute. A relative anchor is meaningless
    without knowing which cwd it is relative to — exactly the ambiguity that
    misroutes worktree edits — so relative/sentinel/empty values yield ``None``.
    rK   N)ra   stripr`   _TERMINAL_CWD_SENTINELSr   r   r   isabs)rp   expandeds     r   _sentinel_free_abs_cwdrv      se     ciR..


 
 C
yy{{---tS!!H7=="" tOr7   c                  Z    t          t          j                            d                    S )un  Return ``$TERMINAL_CWD`` only when it names a real directory anchor.

    Sentinel values (see ``_TERMINAL_CWD_SENTINELS``) and relative paths are
    rejected — a relative anchor is meaningless without knowing which cwd it is
    relative to, which is exactly the ambiguity that misroutes worktree edits.
    Only an absolute, sentinel-free value is honored.
    TERMINAL_CWD)rv   r   environr     r7   r   _configured_terminal_cwdr{      s      ""*.."@"@AAAr7   c                     	 ddl m}  ||           }n# t          $ r Y dS w xY wt          |                    d                    S )a  Return a registered cwd override for the raw task id, when available.

    ``terminal_tool`` intentionally collapses CWD-only task overrides to the
    shared ``"default"`` environment so TUI/dashboard/ACP sessions do not spin
    up isolated sandboxes just because they have different workspaces. The cwd
    value itself is still keyed by the raw session/task id, so file tools must
    read that raw override before falling back to the collapsed container key.
    r   resolve_task_overridesNrL   )r]   r~   r   rv   r    )rF   r~   	overridess      r   _registered_task_cwd_overrider      si    >>>>>>**733		   tt ")--"6"6777s    
""c                     | dS t          | dd          }|sdS t          t          | dd          pd          }t          |pd          }|r|r|dk    r|dk    r||k    rdS |S )a  The env's live cwd, but only when THIS session owns it.

    The terminal env is shared (collapsed to the ``"default"`` container), so its
    ``cwd`` tracks the LAST session that ran a command. With two worktree
    sessions open, trusting it blindly routes one session's edits into the other
    session's checkout (the wrong-worktree-patch bug). ``terminal_tool`` stamps
    ``env.cwd_owner`` with the session that last drove the env; return its cwd
    only when that owner matches the resolving session, else ``None`` so the
    caller falls through to this session's own registered cwd override. Unknown
    owner / ``default`` keys keep the prior behavior (single-session / CLI).
    NrL   	cwd_ownerrK   rD   )getattrra   )rd   rF   liveownertids        r   _live_cwd_if_ownedr     s     {t3t$$D t["--344E
gm

C  )++y0@0@Uc\\tKr7   c                    	 ddl m}  ||           }n# t          $ r | }Y nw xY wt          5  t                              |          pt                              |           }ddd           n# 1 swxY w Y   |kt          |dd          }t          ||           }|rt          ||           |S |4t          |dd          r#t          |dd          }t          ||           |S 	 ddl m	}m
} |5  |                    |          p|                    |           }ddd           n# 1 swxY w Y   t          ||           }|rt          ||           |S n# t          $ r Y nw xY wdS )zCReturn the task's live terminal cwd for bookkeeping when available.r   rX   Nrd   rL   )rU   rV   )r]   rX   r   _file_ops_lock_file_ops_cacher    r   r   _remember_last_known_cwdrU   rV   )	rF   rX   rc   cachedrd   live_cwd
legacy_cwdrU   rV   s	            r   _get_live_tracking_cwdr   '  sm    BBBBBB227;;        
 T T $$]33S7J7J77S7ST T T T T T T T T T T T T T TfeT**%c733 	$]H===O;765$77; 55J$]J???
GGGGGGGG 	_ 	_&**=99^=Q=U=UV]=^=^C	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_%c733 	$]H===O	     4sS    ##5A..A25A2'
E 1+D(E (D,,E /D,0&E 
E%$E%c                     t          |           }|r|S t          |           }|r|S t          |           }|r|S t                      S )u  Best-effort absolute workspace root for divergence checks.

    Prefers the live terminal cwd (the directory the agent is actually working
    in). When no terminal command has run yet — so the live registry is empty —
    falls back to a registered task/session cwd override (TUI/Desktop/ACP
    sessions register a raw-keyed cwd before any tool runs), then to a
    sentinel-free absolute ``$TERMINAL_CWD``. This is what lets a worktree or
    Desktop session warn about (and resolve into) its workspace from the very
    first ``write_file``/``patch``, before any ``cd`` has populated the live cwd.

    Returns ``None`` only when there is genuinely no reliable anchor, in which
    case callers fall back to the process cwd.
    )r   r   _last_known_cwd_forr{   )rF   r   
registered	preserveds       r   _authoritative_workspace_rootr   L  sd     "'**D  /w77J  $G,,I #%%%r7   container_pathsr   c                   t          |           }|t          |           }|rt          |          }nt          j                    }|rIt          j        |          s&t          j        t          j                    |          }t          |          S t          |          }|
                                s#t          t          j                              |z  }|                                S )u  Return the ABSOLUTE base directory for resolving relative paths.

    Resolution order:
      1. The task's live terminal cwd (the directory the agent is actually
         working in — e.g. a git worktree). Authoritative when known.
      2. A registered task/session cwd override (TUI/Desktop/ACP sessions
         register a raw-keyed workspace cwd before any terminal command runs).
      3. A sentinel-free, absolute ``$TERMINAL_CWD`` (the worktree path set by
         ``cli.py``/``main.py`` for ``-w`` sessions). Used even before any
         terminal command has populated the live cwd registry.
      4. The process cwd.

    The returned base is ALWAYS absolute. This is the core invariant that
    prevents the worktree-cwd divergence bug: a relative or sentinel
    ``TERMINAL_CWD`` (commonly the literal ``"."`` from a stale config) is
    meaningless as a resolution anchor — left to ``Path.resolve()`` it silently
    resolves against whatever the agent PROCESS cwd happens to be (e.g. the main
    repo while the terminal is in a worktree), routing edits to the wrong
    checkout. We therefore reject sentinel/relative ``TERMINAL_CWD`` values
    outright (rather than anchoring them to the process cwd) and fall through to
    the process cwd only as a last resort, deterministically.
    )r   rk   r   r   getcwdrm   rt   r   ro   r   is_absoluteresolve)rF   r   root	base_textbases        r   _resolve_base_dirr   s  s    6 )11D/88  !$''		IKK	 8y)) 	?!ry{{I>>I,Y777	??D ( BIKK  4'<<>>r7   c                    t          |          }t          |           }|rFt          j        |          rt	          |          S t          |d          |z  }t	          |          S t          |          }|                                r|                                S t          |d          |z  }|                                S )zResolve *filepath* against the task's absolute base directory.

    See :func:`_resolve_base_dir` for how the base is chosen. Absolute input
    paths are returned resolved-but-unanchored.
    Tr   F)	rk   r   rm   rt   ro   r   r   r   r   )rE   rF   r   ru   resolvedps         r   rH   rH     s     ,G44OX&&H 7?8$$ 	;0:::$WdCCChN,X666XA}} yy{{ %@@@1DHr7   r   c           	         	 t          t          |                                                     rdS t          |          }|sdS t	          |          r*t          t          t          |                              }n.t          t          |                                                    }	 |                    |           dS # t          $ r) d| dt          |          dt          |          dcY S w xY w# t          $ r Y dS w xY w)u3  Warn when a relative path resolved OUTSIDE the task's workspace root.

    Surfaces the worktree-cwd divergence the moment it would matter: if the
    agent passes a relative path but it resolves under a directory that is not
    the workspace root (i.e. the edit is about to land in a different checkout
    than the one the agent is working in), return a message naming the absolute
    target. ``None`` when the path is absolute, the base is unknown, or the
    resolved path is correctly under the workspace root.

    The workspace root is the live terminal cwd when known, else a registered
    task/session cwd override, else a sentinel-free absolute ``$TERMINAL_CWD``
    — so a worktree or Desktop session whose terminal registry is still empty
    (no ``cd`` run yet) is warned on the very first write.
    NzRelative path z resolved to z), which is OUTSIDE the active workspace (z). The edit will land in a different directory than the terminal's cwd. If this is not intended (e.g. a git-worktree session writing into the main checkout), pass an absolute path under the workspace instead.)r   r   r   r   rk   ro   r   relative_to
ValueErrorra   r   )rE   r   rF   workspace_rootr   s        r   _path_resolution_warningr     sJ   h''((4466 	46w?? 	4 )) 	A0mN6S6S1T1TUUDDn5566>>@@D
	  &&&4 	 	 	Q Q Q#h-- Q Q14TQ Q Q  	    tts;   .C: C: A'C: -C 0C74C: 6C77C: :
DDc                 $   t           j                            t          |                     }|t          v rdS |                    d          r|                    d          rdS |                    d          r|                    d          rdS dS )z=Return True for concrete device/fd paths that can hang reads.Tz/proc/)z/fd/0z/fd/1z/fd/2)	z/environz/cmdlinez/mapsz/smapsz/smaps_rollupz
/numa_mapsz/memz/auxvz/pagemapF)r   r   rn   r   _BLOCKED_DEVICE_PATHSr   endswith)r   
normalizeds     r   _is_blocked_device_pathr     s    !!-"5"566J***tX&& :+>+>#, ,  t X&& :+>+>
	
, ,  t5r7   base_dirc                    t          |           }|Qt          j                            |          s2t          j                            t          j        |          |          }t          j                            |          }t          |          rdS t                      }|}t          d          D ]}	 t          j
        |          }n# t          $ r Y  nw xY wt          j                            |          s=t          j                            t          j                            |          |          }t          j                            |          }t          |          r dS ||v r n|                    |           |}	 t          j                            t          j                            |                    }n# t          t          f$ r Y dS w xY wt          |          rdS dS )aB  Return True if the path would hang the process (infinite output or blocking input).

    Check the literal path first so aliases like /dev/stdin are caught before
    they resolve to terminal-specific paths. Then check each symlink hop before
    the final resolved path so aliases to devices cannot bypass the guard.
    NT   F)r   r   r   rt   r   fspathrn   r   setrangereadlinkOSErrordirnameaddrealpathr   )	rE   r   ru   r   seencurrent_targetr   s	            r   _is_blocked_devicer     s    X&&HBGMM($;$;7<<	( 3 3X>>!!(++Jz** tUUDG2YY  	[))FF 	 	 	EE	w}}V$$ 	DW\\"'//'":":FCCF!!&))"6** 	44T>>E7##BG$4$4Z$@$@AAZ    uux(( t5s$   5C


CC<G GGc                     	 t          | |          }n,# t          t          t          f$ r t	          |           cY S w xY wt	          t          |                    S )aT  Return the read-safety error for a search result path.

    Search backends may return paths relative to the task cwd, while
    ``get_read_block_error`` expects an already-resolved path when the task cwd
    can differ from the Python process cwd. Mirror ``read_file_tool``'s path
    resolution before applying the shared read guard.
    )rH   r   r   RuntimeErrorr   ra   )r   rF   r   s      r   _search_result_read_block_errorr   +  sb    *)$88Z. * * *#D)))))*H...s    &<<c                    d}t          | d          rJ| j        rCg }| j        D ]2}t          |j        |          r|dz  }|                    |           3|| _        t          | d          rE| j        r>g }| j        D ]-}t          ||          r|dz  }|                    |           .|| _        t          | d          rJ| j        rCi }| j                                        D ] \  }}t          ||          r|dz  }|||<   !|| _        |S )z?Remove credential/cache/env paths from a SearchResult in-place.r   matchesr,   filescounts)hasattrr   r   r   r0   r   r   items)	resultrF   omittedallowed_matchesmatchallowed_files	file_pathallowed_countsr.   s	            r   #_filter_read_blocked_search_resultsr   :  s\   Gvy!! )fn )^ 	* 	*E.uz7CC 1""5))))(vw %FL % 	, 	,I.y'BB 1  ++++$vx   'V] ' & 3 3 5 5 	. 	.Iu.y'BB 1(-N9%%&Nr7   )z/etc/z/boot/z/usr/lib/systemd/z/private/etc/z/private/var/z/var/run/docker.sockz/run/docker.sock_hermes_config_resolvedFc                  P   t           rt          S da 	 ddlm}  t	           |                                                       an_# t          $ rR 	 t	          t          t          d                                                              an# t          $ r daY nw xY wY nw xY wt          S )zEReturn the resolved absolute path of the Hermes config file (cached).Tr   get_config_pathz~/.hermes/config.yamlN)	_hermes_config_resolved_loadedr   r   r   ra   r   r   r   r   r   s    r   _get_hermes_config_resolvedr   h  s     & '&&%)"+555555"%oo&7&7&?&?&A&A"B"B + + +	+&)$}=T/U/U*V*V*^*^*`*`&a&a## 	+ 	+ 	+&*###	++
 #"s5   /A 
B;B	B	BBBBBc                    	 t          t          | |                    }n# t          t          f$ r | }Y nw xY wt          j                            t          |                     }d|  d}t          D ]0}|	                    |          s|	                    |          r|c S 1|t          v s	|t          v r|S t                      }|r||k    s||k    rd|  dS dS )zHReturn an error message if the path targets a sensitive system location.z,Refusing to write to sensitive system path: zD
Use the terminal tool with sudo if you need to modify system files.z)Refusing to write to Hermes config file: zz
Agent cannot modify security-sensitive configuration. Edit ~/.hermes/config.yaml directly or use 'hermes config' instead.N)ra   rH   r   r   r   r   rn   r   _SENSITIVE_PATH_PREFIXESr   _SENSITIVE_EXACT_PATHSr   )rE   rF   r   r   _errprefixhermes_configs          r   _check_sensitive_pathr   y  s;   -h@@AAZ    !!-"9"9::J	Nx 	N 	N 	N 	 +  v&& 	**?*?*G*G 	KKK	)))Z;Q-Q-Q
 011M 
(m33z]7R7RR R R R	

 4s     66c                    	 ddl m}m}m}m}  ||           }n# t
          $ r Y dS w xY w	 |5  |                    |          p|                    |           }ddd           n# 1 swxY w Y   |2|j        j        dk    r t          t          |dd                    rdS dS  |            }n# t
          $ r Y dS w xY w|                    d          d	k    r|                    d
d          rdS dS )zEReturn the container-side Hermes mirror prefix for Docker file tools.r   rT   NDockerEnvironment_persistentFz/root/.hermesr[   rP   container_persistentT)r]   rU   rV   rW   rX   r   r    r^   r_   boolr   )rF   rU   rV   rW   rX   rc   rd   configs           r   %_get_container_mirror_prefix_for_taskr     s   
	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 327;;   tt 	_ 	_&**=99^=Q=U=UV]=^=^C	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ ?}%)<<<]E22B B< '4 ""   tt zz*))fjj9OQU.V.V)4sF    
((B/ +A%B/ %A))B/ ,A)-3B/ $
B/ /
B=<B=c                    	 ddl m}m}m} n# t          $ r Y dS w xY w	 t          t          | |                    }n# t          t          f$ r | }Y nw xY w ||          }||S  ||          }||S  ||t          |                    S )u  Return a soft-guard warning when ``filepath`` lands in another Hermes
    profile's scoped area, a host-side sandbox-mirror of authoritative profile
    state, or the Docker container's sandbox mirror of Hermes state.

    Three detectors run in order:

    * cross-profile — writes that hit another profile's
      ``skills/plugins/cron/memories`` directory.
    * sandbox-mirror (#32049) — writes that hit the
      ``…/sandboxes/<backend>/<task>/home/.hermes/…`` mirror created by a
      non-local terminal backend (Docker, Daytona, etc.), where the host
      Hermes process never reads the mirror and the authoritative file is
      left untouched.
    * container-mirror (#32049 follow-up) — writes from inside a Docker
      container whose bind-mounted home strips the ``sandboxes/`` prefix, so
      the agent sees a plain ``/root/.hermes/…`` path.

    Returns ``None`` when the write is in-scope or outside Hermes scope.
    All detectors are soft guards — the agent can override any by
    passing ``cross_profile=True`` to its write tool after explicit user
    direction. Defense-in-depth, NOT a security boundary — the terminal
    tool runs as the same OS user and can write any of these paths
    directly. See ``agent/file_safety.classify_cross_profile_target``,
    ``classify_sandbox_mirror_target`` and ``classify_container_mirror_target``
    for the detection rules.
    r   )get_container_mirror_warningget_cross_profile_warningget_sandbox_mirror_warningN)mirror_prefix)
agent.file_safetyr   r   r   r   ra   rH   r   r   r   )rE   rF   r   r   r   r   warnings          r   _check_cross_profile_pathr     s   6		
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	

     tt-h@@AAZ     ('11G((22G'';GDD   s   
 
= AAexcc                 ~    t          | t                    rdS t          | t                    r| j        t          v rdS dS )zFReturn True for expected write denials that should not hit error logs.TF)r!   PermissionErrorr   errno_EXPECTED_WRITE_ERRNOS)r   s    r   _is_expected_write_exceptionr     sB    #'' t#w CI1G$G$Gt5r7   r   _last_known_cwdrL   c                     |sdS t           5  t                              |           |k    r
|t          | <   ddd           dS # 1 swxY w Y   dS )u  Mirror a live terminal cwd into the durable ``_last_known_cwd`` registry.

    Belt-and-suspenders for #26211: the cleanup thread can pop BOTH
    ``_file_ops_cache`` and ``_active_environments`` before ``_get_file_ops``
    reaches its stale-cache detection branch, in which case the old cwd is
    never saved and the rebuilt env falls back to the config default — exactly
    the silent-misplacement bug. By recording the cwd on every successful live
    read (which happens on every relative-path file resolution while the env is
    alive), the durable anchor no longer depends on the cleanup-detection
    branch firing, so it survives recreation regardless of pop ordering.
    N)r   r   r    )rF   rL   s     r   r   r     s      	 + +w''3..'*OG$+ + + + + + + + + + + + + + + + + +s   )AA	Ac                     	 ddl m}  ||           }n# t          $ r | }Y nw xY wt          5  t                              |          pt                              |           cddd           S # 1 swxY w Y   dS )a%  Read the durable last-known cwd for *task_id*, container-key aware.

    The registry is keyed by the resolved container id (the same key used by
    the save sites in ``_get_file_ops`` / ``_get_live_tracking_cwd``), so look
    up the resolved key first and fall back to the raw task id.
    r   r   N)r]   rX   r   r   r   r    )rF   rX   rc   s      r   r   r     s     BBBBBB227;;       	 R R""=11Q_5H5H5Q5QR R R R R R R R R R R R R R R R R Rs    ##4A..A25A2_read_tracker_patch_failure_trackerresolved_pathc                 R   t           5  t                              | i           }t          |          dk    r5||vr1	 t	          t          |                    }||= n# t          $ r Y nw xY w|                    |d          dz   ||<   ||         cddd           S # 1 swxY w Y   dS )zAIncrement and return the consecutive-failure count for this path.@   r   r,   N)_patch_failure_lockr   
setdefaultr-   nextiterStopIterationr    )rF   r   task_failures	first_keys       r   _record_patch_failurer   C  s   	 , ,.99'2FF }##](J(J m!4!455	!),,    '4'8'8'J'JQ'Nm$]+, , , , , , , , , , , , , , , , , ,s4   3BAB
A)&B(A))&BB #B resolved_pathsc                     |sdS t           5  t                              |           }|s	 ddd           dS |D ]}|                    |d           	 ddd           dS # 1 swxY w Y   dS )z5Clear consecutive-failure counts for the given paths.N)r   r   r    pop)rF   r   r   rps       r   _reset_patch_failuresr  T  s     	 ( (.227;; 	( ( ( ( ( ( ( ( ! 	( 	(Bb$''''	(	( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (s   A A  A$'A$  i  u   File unchanged since last read. The content from the earlier read_file result in this conversation is still current — refer to that instead of re-reading.	task_datac                    |                      d          }|gt          |          t          k    rOt          |          t          z
  }t          |          D ](}	 |                                 # t
          $ r Y  nw xY w|                      d          }|t          |          t          k    rqt          |          t          z
  }t          |          D ]J}	 |                    t          t          |                               3# t          t
          f$ r Y  nw xY w|                      d          }|t          |          t          k    rqt          |          t          z
  }t          |          D ]J}	 |                    t          t          |                               3# t          t
          f$ r Y  nw xY w|                      d          }|t          |          t          k    rtt          |          t          z
  }t          |          D ]O}	 |                    t          t          |                               3# t          t
          f$ r Y  dS w xY wdS dS dS )a  Enforce size caps on the per-task read-tracker sub-containers.

    Must be called with ``_read_tracker_lock`` held.  Eviction policy:

      * ``read_history`` (set): pop arbitrary entries on overflow.  This
        is fine because the set only feeds diagnostic summaries; losing
        old entries just trims the summary's tail.
      * ``dedup`` / ``read_timestamps`` (dict): pop oldest by insertion
        order (Python 3.7+ dicts).  Evicted entries lose their dedup
        skip on a future re-read (the file gets re-sent once) and
        external-edit mtime comparison (the write/patch falls back to
        a non-mtime check).  Both are graceful degradations, not bugs.
    read_historyNdedup
dedup_hitsread_timestamps)r    r-   _READ_HISTORY_CAPr   r  KeyError
_DEDUP_CAPr   r   r   _READ_TIMESTAMPS_CAP)r  rhexcessr   r	  r
  tss          r   _cap_read_tracker_datar  o  sy    
~	&	&B	~#b''$555R,,v 	 	A    MM'""ESZZ*44Uj(v 	 	A		$tE{{++,,,,!8,    |,,J#j//J">">Z:-v 	 	AtD$4$4556666!8,    
(	)	)B	~#b''$888R//v 	 	AtDHH~~&&&&!8,    ~88	 	sH   A..
A<;A</DDD7/F''F<;F</IIIc                     t          | t                    sdS |                                 }|sdS |t          k    rdS t          |v r*t	          |          dt	          t                    z  k    rdS dS )u  Return True when content looks like an internal file-tool status, not real file bytes.

    The read_file dedup status message must never be persisted as file
    content.  The obvious shape is the model echoing the message verbatim,
    but in practice it also wraps it with small framing text (a leading
    "Note:", a trailing newline + short comment, etc.) before calling
    write_file.  We treat any short-ish write whose body is dominated by
    the status message as the same class of corruption.

    Heuristic:
      * Strict equality (after strip) — the verbatim shape.
      * OR the stripped content contains the full status message AND is
        short enough that the status dominates it (<=2x the message length).
        Short, status-dominated writes can't plausibly be real files —
        legitimate docs/notes that happen to quote this internal message
        are always dramatically longer.
    FTr   )r!   ra   rr   _READ_DEDUP_STATUS_MESSAGEr-   )r(   strippeds     r   _is_internal_file_status_textr    sz    $ gs## u}}H u---t!X--MMQ%?!@!@@@@t5r7   c           	      l   t          | t                    sdS d |                                 D             }t          |          dk     rdS g }|D ]g}|                                }|                    d          \  }}}|r6|                                r"|                    t          |                     ht          |          dk     rdS t          |          t          |          z  dk     rdS t          d t          ||dd                   D                       }|t          |          dz
  k    S )	a  Return True for content dominated by read_file's ``LINE_NUM|CONTENT`` display.

    ``read_file`` intentionally returns line-numbered text to the model. If
    that display format is echoed into ``write_file``, config/source files are
    silently corrupted with prefixes like `` 1|``.  We reject writes where the
    non-empty lines are mostly consecutive read_file-style numbered lines, while
    allowing sparse literal pipe content such as a single ``1|value`` line.
    Fc                 :    g | ]}|                                 |S rz   )rr   ).0r4   s     r   
<listcomp>z?_looks_like_read_file_line_numbered_content.<locals>.<listcomp>  s%    CCCddjjllCTCCCr7   r   |g333333?c              3   2   K   | ]\  }}||d z   k    d V  dS )r,   Nrz   )r  prevr   s      r   	<genexpr>z>_looks_like_read_file_line_numbered_content.<locals>.<genexpr>  sA        dGdQh 	
 r7   r,   N)r!   ra   
splitlinesr-   lstrip	partitionisdigitr0   r"   sumzip)	r(   r1   numberedr4   r  r   sep_restconsecutive_pairss	            r   +_looks_like_read_file_line_numbered_contentr*    sH    gs## uCCg0022CCCE
5zzA~~uH ) );;==%//44U 	)6>>## 	)OOCKK(((
8}}qu
8}}s5zz!C''u  "8Xabb\::     H 111r7   c                 >    t          |           pt          |           S )zLReturn True when content is file-tool display text, not intended file bytes.)r  r*  )r(   s    r   _is_internal_file_tool_contentr,    s%     	&g.. 	@6w??r7   c                 l
   ddl m}m}m}m}m}m}m}m}m	}	m
}
m} ddl}| pd} |	|          } t          5  t                              |           }ddd           n# 1 swxY w Y   ||5  | |v r%|                                || <   |cddd           S t!          |dd          }|r)t          5  |t"          | <   ddd           n# 1 swxY w Y   t          5  t                              | d           ddd           n# 1 swxY w Y   ddd           n# 1 swxY w Y   |5  | |vrt'          j                    || <   ||          }ddd           n# 1 swxY w Y   |5  |5  | |v r |                                || <   ||          }nd}ddd           n# 1 swxY w Y   |Addl m}  |            }|d         } ||          }|dk    r|                    d	          p|d	         }nn|d
k    r|                    d          p|d         }nJ|dk    r|                    d          p|d         }n&|dk    r|                    d          p|d         }nd}|                    d          p!t"                              |           p|d         }||v rB |
|          r7||d         k    r#t,                              d|||d                    |d         }t,                              d|| dd                    d}|dv r|                    dd          |                    dd          |                    dd          |                    dd          |                    dg           |                    dd          |                    d g           |                    d!d          |                    d"d          d#	}d}|d$k    rl|                    d%d          |                    d&d          |                    d'd(          |                    d)d          |                    d*d          d+}d}|d,k    rd-|                    d.d          i} |||||d/         |||| |                    d0          1	  	        }|5  ||| <   |                                || <   ddd           n# 1 swxY w Y    |             t,                              d2|| dd                    ddd           n# 1 swxY w Y   t1          |          }t          5  |t          | <   ddd           n# 1 swxY w Y   |S )3a  Get or create ShellFileOperations for a terminal environment.

    Respects the TERMINAL_ENV setting -- if the task_id doesn't have an
    environment yet, creates one using the configured backend (local, docker,
    modal, etc.) rather than always defaulting to local.

    Thread-safe: uses the same per-task creation locks as terminal_tool to
    prevent duplicate sandbox creation from concurrent tool calls.

    Note: subagent task_ids are collapsed to "default" via
    ``_resolve_container_task_id`` so delegate_task children share the
    parent's container and its cached file_ops. RL/benchmark task_ids with
    a registered env override keep their isolation.
    r   )rU   rV   _create_environmentrW   _last_activity_start_cleanup_thread_creation_locks_creation_locks_lockrX   _is_unusable_container_cwdrh   NrD   rL   r}   r[   rP   docker_imagerR   singularity_imagerO   modal_imagerQ   daytona_imagerK   zaIgnoring host/relative cwd override %r for %s backend (won't exist in sandbox). Using %r instead.z*Creating new %s environment for task %s...   >   rO   rP   rQ   rR   container_cpur,   container_memoryi   container_diski   r   Tdocker_volumesdocker_mount_cwd_to_workspaceFdocker_forward_envdocker_run_as_host_userdocker_network)	r9  r:  r;  r   r<  r=  r>  r?  r@  rZ   ssh_hostssh_userssh_port   ssh_keyssh_persistent)hostuserportkey
persistentrY   rK  local_persistenttimeouthost_cwd)	r[   imagerL   rM  
ssh_configcontainer_configlocal_configrF   rN  z %s environment ready for task %s)r]   rU   rV   r.  rW   r/  r0  r1  r2  rX   r3  rh   timer   r   r    r   r   r  	threadingLockr~   loggerinfor   )rF   rU   rV   r.  rW   r/  r0  r1  r2  rX   r3  rh   rS  raw_task_idr   old_cwd	task_lockterminal_envr~   r   r[   r   rO  rL   rQ  rP  rR  file_opss                               r   _get_file_opsr]    s                             KKK&YK((55G 
 . . $$W--. . . . . . . . . . . . . . . 	7 	7...*.))++w'	7 	7 	7 	7 	7 	7 	7 	7 "&%66 ;' ; ;3:0; ; ; ; ; ; ; ; ; ; ; ; ; ; ;# 7 7#''6667 7 7 7 7 7 7 7 7 7 7 7 7 7 7	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7" 
 - -/))'0~'7'7OG$#G,	- - - - - - - - - - - - - - -
 
 aS aS 	$ 	$...*.))++w'3G<#	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ BBBBBB$_&&Fj)H..{;;I8##!n55O9O]**!&9::YfEX>YW$$!m44M}8MY&&!o66Q&:Q--&&W/*=*=g*F*FW&QV-C ...3M3Mc3R3R.&-''KKFXve}  
 UmKKDhPWXZYZXZP[\\\#HHH%+ZZ%C%C(.

3Et(L(L&,jj1A5&I&I,2JJ7Mt,T,T&,jj1A2&F&F5;ZZ@_af5g5g*0**5I2*N*N/5zz:SUZ/[/[&,jj1A4&H&H
$ 
$  J5  "JJz266"JJz266"JJz266!::i44"(**-=u"E"E 
  L7"" &**-?"G"G  /.!y)%!1)J//
 
 
L  6 60<$W-*.))++w'6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 "!###KK:HgbqbkRRRCaS aS aS aS aS aS aS aS aS aS aS aS aS aS aSH #<00H	 , ,#+ , , , , , , , , , , , , , , ,Os   AA #A +DD/C:DC
	
DC
	
DD 4D D	DD	DDD$#EEE S1#'F
S1F	S1F	K%S1R, S1,R0	0S13R0	41S11S58S5T))T-0T-c                     t           5  | rt                              | d           nt                                           ddd           dS # 1 swxY w Y   dS )z Clear the file operations cache.N)r   r   r  clear)rF   s    r   clear_file_ops_cacher`    s    	 $ $ 	$....!!###	$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $s   8AAAr,   offsetlimitc                    	 t          ||          \  }}t          |                                                                           rdnt	          |          }t          | |          rt          j        dd|  di          S t          | |          }ddl	m
}m}m}  |t          |                    r	  |t          |                    }	t          |          }
|	                                }t!          |          }||z   dz
  }d	                    ||dz
  |                   }|r|
                    ||          nd
|t&          j                            |          ||k    dd}|d         r#d|dz    d| dt-          ||           d| d	|d<   t!          |d                   }t/                      }||k    rt1          |d         |          \  }}}||z   }||z   dz
  }||d<   d|d<   d|d<   ||d<   d|dd| d| d| d| d| d|d<   t!          |                    d	d          d                   |k    r|dxx         dz  cc<   |d         rt5          |d         d          |d<   t          j        |d !          S # |$ r  t6                              d"| d#           Y nw xY wt;          t          |                    r6|j                                        }t          j        dd$|  d%| d&i          S tA          t          |                    }|rt          j        d|i          S t          |          }|||f}tB          5  tD          #                    |ddtI                      i i i d'          }d(|vri |d(<   d)|vri |d)<   |%                    d*i           %                    |          }ddd           n# 1 swxY w Y   |	 t&          j        &                    |          }||k    rtB          5  |d(         %                    |d          dz   }||d(         |<   tO          |           ddd           n# 1 swxY w Y   |d+k    r$t          j        d,|dz    d-| |dz   d.d !          S t          j        d/tP          | dd d0d !          S n# tR          $ r Y nw xY wt          |          }
|
*                    | ||          }|+                                }t!          |j,        pd
          }|%                    d1d          } t/                      }||k    r|%                    d2d3          }t1          |j,        pd
|          \  }}}||z   }||z   dz
  }||_,        ||d<   d|d<   d|d<   ||d<   d|dd| d| d| d| d| d|d<   t!          |                    d	d          d                   |k    r|dxx         dz  cc<   t!          |          }|j,        r%t5          |j,        d          |_,        |j,        |d<   | rA| tZ          k    r6|d4k    r0|%                    d          r|#                    d5d6| dd7           d8| ||f}!tB          5  d*|vri |d*<   d(|vri |d(<   |d(         .                    |d           |d9         /                    | ||f           |d:         |!k    r|d;xx         dz  cc<   n
|!|d:<   d|d;<   |d;         }"	 t&          j        &                    |          }#|#|d*         |<   |#|#                    d)i           |<   n# tR          $ r Y nw xY wtO          |           ddd           n# 1 swxY w Y   	 |dk    p!ta          |%                    d                    }$tc          j2        |||$<           n,# tf          $ r t6                              d=d#           Y nw xY w|"d>k    rt          j        d?|" d@| |"d.d !          S |"dAk    r	dB|" dC|dD<   t          j        |d !          S # tf          $ r&}%ti          t          |%                    cY d}%~%S d}%~%ww xY w)Ez-Read a file with pagination and line numbers.N)r   errorzCannot read 'zE': this is a device file that would block or produce infinite output.r   )ExtractionErrorextract_document_textis_extractable_documentr,   r+   rK   T)r(   total_lines	file_size	truncatedextracted_documentrj  zUse offset=z to continue reading (showing -z of z lines)hintr(   bytestruncated_bynext_offsetzOutput truncated at the ,z-char read budget after z line(s) (showing lines z). Use offset=z to continue.zv Note: the first line alone exceeded the budget and was clamped mid-line; its remainder is not retrievable via offset.	file_readFensure_asciiz!document extraction failed for %sexc_infozCannot read binary file 'z' (zF). Use vision_analyze for images, or terminal to inspect binary files.)last_keyconsecutiver  r	  r
  r  r
  r  r	  r   z8BLOCKED: You have called read_file on this exact region u    times and the file has NOT changed. STOP calling read_file for this path — the content from your earlier read_file result in this conversation is still current. Proceed with your task using the information you already have.)rd  r   already_read	unchanged)statusmessager   r	  content_returnedri  rh  unknown   _hintzThis file is large (zj bytes). Consider reading only the section you need with offset and limit to keep context usage efficient.readr  rx  ry  )partialzfile_state.record_read failed   z.BLOCKED: You have read this exact file region z| times in a row. The content has NOT changed. You already have this information. STOP re-reading and proceed with your task.   z%You have read this exact file region z times consecutively. The content has not changed since your last read. Use the information you already have. If you are stuck in a loop, stop reading and proceed with writing or responding._warning)5r   r   r   r   r   r   jsondumpsrH   tools.read_extractre  rf  rg  ra   r]  r   r-   r   _add_line_numbersr   r   getsizeminr'   r6   r/   r   rV  debugr   suffixr`   r   _read_tracker_lockr   r   r   r    getmtimer  r  r   	read_fileto_dictr(   _LARGE_FILE_HINT_BYTESr  r   r   r
   record_readr   
tool_error)&r   ra  rb  rF   device_base	_resolvedre  rf  rg  extracted_textr\  r1   rh  end_line	page_textresult_dictcontent_lenr)   trimmed
lines_keptr   rp  	shown_end_extblock_errorresolved_str	dedup_keyr  cached_mtimecurrent_mtimehitsr   ri  read_keyr.   
_mtime_now_partiales&                                         r   read_file_toolr    sG   b"1&%@@
 #4jj3355AACCcddIZ[bIcIcd[999 	:8D 8 8 8    +499	
 	gfffffffff""3y>>22 4	C3C!6!6s9~~!F!F )11&1133!%jj!E>A- IIeFQJx,?&@AA	PYax99)VLLL_a#.!#!;!;!,x!7*.  {+ bhl b b$*b b-0;-G-Gb bMXb b b  ' "+i"899/11	** .F#I.	. .*GZ #):"5K & 3a 7I-4K	*/3K,29K/1<K.'9R ' '!+' 'EK' '$' '*5' 'EP' ' '  ' 7==q11!455BB#F+++6+++
 y) k-B;yCYei-j-j-jK	*z+EBBBBc # W W W@$QUVVVVVWj  I// 	#))++D:Z Z Z Z Z Z    +3y>>:: 	6:w4555 9~~!651	 	E 	E%00  # R; ;  I 9,,*,	,' 	11/1	+,$=="5599)DDL	E 	E 	E 	E 	E 	E 	E 	E 	E 	E 	E 	E 	E 	E 	E #$ " 0 0 > > L00 , : :(6::9aHH1L=A	,/	:.y999: : : : : : : : : : : : : : :
 qyy#z!D04q!D !D !D %),01H+ + ). /  /  / /  :"-#= $!%,1' ' %*+ + + +5 1B     !))##D&%88nn&& &..B//OOK33	'))	"" &//-CCK%=$"i& &"GZ !:-K+a/I$FN%,K	"'+K$*1K')4K&I9P I II I7=I I@II II I.9I I I 
 7==q))!,--::F######
 g,,K > 	426>TRRRFN%+^K	"  	)&<<<CKKOOK00  ""73y= 3 3 3   D&%0  	.  	. i''%'	'"9,,*,	,' l#''	4888n%))4*?@@@$00-(((A-(((((0	*%+,	-(m,EW--l;;
0:	'"9-LV	$$%6;;LII   
 #9---A 	.  	.  	.  	.  	.  	.  	.  	.  	.  	.  	.  	.  	.  	.  	.R	I
ItKOOK,H,H'I'IH"7L(KKKKK 	I 	I 	ILL84LHHHHH	I A:::BU B B B  %  "# # # # aZZc c c c 
# z+E:::: " " "#a&&!!!!!!!!"s>  B` 3` 8J F7` "J-*` ,J--A` 3` 6` A*O;` O` O` ,R :Q;R QR Q,R < R ` 
R+(` *R++F7` "A<]A\#"]#
\0-]/\00]` ]` ]` ?^ ` &_ ` _&` *$` 
`?`:4`?:`?c                    t           5  | rYt                              |           }|r<d|v r|d                                          d|v r|d                                          nXt                                          D ]>}d|v r|d                                          d|v r|d                                          ?ddd           dS # 1 swxY w Y   dS )u  Clear the deduplication cache for file reads.

    Called after context compression — the original read content has been
    summarised away, so the model needs the full content if it reads the
    same file again.  Without this, reads after compression would return
    a "file unchanged" stub pointing at content that no longer exists in
    context.

    Call with a task_id to clear just that task, or without to clear all.
    r	  r
  N)r  r   r    r_  valuesrF   r  s     r   reset_file_dedupr    s6    
 4 4 	4%))'22I 4i''g&,,...9,,l+11333*1133 4 4	i''g&,,...9,,l+113334 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4s   B4C		CCc                     t           5  t                              |           }|r(d|d<   d|d<   d|v r|d                                          ddd           dS # 1 swxY w Y   dS )u  Reset consecutive read/search counter for a task.

    Called by the tool dispatcher (model_tools.py) whenever a tool OTHER
    than read_file / search_files is executed.  This ensures we only warn
    or block on *truly consecutive* repeated reads — if the agent does
    anything else in between (write, patch, terminal, etc.) the counter
    resets and the next read is treated as fresh.
    Nrx  r   ry  r
  )r  r   r    r_  r  s     r   notify_other_tool_callr    s     
 0 0!%%g..	 	0$(Ij!'(Im$ y((,'--///0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0s   AAA!Ac                    	 t          t          |                     n# t          t          f$ r Y dS w xY wt          5  t
                              |          }|	 ddd           dS |                    d          }|s	 ddd           dS fd|D             }|D ]}||= 	 ddd           dS # 1 swxY w Y   dS )u  Remove all dedup cache entries whose resolved path matches *filepath*.

    Called after write_file and patch so that a subsequent read_file on
    the same path always returns fresh content instead of a stale
    "File unchanged" stub.  The dedup cache keys are tuples of
    ``(resolved_path, offset, limit)``; we must evict **all** offset/limit
    combinations for the written path because any cached range could now
    be stale.

    Must be called with ``_read_tracker_lock`` **not** held — acquires it
    internally.
    Nr	  c                 ,    g | ]}|d          k    |S )r   rz   )r  kr   s     r   r  z._invalidate_dedup_for_path.<locals>.<listcomp>  s'    ;;;A!A$(*:*:a*:*:*:r7   )ra   rI   r   r   r  r   r    )rE   rF   r  r	  
stale_keysr  r   s         @r   _invalidate_dedup_for_pathr    sr   }X..//Z    	 
 
!%%g..	
 
 
 
 
 
 
 
 g&& 	
 
 
 
 
 
 
 
 <;;;;;;
 	 	Aa	
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
s)     55B3*B3B33B7:B7c                    t          | |           	 t          t          | |                    }t          j                            |          }n# t          t          f$ r Y dS w xY wt          5  t          
                    |          }|(||                    di           |<   t          |           ddd           dS # 1 swxY w Y   dS )u  Record the file's current modification time after a successful write.

    Called after write_file and patch so that consecutive edits by the
    same task don't trigger false staleness warnings — each write
    refreshes the stored timestamp to match the file's new state.

    Also invalidates the dedup cache for the written path so that
    subsequent reads return fresh content (fixes #13144).
    Nr  )r  ra   rH   r   r   r  r   r   r  r   r    r   r  )rE   rF   r   r  r  s        r   _update_read_timestampr    s    x111-h@@AA((22Z    	 . .!%%g..	 DQI  !2B77A"9---	. . . . . . . . . . . . . . . . . .s$   <A A$#A$.AC  CCc                    	 t          t          | |                    }n# t          t          f$ r Y dS w xY wt          5  t
                              |          }|s	 ddd           dS |                    di                               |          }ddd           n# 1 swxY w Y   |dS 	 t          j        	                    |          }n# t          $ r Y dS w xY w||k    rd|  dS dS )u  Check whether a file was modified since the agent last read it.

    Returns a warning string if the file is stale (mtime changed since
    the last read_file call for this task), or None if the file is fresh
    or was never read.  Does not block — the write still proceeds.
    Nr  z	Warning: z was modified since you last read it (external edit or concurrent agent). The content you read may be stale. Consider re-reading the file to verify before writing.)
ra   rH   r   r   r  r   r    r   r   r  )rE   rF   r   r  
read_mtimer  s         r   _check_file_stalenessr  %  s   -h@@AAZ    tt	 H H!%%g..	 	H H H H H H H H ]]#4b99==hGG
	H H H H H H H H H H H H H H H
 t((22   tt
""L L L L	

 4s5     55B*)BB#&B#/C 
CC
session_idc                    d |D             }|sdS 	 ddl m} ddlm} d}|D ]D}	 t	          t          |          j                  }n# t          $ r Y 1w xY w ||          r|} nE|t          |           }|;	 t	          t          |d                   j                  }n# t          $ r d}Y nw xY w ||p| ||           dS # t          $ r  t          
                    dd	           Y dS w xY w)
zEBest-effort note that successful edits made prior verification stale.c                     g | ]}||S rz   rz   )r  r   s     r   r  z,_mark_verification_stale.<locals>.<listcomp>J  s    ,,,1!,Q,,,r7   Nr   )project_facts_for)mark_workspace_edited)r  rL   pathsz verification stale marker failedTrv  )agent.coding_contextr  agent.verification_evidencer  ra   r   parentr   r   rV  r  )	rF   r   r  r  r  r  rL   r   	candidates	            r   _mark_verification_staler  D  s    -,,,,E H::::::EEEEEE 	 	DT

 122		     ++  ;/88C;$uQx../00   )>wCuUUUUUU H H H7$GGGGGGHsX   C !AC 
AC A&C ;'B# "C #B2/C 1B22C &C10C1cross_profilec                    t          | |          }|rt          |          S |s!t          | |          }|rt          |          S t          |          rt          d          S 	 	 t	          t          | |                    }n# t          $ r d}Y nw xY w|t          | |          }t          |          }	|		                    | |          }
|

                                }|r||d<   |                    d          st          || g|           t          | |           t          j        |d          S t!          j        |          5  t!          j        ||          }t          | |          }t'          | t)          |          |          }t          |          }	|		                    ||          }
|

                                }|p|p|}|r||d<   ||d<   |                    d          s|g|d	<   t          ||g|           t          | |           |                    d          st!          j        ||           ddd           n# 1 swxY w Y   t          j        |d          S # t          $ r}t-          |          r/t.                              d
t3          |          j        |           n0t.                              dt3          |          j        |d           t          t	          |                    cY d}~S d}~ww xY w)uX  Write content to a file.

    ``cross_profile`` opts out of the soft cross-Hermes-profile guard. The
    guard fires only on writes that land in another profile's
    skills/plugins/cron/memories directory; everything else is unaffected.
    Pass ``True`` after explicit user direction — same shape as ``force``
    on the terminal tool.
    zRefusing to write internal read_file display text as file content. Strip read_file line-number prefixes or reconstruct the intended file contents before writing.Nr  rd  r  Frt  r   files_modifiedz"write_file expected denial: %s: %szwrite_file error: %s: %sTrv  )r   r  r   r,  ra   rH   r   r  r]  
write_filer  r    r  r  r  r  r
   	lock_pathcheck_staler   r   
note_writer   rV  r  typer_   rd  )r   r(   rF   r  r  sensitive_errcross_warningr  stale_warningr\  r   r  cwd_warningeffective_warningr  s                  r   write_file_toolr  f  s    *$88M )-((( -1$@@ 	-m,,,%g.. 
,
 
 	

8"	24AABBII 	 	 	III	 1$@@M$W--H((w77F ..**K 8*7J'??7++ Q(4&ZPPPP"4111:k>>>>
 !),, 	: 	: '27IFFM1$@@M 34i'RRK$W--H((G<<F ..**K - M M+  <*;J' ,5K(??7++ V1:,-(9+*UUUU #4111??7++ :%gy9993	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	:4 z+E:::: " " "'** 	YLL=tAww?OQRSSSSLL3T!WW5EqSWLXXX#a&&!!!!!!!!"si   %B I- BI- BB"I- 5I- 	C7I I- II- II- -
L7B	L LLreplacemode
old_string
new_stringreplace_allpatchc	                   "# g }	|r|	                     |           | dk    r(|r%ddl}
ddlm# dt          dt          dz  f#fd}|
                    d||
j                  D ]O}|                    d	                                          } ||          }|r|c S |	                     |           P|
                    d
||
j                  D ]{}|                    d	                                          |                    d                                          fD ]*} ||          }|r|c c S |	                     |           +||	D ]J}t          ||          }|rt          |          c S |s#t          ||          }|rt          |          c S K	 g }t                      }|	D ]c}	 t	          t          ||                    }n# t          $ r d}Y nw xY w|r.||vr*|                     |           |                    |           d|                                 ddlm}  |            5 }|D ])}|                    t)          j        |                     *g }i "|	D ]}	 t	          t          ||                    }n# t          $ r d}Y nw xY w|"|<   |rt)          j        ||          nd}|pt/          ||          }|s |rt1          |t3          |          |          }|r|                     |           t5          |          }| dk    rl|st          d          cddd           S ||t          d          cddd           S "                    |          p|}|                    ||||          }nW| dk    r3|st          d          cddd           S |                    |          }nt          d|            cddd           S |                                }|r3t?          |          d	k    r|d         nd                     |          |d<   "fd|	D             }|                    d          s||d<   t?          |          d	k    r|d         |d<   tC          |||           |	D ]>}tE          ||           "                    |          }|rt)          j#        ||           ?tI          |d "fd|	D             D                        ddd           n# 1 swxY w Y   |                    d          rwdt	          |d                   v r`d}| dk    r)|r'"                    |          p|} tK          ||           }|dk    rd| d|d|d <   nd!t	          |d                   vrd"|d <   tM          j'        |d#$          S # t          $ r&}!t          t	          |!                    cY d}!~!S d}!~!ww xY w)%zPatch a file using replace mode or V4A patch format.

    ``cross_profile`` opts out of the soft cross-Hermes-profile guard for
    targets under another profile's skills/plugins/cron/memories
    directory. Same shape as ``write_file``'s flag.
    r  r   N)has_traversal_componentv4a_pathr   c                 D     |           rt          d| d          S d S )Nz*V4A patch header contains '..' traversal: z. Use the agent's cwd-relative path (no '..') or an absolute path in '*** Update File:' / '*** Add File:' / '*** Delete File:' / '*** Move File:' headers.)r  )r  r  s    r   _reject_v4a_traversalz)patch_tool.<locals>._reject_v4a_traversal  sK     '&x00 !E E E E   4r7   z/^\*\*\*\s*(?:Update|Add|Delete)\s+File:\s*(.+)$r,   z+^\*\*\*\s*Move\s+File:\s*(.+?)\s*->\s*(.+)$r   )	ExitStackr  zpath requiredz"old_string and new_string requiredzpatch content requiredzUnknown mode: z | r  c                 >    g | ]}                     |          p|S rz   r    r  _p_path_to_resolveds     r   r  zpatch_tool.<locals>.<listcomp>>  s:     " " "46!%%b))/R" " "r7   rd  r  r   r  c                     g | ]}||S rz   rz   )r  _rs     r   r  zpatch_tool.<locals>.<listcomp>P  s.     0 0 0Z\00 0 0r7   c              3   B   K   | ]}                     |          V  d S Nr  r  s     r   r  zpatch_tool.<locals>.<genexpr>Q  s2      !V!V"3"7"7";";!V!V!V!V!V!Vr7   zCould not findr  zThis is failure #z
 patching a  . Stop retrying with variations of the same old_string. Either: (1) re-read the file fresh to verify current content, (2) use a longer / more unique old_string with surrounding context lines, or (3) use write_file to replace the entire file if the targeted region is hard to anchor.r  z#Did you mean one of these sections?zfold_string not found. Use read_file to verify the current content, or search_files to locate the text.Frt  )(r0   retools.path_securityr  ra   finditer	MULTILINEgrouprr   r   r  r   r   rH   r   r   sort
contextlibr  enter_contextr
   r  r  r  r   r   r]  r    patch_replace	patch_v4ar  r-   r   r  r  r  r  r   r  r  )$r  r   r  r  r  r  rF   r  r  _paths_to_check_rer  _mr  r   r  r  r  _resolved_paths_seenr  r  _locksstale_warnings_cross_swr\  _replace_targetr   r  _resolved_modifiedfailure_countr   r  r  r  s$                                     @@r   
patch_toolr    sr    O %t$$$w5??????	C 	C$J 	 	 	 	 	 	, ,,QSXZ]Zghh 	- 	-Bxx{{((**H((22D ""8,,,,
 ,,MuVYVcdd 	1 	1BXXa[[..00"((1++2C2C2E2EF 1 1,,X66  KKKKK&&x0000	1
  1 1-b':: 	-m,,,,, 	15b'BBM 1!-00000@" &(%%! 	 	B/G<<==    boo&&r***		"
 	)(((((Y[[ F	F% ? ?$$Z%9"%=%=>>>> )+N02% / /3B@@AABB    BBB(*!"%@BL/<<<B 5b' B B Jr J 32tBxxIIC /"))#...$W--Hy   7%o667F	 F	 F	 F	 F	 F	 F	 F	8 %);%&JKK;F	 F	 F	 F	 F	 F	 F	 F	F #4"7"7"="="E!//ZYdee @%&>??OF	 F	 F	 F	 F	 F	 F	 F	P "++E22!"94"9"9::UF	 F	 F	 F	 F	 F	 F	 F	X !..**K x?B>?R?RVW?W?W.*;*;]b]g]ghv]w]wJ'" " " ":I" " "
 ??7++ 0B,-)**a//3Ea3HK0(2DQ[\\\\) ; ;B*2w777*..r22B ;"-gr::: &g 0 0!V!V!V!Vo!V!V!V0 0 0   IF	 F	 F	 F	 F	 F	 F	 F	 F	 F	 F	 F	 F	 F	 F	V ??7## 	(8CG@T<U<U(U(U
 My  T ,0066>$ 5gx H H!!! ! ! ! ! ! G$$ 7c+gBV>W>WWWC G$ z+E:::: " " "#a&&!!!!!!!!"s   V .GV GV GAV 35S/)JS/JS/JBS/(V 5S/V AS/V ''S/V DS/#V /S33V 6S37B$V 
W&WWWrN   2   patternr   	file_globoutput_modecontextc	           
         	 t          ||          \  }}d| |t          |          |pd||f}	t          5  t                              |ddt                      d          }
|
d         |	k    r|
dxx         dz  cc<   n
|	|
d<   d|
d<   |
d         }ddd           n# 1 swxY w Y   |d	k    rt          j        d
| d| |dd          S 	 t          ||          }n# t          t          t          f$ r d}Y nw xY wt          |rt          |          n|          }|rt          j        d|id          S t          |          }|                    | |||||||          }t          ||          }t!          |d          r<|j        D ]4}t!          |d          r"|j        rt'          |j        d          |_        5|                    d          }|r| d|d<   |dk    r	d| d|d<   t          j        |d          }|                    d          r||z   }|d| dz  }|S # t,          $ r&}t/          t          |                    cY d}~S d}~ww xY w)zSearch for content or files.searchrK   Nr   )rx  ry  r  rx  ry  r,   r  z(BLOCKED: You have run this exact search z times in a row. The results have NOT changed. You already have this information. STOP re-searching and proceed with your task.)rd  r  already_searchedFrt  rd  )r  r   r   r  rb  ra  r  r  r   r(   Trr  )densifyze result(s) omitted because they target credential, token, cache, or secret-bearing environment files._omittedr  zYou have run this exact search zY times consecutively. The results have not changed. Use the information you already have.r  rj  z'

[Hint: Results truncated. Use offset=zC to see more, or narrow with a more specific pattern or file_glob.])r	   ra   r  r   r   r   r  r  rH   r   r   r   r   r]  r  r   r   r   r(   r   r  r    r   r  )r  r   r   r  rb  ra  r  r  rF   
search_keyr  r.   r   r  r\  r   r   mr  result_jsonrp  r  s                         r   search_toolr  {  s   
M"3FEBB IIO

   		- 		-%00 CEE; ;  I $
22-(((A-(((((2	*%+,	-(m,E		- 		- 		- 		- 		- 		- 		- 		- 		- 		- 		- 		- 		- 		- 		- A:::Du D D D #$)  "# # # #	!24AAMM\2 	! 	! 	! MMM	!*+X3}+=+=+=TXYY 	J:w45IIII ))$vK ! 
 
 6fgFF69%% 	Q^ Q Q1i(( QQY Q 5ai4 P P PAInnTn22 	 E E E 
#
 A::V% V V V 
#
 j5AAA ??;'' 	X 5.K  X{  X  X  X  XK " " "#a&&!!!!!!!!"sl   2H' ABH' BH' "B#&H' C H' C85H' 7C88<H' 5C1H' '
I1III)registryr  c                  "    ddl m}   |             S )z=Lazy wrapper to avoid circular import with tools/__init__.py.r   check_file_requirements)toolsr  r  s    r   _check_file_reqsr    s#    ------""$$$r7   r  u  Read a text file with line numbers and pagination. Use this instead of cat/head/tail in terminal. Output format: 'LINE_NUM|CONTENT'. Suggests similar filenames if not found. Use offset and limit for large files. Reads exceeding ~100K characters are truncated on a line boundary and return a next_offset; continue with offset to read the rest. Jupyter notebooks (.ipynb), Word documents (.docx), and Excel workbooks (.xlsx) are auto-extracted to readable text. NOTE: Cannot read images or other binary files — use vision_analyze for images.objectstringz8Path to the file to read (absolute, relative, or ~/path))r  descriptionintegerz9Line number to start reading from (1-indexed, default: 1))r  r  rD   minimumz9Maximum number of lines to read (default: 500, max: 2000)i  )r  r  rD   maximum)r   ra  rb  )r  
propertiesrequired)re   r  
parametersr  u  Write content to a file, completely replacing existing content. Use this instead of echo/cat heredoc in terminal. Creates parent directories automatically. OVERWRITES the entire file — use 'patch' for targeted edits. Auto-runs syntax checks on .py/.json/.yaml/.toml and other linted languages; only NEW errors introduced by this write are surfaced (pre-existing errors are filtered out).zWPath to the file to write (will be created if it doesn't exist, overwritten if it does)z%Complete content to write to the filebooleanu0  Opt out of the cross-profile soft guard. Defaults to false. Set true ONLY after explicit user direction to edit another Hermes profile's skills/plugins/cron/memories — by default these writes are blocked with a warning because they affect a different profile than the one this session is running under.)r  r  rD   )r   r(   r  a  Targeted find-and-replace edits in files. Use this instead of sed/awk in terminal. Uses fuzzy matching (9 strategies) so minor whitespace/indentation differences won't break it. Returns a unified diff. Auto-runs syntax checks after editing.

REPLACE MODE (mode='replace', default): find a unique string and replace it. REQUIRED PARAMETERS: mode, path, old_string, new_string.
PATCH MODE (mode='patch'): apply V4A multi-file patches for bulk changes. REQUIRED PARAMETERS: mode, patch.znEdit mode. 'replace' (default): requires path + old_string + new_string. 'patch': requires patch content only.)r  enumr  rD   z0REQUIRED when mode='replace'. File path to edit.zREQUIRED when mode='replace'. Exact text to find and replace. Must be unique in the file unless replace_all=true. Include surrounding context lines to ensure uniqueness.z`REQUIRED when mode='replace'. Replacement text. Pass empty string '' to delete the matched text.zLReplace all occurrences instead of requiring a unique match (default: false)zREQUIRED when mode='patch'. V4A format patch content. Format:
*** Begin Patch
*** Update File: path/to/file
@@ context hint @@
 context line
-removed line
+added line
*** End PatchzOpt out of the cross-profile soft guard. Defaults to false. Set true ONLY after explicit user direction to edit another Hermes profile's skills/plugins/cron/memories.)r  r   r  r  r  r  r  search_filesu  Search file contents or find files by name. Use this instead of grep/rg/find/ls in terminal. Ripgrep-backed, faster than shell equivalents.

Content search (target='content'): Regex search inside files. Output modes: full matches with line numbers, file paths only, or match counts.

File search (target='files'): Find files by glob pattern (e.g., '*.py', '*config*'). Also use this instead of ls — results sorted by modification time.zPRegex pattern for content search, or glob pattern (e.g., '*.py') for file searchr   zK'content' searches inside file contents, 'files' searches for files by namezCDirectory or file to search in (default: current working directory)zOFilter files by pattern in grep mode (e.g., '*.py' to only search Python files)z1Maximum number of results to return (default: 50)z0Skip first N results for pagination (default: 0))r(   
files_onlyr.   zOutput format for grep mode: 'content' shows matching lines with line numbers, 'files_only' lists file paths, 'count' shows match counts per filezDNumber of context lines before and after each match (grep mode only))r  r   r   r  rb  ra  r  r  c                     |                     d          pd}t          |                      dd          |                      dd          |                      dd          |	          S )
NrF   rD   r   rK   ra  r,   rb  r  )r   ra  rb  rF   )r    r  argskwr   s      r   _handle_read_filer'  D  s_    
&&


(yCtxx33DHHXq<Q<QY]YaYabiknYoYoy|}}}}r7   c                 "   |                     d          pd}|                      d          r(t          |                      d          t                    st          d          S d| vrt          d          S t          | d         t                    s+t          dt	          | d                   j         d          S t          | d         | d         |t          |                      d	d
                    |                     d                    S )NrF   rD   r   zdwrite_file: missing required field 'path'. Re-emit the tool call with both 'path' and 'content' set.r(   u%  write_file: missing required field 'content'. The tool call included a path but no content argument — this is almost always a dropped-arg bug under context pressure. Re-emit the tool call with the full content payload, or use execute_code with hermes_tools.write_file() for very large files.z,write_file: 'content' must be a string, got rN   r  Fr  )r   r(   rF   r  r  )r    r!   ra   r  r  r_   r  r   r$  s      r   _handle_write_filer)  I  s$   
&&


(yC88F 
:dhhv.>.>#D#D 
-
 
 	
 
 
 	
 d9os++ 
1DO$$-1 1 1
 
 	
 &\4	?C488OU;;<<66,''   r7   c                    |                     d          pd}t          |                      dd          |                      d          |                      d          |                      d          |                      dd	          |                      d
          |t          |                      dd	                    |                     d          	  	        S )NrF   rD   r  r  r   r  r  r  Fr  r  r  )	r  r   r  r  r  r  rF   r  r  )r    r  r   r$  s      r   _handle_patchr+  d  s    
&&


(yCXXfi((txx/?/?88L))dhh|6L6LHH]E22$((7:K:KUX488OU;;<<66,''   r7   c                    |                     d          pd}ddd}|                      dd          }|                     ||          }t          |                      dd          ||                      d	d
          |                      d          |                      dd          |                      dd          |                      dd          |                      dd          |	  	        S )NrF   rD   r(   r   )grepfindr   r  rK   r   rN   r  rb  r  ra  r   r  r  )	r  r   r   r  rb  ra  r  r  rF   )r    r  )r%  r&  r   
target_map
raw_targetr   s         r   _handle_search_filesr1  o  s    
&&


(yC#W55J(I..J^^J
33FB''TXXfc=R=R((;''txx/D/DTXXV^`aMbMbHH]I66TU@V@V`ce e e er7   fileu   📖)re   toolsetschemahandlercheck_fnemojimax_result_size_charsu   ✍️u   🔧u   🔎)rD   r  )r,   r  rD   )rD   FN)	r  NNNFNrD   FN)r(   rN   Nr  r   r(   r   rD   )v__doc__r   r  loggingr   rm   rT  pathlibr   r   r   r   tools.binary_extensionsr   tools.file_operationsr   r   r	   r  r
   agent.redactr   	getLoggerr_   rV  EACCESEPERMEROFSr   ra   r   r$   r   r"   __annotations__r'   tupler   r6   r  	frozensetr   rI   rs   ri   rf   rk   ro   rv   r{   r   r   r   r   r   rH   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   rU  r   r   dictr   r   r   r  r   r   r   r   listr  r  r  r  r  r  r  r*  r,  r]  r`  r  r  r  r  r  r  r  r  r  r  tools.registryr  r  r  READ_FILE_SCHEMAWRITE_FILE_SCHEMAPATCH_SCHEMASEARCH_FILES_SCHEMAr'  r)  r+  r1  registerrz   r7   r   <module>rN     sI   < < <    				         ' ' ' ' ' ' ' ' 2 2 2 2 2 2 8 8 8 8 8 8         
       . . . . . .		8	$	$  ,U[A $ $ $ $ $ $> " %) d
 ) ) )"S " " " ".),c ),c ),eCdN>S ), ), ), ),\ !  "	 	# 	# 	# 	 	 5 5C 5# 5d]>R 5 5 5 5 $)$B$B$BCC $-I.[.[.[$\$\ !!A !A !AS !A !A !A !AHF F3 Ft F F F F8d
](B 8} 8 8 8 8d
 sTz    "B#* B B B B8 83 8sTz 8 8 8 8&S S4Z    0" "C "d
 " " " "J$& $&3 $&sTz $& $& $& $&P , $(, , ,, D[, 
M	, , , ,^ S 3 tmG[    (& &s &d &S &Y\_cYc & & & &R!# !$ ! ! ! !H% % %d
T0A %T % % % %P/ /# / /CRVJ / / / /  S    H  12DE &* t * * *!& #S4Z # # # #" C # cDj    < 3 sTz    D9 9 9c 9#PT* 9 9 9 9xi D      !!       +c +d
 +t + + + +&R R RS4Z R R R R< $Y^%% t    %in&& !  ! ! !,3 ,s ,s , , , ,"	(3 	( 	( 	( 	( 	( 	("  
 = 0d 0t 0 0 0 0f3 4    > 2  2  2  2  2  2FC D    _ _3 _/B _ _ _ _D$ $# $ $ $ $d" d" d"c d"c d"# d"^a d" d" d" d"R	4 4c 4 4 4 460 0C 0 0 0 0( s t    <.S .3 .4 . . . .0C # #*    D "H HHIH d
H 
	H H H HD =F*/-1P" P"# P" P"c P"#'P" #d
P"69P" P" P" P"f KOOS?D(," "S "C "C ""48"IL""8<" :" 25" " " "D DGFG=>(R" R" R"c R"S R"R".1R"@CR" R"7:R" R" .1R" R" R" R"t 0 / / / / / / /% % %  s%6pqq(9t  BC  PQ  R  R'8s  AD  QU  V  V
 

 H     [%  7P  Q  Q (9`aa!  R  
 
 Y'   & 	,  !"G,  P$	  !Q 
 !  K 
 !  B 
 "m   !  ] 
 "  H  9!
 !
D HI% %1 1h  O (  :L  M  M')W1E  Wd  qz  {  {%6{  IL  M  M"*  <M  N  N'8kxz{{(9kxyzz$,6X6X6X  j}  JS  T  T )  ;A  NO  P  P	
 	
 K   (~ ~ ~
  6  e e e  {F;KUf  rB  JP  ho  p  p  p  p  |V<MWi  uE  MU  mt  u  u  u  u  w|]eu  ~D  \c  d  d  d  d  ~v>Q[o  {K  SY  qx  y  y  y  y  y  yr7   