
    iX                     ^   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"d{de#de#defdZ$d{de#de#dz  fdZ%d{de#de#defdZ&de#de'fdZ(dZ)ddhZ*d{de#de#de#dz  fdZ+de,de'fdZ- ej.                    Z/i Z0e1ed<    ej.                    Z2i Z3e1ed<   dZ4dZ5dZ6d Z7d!e1ddfd"Z8d#e#de'fd$Z9d{de#defd%Z:d|de#fd&Z;d}d(e#d)ed*ede#de#f
d+Z<d|de#fd,Z=d{de#fd-Z>de#de#ddfd.Z?de#de#ddfd/Z@de#de#de#dz  fd0ZAd{d(e#d#e#de#de#fd1ZB	 	 	 d~d4e#d(e#d5e#d6e#d7e'd8e#de#de#fd9ZC	 	 	 	 dd<e#d=e#d(e#d>e#d*ed)ed?e#d@ede#de#fdAZDddBlEmFZFmGZG dC ZHdDdEdFdGdHdIdJdKd'd'dLdJdMddNdOdPd(gdQdRZIdSdTdFdGdUdIdGdVdIdWd(d#gdQdRZJd8dXdFdGd2d8gdYd2dZdGd[dIdGd\dIdGd]dId^d_d3d`dGdadIdbd4gdQdRZKdcdddFdGdedIdGd#dfgdgd#dZdGdhd:d`dGdidIdJdjd;d`dJdkdd`dGg dldmd#dZdJdndd`dod<gdQdRZLdp ZMdq ZNdr ZOds ZP eFjQ        dDdteIeMeHdu eRdv          w            eFjQ        dSdteJeNeHdxd	w            eFjQ        d8dteKeOeHdyd	w            eFjQ        dcdteLePeHdzd	w           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    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envcwd)_active_environments	_env_lock)	tools.terminal_toolr/   r   _file_ops_lock_file_ops_cacher   getattrr2   r3   )r)   r/   container_keycachedlive_cwdr2   r3   r0   s           r   _get_live_tracking_cwdr;   X   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                 8   t          |                                           }|                                sRt          |          p0t          j                            dt	          j                              }t          |          |z  }|                                S )zFResolve *filepath* against the task's live terminal cwd when possible.TERMINAL_CWD)	r   
expanduseris_absoluter;   osenvironr   getcwdresolve)r(   r)   pbases       r   r+   r+   w   sw    X!!##A==?? %g.. 
"*..BIKK3
 3
 JJN99;;r-   c                     t           j                            |           }|t          v rdS |                    d          r|                    d          rdS dS )uG  Return True if the path would hang the process (infinite output or blocking input).

    Uses the *literal* path — no symlink resolution — because the model
    specifies paths directly and realpath follows symlinks all the way
    through (e.g. /dev/stdin → /proc/self/fd/0 → /dev/pts/0), defeating
    the check.
    Tz/proc/)z/fd/0z/fd/1z/fd/2F)r@   pathr>   _BLOCKED_DEVICE_PATHS
