
    Ki                        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  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edefdZdZddhZdededz  fdZ de!defdZ" ej#                    Z$i Z%e&ed<    ej#                    Z'i Z(e&ed<   drdede	fdZ)dsdefdZ*dtdededededef
dZ+drdede,fd Z-dsdefd!Z.dsdefd"Z/drdefd#Z0dededdfd$Z1dedededz  fd%Z2drded&ededefd'Z3	 	 	 dud*eded+ed,ed-ed.ededefd/Z4	 	 	 	 dvd2ed3eded4ededed5ed6ededefd7Z5d8e+d9d:e3d9d.e4d9d;e5d9gZ6d< Z7dd=l8m9Z9 d> Z:d8d?d@dAdBdCdDdEdddFdDdGddHdIdJdgdKdLZ;d:dMd@dAdNdCdAdOdCdPdd&gdKdLZ<d.dQd@dAd(d.gdRd(dSdAdTdCdAdUdCdAdVdCdWdXd)dYdAdZdCd[d*gdKdLZ=d;d\d@dAd]dCdAd&d^gd_d&dSdAd`d0dYdAdadCdDdbd1dYdDdcddYdAg ddded&dSdDdfddYdgd2gdKdLZ>dh Z?di Z@dj ZAdk ZB e9jC        d8dle;e?e:dmn            e9jC        d:dle<e@e:don            e9jC        d.dle=eAe:dpn            e9jC        d;dle>eBe:dqn           dS )wz6File Tools Module - LLM agent file manipulation tools.    N)Path)ShellFileOperations)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      -/home/ubuntu/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filepathc                     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)ospath
expanduser_BLOCKED_DEVICE_PATHS
startswithendswith)r"   
normalizeds     r   _is_blocked_devicer+   I   sf     ##H--J***tX&& :+>+>#, ,  t5    )z/etc/z/boot/z/usr/lib/systemd/z/var/run/docker.sockz/run/docker.sockc                     	 t           j                            t           j                            |                     }n# t          t
          f$ r | }Y nw xY wt          D ]}|                    |          rd|  dc S  |t          v 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.N)	r$   r%   realpathr&   OSError
ValueError_SENSITIVE_PATH_PREFIXESr(   _SENSITIVE_EXACT_PATHS)r"   resolvedprefixs      r   _check_sensitive_pathr5   b   s    7##BG$6$6x$@$@AAZ    *  v&& 	Vx V V V  	
 )))R8 R R R	
 4s   <? AAexcc                 ~    t          | t                    rdS t          | t                    r| j        t          v rdS dS )zFReturn True for expected write denials that should not hit error logs.TF)r   PermissionErrorr/   errno_EXPECTED_WRITE_ERRNOS)r6   s    r   _is_expected_write_exceptionr;   v   sB    #'' t#w CI1G$G$Gt5r,   _file_ops_cache_read_trackerdefaulttask_idc                 x   ddl 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 rl|                    dd          |                    dd          |                    dd          |                    dd          |                    dg           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 )-a  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.
    r   )_active_environments	_env_lock_create_environment_get_env_config_last_activity_start_cleanup_thread_creation_locks_creation_locks_lockN)_task_env_overridesenv_typedockerdocker_imagesingularitysingularity_imagemodalmodal_imagedaytonadaytona_image cwdz*Creating new %s environment for task %s...   )rK   rM   rO   rQ   container_cpu   container_memoryi   container_diski   container_persistentTdocker_volumes)rV   rX   rY   rZ   r[   sshssh_hostssh_userssh_port   ssh_keyssh_persistentF)hostuserportkey
persistentlocalrg   local_persistenttimeouthost_cwd)	rJ   imagerT   rj   
ssh_configcontainer_configlocal_configr?   rk   z %s environment ready for task %s)tools.terminal_toolrA   rB   rC   rD   rE   rF   rG   rH   time_file_ops_lockr<   r   pop	threadingLockrI   loggerinfor   )r?   rA   rB   rC   rD   rE   rF   rG   rH   rq   cached	task_lockterminal_envrI   configrJ   	overridesrl   rT   rn   rm   ro   file_opss                          r   _get_file_opsr~      s9                       KKK 
 . . $$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,	- - - - - - - - - - - - - - -
 
 IS I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&F$ $  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RSIS IS IS IS IS IS IS IS IS IS IS IS IS IS ISX #<00H	 , ,#+ , , , , , , , , , , , , , , ,Os   AAAC CB/#C/B3	3C6B3	7CC
