
    iL              	          d Z ddlZddlZddlZddlmZmZ ddlmZm	Z	 ddl
mZmZmZmZ ddlmZ ddlmZ ddlmZmZmZmZ  e ej                              Z ee          Z ee          Zd	ee         fd
Zded	e fdZ!e G d d                      Z"e G d d                      Z#e G d d                      Z$e G d d                      Z%e G d d                      Z&e G d d                      Z'e G d d                      Z( G d de          Z)h dZ*ddd d!d"d#Z+d$Z,d$Z-d%Z.d&Z/d'Z0dZ1d(Z2d)ed*e3d	e3fd+Z4e/e0fd,ed-ed	e5e3e3f         fd.Z6e1e2fd,ed-ed	e5e3e3f         fd/Z7 G d0 d1e)          Z8dS )2aL  
File Operations Module

Provides file manipulation capabilities (read, write, patch, search) that work
across all terminal backends (local, docker, singularity, ssh, modal, daytona).

The key insight is that all file operations can be expressed as shell commands,
so we wrap the terminal backend's execute() interface to provide a unified file API.

Usage:
    from tools.file_operations import ShellFileOperations
    from tools.terminal_tool import _active_environments
    
    # Get file operations for a terminal environment
    file_ops = ShellFileOperations(terminal_env)
    
    # Read a file
    result = file_ops.read_file("/path/to/file.py")
    
    # Write a file
    result = file_ops.write_file("/path/to/new.py", "print('hello')")
    
    # Search for content
    result = file_ops.search("TODO", path=".", file_glob="*.py")
    N)ABCabstractmethod)	dataclassfield)OptionalListDictAny)Path)BINARY_EXTENSIONS)build_write_denied_pathsbuild_write_denied_prefixesget_safe_write_rootis_write_deniedreturnc                      t                      S )a\  Return the resolved HERMES_WRITE_SAFE_ROOT path, or None if unset.

    When set, all write_file/patch operations are constrained to this
    directory tree.  Writes outside it are denied even if the target is
    not on the static deny list.  Opt-in hardening for gateway/messaging
    deployments that should only touch a workspace checkout.
    )_shared_get_safe_write_root     :/home/ubuntu/.hermes/hermes-agent/tools/file_operations.py_get_safe_write_rootr   8   s     '(((r   pathc                      t          |           S )z.Return True if path is on the write deny list.)_shared_is_write_denied)r   s    r   _is_write_deniedr   C   s    "4(((r   c                   $   e Zd ZU dZdZeed<   dZeed<   dZ	eed<   dZ
eed<   d	Zee         ed
<   dZeed<   dZeed<   d	Zee         ed<   d	Zee         ed<   d	Zee         ed<   d	Zee         ed<    ee          Zee         ed<   defdZd	S )
ReadResultzResult from reading a file. contentr   total_lines	file_sizeF	truncatedNhint	is_binaryis_imagebase64_content	mime_type
dimensionserrordefault_factorysimilar_filesr   c                 H    d | j                                         D             S )Nc                 *    i | ]\  }}||g k    ||S Nr   .0kvs      r   
<dictcomp>z&ReadResult.to_dict.<locals>.<dictcomp>]   s(    TTTA!-AQSGG1GGGr   __dict__itemsselfs    r   to_dictzReadResult.to_dict\   s$    TT!4!4!6!6TTTTr   )__name__
__module____qualname____doc__r   str__annotations__r    intr!   r"   boolr#   r   r$   r%   r&   r'   r(   r)   r   listr,   r   dictr:   r   r   r   r   r   L   s#        %%GSKIsItD(3-ItHd$(NHSM(((#Ix}### $J$$$E8C=$uT:::M49:::U U U U U U Ur   r   c                   p    e Zd ZU dZdZeed<   dZeed<   dZ	e
e         ed<   dZe
e         ed<   d	efd
ZdS )WriteResultzResult from writing a file.r   bytes_writtenFdirs_createdNr)   warningr   c                 H    d | j                                         D             S )Nc                     i | ]
\  }}|||S r/   r   r0   s      r   r4   z'WriteResult.to_dict.<locals>.<dictcomp>i   s    HHHA!-1---r   r5   r8   s    r   r:   zWriteResult.to_dicth   s$    HH!4!4!6!6HHHHr   )r;   r<   r=   r>   rG   rA   r@   rH   rB   r)   r   r?   rI   rD   r:   r   r   r   rF   rF   `   s         %%M3L$E8C=!GXc]!!!I I I I I I Ir   rF   c                   
   e Zd ZU dZdZeed<   dZeed<    e	e
          Zee         ed<    e	e
          Zee         ed<    e	e
          Zee         ed	<   d
Zeeeef                  ed<   d
Zee         ed<   defdZd
S )PatchResultzResult from patching a file.Fsuccessr   diffr*   files_modifiedfiles_createdfiles_deletedNlintr)   r   c                     d| j         i}| j        r
| j        |d<   | j        r
| j        |d<   | j        r
| j        |d<   | j        r
| j        |d<   | j        r
| j        |d<   | j        r
| j        |d<   |S )NrN   rO   rP   rQ   rR   rS   r)   )rN   rO   rP   rQ   rR   rS   r)   r9   results     r   r:   zPatchResult.to_dictw   s    T\*9 	'!YF6N 	;'+':F#$ 	9&*&8F?# 	9&*&8F?#9 	'!YF6N: 	)"jF7Or   )r;   r<   r=   r>   rN   rB   r@   rO   r?   r   rC   rP   r   rQ   rR   rS   r   r	   r
   r)   rD   r:   r   r   r   rM   rM   l   s         &&GTD#NNN %d ; ; ;NDI;;;$uT:::M49:::$uT:::M49:::%)D(4S>
