
    [jS                        d Z ddlZddlZddlZddlZddlZddlZddl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 ddlmZ ddlmZmZmZ ddlmZ ddlmZ dd	lmZ  ej        e          Z e ej         d
                    Z!e!re"                    ej#                    ej$                    Z%dZ& G d d          Z'dee(gdf         dz  ddfdZ)dee(gdf         dz  fdZ*de+de(ddfdZ,defdZ-dej.        de(ddfdZ/	 d)de0e(         de(dz  dej.        fdZ1dede+fdZ2dede+ddfdZ3de(de4e5e6f         dz  fd Z7 G d! d"e          Z8 G d# d$          Z9d%e(de(fd&Z: G d' d(e          Z;dS )*aC  Base class for all Hermes execution environment backends.

Unified spawn-per-call model: every command spawns a fresh ``bash -c`` process.
A session snapshot (env vars, functions, aliases) is captured once at init and
re-sourced before each command. CWD persists via in-band stdout markers (remote)
or a temp file (local).
    N)ABCabstractmethod)deque)Path)IOCallableProtocol)get_hermes_home)windows_hide_flags)is_interruptedHERMES_DEBUG_INTERRUPTl    c                   |    e Zd ZdZdefdZedefd            Zedefd            Zde	ddfd	Z
d
dde	de	fdZdS )_BoundedOutputCollectorz9Retain a bounded 40/60 head-tail window of streamed text.	max_charsc                 8   t          dt          |                    | _        t          | j        dz            | _        | j        | j        z
  | _        g | _        t                      | _        d| _        d| _	        d| _
        t          j                    | _        d S )N   皙?r   )maxintr   _head_limit_tail_limit_headr   _tail_head_chars_tail_chars_total_chars	threadingLock_lock)selfr   s     9/home/ice/.hermes/hermes-agent/tools/environments/base.py__init__z _BoundedOutputCollector.__init__8   s    QI//t~344>D,<< "
