Ë
    ãß²j´|  ã                   ó®  — 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m	Z	m
Z
 ddlmZ ddlmZmZ ddlmZ ddlmZmZmZ ddlmZmZmZmZ dd	lmZmZmZmZm Z m!Z! dd
l"m#Z# ddl$m%Z%  ejL                  e'«      Z( e%d«      Z)dZ*e)re(jW                  ejX                  «        ejZ                  «       Z. G d„ de/«      Z0dZ1 G d„ de/«      Z2dee3gdf   dz  ddfd„Z4dee3gdf   dz  fd„Z5de6de3ddfd„Z7defd„Z8dede6fd„Z9dede6ddfd„Z:de3de;e<e=f   dz  fd „Z> G d!„ d"e	«      Z?dd#lm@Z@ dd$lmAZA ddlBZBdd%lCmDZD ddlEZEddlFZFddlGZGd&d'd(œZHd)„ ZIy)*a³  Base class for all Hermes execution environment backends.

Unified spawn-per-call model: every command spawns a fresh ``bash -c`` process.
A session snapshot (env vars, functions, aliases) is captured once at init and
re-sourced before each command. CWD persists via in-band stdout markers (remote)
or a temp file (local). Cohesive pieces live in sibling modules (``base_output``,
``base_session_env``, ``base_wait``, ``path_utils``).
é    N)ÚABCÚabstractmethod)ÚPath)ÚCallableÚIterable)Úget_hermes_home)Úconsume_yieldÚis_interruptedÚis_thread_interrupted)ÚProcessHandleÚ_finalize_wait_resultÚ_new_output_collectorÚ_start_drain_thread)Ú_SHELL_ENV_NAME_REÚ_SNAP_TMP_SUFFIXÚ_cwd_markerÚ_snapshot_bootstrap_scriptÚ_split_cwd_markerÚ_wrap_command_script)Ú
_WaitTrace)Úenv_var_enabledÚHERMES_DEBUG_INTERRUPTg       @c                   ó   — e Zd ZdZy)ÚFileFetchErrorz:A file could not be extracted from the backend filesystem.N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__© ó    ú</home/ubuntu/.hermes/hermes-agent/tools/environments/base.pyr   r   8   s   „ ÚDr    r   i,  c                   ó2   ‡ — e Zd ZdZddœdedefˆ fd„Zˆ xZS )ÚEnvironmentConnectionErroruª  Infrastructure/connection-class failure of a terminal backend (SSH host down, Docker
    daemon not running, remote sync on a dead link) â€” never a command that merely exited
    nonzero. Subclassing RuntimeError keeps every ``except RuntimeError`` catcher working.
    ``terminal_tool`` turns this into a structured ``status: "degraded"`` result; the failed
    backend is never cached, so a later call retries from scratch.Ú )Ú
retry_hintÚreasonr%   c                óH   •— t         ‰| �  |«       || _        |xs d| _        y )NuÒ   This is an infrastructure failure, not a command failure. Verify the backend is reachable (network, service running, credentials), then retry the same command â€” recovery is automatic once the backend is back.)ÚsuperÚ__init__r&   r%   )Úselfr&   r%   Ú	__class__s      €r!   r)   z#EnvironmentConnectionError.__init__G   s+   ø€ Ü‰Ñ˜Ô ØˆŒØ$ò 3ð2ð 	�r    )r   r   r   r   Ústrr)   Ú__classcell__)r+   s   @r!   r#   r#   @   s&   ø„ ñFð :<ò 3˜sð 3°3÷ 3ñ 3r    r#   ÚcbÚreturnc                 ó   — | t         _        y)z>Register a callback that _wait_for_process fires periodically.N)Ú_activity_callback_localÚcallback)r.   s    r!   Úset_activity_callbackr3   Q   s   € à(*ÔÕ%r    c                  ó$   — t        t        dd«      S )um  Thread-local activity callback; capture it before handing work to another thread.

    Public accessor for callers outside this module that need to capture the calling thread's callback
    before handing work to another thread (the callback is thread-local, so a freshly spawned thread cannot
    read it back) â€” e.g. the manual cron-run heartbeat (#76502).
    r2   N)Úgetattrr1   r   r    r!   Úget_activity_callbackr6   V   s   € ô Ô+¨Z¸Ó>Ð>r    ÚstateÚlabelc           	      óâ   — t        j                  «       }|| d   z
  | j                  dd«      k  ry|| d<   	 t        «       }|r ||› dt	        || d   z
  «      › d�«       yy# t
        $ r Y yw xY w)z­Fire the activity callback at most once every ``state['interval']`` (default 10 s).
    *state* holds ``last_touch``/``start`` monotonic timestamps. Swallows all exceptions.Ú
