
    PL
j(             	       0   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 ej        d	          Z  ej        d
          Z!dedefd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,dede-ee.ef         dz  fd Z/ G d! d"e          Z0h d#Z1d$d%d&d'd(d)Z2d*d+d,d-Z3d.ed/ede$fd0Z4d1ede-e$ef         fd2Z5d1ede-e$ef         fd3Z6d1ede-e$ef         fd4Z7d1ede-e$ef         fd5Z8e8e5e6e6e7d6Z9d7Z:d7Z;d8Z<d9Z=d:Z>dZ?d;Z@d<ed=e.de.fd>ZAe=e>fd?ed@ede-e.e.f         fdAZBe?e@fd?ed@ede-e.e.f         fdBZC G dC dDe0          ZDdS )Ea\  
File Operations Module

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

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_deniedz!\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)z+'?\x07?__HERMES_FENCE_[A-Za-z0-9]+__\x07?'?textreturnc                 t   | s| S g }|                      d          D ]}d|v pd|v }t                              d|          }t                              d|          }|                    dd          }|r|                    d          dk    rr|                    |           d                    |          S )z;Strip leaked terminal fence wrappers from file read output.Tkeepends__HERMES_FENCE_z] z'
	 )
splitlines_OSC_SEQUENCE_REsub_FENCE_MARKER_REreplacestripappendjoin)r   cleaned_lineslinehad_terminal_wrappercleaneds        9/home/kuhnn/.hermes/hermes-agent/tools/file_operations.py_strip_terminal_fence_leaksr&   <   s     !M.. & &0D8KGtO"&&r400"&&r733//&"-- 	GMM*$=$=$C$CW%%%%77=!!!    c                      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 r'   r%   _get_safe_write_rootr+   M   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/   X   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.r   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>r   s(    TTTA!-AQSGG1GGGr'   __dict__itemsselfs    r%   to_dictzReadResult.to_dictq   s$    TT!4!4!6!6TTTTr'   )__name__
__module____qualname____doc__r2   str__annotations__r3   intr4   r5   boolr6   r   r7   r8   r9   r:   r;   r<   r   listr?   r   dictrM   r*   r'   r%   r1   r1   a   s#        %%GSKIsItD(3-ItHd$(NHSM(((#Ix}### $J$$$E8C=$uT:::M49:::U U U U U U Ur'   r1   c                       e Zd ZU dZdZeed<   dZeed<   dZ	e
eeef                  ed<   dZe
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_createdNlintlsp_diagnosticsr<   warningr   c                 H    d | j                                         D             S )Nc                     i | ]
\  }}|||S rB   r*   rC   s      r%   rG   z'WriteResult.to_dict.<locals>.<dictcomp>   s    HHHA!-1---r'   rH   rK   s    r%   rM   zWriteResult.to_dict   s$    HH!4!4!6!6HHHHr'   )rN   rO   rP   rQ   rZ   rT   rS   r[   rU   r\   r   r	   rR   r
   r]   r<   r^   rW   rM   r*   r'   r%   rY   rY   u   s         %%M3L$%)D(4S>
"))) &*OXc])))E8C=!GXc]!!!I I I I I I Ir'   rY   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
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_deletedNr\   r]   r<   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<   | j        r
| j        |d<   |S )	Nrc   rd   re   rf   rg   r\   r]   r<   )rc   rd   re   rf   rg   r\   r]   r<   rL   results     r%   rM   zPatchResult.to_dict   s    T\*9 	'!YF6N 	;'+':F#$ 	9&*&8F?# 	9&*&8F?#9 	'!YF6N 	=(,(<F$%: 	)"jF7Or'   )rN   rO   rP   rQ   rc   rU   rS   rd   rR   r   rV   re   r   rf   rg   r\   r   r	   r
   r]   r<   rW   rM   r*   r'   r%   rb   rb      s         &&GTD#NNN %d ; ; ;NDI;;;$uT:::M49:::$uT:::M49:::%)D(4S>