startswithendswith)r(   
normalizeds     r   _is_blocked_devicerL      sf     ##H--J***tX&& :+>+>#, ,  t5r-   )z/etc/z/boot/z/usr/lib/systemd/z/private/etc/z/private/var/z/var/run/docker.sockz/run/docker.sockc                    	 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 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.N)strr+   OSError
ValueErrorr@   rG   normpathr>   _SENSITIVE_PATH_PREFIXESrI   _SENSITIVE_EXACT_PATHS)r(   r)   resolvedrK   _errprefixs         r   _check_sensitive_pathrW      s    -h@@AAZ    !!"'"4"4X">">??J	Nx 	N 	N 	N 	 +  v&& 	**?*?*G*G 	KKK	)))Z;Q-Q-Q4s     66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   PermissionErrorrO   errno_EXPECTED_WRITE_ERRNOS)rX   s    r   _is_expected_write_exceptionr]      sB    #'' t#w CI1G$G$Gt5r-   r6   _read_tracker  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   len_READ_HISTORY_CAPrangepopKeyError
_DEDUP_CAPnextiterStopIteration_READ_TIMESTAMPS_CAP)r`   rhexcess_rc   rd   tss          r   _cap_read_tracker_datart      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   rN   strip_READ_DEDUP_STATUS_MESSAGErf   )ru   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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$                              d0|| 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 )1a  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   )	r2   r3   _create_environment_get_env_config_last_activity_start_cleanup_thread_creation_locks_creation_locks_lockr/   N)_task_env_overridesenv_typedockerdocker_imagesingularitysingularity_imagemodalmodal_imagedaytonadaytona_image r1   z*Creating new %s environment for task %s...   )r   r   r   r   vercel_sandboxcontainer_cpu   container_memoryi   container_diski   container_persistentTvercel_runtimedocker_volumesdocker_mount_cwd_to_workspaceFdocker_forward_envdocker_run_as_host_user)	r   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   imager1   r   
ssh_configcontainer_configlocal_configr)   r   z %s environment ready for task %s)r4   r2   r3   r}   r~   r   r   r   r   r/   timer5   r6   r   ri   	threadingLockr   loggerinfor   )r)   r2   r3   r}   r~   r   r   r   r   r/   r   r9   	task_lockterminal_envr   configr   	overridesr   r1   r   r   r   file_opss                           r   _get_file_opsr   1  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,	- - - - - - - - - - - - - - -
 
 MS M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[\\\#ZZZ%+ZZ%C%C(.

3Et(L(L&,jj1A5&I&I,2JJ7Mt,T,T&,jj1A2&F&F&,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R[MS MS MS MS MS MS MS MS MS MS MS MS MS MS M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	JQ*PQP	QP	1QQQ9RRR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)r5   r6   ri   clear)r)   s    r   clear_file_ops_cacher     s    	 $ $ 	$....!!###	$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $s   8AAAr   rG   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          |           }|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                  |_        |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        d/| d0| |dd          S |d1k    r	d2| d3|d4<   t          j        |d          S # tD          $ r&}tK          t          |                    cY d}~S d}~ww xY w)5z-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consecutiverb   rc   rd   re   rd   re   rc   r   rw   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   rG   already_readFensure_ascii	unchangedT)statusmessagerG   rc   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   rG   r   r   ru      	truncated_hintzThis file is large (zj bytes). Consider reading only the section you need with offset and limit to keep context usage efficient.readrb   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   rL   jsondumpsr+   r   rN   suffixlowerr   _read_tracker_lockr^   
setdefaultsetr   r@   rG   getmtimert   ry   rO   r   	read_fileto_dictrf   ru   r   r
   _LARGE_FILE_HINT_BYTESri   addboolr	   record_readr   r   debug
tool_error)rG   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   O"1&%@@
 d## 	:8D 8 8 8    +499	  I// 	#))++D:Z Z Z Z Z Z    +400 	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>BBFN%+^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  &T T "A*ET ET E T &,H/ :GH/ GH/ G ,H/  H/ .T /
H<9T ;H<<B9T 6A:T 0A<Q-AP10Q1
P>;Q=P>>QT Q  T #Q $T (?R( 'T (&ST S&T 8$T 
U'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.
    rc   rd   N)r   r^   r   r   valuesr)   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.
    Nr   r   r   rd   )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.
    Nrc   c                 ,    g | ]}|d          k    |S )r    ).0krT   s     r   
<listcomp>z._invalidate_dedup_for_path.<locals>.<listcomp>  s'    ;;;A!A$(*:*:a*:*:*:r-   )rN   r,   rO   rP   r   r^   r   )r(   r)   r`   rc   
stale_keysr  rT   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).
    Nre   )r  rN   r+   r@   rG   r   rO   rP   r   r^   r   r   rt   )r(   r)   rT   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.
    Nre   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.)
rN   r+   rO   rP   r   r^   r   r@   rG   r   )r(   r)   rT   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c                    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          |          }|                    | |          }|	                                }|	p|}
|
r|
|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t+          |          j        |           n0t&                              dt+          |          j        |d	
           t          t          |                    cY d}~S d}~ww xY w)zWrite content to a file.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   z"write_file expected denial: %s: %szwrite_file error: %s: %sTr   )rW   r   r{   rN   r+   r   r  r   
