
    (GjB                        U 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 ddlmZmZ ddl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edefdZdZ edh          ZdefdZ e            ZdZ dede!fdZ"de#ddfdZ$de#ddfdZ%d3de#dz  de#dz  de#fdZ& eh d          Z'ee         e(d<   ddde!de#eef         fdZ)defdZ* eh d          Z+defd Z,d!Z- e.            Z/e/a0d"e(d#<   dedz  fd$Z1d%edefd&Z2d%edefd'Z3de#ddfd(Z4d)e#dedz  fd*Z5de#de#fd+Z6de7e8e         e!f         fd,Z9de8e         fd-Z:d.ed/e8e         defd0Z; G d1 d2e          Z<dS )4uE   Local execution environment — spawn-per-call with session snapshot.    N)Path)BaseEnvironment_pipe_stdin)windows_hide_flags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/rurouni/.hermes/hermes-agent/tools/environments/local.py_msys_to_windows_pathr      s      c 

'--A 
GGAJJEGGAJJ"%%c400D''doc"gg'''    c                 4   t           r| s| S t          j        d|           }|s| S |                    d                                          }|                    d          pd                    dd                              d          }|rd| d| nd| dS )a+  Translate a native Windows path (``C:\Users\x``) to Git Bash /
    MSYS form (``/c/Users/x``) so ``builtin cd`` resolves it reliably.

    No-ops on non-Windows hosts or for paths that aren't drive-qualified
    native Windows paths. Returns the input unchanged when no translation
    applies.
    z^([a-zA-Z]):[\\/]*(.*)$r   r   r   r   r   )r   r   r   r   lowerr   lstripr   s       r   _windows_to_msys_pathr#   +   s      c 

+S11A 
GGAJJEGGAJJ"%%dC0077<<D!%7ut<u<<<7r   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_AWS_BEARER_TOKEN_BEDROCKc                     t                      } 	 ddlm} |                                D ]b}|                     |j                   |j        dk    r|                     t                     |j        r| 	                    |j                   cn# 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	           |                     d
           t!          |           S )z=Derive the blocklist from provider, tool, and gateway config.r   )PROVIDER_REGISTRYaws_sdk)OPTIONAL_ENV_VARScategory>   tool	messagingsettingpassword>A   GH_TOKENHASS_URL	LLM_MODEL
HASS_TOKENXAI_API_KEYGROQ_API_KEY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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GATEWAY_RELAY_IDHELICONE_API_KEYPARALLEL_API_KEYTOGETHER_API_KEYWHATSAPP_ENABLEDANTHROPIC_API_KEYFIRECRAWL_API_KEYFIRECRAWL_API_URLFIREWORKS_API_KEY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GATEWAY_RELAY_SECRETSIGNAL_ALLOWED_USERSGATEWAY_ALLOWED_USERSSIGNAL_IGNORE_STORIESTELEGRAM_HOME_CHANNELWHATSAPP_ALLOWED_USERSDISCORD_REQUIRE_MENTIONEMAIL_HOME_ADDRESS_NAMESLACK_HOME_CHANNEL_NAMEVERTEX_CREDENTIALS_PATHSIGNAL_HOME_CHANNEL_NAMEDISCORD_HOME_CHANNEL_NAMEGATEWAY_RELAY_DELIVERY_KEYGITHUB_APP_INSTALLATION_IDSIGNAL_GROUP_ALLOWED_USERSTELEGRAM_HOME_CHANNEL_NAMEGITHUB_APP_PRIVATE_KEY_PATHDISCORD_FREE_RESPONSE_CHANNELSGOOGLE_APPLICATION_CREDENTIALSHERMES_DASHBOARD_SESSION_TOKENCLAUDE_CODE_OAUTH_TOKEN)sethermes_cli.authr1   valuesupdateapi_key_env_vars	auth_type_AWS_SDK_CREDENTIAL_ENV_VARSbase_url_env_varaddImportErrorhermes_cli.configr3   itemsgetdiscard	frozenset)blockedr1   pconfigr3   namemetadatar4   s          r   _build_provider_env_blocklistr   w   s   G	555555(//11 	6 	6GNN73444 I--;<<<' 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 D D D D D D\ OO-...Ws%   A=B 
BBA?D 
D,+D,)VIRTUAL_ENVCONDA_PREFIXkeyc                 \   |                                  }|                    d          r,|                    d          s|                    d          rdS |                    d          rA|                    d          s*|                    d          s|                    d          rdS d	S )
u  Return True for Hermes-internal secrets injected under *dynamic* names.

    ``_HERMES_PROVIDER_ENV_BLOCKLIST`` is name-based and derived from the
    provider/tool registries, but the gateway and CLI also inject secrets into
    ``os.environ`` at runtime under names no static registry knows about:

    - ``AUXILIARY_<TASK>_API_KEY`` / ``AUXILIARY_<TASK>_BASE_URL`` — per-task
      side-LLM credentials bridged from ``config.yaml[auxiliary]`` by
      ``gateway/run.py`` and ``cli.py`` (vision, web_extract, approval,
      compression, and any plugin-registered auxiliary task). These are
      separate, often higher-spend API keys plus base URLs that may point at
      private endpoints; a model-authored shell command must never see them.
    - ``GATEWAY_RELAY_*_SECRET`` / ``GATEWAY_RELAY_*_KEY`` /
      ``GATEWAY_RELAY_*_TOKEN`` — relay-auth material provisioned by the
      gateway (``GATEWAY_RELAY_SECRET``, ``GATEWAY_RELAY_DELIVERY_KEY``).
      These are Tier-1 gateway secrets, like the messaging bot tokens in
      ``_ALWAYS_STRIP_KEYS``. Non-secret ``GATEWAY_RELAY_*`` routing hints
      (``GATEWAY_RELAY_URL``, ``GATEWAY_RELAY_PLATFORMS``, …) are NOT matched
      and remain visible.

    ``code_execution_tool.py`` already catches these via substring matching on
    ``KEY`` / ``SECRET`` / ``TOKEN``; the terminal backend's narrower name-based
    blocklist did not, which is the leak this predicate closes.

    This is the single source of truth for "Hermes-internal dynamic secret"
    across every spawn path — the terminal ``_make_run_env`` /
    ``_sanitize_subprocess_env`` filters, the Docker passthrough filter, and the
    non-terminal :func:`hermes_subprocess_env` helper all call it, so the
    dynamic patterns are stripped **unconditionally** regardless of
    ``env_passthrough`` skill registration or ``inherit_credentials``. Nothing
    a model-driving CLI legitimately needs matches these patterns.
    
AUXILIARY__API_KEY	_BASE_URLTGATEWAY_RELAY__SECRET_KEY_TOKENF)r   
startswithendswith)r   r   s     r   _is_hermes_internal_secretr      s    B IIKKE%% z""&+nn[&A&A t()) y!!%*^^F%;%;?D~~h?W?W t5r   envc                 \    	 ddl m}  |            }|r|| d<   dS dS # t          $ r Y dS w xY w)zBBridge the context-local Hermes home override into subprocess env.r   )get_hermes_home_overrideHERMES_HOMEN)hermes_constantsr   	Exception)r   r   values      r   _inject_context_hermes_homer     sk    ======((** 	'!&C	' 	'   s    
++c                    	 ddl m}m}m} n# t          $ r Y dS w xY w |            }|                                D ]L\  }}|                                }||ur|dnt          |          | |<   4|r|                     |d           MdS )u  Bridge gateway session ContextVars into a subprocess environment dict.

    ContextVars don't propagate to child processes, so the live session vars
    (HERMES_SESSION_*) are bridged onto the child env here.

    🔴 Cross-session leak guard. The session vars also have a process-global
    os.environ mirror (written last-writer-wins as a CLI/cron fallback, never
    cleared). Under a concurrent multi-session host (the messaging gateway, ACP
    adapter, API server, TUI) that global belongs to *whichever turn wrote it
    last* — NOT necessarily this task. A subprocess spawned from a task whose
    ContextVar is _UNSET (e.g. a sibling message task that never bound, or one
    that inherited another session's context) would otherwise inherit the
    FOREIGN global and act on another session's identity.

    So once the session-context machinery is engaged in this process (any host
    has called set_session_vars), the session vars are ContextVar-authoritative:
    - ContextVar set (incl. explicitly-empty "") → that value wins, overriding
      any stale snapshot/global value.
    - ContextVar _UNSET → STRIP the var from the child env rather than inherit
      the possibly-foreign process-global.
    In a pure single-process CLI/one-shot that never engaged the session-context
    system there is no concurrency to leak across, so the inherited fallback is
    kept. See gateway/session_context.session_context_engaged and
    tests/tools/test_local_env_session_leak.py.
    r   )_UNSET_VAR_MAPsession_context_engagedNr   )	gateway.session_contextr   r   r   r   r   r   strpop)r   r   r   r   _engagedvar_namevarr   s           r   _inject_session_context_envr   +  s    4	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	

     '&((H!)) $ $#		"'-BBSZZCMM 	$ GGHd###$ $s   
 
base_env	extra_envc                    	 ddl m} n# t          $ r d }Y nw xY wi }| pi                                 D ]I\  }}|                    t
                    r t          |          r0|t          vs ||          r|||<   J|pi                                 D ]z\  }}|                    t
                    r2|t          t
                    d         }t          |          rK|||<   Qt          |          ra|t          vs ||          r|||<   {t          |           ddl
m}  ||           t          |           t          D ]}|                    |d           t          |           |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apply_subprocess_home_env)tools.env_passthroughr   r   r   r   !_HERMES_PROVIDER_ENV_FORCE_PREFIXr   _HERMES_PROVIDER_ENV_BLOCKLISTlenr   r   r   r   _ACTIVE_VENV_MARKER_VARSr   %_apply_windows_msys_bash_env_defaults)	r   r   _is_passthrough	sanitizedr   r   real_keyr   _markers	            r   _sanitize_subprocess_envr   Z  s   *OOOOOOO * * *)/* !#I~2,,.. # #
U>>;<< 	%c** 	4448L8L4"IcN B--// 	# 	#
U>>;<< 	#3@AABBCH)(33 "'Ih',, 	#666//#:N:N6"IcN	***::::::i(((  	***+ % %gt$$$$))444   	 >   r9   r<   GITHUB_TOKENr@   rD   rF   rJ   SLACK_APP_TOKENSLACK_BOT_TOKENrR   DISCORD_BOT_TOKENr]   TELEGRAM_BOT_TOKENrf   SLACK_SIGNING_SECRETrh   GATEWAY_ALLOW_ALL_USERSrr   rs   rv   ry   _ALWAYS_STRIP_KEYSF)inherit_credentialsr   c                 ~   t           j                                        }t          D ]}|                    |d           t          |          D ]X}|                    t                    r|                    |d           3t          |          r|                    |d           Y| s t          D ]}|                    |d           |
                    dd           t          |           ddlm}  ||           t          D ]}|                    |d           t          |           t!          |           |S )u  Build a sanitized environment dict for a spawned subprocess.

    Centralized helper for the **non-terminal** spawn surface (browser,
    ACP/CLI executors, computer-use driver, dep-ensure, TUI Node host,
    detached gateway).  Use this instead of copying ``os.environ`` directly
    so strip-by-default is the uniform policy across every spawn site, with a
    single source of truth (``_HERMES_PROVIDER_ENV_BLOCKLIST``).  The terminal
    / execute_code path keeps using :func:`_sanitize_subprocess_env`, which is
    skill-aware (``env_passthrough``); this helper is for spawns that have no
    skill-passthrough concept.

    Two-tier stripping:

    * **Tier 1 (always):** ``_ALWAYS_STRIP_KEYS`` — gateway bot tokens, GitHub
      auth, and remote-compute secrets are removed regardless of
      ``inherit_credentials``.  No child Hermes spawns legitimately needs them.
    * **Tier 2 (conditional):** the rest of ``_HERMES_PROVIDER_ENV_BLOCKLIST``
      (LLM provider API keys, tool secrets) is removed unless the caller passes
      ``inherit_credentials=True``.

    Pass ``inherit_credentials=True`` **only** when the child legitimately
    needs LLM provider credentials — a user-blessed ``claude`` / ``codex`` /
    ``gemini`` CLI executor, or the TUI Node host that makes model calls.  The
    flag is grep-able for audit: ``grep -rn 'inherit_credentials=True'`` lists
    every spawn site that still receives provider credentials.

    Callers that need a *specific* non-provider secret (e.g. the browser worker
    needs ``BROWSERBASE_API_KEY`` / ``FIRECRAWL_API_KEY``) should call with
    ``inherit_credentials=False`` and copy just those keys back from
    ``os.environ`` into the returned dict.
    N
PYTHONUTF81r   r   )r%   environcopyr   r   listr   r   r   r   
setdefaultr   r   r   r   r   r   )r   r   r   r   r   s        r   hermes_subprocess_envr     sf   @ *//

C "  T Cyy  >>;<< 	GGC',, 	GGC 1 	 	CGGC NN<%%%$$$::::::c""" ,  )#...  $$$Jr   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                            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          j        d          }|r|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&   isfiler   r   joinRuntimeError)custom_local_appdata_hermes_portable_git	candidatefounds        r   
_find_bashr     s@    
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(( !    ! 	RZ^^N4GHH%QVXbcc
RZ^^$79RSSUZ\acmnn
^ZzJJ  	
  		22 	L  E 
	A  r   >   shkshzshr   dashmkshc                  $   t           s|t          j                            d          } | r[t          j                            |           r<t          j        | t          j                  rt          |           j	        t          v r| S t                      S )u   Find the user's login shell for background process spawning.

    Unlike ``_find_bash`` (which always returns a bash binary for callers
    that explicitly need bash), this function prefers the user's configured
    ``$SHELL`` on POSIX so that ``spawn_local`` uses the shell the user
    actually logs in with.

    On macOS Catalina+ the default login shell is zsh, but
    ``shutil.which("bash")`` still finds the system ``/bin/bash`` (GNU bash
    3.2).  When bash 3.2 is invoked with ``-l`` (login) and stdin is
    ``/dev/null``, it sources ``~/.bash_profile`` which on many macOS setups
    contains ``exec /bin/zsh -l``.  That ``exec`` replaces bash with zsh but
    drops the ``-c`` argument, so the background command never runs — the
    subprocess exits 0 with no output and no side effects.

    Preferring ``$SHELL`` (when it is a POSIX-``sh``-family shell) avoids this
    because zsh/bash/sh/dash/ksh handle ``-lic`` correctly even with
    redirected stdin.

    Only POSIX-sh-family shells are honoured: ``spawn_local`` invokes the
    shell as ``[shell, "-lic", "set +m; <cmd>"]``, and that ``-lic`` bundle +
    ``set +m`` job-control syntax is NOT understood by fish, csh/tcsh,
    nushell, elvish, xonsh, etc.  Returning such a ``$SHELL`` would trade the
    bash-3.2 swallow for a parse error on every background command, so for any
    non-allowlisted shell we fall back to ``_find_bash`` (the prior behaviour).

    On Windows, ``$SHELL`` is typically bash (Git Bash), so behaviour is
    unchanged — we fall through to ``_find_bash``.
    r   )r   r%   r   r   r&   r   accessX_OKr   r   _SPAWN_COMPATIBLE_SHELLSr   )
user_shells    r   _find_shellr   A  s~    <  Z^^G,,
	z**	 	*bg..	 Z  %)AAA<<r   za/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binzstr | None | object_HERMES_BIN_DIRc                     t           t          urt           S d} t          j        d          }|rt          j                            |          } | t          j        rt          j        d         nd}t          j        	                    |          
                                }t          j                            |          rY|dk    s|                    d          r>t          j                            |          rt          j                            |          } | t          j        r)t          j                            t          j                  nd}|rJt          rdnd}t          j                            t          j                            ||                    r|} | r!t          j                            |           sd} | a | S )u  Return the directory holding the ``hermes`` console-script, or None.

    The terminal tool runs in a freshly-spawned subshell whose PATH is the
    agent process's PATH plus a static set of system dirs (``_SANE_PATH``).
    When the gateway is launched by something that does NOT source the user's
    shell rc — systemd, a service manager, a desktop launcher, cron — the
    hermes install dir (``~/.local/bin``, the venv ``bin``/``Scripts``, pipx,
    nix) is absent from that PATH, so plugins shelling out to bare ``hermes``
    via the terminal tool hit ``command not found`` (exit 127) even though
    ``hermes`` works fine in the user's own interactive terminal.

    We resolve the install dir once (it never changes within a process) and
    prepend-if-missing it to the subshell PATH so bare ``hermes`` resolves
    regardless of how the gateway was started.

    Resolution order (cheap, no heavy imports):
      1. ``shutil.which("hermes")`` — normal PATH-installed shim.
      2. The directory of ``sys.argv[0]`` when it's an absolute path to a
         real ``hermes`` executable (covers nix-store / venv wrappers).
      3. The directory of ``sys.executable`` — the running interpreter's
         venv ``bin``/``Scripts`` is where its console-scripts live.
    Nr   r   r   zhermes.z
hermes.exe)r   	_SENTINELr   r   r%   r&   r(   sysargvbasenamer!   isabsr   r   
executabler   r   r'   )r   r   argv0baseexe_dirshims         r   _resolve_hermes_bin_dirr	  w  ss   0 i'' IL""E +GOOE**	"x/Rw&&,,..GMM%  	/!!T__Y%?%?!u%% " ..I58^K"'//#.111 	$#.<<<HDw~~bgll7D99:: $#	 y11 	Or   existing_pathc                     t                      }|s| S t          j        }| rd |                     |          D             ng }||v r| S |                    |g|          S )a  Prepend the hermes install dir to ``existing_path`` if it's missing.

    Cross-platform (uses ``os.pathsep``). First-occurrence wins, so a PATH
    that already contains the dir is returned unchanged. Returns the input
    unchanged when the install dir can't be resolved.
    c                     g | ]}||S r   r   ).0es     r   
<listcomp>z+_prepend_hermes_bin_dir.<locals>.<listcomp>  s    888Qa8q888r   )r	  r%   pathsepsplitr   )r
  bin_dirsepentriess       r   _prepend_hermes_bin_dirr    s{     &''G 
*C<IQ88---c228888rG'88W'w'(((r   c                    t           r| S d t                              d          D             }| sd                    |          S t	                      }g }|                     d          D ]3}|r||v r	|                    |           |                    |           4|D ]}||vr|                    |           d                    |          S )u  Return a normalised POSIX PATH with missing sane entries appended.

    On POSIX the caller-supplied PATH is rewritten (not merely appended to):
    empty entries and duplicate entries are dropped, preserving
    first-occurrence order, then each missing ``_SANE_PATH`` entry is appended
    once at the end so existing entries keep their precedence.

    Two intentional normalisations beyond the bare "add Homebrew dirs" fix:

    - **Empty entries are stripped.** A leading/trailing/double ``:`` encodes
      an empty PATH element, which POSIX shells interpret as the current
      working directory — a mild foot-gun in a default terminal environment.
      We drop these rather than carry them through.
    - **Duplicates are collapsed** (first occurrence wins), so a caller PATH
      that already contains repeats is not propagated verbatim.

    For a well-formed PATH (no empties, no duplicates) the leading segment is
    byte-identical to the input and ordering is preserved; only the missing
    sane entries are appended. On Windows this is a no-op passthrough (the
    separator is ``;`` and the native PATH must not be touched).
    c                     g | ]}||S r   r   )r  entrys     r   r  z5_append_missing_sane_path_entries.<locals>.<listcomp>  s    FFFeFEFFFr   r   )r   
_SANE_PATHr  r   r{   r   append)r
  sane_entriesseenordered_entriesr  s        r   !_append_missing_sane_path_entriesr    s    ,  FFz'7'7'<'<FFFL &xx%%% UUD!#O$$S)) & & 	u%%%%  * *""5)))88O$$$r   c                 p    t           sdS |                     dd           |                     dd           dS )uc  Disable MSYS argument path conversion for Git Bash subprocesses.

    Git Bash rewrites arguments that look like Unix paths (``/FO``, ``/TN``,
    ``/Create``) into ``C:/.../git/FO``-style paths, which breaks native
    Windows commands such as ``tasklist``, ``schtasks``, and ``wmic``.  Hermes
    runs terminal commands through bash on Windows, so set the standard MSYS
    opt-out by default.  Users who need conversion can override in their env.
    Refs #56700.

    ``MSYS_NO_PATHCONV`` is honored by Git for Windows bash only.  MSYS2-proper
    and Cygwin bash (which ``_find_bash`` can still return via the final
    ``shutil.which`` fallback) ignore it and honor ``MSYS2_ARG_CONV_EXCL``
    instead, so set both.  ``*`` disables all argv conversion — the semantic
    equivalent of ``MSYS_NO_PATHCONV=1``.  Also fixes ``cmd /c`` mangling
    (#56147).
    NMSYS_NO_PATHCONVr   MSYS2_ARG_CONV_EXCL*)r   r   )r   s    r   r   r     sA    "  NN%s+++NN(#.....r   run_envc                 Z    t           sdS | D ]}|                                dk    r|c S dS )a  Return the PATH env key to update without altering Windows casing.

    Note: this is deliberately a *second* Windows guard, distinct from the
    early-return in ``_append_missing_sane_path_entries``. Its job is to pick
    the correctly-cased key (``Path`` vs ``PATH``) so completion writes back to
    the key the caller already used; the helper's guard makes that helper safe
    to call standalone (it is, e.g. in the Windows unit tests). Both are
    intentional.
    PATHN)r   r   )r#  r   s     r   _path_env_keyr&    sF      v  99;;&  JJJ !4r   c                    	 ddl m} n# t          $ r d }Y nw xY wt          t          j        | z            }i }|                                D ]z\  }}|                    t                    r2|t          t                    d         }t          |          rK|||<   Qt          |          ra|t          vs ||          r|||<   {t          |          }|5t          |                    |d                    }t          |          ||<   t!          |           ddlm}	  |	|           t'          |           t(          D ]}
|                    |
d           t-          |           |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   Nr   r   )r   r   r   dictr%   r   r   r   r   r   r   r   r&  r  r   r  r   r   r   r   r   r   r   )r   r   mergedr#  kvr   path_keynew_pathr   r   s              r   _make_run_envr/    s   *OOOOOOO * * *)/* "*s"##FG 	 	1<<9:: 	>??@@AH)(33  !GH'** 	4448J8J4GAJW%%H4W[[25N5NOO 4H==(((::::::g&&&
  (((+ # #GT"""")'222Nr   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   )r   )r  fs     r   r  z4_read_terminal_shell_init_config.<locals>.<listcomp>V  s#    +++1+A+++r   )r   r1  r   
isinstancer   boolr   )r1  cfgterminal_cfgfilesauto_bashrcs        r    _read_terminal_shell_init_configr=  G  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   r  )explicitr<  
candidatesresolvedrawr&   s         r   _resolve_shell_init_filesrF  [  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   )rG  r;  prelude_partsr&   safepreludes         r   _prepend_shell_initrN    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e	de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   rR  r   )r%   r&   rA  super__init__getcwdinit_session)selfr   rR  r   	__class__s       r   rU  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cacher2  hermes_terminalT)parentsexist_okr   r   )TMPDIRTMPTEMPz/tmp)r   r   r[  r   r   r)   r*   mkdirr   r   r   r   r%   r   r   rstripr&   r'   r   W_OKr   )rX  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c                 D    t          j        t          |                     S )z@Use native paths for Python, but Git Bash-friendly paths for cd.)r   _quote_cwd_for_cdr#   )r   s    r   rj  z"LocalEnvironment._quote_cwd_for_cd  s     01Fs1K1KLLLr   Fx   )loginrR  
stdin_datarG  rl  rm  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          rdt                      ini }t          j        |fd|ddt          j        t          j        |t          j        nt          j        d|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.creationflagsTutf-8r   )	textr   encodingerrorsstdoutstderrstdinstart_new_sessionr   )r   rF  rN  r/  r   r-   r   r   r   loggerwarningr   
subprocessPopenPIPESTDOUTDEVNULLr%   getpgidpid_hermes_pgidProcessLookupErrorr   )rX  rG  rl  rR  rm  r   
init_filesargsr#  safe_cwd
normalized
_popen_cwd_popen_kwargs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
CNV*<*>*>??TV
?$%/%;*//AS"
 
 
 
  	$&Jtx$8$8!!%    !j)))s   1E 
EEc                    dt           dt          fddt           dt          dt          ffd}	 t          rt	 ddlm}  |j        d	           n$# t          $ r                                  Y nw xY w	 	                    d
           dS # t          j        t          f$ r Y dS w xY w	 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rR  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  rR  deadliner  r  s      r   _wait_for_group_exitz<LocalEnvironment._kill_process.<locals>._wait_for_group_exit1  s    ~'''1H.""X--IIKKKK    D#|D))  4
4    .""X--		   #|D))))s#   A 
AAB# #
B0/B0r   )terminate_pidT)forceg       @)rR  r  Ng      ?g?)intr8  floatr   gateway.statusr  r  r   killwaitrz  TimeoutExpiredOSErrorr%   r  r  getattrr  signalSIGTERMSIGKILLr  )rX  r  r  r  r  r  s    `   @r   _kill_processzLocalEnvironment._kill_process#  s   		s 		t 		 		 		 			*s 	*U 	*t 	* 	* 	* 	* 	* 	* 	*$-	 ' <<<<<<!M$($77777       IIKKKKK IIcI*****"17;   DD: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   F  A F  A41F  3A44F  8B B*&F  )B**F  .C F  C)&F  (C))F  -D F  
DF  DF  -E F  
EF  EF  +F FF  FF   G"8G
GG"GG"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.
        rp  )rr  N)open	_cwd_filereadstripr   r   r%   r&   r'   r   r  FileNotFoundError_extract_cwd_from_output)rX  r  r6  cwd_paths       r   _update_cwdzLocalEnvironment._update_cwdr  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   rT  r  r   r   r%   r&   r'   )rX  r  prev_cwdr  rY  s       r   r  z)LocalEnvironment._extract_cwd_from_output  s     8((0008x<GU.tx888TXJ $bgmmJ77 $% $  r   c                 (   | j         | j        fD ]'}	 t          j        |           # t          $ r Y $w xY w	 ddl}|                    | j          d          D ]'}	 t          j        |           # t          $ r Y $w xY wdS # t          $ r Y dS w xY w)zClean up temp files.r   Nz.tmp.*)_snapshot_pathr  r%   unlinkr  globr   )rX  r6  r  tmps       r   cleanupzLocalEnvironment.cleanup  s    %t~6 	 	A	!   	KKKyyD$7!?!?!?@@  IcNNNN   D 
  	 	 	DD	sA   '
44#B A10B 1
A>;B =A>>B 
BB)r   rQ  N)__name__
__module____qualname____doc__r   r  r)  rU  rh  staticmethodrj  r8  rz  r{  r  r  r  r  r  __classcell__)rY  s   @r   rP  rP    sv         C s d      .c . . . .` Ms Ms M M M \M ;@!$+/D D DC D4 DD!DjD4>4DD D D DLM M M^.$ . . . .8$t $ $ $ $ $ $0      r   rP  )N)=r  loggingr%   platformr   r   r  rz  r   r)   r  pathlibr   tools.environments.baser   r   hermes_cli._subprocess_compatr   systemr   	getLoggerr  rx  r   r   r#   r-   r   r   r   r   r   r   r8  r   r)  r   r   r   r   __annotations__r   r   r   r   r  objectr   r   r	  r  r  r   r&  r/  tupler   r=  rF  rN  rP  r   r   r   <module>r     s   K K K  				  				       



         @ @ @ @ @ @ @ @ < < < < < <ho9,		8	$	$(s (s ( ( ( ((8s 8s 8 8 8 8$!3 !3 ! ! ! !D %5 !&  )y*     
iy i i i iX "?!>!@!@  ; *C *D * * * *Z	T 	d 	 	 	 	,$T ,$d ,$ ,$ ,$ ,$^* *td{ *td{ *VZ * * * *j &/Y  0  0  0  &  & IcN      F :? L L L$ L4S> L L L L^6C 6 6 6 6z %9%Q%Q%QRR 'S ' ' ' 'XC  FHH	)2& 2 2 26t 6 6 6 6r)3 )3 ) ) ) )"-%S -%S -% -% -% -%`/t / / / / /.4 C$J    $*t * * * * *Z%S	4*@    ('49 ' ' ' 'T C  S	  c        *] ] ] ] ] ] ] ] ] ]r   