")))%)OXc])))E8C=      r'   rb   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_numberr2   g        mtimeN)	rN   rO   rP   rQ   rR   rS   rT   rn   floatr*   r'   r%   rl   rl      sF           
IIILLLE5r'   rl   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_countFr5   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 )	Nru   c                 8    g | ]}|j         |j        |j        d S ))r,   r"   r2   r,   rm   r2   )rD   ms     r%   
<listcomp>z(SearchResult.to_dict.<locals>.<listcomp>   s8     ! ! ! 19MM! ! !r'   rr   rs   rt   Tr5   r<   )ru   rr   rs   rt   r5   r<   ri   s     r%   rM   zSearchResult.to_dict   s    !12< 	! !! ! !F9 : 	)"jF7O; 	+#{F8> 	'"&F;: 	)"jF7Or'   )rN   rO   rP   rQ   r   rV   rr   r   rl   rS   rs   rR   rW   rt   r	   rT   ru   r5   rU   r<   r   rM   r*   r'   r%   rq   rq      s           !&t!<!<!<GT+<<<uT222E49222"U4888FDcN888KItE8C=      r'   rq   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.Trc   Fskippedr   outputmessager   c                 p    | j         r
d| j        dS | j        rdnd| j        d}| j        r
| j        |d<   |S )Nr}   )statusr   okr<   )r   r~   r   )r}   r   rc   r~   ri   s     r%   rM   zLintResult.to_dict   sR    < 	B'DLAAA$(L=DDgUU< 	- $F9r'   N)rN   rO   rP   rQ   rc   rU   rS   r}   r~   rR   r   rW   rM   r*   r'   r%   r|   r|      sw         %%GTGTFCGS      r'   r|   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)	rN   rO   rP   rQ   r   rR   rS   r   rT   r*   r'   r%   r   r      s8         00FCIsr'   r   r"   c                    | r| dk    rdS d}t          j        d|           D ]}|}|dS | d|                                         }|sdS |t          |                    d                    | |                                d         fS )a]  Parse grep/rg context output in ``path-line-content`` format.

    Context lines are ambiguous because filenames may legitimately contain
    ``-<digits>-`` segments. Prefer the rightmost numeric separator so a path
    like ``dir/file-12-name.py-8-context`` resolves to
    ``dir/file-12-name.py`` line ``8`` instead of truncating at ``file``.
    --Nz-(\d+)-   )refinditerstartrT   groupend)r"   match	candidater,   s       r%   _parse_search_context_liner      s      44<<tE[T22  	}tD tU[[^^$$d599;;<<&888r'   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     r,   offsetlimitr   c                     dS )z$Read a file with pagination support.Nr*   )rL   r,   r   r   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*   rL   r,   s     r%   read_file_rawzFileOperations.read_file_raw  s	     	r'   r2   c                     dS )z8Write content to a file, creating directories as needed.Nr*   )rL   r,   r2   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*   )rL   r,   r   r   r   s        r%   patch_replacezFileOperations.patch_replace  s	     	r'   patch_contentc                     dS )zApply a V4A format patch.Nr*   )rL   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*   )rL   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*   )	rL   r   r,   r   r   r   r   r   r   s	            r%   searchzFileOperations.search/  s	    
 	r'   r   r   Fr   r2   Nr   r   r2   r   )rN   rO   rP   rQ   r   rR   rT   r1   r   r   rY   r   rU   rb   r   r   r   r   r   rq   r   r*   r'   r%   r   r     s       JJ c 3 3     ^ # *    ^ s S [    ^ */ # 3 C #'4?   ^
 s {    ^      ^ S s {    ^ BKOP<= c  C "3-7:IL69BN   ^  r'   r   >   .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).pyz.jsz.tsz.goz.rs)z/this is not the tsc command you are looking forz%could not determine executable to runznot found in npm registry)zno input filename givenzerror: not a workspace)zcannot find packagezgo: cannot find main module)npxrustfmtgobase_cmdr~   c                     t                               |           }|sdS |                                t          fd|D                       S )aU  Return True iff ``output`` from ``base_cmd`` indicates the linter
    itself couldn't run (a tooling gap), as opposed to a real lint error
    in the file being checked.

    ``base_cmd`` is the first word of the linter command line (``npx``,
    ``rustfmt``, ``go``, ...).  ``output`` is the stdout/stderr captured
    from running it.
    Fc              3       K   | ]}|v V  	d S rB   r*   )rD   plowers     r%   	<genexpr>z._looks_like_linter_unusable.<locals>.<genexpr>x  s'      ,,aqEz,,,,,,r'   )_LINTER_UNUSABLE_PATTERNSgetr   any)r   r~   patternsr   s      @r%   _looks_like_linter_unusabler   k  sU     ),,X66H uLLNNE,,,,8,,,,,,r'   r2   c           	          ddl }	 |                    |            dS # |j        $ r'}dd|j         d|j         d|j         dfcY d}~S d}~wt          $ r%}dt          |          j         d	| fcY d}~S d}~ww xY w)
z;In-process JSON syntax check.  Returns (ok, error_message).r   NTr   FzJSONDecodeError:  (line 	, column ): )	jsonloadsJSONDecodeErrormsglinenocolno	ExceptiontyperN   )r2   _jsones      r%   _lint_json_inprocr   {  s    1Gx  V V VU!%UUUU17UUUUUUUUUU 1 1 1a)00Q0000000001s,    
A;A	A;	A;A60A;6A;c                     	 ddl }n# t          $ r Y dS w xY w	 |                    |            dS # |j        $ r}dd| fcY d}~S d}~wt          $ r%}dt          |          j         d| fcY d}~S d}~ww xY w)u   In-process YAML syntax check.  Returns (ok, error_message).

    Skipped gracefully if PyYAML isn't installed — YAML parsing is optional.
    r   NT__SKIP__r   FzYAMLError: r   )yamlImportError	safe_load	YAMLErrorr   r   rN   )r2   _yamlr   s      r%   _lint_yaml_inprocr     s    
        1   x? ( ( ('A''''''''' 1 1 1a)00Q0000000001s8    
0 
A8A A8A8A3-A83A8c                     	 ddl }n(# t          $ r 	 ddl}n# t          $ r Y Y dS w xY wY nw xY w	 |                    |            dS # t          $ r%}dt          |          j         d| fcY d}~S d}~ww xY w)z<In-process TOML syntax check (stdlib tomllib, Python 3.11+).r   Nr   r   Fr   )tomllibr   tomlir   r   r   rN   )r2   _tomlr   s      r%   _lint_toml_inprocr     s    $ $ $ $	$!!!!! 	$ 	$ 	$####	$ "!$1Gx 1 1 1a)00Q0000000001sA    
,,
&,&,,A 
A6A1+A61A6c                 >   ddl }	 |                    |            dS # t          $ rH}|j        rd|j         d|j         dnd}dt          |          j         d	|j         | fcY d}~S d}~wt          $ r%}dt          |          j         d	| fcY d}~S d}~ww xY w)
u   In-process Python syntax check via ast.parse.

    Catches SyntaxError, IndentationError, and everything else the
    ast module rejects — matching py_compile's scope but with no
    subprocess overhead and no dependency on a ``python`` in PATH.
    r   Nr   r   r   r   r   Fr   )	astparseSyntaxErrorr   r   r   rN   r   r   )r2   _astr   locs       r%   _lint_python_inprocr     s     1