last_touchÚintervalg      $@Nz (Ústartz
s elapsed))ÚtimeÚ	monotonicÚgetr6   ÚintÚ	Exception)r7   r8   Únowr.   s       r!   Útouch_activity_if_duerC   `   s…   € ô �.‰.Ó
€CØ
ˆU�<Ñ Ñ  5§9¡9¨Z¸Ó#>Ò>ØØ€Eˆ,ÑðÜ"Ó$ˆÙÙ�%�˜œ3˜s U¨7¡^Ñ3Ó4Ð5°ZÐ@ÕAð øäò Ùðús   ·)A" Á"	A.Á-A.c                  óŠ   — t        j                  d«      } | rt        | «      nt        «       dz  }|j	                  dd¬«       |S )zŸHost-side root for all sandbox storage (Docker workspaces, Singularity
    overlays/SIF cache). ``TERMINAL_SANDBOX_DIR`` overrides ``{HERMES_HOME}/sandboxes``.ÚTERMINAL_SANDBOX_DIRÚ	sandboxesT©ÚparentsÚexist_ok)ÚosÚgetenvr   r   Úmkdir)ÚcustomÚps     r!   Úget_sandbox_dirrO   o   s=   € ô �Y‰YÐ-Ó.€FÙŒˆVŒ¤OÓ$5¸Ñ$C€AØ‡G�G�D 4€GÔ(Ø€Hr    Úpathc                 óp   — 	 t        j                  | j                  d¬«      «      S # t        $ r i cY S w xY w)z:Load a JSON file as a dict, returning ``{}`` on any error.úutf-8©Úencoding)ÚjsonÚloadsÚ	read_textrA   )rP   s    r!   Ú_load_json_storerX   x   s5   € ðÜ�z‰z˜$Ÿ.™.°'˜.Ó:Ó;Ð;øÜò ØŠ	ðús   ‚$' §5´5Údatac                 óŽ   — | j                   j                  dd¬«       | j                  t        j                  |d¬«      d¬«       y)z.Write *data* as pretty-printed JSON to *path*.TrG   é   )ÚindentrR   rS   N)ÚparentrL   Ú
write_textrU   Údumps)rP   rY   s     r!   Ú_save_json_storer`   €   s4   € à‡K�K×Ñ˜d¨TÐÔ2Ø‡O�O”D—J‘J˜t¨AÔ.¸€OÕAr    Ú	host_pathc                 ó„   — 	 t        | «      j                  «       }|j                  |j                  fS # t        $ r Y yw xY w)zIReturn ``(mtime, size)`` for cache comparison, or ``None`` if unreadable.N)r   ÚstatÚst_mtimeÚst_sizeÚOSError)ra   Ústs     r!   Ú_file_mtime_keyrh   †   s>   € ðÜ�)‹_×!Ñ!Ó#ˆØ—‘˜RŸZ™ZÐ(Ð(øÜò Ùðús   ‚03 ³	?¾?c                   ó¼  — e Zd ZU dZdZeed<   dZeed<   dZ	e
ed<   dZeed<   dZeed	<   d
efd„Zd?dede
defd„Zddddœdedede
dedz  d
ef
d„Zed„ «       Zdedede
d
dfd„Zded
edz  fd„Zd
ee   fd„Zd
eedf   fd„Zded
efd „Zd!„ Zd"ed
eeef   fd#„Zeded
efd$„«       Zd%ed
efd&„Z d'eded
efd(„Z!ed'eded
efd)„«       Z"	 d@dddd*œd+ede
d,ed-e
dz  d.e#eegef   dz  d
efd/„Z$ ee%«      Z%d+efd0„Z&d1efd2„Z'd1efd3„Z(dAd4„Z)dAd5„Z*	 dBddd6ddd7œd'edede
dz  dedz  d8ed,ed.e#eegef   dz  d
efd9„Z+dAd:„Z,d;„ Z-d'ed
eeedz  f   fd<„Z.d=Z/d
efd>„Z0y)CÚBaseEnvironmentzöCommon interface and unified execution flow for all Hermes backends. Subclasses
    implement ``_run_bash()`` and ``cleanup()``; the base provides ``execute()`` with
    snapshot sourcing, CWD tracking, interrupt handling and timeout enforcement.ÚpipeÚ_stdin_modeFÚis_localé   Ú_snapshot_timeoutÚ_sudo_nopasswd_probe_supportedÚ_profile_scoped_passthroughr/   c                  ó   — y)z–Backend temp directory for session artifacts (``/tmp`` in sandboxes;
        LocalEnvironment overrides for Termux where only ``TMPDIR`` is writable).z/tmpr   ©r*   s    r!   Úget_temp_dirzBaseEnvironment.get_temp_dir¦   s   € ð r    NÚcwdÚtimeoutÚenvc                 ó’  — || _         || _        |xs i | _        t        j                  «       j
                  d d | _        | j                  «       j                  d«      xs d}|› d| j                  › d�| _	        |› d| j                  › d�| _
        t        | j                  «      | _        d| _        t        «       | _        d| _        y )Né   ú/z/hermes-snap-z.shz/hermes-cwd-z.txtF)ru   rv   rw   ÚuuidÚuuid4ÚhexÚ_session_idrt   ÚrstripÚ_snapshot_pathÚ	_cwd_filer   Ú_snapshot_readyÚsetÚ_snapshot_passthrough_namesÚ_prefer_nonlogin)r*   ru   rv   rw   Útemp_dirs        r!   r)   zBaseEnvironment.__init__«   sº   € ØˆŒØˆŒØ’9˜"ˆŒäŸ:™:›<×+Ñ+¨C¨RÐ0ˆÔØ×$Ñ$Ó&×-Ñ-¨cÓ2Ò9°cˆØ!) 
¨-¸×8HÑ8HÐ7IÈÐMˆÔØ$˜: \°$×2BÑ2BÐ1CÀ4ÐHˆŒÜ& t×'7Ñ'7Ó8ˆÔØ$ˆÔÜ58³UˆÔ(ð !&ˆÕr    éx   ©Úloginrv   Ú
stdin_dataÚ
cmd_stringr‰   rŠ   c                óD   — t        t        | «      j                  › d�«      ‚)zGSpawn a bash process to run *cmd_string*; every backend overrides this.z must implement _run_bash())ÚNotImplementedErrorÚtyper   )r*   r‹   r‰   rv   rŠ   s        r!   Ú	_run_bashzBaseEnvironment._run_bash¼   s"   € ô "¤T¨$£Z×%8Ñ%8Ð$9Ð9TÐ"UÓVÐVr    c                  ó   — y)z<Release backend resources (container, instance, connection).Nr   rs   s    r!   ÚcleanupzBaseEnvironment.cleanupÂ   s   € ð 	r    Úremote_pathÚ
local_destÚ	max_bytesc                ó  — ddl }ddl}dt        j                  «       j                  dd › d�}t        j                  |«      }| j                  d|› d|› d|d	z   › d
|› d|› �
t        d¬«      }|j                  d«      xs d}	|	j                  |«      |	j                  |«      }}
t        |j                  d«      xs d«      dk7  s
|
dk(  s||
k  rt        d|›d�«      ‚	 |j                  dj                  |	|
t!        |«      z   | j#                  «       «      d¬«      }t!        |«      |kD  rt        |›d|dz  › d�«      ‚t)        |«      j+                  |«       y# t$        |j&                  f$ r}t        d|›d|› �«      |‚d}~ww xY w)u0  Copy a regular file out of the backend filesystem to ``local_dest`` on the host.

        One transport for every backend: base64 over the exec channel, bounded INSIDE the sandbox
        (``head -c max+1`` so /dev/zero can't stream unbounded data into host memory â€” the same
        shape ``tools.image_source`` uses to read sandbox images). The payload is fenced between
        unique markers so login-shell noise in the merged stdout/stderr can't corrupt the decode.
        Raises :class:`FileFetchError` on a missing/unreadable/oversized file.
        r   NÚ__HERMES_FETCH_ry   Ú__z[ -f z ] && echo z && head -c é   z < z | base64 && echo F)rv   Úrewrite_compound_backgroundÚoutputr$   Ú
