Ë
    ãß²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Zddl	Z	ddl
Z
ddlZddlZddlmZmZmZ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 ddlmZ dd	lm Z m!Z!  ejD                  e#«      Z$d
Z% e&g d¢«      Z'dZ(dZ)dZ*dZ+dZ,de-dee-ee-ef   f   fd„Z.de-dee-   fd„Z/de0fd„Z1ddddddddœZ2de-fd„Z3de3fdd „ fd!d"„ fd#d$„ ffZ4dod%e-dee-   fd&„Z5	 dpd'ee-   d(e-de-fd)„Z6d*Z7d+e7z   d,z   Z8d-e7z   d.z   Z9d/e-fd0„Z:d1e-d2e-ddfd3„Z;d4ede-fd5„Z<de-de-fd6„Z=de-dee-ee-ef   f   fd7„Z>de-d8e-de-fd9„Z?dddd:œd;e-d<e@d=eAd>ee-   de-f
d?„ZBd@eCdAeAd<e@de-fdB„ZDdCdDdEœZE	 dodFe-dGe-dAeAdHee-ef   dIeee-ef      dee-ef   fdJ„ZFdKee-ef   d8e-ddfdL„ZGdMee-ef   dCe@dAeAde-fdN„ZHd'eee-      de&fdO„ZIdPe-dQe-dRe-dSe&dCe@dTe@dAeAde-fdU„ZJ	 dqdQe-d/ee-   d'eee-      dVe0de-f
dW„ZK	 	 	 drdQe-d/ee-   d'eee-      dVe0de-f
dX„ZLdqdYe0fdZ„ZMdeNfd[„ZOd\ZPd]ZQde-fd^„ZRg d_¢ZS	 	 dsd`eTdae-deNfdb„ZU eU«       ZVdceNde-fdd„ZW ej°                  dedfeVeWe1dgdh¬i«       ddlYZYddlZZZddl[Z[djZ\dkZ]dldmiZ^dn„ Z_y)taš  Code Execution Tool -- Programmatic Tool Calling (PTC).

The LLM writes a Python script that calls Hermes tools via RPC, collapsing
multi-step tool chains into one inference turn; only the script's stdout returns
to the LLM. Local backend: a persistent per-conversation session kernel
(tools/code_kernel.py) over a Unix socket (loopback TCP on Windows). Remote
backends: a remote session kernel (tools/code_kernel_remote.py) falling open to a
per-call script ship, tool calls as request files polled via env.execute()
(needs Python 3 on the backend). Siblings: tools/code_execution_env.py (env
scrubbing, interpreter/cwd), tools/code_execution_rpc.py (RPC servers).
é    N)ÚAnyÚDictÚListÚOptionalÚTuple)Úpropagate_context_to_thread)ÚregistryÚ
tool_error)Úget_timezone_name)Ú_resolve_child_cwdÚ_resolve_child_python)Ú_rpc_poll_loop)Úhead_tail_splitÚtruncation_noticeT)Ú
web_searchÚweb_extractÚ	read_fileÚ
write_fileÚsearch_filesÚpatchÚterminali,  é2   iPÃ  i'  i@KL Ústdout_textÚreturnc                 ó¦  — | j                  dd¬«      }t        |«      }t        |t        «      }||kD  ||||z
  dœ}|t        k  r|j	                  dd¬«      |fS t        t        «      \  }}|d| j	                  dd¬«      t        ||z
  |d¬«      z   || d j	                  dd¬«      z   }d|d	<   t        | «      }|r||d
<   d|› d|› d�|d	<   ||fS )a  Cap stdout by bytes (40% head / 60% tail) with explicit truncation metadata: byte counts
    ride alongside the textual marker because a client layer can miss or re-truncate it. The
    omitted middle is spilled to cache/exec and the result carries the path (recover-don't-rerun).úutf-8Úreplace©Úerrors)Ústdout_truncatedÚstdout_bytes_capturedÚstdout_bytes_totalÚstdout_bytes_omittedNÚbytes)Úunitz¬execute_code stdout was truncated; the script did run, but only the captured head/tail output is included. Re-run only with narrower output if the omitted data is required.ÚwarningÚstdout_spill_pathz^execute_code stdout was truncated (head/tail shown); the script did run. FULL output saved to u"    â€” page it with read_file(path="z%", offset=...) instead of re-running.)ÚencodeÚlenÚminÚMAX_STDOUT_BYTESÚdecoder   r   Ú_spill_full_stdout)	r   Ústdout_bytesÚtotalÚcapturedÚmetadataÚ
head_bytesÚ
tail_bytesÚtextÚ
spill_paths	            ú>/home/ubuntu/.hermes/hermes-agent/tools/code_execution_tool.pyÚ_truncate_stdout_textr7   7   s1  € ð ×%Ñ% g°iÐ%Ó@€LÜ�Ó€EÜ�5Ô*Ó+€HØ49¸HÑ4DÐ_gØ6;ÐUZÐ]eÑUeñ g€HàÔ Ò Ø×"Ñ" 7°9Ð"Ó=¸xÐGÐGÜ,Ô-=Ó>Ñ€J�
Ø˜˜*Ð%×,Ñ,¨W¸YÐ,ÓGÜ ¨Ñ 0°%¸gÔFñGà˜J˜;˜<Ð(×/Ñ/°À	Ð/ÓJñK€DðN€HˆYÑô $ KÓ0€JÙØ(2ˆÐ$Ñ%ð GØGQÀlð S7Ø7A°lÐBgð iˆ�Ñð �ˆ>Ðó    c                 ó¨  — 	 ddl }ddlm} ddlm} t        | «      t        kD  r| dt         dt        d›d�z   }  |dd	«      }|j                  d
d
¬«       |j                  | j                  dd¬«      «      j                  «       dd }|d|› d�z  } ||| dd
¬«       t        |«      S # t        $ r }t        j                  d|«       Y d}~yd}~ww xY w)u  Write full stdout to cache/exec; return its path (None on failure â€” best-effort,
    the truncated inline output is still returned). Keyed by content digest so identical
    reruns coalesce; the dir rides the cache/web remote bind-mount list (credential_files).r   N)Úget_hermes_dir)Úwrite_text_exclusivez

[... spill capped at ú,z bytes ...]z
cache/execÚ
exec_spillT)ÚparentsÚexist_okr   r   r   é   zstdout-z.txtF)ÚprivateÚ	overwritez'Failed to spill execute_code stdout: %s)ÚhashlibÚhermes_constantsr:   Útools.spill_safetyr;   r)   ÚMAX_SPILLED_STDOUT_BYTESÚmkdirÚsha256r(   Ú	hexdigestÚstrÚ	ExceptionÚloggerÚdebug)r   rC   r:   r;   Ú	cache_dirÚdigestÚpathÚexcs           r6   r-   r-   R   sÝ   € ðÛÝ3Ý;Üˆ{ÓÔ6Ò6Ø&Ð'@Ô(@ÐAØ8Ô9QÐRSÐ8TÐT_Ð`ñaˆKá" <°Ó>ˆ	Ø�‰ ¨tˆÔ4Ø—‘ × 2Ñ 2°7À9Ð 2Ó MÓN×XÑXÓZÐ[^Ð\^Ð_ˆØ˜W V H¨DÐ1Ñ1ˆÙ˜T ;¸ÈÕNÜ�4‹yÐøÜò Ü�‰Ð>ÀÔDÜûðús   ‚B%B( Â(	CÂ1CÃCc                  óÀ   — t         sy	 ddlm}  ddlm}  | «       }|j                  d«      d	k7  xs  ||«      S # t
        $ r t        j                  dd¬«       Y yw xY w)
