
    oq'j                       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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	Zdaedz  ed
<   defdZdZ  e!h d          Z"dde#de#defdZ$ e!h d          Z%de#dz  fdZ&dde#de#dz  fdZ'dde#de#dz  fdZ(dde#defdZ)dde#de#defdZ*dde#dede#de#dz  fdZ+de#de,fdZ-de#de,fdZ.dZ/dd hZ0da1e#dz  ed!<   d"a2de#dz  fd#Z3dde#de#de#dz  fd$Z4dde#de#dz  fd%Z5dde#de#de#dz  fd&Z6d'e7de,fd(Z8 ej9                    Z:i Z;e<ed)<    ej9                    Z=i Z>e<ed*<    ej9                    Z?i Z@e<ed+<   de#d,e#defd-ZAde#d.eBddfd/ZCd0ZDd1ZEd1ZFd2ZGd3e<ddfd4ZHd5e#de,fd6ZIdde#defd7ZJdde#fd8ZKdde#d:ed;ede#de#f
d<ZLdde#fd=ZMdde#fd>ZNde#de#ddfd?ZOde#de#ddfd@ZPde#de#de#dz  fdAZQ	 	 dde#d5e#de#dBe,de#f
dCZR	 	 	 ddEe#de#dFe#dGe#dHe,dIe#de#dBe,de#fdJZS	 	 	 	 ddMe#dNe#de#dOe#d;ed:edPe#dQede#de#fdRZTddSlUmVZVmWZW dT ZXdUdVdWdXdYdZd[d\d9d9d]d[d^d0d_d`dadgdbdcZYdddedWdXdfdZdXdgdZdhdid"djdkdd5gdbdcZZdIdldWdXdDdIgdmdDdndXdodZdXdpdZdXdqdZdhdrd"djdXdsdZdhdtd"djdudEgdbdcZ[dvdwdWdXdxdZdXd5dygdzd5dndXd{dKdjdXd|dZd[d}dLdjd[d~ddjdXg ddd5dnd[dddjddMgdbdcZ\d Z]d Z^d Z_d Z` eVja        dUdeYe]eXdd	            eVja        dddeZe^eXdd	            eVja        dIde[e_eXdd	            eVja        dvde\e`eXdd	           dS )z6File Tools Module - LLM agent file manipulation tools.    N)Path)get_read_block_error)has_binary_extension)ShellFileOperationsnormalize_read_paginationnormalize_search_pagination)
file_state)redact_sensitive_texti _max_read_chars_cachedreturnc                     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float	Exception_DEFAULT_MAX_READ_CHARS)r   cfgvals      5/home/ubuntu/.hermes/hermes-agent/tools/file_tools.py_get_max_read_charsr   '   s     )%%111111kmmgg+,,cC<(( 	*S1WW%(XX"))   4!!s   AA. .
A;:A;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)r(   r)   s     r   _resolve_pathr,   Q   s     "(G444    >   ./ cwdauto.c                  &   t           j                            d          pd                                } |                                 t
          v rdS t           j                            |           }t           j                            |          sdS |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_CWDr/   N)	osenvironr   striplower_TERMINAL_CWD_SENTINELSpath