")))E8C=      r   rM   c                   @    e Zd ZU dZeed<   eed<   eed<   dZeed<   dS )SearchMatchzA single search match.r   line_numberr   g        mtimeN)	r;   r<   r=   r>   r?   r@   rA   rZ   floatr   r   r   rX   rX      sF           
IIILLLE5r   rX   c                       e Zd ZU dZ ee          Zee         e	d<    ee          Z
ee         e	d<    ee          Zeeef         e	d<   dZee	d<   dZee	d	<   d
Zee         e	d<   defdZd
S )SearchResultzResult from searching.r*   matchesfilescountsr   total_countFr"   Nr)   r   c                     d| j         i}| j        rd | j        D             |d<   | j        r
| j        |d<   | j        r
| j        |d<   | j        rd|d<   | j        r
| j        |d<   |S )	Nra   c                 8    g | ]}|j         |j        |j        d S ))r   liner   r   rY   r   )r1   ms     r   
<listcomp>z(SearchResult.to_dict.<locals>.<listcomp>   s8     ! ! ! 19MM! ! !r   r^   r_   r`   Tr"   r)   )ra   r^   r_   r`   r"   r)   rU   s     r   r:   zSearchResult.to_dict   s    !12< 	! !! ! !F9 : 	)"jF7O; 	+#{F8> 	'"&F;: 	)"jF7Or   )r;   r<   r=   r>   r   rC   r^   r   rX   r@   r_   r?   rD   r`   r	   rA   ra   r"   rB   r)   r   r:   r   r   r   r]   r]      s           !&t!<!<!<GT+<<<uT222E49222"U4888FDcN888KItE8C=      r   r]   c                   X    e Zd ZU dZdZeed<   dZeed<   dZe	ed<   dZ
e	ed<   d	efd
ZdS )
LintResultzResult from linting a file.TrN   Fskippedr   outputmessager   c                 J    | j         r
d| j        dS | j        rdnd| j        dS )Nrj   )statusrl   okr)   )rn   rk   )rj   rl   rN   rk   r8   s    r   r:   zLintResult.to_dict   s?    < 	B'DLAAA"l7ddk
 
 	
r   N)r;   r<   r=   r>   rN   rB   r@   rj   rk   r?   rl   rD   r:   r   r   r   ri   ri      sw         %%GTGTFCGS
 
 
 
 
 
 
r   ri   c                   0    e Zd ZU dZdZeed<   dZeed<   dS )ExecuteResultz&Result from executing a shell command.r   stdoutr   	exit_codeN)	r;   r<   r=   r>   rr   r?   r@   rs   rA   r   r   r   rq   rq      s8         00FCIsr   rq   c                   d   e Zd ZdZed!dedededefd            Zededefd	            Z	eded
ede
fd            Ze	 d"dededededef
d            Zededefd            Zedede
fd            Zededede
fd            Ze	 	 	 d#dedededee         dededededefd             ZdS )$FileOperationsz@Abstract interface for file operations across terminal backends.     r   offsetlimitr   c                     dS )z$Read a file with pagination support.Nr   )r9   r   rx   ry   s       r   	read_filezFileOperations.read_file   	     	r   c                     dS )a
  Read the complete file content as a plain string.

        No pagination, no line-number prefixes, no per-line truncation.
        Returns ReadResult with .content = full file text, .error set on
        failure. Always reads to EOF regardless of file size.
        Nr   r9   r   s     r   read_file_rawzFileOperations.read_file_raw   s	     	r   r   c                     dS )z8Write content to a file, creating directories as needed.Nr   )r9   r   r   s      r   