zKcheck_fn: available unless the vercel_sandbox backend fails its own checks.Fr   )Ú_get_env_config)Ú"_check_vercel_sandbox_requirementsz?Could not resolve terminal config for execute_code availabilityT©Úexc_infoÚenv_typeÚvercel_sandbox)	ÚSANDBOX_AVAILABLEÚtools.terminal_toolrS   Útools.terminal_tool_backendsrT   rK   rL   rM   Úget)rS   rT   Úconfigs      r6   Úcheck_sandbox_requirementsr^   h   sd   € åØðÝ7ÝSÙ Ó"ˆð �:‰:�jÓ!Ð%5Ñ5ÒcÑ9[Ð\bÓ9cÐcøô ò Ü�‰ÐVÐaeˆÔfÙðús   ‰: º AÁA)zquery: str, limit: int = 5zS"""Search the web. Returns dict with data.web list of {url, title, description}."""z {"query": query, "limit": limit})z"urls: list, char_limit: int = Nonea  """Extract content from URLs (no LLM summarization). Returns dict with results list of {url, title, content, error}. Pages over char_limit (default 15000) are head+tail truncated with the full text stored on disk; the content footer gives the path. content is markdown."""z({"urls": urls, "char_limit": char_limit})z-path: str, offset: int = 1, limit: int = 2000zS"""Read a file (1-indexed lines). Returns dict with "content" and "total_lines"."""z0{"path": path, "offset": offset, "limit": limit})z4path: str, content: str, cross_profile: bool = FalsezL"""Write content to a file (always overwrites). Returns dict with status."""zB{"path": path, "content": content, "cross_profile": cross_profile})z¹pattern: str, target: str = "content", path: str = ".", file_glob: str = None, limit: int = 50, offset: int = 0, output_mode: str = "content", context: int = 0, order: str = "discovery"zr"""Search file contents (target="content") or find files by name (target="files"). Returns dict with "matches"."""z®{"pattern": pattern, "target": target, "path": path, "file_glob": file_glob, "limit": limit, "offset": offset, "output_mode": output_mode, "context": context, "order": order})z¢path: str = None, old_string: str = None, new_string: str = None, replace_all: bool = False, mode: str = "replace", patch: str = None, cross_profile: bool = Falsezt"""Targeted find-and-replace (mode="replace") or V4A multi-file patches (mode="patch"). Returns dict with status."""zœ{"path": path, "old_string": old_string, "new_string": new_string, "replace_all": replace_all, "mode": mode, "patch": patch, "cross_profile": cross_profile})z6command: str, timeout: int = None, workdir: str = NonezX"""Run a shell command (foreground only). Returns dict with "output" and "exit_code"."""z<{"command": command, "timeout": timeout, "workdir": workdir}c                 ó®   — | j                  d«      }|dv rd|› d�S t        t        t        |xs t        «      z  «      }d|› ddj	                  |«      › d�S )	Né   >   ÚretryÚ
json_parseÚshell_quotez.Import helpers with `from hermes_tools import z¤`. If that import failed, the generated module may be stale or another hermes_tools may be first on sys.path. Check hermes_tools.__file__ and retry with reset=true.ú'zK' is not available inside the execute_code sandbox. Importable tools here: ú, zF. For anything else, use the normal tool call instead of execute_code.)ÚgroupÚsortedÚSANDBOX_ALLOWED_TOOLSÚsetÚjoin)ÚmÚenabled_toolsÚmissingÚ	availables       r6   Ú!_missing_hermes_tools_import_hintro   ’   sy   € Ø�g‰g�a‹j€GØÐ8Ñ8Ø@ÀÀ	ð J-ð -ð 	.ô Ô,¬s°=Ò3YÔDYÓ/ZÑZÓ[€IØ�ˆyð &Ø&*§i¡i°	Ó&:Ð%;ð <FðFð Gr8   z.cannot import name '(\w+)' from 'hermes_tools'z?NameError: name '(json_parse|shell_quote|retry)' is not definedc                 óN   — d| j                  d«      › d| j                  d«      › �S )NzImport r`   z- before calling it: from hermes_tools import ©rf   ©rk   Ú_s     r6   ú<lambda>rt   ¥   s)   € �G˜AŸG™G A›J˜<ð (.Ø./¯g©g°a«j¨\ð;€ r8   z/ModuleNotFoundError: No module named '([\w.]+)'c                 ó,   — d| j                  d«      › d�S )Nrd   r`   zœ' is not installed in the sandbox interpreter. Use Python stdlib inside execute_code, or run the code via terminal() with the project venv's python instead.rq   rr   s     r6   rt   rt   ¨   s   € �A�a—g‘g˜a“j�\ð "Gð G€ r8   z^TypeError: string indices must be integers|AttributeError: 'str' object has no attribute 'get'c                  ó   — y)Nu™   Tool functions in the sandbox return DICTS (already parsed) â€” do not json.loads() them or index them like strings. Example: read_file(path)['content'].© rr   s     r6   rt   rt   ¬   s   � r8   Ústderr_textc                 ó˜   — | sy| dd }	 t         D ](  \  }}t        j                  ||«      }|sŒ |||«      c S  	 y# t        $ r Y yw xY w)zzMap well-known sandbox script failures to one actionable recovery hint
    (bounded scan, first match wins, never raises).Ni   )Ú_FAILURE_HINT_RULESÚreÚsearchrK   )rx   rl   ÚwindowÚpatternÚfmtrk   s         r6   Ú_sandbox_failure_hintr€   ±   sg   € ñ ØØ˜˜$Ð€FðÜ/ò 	-‰LˆG�SÜ—	‘	˜' 6Ó*ˆAÚÙ˜1˜mÓ,Ò,ñ	-ð øô ò Ùðús   Š$= ¯
= º= ½	A	ÁA	rl   Ú	transportc                 ó–   ‡ — |dk(  rt         nt        }|dj                  ˆ fd„t        t        j                  «       «      D «       «      z   S )u¶   Source of the hermes_tools.py stub module for SANDBOX_ALLOWED_TOOLS âˆ© *enabled_tools*.
    ``transport``: ``"uds"`` (local socket client) or ``"file"`` (file RPC, remote backends).ÚfileÚ
c              3   ój   •K  — | ]*  \  }\  }}}|t        ‰«      v rd |› d|› d|› d|›d|› d�–— Œ, y­w)zdef ú(z):
    z
    return _call(re   z)
N)ri   )Ú.0ÚnameÚsigÚdocÚ	args_exprrl   s        €r6   ú	<genexpr>z/generate_hermes_tools_module.<locals>.<genexpr>Æ   sT   øè ø€ ò á'ˆDÑ'�3˜˜YÈ$ÔRUÐVcÓRdÑJdð ˆtˆf�A�c�U˜( 3 %Ð':¸4¸(À"ÀYÀKÈsÔSñùs   ƒ03)Ú_FILE_TRANSPORT_HEADERÚ_UDS_TRANSPORT_HEADERrj   rg   Ú_TOOL_STUBSÚitems)rl   r�   Úheaders   `  r6   Úgenerate_hermes_tools_moduler’   Á   sH   ø€ ð (1°FÒ':Õ#Ô@U€FØ�D—I‘Ió ä+1´+×2CÑ2CÓ2EÓ+Fôó ñ ð r8   u²  
# ---------------------------------------------------------------------------
# Convenience helpers (avoid common scripting pitfalls)
# ---------------------------------------------------------------------------

def json_parse(text: str):
    """Parse JSON tolerant of control characters and UTF-8 BOM (strict=False).
    Use this instead of json.loads() when parsing output from terminal()
    or web_extract() that may contain raw tabs/newlines in strings,
    or from tools/files that prepend a UTF-8 BOM (salvage #57870, credit @woxinwuhen713-bit)."""
    if isinstance(text, str) and text.startswith("ï»¿"):
        text = text[1:]
    return json.loads(text, strict=False)


def shell_quote(s: str) -> str:
    """Shell-escape a string for safe interpolation into commands.
    Use this when inserting dynamic content into terminal() commands:
        terminal(f"echo {shell_quote(user_input)}")
    """
    return shlex.quote(s)


def retry(fn, max_attempts=3, delay=2):
    """Retry a function up to max_attempts times with exponential backoff.
    Use for transient failures (network errors, API rate limits):
        result = retry(lambda: terminal("gh issue list ..."))
    """
    last_err = None
    for attempt in range(max_attempts):
        try:
            return fn()
        except Exception as e:
            last_err = e
            if attempt < max_attempts - 1:
                time.sleep(delay * (2 ** attempt))
    raise last_err

a°  """Auto-generated Hermes tools RPC stubs."""
import json, os, socket, shlex, threading, time

_sock = None
# The RPC server handles a single client connection serially and has no
# request-id in the protocol, so concurrent _call() invocations from multiple
# threads (e.g. ThreadPoolExecutor) would race on the shared socket and get
# each other's responses. Serialize the entire send+recv round-trip.
_call_lock = threading.Lock()
uZ
  
def _connect():
    """Connect to the parent's RPC server via the transport it picked.

    HERMES_RPC_SOCKET can be either:
      - a filesystem path (POSIX Unix domain socket â€” the default on
        Linux and macOS)
      - a string of the form ``tcp://127.0.0.1:<port>`` (Windows, where
        AF_UNIX is unreliable â€” the parent falls back to loopback TCP)
    """
    global _sock
    if _sock is None:
        endpoint = os.environ["HERMES_RPC_SOCKET"]
        if endpoint.startswith("tcp://"):
            # tcp://host:port  (host is always 127.0.0.1 in practice â€” we
            # only bind loopback server-side)
            _host_port = endpoint[len("tcp://"):]
            _host, _, _port = _host_port.rpartition(":")
            _sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            _sock.connect((_host or "127.0.0.1", int(_port)))
        else:
            _sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
            _sock.connect(endpoint)
        _sock.settimeout(300)
    return _sock

def _call(tool_name, args):
    """Send a tool call to the parent process and return the parsed result."""
    request = json.dumps({
        "tool": tool_name,
        "args": args,
        "token": os.environ.get("HERMES_RPC_TOKEN", ""),
    }) + "\n"
    # Session kernels outlive the RPC server's 300s idle window, so their
    # connection can be legitimately gone by the next cell. The server
    # re-accepts (HERMES_RPC_PERSISTENT=1); retry once on a fresh socket.
    _attempts = 2 if os.environ.get("HERMES_RPC_PERSISTENT") == "1" else 1
    with _call_lock:
        for _attempt in range(_attempts):
            try:
                conn = _connect()
                conn.sendall(request.encode())
                buf = b""
                while True:
                    chunk = conn.recv(65536)
                    if not chunk:
                        raise RuntimeError("Agent process disconnected")
                    buf += chunk
                    if buf.endswith(b"\n"):
                        break
                break
            except (OSError, RuntimeError):
                global _sock
                try:
                    if _sock is not None:
                        _sock.close()
                except OSError:
                    pass
                _sock = None
                if _attempt + 1 >= _attempts:
                    raise
    raw = buf.decode().strip()
    result = json.loads(raw)
    if isinstance(result, str):
        try:
            return json.loads(result)
        except (json.JSONDecodeError, TypeError):
            return result
    return result

aÝ  """Auto-generated Hermes tools RPC stubs (file-based transport)."""
import json, os, shlex, tempfile, threading, time

_RPC_DIR = os.environ.get("HERMES_RPC_DIR") or os.path.join(tempfile.gettempdir(), "hermes_rpc")
_seq = 0
# `_seq += 1` is not atomic (read-modify-write), so concurrent _call()
# invocations from multiple threads could allocate the same sequence number
# and clobber each other's request files. Guard seq allocation with a lock.
_seq_lock = threading.Lock()
a‘  
def _call(tool_name, args):
    """Send a tool call request via file-based RPC and wait for response."""
    global _seq
    with _seq_lock:
        _seq += 1
        seq = _seq
    seq_str = f"{seq:06d}"
    req_file = os.path.join(_RPC_DIR, f"req_{seq_str}")
    res_file = os.path.join(_RPC_DIR, f"res_{seq_str}")

    # Write request atomically (write to .tmp, then rename).
    # encoding="utf-8" is critical: on Windows-hosted remote backends
    # (or any non-UTF-8 locale) the default open() mode would mangle
    # non-ASCII chars in tool args when encoding them as JSON.
    tmp = req_file + ".tmp"
    with open(tmp, "w", encoding="utf-8") as f:
        json.dump({
            "tool": tool_name,
            "args": args,
            "seq": seq,
            "token": os.environ.get("HERMES_RPC_TOKEN", ""),
        }, f)
    os.rename(tmp, req_file)

    # Wait for response with adaptive polling
    deadline = time.monotonic() + 300  # 5-minute timeout per tool call
    poll_interval = 0.05  # Start at 50ms
    while not os.path.exists(res_file):
        if time.monotonic() > deadline:
            raise RuntimeError(f"RPC timeout: no response for {tool_name} after 300s")
        time.sleep(poll_interval)
        poll_interval = min(poll_interval * 1.2, 0.25)  # Back off to 250ms

    with open(res_file, encoding="utf-8") as f:
        raw = f.read()

    # Clean up response file
    try:
        os.unlink(res_file)
    except OSError:
        pass

    result = json.loads(raw)
    if isinstance(result, str):
        try:
            return json.loads(result)
        except (json.JSONDecodeError, TypeError):
            return result
    return result

Útask_idc                 ó†  ‡‡‡‡— ddl m}m}m} ddlmŠmŠm}mŠm	}m
}m}m}m}	m}
m}m}  |	| «      Šˆˆˆˆfd„} |«       }|�| |«       d   fS |5  |j#                  ‰t%        j&                  «       «      }ddd«       5   |«       }|�| |«       d   fcddd«       S  |«       }|d   }|j)                  ‰i «      }d} ||«      r ||«      }t*        j-                  d|‰dd «        || ||||«      |j)                  d	«      xs |d	   |d
   |dk(  r ||«      nd||dk(  rd|j)                  dd«      ind‰ |
|| «      ¬«	      }‰5  |‰‰<   t/        j.                  «       ‰‰<   ddd«        |«        t*        j-                  d|‰dd «       ||fcddd«       S # 1 sw Y   �Œ)xY w# 1 sw Y   ŒDxY w# 1 sw Y   yxY w)u¶   ``(env, env_type)`` â€” the environment the terminal/file tools share for *task_id*, created on
    first use (same double-checked per-task lock pattern as file_tools._get_file_ops).r   )Ú_container_config_from_configÚ_create_environmentÚ_ssh_config_from_config)Ú_active_environmentsÚ	_env_lockrS   Ú_last_activityÚ_start_cleanup_threadÚ_creation_locksÚ_creation_locks_lockÚ_task_env_overridesÚ_resolve_container_task_idÚ_resolve_task_host_cwdÚ_is_container_backendÚ_select_imagec                  óŒ   •— ‰5  ‰j                  ‰«      } | �t        j                  «       ‰‰<   d d d «       | S # 1 sw Y    S xY w©N)r\   Útime)Úenvr˜   r™   rš   Úeffective_task_ids    €€€€r6   Ú_cachedz#_get_or_create_env.<locals>._cached›  sN   ø€ Øñ 	@Ø&×*Ñ*Ð+<Ó=ˆCØˆÜ48·I±I³K�Ð0Ñ1÷	@ð ˆ