expanduserisabs)rawexpandeds     r   _configured_terminal_cwdr?   c   sv     :>>.))/R
6
6
8
8C
yy{{---tw!!#&&H7=="" tOr-   c                 R   	 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   |5t          t          |dd          dd          pt          |dd          }|r|S 	 ddl m}m} |5  |                    |          p|                    |           }|t          |dd          nd}ddd           n# 1 swxY w Y   |r|S n# t          $ r Y nw xY wdS )zCReturn the task's live terminal cwd for bookkeeping when available.r   )_resolve_container_task_idNenvr0   )_active_environments	_env_lock)	tools.terminal_toolrA   r   _file_ops_lock_file_ops_cacher   getattrrC   rD   )r)   rA   container_keycachedlive_cwdrC   rD   rB   s           r   _get_live_tracking_cwdrL   t   s5    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765$77EE 
E4J
 J
  	O	GGGGGGGG 	N 	N&**=99^=Q=U=UV]=^=^C47OwsE4000H	N 	N 	N 	N 	N 	N 	N 	N 	N 	N 	N 	N 	N 	N 	N  	O	    4sT    ##5A..A25A21
D ;A D;D DD DD 
D$#D$c                 D    t          |           }|r|S t                      S )uT  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 sentinel-free absolute ``$TERMINAL_CWD``. This is what lets
    a worktree session warn about (and resolve into) the worktree 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.
    )rL   r?   )r)   lives     r   _authoritative_workspace_rootrO      s*     "'**D #%%%r-   c                 >   t          |           }|r"t          |                                          }n t          t          j                              }|                                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 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.
      3. 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.
    )rO   r   r;   r5   getcwdis_absoluteresolve)r)   rootbases      r   _resolve_base_dirrV      s    * )11D !Dzz$$&&BIKK   ( BIKK  4'<<>>r-   c                     t          |                                           }|                                r|                                S t	          |          |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.
    )r   r;   rR   rS   rV   )r(   r)   ps      r   r+   r+      sX     	X!!##A}} yy{{g&&*33555r-   resolvedc           	         	 t          |                                                                           rdS t          |          }|sdS 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)u  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 sentinel-free
    absolute ``$TERMINAL_CWD`` — so a worktree 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;   rR   rO   rS   relative_to
ValueErrorstrr   )r(   rY   r)   workspace_rootrT   s        r   _path_resolution_warningr_      s    >>$$&&2244 	46w?? 	4N##..0088::
	  &&&4 	 	 	Q Q Q#h-- Q Q14TQ Q Q  	    tts:   3C C 
3C >B 0CC CC 
CCr:   c                 
   t           j                            |           }|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/mapsF)r5   r:   r;   _BLOCKED_DEVICE_PATHS
