
    iR                     :   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	m
Z
mZ ddlmZ ddlmZmZmZ ddlmZ ddlmZ  ej        e          Z ej                    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e#e         dedz  d	ej!        fdZ$ded	efdZ%deded	dfdZ&ded	e'e(e)f         dz  fdZ* G d de          Z+ G d d          Z,d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)Path)IOCallableProtocol)get_hermes_home)is_interruptedcbreturnc                     | t           _        dS )z>Register a callback that _wait_for_process fires periodically.N)_activity_callback_localcallback)r   s    ?/home/agentuser/.hermes/hermes-agent/tools/environments/base.pyset_activity_callbackr      s    (*%%%    c                  .    t          t          dd           S )Nr   )getattrr    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_touchNstartz (z
s elapsed))time	monotonicgetr   int	Exception)r   r   nowr   r   elapseds         r   touch_activity_if_duer$   (   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_dirr0   B   sT     Y-..F ,LL+GGD4G(((Hr   procdatac                 f      fd}t          j        |d                                           dS )zMWrite *data* to proc.stdin on a daemon thread to avoid pipe-buffer deadlocks.c                      	 j                                         j                                          d S # t          t          f$ r Y d S w xY wN)stdinwritecloseBrokenPipeErrorOSError)r2   r1   s   r   _writez_pipe_stdin.<locals>._writeY   s_    	JT"""J) 	 	 	DD	s   38 AATtargetdaemonN)	threadingThreadr   )r1   r2   r;   s   `` r   _pipe_stdinrA   V   sL          F40006688888r   cmd
stdin_datac                     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.
    NT)stdoutstderrr6   text)
subprocessPopenPIPESTDOUTDEVNULLrA   )rB   rC   kwargsr1   s       r   _popen_bashrN   c   si      !+!7jooZ=O   D D*%%%Kr   pathc                     |                                  r7	 t          j        |                                           S # t          $ r Y nw xY wi S )z:Load a JSON file as a dict, returning ``{}`` on any error.)existsjsonloads	read_textr!   )rO   s    r   _load_json_storerU   y   sV    {{}} 	:dnn../// 	 	 	D	Is   %< 
A	A	c                     | j                             dd           |                     t          j        |d                     dS )z.Write *data* as pretty-printed JSON to *path*.Tr(      )indentN)parentr-   
write_textrR   dumps)rO   r2   s     r   _save_json_storer\      sD    KdT222OODJtA.../////r   	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_keyrc      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 r5   r   selfs    r   pollzProcessHandle.poll         r   c                     d S r5   r   rg   s    r   killzProcessHandle.kill   rj   r   timeoutc                     d S r5   r   rh   rm   s     r   waitzProcessHandle.wait   rj   r   c                     d S r5   r   rg   s    r   rE   zProcessHandle.stdout       (+r   c                     d S r5   r   rg   s    r   
returncodezProcessHandle.returncode   rr   r   r   Nr5   )__name__
__module____qualname____doc__r    ri   rl   floatrp   propertyr   strrE   rt   r   r   r   re   re      s          &cDj%%%%<<EDL<C<<<<+3$+++ X++C$J+++ X+++r   re   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rutf-8replace)encoding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)Nr   r   )r      )_returncoder+   r7   	_write_fdencoder:   r!   _errorr8   _doneset)output	exit_codeexcr   rh   s      r   _workerz0_ThreadedProcessHandle.__init__.<locals>._worker   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   r+   pipefdopen_stdoutr   r@   r   )rh   r   r   read_fdwrite_fdr   ts   ``     r   __init__z_ThreadedProcessHandle.__init__   s    
 $_&&
'+(, GIIy#	RRR!	! 	! 	! 	! 	! 	!& GD999						r   c                     | j         S r5   )r   rg   s    r   rE   z_ThreadedProcessHandle.stdout   s
    |r   r   c                     | j         S r5   )r   rg   s    r   rt   z!_ThreadedProcessHandle.returncode   s    r   c                 F    | j                                         r| j        nd S r5   )r   is_setr   rg   s    r   ri   z_ThreadedProcessHandle.poll   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 r5   )r   r!   rg   s    r   rl   z_ThreadedProcessHandle.kill   sS    ? 	!!!!!   	 	s    