÷		@ð ˆ
ús	   „+9¹ANrW   z7Creating new %s environment for execute_code task %s...é   ÚcwdÚtimeoutÚsshÚlocalÚ
persistentÚlocal_persistentF)	rW   Úimagerª   r«   Ú
ssh_configÚcontainer_configÚlocal_configr“   Úhost_cwdz-%s environment ready for execute_code task %s)r[   r•   r–   r—   rZ   r˜   r™   rS   rš   r›   rœ   r�   rž   rŸ   r    r¡   r¢   Ú
setdefaultÚ	threadingÚLockr\   rL   Úinfor¥   )r“   r•   r–   r—   rS   r›   rœ   r�   rž   rŸ   r    r¡   r¢   r¨   r¦   Ú	task_lockr]   rW   Ú	overridesr²   r˜   r™   rš   r§   s                       @@@@r6   Ú_get_or_create_envr»   ‘  s  û€ ÷ yÑx÷÷ ÷ ó ñ
 3°7Ó;Ð÷ñ ‹)€CØ
€Ø‘OÓ% jÑ1Ð1Ð1Ø	ñ TØ#×.Ñ.Ð/@Ä)Ç.Á.ÓBRÓSˆ	÷Tà	ñ Ù‹iˆØˆ?Ø™Ó)¨*Ñ5Ð5÷ñ ñ !Ó"ˆØ˜*Ñ%ˆØ'×+Ñ+Ð,=¸rÓBˆ	ØÐÙ  Ô*ñ  =¸VÓDÐÜ�‰ÐMØÐ0°°!Ð4ô	6á!Ø¡]°8¸YÈÓ%OØ—‘˜eÓ$Ò5¨¨u©¸vÀiÑ?PØ:BÀeÒ:KÑ.¨vÔ6ÐQUØ-ØRZÐ^eÒRe˜,¨¯
©
Ð3EÀuÓ(MÑNÐkoØ%Ñ0FÀvÈwÓ0Wô
ˆð ñ 	<Ø69Ð Ð!2Ñ3Ü04·	±	³ˆNÐ,Ñ-÷	<ñ 	ÔÜ�‰ÐCØÐ0°°!Ð4ô	6à�Hˆ}÷9ñ ÷Tñ Tú÷0	<ð 	<ú÷-ð ús7   Á%FÂF7Â B+F7ÅF+Å(,F7ÆF(Æ+F4	Æ0F7Æ7G Úremote_pathÚcontentc                 óÄ   — t        j                  |j                  d«      «      j                  d«      }| j	                  d|› dt        j                  |«      › �dd¬«       y)	u½   Write *content* to *remote_path* via ``echo â€¦ | base64 -d`` â€” some backends (Modal) don't
    reliably deliver stdin_data to chained commands; base64 is shell-safe inside single quotes.r   Úasciizecho 'z' | base64 -d > ú/é   ©rª   r«   N)Úbase64Ú	b64encoder(   r,   ÚexecuteÚshlexÚquote)r¦   r¼   r½   Úencodeds       r6   Ú_ship_file_to_remoterÉ   Å  sT   € ô ×Ñ˜wŸ~™~¨gÓ6Ó7×>Ñ>¸wÓG€GØ‡K�K�&˜˜	Ð!1´%·+±+¸kÓ2JÐ1KÐLÐRUÐ_a€KÕbr8   r¦   c                 ón  — d}t        | dd«      }t        |«      r	  |«       }|t        j                  «       fD ];  }t        |t        «      sŒ|j                  d«      sŒ&|j                  d«      xs dc S  t        j                  «       S # t        $ r }t        j	                  d|«       Y d}~ŒŒd}~ww xY w)zAReturn a writable temp dir for env-backed execute_code sandboxes.NÚget_temp_dirz/Could not resolve execute_code env temp dir: %srÀ   )ÚgetattrÚcallablerK   rL   rM   ÚtempfileÚ
