
    `gj                        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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edefdZdedefdZdedefdZdedefdZ d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*dHd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/i Z0e(eef   e-d$<   i Z1e(eef   e-d%<   da2d&e-d'<   d(Z3d)edefd*Z4dId+Z5d,edefd-Z6dJd,ed)edefd.Z7d,edefd/Z8da9d0e-d1<   de:e   fd2Z;d3edefd4Z< e"h d5      Z=defd6Z>d7Z? e@       ZAeAaBd8e-d9<   dedz  fd:ZCd3edefd;ZDd3edefd<ZEde(ddfd=ZFd>e(dedz  fd?ZGde(de(fd@ZHdeIe:e   ef   fdAZJde:e   fdBZKdCedDe:e   defdEZL G dF dGe      ZMy)KuE   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 |j                  d      j	                         }|j                  d      xs dj                  dd      }| d|xs 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.

    Also accepts the Cygwin (``/cygdrive/c/...``) and WSL-mount
    (``/mnt/c/...``) spellings of a drive root. Multi-segment POSIX paths
    like ``/home/x`` or ``/tmp/foo`` are left untouched.

    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)^/(?:(?:cygdrive|mnt)/)?([a-zA-Z])(/.*)?$       /\:\   )_IS_WINDOWSrematchgroupupperreplacechrr   mdrivetails       K/root/.hermes/venv/lib/python3.12/site-packages/tools/environments/local.py_msys_to_windows_pathr      sz     c
 	=sCA
 GGAJEGGAJ"%%c40DWAdoc"g&''    c                 V   | rt         j                  j                  |       nt        j                         }t        r#t        |      }ddl} |j                  |      r|S t         j                  j                  |      r|S t         j                  j                  |      }t        j                         }t         j                  j                  |      sWt        |      j                  }t        |      j                  }|r+t        |      t        |      k  r|t        |       d |k(  r|S |S )a  Resolve the local backend's initial cwd to an absolute host path.

    ``TERMINAL_CWD`` can be populated from config.yaml before the terminal
    backend is created.  If that value is relative and happens to match the
    directory Hermes was already launched from (for example ``hermes-agent``
    while the process cwd is ``~/.hermes/hermes-agent``), passing it through
    unchanged makes the wrapper run ``cd hermes-agent`` *inside* the project
    and fail with a confusing nested-path error.  Anchor relative local cwd
    values once, up front, so both ``subprocess.Popen(cwd=...)`` and the
    in-shell ``cd`` use the same absolute directory.
    r   N)ospath
expandusergetcwdr   r   ntpathisabsabspathisdirr   partslen)r   expandedr%   	candidatecurrentwanted_partscurrent_partss          r   _resolve_local_initial_cwdr0   4   s     +.rww!!#&299;H(2 	6<<!O	ww}}X)IiikG
 77==#H~++W++C-]1CCc,//01\Ar   c                    t         r| s| S t        j                  d|       }|s| S |j                  d      j	                         }|j                  d      xs dj                  dd      j                  d      }|rd| d| S 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_pathr4   \   s     c

+S1A
GGAJEGGAJ"%%dC077<D!%QugQtf7QugQ<7r   r"   c                 \    t         r| s| S t        |       } d| v r| j                  dd      } | S )u  Return *path* in a form safe to embed in a Git Bash script.

    Native ``C:\Users\x`` / ``C:/Users/x`` → ``/c/Users/x`` via
    :func:`_windows_to_msys_path`. Mixed MSYS leftovers
    (``/c/Users\Alexander\Documents``) get backslashes normalized so
    bash does not eat ``\U`` and trip the ``Directory \drivers\etc``
    failure class. No-op off Windows and for empty input.

    ``get_temp_dir`` already emits forward-slash ``C:/...`` forms for
    Python compatibility; those still need the ``/c/...`` rewrite —
    MSYS argument conversion treats ``C:/...`` as a Windows path and
    can corrupt the login-shell ``drivers\etc`` lookup.
    r   r   )r   r4   r   r"   s    r   _bash_safe_pathr7   n   s4     d &Dt|||D#&Kr   c                 >    ddl }|j                  t        |             S )zFQuote *path* for safe interpolation into a Git Bash script on Windows.r   N)shlexquoter7   )r"   r9   s     r   _quote_bash_pathr;      s    ;;t,--r   c                     t         j                  j                  |       xr$ t        j                  | t         j                        S )up  True when *path* is a directory this process can actually chdir into.

    ``os.path.isdir`` alone is not enough: stat() on ``/root`` succeeds for a
    non-root user (only ``/`` needs search permission), but
    ``subprocess.Popen(cwd='/root')`` then dies with ``PermissionError:
    [Errno 13] Permission denied: '/root'``. Seen in the wild when a
    root-launched CLI session leaks ``/root`` into shared state that a
    non-root gateway/cron process later reads (#65583) — every cron job's
    terminal/file tool then fails on every command, forever. Checking
    X_OK up front lets the caller fall back instead.
    )r!   r"   r(   accessX_OKr6   s    r   _cwd_usabler?      s+     77==;299T277#;;r   c           
         t         rt        |       n| } | rt        |       r| S | rKt        j                  j                  |       r,t        j                  d|  t        t        dd                     | rt        j                  j                  |       nd}|rKt        |      r|S t        j                  j                  |      }||k(  r	 t        j                         S |}|rKt        j                         S )uA  Return ``cwd`` if it exists as a directory this process can enter,
    else the nearest existing accessible ancestor.  Falls back to
    ``tempfile.gettempdir()`` only if walking up the path can't find any
    usable 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) — or inaccessible to this user, e.g. ``/root`` leaking
    from a root-launched CLI session into a non-root gateway's cron jobs
    (issue #65583).  Without this guard, ``subprocess.Popen(..., cwd=...)``
    raises ``FileNotFoundError``/``PermissionError`` before bash starts,
    wedging every subsequent terminal call until the gateway restarts.
    u   Configured terminal cwd %r exists but is not accessible to this user (uid=%s) — falling back to the nearest usable directory. If this is a gateway/cron process, check for root-owned paths leaking into terminal.cwd / TERMINAL_CWD (#65583).getuidc                       y)N? rD   r   r   <lambda>z#_resolve_safe_cwd.<locals>.<lambda>       r   r   )r   r   r?   r!   r"   r(   loggerwarninggetattrdirnametempfile
gettempdir)r   parentnext_parents      r   _resolve_safe_cwdrO      s    ( )4

$C
{3

rww}}S!
 3X{35	
 &)RWW__S!bF
vMggoof-&         r   _HERMES_FORCE_AWS_BEARER_TOKEN_BEDROCKc                     t               } 	 ddlm} |j                         D ]i  }| j	                  |j
                         |j                  dk(  r| j	                  t               |j                  sO| j                  |j                         k 	 	 ddlm} |j                         D ]U  \  }}|j                  d      }|dv r| j                  |       -|dk(  s3|j                  d      sE| j                  |       W 	 | j	                  h d	       | j                  d
       t!        |       S # t        $ r Y w xY w# t        $ r Y Iw xY w)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.authrS   valuesupdateapi_key_env_vars	auth_type_AWS_SDK_CREDENTIAL_ENV_VARSbase_url_env_varaddImportErrorhermes_cli.configrU   itemsgetdiscard	frozenset)blockedrS   pconfigrU   namemetadatarV   s          r   _build_provider_env_blocklistr      s/   G	5(//1 	6GNN7334  I-;<''G445	6	7/557 	"ND(||J/H00D!Y&8<<
+CD!	" NN D D\ OO-.W{    s7   A$D. 1D. AD= D= +D= .	D:9D:=	E	E	)VIRTUAL_ENVCONDA_PREFIXkeyc                    | j                         }|j                  d      r#|j                  d      s|j                  d      ry|j                  d      r4|j                  d      s"|j                  d      s|j                  d      ryy	)
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   [  so    B IIKE%z"enn[&A()y!U^^F%;u~~h?Wr   envc                 N    	 ddl m}  |       }|r|| d<   yy# t        $ r Y y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     s6    =(*!&C  s    	$$c                     	 ddl m}m}m}  |       }|j                         D ]A  \  }}|j                         }||ur|dn
t        |      | |<   -|s0| j                  |d       C y# t        $ r Y yw xY w)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!) $#	"'-BSZCM GGHd#$	  s   
A( (	A43A4base_env	extra_envc                 l   	 ddl m} i }| xs i j                         D ]=  \  }}|j	                  t
              rt        |      r(|t        vs	 ||      s9|||<   ? |xs i j                         D ]`  \  }}|j	                  t
              r$|t        t
              d }t        |      r9|||<   ?t        |      rK|t        vs	 ||      s\|||<   b t        |       ddl
