
    .cj6                    h   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*dJde(dz  de(dz  de(fdZ+de(eef         de(eef         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$Z0i Z1e(eef         e.d%<   i Z2e(eef         e.d&<   da3d'e.d(<   d)Z4d*edefd+Z5dKd,Z6d-edefd.Z7dLd-ed*edefd0Z8d-edefd1Z9da:d2e.d3<   de;e         fd4Z<d5edefd6Z= e"h d7          Z>defd8Z?d9Z@ eA            ZBeBaCd:e.d;<   dedz  fd<ZDd5edefd=ZEd5edefd>ZFde(ddfd?ZGd@e(dedz  fdAZHde(de(fdBZIdeJe;e         ef         fdCZKde;e         fdDZLdEedFe;e         defdGZM G dH dIe          ZNdS )MuE   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.

    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       :/home/ice/.hermes/hermes-agent/tools/environments/local.py_msys_to_windows_pathr      s      c 
 	=sCCA 
 GGAJJEGGAJJ"%%c400D''doc"gg'''    c                 z   | rt           j                            |           nt          j                    }t          r%t          |          }ddl} |j        |          r|S t           j                            |          r|S t           j                            |          }t          j                    }t           j        	                    |          sht          |          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    +.>rw!!#&&&29;;H (22 	6<!! 	O	w}}X ))IikkG
 7==## H~~+W+ 	C--]1C1CCCc,///001\AAr   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_pathr4   \   s      c 

+S11A 
GGAJJEGGAJJ"%%dC0077<<D!%7ut<u<<<7r   r"   c                 n    t           r| s| S t          |           } d| v r|                     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   sF      d  &&Dt||||D#&&Kr   c                 N    ddl }|                    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%    LLL;;t,,---r   c                 ~    t           j                            |           o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+     7==;29T27#;#;;r   c           
         t           rt          |           n| } | rt          |           r| S | rXt          j                            |           r9t                              d|  t          t          dd                                  | rt          j        	                    |           nd}|r;t          |          r|S t          j        	                    |          }||k    rn|}|;t          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                      dS )N? rD   r   r   <lambda>z#_resolve_safe_cwd.<locals>.<lambda>   s    s r   r   )r   r   r?   r!   r"   r(   loggerwarninggetattrdirnametempfile
gettempdir)r   parentnext_parents      r   _resolve_safe_cwdrN      s   ( )4
<

$
$
$C
 {3 

 
rw}}S!! 

 3X{{3355	
 	
 	
 &)0RW__S!!!bF
 v 	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.authrR   valuesupdateapi_key_env_vars	auth_type_AWS_SDK_CREDENTIAL_ENV_VARSbase_url_env_varaddImportErrorhermes_cli.configrT   itemsgetdiscard	frozenset)blockedrR   pconfigrT   namemetadatarU   s          r   _build_provider_env_blocklistr      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          |           t!          |          }|S )z<Filter Hermes-managed secrets from a subprocess environment.r   is_env_passthroughc                     dS NFrD   _s    r   rE   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_BLOCKLISTr*   r   r   r   r   _ACTIVE_VENV_MARKER_VARSr   %_apply_windows_msys_bash_env_defaults!_scrub_delegated_child_kanban_env)	r   r   _is_passthrough	sanitizedr   r   real_keyr   _markers	            r   _sanitize_subprocess_envr     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1)<<I   	 c                 d    	 ddl m}m}  |            r ||           S n# t          $ r Y nw xY w| S )zHStrip dispatcher-owned Kanban env from delegate_task child subprocesses.r   )"is_delegated_child_process_contextscrub_kanban_env)agent.delegation_contextr   r   r   )r   r   r   s      r   r   r     s}    		
 	
 	
 	
 	
 	
 	
 	

 .-// 	)##C(((	)   Js     
-->   rZ   r]   GITHUB_TOKENra   re   rg   rk   SLACK_APP_TOKENSLACK_BOT_TOKENrs   DISCORD_BOT_TOKENr~   TELEGRAM_BOT_TOKENr   SLACK_SIGNING_SECRETr   GATEWAY_ALLOW_ALL_USERSr   r   r   r   _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!          |           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   r   s        r   hermes_subprocess_envr   ,  su   @ *//

C "  T Cyy  >>;<< 	GGC',, 	GGC 1 	 	CGGC NN<%%%$$$::::::c""" ,  )#...  $$$ ,C
0
0C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 g } t          j                            d          }|r4t          j                            |          r|                     |           t          j                            dd	          }|r!t          j        	                    |d
d          nd	}|r~t          j        	                    |dd          t          j        	                    |ddd          fD ]:}t          j                            |          r|| vr|                     |           ;t          j        	                    t          j                            dd          ddd          t          j        	                    t          j                            dd          ddd          |r#t          j        	                    |dddd          nd	fD ]<}|r8t          j                            |          r|| vr|                     |           =t          j        d          }|r|| vr|                     |           | D ]X}t          |          rG||k    r=|r;t          j                            |          rt                              d||           |c S Y| rkd	                    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   T   K   | ]"}t                               |          xV  #d S N)_bash_probe_details_cacher   ).0r,   details     r   	<genexpr>z_find_bash.<locals>.<genexpr>  sP       "
 "
377	BBB"
"
 "
 "
 "
 "
 "
r   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_startsrF   rG   _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!    s    
L   #%7>>/#B#BL!w~~k::D z~~g&& 	
 JZ^^233F ""'..(( "&!!! Z^^NB77NLZb27<<%HHH`b -GLL-ujAAGLL-ueZHH
 	- 	-I w~~i(( -Yj-H-H!!),,, 	RZ^^N4GHH%QVXbcc
RZ^^$79RSSUZ\acmnnN\d^ZzJJJbd ) )	
  	)	22 	)y
7R7Ri(((L  E !j((%       		"" 	F""v""'..2H2H"N  
 	  		 "
 "
 "
 "
'"
 "
 "
 
 

 #$$,,0N1
 1
, 2:a=-PPQQQ
 !}
	A  r   _bash_starts_cacher  bool | None_mandatory_aslr_enabled_cachez0/usr/bin/true; /usr/bin/cat --version >/dev/nulldetailsc                 b    |                                  t          fddD                       S )zAMatch Git-for-Windows child-launch failures associated with ASLR.c              3       K   | ]}|v V  	d S r  rD   )r  markerlowereds     r   r  z1_looks_like_msys_spawn_failure.<locals>.<genexpr>  s<         	'     r   )zdofork:zchild_copy:
0xc0000142
0xc0000005)r2   any)r%  r)  s    @r   r  r    sJ    mmooG    
     r   c                     t           t           S 	 t          j        d          pd} t          j        | ddddgdddt                      	          }|j        d
k    rdS |j        pd                                	                                }|dk    rda dS |dv rda dS n2# t          $ r%}t                              d|           Y d}~nd}~ww xY wdS )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   rF   debug)
powershellresultr   excs       r   r  r    s!    %0,,N\"233G7G
!U  ,..
 
 
 !!4$"++--3355D==,0)4%%%,1)5 &  N N NGMMMMMMMMN4s$   AB 5B B 
C'CCr   c                 r   t          j        t          j        |                     }t          j        |                                          dk    rt          j        |          S t          j        |          }t          j        |                                          dk    rt          j        |          S |S )zAResolve Git's root from either <root>/bin or <root>/usr/bin bash.r  r  )r%   rI   normpathbasenamer2   )r   bin_dirrL   s      r   _git_root_from_bashrD    s    nV_T2233Gw%%''500~g&&&^G$$Fv$$&&%//~f%%%Mr   r   c                     t          |           }|                    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.)rD  r   )r   r%  git_rootescaped_rootdetail_lines        r   r  r    s    "4((H##C..LAHP=gdsdm===bK
	Nt 
	N 
	N6A
	N 
	N
 $
	N 
	N 
	Nr   c                 h   t                               |           }||S 	 t          j        | dddt          gdddt
          rt                      nd          }|j        dk    }|sn|j        pd	 |j	        pd	 }|
                                dd
         t          | <   t                              d| |
                                dd                    nT# t          $ rG}t          |          dd
         t          | <   t                              d| |           d}Y d}~nd}~ww xY w|t           | <   |S )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   r7  r8  _BASH_EXTERNAL_PROGRAM_PROBEr   r   r9  r:  stderrr;  r  rF   r<  r   r   )r   cachedr>  okcombinedr?  s         r   r  r  0  s[     ##D))F=(D2NO2=D,...1
 
 
 !# 	W --2Dv}/BDDH.6nn.>.>uu.E%d+LL7x~~?O?OPTQTPT?UVVV   *-c((5D5/!$'2D#>>>
  "tIs   B3C 
D%=D  D%zlist[str] | None_git_bash_bin_dirs_cachec                  ~   t           t           S t          s	g a t           S g } 	 t                      }n# t          $ r g a t           cY S w xY wt          j                            |          }t          j                            |          }t          j                            |                                          dk    rt          j                            |          n|}t          j        	                    |dd          t          j        	                    |dd          t          j        	                    |ddd          t          j        	                    |dd          t          j        	                    |d          fD ]:}t          j        
                    |          r|| vr|                     |           ;| a | S )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.
    Nr  mingw64r  mingw32local)rS  r   r!  r   r!   r"   rI   rB  r2   r  r(   r  )dirsr   rC  rL   rootr,   s         r   _git_bash_bin_dirsrZ  V  s   $  +'' (#% ''D(|| ( ( (#% ''''( good##GW__W%%F&(g&6&6v&>&>&D&D&F&F%&O&O27??6"""U[D
 	T9e,,
T9e,,
T5'511
T5%((
T5!! # #	 7==## 	#	(=(=KK	"""#Ks   1 AAexisting_pathc                     t                      }|s| S t          j        }| rd |                     |          D             ng fd|D             }|s| S |                    g |          S )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.
    c                     g | ]}||S rD   rD   r  es     r   
<listcomp>z*_prepend_git_bash_dirs.<locals>.<listcomp>      888Qa8q888r   c                     g | ]}|v|	S rD   rD   )r  dentriess     r   r`  z*_prepend_git_bash_dirs.<locals>.<listcomp>  s#    777Qaw&6&6q&6&6&6r   )rZ  r!   pathsepsplitr  )r[  git_dirssepmissingrd  s       @r   _prepend_git_bash_dirsrj    s     "##H 
*C<IQ88---c228888rG7777(777G 88(g(()))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  r=   r>   r   r   _SPAWN_COMPATIBLE_SHELLSr!  )
user_shells    r   _find_shellrs    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)rt  	_SENTINELr  r  r!   r"   rI   sysargvrB  r2   r&   r   r  
executabler   r  r(   )r,   r  argv0baseexe_dirshims         r   _resolve_hermes_bin_dirr~    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   c                     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 rD   rD   r^  s     r   r`  z+_prepend_hermes_bin_dir.<locals>.<listcomp>   ra  r   )r~  r!   re  rf  r  )r[  rC  rh  rd  s       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 rD   rD   )r  entrys     r   r`  z5_append_missing_sane_path_entries.<locals>.<listcomp>?  s    FFFeFEFFFr   r   )r   
_SANE_PATHrf  r  r   r   r  )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   V  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  m  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          |          }|Dt          |                    |d                    }t          |          }t!          |          ||<   t#          |           ddlm}	  |	|           t)          |           t*          D ]}
|                    |
d           t/          |           t1          |          }|S )zDBuild a run environment with a sane PATH and provider-var stripping.r   r   c                     dS r   rD   r   s    r   rE   z_make_run_env.<locals>.<lambda>  r   r   Nr   r   )r   r   r   dictr!   r   r   r   r   r*   r   r   r  r  r   rj  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 *(33 4H==(((::::::g&&&
  (((+ # #GT"""")'222/88G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 rD   )r   )r  fs     r   r`  z4_read_terminal_shell_init_config.<locals>.<listcomp>  s#    +++1+A+++r   )r   r  r   
isinstancer   boolr   )r  cfgterminal_cfgfilesauto_bashrcs        r    _read_terminal_shell_init_configr    s    111111kmm!rwwz**0b  !344:%&& 	E<++,@$GGHH+++++[88   4xs   BB
 
BBc                     t                      \  } }g }| r|                    |            n |rt          s|                    g d           g }|D ]}	 t          j                            t          j                            |                    }n# t          $ r Y Lw xY w|r4t          j                            |          r|	                    |           |S )uh  Resolve the list of files to source before the login-shell snapshot.

    Expands ``~`` and ``${VAR}`` references and drops anything that doesn't
    exist on disk, so a missing ``~/.bashrc`` never breaks the snapshot.
    The ``auto_source_bashrc`` path runs only when the user hasn't supplied
    an explicit list — once they have, Hermes trusts them.
    )z