gettempdirÚ
isinstancerJ   Ú
startswithÚrstrip)r¦   Útemp_dirrË   rQ   Ú	candidates        r6   Ú_env_temp_dirrÕ   Ì  s­   € à€HÜ˜3 °Ó5€LÜ�Ôð	QÙ#“~ˆHð ¤× 3Ñ 3Ó 5Ð6ò 0ˆ	Ü�i¤Õ%¨)×*>Ñ*>¸sÕ*CØ×#Ñ# CÓ(Ò/¨CÒ/ð0ô ×ÑÓ Ð øô ò 	QÜ�L‰LÐJÈC×PÑPûð	Qús   œB Â	B4ÂB/Â/B4c                 óF   — ddl m}  |«       }|rd|› d�nd}| r| › d|› �S |S )z=Append an interruption marker without guessing who caused it.r   )Úget_interrupt_reasonu   [execution interrupted â€” ú]z[execution interrupted]r„   )Útools.interruptr×   )r   r×   ÚreasonÚmarkers       r6   Ú_format_interrupted_outputrÜ   Û  s9   € å4Ù!Ó#€FÙ8>Ð*¨6¨(°!Ñ4ÐD]€FÙ)4ˆkˆ]˜"˜V˜HÐ%Ð@¸&Ð@r8   c                 óZ   — ddl m} ddlm} t	        | «      \  } } | || «      d¬«      |fS )uÆ   Shared output pipeline: byte-cap (with spill), ANSI strip, secret redaction. code_file=True:
    output often echoes source/config â€” skip ENV/JSON/f-string false positives, still mask credentials.r   )Ú
strip_ansi)Úredact_sensitive_textT)Ú	code_file)Útools.ansi_striprÞ   Úagent.redactrß   r7   )r   rÞ   rß   r1   s       r6   Ú_clean_outputrã   ã  s0   € õ ,Ý2Ü1°+Ó>Ñ€K�Ù ¡¨KÓ!8ÀDÔIÈ8ÐSÐSr8   Útimeout_msgc                 ó    — | r| d|› �z   S d|› �S )u�   Timeout message goes in the output too â€” an empty result makes models answer as if
    nothing happened, and the gateway drops empty replies.u   

â�° u   â�° rw   )r   rä   s     r6   Ú_with_timeout_noticeræ   ì  s%   € ñ 6Aˆ;˜8 K =Ð1Ñ1ÐZÈÈ[ÈMÐFZÐZr8   )Útool_calls_madeÚdurationÚuser_summaryÚerrorrç   rè   ré   c                óL   — d| ||dœ}|r||d<   t        j                  |d¬«      S )Nrê   )Ústatusrê   rç   Úduration_secondsré   F©Úensure_ascii)ÚjsonÚdumps)rê   rç   rè   ré   Úbodys        r6   Ú_error_resultró   ò  s/   € à¨À/ÐgoÑp€DÙØ+ˆˆ^ÑÜ�:‰:�d¨Ô/Ð/r8   rQ   Ú
exec_startc                 óÎ   — t        t        j                  «       |z
  d«      }t        j	                  d||t        | «      j                  | d¬«       t        t        | «      ||¬«      S )Né   z?execute_code remote failed after %ss with %d tool calls: %s: %sTrU   )rç   rè   )	Úroundr¥   Ú	monotonicrL   rê   ÚtypeÚ__name__ró   rJ   )rQ   rô   rç   rè   s       r6   Ú_remote_failurerû   ú  sW   € Ü”T—^‘^Ó%¨
Ñ2°AÓ6€HÜ
‡L�LÐRØ˜?¬D°«I×,>Ñ,>ÀÈdð ô Täœ˜S›°?ÈXÔVÐVr8   r«   Úinterrupted)é|   é‚   rì   Ú
raw_stdoutÚfieldsÚkernelc                 ó¦   — t        |«      \  }}| |dœ|¥dt        t        j                  «       |z
  d«      i¥}|�||d<   |j	                  |«       |S )z¥Common remote reply shape: status, cleaned output, *fields*, duration, optional kernel
    info, then truncation metadata (key order is part of the result contract).)rì   Úoutputrí   rö   r  )rã   r÷   r¥   rø   Úupdate)rì   rÿ   rô   r   r  r   Ústdout_metadataÚresults           r6   Ú_remote_resultr    sh   € ô $1°Ó#<Ñ €K�Ø(.¸+ñ [Èð [Ø0´%¼¿¹Ó8HÈ:Ñ8UÐWXÓ2Yñ[€FàÐØ!ˆˆxÑØ