7x : : ::;(J666186666a)99QU9C999999999 1 1 1a)00Q0000000001s,    
B=A*$B*B7BBB)r   z.jsonz.yamlz.ymlz.tomli  i   r   r   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.)rT   	TypeError
ValueError)r   r   s     r%   _coerce_intr     s<    5zzz"   s    ''r   r   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_linesr   )tools.tool_output_limitsr   maxr   DEFAULT_READ_OFFSETDEFAULT_READ_LIMITmin)r   r   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   r   )r   r   DEFAULT_SEARCH_OFFSETDEFAULT_SEARCH_LIMIT)r   r   r  r  s       r%   normalize_search_paginationr	    sF     A{63HIIJJ1k%1EFFGG...r'   c                   6   e Zd ZdZdEdefdZ	 	 dFdededededef
d	Zd
ede	fdZ
dEdedede	fdZdede	fdZdGd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dHd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	 dIded'ed(ed)e	def
d*Zd+edefd,ZdEdedee         defd-Z	 dEded.ee         d/ee         defd0Zde	fd1Z d2ede	fd3Z!deddfd4Z"ddd5ded.ee         d/ee         defd6Z#	 	 	 dJd:eded;ed<ee         deded=ed>ede$fd?Z%d:edededede$f
d@Z&d:edededede$f
dAZ'd:eded<ee         deded=ed>ede$fdBZ(d:eded<ee         deded=ed>ede$fdCZ)d:eded<ee         deded=ed>ede$fdDZ*dS )K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)rL   terminal_envr  s      r%   __init__zShellFileOperations.__init__  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  r~   r   
returncoder   )r   r   )r  r  r  executer   r   )rL   r  r  r  r  kwargseffective_cwdrj   s           r%   _execzShellFileOperations._exec"  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  r   r   )rL   r  rj   s      r%   _has_commandz ShellFileOperations._has_commandB  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).
        r   Tc              3   H   K   | ]}t          |          d k     |dvdV  dS )    z
	r   N)ord)rD   cs     r%   r   z8ShellFileOperations._is_likely_binary.<locals>.<genexpr>U  sJ        E  Ea"%a&&2++!82C2C !"2C2C2C2C E  Er'   Ni  g333333?F)osr,   splitextr   r   sumr  len)rL   r,   r"  extnon_printables        r%   _is_likely_binaryz%ShellFileOperations._is_likely_binaryI  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.r   )r(  r,   r)  r   IMAGE_EXTENSIONS)rL   r,   r,  s      r%   	_is_imagezShellFileOperations._is_image[  s4    gt$$Q'--//&&&r'   r   r2   
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
)r   Nz... [truncated]6d|)r   r4  split	enumerater+  r   r    )	rL   r2   r2  r4  max_line_lengthlinesnumberedir"   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~/r   Nr  z[a-zA-Z0-9._-]+zecho ~)	
startswithr  r   r   r   findr   	fullmatchr+  )
rL   r,   rj   homerest	slash_idxusernameexpand_result	user_homesuffixs
             r%   _expand_pathz ShellFileOperations._expand_pathm  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'"'"')r   )rL   rL  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.Tr   za/zb/)fromfiletofiler   )r   difflibunified_diffr    )rL   rP  rQ  rR  	old_lines	new_linesrd   s          r%   _unified_diffz!ShellFileOperations._unified_diff  sn    **D*99	**D*99	#y$(__"??
 
 

 wwt}}r'   r   r   r   c           	         |                      |          }t          ||          \  }}d|                     |           d}|                     |          }|j        dk    r|                     |          S t          |j                  }	 t          |	                                          }n# t          $ r d}Y nw xY w|t          k    r	 |                     |          rt          dd|d          S d|                     |           d}|                     |          }	t          |	j                  }
|                     ||
          rt          d|d	          S ||z   d
z
  }d| d| d|                     |           }|                     |          }|j        dk    rt          d|j                   S t          |j                  }d|                     |           }|                     |          }t          |j                  }	 t          |	                                          }n# t          $ r d}Y nw xY w||k    }d}|rd|d