write_filer   r  r   r   r	   	lock_pathcheck_staler   
note_writer]   r   r   type__name__r   )rG   ru   r)   sensitive_errr   stale_warningr   r   r   cross_warningeffective_warningr   s               r   write_file_toolr    s   )$88M )-((($W-- 
Y
 
 	
,"	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$W--H((w77F ..**K - >  <*;J' #4111??7++ :%gy999	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	:  z+E:::: " " "'** 	YLL=tAww?OQRSSSSLL3T!WW5EqSWLXXX#a&&!!!!!!!!"si   A  G  A/,G .A//A:G *G >B$F."G .F22G 5F26G 
I-B	I("I-(I-replaceFmode
old_string
new_stringreplace_allpatchc                    g }|r|                     |           | dk    r_|r]ddl}|                    d||j                  D ]<}	|                     |	                    d                                                     =|D ]%}
t          |
|          }|rt          |          c S &	 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 ]z}
	 t          t          |
|                    }n# t          $ r d}Y nw xY w|||
<   |rt#          j        ||          nd}|pt)          |
|          }|r|                     |           {t+          |          }| dk    rU|st          d          cddd           S ||t          d	          cddd           S |                    ||||          }nW| dk    r3|st          d
          cddd           S |                    |          }nt          d|            cddd           S |                                }|r3t3          |          dk    r|d         nd                    |          |d<   |                    d          sA|D ]>}
t9          |
|           |                    |
          }|rt#          j        ||           ?ddd           n# 1 swxY w Y   |                    d          r3dt          |d                   v rdt          |d                   vrd|d<   t=          j        |d          S # t          $ r&}t          t          |                    cY d}~S d}~ww xY w)z4Patch a file using replace mode or V4A patch format.r  r   Nz/^\*\*\*\s+(?:Update|Add|Delete)\s+File:\s*(.+)$r   )	ExitStackr  zpath requiredz"old_string and new_string requiredzpatch content requiredzUnknown mode: z | r   r   zCould not findz#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.r   Fr   ) appendrefinditer	MULTILINEgrouprx   rW   r   r   rN   r+   r   r   sort