m}  ||       t        |       t        D ]  }|j                  |d        t        |       |S # t        $ r d }Y *w xY w)z<Filter Hermes-managed secrets from a subprocess environment.r   is_env_passthroughc                      yNFrD   _s    r   rE   z*_sanitize_subprocess_env.<locals>.<lambda>  rF   r   Napply_subprocess_home_env)tools.env_passthroughr   r   r   r   !_HERMES_PROVIDER_ENV_FORCE_PREFIXr   _HERMES_PROVIDER_ENV_BLOCKLISTr*   r   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     sC   *O !#I~2,,. #
U>>;<%c*448L"IcN# !B--/ 	#
U>>;<3@ABCH)(3"'Ih',66/#:N"IcN	#  	*:i(  	*+ %gt$% *)4M  *)*s   D# #D32D3>   r[   r^   GITHUB_TOKENrb   rf   rh   rl   SLACK_APP_TOKENSLACK_BOT_TOKENrt   DISCORD_BOT_TOKENr   TELEGRAM_BOT_TOKENr   SLACK_SIGNING_SECRETr   GATEWAY_ALLOW_ALL_USERSr   r   r   r   _ALWAYS_STRIP_KEYSF)inherit_credentialsr   c                 "   t         j                  j                         }t        D ]  }|j	                  |d        t        |      D ]H  }|j                  t              r|j	                  |d       +t        |      s7|j	                  |d       J | st        D ]  }|j	                  |d        |j                  dd       t        |       ddlm}  ||       t        D ]  }|j	                  |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     s    @ **//
C " T Cy >>;<GGC',GGC	 1 	CGGC	 NN<%$:c" ,  *#.  $Jr   c            	         t         st        j                  d      xsm t        j                  j                  d      rdndxsH t        j                  j                  d      rdndxs# t        j                  j                  d      xs dS g } t        j                  j                  d      }|r0t        j                  j                  |      r| j                  |       t        j                  j                  dd	      }|r!t        j                  j                  |d
d      nd	}|r~t        j                  j                  |dd      t        j                  j                  |ddd      fD ]8  }t        j                  j                  |      s#|| vs(| j                  |       : t        j                  j                  t        j                  j                  dd      ddd      t        j                  j                  t        j                  j                  dd      ddd      |r#t        j                  j                  |dddd      nd	fD ];  }|st        j                  j                  |      s&|| vs+| j                  |       = t        j                  d      }|r|| vr| j                  |       | D ]N  }t        |      s||k7  r8|r6t        j                  j                  |      rt        j                  d||       |c S  | rNdj                  fd| D              }t               du st        |      rt        t!        | d   |            | d   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)Programsz8HERMES_GIT_BASH_PATH=%s fails to start; using %s instead
c              3   P   K   | ]  }t         j                  |      xr  y wN)_bash_probe_details_cacher   ).0r,   details     r   	<genexpr>z_find_bash.<locals>.<genexpr>  s/      "
377	BBB "
s   #&Tr   zGit 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   appendjoin_bash_startsrG   rH   _mandatory_aslr_enabled_looks_like_msys_spawn_failureRuntimeError_git_bash_aslr_help)
candidatescustom_local_appdata_hermes_portable_gitr,   foundprobe_detailsr	  s          @r   
_find_bashr  j  s   LL  #%77>>/#B!ww~~k: zz~~g& 	
 JZZ^^23F"''..(&! ZZ^^NB7NLZ277<<%H`bGGLL-ujAGGLL-ueZH
 	-I ww~~i(Yj-H!!),	- 	RZZ^^N4GH%QVXbc