write_filezFileOperations.write_file   r|   r   F
old_string
new_stringreplace_allc                     dS )z,Replace text in a file using fuzzy matching.Nr   )r9   r   r   r   r   s        r   patch_replacezFileOperations.patch_replace   s	     	r   patch_contentc                     dS )zApply a V4A format patch.Nr   )r9   r   s     r   	patch_v4azFileOperations.patch_v4a   r|   r   c                     dS )z>Delete a file. Returns WriteResult with .error set on failure.Nr   r~   s     r   delete_filezFileOperations.delete_file   r|   r   srcdstc                     dS )zSMove/rename a file from src to dst. Returns WriteResult with .error set on failure.Nr   )r9   r   r   s      r   	move_filezFileOperations.move_file   r|   r   .N2   r   patterntarget	file_globoutput_modecontextc	                     dS )zSearch for content or files.Nr   )	r9   r   r   r   r   ry   rx   r   r   s	            r   searchzFileOperations.search   s	    
 	r   rv   rw   Fr   r   Nr   r   r   r   )r;   r<   r=   r>   r   r?   rA   r   r{   r   rF   r   rB   rM   r   r   r   r   r   r]   r   r   r   r   ru   ru      s       JJ c 3 3     ^ # *    ^ s S [    ^ */ # 3 C #'4?   ^
 s {    ^      ^ S s {    ^ BKOP<= c  C "3-7:IL69BN   ^  r   ru   >   .bmp.gif.ico.jpg.png.jpeg.webpz python -m py_compile {file} 2>&1znode --check {file} 2>&1znpx tsc --noEmit {file} 2>&1zgo vet {file} 2>&1zrustfmt --check {file} 2>&1)z.pyz.jsz.tsz.goz.rsi  i   rv   rw   r   valuedefaultc                 T    	 t          |           S # t          t          f$ r |cY S w xY w)z8Best-effort integer coercion for tool pagination inputs.)rA   	TypeError
ValueError)r   r   s     r   _coerce_intr     s<    5zzz"   s    ''rx   ry   c                     ddl m}  |            }t          dt          | t                              }t          |t
                    }t          dt          ||                    }||fS )a  Return safe read_file pagination bounds.

    Tool schemas declare minimum/maximum values, but not every caller or
    provider enforces schemas before dispatch. Clamp here so invalid values
    cannot leak into sed ranges like ``0,-1p``.

    The upper bound on ``limit`` comes from ``tool_output.max_lines`` in
    config.yaml (defaults to the module-level ``MAX_LINES`` constant).
    r   )get_max_linesrv   )tools.tool_output_limitsr   maxr   DEFAULT_READ_OFFSETDEFAULT_READ_LIMITmin)rx   ry   r   	max_linesnormalized_offsetnormalized_limits         r   normalize_read_paginationr     st     766666IA{63FGGHH"5*<==1c"2I>>??...r   c                     t          dt          | t                              }t          dt          |t                              }||fS )zCReturn safe search pagination bounds for shell head/tail pipelines.r   rv   )r   r   DEFAULT_SEARCH_OFFSETDEFAULT_SEARCH_LIMIT)rx   ry   r   r   s       r   normalize_search_paginationr   2  sF     A{63HIIJJ1k%1EFFGG...r   c                      e Zd ZdZd<defdZ	 	 d=dededededef
d	Zd
ede	fdZ
d<dedede	fdZdede	fdZd>dededefdZdedefdZdedefdZdedededefdZd?dedededefdZdedefdZdedefd Zdedefd!Zd"ed#edefd$Zdededefd%Z	 d@ded'ed(ed)e	def
d*Zd+edefd,Zdedefd-Z	 	 	 dAd1eded2ed3ee         deded4ed5edefd6Z d1ededededef
d7Z!d1ededededef
d8Z"d1eded3ee         deded4ed5edefd9Z#d1eded3ee         deded4ed5edefd:Z$d1eded3ee         deded4ed5edefd;Z%dS )BShellFileOperationsz
    File operations implemented via shell commands.
    
    Works with ANY terminal backend that has execute(command, cwd) method.
    This includes local, docker, singularity, ssh, modal, and daytona environments.
    Ncwdc                     || _         |p2t          |dd          p!t          t          |dd          dd          pd| _        i | _        dS )u  
        Initialize file operations with a terminal environment.

        Args:
            terminal_env: Any object with execute(command, cwd) method.
                         Returns {"output": str, "returncode": int}
            cwd: Optional explicit fallback cwd when the terminal env has
                 no cwd attribute (rare — most backends track cwd live).

        Note:
            Every _exec() call prefers the LIVE ``terminal_env.cwd`` over
            ``self.cwd`` so ``cd`` commands run via the terminal tool are
            picked up immediately.  ``self.cwd`` is only used as a fallback
            when the env has no cwd at all — it is NOT the authoritative
            cwd, despite being settable at init time.

            Historical bug (fixed): prior versions of this class used the
            init-time cwd for every _exec() call, which caused relative
            paths passed to patch/read/write to target the wrong directory
            after the user ran ``cd`` in the terminal.  Patches would
            claim success and return a plausible diff but land in the
            original directory, producing apparent silent failures.
        r   Nconfig/)envgetattrr   _command_cache)r9   terminal_envr   s      r   __init__zShellFileOperations.__init__B  se    0  
  V',t<< V7<4@@%NNVRU 	 02r   commandtimeout
stdin_datar   c                     i }|r||d<   |||d<   |pt          | j        dd          p| j        } | j        j        |fd|i|}t	          |                    dd          |                    dd          	          S )
u  Execute command via terminal backend.

        Args:
            stdin_data: If provided, piped to the process's stdin instead of
                        embedding in the command string. Bypasses ARG_MAX.

        Cwd resolution order (critical — see class docstring):
          1. Explicit ``cwd`` arg (if provided)
          2. Live ``self.env.cwd`` (tracks ``cd`` commands run via terminal)
          3. Init-time ``self.cwd`` (fallback when env has no cwd attribute)

        This ordering ensures relative paths in file operations follow the
        terminal's current directory — not the directory this file_ops was
        originally created in.  See test_file_ops_cwd_tracking.py.
        r   Nr   r   rk   r   
returncoder   )rr   rs   )r   r   r   executerq   get)r9   r   r   r   r   kwargseffective_cwdrV   s           r   _execzShellFileOperations._exece  s    "  	( 'F9!#-F<  Iwtx==I!!'GG}GGG::h++jjq11
 
 
 	
r   cmdc                     || j         vr>|                     d| d          }|j                                        dk    | j         |<   | j         |         S )z6Check if a command exists in the environment (cached).zcommand -v z >/dev/null 2>&1 && echo 'yes'yes)r   r   rr   strip)r9   r   rV   s      r   _has_commandz ShellFileOperations._has_command  s[    d)))ZZ Qc Q Q QRRF'-}':':'<'<'ED$"3''r   r   content_samplec                    t           j                            |          d                                         }|t          v rdS |rEt          d |dd         D                       }|t          t          |          d          z  dk    S dS )z
        Check if a file is likely binary.
        
        Uses extension check (fast) + content analysis (fallback).
        rv   Tc              3   H   K   | ]}t          |          d k     |dvdV  dS )    z
	rv   N)ord)r1   cs     r   	<genexpr>z8ShellFileOperations._is_likely_binary.<locals>.<genexpr>  sJ        E  Ea"%a&&2++!82C2C !"2C2C2C2C E  Er   Ni  g333333?F)osr   splitextlowerr   sumr   len)r9   r   r   extnon_printables        r   _is_likely_binaryz%ShellFileOperations._is_likely_binary  s     gt$$Q'--//###4  	I  E  E>%4%+@  E  E  E E EM 3s>':':D#A#AADHHur   c                     t           j                            |          d                                         }|t          v S )z2Check if file is an image we can return as base64.rv   )r   r   r   r   IMAGE_EXTENSIONS)r9   r   r   s      r   	_is_imagezShellFileOperations._is_image  s4    gt$$Q'--//&&&r   rv   r   
