
    PL
jDg                        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
mZ ddlmZmZ  ej                    dk    Z ej        e          ZdedefdZdedefd	Zd
ZdefdZ e            Zddedz  dedz  defdZdefdZeZdZdedefdZ de!e"e         e#f         fdZ$de"e         fdZ%dede"e         defdZ& G d de          Z'dS )uE   Local execution environment — spawn-per-call with session snapshot.    N)Path)BaseEnvironment_pipe_stdinWindowscwdreturnc                    t           r| s| S t          j        d|           }|s| S |                    d                                          }|                    d          pd                    dd          }| d|pt          d           S )	u  Translate a Git Bash / MSYS-style POSIX path (``/c/Users/x``) to the
    native Windows form (``C:\Users\x``) so ``os.path.isdir`` and
    ``subprocess.Popen(..., cwd=...)`` can find it.

    No-ops on non-Windows hosts or for paths that aren't in MSYS form.
    Returns the input unchanged when no translation applies. This is
    idempotent — calling it on an already-Windows path returns it as-is.
    z^/([a-zA-Z])(/.*)?$       /\:\   )_IS_WINDOWSrematchgroupupperreplacechr)r   mdrivetails       </home/kuhnn/.hermes/hermes-agent/tools/environments/local.py_msys_to_windows_pathr      s      c 