--rm   c                 F    | j                             |           | j        S )Nrm   )r   rp   r   ro   s     r   rp   z_ThreadedProcessHandle.wait   s!    
(((r   r5   )rv   rw   rx   ry   r   tupler|   r    r   r{   rE   rt   ri   rl   rz   rp   r   r   r   r~   r~      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___r   )r   s    r   _cwd_markerr      s    ):))))r   c                   z   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ded	edefdZedededefd            Zd'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	 d)ddd!ded	ed
edz  dedz  de
f
d"Zd# Zd$ Zdedeeedz  f         fd%ZdS )*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/tmpr   rg   s    r   get_temp_dirzBaseEnvironment.get_temp_dir
  s	     vr   Ncwdrm   envc                 X   || _         || _        |pi | _        t          j                    j        d d         | _        |                                                     d          pd}| d| j         d| _	        | d| j         d| _
        t          | j                  | _        d| _        d S )N   /z/hermes-snap-z.shz/hermes-cwd-z.txtF)r   rm   r   uuiduuid4hex_session_idr   rstrip_snapshot_path	_cwd_filer   _snapshot_ready)rh   r   rm   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   loginrm   rC   
cmd_stringr   rC   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typerv   )rh   r   r   rm   rC   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).Nr   rg   s    r   cleanupzBaseEnvironment.cleanup2  s	     	r   c                    d| j          d| j          d| j          d| j          d| j          d| j          d| j         d| j         d	| j         d
}	 |                     |d| j                  }|                     || j                  }d| _        |                     |           t          	                    d| j
        | j                   dS # t          $ r3}t                              d| j
        |           d| _        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``.
        export -p > z#
declare -f | grep -vE '^_[^_]' >> z
alias -p >> z#
echo 'shopt -s expand_aliases' >> z
echo 'set +e' >> z
echo 'set +u' >> z

pwd -P > z 2>/dev/null || true
printf '\n%sz\n' "$(pwd -P)"
T)r   rm   r   z-Session snapshot created (session=%s, cwd=%s)uL   init_session failed (session=%s): %s — falling back to bash -l per commandFN)r   r   r   r   r   _wait_for_processr   _update_cwdloggerinfor   r   r!   warning)rh   	bootstrapr1   resultr   s        r   init_sessionzBaseEnvironment.init_session;  s   U4. U U151DU U.U U 261DU U !% 3	U U
 !% 3U U U U *U U /3.>U U U 		)>>)4AW>XXD++D$:P+QQF#'D V$$$KK?     
  	) 	) 	)NN6 	   $)D       	)s   A;C
 

D(DDcommandc                    |                     dd          }g }| j        r|                    d| j         d           |dk    r)|                    d          st          j        |          n|}|                    d| d           |                    d	| d           |                    d
           | j        r|                    d| j         d           |                    d| j         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'\''zsource z 2>/dev/null || true~z~/zcd z || exit 126zeval 'z__hermes_ec=$?r   z	pwd -P > z
printf '\nr   z\n' "$(pwd -P)"zexit $__hermes_ec
)
r   r   appendr   
startswithshlexquoter   r   join)rh   r   r   escapedparts
quoted_cwds         r   _wrap_commandzBaseEnvironment._wrap_commandd  s{    //#w//  	NLLL4#6LLLMMM !$s

3>>$3G3G
EKS 	 	3:333444 	(g((()))%&&&  	SLLQ(;QQQRRR 	EEEEFFF
 	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   rC   	delimiters      r   _embed_stdin_heredocz$BaseEnvironment._embed_stdin_heredoc  sG     <DJLL$4SbS$9;;	GG	GGjGGIGGGr   r1   c                   
 g 

fd}t          j        |d          }|                                 t          j                    |z   }t          j                    }||d}                                t                      rF|                                |                    d           d                    
          d	z   d
dS t          j                    |k    rd|                                |                    d           d                    
          }d| d}	|r||	z   n|		                                ddS t          |d           t          j        d                                           |                    d           	 j                                         n# t          $ r Y nw xY wd                    
          j        dS )u?  Poll-based wait with interrupt checking and stdout draining.

        Shared across all backends — not overridden.

        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.
        c                      	 j         D ]}                     |            d S # t          $ r-                                                      d           Y d S t          t
          f$ r Y d S w xY w)Nu6   [binary output detected — raw bytes not displayable])rE   r   UnicodeDecodeErrorclear