z    d| d| d| d	}t          |                     ||          ||||          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.)r8   r7   r4   r6   head -c 1000 zUBinary file - cannot display as text. Use appropriate tools to handle this file type.r7   r4   r<   r   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))r2   r3   r4   r5   r6   )rK  r  rO  r  r   _suggest_similar_filesr&   r   rT   r   r   MAX_FILE_SIZEr1  r1   r.  r>  )rL   r,   r   r   stat_cmdstat_resultstat_outputr4   
sample_cmdsample_resultsample_outputend_lineread_cmdread_resultread_outputwc_cmd	wc_result	wc_outputr3   r5   r6   s                        r%   r   zShellFileOperations.read_file  s      &&1&%@@ Id44T::HHHjj** A%%..t4441+2DEE	K--//00II 	 	 	III	 }$$ >>$ 		#\    PT%;%;D%A%AOOO


:..3M4HII!!$66 	#m    E>A%RfRRxRRD4J4J44P4PRRjj** A%%$PK<N$P$PQQQQ1+2DEE ;D22488::JJv&&	/	0@AA		ioo//00KK 	 	 	KKK	  (*	 	yxAxxVxxV^xxdoxxxD**;??#
 
 
 	
s$   !B. .B=<B=!H4 4II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   r   ls -1 z 2>/dev/null | head -50r5  d   Z   F   <      (   g?   c                     | d          S )Nr   r*   )xs    r%   <lambda>z<ShellFileOperations._suggest_similar_files.<locals>.<lambda>.  s    1Q4% r'   )keyc                     g | ]\  }}|S r*   r*   )rD   _fps      r%   rz   z>ShellFileOperations._suggest_similar_files.<locals>.<listcomp>/  s    ...%!R2...r'   N   zFile not found: )r<   r?   )r(  r,   dirnamebasenamer)  r   rO  r  r   r   r   r8  rA  r+  setr   r   r    sortr1   )rL   r,   dir_pathrR  basename_no_extr,  
lower_namels_cmd	ls_resultscoredflfscorecommonsimilars                  r%   rd  z*ShellFileOperations._suggest_similar_files  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                 Z   |                      |          }d|                     |           d}|                     |          }|j        dk    r|                     |          S t          |j                  }	 t          |                                          }n# t          $ r d}Y nw xY w| 
                    |          rt          dd|          S |                     d|                     |           d          }t          |j                  }|                     ||          rt          d|d          S |                     d	|                     |                     }|j        dk    rt          d
|j                   S t          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)r8   r7   r4   r^  u'   Binary file — cannot display as text.r_  cat ra  rb  )r2   r4   )rK  rO  r  r   rd  r&   r   rT   r   r   r1  r1   r.  )	rL   r,   rf  rg  rh  r4   rj  rk  
cat_results	            r%   r   z!ShellFileOperations.read_file_raw6  s      &&Hd44T::HHHjj** A%%..t4441+2DEE	K--//00II 	 	 	III	>>$ 	RttyQQQQ

#]43I3I$3O3O#]#]#]^^3M4HII!!$66 	)?    ZZ Et'='=d'C'C E EFF
1$$$OJ<M$O$OPPPP/
0ABB
 
 
 	
s   9!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 pathrb  zrm -f r   zFailed to delete r   rK  r/   rY   r  rO  r   r   )rL   r,   rj   s      r%   r   zShellFileOperations.delete_fileW  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: r  rb  zmv  r   zFailed to move z -> r   r  )rL   r   r   r   rj   s        r%   r   zShellFileOperations.move_filea  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                                         }d}|t          v p|                     |          }|rGd| 	                    |           d}| 
                    |          }|j        dk    r|j        r|j        }|                     |           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|                     |||          }d}|j        s|j        r|                     |||          }|r|}t          ||	|r|                                nd|          S )uO  
        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.

        After the write, runs a post-first / pre-lazy lint check via
        ``_check_lint_delta()``.  If the new content is clean, the lint
        call is O(one parse).  If the new content has errors, the pre-write
        content is linted too and only errors newly introduced by this
        write are surfaced — pre-existing problems are filtered out so
        the agent isn't distracted chasing them.

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

        Returns:
            WriteResult with bytes written, lint summary, or error.
        Write denied: '(' is a protected system/credential file.rb  r   Nr  r]  r   Fz	mkdir -p Tzcat > )r  zFailed to write file: r\  zutf-8pre_contentpost_content)rZ   r[   r\   r]   )rK  r/   rY   r(  r,   r)  r   LINTERS_INPROC_lsp_handles_extensionrO  r  r   r   _snapshot_lsp_baseliner  rT   r   r   r+  encode_check_lint_deltarc   r}   _maybe_lsp_diagnosticsrM   )rL   r,   r2   r,  r  want_prerm  rn  parentr[   	mkdir_cmdmkdir_result	write_cmdwrite_resultrf  rg  rZ   lint_resultr]   blocks                       r%   r   zShellFileOperations.write_files  s   .   && D!! 	g%et%e%e%effff" gt$$Q'--//%).(LD,G,G,L,L 	1
 Id44T::HHHH**X..K$))k.@))0 	##D))) && 	$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 ,,T{Y`,aa *. 	(+"5 	(//+G 0  E  ("''%*5?$$&&&4+	
 
 
 	