‡M�M�/Ô"Ø€Mr8   r  c                 ó2   — || d<   t        | d   |«      | d<   y )Nrê   r  )ræ   )r  rä   s     r6   Ú_apply_timeoutr	    s!   € Ø!€Fˆ7�OÜ+¨F°8Ñ,<¸kÓJ€Fˆ8Òr8   Úkernel_resultc                óÎ  — | j                  dd«      xs d}| j                  dd«      xs d}| j                  dd«      xs d}|s|r|dz   |z   |z   }t        | j                  dd«      ||d| j                  dd	«      i| j                  d
ddi«      ¬«      }|d   dk(  rt        |d|› d�«       n!|d   dk(  r| j                  d«      r| d   |d<   t        j                  |d¬«      S )zªPost-process a remote-kernel cell result into the tool's JSON reply. Timeout messaging
    mirrors the local kernel contract (kernel killed, state lost, next call fresh).ÚstdoutÚ ÚstderrÚ	tracebackz
--- stderr ---
rì   rê   rç   r   r  ÚremoteT)r  r«   zCell timed out after z[s; the remote session kernel was killed and its state was lost. The next call starts fresh.Frî   )r\   r  r	  rð   rñ   )r
  r«   rô   r   rx   Útraceback_textr  s          r6   Ú_finish_remote_kernel_resultr    s  € ð  ×#Ñ# H¨bÓ1Ò7°R€KØ×#Ñ# H¨bÓ1Ò7°R€KØ"×&Ñ& {°BÓ7Ò=¸2€NÙ‘nð "Ð$8Ñ8¸;ÑFÈÑWˆÜ˜M×-Ñ-¨h¸Ó@À+ÈzØ.°×0AÑ0AÐBSÐUVÓ0WÐXØ#0×#4Ñ#4°XÀÈ$Ð?OÓ#PôR€Fð ˆhÑ˜9Ò$Ü�vÐ!6°w°ið @\ð  \õ 	]à	�Ñ	˜WÒ	$¨×):Ñ):¸7Ô)CØ'¨Ñ0ˆˆw‰Ü�:‰:�f¨5Ô1Ð1r8   c                 óP   — t        t        t        | xs d«      z  «      xs t        S )uX   Enabled âˆ© SANDBOX_ALLOWED_TOOLS, or every sandbox tool when the intersection is empty.rw   )Ú	frozensetrh   ri   )rl   s    r6   Ú_sandbox_tools_forr  ,  s#   € äÔ*¬S°Ò1DÀ"Ó-EÑEÓFÒ_ÔJ_Ð_r8   rW   Úcoder§   Úsandbox_toolsÚmax_tool_callsc                óJ  — t        | «      › dt        j                  «       j                  dd › �}t	        j
                  |«      }	t	        j
                  |› d�«      }
dgt        j                  «       d}}}	 | j                  d|
› �dd¬	«       t        j                  d
«      }t        | |› d�t        t        |«      d¬«      «       t        | |› d�|«       t        j                  t        t         «      d| |› d�|g |||||f	¬«      }|j#                  «        d|
› dt	        j
                  |«      › d�}t%        «       }|r|dt	        j
                  |«      › �z  }t&        j)                  d||dd «       | j                  d|	› d|› d�|¬«      }|j+                  dd«      xs d}|j+                  dd«      }t,        j+                  |d«      }	 |j3                  «        |�|j5                  d ¬«       	 | j                  d!|	› �dd"¬	«       t9        |||||d   d$œ«      }|d%k(  r/t;        |d&|› d'�«       t&        j=                  d(|d)   ||d   «       n)|d*k(  rt?        |d   «      |d<   n|dk7  rd+|d,<   d-|› �|d+<   tA        jB                  |d.¬/«      S # t.        $ r{}t1        |||d   «      cY d}~|j3                  «        |�|j5                  d ¬«       	 | j                  d!|	› �dd"¬	«       S # t.        $ r t&        j7                  d#|«       Y S w xY wd}~ww xY w# t.        $ r t&        j7                  d#|«       Y �Œ1w xY w# |j3                  «        |�|j5                  d ¬«       	 | j                  d!|	› �dd"¬	«       w # t.        $ r t&        j7                  d#|«       Y w w xY wxY w)0z�Per-call script ship: stage hermes_tools.py + script.py in a fresh remote sandbox dir,
    serve file-RPC from a polling thread, run, clean up.z/hermes_exec_Nr@   z/rpcr   z	mkdir -p rÀ   é
   rÂ   é    z/hermes_tools.pyrƒ   )r�   z
/script.pyT)ÚtargetÚdaemonÚargszHERMES_RPC_DIR=z HERMES_RPC_TOKEN=z PYTHONDONTWRITEBYTECODE=1z TZ=z)Executing code on %s backend (task %s)...r©   zcd z && z python3 script.py©r«   r  r  Ú
returncodeéÿÿÿÿÚsuccessé   zrm -rf é   z$Failed to clean up remote sandbox %s)Ú	exit_coderç   r«   zScript timed out after zs and was killed.zHexecute_code (remote) timed out after %ss (limit %ss) with %d tool callsrí   rü   rê   rì   zScript exited with code Frî   )"rÕ   ÚuuidÚuuid4ÚhexrÆ   rÇ   r¶   ÚEventrÅ   ÚsecretsÚtoken_urlsaferÉ   r’   ÚlistÚThreadr   r   Ústartr   rL   r¸   r\   Ú_REMOTE_EXIT_STATUSrK   rû   ri   rj   rM   r  r	  r&   rÜ   rð   rñ   )r¦   rW   r  r§   r  r«   r  rô   Úsandbox_dirÚquoted_sandbox_dirÚquoted_rpc_dirÚtool_call_counterÚ
stop_eventÚ
rpc_threadÚ	rpc_tokenÚ
env_prefixÚtzÚscript_resultr   r%  rì   rQ   r  s                          r6   Ú_run_remote_per_callr:  1  så  € ô
 # 3Ó'Ð(¨´d·j±j³l×6FÑ6FÀsÈÐ6KÐ5LÐM€KÜŸ™ [Ó1ÐÜ—[‘[ K =°Ð!5Ó6€NØ12°´Y·_±_Ó5FÈ :�zÐð#NØ�‰�i Ð/Ð0°cÀ2ˆÔFÜ×)Ñ)¨"Ó-ˆ	Ü˜S [ MÐ1AÐ"BÜ9¼$¸}Ó:MÐY_Ô`ô	bä˜S [ M°Ð"<¸dÔCô ×%Ñ%Ü.¬~Ó>ÀtØ˜+˜ dÐ+Ð->ÀÐDUØ  -°¸YðHôIˆ