contextlibr  enter_contextr	   r  r  r  r   patch_replace	patch_v4ar   rf   joinr   r  r  r   r   )r  rG   r  r  r  r  r)   _paths_to_check_re_m_pr  _resolved_paths_seen_rr  _locksstale_warnings_path_to_resolved_cross_swr   r   r   r   s                            r   
patch_toolr7  R  s   
 O %t$$$w5,,QSXZ]Zghh 	8 	8B""288A;;#4#4#6#67777 - --b':: 	-m,,,,,	-M" &(%%! 	 	B/G<<==    boo&&r***		"
 	)(((((Y[[ ,	;F% ? ?$$Z%9"%=%=>>>> )+N02% 	/ 	/3B@@AABB    BBB(*!"%@BL/<<<B 5b' B B /"))#...$W--Hy   7%o66/,	; ,	; ,	; ,	; ,	; ,	; ,	; ,	;0 %);%&JKK3,	; ,	; ,	; ,	; ,	; ,	; ,	; ,	;4 "//j*kZZ @%&>??;,	; ,	; ,	; ,	; ,	; ,	; ,	; ,	;< "++E22!"94"9"9::A,	; ,	; ,	; ,	; ,	; ,	; ,	; ,	;D !..**K x?B>?R?RVW?W?W.*;*;]b]g]ghv]w]wJ' ??7++ ;) ; ;B*2w777*..r22B ;"-gr:::Y,	; ,	; ,	; ,	; ,	; ,	; ,	; ,	; ,	; ,	; ,	; ,	; ,	; ,	; ,	;b ??7## 	(8CG@T<U<U(U(U4CG@T<U<UUUC G$ z+E:::: " " "#a&&!!!!!!!!"s   (O =CO C*'O )C**AO 5M8FMF%"M$F%%A0MO "M4O /M0O ='M$O 1BMO M  O #M $A O 
O5O0*O50O5.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 ]2}t          |d          r |j        rt          |j                  |_        3|                                }|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   rb   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:  rG   r;  r<  r   r   r=  r>  matchesru   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   rN   r   r^   r   r   r   r   r   r@  hasattrrB  ru   r
   r   r   r   r   )r:  r;  rG   r<  r   r   r=  r>  r)   
search_keyr`   r   r   r   mr   result_jsonnext_offsetr   s                      r   search_toolrH    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%% 	A^ A A1i(( AQY A 5ai @ @AI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)toolsrL  rK  s    r   _check_file_reqsrN     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))r  descriptionintegerz9Line number to start reading from (1-indexed, default: 1))r  rQ  r'   minimumz9Maximum number of lines to read (default: 500, max: 2000)i  )r  rQ  r'   maximum)rG   r   r   )r  
propertiesrequired)namerQ  
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.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)rG   ru   ai  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 (default): find a unique string and replace it.
Patch mode: apply V4A multi-file patches for bulk changes.zVEdit mode: 'replace' for targeted find-and-replace, 'patch' for V4A multi-file patches)r  enumrQ  r'   z/File path to edit (required for 'replace' mode)zText to find in the file (required for 'replace' mode). Must be unique in the file unless replace_all=true. Include enough surrounding context to ensure uniqueness.z_Replacement text (required for 'replace' mode). Can be empty string to delete the matched text.booleanzLReplace all occurrences instead of requiring a unique match (default: false))r  rQ  r'   zV4A format patch content (required for 'patch' mode). Format:
*** Begin Patch
*** Update File: path/to/file
@@ context hint @@
 context line
-removed line
+added line
*** End Patch)r  rG   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 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))ru   
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;  rG   r<  r   r   r=  r>  c                     |                     d          pd}t          |                      dd          |                      dd          |                      dd          |	          S )
Nr)   r'   rG   r   r   r   r   r_   )rG   r   r   r)   )r   r   argskwtids      r   _handle_read_filerc  E  s_    
&&


(yCtxx33DHHXq<Q<QY]YaYabiknYoYoy|}}}}r-   c                     |                     d          pd}t          |                      dd          |                      dd          |          S )Nr)   r'   rG   r   ru   )rG   ru   r)   )r   r  r_  s      r   _handle_write_filere  J  sK    
&&


(yC 4 4dhhyRT>U>U_bccccr-   c           
      D   |                     d          pd}t          |                      dd          |                      d          |                      d          |                      d          |                      dd	          |                      d
          |          S )Nr)   r'   r  r  rG   r  r  r  Fr  )r  rG   r  r  r  r  r)   )r   r7  r_  s      r   _handle_patchrg  O  s    
&&


(yCXXfi((txx/?/?88L))dhh|6L6LHH]E22$((7:K:KUXZ Z Z Z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'   ru   r\  )grepfindr;  r:  r   rG   r8  r<  r   r9  r   r   r=  r>  )	r:  r;  rG   r<  r   r   r=  r>  r)   )r   rH  )r`  ra  rb  
target_map
raw_targetr;  s         r   _handle_search_filesrm  W  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   📖inf)rW  toolsetschemahandlercheck_fnemojimax_result_size_charsu   ✍️u   🔧u   🔎)r'   )N)r   r_   r'   )r  NNNFNr'   )ru   r8  Nr9  r   ru   r   r'   )S__doc__r[   r   loggingr@   r   pathlibr   agent.file_safetyr   tools.binary_extensionsr   tools.file_operationsr   r   r   rM  r	   agent.redactr
   	getLoggerr  r   EACCESEPERMEROFSr\   r   r   r   __annotations__r   r   	frozensetrH   rN   r,   r;   r+   r   rL   rR   rS   rW   r   r]   r   r5   r6   dictr   r^   rg   rk   ro   ry   rt   r{   r   r   r   r   r   r  r  r  r  r7  rH  tools.registryrI  r   rN  READ_FILE_SCHEMAWRITE_FILE_SCHEMAPATCH_SCHEMASEARCH_FILES_SCHEMArc  re  rg  rm  registerr   r  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 C d
    > S 3 t         *  12DE  C # cDj    &i D      !!   " $Y^%% t     
 = 0d 0t 0 0 0 0f3 4    >B B3 B/B B B B BJ$ $# $ $ $ $Q" Q" Q"c Q"c Q"# Q"^a Q" Q" Q" Q"l4 4c 4 4 4 460 0C 0 0 0 0( s t    <.S .3 .4 . . . .0C # #*    >6" 6"# 6" 6"c 6"# 6" 6" 6" 6"r KOOS']" ]"S ]"C ]"C ]"]"48]"IL]"]",/]" ]" ]" ]"@ 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     p%  7P  Q  Q (9`aa
 
 Y'     B%	7/C  Um  zC  D  D%6ghh#+  =c  d  d#+  =^  _  _$-  ?M  Z_  `  `&  8u  v  v
 
 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   (~ ~ ~
d d d
Z Z Ze e e  {F;KUf  rB  JP  hm  hm  ns  ht  ht  u  u  u  u  |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-   