s   &G3 3,H"!H"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                            dd                              dd          }|	                    dd                              dd          }||k    r4t          d| dt          |           dt          |           d          S |                     ||	|          }|                     |||	          }t          d||g|r|                                nd|j                  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
        r  r  rb  r  r]  r   ra  )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
r5  z#Post-write verification failed for z5: on-disk content differs from intended write (wrote z chars, read back zb chars after normalizing line endings). The patch did not persist. Re-read the file and try again.r  TN)rc   rd   re   r\   r]   )rK  r/   rb   rO  r  r   r   tools.fuzzy_matchr  r  r   r   r<   r   r+  rZ  r  rM   r]   )rL   r,   r   r   r   rm  rn  r2   r  rQ  match_count	_strategyr<   err_msgr  r  
verify_cmdverify_result_verify_stdout_normalized_new_content_normalizedrd   r  s                         r%   r   z!ShellFileOperations.patch_replace  sB      && 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 %2$8$@$@$N$N$V$VW[]a$b$b!"-"5"5fd"C"C"K"KDRV"W"W$(???Md M M566M M 011M M M    !!';==
 ,,TwU`,aa 6*5?$$&&&4 )8
 
 
 	
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: rb  )tools.patch_parserr  r  rb   )rL   r   r  r  
operationsparse_errorrj   s          r%   r   zShellFileOperations.patch_v4aM  sm    ( 	MLLLLLLL"1/-"@"@
K 	N%L{%L%LMMMM &%j$77r'   c                    t           j                            |          d                                         }t                              |          }||Ud|                     |           d}|                     |          }|j        dk    rt          dd| d	          S |j
        } ||          \  }}|d
k    rt          dd| d	          S t          ||rdn|          S |t          vrt          dd| d	          S t          |         }	|	                                d         }
|                     |
          st          d|
 d	          S |	                    d|                     |                    }|                     |d          }|j        dk    rt          |
|j
                  rzddlm}  ||j
                                                  }t'          d |                                D             |dd                   }t          d|
 d|dd          	          S t          |j        dk    |j
                                        r|j
                                        nd          S )u8  
        Run syntax check on a file after editing.

        Prefers the in-process linter for structured formats (JSON, YAML,
        TOML) when possible — those parse via the Python stdlib in
        microseconds and don't require a subprocess.  Falls back to the
        shell linter table for compiled/type-checked languages
        (py_compile, node --check, tsc, go vet, rustfmt).

        Args:
            path: File path (used to select the linter + for shell invocation).
            content: Optional file content.  If provided AND an in-process
                     linter matches the extension, we lint the content
                     directly without re-reading the file from disk.  Ignored
                     for shell linters.

        Returns:
            LintResult with status and any errors.
        r   Nr  r]  r   TzFailed to read z	 for lint)r}   r   r   zNo linter available for z (missing dependency)r   rc   r~   zNo linter for z filesz not availablez{file}r{  r  )
strip_ansic              3   f   K   | ],}|                                 |                                 V  -d S rB   r   rD   lns     r%   r   z2ShellFileOperations._check_lint.<locals>.<genexpr>  s7      IIbhhjjIIIIIIIr'   x   z not usable:    )r(  r,   r)  r   r  r   rO  r  r   r|   r   LINTERSr8  r!  r   r   tools.ansi_stripr  r   nextr   )rL   r,   r2   r,  inprocrm  rn  r   err
linter_cmdr   r  rj   r  r$   
first_lines                   r%   _check_lintzShellFileOperations._check_lintk  s   ( gt$$Q'--//  ##C((L$"8"8">">LLL"jj22(A--%d<]d<]<]<]^^^^%,fWooGBj  !$8mSV8m8m8mnnnnbr1BsCCCC gd4PS4P4P4PQQQQS\
##%%a(  ** 	Qdx4O4O4OPPPP   4+A+A$+G+GHHC,,q  %@6=%Y%Y  433333 j//5577GIIg&8&8&:&:III J #DD*TcT2BDD   
 $),2M,?,?,A,AI6=&&(((r
 
 
 	
r'   r  r  c                    |                      ||          }|j        s|j        r|S ||S |                      ||          }|j        s|j        s|j        s|S d |j                                        D             fd|j                                        D             }|st          d|j        d          S t          ddd	                    |          z   
          S )u\  
        Run post-write syntax lint with pre-write baseline comparison.

        Two-tier strategy:

        1. **Syntax check** (in-process or shell-based, microseconds).
           Catches the bug class that motivated this layer: corrupt
           writes, mashed quotes, truncated output.  Hot path.

        2. **Delta refinement against pre-write content** when the
           syntax tier reports errors.  Filter out errors that already
           existed pre-edit so the agent isn't distracted by inherited
           state.

        Semantic diagnostics from the LSP layer are fetched separately
        via :meth:`_maybe_lsp_diagnostics` and surfaced in the
        ``lsp_diagnostics`` field on :class:`WriteResult` /
        :class:`PatchResult`.  Keeping the two channels separate lets
        the agent (and any downstream parsers) read syntax errors and
        semantic errors as independent signals.

        Args:
            path: File path (for linter selection).
            pre_content: File content BEFORE the write.  Pass None for new
                         files or when the pre-state isn't available — the
                         delta refinement is skipped and all post errors
                         are returned.
            post_content: File content AFTER the write.  Optional; if None,
                          the shell linter reads from disk (same as
                          _check_lint).

        Returns:
            LintResult.  ``output`` contains either the full post-lint
            errors (no pre-state) or just the new-error lines (delta
            refinement applied).
        )r2   Nc                 ^    h | ]*}|                                 |                                 +S r*   r  r  s     r%   	<setcomp>z8ShellFileOperations._check_lint_delta.<locals>.<setcomp>  s-    PPPBRXXZZPRXXZZPPPr'   c                 h    g | ].}|                                 |                                 v,|/S r*   r  )rD   r  	pre_liness     r%   rz   z9ShellFileOperations._check_lint_delta.<locals>.<listcomp>  s9    jjjRrxxzzjbhhjj`iNiNibNiNiNir'   Fu^   Pre-existing lint errors — this edit didn't introduce new ones but the file is still broken.)rc   r~   r   zLNew lint errors introduced by this edit (pre-existing errors filtered out):
