"""The ``bot_mode_dm.py`` runner entry boots Hermes' dependency environment itself. ``message_agent`` spawns `` tools/bot_mode_dm.py --run-delivery …`` and the relay spawns ``… --wait-reply …`` (``bot_relay.waiter_command``). Under a PM-managed install ``sys.executable`` is the bare store interpreter: dependencies are activated in-process at boot, and the terminal backend strips the Hermes-owned ``PYTHONPATH``, so a runner that does not boot like every other entry point dies on its first third-party import. Live, every local Bot Chat DM came back ``Live admission outcome unknown: No module named 'ruamel'. Do not resend.`` Each test launches the real script under ``-I -S`` (no site-packages, no ``PYTHON*`` env), with a PM-committed dependency record in a temp ``HERMES_HOME`` as the only way to reach the packages. """ import json import os import shlex import subprocess import sys from pathlib import Path import pytest from pm.environments import install_state_dir, runtime_facts_path, site_packages from tools import bot_relay from tools.bot_mode_dm import _dm_delivery_id REPO = Path(__file__).resolve().parents[2] RUNNER = REPO / "tools" / "bot_mode_dm.py" # A bare interpreter: isolated (-I ignores PYTHONPATH/PYTHONHOME, user site) and no site # module (-S), so no third-party package is importable unless the entry activates one. BARE = [sys.executable, "-I", "-S"] @pytest.fixture def committed_home(tmp_path, monkeypatch): """A temp ``HERMES_HOME`` whose install state commits a dependency generation for REPO. The generation's site-packages carries a ``.pth`` naming this interpreter's own package directories, so activating it (and only activating it) makes the real dependencies importable. """ home = tmp_path / "hermes-home" home.mkdir() monkeypatch.setenv("HERMES_HOME", str(home)) venv = install_state_dir(REPO) / "environments" / "gen" / "venv" venv.mkdir(parents=True) (venv / "pyvenv.cfg").write_text( "version = {}.{}.{}\n".format(*sys.version_info[:3]), encoding="utf-8") packages = site_packages(venv) packages.mkdir(parents=True) dirs = [p for p in sys.path if p and Path(p).is_dir() and Path(p).resolve() != REPO] (packages / "hermes-test-deps.pth").write_text("\n".join(dirs) + "\n", encoding="utf-8") return home, _commit(home, venv) @pytest.fixture def uncommittable_home(tmp_path, monkeypatch): """A temp ``HERMES_HOME`` whose committed generation is gone, so activation cannot succeed.""" home = tmp_path / "hermes-home" home.mkdir() monkeypatch.setenv("HERMES_HOME", str(home)) install_state_dir(REPO).mkdir(parents=True) return home, _commit(home, install_state_dir(REPO) / "environments" / "missing" / "venv") def _commit(home, venv): runtime_facts_path(REPO).write_text( json.dumps({"packages": {"venv": {"environment": str(venv)}}}), encoding="utf-8") env = {k: v for k, v in os.environ.items() if k not in ("PYTHONPATH", "PYTHONHOME", "VIRTUAL_ENV")} env["HERMES_HOME"] = str(home) return env def _run(argv, env): return subprocess.run(argv, capture_output=True, text=True, encoding="utf-8", errors="replace", env=env, cwd=str(REPO.parent), timeout=120) def test_delivery_runner_admits_through_dependencies_it_activates(tmp_path, committed_home): """Live admission (``_admit_live_dm``) imports Hermes' third-party graph. Launched bare, the runner must activate the committed environment, find no live owner, and hand the DM to the transport, which consumes the file — never report an ``ambiguous`` import failure.""" _home, env = committed_home profile_home = tmp_path / "profile" profile_home.mkdir() dm_file = tmp_path / "dm.txt" dm_file.write_text("hello teammate", encoding="utf-8") # The transport echoes the DM file it receives via --query-file. transport = [sys.executable, "-I", "-S", "-c", "import sys; print(open(sys.argv[2], encoding='utf-8').read())"] result = _run([*BARE, str(RUNNER), "--run-delivery", "query-file", str(dm_file), "--profile-home", str(profile_home), *transport], env) assert "No module named" not in result.stdout + result.stderr, result assert result.returncode == 0, result assert result.stdout.strip() == "hello teammate" assert not dm_file.exists() def test_delivery_runner_that_cannot_activate_names_the_repair_remedy(tmp_path, uncommittable_home): """When the committed environment cannot be activated, the runner exits with PM's ``hermes pm repair`` remedy before touching the DM, instead of an ambiguous ``No module named …`` outcome the sender is told not to resend.""" _home, env = uncommittable_home dm_file = tmp_path / "dm.txt" dm_file.write_text("hello teammate", encoding="utf-8") result = _run([*BARE, str(RUNNER), "--run-delivery", "query-file", str(dm_file), sys.executable, "-c", "pass"], env) assert result.returncode == 1, result assert "hermes pm repair" in result.stderr assert "No module named" not in result.stdout + result.stderr assert result.stdout == "" # nothing was handed over, so no "do not resend" outcome assert dm_file.read_text(encoding="utf-8") == "hello teammate" def test_delivery_runner_that_cannot_activate_after_live_admission_stays_ambiguous(tmp_path, uncommittable_home): """The sender may already have admitted this DM to the live owner (its pinned intent exists) and was told not to resend. A runner that then cannot activate must keep that outcome unknown, with the same delivery id, instead of a bare repair hint that reads as a failure.""" _home, env = uncommittable_home dm_file = tmp_path / "dm.txt" dm_file.write_text("hello teammate", encoding="utf-8") intent = Path(str(dm_file) + ".live.json") intent.write_text("{}", encoding="utf-8") result = _run([*BARE, str(RUNNER), "--run-delivery", "query-file", str(dm_file), "--profile-home", str(tmp_path), sys.executable, "-c", "pass"], env) assert result.returncode == 1, result assert "hermes pm repair" in result.stderr payload = json.loads(result.stdout) assert payload["status"] == "ambiguous" assert payload["delivery_id"] == _dm_delivery_id(str(dm_file)) assert "Do not resend" in payload["error"] assert dm_file.exists() and intent.exists() @pytest.mark.parametrize("home", ["committed_home", "uncommittable_home"]) def test_reply_waiter_prints_the_reply_whatever_the_dependency_state(tmp_path, request, home): """The relay's reply waiter shares the entry but is stdlib only, and its stdout is the sender's wake-up: its argv, exactly as ``waiter_command`` builds it, must print the reply whether or not the committed environment can be activated.""" _home, env = request.getfixturevalue(home) root = tmp_path / "root" envelope = {"id": "e" * 32, "target_handle": "researcher", "target_connection": "ssh-vps"} reply_path = bot_relay.relay_root(root) / bot_relay.REPLIES_DIR / f"{envelope['id']}.json" reply_path.parent.mkdir(parents=True) reply_path.write_text(json.dumps({"reply": "pong"}), encoding="utf-8") argv = shlex.split(bot_relay.waiter_command(root, envelope)) result = _run([*BARE, *argv[1:]], env) assert result.returncode == 0, result assert result.stdout.splitlines() == ["Reply from @researcher on ssh-vps:", "pong"]