C
#DD	DO7'E9O7E			O7E		H<O7	N2&O72N6	6O79N6	:1O77O;>O;P//P36P3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)rr   r<   rs   clearr?   s    r   clear_file_ops_cacher     s    	 $ $ 	$....!!###	$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $   8AAArW     r%   offsetlimitc           	      Z   	 t          |           rt          j        dd|  di          S ddl}ddlm} |                    |                                                                           } |                                            }|dz  dz  d	z  |dz  dz  g}|D ]C}		 |	                    |	           t          j        dd
|  di          c S # t          $ r Y @w xY wt          |          }
|
||f}t          5  t                              |ddt                      i d          }|                    di                               |          }ddd           n# 1 swxY w Y   |Q	 t"          j                            |
          }||k    rt          j        d| ddd          S n# t(          $ r Y nw xY wt+          |          }|                    | ||          }|j        rt1          |j                  |_        |                                }t5          |j        pd          }|                    dd          }t7                      }||k    r=|                    dd          }t          j        d|dd|dd| d| ||dd          S |rA|t8          k    r6|dk    r0|                    d          r|                    dd |dd!           d"| ||f}t          5  d|vri |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ddd           n# 1 swxY w Y   |d(k    rt          j        d)| d*| |d+d          S |d,k    r	d-| d.|d/<   t          j        |d          S # t<          $ r/}t          j        dt          |          id          cY d}~S d}~ww xY w)0z-Read a file with pagination and line numbers.errorzCannot read 'zE': this is a device file that would block or produce infinite output.r   N)get_hermes_homeskillsz.hubzindex-cachezAccess denied: z is an internal Hermes cache file and cannot be read directly to prevent prompt injection. Use the skills_list or skill_view tools instead.)last_keyconsecutiveread_historydedupr   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.T)contentr%   r   Fensure_asciirS   	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      	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   rW   read_timestamps   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.)r   r%   already_read   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+   jsondumpspathlibhermes_constantsr   r   r&   resolverelative_tor0   str_read_tracker_lockr=   
setdefaultsetr   r$   r%   getmtimer/   r~   	read_filer   r   to_dictlenr   _LARGE_FILE_HINT_BYTESaddr   )r%   r   r   r?   _pathlib_get_hh	_resolved_hermes_home_blocked_dirs_blockedresolved_str	dedup_key	task_datacached_mtimecurrent_mtimer}   resultresult_dictcontent_lenr   	max_charsr   read_keycount
_mtime_nowes                             r   read_file_toolr     st   VA d## 	:8D 8 8 8    	#"""??????MM$''2244<<>>	wyy((**8#f,}<8#f,
 & 	 	H
%%h///zK$ K K K#          9~~!651	 	E 	E%00  #; ;  I %=="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:U !%!%' ' %*+ + + + 1     !))##D&%88> 	C26>BBFNnn&& &..B//OOK33	'))	""%//-CCK:?[N ? ?)2?? ? %0? ? ?
 *&
 
 "
# 
# 
# 
#  	)&<<<CKKOOK00  ""73y= 3 3 3   D&%0 	 	i''%'	'"n%))4*?@@@$00-(((A-(((((0	*%+,	-(m,EW--l;;
0:	'"9-LV	$$%6;;LII   )	 	 	 	 	 	 	 	 	 	 	 	 	 	 	. A:::BU B B B  %  "# # # # aZZc c c c 
# z+E:::: A A Az7CFF+%@@@@@@@@@As   (O1 A6O1 ".CO1 
C O1 C  O1 >AE O1  E$$O1 'E$(O1 .>F. -O1 .
F;8O1 :F;;CO1 AO1 %AN=AN N
NNNNO1 N!!O1 $N!%&O1 $O1 1
P*;$P%P*%P*c           	         t           5  t                              | i           }|                    dt                                }i }|D ]6\  }}}||vrg ||<   ||                             d| d||z   dz
              7d t          |                                          D             cddd           S # 1 swxY w Y   dS )zReturn a list of files read in this session for the given task.

    Used by context compression to preserve file-read history across
    compression boundaries.
    r   zlines -rW   c                     g | ]