start_linec                    ddl m}  |            }|                    d          }g }t          ||          D ]@\  }}t	          |          |k    r|d|         dz   }|                    |dd|            Ad                    |          S )	z7Add line numbers to content in LINE_NUM|CONTENT format.r   )get_max_line_length
)startNz... [truncated]6d|)r   r   split	enumerater   appendjoin)	r9   r   r   r   max_line_lengthlinesnumberedird   s	            r   _add_line_numbersz%ShellFileOperations._add_line_numbers  s    @@@@@@--//d## j999 	. 	.GAt4yy?**,_,-0AAOOq,,,d,,----yy"""r   c                    |s|S |                     d          r9|                     d          }|j        dk    r|j                                        r|j                                        }|dk    r|S |                     d          r||dd         z   S |dd         }|                    d          }|dk    r
|d|         n|}|rt          j        d|          rt|                     d	|           }|j        dk    rQ|j                                        r8|j                                        }|dt          |          z   d         }	||	z   S |S )
z
        Expand shell-style paths like ~ and ~user to absolute paths.
        
        This must be done BEFORE shell escaping, since ~ doesn't expand
        inside single quotes.
        ~z
echo $HOMEr   z~/rv   Nr   z[a-zA-Z0-9._-]+zecho ~)	
startswithr   rs   rr   r   findre	fullmatchr   )
r9   r   rV   homerest	slash_idxusernameexpand_result	user_homesuffixs
             r   _expand_pathz ShellFileOperations._expand_path  sr     	K ??3 	2ZZ--F1$$)<)<)>)>$}**,,3;;K__T** +$qrr(?* ABBx IIcNN	/8A~~4

++4 2-? J J 2 %)JJ/B/B/B$C$CM$.!338L8R8R8T8T3$1$8$>$>$@$@	!%a#h--&7&8&8!9(611r   argc                 :    d|                     dd          z   dz   S )z/Escape a string for safe use in shell commands.'z'"'"')replace)r9   r  s     r   _escape_shell_argz%ShellFileOperations._escape_shell_arg  s"     S[[i000366r   old_contentnew_contentfilenamec                     |                     d          }|                     d          }t          j        ||d| d|           }d                    |          S )z2Generate unified diff between old and new content.T)keependsza/zb/)fromfiletofiler   )
splitlinesdifflibunified_diffr   )r9   r  r  r	  	old_lines	new_linesrO   s          r   _unified_diffz!ShellFileOperations._unified_diff  sn    **D*99	**D*99	#y$(__"??
 
 

 wwt}}r   rw   rx   ry   c           	         |                      |          }t          ||          \  }}d|                     |           d}|                     |          }|j        dk    r|                     |          S 	 t          |j                                                  }n# t          $ r d}Y nw xY w|t          k    r	 |                     |          rt          dd|d          S d|                     |           d}|                     |          }|                     ||j                  rt          d|d	          S ||z   d
z
  }	d| d|	 d|                     |           }
|                     |
          }|j        dk    rt          d|j                   S d|                     |           }|                     |          }	 t          |j                                                  }n# t          $ r d}Y nw xY w||	k    }d}|rd|	d
z    d| d|	 d| d	}t          |                     |j        |          ||||          S )a  
        Read a file with pagination, binary detection, and line numbers.
        
        Args:
            path: File path (absolute or relative to cwd)
            offset: Line number to start from (1-indexed, default 1)
            limit: Maximum lines to return (default 500, max 2000)
        
        Returns:
            ReadResult with content, metadata, or error info
        wc -c <  2>/dev/nullr   TzImage file detected. Automatically redirected to vision_analyze tool. Use vision_analyze with this file path to inspect the image contents.)r%   r$   r!   r#   head -c 1000 zUBinary file - cannot display as text. Use appropriate tools to handle this file type.r$   r!   r)   rv   zsed -n ',zp' Failed to read file: r)   zwc -l < NzUse offset=z to continue reading (showing -z of z lines))r   r    r!   r"   r#   )r  r   r  r   rs   _suggest_similar_filesrA   rr   r   r   MAX_FILE_SIZEr   r   r   r   )r9   r   rx   ry   stat_cmdstat_resultr!   
sample_cmdsample_resultend_lineread_cmdread_resultwc_cmd	wc_resultr    r"   r#   s                    r   r{   zShellFileOperations.read_file  s      &&1&%@@ Id44T::HHHjj** A%%..t444	K.446677II 	 	 	III	 }$$ >>$ 		#\    PT%;%;D%A%AOOO


:..!!$(<== 	#m    E>A%RfRRxRRD4J4J44P4PRRjj** A%%$PK<N$P$PQQQQ ;D22488::JJv&&		i.446677KK 	 	 	KKK	  (*	 	yxAxxVxxV^xxdoxxxD**;+=vFF#
 
 
 	
s$   8&B B.-B.&G3 3HHc                    t           j                            |          pd}t           j                            |          }t           j                            |          d         }t           j                            |          d                                         }|                                }d|                     |           d}|                     |          }g }	|j        dk    r|j	        
                                r|j	        
                                                    d          D ]}
|
s|
                                }d}||k    rd}n:t           j                            |
          d                                         |                                k    rd}n|                    |          s|                    |          rd	}n||v rd
}n||v rt          |          dk    rd}n|rt           j                            |
          d                                         |k    r_t          |          t          |          z  }t          |          t          t          |          t          |                    dz  k    rd}|dk    r5|	                    |t           j                            ||
          f           |	                    d            d |	dd         D             }t'          d| |          S )z;Suggest similar files when the requested file is not found.r   r   rv   ls -1 z 2>/dev/null | head -50r   d   Z   F   <      (   g?   c                     | d          S )Nr   r   )xs    r   <lambda>z<ShellFileOperations._suggest_similar_files.<locals>.<lambda>m  s    1Q4% r   )keyc                     g | ]\  }}|S r   r   )r1   _fps      r   rg   z>ShellFileOperations._suggest_similar_files.<locals>.<listcomp>n  s    ...%!R2...r   N   zFile not found: )r)   r,   )r   r   dirnamebasenamer   r   r  r   rs   rr   r   r   r   r   setr   r   r   sortr   )r9   r   dir_pathr	  basename_no_extr   