startswithendswith)r:   
normalizeds     r   _is_blocked_device_pathre      s    ##D))J***tX&& :+>+>#, ,  t X&& :+>+>), ,  t5r-   c                    t           j                            |           }t          |          rdS 	 t           j                            |          }n# t
          t          f$ r Y dS w xY w||k    rt          |          rdS dS )a2  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 the resolved path so a
    workspace symlink to /dev/zero cannot bypass the guard.
    TF)r5   r:   r;   re   realpathOSErrorr\   )r(   rd   rY   s      r   _is_blocked_deviceri     s     ##H--Jz** t7##J//Z    uu:"9("C"Ct5s   A A'&A')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                  Z   t           rt          S da 	 ddlm}  t	           |                                                       and# t          $ rW 	 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_loadedrj   r   rm   r]   rS   r   r   r;   rl   s    r   _get_hermes_config_resolvedro   *  s     & '&&%)"+555555"%oo&7&7&?&?&A&A"B"B + + +	+&)$/F*G*G*R*R*T*T*\*\*^*^&_&_## 	+ 	+ 	+&*###	++
 #"s6   /A 
B#A BB#BB#BB#"B#c                    	 t          t          | |                    }n# t          t          f$ r | }Y nw xY wt          j                            t          j                            |                     }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)r]   r+   rh   r\   r5   r:   normpathr;   _SENSITIVE_PATH_PREFIXESrb   _SENSITIVE_EXACT_PATHSro   )r(   r)   rY   rd   _errprefixhermes_configs          r   _check_sensitive_pathrw   ;  sC   -h@@AAZ    !!"'"4"4X">">??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   )rC   rD   _get_env_configrA   NDockerEnvironment_persistentFz/root/.hermesenv_typedockercontainer_persistentT)rE   rC   rD   ry   rA   r   r   	__class____name__boolrH   )r)   rC   rD   ry   rA   rI   rB   configs           r   %_get_container_mirror_prefix_for_taskr   Y  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 (#TBD) — 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   r]   r+   rh   r\   r   )r(   r)   r   r   r   rY   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   PermissionErrorrh   errno_EXPECTED_WRITE_ERRNOS)r   s    r   _is_expected_write_exceptionr     sB    #'' t#w CI1G$G$Gt5r-   rG   _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      N)_patch_failure_lockr   
setdefaultlennextiterStopIterationr   )r)   r   task_failures	first_keys       r   _record_patch_failurer     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)r)   r   r   rps       r   _reset_patch_failuresr     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_CAPranger   KeyError
_DEDUP_CAPr   r   r   _READ_TIMESTAMPS_CAP)r   rhexcess_r   r   tss          r   _cap_read_tracker_datar   
  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contentc                     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.
    FT   )r   r]   r7   _READ_DEDUP_STATUS_MESSAGEr   )r   strippeds     r   _is_internal_file_status_textr   =  sz    $ gs## u}}H u---t!X--MMQ%?!@!@@@@t5r-   c                 	   ddl m}m}m}m}m}m}m}m}m	}	 ddl
}
 |	|           } t          5  t                              |           }ddd           n# 1 swxY w Y   |}|5  | |v r%|

                                || <   |cddd           S 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   |ddl m}  |            }|d         }|                    | i           }|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|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!k    rl|                    d"d          |                    d#d          |                    d$d%          |                    d&d          |                    d'd          d(}d}|d)k    rd*|                    d+d          i} |||||d,         |||| |                    d-          .	  	        }|5  ||| <   |

                                || <   ddd           n# 1 swxY w Y    |             t$                              d/|| dd                    ddd           n# 1 swxY w Y   t)          |          }t          5  |t          | <   ddd           n# 1 swxY w Y   |S )0a  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   )	rC   rD   _create_environmentry   _last_activity_start_cleanup_thread_creation_locks_creation_locks_lockrA   N)_task_env_overridesr|   r}   docker_imagesingularitysingularity_imagemodalmodal_imagedaytonadaytona_imager/   r0   z*Creating new %s environment for task %s...   >   r   r}   r   r   container_cpur   container_memoryi   container_diski   r~   Tdocker_volumesdocker_mount_cwd_to_workspaceFdocker_forward_envdocker_run_as_host_user)r   r   r   r~   r   r   r   r   sshssh_hostssh_userssh_port   ssh_keyssh_persistent)hostuserportkey
persistentlocalr   local_persistenttimeouthost_cwd)	r|   imager0   r   
ssh_configcontainer_configlocal_configr)   r   z %s environment ready for task %s)rE   rC   rD   r   ry   r   r   r   r   rA   timerF   rG   r   r   	threadingLockr   loggerinfor   )r)   rC   rD   r   ry   r   r   r   r   rA   r   rJ   	task_lockterminal_envr   r   r|   	overridesr   r0   r   r   r   file_opss                           r   _get_file_opsr   \  s                         KKK((11G 
 . . $$W--. . . . . . . . . . . . . . . 	7 	7...*.))++w'	7 	7 	7 	7 	7 	7 	7 	7 $ 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,	- - - - - - - - - - - - - - -
 
 LS LS 	$ 	$...*.))++w'3G<#	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ ??????$_&&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--&&7&-C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/[/[	$ 	$  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RYLS LS LS LS LS LS LS LS LS LS LS LS LS LS LS^ #<00H	 , ,#+ , , , , , , , , , , , , , , ,Os   AAA#CCB<0C<C 	 CC 	CCC #DDDQ'EQE	QE	I;QO>2Q>P	QP	1QQ