RZZ^^$79RSUZ\acmnN\^ZzJbd )	
 	2y
7Ri() LL Ej(%    		"F"v"''..2HN
  		 "
'"
 

 #$,0N1
 2:a=-PQQ
 !}
	A r   _bash_starts_cacher  bool | None_mandatory_aslr_enabled_cachez0/usr/bin/true; /usr/bin/cat --version >/dev/nulldetailsc                 L    | j                         t        fddD              S )zAMatch Git-for-Windows child-launch failures associated with ASLR.c              3   &   K   | ]  }|v  
 y wr  rD   )r  markerlowereds     r   r
  z1_looks_like_msys_spawn_failure.<locals>.<genexpr>  s       	's   )zdofork:zchild_copy:
0xc0000142
0xc0000005)r2   any)r  r#  s    @r   r  r    s*    mmoG 
  r   c                     t         t         S 	 t        j                  d      xs d} t        j                  | ddddgdddt               	      }|j                  d
k7  ry|j                  xs dj                         j                         }|dk(  rda y|dv rda y	 y# t        $ r }t        j                  d|       Y d}~yd}~ww xY w)zEReturn Windows' system-wide ForceRelocateImages state when available.Nzpowershell.exez
-NoProfilez-NonInteractivez-CommandzC(Get-ProcessMitigation -System).Aslr.ForceRelocateImages.ToString()T
   capture_outputtexttimeoutcreationflagsr   r   ON>   OFFNOTSETFz0Could not query Windows Mandatory ASLR state: %s)r  r  r  