lower_namels_cmd	ls_resultscoredflfscorecommonsimilars                  r   r  z*ShellFileOperations._suggest_similar_filesA  s   7??4((/C7##D))'**844Q7gx((+1133^^%%
 T$00::SSSJJv&&	!##	(8(>(>(@(@#%++--33D99 F F WWYY ##EEW%%a((+11337L7L7N7NNNEE]]:.. #*2G2G2K2K #EE2%%EE:%%#b''A++EE #RW--a00399;;sBB __s2ww6F6{{c#j//3r77&C&Cc&III "199MM5"',,x*C*C"DEEE(((..6"1":...+T++!
 
 
 	
r   c                    |                      |          }d|                     |           d}|                     |          }|j        dk    r|                     |          S 	 t          |j                                                  }n# t          $ r d}Y nw xY w| 	                    |          rt          dd|          S |                     d|                     |           d          }|                     ||j                  rt          d|d          S |                     d	|                     |                     }|j        dk    rt          d
|j                   S t          |j        |          S )zRead the complete file content as a plain string.

        No pagination, no line-number prefixes, no per-line truncation.
        Uses cat so the full file is returned regardless of size.
        r  r  r   T)r%   r$   r!   r  u'   Binary file — cannot display as text.r  cat r  r  )r   r!   )r  r  r   rs   r  rA   rr   r   r   r   r   r   )r9   r   r  r   r!   r"  
cat_results          r   r   z!ShellFileOperations.read_file_rawu  s      &&Hd44T::HHHjj** A%%..t444	K.446677II 	 	 	III	>>$ 	RttyQQQQ

#]43I3I$3O3O#]#]#]^^!!$(<== 	)?    ZZ Et'='=d'C'C E EFF
1$$$OJ<M$O$OPPPP*"3yIIIIs   %&B BBc                 0   |                      |          }t          |          rt          d| d          S |                     d|                     |                     }|j        dk    rt          d| d|j                   S t                      S )zDelete a file via rm.zDelete denied:  is a protected pathr  zrm -f r   zFailed to delete : r  r   rF   r   r  rs   rr   )r9   r   rV   s      r   r   zShellFileOperations.delete_file  s      &&D!! 	S%Qt%Q%Q%QRRRRCT%;%;D%A%ACCDDq  %P%P%P%P%PQQQQ}}r   r   r   c                    |                      |          }|                      |          }||fD ]'}t          |          rt          d| d          c S (|                     d|                     |           d|                     |                     }|j        dk    rt          d| d| d	|j                   S t                      S )
zMove a file via mv.zMove denied: rL  r  zmv  r   zFailed to move z -> rM  rN  )r9   r   r   prV   s        r   r   zShellFileOperations.move_file  s    $$$$s 	R 	RA"" R")P)P)P)PQQQQQQRM$((--MM0F0Fs0K0KMM
 
 q  %Vs%V%V%V%Vv}%V%VWWWW}}r   c                    |                      |          }t          |          rt          d| d          S t          j                            |          }d}|r:d|                     |           }|                     |          }|j        dk    rd}d|                     |           }|                     ||	          }|j        dk    rt          d
|j	                   S d|                     |           d}	|                     |	          }
	 t          |
j	                                                  }n2# t          $ r% t          |                    d                    }Y nw xY wt          ||          S )u  
        Write content to a file, creating parent directories as needed.

        Pipes content through stdin to avoid OS ARG_MAX limits on large
        files. The content never appears in the shell command string —
        only the file path does.

        Args:
            path: File path to write
            content: Content to write

        Returns:
            WriteResult with bytes written or error
        Write denied: '(' is a protected system/credential file.r  Fz	mkdir -p r   Tzcat > )r   zFailed to write file: r  r  zutf-8)rG   rH   )r  r   rF   r   r   r9  r  r   rs   rr   rA   r   r   r   encode)r9   r   r   parentrH   	mkdir_cmdmkdir_result	write_cmdwrite_resultr  r   rG   s               r   r   zShellFileOperations.write_file  s       && D!! 	g%et%e%e%effff && 	$DD$:$:6$B$BDDI::i00L%**# <T33D99;;	zz)z@@!Q&&%Sl>Q%S%STTTT Id44T::HHHjj**	9 2 8 8 : :;;MM 	9 	9 	9w 7 788MMM	9 '%
 
 
 	
s   &D> >,E-,E-Fr   r   r   c           
         |                      |          }t          |          rt          d| d          S d|                     |           d}|                     |          }|j        dk    rt          d|           S |j        }ddlm}  |||||          \  }	}
}}|s|
dk    r@|pd	| }	 dd
lm	} | |||
||          z  }n# t          $ r Y nw xY wt          |          S |                     ||	          }|j        rt          d|j                   S d|                     |           d}|                     |          }|j        dk    rt          d|           S |j        |	k    r9t          d| dt          |	           dt          |j                   d          S |                     ||	|          }|                     |          }t          d||g|r|                                nd          S )ai  
        Replace text in a file using fuzzy matching.

        Args:
            path: File path to modify
            old_string: Text to find (must be unique unless replace_all=True)
            new_string: Replacement text
            replace_all: If True, replace all occurrences

        Returns:
            PatchResult with diff and lint results
        rS  rT  r  rI  r  r   r  )fuzzy_find_and_replacez'Could not find match for old_string in )format_no_match_hintzFailed to write changes: z2Post-write verification failed: could not re-read z#Post-write verification failed for z5: on-disk content differs from intended write (wrote z chars, read back z=). The patch did not persist. Re-read the file and try again.TN)rN   rO   rP   rS   )r  r   rM   r  r   rs   rr   tools.fuzzy_matchr\  r]  	Exceptionr   r)   r   r  _check_lintr:   )r9   r   r   r   r   r$  r%  r   r\  r  match_count	_strategyr)   err_msgr]  rZ  