'--A 
GGAJJEGGAJJ"%%c400D''doc"gg'''    c                 ~   t           rt          |           n| } | r!t          j                            |           r| S | rt          j                            |           nd}|rKt          j                            |          r|S t          j                            |          }||k    rn|}|Kt          j                    S )ut  Return ``cwd`` if it exists as a directory, else the nearest existing
    ancestor.  Falls back to ``tempfile.gettempdir()`` only if walking up the
    path can't find any existing directory (effectively never on a healthy
    filesystem, but cheap belt-and-braces).

    On Windows, also normalizes Git Bash / MSYS-style POSIX paths
    (``/c/Users/x``) to native Windows form before the isdir check so a
    perfectly valid ``pwd -P`` result from bash doesn't get rejected as
    "missing" (see ``_msys_to_windows_path``).

    Used by ``_run_bash`` to recover when the configured cwd is gone — most
    commonly because a previous tool call deleted its own working directory
    (issue #17558).  Without this guard, ``subprocess.Popen(..., cwd=...)``
    raises ``FileNotFoundError`` before bash starts, wedging every subsequent
    terminal call until the gateway restarts.
    r   )r   r   ospathisdirdirnametempfile
gettempdir)r   parentnext_parents      r   _resolve_safe_cwdr'   )   s    " )4
<

$
$
$C
 rw}}S!! 
%(0RW__S!!!bF
 7==   	Mgoof--&         r   _HERMES_FORCE_c                  `   t                      } 	 ddlm} |                                D ]=}|                     |j                   |j        r|                     |j                   >n# t          $ r Y nw xY w	 ddl	m
} |                                D ]d\  }}|                    d          }|dv r|                     |           4|dk    r*|                    d          r|                     |           en# t          $ r Y nw xY w|                     h d           t          |           S )	z=Derive the blocklist from provider, tool, and gateway config.r   )PROVIDER_REGISTRY)OPTIONAL_ENV_VARScategory>   tool	messagingsettingpassword>?   GH_TOKENHASS_URL	LLM_MODEL
HASS_TOKENXAI_API_KEYGROQ_API_KEYVERCEL_TOKENEMAIL_ADDRESSGITHUB_APP_IDOPENAI_ORG_IDWHATSAPP_MODECOHERE_API_KEYEMAIL_PASSWORDGOOGLE_API_KEYMODAL_TOKEN_IDOPENAI_API_KEYSIGNAL_ACCOUNTVERCEL_TEAM_IDANTHROPIC_TOKENDAYTONA_API_KEYEMAIL_IMAP_HOSTEMAIL_SMTP_HOSTMISTRAL_API_KEYOPENAI_API_BASEOPENAI_BASE_URLSIGNAL_HTTP_URLDEEPSEEK_API_KEYHELICONE_API_KEYPARALLEL_API_KEYTOGETHER_API_KEYWHATSAPP_ENABLEDFIRECRAWL_API_KEYFIRECRAWL_API_URLFIREWORKS_API_KEYVERCEL_OIDC_TOKENVERCEL_PROJECT_IDANTHROPIC_BASE_URLEMAIL_HOME_ADDRESSMODAL_TOKEN_SECRETOPENROUTER_API_KEYPERPLEXITY_API_KEYSLACK_HOME_CHANNELDISCORD_AUTO_THREADOPENAI_ORGANIZATIONSIGNAL_HOME_CHANNELSLACK_ALLOWED_USERSDISCORD_HOME_CHANNELSIGNAL_ALLOWED_USERSGATEWAY_ALLOWED_USERSSIGNAL_IGNORE_STORIESTELEGRAM_HOME_CHANNELWHATSAPP_ALLOWED_USERSCLAUDE_CODE_OAUTH_TOKENDISCORD_REQUIRE_MENTIONEMAIL_HOME_ADDRESS_NAMESLACK_HOME_CHANNEL_NAMESIGNAL_HOME_CHANNEL_NAMEDISCORD_HOME_CHANNEL_NAMEGITHUB_APP_INSTALLATION_IDSIGNAL_GROUP_ALLOWED_USERSTELEGRAM_HOME_CHANNEL_NAMEGITHUB_APP_PRIVATE_KEY_PATHDISCORD_FREE_RESPONSE_CHANNELS)sethermes_cli.authr*   valuesupdateapi_key_env_varsbase_url_env_varaddImportErrorhermes_cli.configr+   itemsget	frozenset)blockedr*   pconfigr+   namemetadatar,   s          r   _build_provider_env_blocklistr   N   s   G555555(//11 	6 	6GNN73444' 6G4555	6    	777777/5577 	" 	"ND(||J//H000D!!!!Y&&8<<
+C+C&D!!!	"     NN @ @ @ @ @ @B Ws%   AA) )
A65A6:A?C: :
DDbase_env	extra_envc                    	 ddl m} n# t          $ r d }Y nw xY wi }| pi                                 D ]9\  }}|                    t
                    r |t          vs ||          r|||<   :|pi                                 D ]Z\  }}|                    t
                    r"|t          t
                    d         }|||<   A|t          vs ||          r|||<   [ddlm	}  |            }|r||d<   |S )z<Filter Hermes-managed secrets from a subprocess environment.r   is_env_passthroughc                     dS NF _s    r   <lambda>z*_sanitize_subprocess_env.<locals>.<lambda>       E r   Nget_subprocess_homeHOME)
tools.env_passthroughr   	Exceptionry   
startswith!_HERMES_PROVIDER_ENV_FORCE_PREFIX_HERMES_PROVIDER_ENV_BLOCKLISTlenhermes_constantsr   )	r   r   _is_passthrough	sanitizedkeyvaluereal_keyr   _profile_homes	            r   _sanitize_subprocess_envr      sf   *OOOOOOO * * *)/* !#I~2,,.. # #
U>>;<< 	4448L8L4"IcN B--// # #
U>>;<< 	#3@AABBCH"'Ih666//#:N:N6"IcN 544444''))M *)	&s   	 c            	         t           s{t          j        d          pft          j                            d          rdndpCt          j                            d          rdndp t          j                            d          pdS t          j                            d          } | r!t          j                            |           r| S t          j                            dd	          }|r!t          j                            |d
d          nd	}|rit          j                            |dd          t          j                            |ddd          fD ]%}t          j                            |          r|c S &t          j        d          }|r|S t          j                            t          j                            dd          ddd          t          j                            t          j                            dd          ddd          t          j                            |dddd          fD ]'}|r#t          j                            |          r|c S (t          d          )z Find bash for command execution.bashz/usr/bin/bashNz	/bin/bashSHELLz/bin/shHERMES_GIT_BASH_PATHLOCALAPPDATAr   hermesgitbinzbash.exeusrProgramFileszC:\Program FilesGitzProgramFiles(x86)zC:\Program Files (x86)ProgramszGit Bash not found. Hermes Agent requires Git for Windows on Windows.
Install it from: https://git-scm.com/download/win
Or set HERMES_GIT_BASH_PATH to your bash.exe location.)
r   shutilwhichr   r    isfileenvironrz   joinRuntimeError)custom_local_appdata_hermes_portable_git	candidatefounds        r   
_find_bashr      sB    
L   #%7>>/#B#BL!w~~k::D z~~g&& 	
 Z^^233F "'..((  Z^^NB77NLZb27<<%HHH`b !GLL-ujAAGLL-ueZHH
 	! 	!I w~~i(( !    ! L  E  	RZ^^N4GHH%QVXbcc
RZ^^$79RSSUZ\acmnn
^ZzJJ  	
  		22 	
	A  r   za/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binenvc                    	 ddl m} n# t          $ r d }Y nw xY wt          t          j        | z            }i }|                                D ]Z\  }}|                    t                    r"|t          t                    d         }|||<   A|t          vs ||          r|||<   [|                    dd          }t          s/d|                    d          vr|r| dt           nt          |d<   dd	lm}  |            }	|	r|	|d
<   	 ddlm}
m}m} |                                D ]$\  }}|                                }||ur|r|||<   %n# t          $ r Y nw xY w|S )zDBuild a run environment with a sane PATH and provider-var stripping.r   r   c                     dS r   r   r   s    r   r   z_make_run_env.<locals>.<lambda>  r   r   NPATHr   z/usr/binr   r   r   )get_session_env_UNSET_VAR_MAP)r   r   r   dictr   r   ry   r   r   r   r   rz   r   split
_SANE_PATHr   r   gateway.session_contextr   r   r   )r   r   mergedrun_envkvr   existing_pathr   r   r   r   r   var_namevarr   s                   r   _make_run_envr     s   *OOOOOOO * * *)/* "*s"##FG  1<<9:: 	>??@@AH !GH4448J8J4GAJKK++M  [:]-@-@-E-EEE=JZ]99Z999PZ
 544444''))M ('MMMMMMMMMM%^^-- 	* 	*MHcGGIIEF""u"$)!	*     Ns   	 AE 
EEc                  <   	 ddl m}   |             pi }|                    d          pi }|                    d          pg }t          |t                    sg }t          |                    dd                    }d |D             |fS # t          $ r g dfcY S w xY w)u   Return (shell_init_files, auto_source_bashrc) from config.yaml.

    Best-effort — returns sensible defaults on any failure so terminal
    execution never breaks because the config file is unreadable.
    r   )load_configterminalshell_init_filesauto_source_bashrcTc                 0    g | ]}|t          |          S r   )str).0fs     r   
<listcomp>z4_read_terminal_shell_init_config.<locals>.<listcomp>L  s#    +++1+A+++r   )rx   r   rz   
isinstancelistboolr   )r   cfgterminal_cfgfilesauto_bashrcs        r    _read_terminal_shell_init_configr   =  s    111111kmm!rwwz**0b  !344:%&& 	E<++,@$GGHH+++++[88   4xs   BB
 
BBc                     t                      \  } }g }| r|                    |            n |rt          s|                    g d           g }|D ]}	 t          j                            t          j                            |                    }n# t          $ r Y Lw xY w|r4t          j                            |          r|	                    |           |S )uh  Resolve the list of files to source before the login-shell snapshot.

    Expands ``~`` and ``${VAR}`` references and drops anything that doesn't
    exist on disk, so a missing ``~/.bashrc`` never breaks the snapshot.
    The ``auto_source_bashrc`` path runs only when the user hasn't supplied
    an explicit list — once they have, Hermes trusts them.
    )z
~/.profilez~/.bash_profilez	~/.bashrc)
r   extendr   r   r    
expandvars
expanduserr   r   append)explicitr   
candidatesresolvedrawr    s         r   _resolve_shell_init_filesr   Q  s     =>>HkJ J(####	 J[ J  	HHHIIIH " "	7%%bg&8&8&=&=>>DD 	 	 	H	 	"BGNN4(( 	"OOD!!!Os   <B
BB
cmd_stringr   c                     |s| S dg}|D ]4}|                     dd          }|                    d| d| d           5d                    |          dz   }|| z   S )a  Prepend ``source <file>`` lines (guarded + silent) to a bash script.

    Each file is wrapped so a failing rc file doesn't abort the whole
    bootstrap: ``set +e`` keeps going on errors, ``2>/dev/null`` hides
    noisy prompts, and ``|| true`` neutralises the exit status.
    zset +e'z'\''z[ -r 'z
' ] && . 'z' 2>/dev/null || true
)r   r   r   )r   r   prelude_partsr    safepreludes         r   _prepend_shell_initr   {  s      JM S S ||C))QdQQdQQQRRRRii&&-GZr   c                        e Zd ZdZddededef fdZd	efd
Zdddddede	dededz  d	e
j        f
dZd ZdefdZdef fdZd Z xZS )LocalEnvironmentzRun commands directly on the host machine.

    Spawn-per-call: every execute() spawns a fresh bash process.
    Session snapshot preserves env vars across calls.
    CWD persists via file-based read after each command.
    r   <   Nr   timeoutr   c                     |rt           j                            |          }t                                          |pt          j                    ||           |                                  d S )N)r   r   r   )r   r    r   super__init__getcwdinit_session)selfr   r   r   	__class__s       r   r   zLocalEnvironment.__init__  sb     	*'$$S))CS/BIKKcJJJr   r   c                    t           r	 ddlm}  |            dz  dz  }n3# t          $ r& t	          t          j                              dz  }Y nw xY w|                    dd           t          |          	                    dd	          S d
D ]k}| j
                            |          pt          j                            |          }|r.|                    d	          r|                    d	          pd	c S lt          j                            d          r.t          j        dt          j        t          j        z            rdS t          j                    }|                    d	          r|                    d	          pd	S dS )u  Return a shell-safe writable temp dir for local execution.

        Termux does not provide /tmp by default, but exposes a POSIX TMPDIR.
        Prefer POSIX-style env vars when available, keep using /tmp on regular
        Unix systems, and only fall back to tempfile.gettempdir() when it also
        resolves to a POSIX path.

        Check the environment configured for this backend first so callers can
        override the temp root explicitly (for example via terminal.env or a
        custom TMPDIR), then fall back to the host process environment.

        **Windows:** hardcoded ``/tmp`` is wrong in two ways — native Python
        can't open the path, and the Windows default temp (``%TEMP%``) often
        contains spaces (``C:\Users\Some Name\AppData\Local\Temp``) that
        break unquoted bash interpolations.  Use a dedicated cache dir under
        ``HERMES_HOME`` instead — single-word path, guaranteed to exist, same
        string resolves in both Git Bash and native Python.
        r   )get_hermes_homecacher   hermes_terminalT)parentsexist_okr   r   )TMPDIRTMPTEMPz/tmp)r   r   r   r   r   r#   r$   mkdirr   r   r   rz   r   r   r   rstripr    r!   accessW_OKX_OK)r   r   	cache_direnv_varr   s        r   get_temp_dirzLocalEnvironment.get_temp_dir  s   &  	5L<<<<<<+O--7*D		 L L L !4!6!677:KK			LOOD4O888y>>))$4440 	4 	4GW--H1H1HI 4Y11#66 4 '',,33337==   	RYvrw7H%I%I 	6'))	$$ 	0##C((/C/vs     -AAFx   )loginr   
stdin_datar   r
  r  c                   t                      }|r t                      }|rt          ||          }|r|dd|gn|d|g}t          | j                  }t          | j                  }	|	| j        k    rPt          rt          | j                  n| j        }
|	|
k    r!t          
                    d| j        |	           |	| _        | j        }t          j        |d|ddt          j        t          j        |t          j        nt          j        t          rd nt           j        t          rt          j        nd|          }t          s0	 t!          j        |j                  |_        n# t,          $ r Y nw xY w|t/          ||           |S )	Nz-lz-czaLocalEnvironment cwd %r is missing on disk; falling back to %r so terminal commands keep working.Tutf-8r   r   )
textr   encodingerrorsstdoutstderrstdin
preexec_fncreationflagsr   )r   r   r   r   r   r'   r   r   r   loggerwarning
subprocessPopenPIPESTDOUTDEVNULLr   setsidCREATE_NO_WINDOWgetpgidpid_hermes_pgidProcessLookupErrorr   )r   r   r
  r   r  r   
init_filesargsr   safe_cwd
normalized
_popen_cwdprocs                r   	_run_bashzLocalEnvironment._run_bash  s    ||  	I244J I0ZHH
16TdD*--T4<T)) %TX..tx =HU.tx888TXJ:%%LH	    DHX
?$%/%;*//AS*9tt	9DK*55!
 
 
  	$&Jtx$8$8!!%    !j)))s   E   
E-,E-c                    dt           dt          fddt           dt          dt          ffd}	 t          r                                 dS 	 t          j        j                  }n$# t          $ r t          dd          }| Y nw xY w	 t          j
        |t          j                   n# t          $ r Y dS w xY w ||d          rdS 	 t          j
        |t          j                   n# t          $ r Y dS w xY w ||d	           	                     d
           dS # t          j        t"          f$ r Y dS w xY w# t          t$          t"          f$ r+ 	                                  Y dS # t(          $ r Y Y dS w xY ww xY w)z-Kill the entire process group (all children).pgidr   c                 l    	 t          j        | d           dS # t          $ r Y dS t          $ r Y dS w xY w)Nr   TF)r   killpgr"  PermissionError)r+  s    r   _group_alivez4LocalEnvironment._kill_process.<locals>._group_alive  sY    	$"""t%   uu"   tts    
3	33r   c                 ~   t          j                    |z   }t          j                    |k     r^	                                  n# t          $ r Y nw xY w |           sdS t          j        d           t          j                    |k     ^	                                  n# t          $ r Y nw xY w |            S )NTg?)time	monotonicpollr   sleep)r+  r   deadliner/  r(  s      r   _wait_for_group_exitz<LocalEnvironment._kill_process.<locals>._wait_for_group_exit   s    ~'''1H.""X--IIKKKK    D#|D))  4
4    .""X--		   #|D))))s#   A 
AAB# #
B0/B0r!  Ng      ?g       @g?)r   )intr   floatr   	terminater   r  r   r"  getattrr-  signalSIGTERMSIGKILLwaitr  TimeoutExpiredOSErrorr.  killr   )r   r(  r6  r+  r/  s    `  @r   _kill_processzLocalEnvironment._kill_process  s&   		s 		t 		 		 		 			*s 	*U 	*t 	* 	* 	* 	* 	* 	* 	*$$	      :dh//DD)   "4>>D| $|
IdFN3333)   FF ('c22 FIdFN3333)   FF$$T3///IIcI*****"17;   DD"OW= 	 	 			   	s   E A* )E *BE 
BE B/ .E /
B=9E <B==E C/ .E /
C=9E <C==E D% %D?;E >D??E FE11
F ;F?F  Fresultc                    	 t          | j        d          5 }|                                                                }ddd           n# 1 swxY w Y   t          rt          |          }|r&t          j                            |          r|| _	        n# t          t          f$ r Y nw xY w|                     |           dS )u  Read CWD from temp file (local-only, no round-trip needed).

        Skip the assignment when the path no longer exists as a directory —
        ``pwd -P`` on a deleted cwd can leave a stale value in the marker
        file, and propagating it would re-wedge the next ``Popen``.  The
        ``_run_bash`` recovery path will resolve a safe fallback if needed.

        On Windows, the value written by Git Bash's ``pwd -P`` is in
        MSYS form (``/c/Users/x``). Translate it to native Windows form
        before validating with ``os.path.isdir`` and before storing on
        ``self.cwd``; otherwise the isdir check rejects every valid
        result and ``_run_bash`` later prints a misleading "cwd is
        missing" warning on every command.
        r  )r  N)open	_cwd_filereadstripr   r   r   r    r!   r   r@  FileNotFoundError_extract_cwd_from_output)r   rC  r   cwd_paths       r   _update_cwdzLocalEnvironment._update_cwdX  s   	dnw777 ,16688>>++, , , , , , , , , , , , , , , ;0:: $BGMM(33 $#*+ 	 	 	D	 	%%f-----s4   B 'AB AB AAB B)(B)c                    | j         }t                                          |           | j         |k    rUt          rt	          | j                   n| j         }|r(t
          j                            |          r	|| _         dS || _         dS dS )u  Same semantics as the base class, but on Windows the value
        emitted by ``pwd -P`` inside Git Bash is in MSYS form
        (``/c/Users/x``). Normalize to native Windows form and validate
        the directory exists before assigning to ``self.cwd`` — otherwise
        ``_run_bash``'s safe-cwd recovery would warn on every subsequent
        command.

        Always defers to the base class for stripping the marker text from
        ``result["output"]`` so output formatting is identical.
        N)r   r   rJ  r   r   r   r    r!   )r   rC  prev_cwdr&  r   s       r   rJ  z)LocalEnvironment._extract_cwd_from_outputt  s     8((0008x<GU.tx888TXJ $bgmmJ77 $% $  r   c                 r    | j         | j        fD ]'}	 t          j        |           # t          $ r Y $w xY wdS )zClean up temp files.N)_snapshot_pathrF  r   unlinkr@  )r   r   s     r   cleanupzLocalEnvironment.cleanup  sY    %t~6 	 	A	!   	 	s   '
44)r   r   N)__name__
__module____qualname____doc__r   r7  r   r   r  r   r  r  r)  rB  rL  rJ  rR  __classcell__)r   s   @r   r   r     sB         C s d      .c . . . .` ;@!$+/B B BC B4 BB!DjB4>4DB B B BHD D DL.$ . . . .8$t $ $ $ $ $ $0      r   r   )N)(rV  loggingr   platformr   r   r;  r  r#   r1  pathlibr   tools.environments.baser   r   systemr   	getLoggerrS  r  r   r   r'   r   r{   r   r   r   r   r   _find_shellr   r   tupler   r   r   r   r   r   r   r   r   <module>r`     si   K K  				  				               @ @ @ @ @ @ @ @ho9,		8	$	$(s (s ( ( ( ((!3 !3 ! ! ! !D %5 !Yy Y Y Y Yx "?!>!@!@  td{ td{ VZ    >2C 2 2 2 2l 
C .t . . . . .b%S	4*@    ('49 ' ' ' 'T C  S	  c        *B B B B B B B B B Br   