~/.profilez~/.bash_profilez	~/.bashrc)
r  extendr   r!   r"   
expandvarsr#   r   r  r  )explicitr  r  resolvedrawr"   s         r   _resolve_shell_init_filesr    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 +erF  z'\''z[ -r 'z
' ] && . 'z' 2>/dev/null || truer
  )r   r  r  )r  r  prelude_partsr"   safepreludes         r   _prepend_shell_initr    s      JM S S ||C))QdQQdQQQRRRRii&&-GZr   c                        e Zd ZdZddededef fdZd	efd
Z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.
    r   <   Nr   r2  r   c                     t          |          }t                                          |||           |                                  d S )N)r   r2  r   )r0   super__init__init_session)selfr   r2  r   	__class__s       r   r  zLocalEnvironment.__init__  sG    (--S's;;;r   r	   c                    t           r	 ddlm}  |            dz  dz  }n3# t          $ r& t	          t          j                              dz  }Y nw xY w|                    dd           t          |          	                    dd	          S d
D ]k}| j
                            |          pt          j                            |          }|r.|                    d	          r|                    d	          pd	c S lt          j                            d          r.t          j        dt          j        t          j        z            rdS t          j                    }|                    d	          r|                    d	          pd	S dS )u  Return a shell-safe writable temp dir for local execution.

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

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

        **Windows:** hardcoded ``/tmp`` is wrong in two ways — native Python
        can't open the path, and the Windows default temp (``%TEMP%``) often
        contains spaces (``C:\Users\Some Name\AppData\Local\Temp``) that
        break unquoted bash interpolations.  Use a dedicated cache dir under
        ``HERMES_HOME`` instead — single-word path, guaranteed to exist, same
        string resolves in both Git Bash and native Python.
        r   )get_hermes_homecacher  hermes_terminalT)parentsexist_okr   r   )TMPDIRTMPTEMPz/tmp)r   r   r  r   r   rJ   rK   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  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_cdr4   )r   s    r   r  z"LocalEnvironment._quote_cwd_for_cdE  s     01Fs1K1KL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_pathJ  s    %%%r   Fx   )loginr2  