ð 	×ÑÔØ'¨Ð'7Ð7IÌ%Ï+É+ÐV_ÓJ`ÐIað b2ð 2ˆ
äÓ ˆÙØ˜D¤§¡¨R£Ð 1Ð2Ñ2ˆJÜ�‰Ð?ÀÐK\Ð]_Ð^_ÐK`ÔaØŸ™ cÐ*<Ð)=¸TÀ*ÀÐM_Ð$`Ø,3ð $ó 5ˆà#×'Ñ'¨°"Ó5Ò;¸ˆØ!×%Ñ% l°BÓ7ˆ	ä$×(Ñ(¨°IÓ>‰ð 	�‰ÔØÐ!Ø�O‰O AˆOÔ&ð	NØ�K‰K˜'Ð"4Ð!5Ð6¸CÈˆKÔLô ˜F K°Ø*3ÐHYÐZ[ÑH\Ñ]ó_€Fà�ÒÜ�vÐ!8¸¸	ÐARÐSÔTÜ�‰ÐaØÐ0Ñ1°7Ð<MÈaÑ<Põ	Rà	�=Ò	 Ü5°f¸XÑ6FÓGˆˆxÒØ	�aŠØ"ˆˆxÑØ4°Y°KÐ@ˆˆw‰Ü�:‰:�f¨5Ô1Ð1øô+ ò FÜ˜s JÐ0AÀ!Ñ0DÓEÔEà�‰ÔØÐ!Ø�O‰O AˆOÔ&ð	NØ�K‰K˜'Ð"4Ð!5Ð6¸CÈˆKÕLøÜò 	NÜ�L‰LÐ?ÀÖMð	NþðFûô ò 	NÜ�L‰LÐ?À×Mð	Nûð 	�‰ÔØÐ!Ø�O‰O AˆOÔ&ð	NØ�K‰K˜'Ð"4Ð!5Ð6¸CÈˆKÕLøÜò 	NÜ�L‰LÐ?ÀÖMð	Nýsy   Á7EJ Ç3L Ê	LÊLÊ*LÊ+L? ËK,Ë,LÌLÌLÌL? ÌL<Ì;L<Ì?%N"Í%M=Í<N"Í=NÎN"ÎNÎN"Úresetc                 óÒ  — t        «       }|j                  dt        «      |j                  dt        «      }}t	        |«      |xs d}}t        |«      \  }	}
t        j                  «       }	 |	j                  ddd¬«      }d|j                  d	d
«      vrt        d|
› d�«      S 	 ddl
m}  || |	|
|t        |«      ||t        |«      t        |j                  dd«      «      ¬«	      }|�t%        |||¬«      S t         j'                  d|
«       t+        |	|
| |||||¬«      S # t        $ r t         j#                  dd¬«       d}Y Œ]w xY w# t        $ r}t)        ||d«      cY d}~S d}~ww xY w)u  Run code on the remote terminal backend: the owner's persistent remote session kernel
    (tools/code_kernel_remote.py) first, else the per-call script ship â€” the fail-open route when
    a kernel cannot be spawned and the only route for hosts that cannot sustain a background process.r«   r  Údefaultz-command -v python3 >/dev/null 2>&1 && echo OKrÀ   r$  rÂ   ÚOKr  r  z!Python 3 is not available in the zO terminal environment. Install Python to use execute_code with remote backends.r   )Úexecute_in_remote_kernelÚkernel_idle_timeouti  )r¦   rW   Útask_env_idr  r«   r  r;  Ú	idle_exitz;remote session-kernel path failed; falling back to per-callTrU   N)r«   rô   z<remote session kernel unavailable on %s; using per-call path)r«   r  rô   )Ú_load_configr\   ÚDEFAULT_TIMEOUTÚDEFAULT_MAX_TOOL_CALLSr  r»   r¥   rø   rÅ   ró   Útools.code_kernel_remoter?  r  ÚboolÚintrK   rL   r&   r  r¸   rû   r:  )r  r“   rl   r;  Ú_cfgr«   r  r  r§   r¦   rW   rô   Úpy_checkr?  r
  rQ   s                   r6   Ú_execute_remoterK  l  sˆ  € ô
 ‹>€DØ"Ÿh™h y´/ÓBÀDÇHÁHÐM]Ô_uÓDvˆ^€GÜ'9¸-Ó'HÈ'ÒJ^ÐU^Ð$€MÜ&Ð'8Ó9�M€CˆÜ—‘Ó!€Jð3Ø—;‘;ÐNÐTWÐac�;ÓdˆØ�x—|‘| H¨bÓ1Ñ1Ü Ð#DÀXÀJð Oið "ió jð jð
	!õ JÙ4Ø˜#¨Ð>OÜ'¨Ó6ÀØ-´T¸%³[Ü˜dŸh™hÐ'<¸dÓCÓDô	ˆMð Ð$Ü/°ÀwÐ[eÔfÐfÜ�‰ÐRÐT\Ô]ô    X¨tÐ5FÈØ(/ÀÐ[eôgð gøô ò 	!Ü�N‰NÐXÐcgˆNÔhØ ŠMð	!ûô ò 3Ü˜s J°Ó2Õ2ûð3úsI   Á+6E Â#AD Ã%E Ã5E Ä"EÅ E ÅEÅE Å	E&ÅE!ÅE&Å!E&c                 ón  — t         st        d«      S 	 ddlm}  |«        | r| j                  «       st        d«      S ddlm} dd	lm	} dd
l
m} t        t        «       j                  dt        «      |«      }	 |||	d¬«      }
|
j                   rt        d|	› d�«      S |
j"                  r0ddlm}m}m}  || «      r || «      rt        |«      S t        d«      S ddl
m}m}  |«       }|d   }ddlm}  || | ||«      ¬«      }|j                  dd«      s/t5        |j                  d«      xs d|j                  d«      ¬«      S |j                  d«      rddlm}  |«        |dk7  rt;        | ||t=        |«      ¬«      S dd lm} dd!l m!} t        «       }tE        «       } || |xs d"|tG        |«      tI        |d"|xs d"¬#«      tK        tM        |«      «      |j                  dt        «      |j                  d$tN        «      t=        |«      |¬%«
      S # t        $ r}t        d|› d�«      cY d}~S d}~ww xY w)&uÓ  Run Python in the session's persistent kernel (local) or on the remote terminal backend,
    with RPC access to a subset of Hermes tools; returns the JSON result string. "Sandbox" means
    the security envelope (env scrubbing, tool whitelist + call budget, output redaction), not an
    isolation jail: default `project` mode runs in the session's cwd with the project venv.
    ``enabled_tools`` âˆ© SANDBOX_ALLOWED_TOOLS; ``reset`` kills the existing kernel first.z~execute_code sandbox is unavailable in this environment. Use normal tool calls (terminal, read_file, write_file, ...) instead.r   )Úenforce_no_refusalzexecute_code refused: zU (profile terminal policy unresolved; fix the profile's config.yaml / .env and retry)Nz˜No code provided. execute_code requires a non-empty 'code' parameter containing Python source. To run shell commands, use terminal(command=...) instead.)Úrun_bounded_sync)Ú_is_supervised_gateway_process)Ú_PRE_EXEC_GUARD_MIN_TIMEOUT_Sr«   zexecute_code.lifecycle-guard)Úlabelz3execute_code lifecycle guard did not finish within zHs (process-identity probe wedged); the code was not run. Retry the call.)ÚHOST_INTERPRETER_KILL_REJECTIONÚ"contains_gateway_lifecycle_commandÚcontains_host_interpreter_killzíBlocked: cannot restart or stop the gateway from inside the gateway process. The gateway would kill this script before it could complete (SIGTERM propagates to child processes). Run the lifecycle command from a shell outside the gateway.)rS   Ú_docker_has_host_accessrW   )Úcheck_execute_code_guard)Úhas_host_accessÚapprovedFÚmessagez'execute_code blocked by approval guard.ré   )ré   Úuser_approved)Úclear_current_thread_interruptr­   )r;  )Úis_interrupted)Úexecute_in_session_kernelr  )r“   r  )	r“   ÚmodeÚchild_pythonÚ	child_cwdr  r«   r  r;  r\  )(rY   r
   Útools.terminal_scoperM  rK   ÚstripÚagent.deadlinerN  Útools.process_registryrO  rZ   rP  ÚmaxrC  r\   rD  Ú	timed_outÚvalueÚcron.lifecycle_guardrR  rS  rT  rS   rU  Útools.approvalrV  ró   rÙ   r[  rK  rG  r\  Útools.code_kernelr]  Ú_get_execution_moder   r   r  r  rE  )r  r“   rl   r;  rM  ÚrefusalrN  rO  rP  Ú_probe_timeoutÚ_proberR  rS  rT  rS   rU  Ú_env_configrW   rV  Ú_guardr[  Ú_is_interruptedr]  rI  Ú_modes                            r6   Úexecute_coders  ˜  sK  € õ Üð bó cð 	cðrå;ÙÔñ �t—z‘z”|Üð zó {ð 	{õ 0ÝEÝAÜœ›×+Ñ+¨I´ÓGÐIfÓg€NÙØ&¨Ð>\ô€Fð ×ÒÜØAÀ.ÐAQð RUð Uó
ð 	
ð ‡|‚|÷	
ñ 	
ñ
 .¨dÔ3Ù-¨dÔ3Ü!Ð"AÓBÐBÜðNóð ÷ MÙ!Ó#€KØ˜:Ñ&€Hõ
 8Ù% d¨HÑF]Ð^iÓFjÔk€FØ�:‰:�j %Ô(Ü˜VŸZ™Z¨	Ó2Ò_Ð6_Ø*0¯*©*°^Ó*DôFð 	Fð ‡z�z�/Ô"ÝBÙ&Ô(Ø�7ÒÜ˜t W¨mÄ4ÈÃ;ÔOÐOÝAõ <Ü‹>€DÜÓ!€EÙ$Ø�g’m ¨%Ô>SÐTYÓ>ZÜ$ U¨B¸ºÀ2ÔFÜÔ 2°=Ó AÓBØ—‘˜¤OÓ4Ø—x‘xÐ 0Ô2HÓIÜ�5‹k¨/ôð øôG ò rÜÐ2°7°)ð <qð qó rõ 	rûðrús   “H È	H4ÈH/È)H4È/H4Úescalatec                 óÄ   ‡ — ddl }dˆ fd„} |t        |dd«      «       |r	 ‰ j                  d¬«       yy# t        j                  $ r  |t        |dd«      «       Y yw xY w)	u  Kill the child and its whole process tree via agent.deadline.kill_process_tree: SIGTERM
    (killpg + psutil descendant sweep; ``taskkill /T /F`` on Windows, where sig is ignored); with
    ``escalate=True`` wait 5s then SIGKILL survivors. Never raises â€” falls back to ``proc.kill()``.r   Nc                 ó"  •— 	 ddl m}  |‰j                  | ¬«       y # t        $ rg}t        j                  d|d¬«       	 ‰j                  «        n3# t        $ r"}t        j                  d|d¬«       Y d }~nd }~ww xY wY d }~y Y d }~y d }~ww xY w)Nr   )Úkill_process_tree)r‰   z$Could not terminate process tree: %sTrU   zCould not kill process: %s)rc  rw  ÚpidrK   rL   rM   Úkill)r‰   rw  ÚeÚe2Úprocs       €r6   Ú_tree_signalz)_kill_process_group.<locals>._tree_signalþ  s~   ø€ ð	NÝ8Ù˜dŸh™h¨CÖ0øÜò 	NÜ�L‰LÐ?ÀÈTˆLÔRðNØ—	‘	•øÜò NÜ—‘Ð9¸2È�×MÑMûðNúÜMô ûð	Nús>   ƒ ž	B§B	Á AÁB	Á	A<ÁA7Á2B	Á7A<Á<B	Â	BÚSIGTERMr#  r  ÚSIGKILL)r   N)ÚsignalrÌ   ÚwaitÚ
subprocessÚTimeoutExpired)r|  rt  Ú_signalr}  s   `   r6   Ú_kill_process_groupr…  ù  sd   ø€ ó õ	Nñ ”˜ )¨TÓ2Ô3Ùð	<Ø�I‰I˜aˆIÕ ð øô ×(Ñ(ò 	<Ùœ ¨)°TÓ:Ö;ð	<ús   ¢6 ¶&AÁAc                  óˆ   — 	 ddl m}   | «       j                  di «      }t        |t        «      r|S i S # t
        $ r i cY S w xY w)u³   Effective ``code_execution`` section (defaults + user file + managed overlay) â€” runs while the
    module-level schema is built at tool discovery, so it must not import ``cli``.r   )Úload_config_readonlyÚcode_execution)Úhermes_cli.configr‡  r\   rÐ   ÚdictrK   )r‡  Úcfgs     r6   rC  rC    sH   € ðÝ:Ù"Ó$×(Ñ(Ð)9¸2Ó>ˆÜ  ¤dÔ+ˆsÐ3°Ð3øÜò ØŠ	ðús   ‚.3 ±3 ³AÁ A)ÚprojectÚstrictrŒ  c                  óè   — t        t        «       j                  dt        «      «      j	                  «       j                  «       } | t        v r| S t        j                  d| t        t        «       t        S )uÇ   ``code_execution.mode`` (invalid â†’ default with a warning). ``project``: session cwd + active
    venv python; ``strict``: isolated temp dir + ``sys.executable``. Scrubbing/whitelist apply to both.r^  zHIgnoring code_execution.mode=%r (expected one of %s), falling back to %r)	rJ   rC  r\   ÚDEFAULT_EXECUTION_MODErb  ÚlowerÚEXECUTION_MODESrL   r&   )Ú	cfg_values    r6   rk  rk  #  s]   € ô ”L“N×&Ñ& vÔ/EÓFÓG×MÑMÓO×UÑUÓW€IØ”OÑ#ØÐÜ