Q$Q;;Q?Q?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)rF   rG   r   clear)r)   s    r   clear_file_ops_cacher     s    	 $ $ 	$....!!###	$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $s   8AAAr   offsetlimitc                 
   	 t          ||          \  }}t          |           rt          j        dd|  di          S t	          | |          }t          t          |                    r6|j                                        }t          j        dd|  d| di          S t          t          |                    }|rt          j        d|i          S t          |          }|||f}t          5  t                              |ddt                      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    rt          5  |	d
                             |d          dz   }||	d
         |<   t%          |	           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t&          | dddd          S n# t(          $ r Y nw xY wt+          |          }|                    | ||          }|                                }t1          |j        pd          }|                    dd          }t5                      }||k    r=|                    dd          }t          j        d|dd|dd| d| ||d d          S |j        r%t7          |j        d!          |_        |j        |d"<   |rA|t8          k    r6|d#k    r0|                    d$          r|                    d%d&|dd'           d(| ||f}t          5  d|	vri |	d<   d
|	vri |	d
<   |	d
                             |d           |	d)                             | ||f           |	d*         |k    r|	d+xx         dz  cc<   n
||	d*<   d|	d+<   |	d+         }	 t          j                            |          }||	d         |<   ||	                    di           |<   n# t(          $ r Y nw xY wt%          |	           ddd           n# 1 swxY w Y   	 |dk    p!t?          |                    d$                    }tA          j!        |||,           n,# tD          $ r tF          $                    d-d.           Y nw xY w|d/k    rt          j        d0| d1| |dd          S |d2k    r	d3| d4|d5<   t          j        |d          S # tD          $ r&}tK          t          |                    cY d}~S d}~ww xY w)6z-Read a file with pagination and line numbers.errorzCannot read 'zE': this is a device file that would block or produce infinite output.zCannot read binary file 'z' (zF). Use vision_analyze for images, or terminal to inspect binary files.Nr   )last_keyconsecutiver   r   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.)r   r:   already_readFensure_ascii	unchangedT)statusmessager:   r   content_returnedr/   	file_sizetotal_linesunknownzRead produced ,z, characters which exceeds the safety limit (zD chars). Use offset and limit to read a smaller range. The file has z lines total.)r   r:   r  r  	code_filer      	truncated_hintzThis file is large (zj bytes). Consider reading only the section you need with offset and limit to keep context usage efficient.readr   r   r   )partialzfile_state.record_read failedexc_info   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)&r   ri   jsondumpsr+   r   r]   suffixr8   r   _read_tracker_lockr   r   setr   r5   r:   getmtimer   r   rh   r   	read_fileto_dictr   r   r   r
   _LARGE_FILE_HINT_BYTESr   addr   r	   record_readr   r   debug
tool_error)r:   r   r   r)   	_resolved_extblock_errorresolved_str	dedup_keyr   cached_mtimecurrent_mtimehitsr   resultresult_dictcontent_lenr  	max_charsr  read_keycount
_mtime_now_partiales                            r   read_file_toolr/    s   T"1&%@@
 d## 	:8D 8 8 8    +499	  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:?[N ? ?)2?? ? %0? ? ?
 *&
 
 "
# 
# 
# 
# > 	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  ;T, A!T,  3T, T, /A*E%T, %E))T, ,E)-T, 3,H< :G%H< %G))H< ,G)-,H<  H< ;T, <
I	T, I		B9T, A<T, ?A<Q+<AQ ?Q+ 
Q
Q+QQ+T, +Q//T, 2Q/3T, 7?R7 6T, 7&S T, S  &T, $T, ,
U6UUU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r)   r   s     r   reset_file_dedupr3    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.
    Nr   r   r   r   )r  r   r   r   r2  s     r   notify_other_tool_callr5    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    ).0krY   s     r   
<listcomp>z._invalidate_dedup_for_path.<locals>.<listcomp>  s'    ;;;A!A$(*:*:a*:*:*:r-   )r]   r,   rh   r\   r  r   r   )r(   r)   r   r   
stale_keysr:  rY   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=  r]   r+   r5   r:   r  rh   r\   r  r   r   r   r   )r(   r)   rY   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.)
r]   r+   rh   r\   r  r   r   r5   r:   r  )r(   r)   rY   r   
read_mtimer$  s         r   _check_file_stalenessrB  )  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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|vt          | |          }t          |          }|	                    | |          }	|	
                                }