verify_cmdverify_resultrO   lint_results                       r   r   z!ShellFileOperations.patch_replace  s      && D!! 	g%et%e%e%effff E$0066DDDjj** A%%%CT%C%CDDDD$ 	=<<<<<5K5KZ[6
 6
2[)U  	.K1$$OOOOGBBBBBB//jRYZZZ   W----t[99 	W%UAS%U%UVVVV GD22488FFF


:.."a''%`Z^%`%`aaaa;..Md M Mk**M M>A-BV>W>WM M M    !!';== &&t,, 6*5?$$&&&4	
 
 
 	
s   5C 
CCr   c                 t    ddl m}m}  ||          \  }}|rt          d|           S  |||           }|S )a  
        Apply a V4A format patch.
        
        V4A format:
            *** Begin Patch
            *** Update File: path/to/file.py
            @@ context hint @@
             context line
            -removed line
            +added line
            *** End Patch
        
        Args:
            patch_content: V4A format patch string
        
        Returns:
            PatchResult with changes made
        r   )parse_v4a_patchapply_v4a_operationszFailed to parse patch: r  )tools.patch_parserrh  ri  rM   )r9   r   rh  ri  
operationsparse_errorrV   s          r   r   zShellFileOperations.patch_v4a7  sm    ( 	MLLLLLLL"1/-"@"@
K 	N%L{%L%LMMMM &%j$77r   c                 d   t           j                            |          d                                         }|t          vrt          dd| d          S t          |         }|                                d         }|                     |          st          d| d          S |                    d| 	                    |                    }| 
                    |d	
          }t          |j        dk    |j                                        r|j                                        nd          S )z
        Run syntax check on a file after editing.
        
        Args:
            path: File path to lint
        
        Returns:
            LintResult with status and any errors
        rv   TzNo linter for z files)rj   rl   r   z not availablez{file}r0  r   r   )rN   rk   )r   r   r   r   LINTERSri   r   r   r  r  r   rs   rr   r   )r9   r   r   