ValueErrorr:   )lineoutput_chunksr1   s    r   _drainz1BaseEnvironment._wait_for_process.<locals>._drain  s    	 K / /D!((..../ /%   ##%%%$$L      (   s   $ 3A.A.-A.Tr<   )r   r   NrW   r    z
[Command interrupted]   )r   rt   z
[Command timed out after zs]|   zterminal command runningg?   )r?   r@   r   r   r   ri   r
   _kill_processr   lstripr$   sleeprE   r8   r!   rt   )rh   r1   rm   r   drain_threaddeadline_now_activity_statepartialtimeout_msgr   s    `        @r   r   z!BaseEnvironment._wait_for_process  s,    $&
	 
	 
	 
	 
	 
	 !'vdCCC>##g-~
 

 iikk! ""4(((!!!!,,, ggm447PP"%   ~(**""4(((!!!!,,,''-00GGGGG .g33$++--"%	   "/3MNNNJsOOO+ iikk!. 	!$$$	K 	 	 	D	 ''-00PPPs   F1 1
F>=F>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)rl   ProcessLookupErrorPermissionErrorr:   )rh   r1   s     r   r   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)rh   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   r   Nr   i   r   r   )r   r   rfindmaxlenstripr   find)
rh   r   r   markerlastsearch_startfirstcwd_path
line_startline_ends
             r   r   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.
        Nr   rg   s    r   _before_executezBaseEnvironment._before_execute  s	     	r   r   )rm   rC   c                   |                                   |                     |          \  }}|p| j        }|p| j        }||||z   }	n||}	n|}	|	r#| j        dk    r|                     ||	          }d}	|                     ||          }
| j         }|                     |
|||	          }| 	                    ||          }| 
                    |           |S )z=Execute a command, return {"output": str, "returncode": int}.Nheredocr   r   )r  _prepare_commandrm   r   r   r   r   r   r   r   r   )rh   r   r   rm   rC   exec_command
sudo_stdineffective_timeouteffective_cwdeffective_stdinwrappedr   r1   r   s                 r   executezBaseEnvironment.execute  s    	#'#8#8#A#A j#3t|tx !j&<(:5OO#(OO(O  	#t/9<<44\?SSL"O$$\=AA ((~~5*;  
 
 ''6G'HH   r   c                 .    |                                   dS )z.Alias for cleanup (compat with older callers).N)r   rg   s    r   stopzBaseEnvironment.stopH  s    r   c                 R    	 |                                   d S # t          $ r Y d S w xY wr5   )r   r!   rg   s    r   __del__zBaseEnvironment.__del__L  s:    	LLNNNNN 	 	 	DD	s    
&&c                 $    ddl m}  ||          S )z6Transform sudo commands if SUDO_PASSWORD is available.r   )_transform_sudo_command)tools.terminal_toolr  )rh   r   r  s      r   r  z BaseEnvironment._prepare_commandR  s%    ??????&&w///r   r5   )r   ru   )r   )rv   rw   rx   ry   r   r|   __annotations__r   r    r   dictr   boolre   r   r   r   r   r   staticmethodr   r   r   r   r   r  r  r  r  r   r  r   r   r   r   r      s          K  sc    
% 
%C 
%# 
%D 
% 
% 
% 
%( !%W W WW 	W
 W $JW 
W W W W   ^#) #) #)R$ S $ s $ s $  $  $  $ T Hc Hs Hs H H H \H>Q >Qm >Qc >QD >Q >Q >Q >Q@-    .$ . . . . Ct  C  C  C  CL   " '
 #!%' ' '' '
 t' $J' 
' ' ' 'Z    0 0c3:o0F 0 0 0 0 0 0r   r   r5   )/ry   rR   loggingr+   r   rH   r?   r   r   abcr   r   pathlibr   typingr   r   r   hermes_constantsr	   tools.interruptr
   	getLoggerrv   r   localr   r|   r   r   r!  r$   r0   rI   rA   listrN   rU   r\   r   rz   r    rc   re   r~   r   r   r   r   r   <module>r-     s8      				            # # # # # # # #       ) ) ) ) ) ) ) ) ) ) , , , , , , * * * * * *		8	$	$ +9?,, +hud{3d: +t + + + +
?# 5 < ? ? ? ? 
   4    (
9j& 
9c 
9d 
9 
9 
9 
9 .2 	c #d
   ,4 D    04 0t 0 0 0 0 0s uUCZ'84'?    , , , , ,H , , ,$B  B  B  B  B  B  B  B T*C *C * * * *Z0 Z0 Z0 Z0 Z0c Z0 Z0 Z0 Z0 Z0r   