|r||
d<   t          | |           t          j        |
d          S t          j        |          5  t          j        ||          }t          | |          }t#          | t%          |          |          }t          |          }|	                    ||          }	|	
                                }
|p|p|}|r||
d<   ||
d<   |
                    d          s|g|
d<   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	t1          |          j        |           n0t,                              d
t1          |          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 status text as file content. Re-read the file or reconstruct the intended file contents before writing.Nr  Fr   r   r   files_modifiedz"write_file expected denial: %s: %szwrite_file error: %s: %sTr  )rw   r  r   r   r]   r+   r   rB  r   
write_filer  r?  r  r  r	   	lock_pathcheck_staler_   r   r   
note_writer   r   r  typer   r   )r:   r   r)   rC  sensitive_errcross_warningr  stale_warningr   r&  r'  cwd_warningeffective_warningr.  s                 r   write_file_toolrP  H  sU    *$88M )-((( -1$@@ 	-m,,,$W-- 
Y
 
 	
5"	24AABBII 	 	 	III	 1$@@M$W--H((w77F ..**K 8*7J'"4111:k>>>>
 !),, 	: 	: '27IFFM1$@@M 34i'RRK$W--H((G<<F ..**K - M M+  <*;J' ,5K(??7++ <1:,- #4111??7++ :%gy9991	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	:2 z+E:::: " " "'** 	YLL=tAww?OQRSSSSLL3T!WW5EqSWLXXX#a&&!!!!!!!!"si   %B H2 BH2 BA:H2 H2 !C$HH2 HH2 HH2 2
K<B	KKKreplacemode
old_string
new_stringreplace_allpatchc                 4
    g }|r|                     |           | dk    r|rddl}	ddlm}
 |	                    d||	j                  D ]^}|                    d                                          } |
|          rt          d|d          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<   |D ]>}tC          ||                                |          }|rt)          j"        ||           ?tG          |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|}tI          ||          }|dk    rd| d|d|d<   ndt          |d                   vrd|d<   tK          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.
    rV  r   N)has_traversal_componentz/^\*\*\*\s+(?:Update|Add|Delete)\s+File:\s*(.+)$r   z*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:' headers.)	ExitStackrQ  zpath requiredz"old_string and new_string requiredzpatch content requiredzUnknown mode: z | r  c                 >    g | ]}                     |          p|S r8  r   r9  _p_path_to_resolveds     r   r;  zpatch_tool.<locals>.<listcomp>  s:     " " "46!%%b))/R" " "r-   r   rE  r   c                     g | ]}||S r8  r8  )r9  _rs     r   r;  zpatch_tool.<locals>.<listcomp>  s.     0 0 0Z\00 0 0r-   c              3   B   K   | ]}                     |          V  d S Nr[  r\  s     r   	<genexpr>zpatch_tool.<locals>.<genexpr>  s2      !V!V"3"7"7";";!V!V!V!V!V!Vr-   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.Fr   )'appendretools.path_securityrX  finditer	MULTILINEgroupr7   r  rw   r   r  r]   r+   r   r  sort