linter_cmdbase_cmdr   rV   s          r   r`  zShellFileOperations._check_lintU  s-    gt$$Q'--//gd4PS4P4P4PQQQQ S\
##%%a(  ** 	Qdx4O4O4OPPPP   4+A+A$+G+GHHC,,$),2M,?,?,A,AI6=&&(((r
 
 
 	
r   r   r   r   r   r   r   r   r   c	           	         t          ||          \  }}|                     |          }|                     d|                     |           d          }	d|	j        v rt
          j                            |          pd}
t
          j                            |          }d| g}|                     d|                     |
           d          }d|j        v r=|r:|                     d	|                     |
           d
          }|j	        dk    r|j        
                                r|                                }g }|j        
                                                    d          D ]q}|s|                                }||v s!||v s|                    |dd                   r3|                    t
          j                            |
|                     r|r3|                    dd                    |dd                   z              t!          d                    |          d          S |dk    r|                     ||||          S |                     |||||||          S )a\  
        Search for content or files.
        
        Args:
            pattern: Regex (for content) or glob pattern (for files)
            path: Directory/file to search (default: cwd)
            target: "content" (grep) or "files" (glob)
            file_glob: File pattern filter for content search (e.g., "*.py")
            limit: Max results (default 50)
            offset: Skip first N results
            output_mode: "content", "files_only", or "count"
            context: Lines of context around matches
        
        Returns:
            SearchResult with matches or file list
        ztest -e z! && echo exists || echo not_found	not_foundr   zPath not found: ztest -d z && echo yes || echo nor   r)  z 2>/dev/null | head -20r   r   N   zSimilar paths: z, r8  z. r)   ra   r_   )r   r  r   r  rr   r   r   r9  r:  rs   r   r   r   r   r   r   r]   _search_files_search_content)r9   r   r   r   r   ry   rx   r   r   checkrV  basename_query
hint_partsparent_checkrA  lower_q
candidatesentryles                      r   r   zShellFileOperations.searchy  s   & 4FEBB   && 

ed&<&<T&B&Beeeff%,&&W__T**1cFW--d33N3T334J::R411&99RRR L ++++ JJTT33F;;TTT 	 &!++	0@0F0F0H0H+,2244G!#J!*!1!7!7!9!9!?!?!E!E K K$ %$"[[]]"b==B'MMR]]7SUTUSU;=W=WM&--bgll65.I.IJJJ! "))-		*RaR.0I0II    ii
++   
 W%%gtUFCCC''y%(3W> > >r   c                 H   |                     d          sd|vr|}n|                    d          d         }|                     d          r|                     ||||          S |                     d          st	          d          S d}d	|                     |           d
| d|                     |           d|dz    d| 
}|                     |d          }|j                                        sTd	|                     |           d
| d|                     |           d||z    d|dz    
}	|                     |	d          }g }
|j                                                            d          D ]}|s|                    d
d          }t          |          dk    rJ|d         
                    dd                                          r|
                    |d                    x|
                    |           t	          |
t          |
                    S )z-Search for files by name pattern (glob-like).z**/r   rgr   zFile search requires 'rg' (ripgrep) or 'find'. Install ripgrep for best results: https://github.com/BurntSushi/ripgrep#installationr  z-not -path '*/.*'zfind rP  z -type f -name z6 -printf '%T@ %p\n' 2>/dev/null | sort -rn | tail -n +rv   z | head -n r-  rn   2>/dev/null | head -n z | tail -n +r   r.  r   r   r   r_   ra   )r   r   r   _search_files_rgr]   r  r   rr   r   r   r  isdigitr   )r9   r   r   ry   rx   search_patternhidden_excluder   rV   
cmd_simpler_   rd   partss                r   rv  z!ShellFileOperations._search_files  s    !!%(( 	4S-?-?$NN$]]3//3N
 T"" 	N((ufMMM   (( 	K    -fd,,T22 f f^ f fTXTjTjkyTzTz f fGMPQzf f^cf f C,,}""$$ 	8Z!7!7!=!= Z Z Z Z_c_u_u  wE  `F  `F Z Z16Z ZMSVWZZ ZJZZ
BZ77FM''))//55 	# 	#D JJsA&&E5zzQ58#3#3C#<#<#D#D#F#FU1X&&&&T""""E


 
 
 	
r   c                    d|vr|                     d          sd| }n|}||z   }d|                     |           d|                     |           d| }|                     |d          }d |j                                                            d	          D             }	|	s~d
|                     |           d|                     |           d| }
|                     |
d          }d |j                                                            d	          D             }	|	|||z            }t          |t          |	          t          |	          |k              S )ad  Search for files by name using ripgrep's --files mode.

        rg --files respects .gitignore and excludes hidden directories by
        default, and uses parallel directory traversal for ~200x speedup
        over find on wide trees.  Results are sorted by modification time
        (most recently edited first) when rg >= 13.0 supports --sortr.
        r   *zrg --files --sortr=modified -g rP  r  r-  rn  c                     g | ]}||S r   r   r1   rC  s     r   rg   z8ShellFileOperations._search_files_rg.<locals>.<listcomp>  s    GGG1QGQGGGr   r   zrg --files -g c                     g | ]}||S r   r   r  s     r   rg   z8ShellFileOperations._search_files_rg.<locals>.<listcomp>      KKKqKKKKr   )r_   ra   r"   )r   r  r   rr   r   r   r]   r   )r9   r   r   ry   rx   glob_patternfetch_limit
cmd_sortedrV   	all_files	cmd_plainpages               r   r  z$ShellFileOperations._search_files_rg  s    gg&8&8&=&=(w==LL"Lfn'd.D.D\.R.R ' '%%d++' '$' ' 	
 J33GG 3 3 5 5 ; ;D A AGGG	 	L+!7!7!E!E + +))$//+ +(+ + 
 ZZ	2Z66FKKFM$7$7$9$9$?$?$E$EKKKI./I)nn3
 
 
 	
r   c           	          |                      d          r|                     |||||||          S |                      d          r|                     |||||||          S t          d          S )z,Search for content inside files (grep-like).r  grepzqContent search requires ripgrep (rg) or grep. Install ripgrep: https://github.com/BurntSushi/ripgrep#installationr  )r   _search_with_rg_search_with_grepr]   )r9   r   r   r   ry   rx   r   r   s           r   rw  z#ShellFileOperations._search_content  s     T"" 	''y%(3W> > >v&& 	))'4E6*5w@ @ @  \   r   c                 	   g d}|dk    r$|                     dt          |          g           |r*|                     d|                     |          g           |dk    r|                    d           n|dk    r|                    d           |                    |                     |                     |                    |                     |                     |dk    r||z   d	z   n||z   }	|                     d
ddt          |	          g           d                    |          }
|                     |
d          }|j        dk    r_|j                                        sFt          |d          r |j
        r|j
                                        nd}t          d| d          S |dk    rcd |j                                                            d          D             }t          |          }||||z            }t          ||          S |dk    ri }|j                                                            d          D ]_}d|v rY|                    dd          }t          |          dk    r0	 t          |d                   ||d         <   O# t           $ r Y [w xY w`t          |t#          |                                                    S t'          j        d          }t'          j        d          }g }|j                                                            d          D ]W}|r|dk    r|                    |          }|r|                    t-          |                    d          pd|                    d          z   t          |                    d                    |                    d           d!d"         #                     |dk    r|                    |          }|r|                    t-          |                    d          pd|                    d          z   t          |                    d                    |                    d           d!d"         #                     Yt          |          }||||z            }t          |||||z   k    $          S )%zSearch using ripgrep.)r  z--line-numberz--no-headingz--with-filenamer   -Cz--glob
files_only-lcount-c   r   head-nrP  r-  rn  r.  stderrSearch errorSearch failed: ru  c                     g | ]}||S r   r   r  s     r   rg   z7ShellFileOperations._search_with_rg.<locals>.<listcomp>N  r  r   r   r  :rv   r`   ra   ^([A-Za-z]:)?(.*?):(\d+):(.*)$^([A-Za-z]:)?(.*?)-(\d+)-(.*)$--r   rt     Nrw   re   r^   ra   r"   )extendr?   r  r   r   r   rs   rr   r   hasattrr  r]   r   r   rsplitrA   r   r   valuesr   compilematchrX   groupr9   r   r   r   ry   rx   r   r   	cmd_partsr  r   rV   	error_msgr  totalr  r`   rd   r  	_match_re_ctx_rer^   rf   s                          r   r  z#ShellFileOperations._search_with_rg'  s    ONN	 Q;;dCLL1222  	Lh(>(>y(I(IJKKK ,&&T""""G##T""" 	//88999//55666
 /6kkefns**uv~#vtS-=-=>???hhy!!C,, q  )<)<)>)> 181J1Jpv}p++---bpI&C	&C&CQRSSSS ,&&KKFM$7$7$9$9$?$?$E$EKKKI	NNEVFUN23Dd>>>>G##F++--33D99 ! !$;; KKQ//E5zzQ!/258}}F58,,) ! ! ! D!v3v}};O;OPPPP 