!&
^%%


    returnc                 b    | j         5  | j        | j        z   cd d d            S # 1 swxY w Y   d S N)r   r   r   r    s    r!   buffered_charsz&_BoundedOutputCollector.buffered_charsC   s|    Z 	7 	7#d&66	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7s   $((c                 R    | j         5  | j        cd d d            S # 1 swxY w Y   d S r&   )r   r   r'   s    r!   total_charsz#_BoundedOutputCollector.total_charsH   st    Z 	% 	%$	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	%s     textNc                 <   |sd S | j         5  t          |          }| xj        |z  c_        d}| j        | j        k     rSt          | j        | j        z
  |          }|r4| j                            |d |                    | xj        |z  c_        |}||z
  }|dk    s| j        dk    r	 d d d            d S || j        k    r[| j	        
                                 | j	                            || j         d                     | j        | _        	 d d d            d S ||d          }| j	                            |           | xj        t          |          z  c_        | j        | j        k    r| j        | j        z
  }| j	        d         }t          |          |k    r7| j	                                         | xj        t          |          z  c_        n"||d          | j	        d<   | xj        |z  c_        | j        | j        k    d d d            d S # 1 swxY w Y   d S )Nr   )r   lenr   r   r   minr   appendr   r   clearr   popleft)	r    r+   text_lenstarttake	remainingchunkexcessfirsts	            r!   r/   z_BoundedOutputCollector.appendM   s    	FZ  	/  	/4yyH)E$"2224+d.>>II !J%%d5D5k222$$,$$ E 5(IA~~!1Q!6!6 	/  	/  	/  	/  	/  	/  	/  	/ D,,,
  """
!!$(8'8':':";<<<#'#3 ' 	/  	/  	/  	/  	/  	/  	/  	/* LEJe$$$E

*"T%555)D,<<
1u::''J&&((($$E

2$$$$)&''NDJqM$$.$$ "T%5551 	/  	/  	/  	/  	/  	/  	/  	/  	/  	/  	/  	/  	/  	/  	/  	/  	/  	/s    BH5AHC)HHH suffixr;   c          	      B   | j         5  t          |          | j        k    r|| j         d         cddd           S d                    | j                  }d                    | j                  }| j        t          |          z
  }| j        |k    r||z   |z   cddd           S d}t          d          D ]n}t          d|t          |          z
            }t          |dz            }||z
  }	t          d| j        |z
  |	z
            }
d|
dd| j        dd	}||k    r n|}ot          d|t          |          z
            }t          |dz            }||z
  }	|	r||	 d         nd}|d|         |d|         z   |z   |z   cddd           S # 1 swxY w Y   dS )
zARender within ``max_chars``, preserving a required status suffix.Nr9      r   r   z

... [OUTPUT TRUNCATED - ,z chars omitted out of z total] ...

)
r   r-   r   joinr   r   r   ranger   r   )r    r;   headtail	availablenotice_content_budget
head_chars
tail_charsomittedupdatedrendered_tails                r!   renderz_BoundedOutputCollector.renderr   s   Z 	S 	S6{{dn,,t~o//0	S 	S 	S 	S 	S 	S 	S 	S 774:&&D774:&&DV4I I--d{V+	S 	S 	S 	S 	S 	S 	S 	S F1XX ! !!$Q	CKK(?!@!@ #!566
+j8
a!2Z!?*!LMMC7M C C"/BC C C  f$$E  IF$;<<N^c122J'*4J2<DD*.."M$vjyj'99MIFR9	S 	S 	S 	S 	S 	S 	S 	S 	S 	S 	S 	S 	S 	S 	S 	S 	S 	Ss   (FAF'C FFF)__name__
__module____qualname____doc__r   r"   propertyr(   r*   strr/   rL    r#   r!   r   r   6   s        CC	&# 	& 	& 	& 	& 7 7 7 7 X7 %S % % % X%#/3 #/4 #/ #/ #/ #/J ') S S S SS S S S S S Sr#   r   cbr$   c                     | t           _        dS )z>Register a callback that _wait_for_process fires periodically.N)_activity_callback_localcallback)rT   s    r!   set_activity_callbackrX      s    (*%%%r#   c                  .    t          t          dd           S )NrW   )getattrrV   rS   r#   r!   _get_activity_callbackr[      s    +Z>>>r#   statelabelc                    t          j                    }|                     dd          }|| d         z
  |k     rdS || d<   	 t                      }|r+t	          || d         z
            } || d| d           dS dS # t
          $ r Y dS w xY w)a_  Fire the activity callback at most once every ``state['interval']`` seconds.

    *state* must contain ``last_touch`` (monotonic timestamp) and ``start``
    (monotonic timestamp of the operation start).  An optional ``interval``
    key overrides the default 10 s cadence.

    Swallows all exceptions so callers don't need their own try/except.
    intervalg      $@
last_touchNr3   z (z
s elapsed))time	monotonicgetr[   r   	Exception)r\   r]   nowr_   rT   elapseds         r!   touch_activity_if_duerg      s     .

CyyT**H
U<  8++E,#%% 	0#g.//GB%..7.../////	0 	0    s   9A> >
BBc                      t          j        d          } | rt          |           }nt                      dz  }|                    dd           |S )zReturn the host-side root for all sandbox storage (Docker workspaces,
    Singularity overlays/SIF cache, etc.).

    Configurable via TERMINAL_SANDBOX_DIR. Defaults to {HERMES_HOME}/sandboxes/.
    TERMINAL_SANDBOX_DIR	sandboxesTparentsexist_ok)osgetenvr   r
   mkdir)customps     r!   get_sandbox_dirrs      sT     Y-..F ,LL+GGD4G(((Hr#   procdatac                 f      fd}t          j        |d                                           dS )u  Write *data* to proc.stdin on a daemon thread to avoid pipe-buffer deadlocks.

    On Windows, text-mode stdin (``text=True`` / ``encoding="utf-8"``)
    translates ``\n`` → ``\r\n`` as the data flows through the pipe —
    which corrupts every write_file / patch call because the bytes that
    land on disk include injected carriage returns.  The file IS created,
    but every subsequent byte-count / content compare against the
    caller's ``\n``-only string fails.

    Workaround: write through ``proc.stdin.buffer`` (the underlying byte
    buffer), encoding to UTF-8 ourselves.  That bypasses Python's
    newline translation entirely on every platform.  No behaviour change
    on POSIX — the byte sequence is identical to what text-mode would
    produce there.
    c                     	 t          t                    r                    d          n} t          j        dj                  }|                    |            |                                 d S # t          t          f$ r Y d S w xY w)Nutf-8buffer)	
isinstancerR   encoderZ   stdinwritecloseBrokenPipeErrorOSError)rawtargetru   rt   s     r!   _writez_pipe_stdin.<locals>._write   s    	 +5T3*?*?I$++g&&&TCTZ4:>>FLLLLNNNNN) 	 	 	DD	s   A0A5 5B
	B
Tr   daemonN)r   Threadr3   )rt   ru   r   s   `` r!   _pipe_stdinr      sL    "      F40006688888r#   cmd
stdin_datac                     |                     dt                                 t          j        | ft          j        t          j        |t          j        nt          j        dd|}|t          ||           |S )a  Spawn a subprocess with standard stdout/stderr/stdin setup.

    If *stdin_data* is provided, writes it asynchronously via :func:`_pipe_stdin`.
    Backends with special Popen needs (e.g. local's ``preexec_fn``) can bypass
    this and call :func:`_pipe_stdin` directly.
    creationflagsNT)stdoutstderrr|   r+   )
setdefaultr   
subprocessPopenPIPESTDOUTDEVNULLr   )r   r   kwargsrt   s       r!   _popen_bashr      s     o'9';';<<< !+!7jooZ=O   D D*%%%Kr#   pathc                     |                                  r9	 t          j        |                     d                    S # t          $ r Y nw xY wi S )z:Load a JSON file as a dict, returning ``{}`` on any error.rx   encoding)existsjsonloads	read_textrd   )r   s    r!   _load_json_storer     sZ    {{}} 	:dnngn>>??? 	 	 	D	Is   '> 
A
Ac                     | j                             dd           |                     t          j        |d          d           dS )z.Write *data* as pretty-printed JSON to *path*.Trk      )indentrx   r   N)parentrp   
write_textr   dumps)r   ru   s     r!   _save_json_storer     sH    KdT222OODJtA...OAAAAAr#   	host_pathc                     	 t          |                                           }|j        |j        fS # t          $ r Y dS w xY w)zIReturn ``(mtime, size)`` for cache comparison, or ``None`` if unreadable.N)r   statst_mtimest_sizer   )r   sts     r!   _file_mtime_keyr     sO    )__!!##RZ((   tts   .1 
??c                       e Zd ZdZdedz  fdZd
dZddedz  defdZe	de
e         dz  fd            Ze	dedz  fd	            ZdS )ProcessHandlezDuck type that every backend's _run_bash() must return.

    subprocess.Popen satisfies this natively.  SDK backends (Modal, Daytona)
    return _ThreadedProcessHandle which adapts their blocking calls.
    r$   Nc                     d S r&   rS   r'   s    r!   pollzProcessHandle.poll(        r#   c                     d S r&   rS   r'   s    r!   killzProcessHandle.kill)  r   r#   timeoutc                     d S r&   rS   r    r   s     r!   waitzProcessHandle.wait*  r   r#   c                     d S r&   rS   r'   s    r!   r   zProcessHandle.stdout,      (+r#   c                     d S r&   rS   r'   s    r!   
returncodezProcessHandle.returncode/  r   r#   r$   Nr&   )rM   rN   rO   rP   r   r   r   floatr   rQ   r   rR   r   r   rS   r#   r!   r   r   !  s          &cDj%%%%<<EDL<C<<<<+3$+++ X++C$J+++ X+++r#   r   c                       e Zd ZdZ	 ddeg eeef         f         deg df         dz  fdZe	d             Z
e	dedz  fd            Zdedz  fd	Zd
 Zddedz  defdZdS )_ThreadedProcessHandleal  Adapter for SDK backends (Modal, Daytona) that have no real subprocess.

    Wraps a blocking ``exec_fn() -> (output_str, exit_code)`` in a background
    thread and exposes a ProcessHandle-compatible interface.  An optional
    ``cancel_fn`` is invoked on ``kill()`` for backend-specific cancellation
    (e.g. Modal sandbox.terminate, Daytona sandbox.stop).
    Nexec_fn	cancel_fnc                 8    | _         t          j                     _        d  _        d  _        t          j                    \  }}t          j        |ddd           _	        | _
         fd}t          j        |d          }|                                 d S )Nrrx   replace)r   errorsc                     	              \  } }|_         	 t          j        j        |                     dd                     n# t
          $ r Y nw xY wn%# t          $ r}|_        d_         Y d }~nd }~ww xY w	 t          j        j                   n# t
          $ r Y nw xY wj	        
                                 d S # 	 t          j        j                   n# t
          $ r Y nw xY wj	        
                                 w xY w)Nrx   r   r   r   )_returncodern   r}   	_write_fdr{   r   rd   _errorr~   _doneset)output	exit_codeexcr   r    s      r!   _workerz0_ThreadedProcessHandle.__init__.<locals>._workerK  sO   !$+GII!	#, HT^V]]79]-U-UVVVV   D % % %!#$      %HT^,,,,   D
     	HT^,,,,   D
    s   A /A A 
AA AA C 
A;#A61C 6A;;C ?B 
B&%B&D
C D
 
C-*D
,C--D
Tr   )
_cancel_fnr   Eventr   r   r   rn   pipefdopen_stdoutr   r   r3   )r    r   r   read_fdwrite_fdr   ts   ``     r!   r"   z_ThreadedProcessHandle.__init__<  s    
 $_&&
'+(, GIIy#	RRR!	! 	! 	! 	! 	! 	!& GD999						r#   c                     | j         S r&   )r   r'   s    r!   r   z_ThreadedProcessHandle.stdouta  s
    |r#   r$   c                     | j         S r&   )r   r'   s    r!   r   z!_ThreadedProcessHandle.returncodee  s    r#   c                 F    | j                                         r| j        nd S r&   )r   is_setr   r'   s    r!   r   z_ThreadedProcessHandle.polli  s#    #':#4#4#6#6@tD@r#   c                 d    | j         r(	 |                                   d S # t          $ r Y d S w xY wd S r&   )r   rd   r'   s    r!   r   z_ThreadedProcessHandle.killl  sS    ? 	!!!!!   	 	s    
--r   c                 F    | j                             |           | j        S )Nr   )r   r   r   r   s     r!   r   z_ThreadedProcessHandle.waits  s!    
(((r#   r&   )rM   rN   rO   rP   r   tuplerR   r   r"   rQ   r   r   r   r   r   r   rS   r#   r!   r   r   3  s         04# #"eCHo-.# BH%,# # # #J   X  C$J       X AcDj A A A A     EDL  C            r#   r   
session_idc                     d|  dS )N__HERMES_CWD___rS   )r   s    r!   _cwd_markerr   }  s    ):))))r#   c                      e Zd ZU dZdZeed<   dZeed<   defdZ	d-d	ed
ede
fdZdddddeded
ededz  def
dZed             Zd Zed	edefd            ZdedefdZded	edefdZedededefd            Z	 d.ddded
edede
fdZdefd Zd!e
fd"Zd!e
fd#Zd/d$Z	 d0ddd&dd'ded	ed
edz  dedz  d(edede
fd)Zd* Zd+ Zdedeeedz  f         fd,Z dS )1BaseEnvironmenta  Common interface and unified execution flow for all Hermes backends.

    Subclasses implement ``_run_bash()`` and ``cleanup()``.  The base class
    provides ``execute()`` with session snapshot sourcing, CWD tracking,
    interrupt handling, and timeout enforcement.
    r   _stdin_mode   _snapshot_timeoutr$   c                     dS )a,  Return the backend temp directory used for session artifacts.

        Most sandboxed backends use ``/tmp`` inside the target environment.
        LocalEnvironment overrides this on platforms like Termux where ``/tmp``
        may be missing and ``TMPDIR`` is the portable writable location.
        z/tmprS   r'   s    r!   get_temp_dirzBaseEnvironment.get_temp_dir  s	     vr#   Ncwdr   envc                 f   || _         || _        |pi | _        t          j                    j        d d         | _        |                                                     d          pd}| d| j         d| _	        | d| j         d| _
        t          | j                  | _        d| _        d| _        d S )N   /z/hermes-snap-z.shz/hermes-cwd-z.txtF)r   r   r   uuiduuid4hex_session_idr   rstrip_snapshot_path	_cwd_filer   _snapshot_ready_prefer_nonlogin)r    r   r   r   temp_dirs        r!   r"   zBaseEnvironment.__init__  s    9":<<+CRC0$$&&--c229c!)MM8HMMM$HH$2BHHH&t'788$ !&r#   Fx   loginr   r   
cmd_stringr   r   c                J    t          t          |           j         d          )zSpawn a bash process to run *cmd_string*.

        Returns a ProcessHandle (subprocess.Popen or _ThreadedProcessHandle).
        Must be overridden by every backend.
        z must implement _run_bash())NotImplementedErrortyperM   )r    r   r   r   r   s        r!   	_run_bashzBaseEnvironment._run_bash  s$     "T$ZZ%8"U"U"UVVVr#   c                     dS )z<Release backend resources (container, instance, connection).NrS   r'   s    r!   cleanupzBaseEnvironment.cleanup  s	     	r#   c                 <   |                      | j                  }|                     | j                  }|                     | j        dz             dz   }d| d| d| d| d| d| d	| d
| d| d| d| j         d| j         d}	 |                     |d| j                  }|                     || j                  }t          |	                    d          pd          dk    r%t          d|	                    d                     d| _        |                     |           t                              d| j        | j                   dS # t           $ r?}d| _        t#          |          }d}		 |                     ddt%          d| j                            }
|                     |
t%          d| j                            }t          |	                    d          pd          dk    }	|	s+|	                    d          p|                                p|}n# t           $ r}| d| }Y d}~nd}~ww xY w|	| _        |	r"t                              d| j        |           n't                              d| j        |           Y d}~dS Y d}~dS d}~ww xY w)zCapture login shell environment into a snapshot file.

        Called once after backend construction.  On success, sets
        ``_snapshot_ready = True`` so subsequent commands source the snapshot
        instead of running with ``bash -l``.
        .tmp.$BASHPIDzumask 077
export -p > z
__hermes_fns=$(declare -F | awk '{print $3}' | grep -vE '^_[^_]') || true
[ -n "$__hermes_fns" ] && declare -f $__hermes_fns >> z! 2>/dev/null || true
alias -p >> z#
echo 'shopt -s expand_aliases' >> z
echo 'set +e' >> z
echo 'set +u' >> z
mv -f  z
 || rm -f z
builtin cd -- z 2>/dev/null || true
printf '\n%sz\n' "$(pwd -P)"
T)r   r   r   r   r   z)snapshot bootstrap failed with exit code z-Session snapshot created (session=%s, cwd=%s)Ftrue   r   z; non-login probe: Nu_   init_session failed (session=%s): %s — login bash unusable; falling back to non-login bash -cuL   init_session failed (session=%s): %s — falling back to bash -l per command)_quote_cwd_for_cdr   _quote_shell_pathr   r   r  r   _wait_for_processr   rc   RuntimeErrorr   _update_cwdloggerinfor   rd   rR   r.   stripr   warning)r    _quoted_cwd_quoted_snap	_snap_tmp	bootstraprt   resultr   detailprefer_nonloginprobeprobe_result	probe_excs                r!   init_sessionzBaseEnvironment.init_session  s     ,,TX66 --d.ABB$ **4+>+HIIJV	U$U U  !U U" %#U U$ 2;%U U& !*'U U( !*)U U. /U U. "./U U. :C/U U0 )1U U2 *3U U2 /3.>3U U U 	8-	>>)4AW>XXD++D$:P+QQF6::l++0q11Q66"Z

<@X@XZZ   $(D V$$$KK?     
  	 	 	#(D 
 XXF#OCvUCDLbDcDcdd#55eSTMcEdEd5ee"%l&6&6|&D&D&I"J"Ja"O& V*..x88BFIIKKUvF C C C"BByBBC %4D! M$	    :$	             '	sE   CE JJ5B*H J 
H;*H61J6H;;AJJc                     | dk    r| S | dk    rdS |                      d          rdt          j        | dd                    S t          j        |           S )z7Quote a ``cd`` target while preserving ``~`` expansion.~z~/z$HOMEz$HOME/r   N)
startswithshlexquote)r   s    r!   r  z!BaseEnvironment._quote_cwd_for_cdB  sd     #::J$;;7>>$ 	32EKABB00222{3r#   r   c                 *    t          j        |          S )zQuote *path* for interpolation into a bash script.

        LocalEnvironment overrides this to rewrite native/mixed Windows
        paths to ``/c/...`` before quoting. Remote backends leave paths
        as-is (they already speak POSIX).
        )r$  r%  )r    r   s     r!   r  z!BaseEnvironment._quote_shell_pathM  s     {4   r#   commandc                    |                     dd          }|                     | j                  }|                     | j        dz             dz   }g }| j        r|                    d| d           |                     |          }|                    d| d           |                    d	| d           |                    d
           |                    d           | j        r"|                    d| d| d| d| d	           |                    d| j         d| j         d           |                    d           d                    |          S )zwBuild the full bash script that sources snapshot, cd's, runs command,
        re-dumps env vars, and emits CWD markers.'z'\''r  r  zsource z >/dev/null 2>&1 || truezbuiltin cd -- z || exit 126zeval 'z__hermes_ec=$?z	umask 077z{ export -p > z
 && mv -f r	  z; } 2>/dev/null || rm -f z 2>/dev/null || truez
printf '\nr
  z\n' "$(pwd -P)"zexit $__hermes_ec
)r   r  r   r   r/   r  r   r?   )r    r'  r   escapedr  r  parts
quoted_cwds           r!   _wrap_commandzBaseEnvironment._wrap_commandV  s    //#w// --d.ABB **4+>+HIIJV	  	LL@,@@@   ++C00
>j>>>??? 	(g((()))%&&& 	[!!!  	LLH) H Hy H H< H H(1H H H   	R$*RRd.>RRR	
 	
 	
 	()))yyr#   c                 b    dt          j                    j        dd          }|  d| d| d| S )z;Append stdin_data as a shell heredoc to the command string.HERMES_STDIN_Nr   z << 'z'
r*  )r   r   r   )r'  r   	delimiters      r!   _embed_stdin_heredocz$BaseEnvironment._embed_stdin_heredoc  sG     <DJLL$4SbS$9;;	GG	GGjGGIGGGr#   )bounded_capturert   r3  c          
      ~	   |r$	 ddl m}  |            }n# t          $ r d}Y nw xY wt          }t	          |           t          j        d          d          fdfd}t          j        |d	
          }|	                                 t          j                    |z   }t          j                    }	|	|	d}
t          j                    j        }t          dd          }d}|	}d}t                      du }t           r/t"                              d||||sdndt'                                 	 d}                                8|dz  }t'                      rt           r8t"                              d|||t          j                    |
d         z
             |                                |                    d                               d          ddS t          j                    |k    rt           rt"                              d||||           |                                |                    d           d| d}j        dk    r(                    |                                          n                    |          ddS t5          |
d            t           rt          j                    |z
  d!k    rvt                      du }t"                              d"|||t          j                    |
d         z
  t'                      |sdnd|r|sd#nd$           t          j                    }|}t          j        |           |d%k     rt9          |d&z  d%          }                                8n# t:          t<          f$ r~ t           r8t"                              d'|||t          j                    |
d         z
             	 |                                |                    d           n# t          $ r Y nw xY w w xY w|                    d           	 j                                          n# t          $ r Y nw xY wt           r>t"                              d(|||t          j                    |
d         z
  j!                                                   j!        dS ))u  Poll-based wait with interrupt checking and stdout draining.

        Shared across all backends — not overridden.

        ``bounded_capture=True`` (foreground terminal-tool path only) retains
        at most ``tool_output.max_bytes`` of output in a head/tail window
        while draining, so a verbose subprocess cannot OOM the process
        (#64435). The default (False) preserves full-fidelity capture for
        internal consumers — file-operation ``cat`` reads feeding the patch
        engine, code-execution RPC reads, log reads — where truncation would
        corrupt data.

        Fires the ``activity_callback`` (if set on this instance) every 10s
        while the process is running so the gateway's inactivity timeout
        doesn't kill long-running commands.

        Also wraps the poll loop in a ``try/finally`` that guarantees we
        call ``self._kill_process(proc)`` if we exit via ``KeyboardInterrupt``
        or ``SystemExit``.  Without this, the local backend (which spawns
        subprocesses with ``os.setsid`` into their own process group) leaves
        an orphan with ``PPID=1`` when python is shut down mid-tool — the
        ``sleep 300``-survives-30-min bug Physikal and I both hit.
        r   )get_max_bytesiP  rx   r   r   c                 
   	 | D ]e}|t          |t                    r)                                        |                     C                    t	          |                     fn# t
          $ r Y nw xY w	                     dd          }|r                    |           d S d S # t
          $ r Y d S w xY w# 	                     dd          }|r                    |           w w # t
          $ r Y w w xY wxY w)Nr#   Tfinal)rz   bytesr/   decoderR   rd   )streampiecerB   decoderr   s      r!   _drain_iterablez:BaseEnvironment._wait_for_process.<locals>._drain_iterable  sZ   # 2 2E} !%// 2gnnU&;&;<<<<c%jj11112    ">>#T>::D ,d+++++, ,    DD	">>#T>::D ,d++++,    Ds`   A(A, +C  ,
A96C  8A99C  =.B/ /
B=<B= D.C20D2
C?<D>C??Dc                  P   j         } | d S t          | dd           }	 t          |          r
 |            nd }n# t          $ r d }Y nw xY wt	          |t
                    r|dk     r |            d S t          j        dk    r	 	 t          j        |d          }|sn)
	                    	
                    |                     An# t          t          f$ r Y nw xY w	 	
                    dd          }|r
	                    |           nU# t          $ r Y nIw xY w# 	 	
                    dd          }|r
	                    |           w w # t          $ r Y w w xY wxY wd S d}	 	 	 t          j        |gg g d          \  }}}n# t          t          f$ r Y nw xY w|r\	 t          j        |d          }n# t          t          f$ r Y nSw xY w|snL
	                    	
                    |                     d}n                                 |d	z  }|d
k    rn	 	
                    dd          }|r
	                    |           d S d S # t          $ r Y d S w xY w# 	 	
                    dd          }|r
	                    |           w w # t          $ r Y w w xY wxY w)Nfilenor   ntT   r#   r7  g?r      )r   rZ   callablerd   rz   r   rn   namereadr/   r:  
ValueErrorr   selectr   )r;  r@  fdr6   rB   idle_after_exitreadyrE   r>  r=  r   rt   s           r!   _drainz1BaseEnvironment._wait_for_process.<locals>._drain  s    [F~VXt44F!)&!1!1;VVXXXt   b#&& "q&&''' w$= "D 1 1$ "!gnnU&;&;<<<	= ""G,   D&~~c~>> 0"MM$///$   	&~~c~>> 0"MM$////0$   O"&,mRD"b#&F&Fq!!&0    ""$&GB$5$5EE *G4 " " "!E"$ "!gnnU&;&;<<<*+0 (1,*a//!)"2">>#T>::D ,d+++++, ,    DD	">>#T>::D ,d++++,    Ds   ; A
	A
AC	 D  	CD  CD  !.D 
DD E"".EE"
EE"EE"*I# ,F	 I# 	FI# FI# #F9 8I# 9G
I# GAI#  .I 
I I #J%%.JJ%
J"J%!J""J%Tr   )r`   r3   pidNFzg[interrupt-debug] _wait_for_process ENTER tid=%s pid=%s timeout=%ss activity_cb=%s initial_interrupt=%sr   MISSINGg{Gzt?r   ut   [interrupt-debug] _wait_for_process INTERRUPT DETECTED tid=%s pid=%s iter=%d elapsed=%.1fs — killing process groupr3   r   r   z
[Command interrupted]r:      )r   r   zM[interrupt-debug] _wait_for_process TIMEOUT tid=%s pid=%s iter=%d timeout=%ssz
[Command timed out after zs]|   zterminal command runningg      >@zo[interrupt-debug] _wait_for_process HEARTBEAT tid=%s pid=%s iter=%d elapsed=%.0fs interrupt=%s activity_cb=%s%sz (LOST during run)r9   g?g      ?u   [interrupt-debug] _wait_for_process EXCEPTION_EXIT tid=%s pid=%s iter=%d elapsed=%.1fs — killing subprocess group before re-raisezd[interrupt-debug] _wait_for_process EXIT (natural) tid=%s pid=%s iter=%d elapsed=%.1fs returncode=%s)"tools.tool_output_limitsr5  rd   _UNBOUNDED_CAPTURE_CHARSr   codecsgetincrementaldecoderr   r   r3   ra   rb   current_threadidentrZ   r[   _DEBUG_INTERRUPTr  r  r   r   _kill_processr?   rL   r*   lstriprg   sleepr.   KeyboardInterrupt
SystemExitr   r~   r   )r    rt   r   r3  r5  capture_limitrL  drain_threaddeadline_now_activity_state_tid_pid_iter_count_last_heartbeat_last_interrupt_state_cb_was_none_poll_sleeptimeout_msg_cb_now_noner>  r=  r   s    `                  @@@r!   r  z!BaseEnvironment._wait_for_process  s   4  	5'BBBBBB - ' ' ' &' 5M(774 8&.w77yIII	 	 	 	 	 	4F	 F	 F	 F	 F	 F	 F	 F	P !'vdCCC>##g-~
 
 '))/tUD)) %-//47 	KKBdG)8y    U	K))++%q !## ' \ $T^5E5EX_H`5`  
 &&t,,, %%a%000"(--7P-"Q"Q&)   >##h..' @ $W  
 &&t,,, %%a%000"K"K"K"KK "-22 #)--{-"C"C"J"J"L"L"L#]]+]>>&)	   &o7QRRR
 $ 0(8(8?(Jd(R(R#9#;#;t#CLKK8 dK((?7+CC&((%1@y0<Y\Y,,WY	 	 	 '+n&6&6O#/L 
;'''$$"%kC&7"="=K} ))++%~ ":. 	 	 	   g$N$$w'??	  ""4(((!!!!,,,,   )	2 	!$$$	K 	 	 	D	  	KKDdK  ?7#;;   !--//IIIs^    ((;B,N (B8N !C)N AP+PP
PPPP4Q 
QQc                 l    	 |                                  dS # t          t          t          f$ r Y dS w xY w)zDTerminate a process. Subclasses may override for process-group kill.N)r   ProcessLookupErrorPermissionErrorr   )r    rt   s     r!   rX  zBaseEnvironment._kill_process  sA    	IIKKKKK"OW= 	 	 	DD	s    33r  c                 0    |                      |           dS )zDExtract CWD from command output. Override for local file-based read.N)_extract_cwd_from_output)r    r  s     r!   r  zBaseEnvironment._update_cwd  s    %%f-----r#   c                 B   |                     dd          }| j        }|                    |          }|dk    rdS t          d|dz
            }|                    |||          }|dk    s||k    rdS ||t	          |          z   |                                         }|r|| _        |                    dd|          }|dk    r|}|                    d|t	          |          z             }	|	dk    r|	dz   nt	          |          }	|d|         ||	d         z   |d<   dS )	zParse the __HERMES_CWD_{session}__ marker from stdout output.

        Updates self.cwd and strips the marker from result["output"].
        Used by remote backends (Docker, SSH, Modal, Daytona, Singularity).
        r   r9   Nr   rB  r*  r   )rc   r   rfindr   r-   r  r   find)
r    r  r   markerlastsearch_startr8   cwd_path
line_startline_ends
             r!   ro  z(BaseEnvironment._extract_cwd_from_output  s:    Hb))!||F##2::F 1dTk**V\488B;;%4--F%#f++-45;;== 	 DH \\$511
J;;tTCKK%788#+r>>8a<<s6{{!+:+.		1BBxr#   c                     dS )u>  Hook called before each command execution.

        Remote backends (SSH, Modal, Daytona) override this to trigger
        their FileSyncManager.  Bind-mount backends (Docker, Singularity)
        and Local don't need file sync — the host filesystem is directly
        visible inside the container/process.
        NrS   r'   s    r!   _before_executezBaseEnvironment._before_execute  s	     	r#   r9   T)r   r   rewrite_compound_backgroundr3  r|  c                   |                                   |                     |          \  }}|rddlm}	  |	|          }|p| j        }
|p| j        }||||z   }n||}n|}|r#| j        dk    r|                     ||          }d}|                     ||          }| j	         o| j
         }|                     |||
|          }|                     ||
|          }|                     |           |S )u  Execute a command, return {"output": str, "returncode": int}.

        ``bounded_capture=True`` caps stdout/stderr retention at
        ``tool_output.max_bytes`` WHILE the stream is drained (head/tail
        window) instead of holding the full output in memory (#64435).
        It must only be set by callers whose output is destined for the
        model/tool payload (the foreground terminal tool). Internal
        full-fidelity consumers — file operations ``cat`` reads that feed
        the patch engine, code-execution RPC reads, log reads — MUST leave
        it False: truncating those corrupts data, not just display.
        r   )_rewrite_compound_backgroundNheredocr   )r   r3  )r{  _prepare_commandtools.terminal_toolr~  r   r   r   r2  r.  r   r   r  r  r  )r    r'  r   r   r   r|  r3  exec_command
sudo_stdinr~  effective_timeouteffective_cwdeffective_stdinwrappedr   rt   r  s                    r!   executezBaseEnvironment.execute  s]   * 	#'#8#8#A#A j ' 	FHHHHHH77EEL#3t|tx !j&<(:5OO#(OO(O  	#t/9<<44\?SSL"O$$\=AA ((F1F-F~~5*;  
 
 ''+_ ( 
 
 	   r#   c                 .    |                                   dS )z.Alias for cleanup (compat with older callers).N)r  r'   s    r!   stopzBaseEnvironment.stopW  s    r#   c                 R    	 |                                   d S # t          $ r Y d S w xY wr&   )r  rd   r'   s    r!   __del__zBaseEnvironment.__del__[  s:    	LLNNNNN 	 	 	DD	s    
&&c                 $    ddl m}  ||          S )z6Transform sudo commands if SUDO_PASSWORD is available.r   )_transform_sudo_command)r  r  )r    r'  r  s      r!   r  z BaseEnvironment._prepare_commanda  s%    ??????&&w///r#   r&   )r   r   )r9   )!rM   rN   rO   rP   r   rR   __annotations__r   r   r   dictr"   boolr   r  r   r  r   staticmethodr  r  r.  r2  r  rX  r  ro  r{  r  r  r  r   r  rS   r#   r!   r   r     s~          K  sc    & &C &# &D & & & &0 !%W W WW 	W
 W $JW 
W W W W   ^s s sr  s  s       \ !c !c ! ! ! !@ S @ s @ s @  @  @  @ L Hc Hs Hs H H H \H 36jJRWjJ jJ jJ!jJ,/jJKOjJ	jJ jJ jJ jJX	-    .$ . . . . Ct  C  C  C  CL   " <
 #!%,0 %< < << <
 t< $J< &*< < 
< < < <D    0 0c3:o0F 0 0 0 0 0 0r#   r   r&   )<rP   rS  r   loggingrn   rH  r$  r   r   ra   r   abcr   r   collectionsr   pathlibr   typingr   r   r	   hermes_constantsr
   hermes_cli._subprocess_compatr   tools.interruptr   	getLoggerrM   r  r  ro   rW  setLevelINFOlocalrV   rR  r   rR   rX   r[   r  rg   rs   r   r   listr   r   r   r   r   r   r   r   r   r   r   rS   r#   r!   <module>r     s       				             # # # # # # # #             ) ) ) ) ) ) ) ) ) ) , , , , , , < < < < < < * * * * * *		8	$	$ 4		":;;<<  "
 OOGL!!! +9?,,  % ZS ZS ZS ZS ZS ZS ZS ZSz+hud{3d: +t + + + +
?# 5 < ? ? ? ? 
   4    (9j& 9c 9d 9 9 9 9F .2 	c #d
   .4 D    B4 Bt B B B B Bs uUCZ'84'?    , , , , ,H , , ,$B  B  B  B  B  B  B  B T*C *C * * * *_0 _0 _0 _0 _0c _0 _0 _0 _0 _0r#   