contextlibrY  enter_contextr	   rG  rH  rB  r_   r   r   r   patch_replace	patch_v4ar  r   joinr?  rI  r   r   r  r  )!rR  r:   rS  rT  rU  rV  r)   rC  _paths_to_check_rerX  _mv4a_pathr]  rK  rL  _resolved_paths_seenr`  rY  _locksstale_warnings_cross_swr   _replace_targetr&  r'  _resolved_modifiedfailure_countrY   r.  r^  s!                                   @r   
patch_toolr}    s    O %t$$$w5??????,,QSXZ]Zghh 	- 	-Bxx{{((**H '&x00 !a a a a    
 ""8,,,, 1 1-b':: 	-m,,,,, 	15b'BBM 1!-00000" &(%%! 	 	B/G<<==    boo&&r***		"
 	)(((((Y[[ E	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67E	 E	 E	 E	 E	 E	 E	 E	8 %);%&JKK;E	 E	 E	 E	 E	 E	 E	 E	F #4"7"7"="="E!//ZYdee @%&>??OE	 E	 E	 E	 E	 E	 E	 E	P "++E22!"94"9"9::UE	 E	 E	 E	 E	 E	 E	 E	X !..**K x?B>?R?RVW?W?W.*;*;]b]g]ghv]w]wJ'" " " ":I" " "
 ??7++ 0B,-)**a//3Ea3HK0) ; ;B*2w777*..r22B ;"-gr::: &g 0 0!V!V!V!Vo!V!V!V0 0 0   GE	 E	 E	 E	 E	 E	 E	 E	 E	 E	 E	 E	 E	 E	 E	T ??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   6S' D)(S' )D85S' 7D88AS' 5P:G$#P:$G30P:2G33BP:S' P:$S' 1AP:7S' 'P:+S' 8C6P:.S' :P>>S' P>B$S' '
T1TTTr2   2   patterntarget	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          |          }|	                    | |||||||          }t          |d          r<|j        D ]4}t          |d          r"|j        rt          |j        d          |_        5|                                }|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.searchr/   Nr   )r   r   r   r   r   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.)r   r  already_searchedFr   )r  r:   r  r  r   r   r  r  matchesr   Tr  r  zYou have run this exact search zY times consecutively. The results have not changed. Use the information you already have.r  r  z'

[Hint: Results truncated. Use offset=zC to see more, or narrow with a more specific pattern or file_glob.])r   r]   r  r   r   r  r  r  r   r  hasattrr  r   r
   r  r   r   r  )r  r  r:   r  r   r   r  r  r)   
search_keyr   r+  r   r&  mr'  result_jsonnext_offsetr.  s                      r   search_toolr  A  s   
>"3FEBB IIO

   		- 		-%00 CEE; ;  I $
22-(((A-(((((2	*%+,	-(m,E		- 		- 		- 		- 		- 		- 		- 		- 		- 		- 		- 		- 		- 		- 		- A:::Du D D D #$)  "# # # # !))$vK ! 
 
 69%% 	Q^ Q Q1i(( QQY Q 5ai4 P P PAInn&&A::V% V V V 
#
 j5AAA ??;'' 	X 5.K  X{  X  X  X  XK " " "#a&&!!!!!!!!"sH   2F  ABF  BF  "B#&F  
CF   
G*GGG)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#    ------""$$$r-   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 rejected; use offset and limit to read specific sections of large files. NOTE: Cannot read images or binary files — use vision_analyze for images.objectstringz8Path to the file to read (absolute, relative, or ~/path))rJ  descriptionintegerz9Line number to start reading from (1-indexed, default: 1))rJ  r  r'   minimumz9Maximum number of lines to read (default: 500, max: 2000)i  )rJ  r  r'   maximum)r:   r   r   )rJ  
propertiesrequired)namer  
parametersrF  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.)rJ  r  r'   )r:   r   rC  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.)rJ  enumr  r'   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.)rR  r:   rS  rT  rU  rV  rC  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 searchfileszK'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  r   r   r  r  c                     |                     d          pd}t          |                      dd          |                      dd          |                      dd          |	          S )
Nr)   r'   r:   r/   r   r   r   r   )r:   r   r   r)   )r   r/  argskwtids      r   _handle_read_filer    s_    
&&


(yCtxx33DHHXq<Q<QY]YaYabiknYoYoy|}}}}r-   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
                              S )Nr)   r'   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 r2   rC  F)r:   r   r)   rC  )r   r   r]   r  rJ  r   rP  r   r  s      r   _handle_write_filer     s   
&&


(yC88F 
:dhhv.>.>#D#D 
-
 
 	
 
 
 	
 d9os++ 
1DO$$-1 1 1
 
 	
 &\4	?C488OU;;<<   r-   c                    |                     d          pd}t          |                      dd          |                      d          |                      d          |                      d          |                      dd	          |                      d
          |t          |                      dd	                              S )Nr)   r'   rR  rQ  r:   rS  rT  rU  FrV  rC  )rR  r:   rS  rT  rU  rV  r)   rC  )r   r}  r   r  s      r   _handle_patchr    s    
&&


(yCXXfi((txx/?/?88L))dhh|6L6LHH]E22$((7:K:KUX488OU;;<<	   r-   c                    |                     d          pd}ddd}|                      dd          }|                     ||          }t          |                      dd          ||                      d	d
          |                      d          |                      dd          |                      dd          |                      dd          |                      dd          |	  	        S )Nr)   r'   r   r  )grepfindr  r  r/   r:   r2   r  r   r~  r   r   r  r  )	r  r  r:   r  r   r   r  r  r)   )r   r  )r  r  r  
target_map
raw_targetr  s         r   _handle_search_filesr  $  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r-   fileu   📖)r  toolsetschemahandlercheck_fnemojimax_result_size_charsu   ✍️u   🔧u   🔎)r'   rb  )r   r   r'   )r'   F)rQ  NNNFNr'   F)r   r2   Nr~  r   r   r   r'   )b__doc__r   r  loggingr5   r   pathlibr   r   r   tools.binary_extensionsr   tools.file_operationsr   r   r   r  r	   agent.redactr
   	getLoggerr   r   EACCESEPERMEROFSr   r   r   r   __annotations__r   r  	frozensetra   r]   r,   r9   r?   rL   rO   rV   r+   r_   r   re   ri   rr   rs   rj   rn   ro   rw   r   r   r   r   r   rF   rG   dictr  r   r   r   r   listr   r   r   r   r   r   r   r   r   r/  r3  r5  r=  r?  rB  rP  r}  r  tools.registryr  r  r  READ_FILE_SCHEMAWRITE_FILE_SCHEMAPATCH_SCHEMASEARCH_FILES_SCHEMAr  r  r  r  registerr8  r-   r   <module>r     s   < < <    				           2 2 2 2 2 2 8 8 8 8 8 8         
       . . . . . .		8	$	$  ,U[A  " %) d
 ) ) )"S " " " "0 !  "	 	# 	# 	# 	 	 5 5C 5# 5d 5 5 5 5 $)$B$B$BCC #*    " C d
    >& &3 &sTz & & & &$ s 4    D	6 	6S 	63 	6t 	6 	6 	6 	6" "s "d "S "Y\_cYc " " " "J# $    &     *  12DE &* t * * *!& #S4Z # # # #" C # cDj    < 3 sTz    D9 9 9c 9#PT* 9 9 9 9xi D      !!   " $Y^%% t    %in&& !  ! ! !,3 ,s ,s , , , ,"	(3 	( 	( 	( 	( 	( 	("  
 = 0d 0t 0 0 0 0f3 4    >A A3 A/B A A A AH$ $# $ $ $ $V" V" V"c V"c V"# V"^a V" V" V" V"v4 4c 4 4 4 460 0C 0 0 0 0( s t    <.S .3 .4 . . . .0C # #*    > =F*/K" K"# K" K"c K"#'K"47K" K" K" K"\ KOOS?Dh" h"S h"C h"C h"h"48h"ILh"h"8<h"ILh" h" h" h"V DGFG=>(C" C" C"c C"S C"C".1C"@CC" C"7:C" C" .1C" C" C" C"V 0 / / / / / / /% % %  c%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   (~ ~ ~
  4  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r-   