r5  r  )r  rc   r}   r~   r   r|   r    )rL   r,   r  r  postpre
post_linesr  s          @r%   r  z%ShellFileOperations._check_lint_delta  s-   L l;; < 	4< 	K Kt[99; 	#+ 	SZ 	 K QP#**?*?*A*APPP	jjjj4;#9#9#;#;jjj
 	 {x    8:>))J:O:OP
 
 
 	
r'   c                 ~    t          | dd          }|dS 	 ddlm} n# t          $ r Y dS w xY wt	          ||          S )u}  Return True iff this FileOperations is wired to a local backend.

        LSP servers run on the host process — they need access to the
        files they're linting.  Remote/sandboxed backends (Docker,
        Modal, SSH, Daytona) keep files inside the sandbox where the
        host-side LSP server can't reach them, so we skip the LSP
        path for those entirely.
        r  NFr   )LocalEnvironment)r  tools.environments.localr  r   
isinstance)rL   r  r  s      r%   _lsp_local_onlyz#ShellFileOperations._lsp_local_only  sn     dE4((; 5	AAAAAAA 	 	 	55	#/000s    
,,r,  c                     |sdS 	 ddl m} n# t          $ r Y dS w xY w|                                }|D ]}||j        v r dS dS )u  Return True iff some registered LSP server claims this extension.

        Used to decide whether to capture pre-write content for the
        line-shift map.  Capturing is cheap (one ``cat`` on the host)
        but pointless if no LSP would ever look at the file.

        Safe to call on remote backends — the registry is purely
        in-process metadata; we still gate the actual LSP path on
        :meth:`_lsp_local_only`.
        Fr   )SERVERST)agent.lsp.serversr  r   r   
extensions)rL   r,  r  	ext_lowersrvs        r%   r  z*ShellFileOperations._lsp_handles_extension   s      	5	1111111 	 	 	55	IIKK	 	 	CCN**tt +us    
c                     |                                  sdS 	 ddlm}  |            }n# t          $ r Y dS w xY w|dS 	 |                    |           dS # t          $ r Y dS w xY w)uL  Capture pre-edit LSP diagnostics so the post-write delta is correct.

        Best-effort.  Silent on every failure path — LSP is an
        enrichment layer and must never break a write.

        Skipped entirely on non-local backends (Docker, Modal, SSH,
        etc.) — the server can't see files inside the sandbox.
        Nr   get_service)r  	agent.lspr  r   snapshot_baseline)rL   r,   r  svcs       r%   r  z*ShellFileOperations._snapshot_lsp_baseline7  s     ##%% 	F	------+--CC 	 	 	FF	;F	!!$''''' 	 	 	DD	s   ) 
77A 
A$#A$r  c                   |                                  sdS 	 ddlm} n# t          $ r Y dS w xY w	  |            }n# t          $ r Y dS w xY w||                    |          sdS d}|.|,||k    r&	 ddlm}  |||          }n# t          $ r d}Y nw xY w	 |                    |d|          }n# t          $ r Y dS w xY w|sdS 	 ddlm	}	m
}
  |	||          }|sdS  |
d	|z             S # t          $ r Y dS w xY w)
u  Best-effort LSP semantic diagnostics for ``path``.

        Returns a formatted ``<diagnostics>`` block, or empty string
        when LSP is unavailable / disabled / produced no errors.

        When both ``pre_content`` and ``post_content`` are provided,
        a line-shift map is built and passed to the LSPService so
        baseline diagnostics are remapped into post-edit coordinates
        before the set-difference.  Without this, edits that delete
        or insert lines surface every pre-existing diagnostic below
        the edit point as "introduced by this edit".

        Wraps everything in a try/except so a misbehaving LSP server
        can't break a write.  This intentionally swallows all errors
        — the calling tier already returned a clean syntax result, so
        ``""`` here just means "no extra info to add".

        Skipped entirely on non-local backends (Docker, Modal, SSH,
        etc.) — same reasoning as ``_snapshot_lsp_baseline``.
        r   r   r  N)build_line_shiftT)delta