subprocessrunr   
returncodestdoutstripr   r   rG   debug)
powershellresultr   excs       r   r  r    s     %0,,N\\"23G7G
!U  ,.
 !$"++-335D=,0)%%,1) &
   NGMMNs$   AB 3B B 	C$B??Cr   c                 l   t        j                  t        j                  |             }t        j                  |      j	                         dk7  rt        j                  |      S t        j                  |      }t        j                  |      j	                         dk(  rt        j                  |      S |S )zAResolve Git's root from either <root>/bin or <root>/usr/bin bash.r   r   )r%   rJ   normpathbasenamer2   )r   bin_dirrM   s      r   _git_root_from_bashr>    s~    nnV__T23Gw%%'50~~g&&^^G$Fv$$&%/~~f%%Mr   c                 l    t        |       }|j                  dd      }|rd|dd  nd}d|  d| d	| d
S )z:Build the targeted per-program Mandatory-ASLR remediation.'z''z
Git Bash probe output: Ni  r   zGit Bash at z cannot launch required MSYS child processes while Windows Mandatory ASLR (ForceRelocateImages) is enabled, or its output matches that Git-for-Windows failure class.zw
Reinstalling Git will not change the Windows mitigation policy. Open PowerShell as Administrator and run:
$gitRoot = 'a=  '
Get-Item "$gitRoot\bin\bash.exe", "$gitRoot\usr\bin\*.exe" -ErrorAction SilentlyContinue | ForEach-Object { Set-ProcessMitigation -Name $_.FullName -Disable ForceRelocateImages }
Then restart Hermes. If the override is blocked or later re-applied, ask your Windows administrator to allow this per-program exception.)r>  r   )r   r  git_rootescaped_rootdetail_lines        r   r  r    sd    "4(H##C.LAH-gdsm_=bK
tf 66A] C $n %N
	Nr   c                 $   t         j                  |       }||S 	 t        j                  | dddt        gdddt
        r
t               nd      }|j                  dk(  }|sd|j                  xs d	 |j                  xs d	 }|j                         dd
 t        | <   t        j                  d| |j                         dd        |t         | <   |S # t        $ r8}t        |      dd
 t        | <   t        j                  d| |       d}Y d}~Gd}~ww xY w)a  True if *bash* can launch external MSYS programs.

    Uses ``--noprofile --norc`` so a broken login post-install
    (``Directory \drivers\etc``) does not falsely condemn an otherwise
    usable bash. The external ``true`` and ``cat`` calls are intentional:
    a builtin-only ``exit 0`` probe misses Git-for-Windows fork/spawn failures
    under system-wide Mandatory ASLR. Cached per path for the process lifetime.
    Nz--noprofilez--norc-cT   r   r)  r   i  zbash probe failed for %s: %s   zbash probe error for %s: %sF)r  r   r1  r2  _BASH_EXTERNAL_PROGRAM_PROBEr   r   r3  r4  stderrr5  r  rG   r6  r   r   )r   cachedr8  okcombinedr9  s         r   r  r    s     ##D)F=(D2NO2=,.1
 !# ---2.v}}/B.CDH.6nn.>u.E%d+LL7x~~?OPTQT?UV  "tI  *-c(5D/!$'2D#>s   B(C 	D.D