#DEEIj!BCCGG++--33D99   tt|| OOD)) NN;ggajj.B!''!**<$'

OO !

4C4 0$ $ $   
  Q;;d++A {"#''!**"2aggajj!@(+AGGAJJ$%GGAJJtt$4( ( (    LLE6&5.01D!&5.0   s   >J
J*)J*c                 	   ddg}|                     d           |dk    r$|                    dt          |          g           |r*|                    d|                     |          g           |dk    r|                     d           n|d	k    r|                     d
           |                     |                     |                     |                     |                     |                     ||z   |dk    rdndz   }	|                    dddt          |	          g           d                    |          }
|                     |
d          }|j        dk    r_|j                                        sFt          |d          r |j
        r|j
                                        nd}t          d| d          S |dk    rcd |j                                                            d          D             }t          |          }||||z            }t          ||          S |d	k    ri }|j                                                            d          D ]_}d|v rY|                    dd          }t          |          dk    r0	 t          |d                   ||d         <   O# t           $ r Y [w xY w`t          |t#          |                                                    S t'          j        d          }t'          j        d          }g }|j                                                            d          D ]W}|r|dk    r|                    |          }|r|                     t-          |                    d          pd |                    d          z   t          |                    d!                    |                    d"          d#d$         %                     |dk    r|                    |          }|r|                     t-          |                    d          pd |                    d          z   t          |                    d!                    |                    d"          d#d$         %                     Yt          |          }||||z            }t          |||||z   k    &          S )'zFallback search using grep.r  z-rnHz--exclude-dir='.*'r   r  z	--includer  r  r  r  r  r   r  r  rP  r-  rn  r.  r  r  r  ru  c                     g | ]}||S r   r   r  s     r   rg   z9ShellFileOperations._search_with_grep.<locals>.<listcomp>  r  r   r   r  r  rv   r  r  r  r  r   rt  r  Nrw   re   r  )r   r  r?   r  r   r   rs   rr   r   r  r  r]   r   r   r  rA   r   r   r  r   r  r  rX   r  r  s                          r   r  z%ShellFileOperations._search_with_grep  s    V$	 	-... Q;;dCLL1222  	Ok4+A+A)+L+LMNNN ,&&T""""G##T""" 	//88999//55666 fnw{{B#vtS-=-=>???hhy!!C,, q  )<)<)>)> 181J1Jpv}p++---bpI&C	&C&CQRSSSS,&&KKFM$7$7$9$9$?$?$E$EKKKI	NNEVFUN23Dd>>>>G##F++--33D99 ! !$;; KKQ//E5zzQ!/258}}F58,,) ! ! ! D!v3v}};O;OPPPP 
#DEEIj!BCCGG++--33D99   tt||OOD)) NN;ggajj.B!''!**<$'

OO !

4C4 0$ $ $   
 Q;;d++A {"#''!**"2aggajj!@(+AGGAJJ$%GGAJJtt$4( ( (    LLE6&5.01D!&5.0   s   J//
J<;J<r/   )NNN)rv   r   r   r   )&r;   r<   r=   r>   r?   r   rA   rq   r   rB   r   r   r   r   r  r  r  r   r{   r  r   rF   r   r   r   rM   r   r   ri   r`  r   r]   r   rv  r  rw  r  r  r   r   r   r   r   :  s,        !2 !2# !2 !2 !2 !2F CG $
 
S 
s 
C 

)6
 
 
 
@( ( ( ( ( ( c 3 $    $'c 'd ' ' ' '
# # ## #c # # # ## # # # # #J7S 7S 7 7 7 7
	 	3 	# 	RU 	 	 	 	V
 V
c V
3 V
3 V
 V
 V
 V
 V
p2
3 2
: 2
 2
 2
 2
hJ# J* J J J J8     S s {    $4
s 4
S 4
[ 4
 4
 4
 4
v +0N
 N
# N
3 N
C N
#'N
4?N
 N
 N
 N
`s {    <
 

 
 
 
 
H CLOP<==> =>c => =>C =>"3-=>7:=>IL=>=>69=>BN=> => => =>~1
S 1
 1
C 1
 1
Q] 1
 1
 1
 1
f)
 )
3 )
s )
C )
T` )
 )
 )
 )
Vs # (3- ",/>ALOT`   "as a# a(3- a"a,/a>AaLOaT`a a a aF_ _C _HSM _!$_.1_@C_NQ_Vb_ _ _ _ _ _r   r   )9r>   r   r   r  abcr   r   dataclassesr   r   typingr   r   r	   r
   pathlibr   tools.binary_extensionsr   agent.file_safetyr   r   r   r   r   r   r?   r   _HOMEWRITE_DENIED_PATHSWRITE_DENIED_PREFIXESr   rB   r   r   rF   rM   rX   r]   ri   rq   ru   r   ro  	MAX_LINESMAX_LINE_LENGTHr  r   r   r   r   rA   r   tupler   r   r   r   r   r   <module>r     sB   4 
			 				  # # # # # # # # ( ( ( ( ( ( ( ( , , , , , , , , , , , ,       5 5 5 5 5 5            	IDIKK--e44 33E:: )hsm ) ) ) ))3 )4 ) ) ) ) U U U U U U U U& I I I I I I I I        6                6 
 
 
 
 
 
 
 
         1 1 1 1 1S 1 1 1r NMM  .%)(  	    s S S     -@+=/ /c /%(/BGS// / / /& /D-A/ / /'*/FKCQTHo/ / / /o o o o o. o o o o or   