returncodeéÿÿÿÿzcould not read z< in the sandbox (missing, not a regular file, or unreadable)T)Úvalidateztransfer of z was corrupted in transit: z exceeds the i   z MB delivery limit)Úbase64Úbinasciir{   r|   r}   ÚshlexÚquoteÚexecuteÚ_FETCH_TIMEOUT_SECONDSr?   ÚfindÚrfindr@   r   Ú	b64decodeÚjoinÚlenÚsplitÚ
ValueErrorÚErrorr   Úwrite_bytes)r*   r’   r“   r”   rž   rŸ   ÚmarkerÚquotedÚresultrš   ÚfirstÚlastrY   Úexcs                 r!   Ú
fetch_filezBaseEnvironment.fetch_fileÈ   s£  € ó 	ÛØ"¤4§:¡:£<×#3Ñ#3°C°RÐ#8Ð"9¸Ð<ˆÜ—‘˜[Ó)ˆð —‘Ø�F�8˜; v h¨l¸9Àq¹=¸/ÈÈVÈHÐTfÐgmÐfnÐoÜ*Èð ó Oˆð —‘˜HÓ%Ò+¨ˆØ—k‘k &Ó)¨6¯<©<¸Ó+?ˆtˆÜˆv�z‰z˜,Ó'Ò,¨1Ó-°Ò2°e¸r²kÀTÈUÂ]Ü  ?°;°/ÐA}Ð!~ÓÐð	jØ×#Ñ# B§G¡G¨F°5¼3¸v»;Ñ3FÀtÐ,L×,RÑ,RÓ,TÓ$UÐ`dÐ#ÓeˆDô ˆt‹9�yÒ Ü  K ?°-À	ÈkÑ@ZÐ?[Ð[mÐ!nÓoÐoÜˆZÓ×$Ñ$ TÕ*øô	 ˜HŸN™NÐ+ò 	jÜ  <°¨Ð>YÐZ]ÐY^Ð!_Ó`ÐfiÐiûð	jús   Ã?E ÅFÅ0FÆFc                 ó  — | j                  dt        j                  |«      › d�d¬«      }t        |j	                  d«      xs d«      dk7  ryt        d„ t        |j	                  d	«      xs d
j                  «       «      D «       d«      S )zG``readlink -f`` inside the backend, or None when it cannot be resolved.zreadlink -f z 2>/dev/nullF)r™   r›   r   Nc              3   ó|   K  — | ]4  }|j                  «       j                  d «      sŒ#|j                  «       –— Œ6 y­w)rz   N)ÚstripÚ
startswith)Ú.0Úlns     r!   ú	<genexpr>z1BaseEnvironment.fetch_realpath.<locals>.<genexpr>ë   s.   è ø€ Ò| BÐac×aiÑaiÓak×avÑavÐwzÕa{�R—X‘X—ZÑ|ùs   ‚$<§<rš   r$   )r¢   r    r¡   r@   r?   ÚnextÚreversedÚ
splitlines)r*   r’   r¯   s      r!   Úfetch_realpathzBaseEnvironment.fetch_realpathæ   s€   € à—‘ ¬U¯[©[¸Ó-EÐ,FÀlÐSÐqv�ÓwˆÜˆv�z‰z˜,Ó'Ò,¨1Ó-°Ò2ØÜÑ|¬(°F·J±J¸xÓ4HÒ4NÈB×3ZÑ3ZÓ3\Ó*]Ô|ð  Có  Dð  	Dr    c                  ó   — y)zAReturn backend-specific names that must not persist in snapshots.r   r   rs   s    r!   Ú,_additional_profile_scoped_passthrough_namesz<BaseEnvironment._additional_profile_scoped_passthrough_namesî   s   € àr    .c                 óD  — | j                   sy	 ddlm}  |«       rBddlm} g  |«       ¢| j                  «       ¢­}| j                  j                  d„ |D «       «       t        t        | j                  «      «      S # t        $ r t        j                  dd¬«       Y Œ@w xY w)	zýProfile-scoped names that must not persist in the snapshot. Monotonic for the
        environment lifetime: an allowlist can be cleared after a value was captured, and
        retaining the exclusion keeps that old value from leaking to a later profile.r   r   )Úis_multiplex_active)Úget_all_passthroughc              3   ón   K  — | ]-  }t        |t        «      sŒt        j                  |«      sŒ*|–— Œ/ y ­w©N)Ú
