fix(tools): isolate external project environments
This commit is contained in:
parent
a3bcb2c232
commit
76961b61bd
|
|
@ -1455,16 +1455,6 @@ def execute_code(
|
|||
# with a C/POSIX locale (containers, minimal base images).
|
||||
child_env["PYTHONIOENCODING"] = "utf-8"
|
||||
child_env["PYTHONUTF8"] = "1"
|
||||
# Ensure the hermes-agent root is importable in the sandbox so
|
||||
# repo-root modules are available to child scripts. We also prepend
|
||||
# the staging tmpdir so ``from hermes_tools import ...`` resolves even
|
||||
# when the subprocess CWD is not tmpdir (project mode).
|
||||
_hermes_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
_existing_pp = child_env.get("PYTHONPATH", "")
|
||||
_pp_parts = [tmpdir, _hermes_root]
|
||||
if _existing_pp:
|
||||
_pp_parts.append(_existing_pp)
|
||||
child_env["PYTHONPATH"] = os.pathsep.join(_pp_parts)
|
||||
# Inject user's configured timezone so datetime.now() in sandboxed
|
||||
# code reflects the correct wall-clock time. Only TZ is set —
|
||||
# HERMES_TIMEZONE is an internal Hermes setting and must not leak
|
||||
|
|
@ -1487,6 +1477,22 @@ def execute_code(
|
|||
_child_cwd = _resolve_child_cwd(_mode, tmpdir, task_id=task_id or "")
|
||||
_script_path = os.path.join(tmpdir, "script.py")
|
||||
|
||||
# ``hermes_tools.py`` always lives in the staging directory, so that
|
||||
# directory must be importable even when project mode changes CWD.
|
||||
# Hermes's own package root is useful too, but only when the child
|
||||
# uses the same Python environment. Project mode can select an
|
||||
# external venv; exposing Hermes's site-packages to that interpreter
|
||||
# can mix incompatible compiled extensions (for example, Python 3.12
|
||||
# NumPy with a Python 3.9 project interpreter).
|
||||
_hermes_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
_existing_pp = child_env.get("PYTHONPATH", "")
|
||||
_pp_parts = [tmpdir]
|
||||
if _uses_hermes_python_environment(_child_python):
|
||||
_pp_parts.append(_hermes_root)
|
||||
if _existing_pp:
|
||||
_pp_parts.append(_existing_pp)
|
||||
child_env["PYTHONPATH"] = os.pathsep.join(_pp_parts)
|
||||
|
||||
proc = subprocess.Popen(
|
||||
[_child_python, _script_path],
|
||||
cwd=_child_cwd,
|
||||
|
|
@ -1850,6 +1856,33 @@ def _is_usable_python(python_path: str) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=32)
|
||||
def _python_environment_prefix(python_path: str) -> str:
|
||||
"""Return the resolved ``sys.prefix`` reported by *python_path*, if any."""
|
||||
try:
|
||||
from agent.delegation_context import delegated_child_subprocess_env
|
||||
|
||||
result = subprocess.run(
|
||||
[python_path, "-c", "import sys; print(sys.prefix)"],
|
||||
timeout=5,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
creationflags=subprocess.CREATE_NO_WINDOW if _IS_WINDOWS else 0,
|
||||
stdin=subprocess.DEVNULL,
|
||||
env=delegated_child_subprocess_env(),
|
||||
)
|
||||
if result.returncode == 0 and result.stdout.strip():
|
||||
return os.path.realpath(result.stdout.strip())
|
||||
except (OSError, subprocess.TimeoutExpired, subprocess.SubprocessError):
|
||||
pass
|
||||
return ""
|
||||
|
||||
|
||||
def _uses_hermes_python_environment(python_path: str) -> bool:
|
||||
"""Whether *python_path* belongs to Hermes's active Python environment."""
|
||||
return _python_environment_prefix(python_path) == os.path.realpath(sys.prefix)
|
||||
|
||||
|
||||
def _resolve_child_python(mode: str) -> str:
|
||||
"""Pick the Python interpreter for the execute_code subprocess.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue