mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-07-07 16:21:23 +00:00
fix(#2698): keep the pinned home across isolated fallbacks
This commit is contained in:
committed by
nesquena-hermes
parent
c7375c60c4
commit
e77ca8dbac
+5
-1
@@ -507,6 +507,8 @@ def install_cron_scheduler_profile_isolation() -> None:
|
||||
return original(job, *args, **kwargs)
|
||||
finally:
|
||||
event_profile = str((job or {}).get("profile") or "").strip() or None
|
||||
if _is_isolated_profile_mode():
|
||||
event_profile = _isolated_profile_name()
|
||||
try:
|
||||
publish_session_list_changed("cron_complete", profile=event_profile)
|
||||
except TypeError:
|
||||
@@ -1102,7 +1104,9 @@ def switch_profile(name: str, *, process_wide: bool = True) -> dict:
|
||||
)
|
||||
|
||||
# Resolve profile directory
|
||||
if _is_root_profile(name):
|
||||
if _is_isolated_profile_mode():
|
||||
home = Path(_INITIAL_HERMES_HOME).expanduser()
|
||||
elif _is_root_profile(name):
|
||||
home = _DEFAULT_HERMES_HOME
|
||||
else:
|
||||
home = _resolve_named_profile_home(name)
|
||||
|
||||
@@ -549,6 +549,8 @@ def main() -> int:
|
||||
# for SO_EXCLUSIVEADDRUSE.
|
||||
_CREATE_NEW_PROCESS_GROUP = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
|
||||
subprocess.Popen([python_exe, server_path],
|
||||
cwd=server_cwd,
|
||||
env=os.environ.copy(),
|
||||
creationflags=_CREATE_NEW_PROCESS_GROUP)
|
||||
sys.exit(0)
|
||||
os.execv(python_exe, [python_exe, server_path])
|
||||
|
||||
@@ -429,6 +429,28 @@ class TestForegroundEnvAndCwd:
|
||||
|
||||
assert chdir_calls == [str(workspace)]
|
||||
|
||||
def test_windows_foreground_popen_uses_server_cwd_override(self, setup, monkeypatch, clean_env, tmp_path):
|
||||
bs, _agent_dir = setup
|
||||
workspace = tmp_path / "workspace-win"
|
||||
workspace.mkdir()
|
||||
monkeypatch.setenv("HERMES_WEBUI_SERVER_CWD", str(workspace))
|
||||
monkeypatch.setattr(sys, "argv", ["bootstrap.py", "--foreground"])
|
||||
monkeypatch.setattr(sys, "platform", "win32")
|
||||
monkeypatch.setattr(os, "chdir", lambda p: None)
|
||||
|
||||
popen_calls = []
|
||||
|
||||
def fake_popen(*args, **kwargs):
|
||||
popen_calls.append((args, kwargs))
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(subprocess, "Popen", fake_popen)
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
bs.main()
|
||||
|
||||
assert popen_calls[0][1]["cwd"] == str(workspace)
|
||||
|
||||
def test_foreground_exports_resolved_env_vars(self, setup, monkeypatch, clean_env):
|
||||
bs, agent_dir = setup
|
||||
monkeypatch.setattr(sys, "argv", [
|
||||
|
||||
@@ -10,6 +10,7 @@ import os
|
||||
import io
|
||||
import sys
|
||||
import tempfile
|
||||
import types
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
@@ -421,6 +422,28 @@ class TestProfileMutationsInIsolatedMode:
|
||||
except (ImportError, ValueError, RuntimeError):
|
||||
pass # expected in test env without hermes_cli
|
||||
|
||||
def test_switch_to_same_default_profile_keeps_pinned_home(self, temp_hermes_home, monkeypatch, tmp_path):
|
||||
"""A same-name switch for isolated profiles/default must keep using the pinned home."""
|
||||
isolated_default = temp_hermes_home / "profiles" / "default"
|
||||
isolated_default.mkdir(parents=True)
|
||||
(isolated_default / "workspace").mkdir()
|
||||
base_workspace = tmp_path / "base-workspace"
|
||||
isolated_workspace = tmp_path / "isolated-workspace"
|
||||
base_workspace.mkdir()
|
||||
isolated_workspace.mkdir()
|
||||
(temp_hermes_home / "config.yaml").write_text(f"workspace: {base_workspace}\n", encoding="utf-8")
|
||||
(isolated_default / "config.yaml").write_text(f"workspace: {isolated_workspace}\n", encoding="utf-8")
|
||||
|
||||
monkeypatch.setenv("HERMES_HOME", str(isolated_default))
|
||||
monkeypatch.setenv("HERMES_BASE_HOME", "")
|
||||
monkeypatch.setattr(_profiles_mod, "_DEFAULT_HERMES_HOME", temp_hermes_home)
|
||||
monkeypatch.setattr(_profiles_mod, "_INITIAL_HERMES_HOME", str(isolated_default))
|
||||
monkeypatch.setattr(_profiles_mod, "list_profiles_api", lambda: [])
|
||||
|
||||
result = switch_profile("default", process_wide=False)
|
||||
|
||||
assert result["default_workspace"] == str(isolated_workspace.resolve())
|
||||
|
||||
def test_scheduled_cron_jobs_stay_pinned_to_isolated_home(self, temp_single_profile, monkeypatch):
|
||||
"""Scheduler jobs must not resolve foreign profile homes in isolated mode."""
|
||||
base_home = temp_single_profile.parent.parent
|
||||
@@ -467,6 +490,46 @@ class TestProfileMutationsInIsolatedMode:
|
||||
403,
|
||||
)
|
||||
|
||||
def test_scheduler_publishes_isolated_profile_after_foreign_job_profile(self, temp_single_profile, monkeypatch):
|
||||
"""Scheduled cron completion must publish the isolated profile identity."""
|
||||
events = []
|
||||
base_home = temp_single_profile.parent.parent
|
||||
|
||||
cron_pkg = types.ModuleType("cron")
|
||||
cron_pkg.__path__ = []
|
||||
cron_scheduler = types.ModuleType("cron.scheduler")
|
||||
cron_scheduler.run_job = lambda job: events.append(("run", job["id"])) or "ok"
|
||||
|
||||
class _Ctx:
|
||||
def __init__(self, home):
|
||||
self.home = str(home)
|
||||
|
||||
def __enter__(self):
|
||||
events.append(("enter", self.home))
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc, tb):
|
||||
events.append(("exit", self.home))
|
||||
return False
|
||||
|
||||
monkeypatch.setitem(sys.modules, "cron", cron_pkg)
|
||||
monkeypatch.setitem(sys.modules, "cron.scheduler", cron_scheduler)
|
||||
monkeypatch.setenv("HERMES_HOME", str(temp_single_profile))
|
||||
monkeypatch.setenv("HERMES_BASE_HOME", "")
|
||||
monkeypatch.setattr(_profiles_mod, "_DEFAULT_HERMES_HOME", base_home)
|
||||
monkeypatch.setattr(_profiles_mod, "_INITIAL_HERMES_HOME", str(temp_single_profile))
|
||||
monkeypatch.setattr(_profiles_mod, "cron_profile_context_for_home", _Ctx)
|
||||
monkeypatch.setattr(
|
||||
_profiles_mod,
|
||||
"publish_session_list_changed",
|
||||
lambda reason, profile=None: events.append(("publish", reason, profile)),
|
||||
)
|
||||
|
||||
_profiles_mod.install_cron_scheduler_profile_isolation()
|
||||
|
||||
assert cron_scheduler.run_job({"id": "job2698", "profile": "other"}) == "ok"
|
||||
assert events[-1] == ("publish", "cron_complete", "user1")
|
||||
|
||||
|
||||
class TestNormalModePreservation:
|
||||
"""Test that normal mode behavior is completely unchanged."""
|
||||
|
||||
Reference in New Issue
Block a user