Dzlist[str] | None_git_bash_bin_dirs_cachec                  ^   t         t         S t        sg a t         S g } 	 t               }t        j
                  j                  |      }t        j
                  j                  |      }t        j
                  j                  |      j                         dk(  rt        j
                  j                  |      n|}t        j
                  j                  |dd      t        j
                  j                  |dd      t        j
                  j                  |ddd      t        j
                  j                  |dd      t        j
                  j                  |d      fD ]8  }t        j
                  j                  |      s#|| vs(| j                  |       : | a | S # t        $ r g a t         cY S w xY w)u  Git Bash's coreutils/binary dirs, in ``/etc/profile`` precedence order.

    A non-login ``bash -c`` (the fallback used when ``bash -l`` is broken —
    the classic Windows ``Directory \drivers\etc does not exist`` failure)
    never sources ``/etc/profile``, so it never gets ``…\usr\bin`` on PATH.
    That directory holds every coreutil the file/terminal tools shell out to
    (``cat``, ``mktemp``, ``mv``, ``wc``, ``head``, ``stat``, ``chmod``,
    ``mkdir``, ``find`` …).  Without it, ``write_file`` fails with an empty
    error (the failure text went to a missing binary's stderr) and terminal
    commands exit 127.  We derive these dirs from the resolved ``bash.exe`` so
    the fallback shell can find coreutils regardless of the login shell.

    Returns ``[]`` off Windows or when bash can't be located.  Dirs are
    returned in the order Git Bash's own ``/etc/profile`` prepends them
    (mingw first, then usr/bin, then bin) and only if they exist on disk.
    r   mingw64r   mingw32local)rM  r   r  r   r!   r"   rJ   r<  r2   r  r(   r  )dirsr   r=  rM   rootr,   s         r   _git_bash_bin_dirsrT  ?  sP   $  +''#% ''D(|
 ggood#GWW__W%F&(gg&6&6v&>&D&D&F%&O277??6"U[D
 	T9e,
T9e,
T5'51
T5%(
T5! #	 77==#	(=KK	"#  $K-  (#% ''(s   
F F,+F,existing_pathc                     t               }|s| S t        j                  }| r"| j                  |      D cg c]  }|s|	 c}ng }|D cg c]	  }||vs| }}|s| S |j	                  g ||      S c c}w c c}w )a  Prepend Git Bash's binary dirs to ``existing_path`` if missing.

    No-op off Windows or when the dirs can't be resolved.  First-occurrence
    wins, so a PATH that already lists a dir keeps its position.  This is what
    lets the non-login ``bash -c`` fallback find coreutils; in the healthy
    case the session snapshot re-exports the full login PATH inside the shell,
    so this only matters when that snapshot is absent.
    )rT  r!   pathsepsplitr  )rU  git_dirssepeentriesdmissings          r   _prepend_git_bash_dirsr_  t  s     "#H