isinstancer,   r   Ú	fullmatch)r¸   Únames     r!   rº   zGBaseEnvironment._snapshot_excluded_passthrough_names.<locals>.<genexpr>ý   s/   è ø€ ò 8lØ!¬j¸¼sÕ.CÔHZ×HdÑHdÐeiÕHj”Dñ8lùs   ‚5˜5®5z4Could not refresh profile-scoped snapshot exclusionsT©Úexc_info)rq   Úagent.secret_scoperÂ   Útools.env_passthroughrÃ   rÀ   r„   ÚupdaterA   ÚloggerÚdebugÚtupleÚsorted)r*   rÂ   rÃ   Únamess       r!   Ú$_snapshot_excluded_passthrough_namesz4BaseEnvironment._snapshot_excluded_passthrough_namesò   sŸ   € ð ×/Ò/Øð	`Ý>Ù"Ô$ÝEØfÑ-Ó/Ðf°$×2cÑ2cÓ2eÑf�Ø×0Ñ0×7Ñ7ñ 8lØ%*ô8lô lô ”V˜D×<Ñ<Ó=Ó>Ð>øô ò 	`Ü�L‰LÐOÐZ^ˆLÖ_ð	`ús   �AA< Á< BÂBc                 óÄ   — t        | j                  |«      | j                  | j                  «      | j                  | j                  t        z   «      | j
                  ¬«      S )a  Quoting inputs shared by the bootstrap and per-command wrapper scripts.
        ``_quote_cwd_for_cd`` / ``_quote_shell_path`` (not bare shlex.quote) let the Windows
        subclass rewrite ``C:\...`` to ``/c/...`` so ``cd`` resolves and MSYS doesn't choke.)Ú
quoted_cwdÚquoted_snapÚsnap_tmp_templateÚ
cwd_marker)ÚdictÚ_quote_cwd_for_cdÚ_quote_shell_pathr€   r   r   )r*   ru   s     r!   Ú_snapshot_script_kwargsz'BaseEnvironment._snapshot_script_kwargs  sV   € ô Ø×-Ñ-¨cÓ2Ø×.Ñ.¨t×/BÑ/BÓCØ"×4Ñ4°T×5HÑ5HÔK[Ñ5[Ó\Ø×'Ñ'ô	)ð 	)r    c                 ó  — t        dd| j                  «       i| j                  | j                  «      ¤Ž}	 | j	                  |d| j
                  ¬«      }| j                  || j
                  ¬«      }t        |j                  d«      xs d«      dk7  rt        d|j                  d«      › �«      ‚d| _
        | j                  |«       t        j                  d| j                  | j                  «       y# t        $ r‡}d	| _
        | j!                  t#        |«      «      \  | _        }| j$                  r"t        j'                  d
| j                  |«       n&t        j'                  d| j                  |«       Y d}~yY d}~yd}~ww xY w)uk  Capture the login shell environment into the snapshot file (once, after construction).
        On success ``_snapshot_ready`` is set so commands source the snapshot instead of running
        under ``bash -l``. On failure, fall back to ``bash -l`` per command â€” unless a non-login
        probe shows login bash itself is dead, in which case prefer ``bash -c``.Úexcluded_namesT©r‰   rv   ©rv   r›   r   z)snapshot bootstrap failed with exit code z-Session snapshot created (session=%s, cwd=%s)Fu_   init_session failed (session=%s): %s â€” login bash unusable; falling back to non-login bash -cuL   init_session failed (session=%s): %s â€” falling back to bash -l per commandNr   )r   rÓ   rÜ   ru   r�   ro   Ú_wait_for_processr@   r?   ÚRuntimeErrorr‚   Ú_update_cwdrÎ   Úinfor~   rA   Ú_probe_nonlogin_fallbackr,   r…   Úwarning)r*   Ú	bootstrapÚprocr¯   r²   Údetails         r!   Úinit_sessionzBaseEnvironment.init_session  sc  € ô
 /ñ rØ×DÑDÓFðrØJN×JfÑJfÐgk×goÑgoÓJpñrˆ	ð	.Ø—>‘> )°4À×AWÑAW�>ÓXˆDØ×+Ñ+¨D¸$×:PÑ:PÐ+ÓQˆFÜ�6—:‘:˜lÓ+Ò0¨qÓ1°QÒ6Ü"Ð%NÈvÏzÉzÐZfÓOgÐNhÐ#iÓjÐjØ#'ˆDÔ Ø×Ñ˜VÔ$Ü�K‰KÐGÈ×IYÑIYÐ[_×[cÑ[cÕdøÜò 	.Ø#(ˆDÔ Ø,0×,IÑ,IÌ#ÈcË(Ó,SÑ)ˆDÔ! 6Ø×$Ò$Ü—‘ðMà×$Ñ$ cõ+ô
 —‘ØbØ×$Ñ$ f÷.ñ .ô+ûð		.ús   ¶B<C3 Ã3	FÃ<A8E>Å>Fré   c                 óX  — t        d| j                  «      }	 | j                  dd|¬«      }| j                  ||¬«      }t	        |j                  d«      xs d«      dk(  }|s'|j                  d«      xs |j                  «       xs |}||fS # t        $ r}d|› d	|› �fcY d
}~S d
}~ww xY w)zHRun ``true`` under non-login bash; return ``(prefer_nonlogin, detail)``.é   ÚtrueFrß   rà   r›   r   Ústdoutz; non-login probe: N)Úminro   r�   rá   r@   r?   r¶   rA   )r*   ré   Úprobe_timeoutÚprobeÚprobe_resultÚprefer_nonloginÚ	probe_excs          r!   rå   z(BaseEnvironment._probe_nonlogin_fallback)  s¼   € ä˜B × 6Ñ 6Ó7ˆð	DØ—N‘N 6°À�NÓNˆEØ×1Ñ1°%ÀÐ1ÓOˆLÜ! ,×"2Ñ"2°<Ó"@Ò"EÀAÓFÈ!ÑKˆOÙ"Ø&×*Ñ*¨8Ó4Ò>¸×EÑEÓGÒQÈ6�Ø" FÐ*Ð*øÜò 	DØ˜V˜HÐ$7¸	°{ÐCÐCÕCûð	Dús   ˜A4B Â	B)ÂB$ÂB)Â$B)c                 óž   — | dk(  r| S | dk(  ry| j                  d«      rdt        j                  | dd «      › �S t        j                  | «      S )zˆQuote a ``cd`` target while preserving ``~`` expansion (``~/...``
        goes through ``$HOME`` so suffixes with spaces stay one word).Ú~z~/z$HOMEz$HOME/r[   N)r·   r    r¡   )ru   s    r!   rÚ   z!BaseEnvironment._quote_cwd_for_cd7  sR   € ð �#Š:ØˆJØ�$Š;ØØ�>‰>˜$ÔØœEŸK™K¨¨A¨B¨Ó0Ð1Ð2Ð2Ü�{‰{˜3ÓÐr    rP   c                 ó,   — t        j                  |«      S )z—Quote *path* for a bash script. LocalEnvironment overrides this to
        rewrite native/mixed Windows paths to ``/c/...``; remote backends are POSIX.)r    r¡   )r*   rP   s     r!   rÛ   z!BaseEnvironment._quote_shell_pathC  s   € ô �{‰{˜4Ó Ð r    Úcommandc                 ón   — t        |f| j                  «       | j                  dœ| j                  |«      ¤ŽS )zJFull bash script: source snapshot, cd, run, re-dump env, emit CWD markers.)Úpassthrough_namesÚsnapshot_ready)r   rÓ   r‚   rÜ   )r*   rø   ru   s      r!   Ú_wrap_commandzBaseEnvironment._wrap_commandH  sA   € ä#Øð1à"×GÑGÓIØ×/Ñ/ñ1ð ×*Ñ*¨3Ó/ñ	1ð 	1r    c                 ód   — dt        j                  «       j                  dd › �}| › d|› d|› d|› �S )zJAppend stdin_data as a shell heredoc to the command string (SDK backends).ÚHERMES_STDIN_Nry   z << 'z'
Ú
)r{   r|   r}   )rø   rŠ   Ú	delimiters      r!   Ú_embed_stdin_heredocz$BaseEnvironment._embed_stdin_heredocP  sA   € ð $¤D§J¡J£L×$4Ñ$4°S°bÐ$9Ð#:Ð;ˆ	Ø�˜% 	˜{¨#¨j¨\¸¸I¸;ÐGÐGr    )Úbounded_captureÚwatch_interrupt_tidÚyield_handlerrè   r  r  r  c                óð  ‡ ‡‡— t        ‰|«      }|�t        j                  «       nd}t        ‰||«      Št	        j
                  «       }||z   }	||dœ}
t        ‰|t        t        ¬«      }|j                  «        ˆˆˆ fd„}	 d}‰j                  «       �€©|xj                  dz  c_        t        «       st        |«      r:|j                  «         |«        ‰ j                  ||j!                  d¬«      d	«      S |�vt#        |«      rk|j%                  «        ‰j'                  d¬
«       	  |‰|j!                  «       «      }|�|j-                  «        |S |j/                  «        t        ‰||«      Št	        j
                  «       |	kD  r_|j1                  «         |«        |j!                  d|› d�¬«      }|j2                  dk(  r|j5                  «       }‰ j                  ||d«      S t7        |
d«       |j9                  «        t	        j:                  |«       |dk  rt=        |dz  d«      }‰j                  «       €�Œ©‰j'                  d¬
«       	 ‰jD                  jG                  «        |jI                  ‰jJ                  «       tM        ‰dd«      }|�|j'                  d¬
«       |j!                  «       }‰ j                  ||‰jJ                  «      }tM        ‰dd«      x}rtO        |d   «      x|d<   }|d|› d�z   |d<   |S # t(        $ r t        j+                  dd¬«       d}Y �Œéw xY w# t>        t@        f$ r) |jC                  «        	  |«        ‚ # t(        $ r Y ‚ w xY ww xY w# t(        $ r Y �Œ	w xY w)u»  Poll-based wait with interrupt checking and stdout draining (shared, not overridden).
        ``yield_handler(proc, output_so_far)``: when the tool thread is asked to yield
        (``tools.interrupt.request_yield`` â€” a user message arrived mid-command), the drain
        thread is stopped, the still-running process is handed to the handler and its dict
        is returned as the result; the process is NOT killed.
        ``bounded_capture=True`` (foreground terminal-tool path only) retains at most
        ``tool_output.max_bytes`` in a head/tail window so a verbose subprocess cannot OOM the
        process; the default keeps full fidelity for internal consumers. Fires the activity
        callback every 10s so the gateway's inactivity timeout doesn't kill long commands.
        ``watch_interrupt_tid`` is the tool-worker thread that submitted this wait: ``execute()``
        may move the wait onto a ``run_bounded_sync`` worker while ``/stop`` still interrupts the
        original tid, so both bits are honored. ``KeyboardInterrupt``/``SystemExit`` mid-poll
        kills the process first â€” the local backend spawns into its own process group, so an
        unkilled child would be orphaned.

        The default (False) preserves full-fidelity capture for internal consumers â€” file-operation ``cat``
        reads feeding the patch engine, code-execution RPC reads, log reads â€” where truncation would corrupt
        data. See #64435.
        N)r:   r<   )ÚenabledrÎ   c                  óL   •— ‰j                  ‰«       ‰ j                  d¬«       y )Nr[   rà   )Ú_kill_processr§   )Údrain_threadrè   r*   s   €€€r!   Ú_kill_and_joinz9BaseEnvironment._wait_for_process.<locals>._kill_and_joinw  s"   ø€ Ø×Ñ˜tÔ$Ø×Ñ aÐÕ(r    g{®Gázt?r˜   z
[Command interrupted])Úsuffixé‚   rà   z6yield-to-background handoff failed; continuing to waitTrÉ   z
[Command timed out after ús]r   é|   zterminal command runninggš™™™™™É?g      ø?r[   Ú_hermes_stdin_threadé   Ú_hermes_stdin_errorsÚstdin_errorz
[stdin write failed: ú]rš   )(r   Ú	threadingÚEventr   r=   r>   r   Ú_DEBUG_INTERRUPTrÎ   ÚenterÚpollÚ
iterationsr
   r   Úinterruptedr   Úrenderr	   rƒ   r§   rA   ræ   Úclose_spillÚclearÚ	timed_outÚtotal_charsÚlstriprC   Ú	heartbeatÚsleeprï   ÚKeyboardInterruptÚ
SystemExitÚexception_exitrî   ÚcloseÚnatural_exitr›   r5   r,   )r*   rè   rv   r  r  r  rš   Ú
drain_stopÚ_nowÚdeadlineÚ_activity_stateÚtracer
  Ú_poll_sleepÚhandedÚrenderedÚstdin_threadr¯   Ústdin_errorsÚerrr	  s   ``                  @r!   rá   z!BaseEnvironment._wait_for_processW  sY  ú€ ô. ' t¨_Ó=ˆØ*7Ð*C”Y—_‘_Ô&Èˆ
Ü*¨4°¸ÓDˆÜ�~‰~ÓˆØ˜'‘>ˆØ)-¸Ñ=ˆÜ˜4 Ô2BÌ6ÔRˆØ�‰Œö	)ð)	ð  ˆKØ—)‘)“+Ñ%Ø× Ò  AÑ%Õ Ü!Ô#Ô'<Ð=PÔ'QØ×%Ñ%Ô'Ù"Ô$Ø×5Ñ5°f¸f¿m¹mÐSl¸mÓ>mÐorÓsÐsØ Ð,´Ð?RÔ1SØ—N‘NÔ$Ø ×%Ñ%¨aÐ%Ô0ð&Ù!.¨t°V·]±]³_Ó!E˜ð Ð)Ø×*Ñ*Ô,Ø%˜Ø×$Ñ$Ô&Ü#6°t¸VÀZÓ#P�LÜ—>‘>Ó# hÒ.Ø—O‘OÔ%Ù"Ô$Ø%Ÿ}™}Ð6QÐRYÐQZÐZ\Ð4]˜}Ó^�HØ×)Ñ)¨QÒ.Ø#+§?¡?Ó#4˜Ø×5Ñ5°f¸hÈÓLÐLÜ% oÐ7QÔRØ—‘Ô!Ü—
‘
˜;Ô'Ø Ò$Ü"% k°CÑ&7¸Ó"=�Kð= —)‘)“+Ò%ðR 	×Ñ !ÐÔ$ð	Ø�K‰K×ÑÔð 	×Ñ˜4Ÿ?™?Ô+ô
 ˜tÐ%;¸TÓBˆØÐ#Ø×Ñ aÐÔ(Ø—=‘=“?ˆØ×+Ñ+¨F°H¸d¿o¹oÓNˆÜ" 4Ð)?ÀÓFÐFˆ<ÐFÜ*-¨l¸1©oÓ*>Ð>ˆF�=Ñ! CØ'Ð,CÀCÀ5ÈÐ*JÑJˆF�8ÑØˆøôa %ò &ÜŸ™Ð'_Ðjn˜ÔoØ!%›ð&ûô( "¤:Ð.ò 	Ø× Ñ Ô"ðÙÔ ð øô ò ØØðúð		ûô ò 	Úð	ús{   ÂA6L- Ã9/L- Ä)L Å L- ÅBL- Ç'AL- ÉM( Ì"L*Ì&L- Ì)L*Ì*L- Ì-M%ÍMÍM%Í	M!ÍM%Í M!Í!M%Í(	M5Í4M5c                 óZ   — 	 |j                  «        y# t        t        t        f$ r Y yw xY w)zDTerminate a process. Subclasses may override for process-group kill.N)ÚkillÚProcessLookupErrorÚPermissionErrorrf   ©r*   rè   s     r!   r  zBaseEnvironment._kill_process¾  s)   € ð	Ø�I‰I�KøÜ"¤O´WÐ=ò 	Ùð	ús   ‚ “*©*r¯   c                 ó&   — | j                  |«       y)zDExtract CWD from command output. Override for local file-based read.N)Ú_extract_cwd_from_output)r*   r¯   s     r!   rã   zBaseEnvironment._update_cwdÆ  s   € à×%Ñ% fÕ-r    c                 ó�   — t        |j                  dd«      | j                  «      }|€y|\  }}|r|| _        d|d<   ||d<   ||d<   y)a·  Parse the ``__HERMES_CWD_{session}__`` marker from ``result["output"]``, update
        ``self.cwd`` and strip the marker line. ``result["cwd_observed"]``/``["cwd"]`` are set
        only when THIS command emitted a marker: a killed/timed-out command emits none and
        ``self.cwd`` keeps the previous value. The environment is shared across sessions, so
        concurrent callers must read ``result["cwd"]`` rather than ``self.cwd``.rš   r$   NTÚcwd_observedru   )r   r?   r   ru   )r*   r¯   r©   Úcwd_pathÚcleaneds        r!   r9  z(BaseEnvironment._extract_cwd_from_outputÊ  sZ   € ô " &§*¡*¨X°rÓ":¸D×<LÑ<LÓMˆØˆ=ØØ!Ñˆ�'ÙØˆDŒHØ%)ˆF�>Ñ"Ø$ˆF�5‰MØ"ˆˆxÒr    c                  ó   — y)z�Hook before each command. Remote backends (SSH, Modal, Daytona)
        trigger their FileSyncManager here; bind-mount backends and Local don't.Nr   rs   s    r!   Ú_before_executezBaseEnvironment._before_executeÛ  s   € ð 	r    c                 ó   — d| _         y)u¨  Flag that the live container/sandbox was replaced while serving the
        current command. ``execute`` folds the flag into the result as
        ``environment_recreated`` so the tool layer can warn the model that
        background processes died and non-persisted files may be gone â€”
        without this the recovery is silent and the model keeps assuming the
        old workspace state (lobehub/lobehub#19329 class).TN)Ú_recreated_notice_pendingrs   s    r!   Ú_mark_recreatedzBaseEnvironment._mark_recreatedà  s   € ð *.ˆÕ&r    T)rv   rŠ   r™   r  r  r™   c                ó„  ‡ ‡‡‡‡‡‡‡‡‡— ‰ j                  «        ‰ j                  |«      \  }}	|rddlm}
  |
|«      }|xs ‰ j                  Š|xs ‰ j
                  }|	�	|	|xs dz   n|Š‰r#‰ j                  dk(  r‰ j                  |‰«      }dŠ‰ j                  ||«      Š‰ j                   xr ‰ j                   Št        j                  «       j                  Št        «       Šg Šdt        fˆˆˆˆˆˆˆˆ ˆˆf
d„}dˆˆ fd„}dd	lm} 	 t%        ‰«      }|t*        z  }	  |||dt-        ‰ «      j.                  › �|¬«      }|j4                  r	d‰› d�ddœn|j6                  }‰ j9                  |«       t;        ‰ dd«      rd‰ _        d|d<   |S # t&        t(        f$ r d
}Y ŒŒw xY w# t0        t2        f$ r	  |«        ‚ w xY w)u  Execute a command, return {"output": str, "returncode": int}. ``bounded_capture=True``
        caps retention at ``tool_output.max_bytes`` WHILE draining; only the foreground terminal
        tool may set it â€” internal full-fidelity consumers (file-op ``cat`` reads feeding the
        patch engine, RPC reads, log reads) MUST leave it False or data is corrupted. The wait is
        bounded by ``agent.deadline.run_bounded_sync`` so a wedged poll loop cannot hang past
        ``timeout`` and silently disable every asyncio timer in the process.

        ``bounded_capture=True`` caps stdout/stderr retention at ``tool_output.max_bytes`` WHILE the stream
        is drained (head/tail window) instead of holding the full output in memory (#64435).
        See #94285.
        r   )Ú_rewrite_compound_backgroundNr$   Úheredocr/   c                  ó¨   •
— ‰�t        ‰«       ‰j                  ‰	‰‰‰¬«      } ‰j                  | «        ‰j                  | f‰‰‰dœ‰
�d‰
i¤ŽS i ¤ŽS )Nrˆ   )rv   r  r  r  )r3   r�   Úappendrá   )Úspawnedr  Úeffective_stdinÚeffective_timeoutr‰   Úparent_activity_cbÚ
parent_tidÚproc_holderr*   Úwrappedr  s    €€€€€€€€€€r!   Ú_spawn_and_waitz0BaseEnvironment.execute.<locals>._spawn_and_wait  s‰   ø€ Ø!Ð-Ü%Ð&8Ô9Ø—n‘n W°EÐCTÐap�nÓqˆGØ×Ñ˜wÔ'Ø)�4×)Ñ)Øð[Ø!2ÀOØ$.ñ[ð 8EÐ7P�O ]Ð3ñ[ð [ð WYñ[ð [r    c                  ó4   •— ‰ r‰j                  ‰ d   «       y y )Nr   )Ú_kill_spawned_tree)rM  r*   s   €€r!   Ú_on_timeoutz,BaseEnvironment.execute.<locals>._on_timeout&  s   ø€ ÙØ×'Ñ'¨°A©Õ7ð r    )Úrun_bounded_syncg      ^@zterminal.wait:)r8   Ú
on_timeoutz[Command timed out after r  r  )rš   r›   rA  FTÚenvironment_recreated©r/   N)r?  Ú_prepare_commandÚtools.terminal_tool_sudorD  rv   ru   rl   r  rü   r‚   r…   r  Úcurrent_threadÚidentr6   rÙ   Úagent.deadlinerS  ÚfloatÚ	TypeErrorrª   Ú_EXECUTE_WAIT_BOUND_GRACE_SrŽ   r   r#  r$  r  Úvaluerã   r5   rA  )r*   rø   ru   rv   rŠ   r™   r  r  Úexec_commandÚ
sudo_stdinrD  Úeffective_cwdrO  rR  rS  Úbound_sÚboundedr¯   rI  rJ  r‰   rK  rL  rM  rN  s   `     ``          @@@@@@@r!   r¢   zBaseEnvironment.executeê  së  ÿù€ ð* 	×ÑÔà#'×#8Ñ#8¸Ó#AÑ ˆ�jñ 'ÝMÙ7¸ÓEˆLØ#Ò3 t§|¡|ÐØš˜tŸx™xˆð >HÐ=S˜*¨
Ò(8°bÒ9ÐYcˆÙ˜t×/Ñ/°9Ò<Ø×4Ñ4°\À?ÓSˆLØ"ˆOà×$Ñ$ \°=ÓAˆð ×(Ñ(Ð(ÒF°×1FÑ1FÐ-Fˆä×-Ñ-Ó/×5Ñ5ˆ
ô 3Ó4ÐØˆð	[¤÷ 	[ö 	[ö	8õ 	4ð	ÜÐ-Ó.ˆGð 	Ô.Ñ.ˆð	Ù&Ø °.ÄÀdÃ×ATÑATÐ@UÐ0VÐcnôpˆGð × Ò ð 3Ð3DÐ2EÀRÐHÐX[Ò\Ø&-§m¡mð 	ð 	×Ñ˜Ô Ü�4Ð4°eÔ<Ø-2ˆDÔ*Ø.2ˆFÐ*Ñ+Øˆøô% œ:Ð&ò 	ØŠGð	ûô "¤:Ð.ò 	ÙŒMØð	ús   ÄF Ä!"F' ÆF$Æ#F$Æ'F?c                 ó  — 	 | j                  |«       t	        |dd«      }|sy	 ddlm}  |t        |«      «       y# t        $ r t        j                  dd¬«       Y ŒKw xY w# t        $ r t        j                  dd¬«       Y yw xY w)	zJBest-effort kill of a wedged spawned process and its tree (backstop path).z'terminal wait-bound kill_process failedTrÉ   ÚpidNr   )Úkill_process_treez,terminal wait-bound kill_process_tree failed)r  rA   rÎ   rÏ   r5   r[  rg  r@   )r*   rH  rf  rg  s       r!   rQ  z"BaseEnvironment._kill_spawned_treeH  s‰   € ð	SØ×Ñ˜wÔ'ô �g˜u dÓ+ˆÙØð	XÝ8Ùœc #›hÕ'øô ò 	SÜ�L‰LÐBÈTˆLÖRð	Sûô ò 	XÜ�L‰LÐGÐRVˆLÖWð	Xús    ‚< ¤A" ¼ AÁAÁ" BÂBc                 óD   — 	 | j                  «        y # t        $ r Y y w xY wrÅ   )r‘   rA   rs   s    r!   Ú__del__zBaseEnvironment.__del__X  s"   € ð	Ø�L‰L�NøÜò 	Ùð	ús   ‚ “	žc                 ó6   — ddl m}  ||| j                  ¬«      S )zTRewrite sudo for a piped password, or leave it alone when this backend has NOPASSWD.r   )Ú_transform_sudo_command)Úsudo_nopasswd_check)rX  rk  Ú_sudo_nopasswd_works)r*   rø   rk  s      r!   rW  z BaseEnvironment._prepare_command^  s   € åDÙ& wÀD×D]ÑD]Ô^Ð^r    é   c                 óÔ   — | j                   sy	 | j                  d| j                  ¬«      }| j                  || j                  ¬«      j	                  d«      dk(  S # t
        $ r Y yw xY w)z®``sudo -n true`` inside THIS backend (host sudo state must not leak into a sandbox).
        Fails closed: any error or a timed-out probe means "assume a password is needed".Fzsudo -n truerà   r›   r   )rp   r�   Ú_SUDO_PROBE_TIMEOUT_Srá   r?   rA   r7  s     r!   rm  z$BaseEnvironment._sudo_nopasswd_workse  sm   € ð ×2Ò2Øð	Ø—>‘> .¸$×:TÑ:T�>ÓUˆDØ×)Ñ)¨$¸×8RÑ8RÐ)ÓS×WÑWÐXdÓeÐijÑjÐjøÜò 	Ùð	ús   �AA Á	A'Á&A'rÅ   )r‡   rV  )r$   )1r   r   r   r   rl   r,   Ú__annotations__rm   Úboolro   r@   rp   rq   rt   rÙ   r)   r   r�   r   r‘   r   r³   r¾   r   rÀ   rÐ   rÓ   rÜ   rê   rå   ÚstaticmethodrÚ   rÛ   rü   r  r   rá   r   r  rã   r9  r?  rB  r¢   rQ  ri  rW  rp  rm  r   r    r!   rj   rj   �   s[  … ñTð
 €K�Óð €HˆdÓð  Ð�sÓð ,1Ð" DÓ0ð ).Ð Ó-ð˜có ñ
&˜Cð &¨#ð &°Dó &ð$ 16ÀcÐdhòWØðWØ)-ðWØ@CðWØWZÐ]aÑWaðWà	óWð ñó ðð
+ cð +°tð +È3ð +ÐSWó +ð<D¨#ð D°#¸±*ó Dð¸hÀs¹mó ð?°e¸CÀ¸H±oó ?ð")¨3ð )°4ó )ò.ð8D¨sð D°u¸TÀ3¸YÑ7Gó Dð ð	 ˜sð 	  sò 	 ó ð	 ð! cð !¨có !ð
1 Sð 1¨sð 1°só 1ð ðH cð H°sð H¸sò Hó ðHð 36ðcà %ÈØEIòcØ!ðcØ,/ðcàðcà<?À$¹Jðcð   °Ð 4°dÐ :Ñ;¸dÑBðcð OSócñJ )Ð)>Ó?Ðð -ó ð. $ó .ð#¨tó #ó"ó
.ð ð\ð
 #Ø!%Ø,0Ø %ØEIò\àð\ð ð\ð
 �t‘ð\ð ˜$‘Jð\ð &*ð\ð ð\ð   °Ð 4°dÐ :Ñ;¸dÑBð\ð OSó\ó|Xò ð_¨ð _°°c¸3À¹:°oÑ0Fó _ð
 Ðð	 dô 	r    rj   )ÚIO)ÚProtocol)Údeque)ztools.environments.path_utilsÚsanitize_task_id_for_path)zhermes_cli._subprocess_compatÚwindows_hide_flags)rw  rx  c                 óÌ   — t         j                  | «      }|€t        dt        ›d| ›�«      ‚dd l}ddlm}  |t        | g|¢­Ž  t        |j                  |d   «      |d   «      S )Nzmodule z has no attribute r   )Ú	warn_oncer˜   )	Ú_PLUGIN_COMPAT_LAZYr?   ÚAttributeErrorr   Ú	importlibÚhermes_cli.plugin_compatrz  r5   Úimport_module)rÈ   Útargetr}  rz  s       r!   Ú__getattr__r�  „  se   € Ü ×$Ñ$ TÓ*€FØ€~Ü˜w¤x lÐ2DÀTÀHÐMÓNÐNÛÝ2ÙŒh˜Ð&˜vÓ&Ü�9×*Ñ*¨6°!©9Ó5°v¸a±yÓAÐAr    )Jr   rU   ÚloggingrJ   r    r  r=   r{   Úabcr   r   Úpathlibr   Útypingr   r   Úhermes_constantsr   Útools.interruptr	   r
   r   Útools.environments.base_outputr   r   r   r   Ú#tools.environments.base_session_envr   r   r   r   r   r   Útools.environments.base_waitr   Úutilsr   Ú	getLoggerr   rÎ   r  r^  ÚsetLevelÚINFOÚlocalr1   râ   r   r£   r#   r,   r3   r6   rÙ   rC   rO   rX   r`   rÐ   r\  r@   rh   rj   rt  ru  ÚcodecsÚcollectionsrv  ÚreÚselectÚ
subprocessr{  r�  r   r    r!   ú<module>r•     s³  ðñó Û Û 	Û Û Û Û ß #Ý ß %å ,ß PÑ P÷ó ÷÷ õ 4Ý !à	ˆ×	Ñ	˜8Ó	$€ñ #Ð#;Ó<Ð ð "Ð áð ‡O�O�G—L‘LÔ!ð +˜9Ÿ?™?Ó,Ð ôE�\ô Eð
 Ð ô3 ô 3ð"+˜h¨ u¨d {Ñ3°dÑ:ð +¸tó +ð
?˜x¨¨¨t¨Ñ4°tÑ;ó ?ð ð ¨cð °dó ð˜ó ð˜4ð  Dó ðB˜4ð B tð B°ó Bð˜sð  u¨U°C¨ZÑ'8¸4Ñ'?ó ô_�cô _õL Ý Û Ý Û 	Û Û ð "`ØQñÐ óBr    