line_shift)report_for_filetruncatez)LSP diagnostics introduced by this edit:
)r  r  r  r   enabled_foragent.lsp.range_shiftr  get_diagnostics_syncagent.lsp.reporterr  r  )rL   r,   r  r  r  r  r  r  diagnosticsr  r  r  s               r%   r  z*ShellFileOperations._maybe_lsp_diagnosticsN  s   6 ##%% 	2	------- 	 	 	22		+--CC 	 	 	22	;cood33;2
 
"|'?KS_D_D_"BBBBBB--k<HH

 " " "!


"	224tPZ2[[KK 	 	 	22	 	2	DDDDDDDD#OD+66E r8H5PQQQ 	 	 	22	sW    
--
< 
A
	A
3B BBB2 2
C ?C C.  C. .
C<;C<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   rt  z 2>/dev/null | head -20r   r5  N   zSimilar paths: z, r  z. r<   ru   rs   )r	  rK  r  rO  r   r(  r,   r  r  r   r   r   r8  rA  r   r    rq   _search_files_search_content)rL   r   r,   r   r   r   r   r   r   checkr  basename_query
hint_partsparent_checkr  lower_q
candidatesentryles                      r%   r   zShellFileOperations.search  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                 (   |                     d          sd|vr|}n|                    d          d         }t          |          }t          d |j        D                       }|                     d          r|                     ||||          S |                     d          st          d          S |sd	nd
}|rd| nd
}	d
}
|sd|dz    d| }
d|                     |           |	 d|                     |           d|
 }| 	                    |d          }|j
                                        sJd|                     |           |	 d|                     |           d|
 }| 	                    |d          }g }|j
                                                            d          D ]}|s|                    dd          }t          |          dk    rJ|d                             dd
                                          r|                    |d                    x|                    |           |r|                                }g }|D ]}	 t          |                                                              |          j        }n$# t$          $ r t          |          j        }Y nw xY wt          d |D                       r{|                    |           ||||z            }t          |t          |                    S )z-Search for files by name pattern (glob-like).z**/r  c              3   H   K   | ]}|d vo|                     d          V  dS >   ..r   r   NrA  rD   parts     r%   r   z4ShellFileOperations._search_files.<locals>.<genexpr>  sL       '
 '
 #<(<(<'
 '
 '
 '
 '
 '
r'   rgrB  zFile search requires 'rg' (ripgrep) or 'find'. Install ripgrep for best results: https://github.com/BurntSushi/ripgrep#installationrb  z-not -path '*/.*'r   r  z | tail -n +r   z | head -n zfind z -type f -name z* -printf '%T@ %p\n' 2>/dev/null | sort -rnrx  r  z 2>/dev/null | sort -rnr5  ry  r   r   c              3   H   K   | ]}|d vo|                     d          V  dS r  r
  r  s     r%   r   z4ShellFileOperations._search_files.<locals>.<genexpr>  s8      ^^Dt;.G4??33G3G^^^^^^r'   rs   ru   )rA  r8  r   r   partsr!  _search_files_rgrq   rO  r  r   r   r+  r   isdigitr   resolverelative_tor   )rL   r   r,   r   r   search_patternsearch_roothas_hidden_path_ancestorhidden_excludehidden_filter_exprpagination_exprr  rj   
cmd_simplers   r"   r  normalized_rootfiltered_files	file_path	rel_partss                        r%   r  z!ShellFileOperations._search_files  s    !!%(( 	4S-?-?$NN$]]3//3N4jj#& '
 '
#)'
 '
 '
 $
 $
  T"" 	N((ufMMM   (( 	K    5MT,,RT5CK1111
 ' 	LKVaZKKEKKOMd,,T22 M4F M MW[WmWmn|W}W} M M;JM M C,,}""$$ 	8C!7!7!=!= C?Q C Cbfbxbx  zH  cI  cI C C1@C CJZZ
BZ77FM''))//55 	# 	#D JJsA&&E5zzQ58#3#3C#<#<#D#D#F#FU1X&&&&T""""
 $ 	:)1133ON" 1 1	6 $Y 7 7 9 9 E Eo V V \II! 6 6 6 $Y 5III6^^T]^^^^^ %%i0000"6&5.#89E E


 
 
 	
s   9JJ65J6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 r  z 2>/dev/null | head -n rx  r  c                     g | ]}||S r*   r*   rD   r  s     r%   rz   z8ShellFileOperations._search_files_rg.<locals>.<listcomp><  s    GGG1QGQGGGr'   r5  zrg --files -g c                     g | ]}||S r*   r*   r#  s     r%   rz   z8ShellFileOperations._search_files_rg.<locals>.<listcomp>F      KKKqKKKKr'   )rs   ru   r5   )rA  rO  r  r   r   r8  rq   r+  )rL   r   r,   r   r   glob_patternfetch_limit
cmd_sortedrj   	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#installationrb  )r!  _search_with_rg_search_with_greprq   )rL   r   r,   r   r   r   r   r   s           r%   r  z#ShellFileOperations._search_contentP  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          }g }|j                                                            d          D ]}|r|dk    r|                    |          }|r|                    t-          |                    d          pd|                    d          z   t          |                    d                    |                    d          d d!         "                     |dk    rPt1          |          }|r?|                    t-          |d         |d         |d         d d!         "                     t          |          }||||z            }t          |||||z   k    #          S )$zSearch using ripgrep.)r  z--line-numberz--no-headingz--with-filenamer   -Cz--glob
files_only-lcount-cr  r7  head-nr  rx  r  ry  stderrSearch errorSearch failed: r  c                     g | ]}||S r*   r*   r#  s     r%   rz   z7ShellFileOperations._search_with_rg.<locals>.<listcomp>  r%  r'   r5  r  :r   rt   ru   ^([A-Za-z]:)?(.*?):(\d+):(.*)$r   r   r     Nr   rx   rr   ru   r5   )extendrR   rO  r   r    r  r   r   r   hasattrr8  rq   r8  r+  rsplitrT   r   r*  valuesr   compiler   rl   r   r   rL   r   r,   r   r   r   r   r   	cmd_partsr'  r  rj   	error_msgr)  totalr+  rt   r"   r  	_match_rerr   ry   parseds                          r%   r.  z#ShellFileOperations._search_with_rga  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G++--33D99   tt|| OOD)) NN;ggajj.B!''!**<$'