‡N�NØRØ”?Ô$:ôô "Ð!r8   ))r   zv  web_search(query: str, limit: int = 5) -> dict
    Returns {"data": {"web": [{"url", "title", "description"}, ...]}})r   a.    web_extract(urls: list[str], char_limit: int = None) -> dict
    Returns {"results": [{"url", "title", "content", "error"}, ...]} where content is markdown.
    No LLM summarization. Pages over char_limit (default 15000) are head+tail truncated; full text stored on disk (path in the content footer).)r   zˆ  read_file(path: str, offset: int = 1, limit: int = 2000) -> dict
    Lines are 1-indexed. Returns {"content": "...", "total_lines": N})r   zT  write_file(path: str, content: str) -> dict
    Always overwrites the entire file.)r   zÕ  search_files(pattern: str, target="content", path=".", file_glob=None, limit=50, order="discovery") -> dict
    target: "content" (search inside files) or "files" (find files by name). Returns {"matches": [...]})r   zŒ  patch(path: str, old_string: str, new_string: str, replace_all: bool = False) -> dict
    Replaces old_string with new_string in the file.)r   z�  terminal(command: str, timeout=None, workdir=None) -> dict
    Foreground only (no background/pty). Returns {"output": "...", "exit_code": N}Úenabled_sandbox_toolsr^  c                 óD  ‡ — ‰ €t         Š |€