**C<I---c28Qaq8rG"7Qaw&6q7G788(g(())	 97s   A2A2		A7A7>   shkshzshr   dashmkshc                  $   t         st        j                  j                  d      } | r`t        j                  j                  |       rAt        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  r=   r>   r   r   _SPAWN_COMPATIBLE_SHELLSr  )
user_shells    r   _find_shellrh    s`    < ZZ^^G,
z*		*bgg.Z %%)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
                  j                  |      } | t        j                  rt        j                  d   nd}t        j
                  j                  |      j                         }t        j
                  j                  |      rT|dk(  s|j                  d      r>t        j
                  j                  |      rt        j
                  j                  |      } | t        j                  r-t        j
                  j                  t        j                        nd}|rIt        rdnd}t        j
                  j                  t        j
                  j!                  ||            r|} | r!t        j
                  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)ri  	_SENTINELr  r  r!   r"   rJ   sysargvr<  r2   r&   r   r  
executabler   r  r(   )r,   r  argv0baseexe_dirshims         r   _resolve_hermes_bin_dirrs    s-   0 i' ILL"EGGOOE*	"xxRww&,,.GGMM% !T__Y%?u%.I58^^"''//#..1#.<HDww~~bggll7D9:#	y1	Or   c                     t               }|s| S t        j                  }| r"| j                  |      D cg c]  }|s|	 c}ng }||v r| S |j	                  |g|      S c c}w )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.
    )rs  r!   rW  rX  r  )rU  r=  rZ  r[  r\  s        r   _prepend_hermes_bin_dirru    si     &'G
**C<I---c28Qaq8rG'88W'w'(( 9s
   AAc                 ~   t         r| S t        j                  d      D cg c]  }|s|	 }}| sdj                  |      S t	               }g }| j                  d      D ]+  }|r||v r
|j                  |       |j                  |       - |D ]  }||vs|j                  |        dj                  |      S c c}w )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).
    r   )r   
_SANE_PATHrX  r  r   r   r  )rU  entrysane_entriesseenordered_entriess        r   !_append_missing_sane_path_entriesr|    s    , '1'7'7'<FeEFLFxx%% UD!#O$$S) &u%	&  *""5)* 88O$$) Gs
   B:B:c                 Z    t         sy| j                  dd       | j                  dd       y)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   ?  s(    " NN%s+NN(#.r   run_envc                 N    t         sy| D ]  }|j                         dk(  s|c S  y)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  V  s1      99;& J r   c                 v   	 ddl m} t        t        j
                  | z        }i }|j                         D ]`  \  }}|j                  t              r$|t        t              d }t        |      r9|||<   ?t        |      rK|t        vs	 ||      s\|||<   b t        |      }|4t        |j                  |d            }t        |      }t!        |      ||<   t#        |       ddlm}	  |	|       t)        |       t*        D ]  }