OO !

4C4 0$ $ $   
  Q;;7==F {!'(.q	$*1IdsdO( ( (    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          }g }|j                                                            d          D ]}|r|dk    r|                    |          }|r|                     t-          |                    d          pd|                    d          z   t          |                    d                     |                    d!          d"d#         $                     |dk    rPt1          |          }|r?|                     t-          |d         |d         |d         d"d#         $                     t          |          }||||z            }t          |||||z   k    %          S )&zFallback search using grep.r-  z-rnHz--exclude-dir='.*'r   r1  z	--includer2  r3  r4  r5  r  r7  r6  r7  r  rx  r  ry  r8  r9  r:  r  c                     g | ]}||S r*   r*   r#  s     r%   rz   z9ShellFileOperations._search_with_grep.<locals>.<listcomp>  r%  r'   r5  r  r<  r   r=  r>  r   r   r  r?  Nr   rx   r@  )r   rA  rR   rO  r    r  r   r   r   rB  r8  rq   r8  r+  rC  rT   r   r*  rD  r   rE  r   rl   r   r   rF  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G++--33D99   tt||OOD)) NN;ggajj.B!''!**<$'

OO !

4C4 0$ $ $   
 Q;;7==F {!'(.q	$*1IdsdO( ( (    LLE6&5.01D!&5.0   s   J//
J<;J<rB   )NNN)r   r   r   r   )+rN   rO   rP   rQ   rR   r  rT   r   r  rU   r!  r.  r1  r>  rK  rO  rZ  r1   r   rd  r   rY   r   r   r   rb   r   r   r   r|   r  r  r  r  r  r  rq   r   r  r  r  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 	 	 	 	Z
 Z
c Z
3 Z
3 Z
 Z
 Z
 Z
 Z
x2
3 2
: 2
 2
 2
 2
h
# 
* 
 
 
 
B     S s {    $q
s q
S q
[ q
 q
 q
 q
p +0a
 a
# a
3 a
C a
#'a
4?a
 a
 a
 a
Fs {    <I
 I
 I
hsm I
z I
 I
 I
 I
X 9=S
 S
c S
 S
(0S
AKS
 S
 S
 S
j1 1 1 1 1*# $    .3 4    6 &*&*@ @ @@ c]	@
 sm@ 
@ @ @ @L CLOP<==> =>c => =>C =>"3-=>7:=>IL=>=>69=>BN=> => => =>~P
S P
 P
C P
 P
Q] P
 P
 P
 P
d)
 )
3 )
s )
C )
T` )
 )
 )
 )
Vs # (3- ",/>ALOT`   "`s `# `(3- `"`,/`>A`LO`T`` ` ` `D^ ^C ^HSM ^!$^.1^@C^NQ^Vb^ ^ ^ ^ ^ ^r'   r  )ErQ   r(  r   rV  abcr   r   dataclassesr   r   typingr   r   r	   r
   pathlibr   tools.binary_extensionsr   agent.file_safetyr   r   r   r)   r   r.   rR   rD  _HOMEWRITE_DENIED_PATHSWRITE_DENIED_PREFIXESrE  r   r   r&   r+   rU   r/   r1   rY   rb   rl   rq   r|   r   tuplerT   r   r   r0  r  r   r   r   r   r   r   r  	MAX_LINESMAX_LINE_LENGTHre  r   r   r  r  r   r  r	  r  r*   r'   r%   <module>rZ     s   4 
			 				  # # # # # # # # ( ( ( ( ( ( ( ( , , , , , , , , , , , ,       5 5 5 5 5 5            	IDIKK--e44 33E::  2:BCC 2:LMM "c "c " " " "")hsm ) ) ) ))3 )4 ) ) ) ) U U U U U U U U& I I I I I I I I&        >                6                 9S 9U3S=-AD-H 9 9 9 9:1 1 1 1 1S 1 1 1r NMM  .%)( (
  .-# -s -t - - - - 	1s 	1uT3Y'7 	1 	1 	1 	11s 1uT3Y'7 1 1 1 1&1s 1uT3Y'7 1 1 1 1"1 1tSy)9 1 1 1 10   	    s S S     -@+=/ /c /%(/BGS// / / /& /D-A/ / /'*/FKCQTHo/ / / /j j j j j. j j j j jr'   