\  }}||d S ))r%   regions ).0pr   s      r   
<listcomp>z*get_read_files_summary.<locals>.<listcomp>  s4     
 
 
7 7++
 
 
r,   N)r   r=   r   r   appendsorteditems)r?   r   r   
seen_pathsr%   r   r   s          r   get_read_files_summaryr     s2    
 
 
!%%gr22	 }}^SUU;;
%1 	L 	L!T65:%%#%
4 t##$JV$J$Jfunq6H$J$JKKKK
 
$Z%5%5%7%788
 
 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
s   B#B88B<?B<c                     t           5  | rt                              | d           nt                                           ddd           dS # 1 swxY w Y   dS )zClear the read tracker.

    Call with a task_id to clear just that task, or without to clear all.
    Should be called when a session is destroyed to prevent memory leaks
    in long-running gateway processes.
    N)r   r=   rs   r   r   s    r   clear_read_trackerr     s     
 " " 	"gt,,,,!!!	" " " " " " " " " " " " " " " " " "r   c                 4   t           5  | r;t                              |           }|rd|v r|d                                          n:t                                          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   N)r   r=   r   r   valuesr?   r   s     r   reset_file_dedupr     s     
 / / 	/%))'22I +W	11'"((****1133 / /	i''g&,,.../ / / / / / / / / / / / / / / / / /s   A8BBBc                     t           5  t                              |           }|r
d|d<   d|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   s     r   notify_other_tool_callr     s     
 ) )!%%g..	 	)$(Ij!'(Im$	) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )s   '<A A c                    	 t          t          |                                                                                     }t          j                            |          }n# t          t          f$ r Y dS w xY wt          5  t                              |          }|||                    di           |<   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.
    Nr   )r   r   r&   r   r$   r%   r   r/   r0   r   r=   r   r   )r"   r?   r3   r   r   s        r   _update_read_timestampr     s"   tH~~0022::<<==((22Z    	 R R!%%g..	 DQI  !2B77AR R R R R R R R R R R R R R R R R Rs$   AA" "A76A76C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   r&   r   r/   r0   r   r=   r   r$   r%   r   )r"   r?   r3   r   
read_mtimer   s         r   _check_file_stalenessr     s   tH~~0022::<<==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	

 4s<   A A AA"C)CC	CC2 2
D ?D r   c                    t          |           }|rt          j        d|id          S 	 t          | |          }t	          |          }|                    | |          }|                                }|r||d<   t          | |           t          j        |d          S # t          $ r}t          |          r/t                              dt          |          j        |           n0t                              dt          |          j        |d           t          j        dt          |          id          cY d	}~S d	}~ww xY w)
zWrite content to a file.r   Fr   r   z"write_file expected denial: %s: %szwrite_file error: %s: %sT)exc_infoN)r5   r   r   r   r~   
write_filer   r   r   r;   rv   debugtype__name__r   r   )	r%   r   r?   sensitive_errstale_warningr}   r   r   r   s	            r   write_file_toolr   +  s]   )$//M Hz7M2GGGGA-dG<< ))$$T733nn&& 	4&3K
# 	tW---z+E:::: A A A'** 	YLL=tAww?OQRSSSSLL3T!WW5EqSWLXXXz7CFF+%@@@@@@@@@As   A5B! !
E+BE=EE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          j        d|id          c S .	 g }|D ])}
t          |
|          }|r|                     |           *t          |          }| d	k    rK|st          j        dd
i          S ||t          j        ddi          S |                    ||||          }nM| dk    r.|st          j        ddi          S |                    |          }nt          j        dd|  i          S |                                }|r3t          |          dk    r|d         nd                    |          |d<   |                    d          s|D ]}
t#          |
|           t          j        |d          }|                    d          rdt%          |d                   v r|dz  }|S # t&          $ r/}t          j        dt%          |          id          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*(.+)$rW   r   Fr   r   zpath requiredz"old_string and new_string requiredzpatch content requiredzUnknown mode: z | r   zCould not findzp