|j-                  |
d        t/        |       |S # t        $ r d }Y /w xY w)zDBuild a run environment with a sane PATH and provider-var stripping.r   r   c                      yr   rD   r   s    r   rE   z_make_run_env.<locals>.<lambda>m  rF   r   Nr   r   )r   r   r   dictr!   r   r   r   r   r*   r   r   r  r|  r   r_  ru  r   r   r   r   r   r   r   )r   r   mergedr  kvr   path_keynew_pathr   r   s              r   _make_run_envr  h  s:   *O "**s"#FG 	1<<9:>?@AH)(3 !GH'*448JGAJ	 W%H4W[[25NO *(3 4H=(:g&
  (+ #GT"# *'2N[  *)*s   D( (D87D8c                  @   	 ddl m}   |        xs i }|j                  d      xs i }|j                  d      xs g }t        |t              sg }t        |j                  dd            }|D cg c]  }|st        |       c}|fS c c}w # 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_bashrcT)r   r  r   
isinstancer   boolr   r   )r  cfgterminal_cfgfilesauto_bashrcfs         r    _read_terminal_shell_init_configr    s    1m!rwwz*0b  !34:%&E<++,@$GH %+1A+[88+ 4xs*   A,B .B6BB B BBc                     t               \  } }g }| r|j                  |        n|rt        s|j                  g d       g }|D ]s  }	 t        j                  j                  t        j                  j                  |            }|sCt        j                  j                  |      sc|j                  |       u |S # t        $ r Y w xY w)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"   
expandvarsr#   r   r  r  )explicitr  r  resolvedrawr"   s         r   _resolve_shell_init_filesr    s     =>HkJ(#	[  	HIH "	77%%bgg&8&8&=>D BGGNN4(OOD!" O	  		s   <B;;	CC
cmd_stringr  c                     |s| S dg}|D ],  }|j                  dd      }|j                  d| d| d       . dj                  |      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 +er@  z'\''z[ -r 'z
' ] && . 'z' 2>/dev/null || truer  )r   r  r  )r  r  prelude_partsr"   safepreludes         r   _prepend_shell_initr    su     JM S ||C)vdV:dV;PQRS 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
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.
    Nr   r,  r   c                 `    t        |      }t        | 	  |||       | j                          y )N)r   r,  r   )r0   super__init__init_session)selfr   r,  r   	__class__s       r   r  zLocalEnvironment.__init__  s-    (-S's;r   r	   c                    t         rB	 ddlm}  |       dz  dz  }|j                  dd       t        |      j                  dd	      S d
D ]i  }| j                  j                  |      xs t        j                  j                  |      }|sB|j                  d	      sT|j                  d	      xs d	c S  t        j                   j#                  d      r6t        j$                  dt        j&                  t        j(                  z        ryt        j                         }|j                  d	      r|j                  d	      xs d	S y# t        $ r$ t	        t        j                               dz  }Y Xw xY w)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   rK   rL   mkdirr   r   r   r   r!   r   r   rstripr"   r(   r=   W_OKr>   )r  r  	cache_direnv_varr,   s        r   get_temp_dirzLocalEnvironment.get_temp_dir  s4   & L<+-7*D	 OOD4O8y>))$440 	4GW-H1HIY11#6 '',33	4
 77== RYYvrww7H%I'')	$##C(/C/%  L !4!4!67:KK	Ls   E )E43E4c                 >    t        j                  t        |             S )z@Use native paths for Python, but Git Bash-friendly paths for cd.)r   _quote_cwd_for_cdr4   )r   s    r   r  z"LocalEnvironment._quote_cwd_for_cd,  s     001Fs1KLLr   r"   c                     t        |      S )z?Rewrite native/mixed Windows paths before quoting for Git Bash.)r;   )r  r"   s     r   _quote_shell_pathz"LocalEnvironment._quote_shell_path1  s    %%r   Fx   )loginr,  
stdin_datar  r  r  c                
   t               }|rt               }|rt        ||      }|r|dd|gn|d|g}t        | j                        }t        | j                        }	|	| j                  k7  rTt        rt        | j                        n| j                  }
|	|
k7  r!t        j                  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        s%	 t#        j$                  |j&                        |_        |t-        ||       |S # t*        $ r Y w xY w)	Nz-lrE  zaLocalEnvironment cwd %r is missing on disk; falling back to %r so terminal commands keep working.r-  Tzutf-8r   )	r+  r   encodingerrorsr4  rI  stdinstart_new_sessionr   )r  r  r  r  r   rO   r   r   r   rG   rH   r   r1  PopenPIPESTDOUTDEVNULLr!   getpgidpid_hermes_pgidProcessLookupErrorr   )r  r  r  r,  r  r   
init_filesargsr  safe_cwd
normalized
_popen_cwd_popen_kwargsprocs                 r   	_run_bashzLocalEnvironment._run_bash5  so    | 24J0ZH
16dD*-T4<T) %TXX.txx =H.txx8TXXJ:%LHH	  DHXX
CN*<*>?TV
??$$%/%;*//ASAS"
 
 $&JJtxx$8! !j) & s   $E6 6	FFc                    dt         dt        fddt         dt        dt        ffd}	 t        r/	 ddlm}  |j                  d	       	 j                  d
       y	 t        j                  j                        }	 t        j"                  |t$        j&                          ||d      ry	 t        j"                  |t$        j(                          ||d
       	 j                  d       y# t        $ r j                          Y w xY w# t        j                  t        f$ r Y yw xY w# t        $ r t!        dd      }| Y w xY w# t        $ r Y yw xY w# t        $ r Y yw xY w# t        j                  t        f$ r Y yw xY w# t        t*        t        f$ r$ 	 j                          Y y# t        $ r Y Y yw xY ww xY w)z-Kill the entire process group (all children).pgidr	   c                 d    	 t        j                  | d       y# t        $ r Y yt        $ r Y yw xY w)Nr   TF)r!   killpgr  PermissionError)r  s    r   _group_alivez4LocalEnvironment._kill_process.<locals>._group_alive~  s5    		$"% " s    	///r,  c                 ^   t        j                         |z   }t        j                         |k  rG	 j                           |       syt        j                  d       t        j                         |k  rG	 j                           |        S # t        $ r Y [w xY w# t        $ r Y #w xY w)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-IIK $D)

4  .."X-		 $D))) !   s#   B 8B  	BB 	B,+B,r   )terminate_pidT)forceg       @)r,  r  Ng      ?g?)intr  floatr   gateway.statusr  r  r   killwaitr1  TimeoutExpiredOSErrorr!   r  r  rI   r  signalSIGTERMSIGKILLr  )r  r  r  r  r  r  s    `   @r   _kill_processzLocalEnvironment._kill_process{  s   		s 		t 			*s 	*U 	*t 	*$-	 <!$(($7IIcI*::dhh/DIIdFNN3 (c2IIdFNN3 %T3/IIcI*A !  IIK  #117; 
 * "4>D| $ *  * 
 #117; "OW= 			 	s   F C3 D #D1 $E '	F 2$E 	F  E. 3DF DF D.+F -D..F 1E
F EF 	EF EF 	E+(F *E++F .F
F 	F

F G"F44	G=G GGr8  c                 &    | j                  |       y)a  Update cwd from the stdout marker emitted by the wrapped command.

        The base command wrapper already appends ``pwd -P`` to stdout inside a
        session-specific marker, so the local backend can share the same parser
        as remote backends instead of re-reading the temp file it just wrote.
        ``_extract_cwd_from_output`` keeps the local Windows normalization and
        stale-path rollback semantics intact.
        N)_extract_cwd_from_output)r  r8  s     r   _update_cwdzLocalEnvironment._update_cwd  s     	%%f-r   c                 
   | j                   }t        | 	  |       | j                   |k7  rXt        rt	        | j                         n| j                   }|r't
        j                  j                  |      r|| _         y|| _         yy)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  r  r   r   r!   r"   r(   )r  r8  prev_cwdr  r  s       r   r  z)LocalEnvironment._extract_cwd_from_output  se     88(088x<G.txx8TXXJbggmmJ7% $  r   c                 @   | j                   | j                  fD ]  }	 t        j                  |        	 ddl}|j                  | j                    d      D ]  }	 t        j                  |        y# t        $ r Y ew xY w# t        $ r Y 6w xY w# t        $ r Y yw xY w)zClean up temp files.r   Nz.tmp.*)_snapshot_path	_cwd_filer!   unlinkr  globr   )r  r  r  tmps       r   cleanupzLocalEnvironment.cleanup  s    %%t~~6 	A		!		yyD$7$7#8!?@ IIcN     		sF   A3%B B0B 3	A?>A?	BB BB 	BB)r   <   N)__name__
__module____qualname____doc__r   r  r  r  r  staticmethodr  r  r  r1  r  r  r  r  r  r   __classcell__)r  s   @r   r  r    s    C s d 
.c .` Ms Ms M M&c &c & ;@!$+/DC D4 DD!DjD4>4D4DDLM^	.$ 	.$t $0r   r  r  )r	   r  )r   )Nr  loggingr%   r!   platformr   r  r  r1  rl  rK   r  pathlibr   tools.environments.baser   r   hermes_cli._subprocess_compatr   systemr   	getLoggerr  rG   r   r   r0   r4   r7   r;   r  r?   rO   r   r   r   r   r   r   r   r  r   r   r   r   __annotations__r   r  r  r  r  rH  r  r  r>  r  r  rM  r   rT  r_  rf  rh  rw  objectrk  ri  rs  ru  r|  r   r  r  tupler  r  r  r  rD   r   r   <module>r     s   K   	  	    
    @ <hoo9,			8	$(s (s (8%C %C %P8s 8s 8$# # ,.3 .3 .<c <d <*!3 *!3 *!\ %5 !&  )*   
iy iX "?!@  ; *C *D *Z	T 	d 	,$T ,$d ,$^*td{ *td{ *VZ *j &/  0  & IcN  F :? L$ L4S> L^UC Up ') DdO (,. 4S> ./3 } 3Q C D  Fc c c C  ( s  t  F 04 , 32DI 2j*# *# *0 %%QR 'S 'XC  H	)2& 26t 6r)3 )3 )"-%S -%S -%`/t / /.4 C$J $1t 1 1h%S	4*@ ('49 'T C  S	  c  *O Or   