stdin_datar  r  r  c                   t                      }|r t                      }|rt          ||          }|r|dd|gn|d|g}t          | j                  }t          | j                  }	|	| j        k    rPt          rt          | j                  n| j        }
|	|
k    r!t          
                    d| j        |	           |	| _        | j        }t          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-lrK  zaLocalEnvironment cwd %r is missing on disk; falling back to %r so terminal commands keep working.r3  Tzutf-8r   )	r1  r   encodingerrorsr:  rO  stdinstart_new_sessionr   )r!  r  r  r  r   rN   r   r   r   rF   rG   r   r7  PopenPIPESTDOUTDEVNULLr!   getpgidpid_hermes_pgidProcessLookupErrorr   )r  r  r  r2  r  r   
init_filesargsr  safe_cwd
normalized
_popen_cwd_popen_kwargsprocs                 r   	_run_bashzLocalEnvironment._run_bashN  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r2  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  r2  deadliner  r  s      r   _wait_for_group_exitz<LocalEnvironment._kill_process.<locals>._wait_for_group_exit  s    ~'''1H.""X--IIKKKK    D#|D))  4
4    .""X--		   #|D))))s#   A 
AAB# #
B0/B0r   )terminate_pidT)forceg       @)r2  r  Ng      ?g?)intr  floatr   gateway.statusr  r  r   killwaitr7  TimeoutExpiredOSErrorr!   r  r  rH   r  signalSIGTERMSIGKILLr  )r  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"r>  c                 0    |                      |           dS )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  r>  s     r   _update_cwdzLocalEnvironment._update_cwd  s     	%%f-----r   c                    | j         }t                                          |           | j         |k    rUt          rt	          | j                   n| j         }|r(t
          j                            |          r	|| _         dS || _         dS dS )u  Same semantics as the base class, but on Windows the value
        emitted by ``pwd -P`` inside Git Bash is in MSYS form
        (``/c/Users/x``). Normalize to native Windows form and validate
        the directory exists before assigning to ``self.cwd`` — otherwise
        ``_run_bash``'s safe-cwd recovery would warn on every subsequent
        command.

        Always defers to the base class for stripping the marker text from
        ``result["output"]`` so output formatting is identical.
        N)r   r  r  r   r   r!   r"   r(   )r  r>  prev_cwdr  r  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_path	_cwd_filer!   unlinkr  globr   )r  r  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   r  N)__name__
__module____qualname____doc__r   r  r  r  r  staticmethodr  r  r  r7  r  r  r  r  r  r  __classcell__)r  s   @r   r  r    s         C s d      
.c . . . .` Ms Ms M M M \M&c &c & & & & ;@!$+/D D DC D4 DD!DjD4>4DD D D DLM M M^	.$ 	. 	. 	. 	.$t $ $ $ $ $ $0      r   r  r  )r	   r#  )r   )Or  loggingr%   r!   platformr   r  r   r7  rw  rJ   r  pathlibr   tools.environments.baser   r   hermes_cli._subprocess_compatr   systemr   	getLoggerr  rF   r   r   r0   r4   r7   r;   r  r?   rN   r   r   r   r   r   r   r   r  r   r   r   r   r   __annotations__r   r!  r"  r  r$  rN  r  r  rD  r  r  rS  r   rZ  rj  rq  rs  r  objectrv  rt  r~  r  r  r   r  r  tupler  r  r  r  rD   r   r   <module>r      s   K K K   				  				       



         @ @ @ @ @ @ @ @ < < < < < <ho9,		8	$	$(s (s ( ( ( (8%C %C % % % %P8s 8s 8 8 8 8$# #    ,.3 .3 . . . .<c <d < < < <*!3 *!3 *! *! *! *!\ %5 !&  )y*     
iy i i i iX "?!>!@!@  ; *C *D * * * *Z	T 	d 	 	 	 	,$T ,$d ,$ ,$ ,$ ,$^, ,td{ ,td{ ,VZ , , , ,^4S> d38n    . &/Y  0  0  0  &  & IcN      F :? R R R$ R4S> R R R RjUC U U U Up ') DdO ( ( (,. 4S> . . ./3 } 3 3 3Q C D           Fc c     c C     ( s  t        F 04 , 3 3 32DI 2 2 2 2j*# *# * * * *0 %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    $3t 3 3 3 3 3l%S	4*@    ('49 ' ' ' 'T C  S	  c        *O O O O O O O O O Or   