[Hint: old_string not found. Use read_file to verify the current content, or search_files to locate the text.])r   refinditer	MULTILINEgroupstripr5   r   r   r   r~   patch_replace	patch_v4ar   r   joinr   r   r   r   )r   r%   r   r   r   r   r?   _paths_to_check_re_m_pr   stale_warnings_swr}   r   r   result_jsonr   s                      r   
patch_toolr   C  s"   
 O %t$$$w5,,QSXZ]Zghh 	8 	8B""288A;;#4#4#6#67777 L L-b11 	L:w6UKKKKKK	L&A! 	+ 	+B'G44C +%%c*** ))9 >z7O"<===!Z%7z7,P"QRRR++D*j+VVFFW__ Gz7,D"EFFF''..FF:w(?(?(?@AAAnn&& 	t;>~;N;NRS;S;SnQ&7&7Y^YcYcdrYsYsK
# w'' 	4% 4 4&r73333j5AAA ??7## 	P(8CG@T<U<U(U(U  P  PK A A Az7CFF+%@@@@@@@@@As8   0AI I %6I .I B;I 
J $I;5J ;J .2   patterntarget	file_globoutput_modecontextc	           
         	 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          j        dt          |          id          cY d}~S d}~ww xY w)zSearch for content or files.searchrS   Nr   )r   r   r   r   r   rW   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   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?   
search_keyr   r   r}   r   mr   r   next_offsetr   s                      r   search_toolr  |  s   
<A
 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 A Az7CFF+%@@@@@@@@@AsH   F AB<F BF B&F 7CF 
G$F?9G?Gr   )namefunctionr   search_filesc                      t           S )z&Get the list of file tool definitions.)
FILE_TOOLSr   r,   r   get_file_toolsr    s    r,   )registryc                  "    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,   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   r  r>   minimumz9Maximum number of lines to read (default: 500, max: 2000)i  )r   r  r>   maximum)r%   r   r   )r   
propertiesrequired)r  r  
parametersu   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)r%   r   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   enumr  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   r  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   r%   r   r   r   r   u  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%   rS   r   rW   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}t          |                      dd          |                      dd          |          S )Nr?   r>   r%   rS   r   )r%   r   r?   )r   r   r'  s      r   _handle_write_filer-    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   r%   r   r   r   Fr   )r   r%   r   r   r   r   r?   )r   r   r'  s      r   _handle_patchr/  "  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>   r   r$  )grepfindr   r   rS   r%   r   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_filesr5  *  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emojiu   ✍️u   🔧u   🔎)r>   )N)rW   r   r>   )r   NNNFNr>   )r   r   Nr   r   r   r   r>   )D__doc__r9   r   loggingr$   rt   r   r   tools.file_operationsr   agent.redactr   	getLoggerr   rv   EACCESEPERMEROFSr:   r   r   r   __annotations__r   r   	frozensetr'   r   boolr+   r1   r2   r5   r   r;   ru   rr   r<   dictr   r=   r~   r   r   listr   r   r   r   r   r   r   r   r  r  r  tools.registryr  r  READ_FILE_SCHEMAWRITE_FILE_SCHEMAPATCH_SCHEMASEARCH_FILES_SCHEMAr+  r-  r/  r5  registerr   r,   r   <module>rO     s   < < <    				           5 5 5 5 5 5 . . . . . .		8	$	$  ,U[A  " %) d
 ) ) )"S " " " "0 !  "	 	# 	# 	# 	 	      * D 02DE C C$J    (i D      !!   " $Y^%% t   v v3 v/B v v v vr$ $# $ $ $ $XA XA XAc XAc XA# XA^a XA XA XA XAv
 
C 
 
 
 
 
(" " " " " "/ /c / / / /,) )C ) ) ) ) RS R3 R4 R R R R$C # #*    >A A# A Ac A# A A A A0 KOOS'6A 6AS 6AC 6AC 6A6A486AIL6A6A,/6A 6A 6A 6Ar DGFG=>(AA AA AAc AAS AAAA.1AA@CAA AA7:AA AA .1AA AA AA AAJ n5577*--55	
   $ # # # # #% % %  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  Q  Q  Q  Q  |V<MWi  uE  MU  V  V  V  V  w|]eu  ~D  E  E  E  E  ~v>Q[o  {K  SY  Z  Z  Z  Z  Z  Zr,   