t        «       }dj                  ˆ fd„t        D «       «      }dD �cg c]	  }|‰ v sŒ|‘Œ }}|xs t	        ‰ «      dd }|rdj                  |«      dz   nd}|d	k(  rd
}nd}d|› d|› d�}d|ddd|› d�dœdddœdœdgdœdœS c c}w )uÀ   execute_code schema listing only *enabled_sandbox_tools* â€” a disabled tool (e.g. web off)
    must not appear or the model keeps trying it. ``mode`` (None â†’ config) picks the cwd sentence.Nr„   c              3   ó2   •K  — | ]  \  }}|‰v sŒ|–— Œ y ­wr¤   rw   )r‡   rˆ   rŠ   r“  s      €r6   rŒ   z,build_execute_code_schema.<locals>.<genexpr>M  s   øè ø€ Òa¡9 4¨À4ÐK`ÒC`œ3Ñaùs   ƒ�)r   r   rö   re   z, ...z...r�  u    Scripts run in their own temp dir, not the session's CWD â€” use absolute paths (os.path.expanduser('~/.hermes/.env')) or terminal()/read_file() for user files.u0  Scripts run in the session's working directory. Interpreter: the project's activated venv/conda python when one is active (VIRTUAL_ENV/CONDA_PREFIX â€” matches terminal()); otherwise Hermes's own python (the common case â€” stdlib plus Hermes's deps; check `import x` before relying on project packages).a\  Run Python that calls Hermes tools programmatically. Use when you need 3+ tool calls with logic between them: filtering/reducing large outputs before they enter context, branching, or loops (N pages/files, retry on failure). Use normal tool calls for single calls, results you must reason over in full, or anything needing user interaction.

Calls run in a persistent session kernel: variables, imports, and loaded data survive across execute_code calls, so build on earlier work instead of re-loading it. A timed-out or interrupted call loses that state.

Available via `from hermes_tools import ...`:

z¨

Limits: 5-minute timeout, max 50 tool calls per call. Stdout over 50KB shows head/tail inline; the FULL text is auto-saved to a file whose path rides in the result.

u  

Helpers require imports: `from hermes_tools import json_parse, shell_quote, retry`. json_parse(text) â€” tolerant json.loads for terminal() output; shell_quote(s) â€” shlex.quote for dynamic shell args; retry(fn, max_attempts=3, delay=2) â€” exponential backoff.rs  ÚobjectÚstringzDPython code to execute. Import tools with `from hermes_tools import z(` and print your final result to stdout.)rù   ÚdescriptionÚbooleanzODiscard the kernel's persistent state and start fresh before running this code.)r  r;  r  )rù   Ú
propertiesÚrequired)rˆ   r˜  Ú
parameters)rh   rk  rj   Ú_TOOL_DOC_LINESrg   )r“  r^  Ú
tool_linesÚnÚimport_examplesÚ
import_strÚcwd_noter˜  s   `       r6   Úbuild_execute_code_schemar£  E  s  ø€ ð Ð$Ü 5ÐØ€|Ü"Ó$ˆØ—‘Óa´ÔaÓa€JØ"<Ö[˜QÀÐEZÒ@Z’qÐ[€OÐ[Ø%ÒJ¬Ð0EÓ)FÀrÈÐ)J€OÙ9H�—‘˜?Ó+¨gÒ5Èe€JØˆxÒð_ñ 	ðJð 	ð	<ð ˆ,ð qð ˆ*ð Zð	Zð ð( Ø"àà!)ð1Ø1;°ð ==ð=ñ?ð #,Øeñhñð  ˜ñ
ñð ùòO \s   ¸	BÁBr  c                 ór  — d| vr$d| v r t         j                  d«       t        d«      S | j                  dd«      }|�2t	        |t
        «      s"t        dt        |«      j                  › d�«      S t        |xs d|j                  d«      |j                  d	«      t        | j                  d
d«      «      ¬«      S )z‘Redirect misdirected calls (terminal's ``command`` arg, non-string ``code``) with an
    actionable error before dispatching to ``execute_code``.r  ÚcommandzGexecute_code received 'command' instead of the required 'code' argumentz°execute_code received a 'command' parameter, but it requires Python source in 'code'. Use terminal(command=...) for shell commands; for Python, retry as execute_code(code=...).r  zexecute_code received a zY in 'code', but it requires Python source as a string. Retry as execute_code(code="...").r“   rl   r;  F)r  r“   rl   r;  )
rL   r&   r
   r\   rÐ   rJ   rù   rú   rs  rG  )r  Úkwargsr  s      r6   Ú_execute_code_handlerr§  ‹  sÄ   € ð �TÑ˜i¨4Ñ/Ü�‰Ð`ÔaÜð Só Tð 	Tð �8‰8�F˜BÓ€DØÐ¤
¨4´Ô 5ÜÐ4´T¸$³Z×5HÑ5HÐ4Ið Jeð eó fð 	fä˜TšZ R°·±¸IÓ1FØ&,§j¡j°Ó&AÌÈdÏhÉhÐW^Ð`eÓNfÓIgôið ir8   rs  rˆ  u   ðŸ��i † )rˆ   ÚtoolsetÚschemaÚhandlerÚcheck_fnÚemojiÚmax_result_size_charsÚsession)zper-callr®  Úthread_scoped_silence)zagent.thread_scoped_outputr¯  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_compatr±  rÌ   Úimport_module)rˆ   r  r´  r±  s       r6   Ú__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ÐAr8   r¤   )Úuds)F)NNF)NN)`Ú__doc__rÃ   rð   ÚloggingÚosr{   r*  rÆ   r‚  rÎ   r¶   r¥   r&  Útypingr   r   r   r   r   Útools.thread_contextr   Útools.registryr	   r
   Úhermes_timer   Útools.code_execution_envr   r   Útools.code_execution_rpcr   Útools.tool_output_truncater   r   Ú	getLoggerrú   rL   rY   r  rh   rD  rE  r+   ÚMAX_STDERR_BYTESrF   rJ   r7   r-   rG  r^   r�   ro   rz   r€   r’   Ú_COMMON_HELPERSrŽ   r�   r»   rÉ   rÕ   rÜ   rã   ræ   rH  Úfloatró   ÚBaseExceptionrû   r/  r  r	  r  r  r:  rK  rs  r…  rŠ  rC  r‘  r�  rk  r�  ri   r£  ÚEXECUTE_CODE_SCHEMAr§  ÚregisterÚplatformÚsocketÚsysÚDEFAULT_KERNEL_MODEÚKERNEL_MODESr²  r·  rw   r8   r6   ú<module>rÏ     sS  ðñ
ó Û Û Û 	Û 	Û Û Û Û Û Û Û ß 3Õ 3å <ß /å )ß NÝ 3ß Ià	ˆ×	Ñ	˜8Ó	$€ð Ð ñ "ò #ó Ð ð
 €ØÐ ØÐ ØÐ à$Ð ð sð ¨u°S¸$¸sÀC¸x¹.Ð5HÑ/Ió ð6 Cð ¨H°S©Mó ð,d Dó dð$,ð4ð<ðNðzðhðHñ'€ð2
G¸3ó 
Gð" 7Ð8YÐZØGñ;ð<ð 8ñGðHð gñnðoðÐ ñ sð À8ÈCÁ=ó ð" 38ñ°°S±	ð Ø,/ðØ<?óð'€ðV
ð ñ
ðGñQÐ ðj
ð ñ
ð4ñ>Ð ðF1 ó 1ðhc¨3ð c¸ð cÀó cð!�sð !˜só !ðA¨Cð A°Có AðT˜sð T u¨S°$°s¸C°x±.Ð-@Ñ'Aó Tð[ cð [¸ð [Àó [ð 9:ÈQØ04ò0˜ð 0°#ð 0ÀUð 0Ø (¨¡ð0Ø9<ó0ðW˜ð W°Eð WÈCð WÐTWó Wð &¨MÑ:Ð ð 7;ñ
˜3ð 
¨Cð 
¸Uð 
ÈDÐQTÐVYÐQYÉNð 
Ø# D¨¨c¨¡NÑ3ð
Ø?CÀCÈÀH¹~ó
ðK˜4  S ™>ð K¸ð KÀó Kð
2°°S¸#°X±ð 2Ø*-ð2Ø;@ð2ØEHó2ð,` h¨t°C©yÑ&9ð `¸ió `ð
82¨ð 82°3ð 82È3ð 82Ø(1ð82Ø?Bð82ØTWð82à%*ð82à/2ó82ðx #(ñ&g˜#ð &g¨°©ð &gÀhÈtÐTWÉyÑFYð &gØð&gØ,/ó&gð\ "Ø)-Øñ	^Ø
ð^à�c‰]ð^ð ˜D ™IÑ&ð^ð ð	^ð
 	ó^ñB<¨ó <ð.�dó ð (€Ø"Ð ð
"˜Só 
"ò €ð$ <@Ø*.ñ?°Sð ?Ø$'ð?Ø37ó?ñF 0Ó1Ð ði ð i°3ó ið  €× Ñ Ø	Ð!1Ð:MØ!Ð,FÈfØ!õó Û Û 
àÐ à&€ð ÐTðÐ ó
Br8   