From 52b82a0b676936a3f4eee0497c8ff21a79b27552 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Wed, 2 Sep 2026 21:50:40 -0700 Subject: [PATCH 1/2] refactor(hermes_cli): gateway.py extract _s6_gateway_snapshot; table-driven install-scope prompt; compact reaper exclusions --- hermes_cli/gateway.py | 154 +++++++++++++++++------------------------- 1 file changed, 61 insertions(+), 93 deletions(-) diff --git a/hermes_cli/gateway.py b/hermes_cli/gateway.py index d4ddf38435..591d464a1c 100644 --- a/hermes_cli/gateway.py +++ b/hermes_cli/gateway.py @@ -1469,6 +1469,35 @@ def _probe_launchd_service_running() -> bool: return get_launchd_plist_path().exists() and _launchctl_label_supervising_process(get_launchd_label()) +def _s6_gateway_snapshot(gateway_pids: tuple[int, ...]) -> GatewayRuntimeSnapshot | None: + """Snapshot for an s6-supervised container gateway, or None when s6 isn't the service manager.""" + from hermes_cli.service_manager import detect_service_manager, get_service_manager + + if detect_service_manager() != "s6": + return None + service_name = f"gateway-{_profile_suffix() or 'default'}" + mgr = get_service_manager() + service_installed = service_running = False + try: + service_dir = getattr(mgr, "scandir", None) + if service_dir is not None: + service_installed = (service_dir / service_name).is_dir() + except Exception: + service_installed = False + if service_installed: + try: + service_running = bool(mgr.is_running(service_name)) + except Exception: + service_running = False + return GatewayRuntimeSnapshot( + manager="s6 (container supervisor)", + service_installed=service_installed, + service_running=service_running, + gateway_pids=gateway_pids, + service_scope="s6", + ) + + def get_gateway_runtime_snapshot(system: bool = False) -> GatewayRuntimeSnapshot: """Return a unified view of gateway liveness for the current profile.""" gateway_pids = tuple(find_gateway_pids()) @@ -1480,29 +1509,9 @@ def get_gateway_runtime_snapshot(system: bool = False) -> GatewayRuntimeSnapshot if is_linux() and is_container(): # Report s6 supervision under our /init; other container runtimes keep "docker (foreground)". try: - from hermes_cli.service_manager import detect_service_manager, get_service_manager - if detect_service_manager() == "s6": - service_name = f"gateway-{_profile_suffix() or 'default'}" - mgr = get_service_manager() - service_installed = service_running = False - try: - service_dir = getattr(mgr, "scandir", None) - if service_dir is not None: - service_installed = (service_dir / service_name).is_dir() - except Exception: - service_installed = False - if service_installed: - try: - service_running = bool(mgr.is_running(service_name)) - except Exception: - service_running = False - return GatewayRuntimeSnapshot( - manager="s6 (container supervisor)", - service_installed=service_installed, - service_running=service_running, - gateway_pids=gateway_pids, - service_scope="s6", - ) + snapshot = _s6_gateway_snapshot(gateway_pids) + if snapshot is not None: + return snapshot except Exception: pass # Fall through to the legacy label on any detection error. return GatewayRuntimeSnapshot(manager="docker (foreground)", gateway_pids=gateway_pids) @@ -1747,9 +1756,7 @@ def _reap_unsupervised_gateway_orphans(extra_exclude: set | None = None) -> bool def _reaper_exclusion_pids(extra_exclude: set | None) -> set[int]: """PIDs the orphan reaper must never kill: self, caller extras, service-managed, recorded.""" - own = {os.getpid()} - if extra_exclude: - own |= extra_exclude + own = {os.getpid()} | (extra_exclude or set()) # Service-managed gateways are never orphans (on macOS supports_systemd_services() is False, so # a launchd gateway would otherwise be SIGTERM'd). all_profiles=True: the scan sees every # profile's gateway and a sibling's launchd gateway must not be reaped. @@ -1761,22 +1768,13 @@ def _reaper_exclusion_pids(extra_exclude: set | None) -> set[int]: # standalone gateway would be hard-killed. For a KILL exclusion list a stale PID at worst spares # one process; a false-negative kills a live gateway. try: - from gateway.status import ( - _pid_from_record, - _read_gateway_lock_record, - _read_pid_record, - get_running_pid, - ) + from gateway.status import _pid_from_record, _read_gateway_lock_record, _read_pid_record, get_running_pid - recorded_pids = set() - for _record in (_read_pid_record(), _read_gateway_lock_record()): - _raw_pid = _pid_from_record(_record) - if _raw_pid and _raw_pid > 0: - recorded_pids.add(_raw_pid) - _probed = get_running_pid(cleanup_stale=False) - if _probed and _probed > 0: - recorded_pids.add(_probed) + recorded_pids = {_pid_from_record(rec) for rec in (_read_pid_record(), _read_gateway_lock_record())} + recorded_pids.add(get_running_pid(cleanup_stale=False)) for recorded in recorded_pids: + if not recorded or recorded <= 0: + continue own.add(recorded) try: import psutil # type: ignore @@ -1866,7 +1864,6 @@ def stop_profile_gateway() -> bool: return _reap_unsupervised_gateway_orphans() _mark_planned_stop(pid) - try: os.kill(pid, signal.SIGTERM) except ProcessLookupError: @@ -1891,7 +1888,6 @@ def stop_profile_gateway() -> bool: _reap_unsupervised_gateway_orphans(extra_exclude={pid} if pid else None) except Exception as exc: logger.debug("orphan reap after stop_profile_gateway failed: %s", exc) - return True @@ -2149,14 +2145,11 @@ def _ensure_user_systemd_env() -> None: """ uid = os.getuid() # windows-footgun: ok — POSIX systemd helper, never invoked on Windows xdg = os.environ.get("XDG_RUNTIME_DIR") - if not xdg or not _runtime_dir_is_ours(xdg): - runtime_dir = f"/run/user/{uid}" - if _runtime_dir_is_ours(runtime_dir): - os.environ["XDG_RUNTIME_DIR"] = runtime_dir + if (not xdg or not _runtime_dir_is_ours(xdg)) and _runtime_dir_is_ours(f"/run/user/{uid}"): + os.environ["XDG_RUNTIME_DIR"] = f"/run/user/{uid}" if "DBUS_SESSION_BUS_ADDRESS" not in os.environ: - xdg_runtime = os.environ.get("XDG_RUNTIME_DIR", f"/run/user/{uid}") - bus_path = Path(xdg_runtime) / "bus" + bus_path = Path(os.environ.get("XDG_RUNTIME_DIR", f"/run/user/{uid}")) / "bus" if _path_exists_safe(bus_path): os.environ["DBUS_SESSION_BUS_ADDRESS"] = f"unix:path={bus_path}" @@ -2282,7 +2275,7 @@ def _service_scope_label(system: bool = False) -> str: def get_installed_systemd_scopes() -> list[str]: - scopes = [] + scopes: list[str] = [] seen_paths: set[Path] = set() for system, label in ((False, "user"), (True, "system")): unit_path = get_systemd_unit_path(system=system) @@ -2489,30 +2482,18 @@ def _default_system_service_user() -> str | None: def prompt_linux_gateway_install_scope() -> str | None: # Only root can create a boot-time system service, so that scope is offered only to root # sessions — a non-root user is never handed a "re-run under sudo" recipe. - user_option = "User service (no sudo; best for laptops/dev boxes; may need linger after logout)" - if os.geteuid() != 0: # windows-footgun: ok — Linux systemd install wizard, never invoked on Windows - choice = prompt_choice( - " Choose how the gateway should run in the background:", - [user_option, "Skip service install for now"], - default=0, - ) - if choice == 0: - print_info( - " Tip: for a boot-time system service, re-run setup as root " - "(e.g. from a root shell or `sudo -i`)." - ) - return {0: "user", 1: None}[choice] - - choice = prompt_choice( - " Choose how the gateway should run in the background:", - [ - user_option, - "System service (starts on boot; runs as your chosen user)", - "Skip service install for now", - ], - default=0, - ) - return {0: "user", 1: "system", 2: None}[choice] + is_root = os.geteuid() == 0 # windows-footgun: ok — Linux systemd install wizard, never invoked on Windows + options = ["User service (no sudo; best for laptops/dev boxes; may need linger after logout)"] + values: list[str | None] = ["user"] + if is_root: + options.append("System service (starts on boot; runs as your chosen user)") + values.append("system") + options.append("Skip service install for now") + values.append(None) + choice = prompt_choice(" Choose how the gateway should run in the background:", options, default=0) + if not is_root and choice == 0: + print_info(" Tip: for a boot-time system service, re-run setup as root (e.g. from a root shell or `sudo -i`).") + return values[choice] def install_linux_gateway_from_setup(force: bool = False, enable_on_startup: bool = True) -> tuple[str | None, bool]: @@ -2569,11 +2550,9 @@ def ensure_gateway_service(context: str = "setup") -> bool: try: if _is_service_running(): return True - if not _is_service_installed(): if supports_systemd and has_conflicting_systemd_units(): - # Both user and system units would fight over bot tokens. - # Don't pile a fresh install onto a conflicted state. + # Both units would fight over bot tokens; don't pile a fresh install onto a conflicted state. print_systemd_scope_conflict_warning() return False print_info(" Installing the gateway background service ...") @@ -2582,11 +2561,9 @@ def ensure_gateway_service(context: str = "setup") -> bool: elif is_macos(): launchd_install(force=False) else: - # Registers the Scheduled Task AND starts it. - _gw_windows().install(force=False) + _gw_windows().install(force=False) # Registers the Scheduled Task AND starts it. print_success(" Gateway service installed and started.") return True - if supports_systemd: systemd_start() elif is_macos(): @@ -2602,8 +2579,7 @@ def ensure_gateway_service(context: str = "setup") -> bool: print_warning(f" Gateway service needs root for this scope: {e}") _print_system_scope_remediation("start") except SystemExit: - # Some install/start paths sys.exit() on hard failures (e.g. temp-HOME - # guard). A background-service failure must never abort setup/import. + # Some install/start paths sys.exit() on hard failures (temp-HOME guard); never abort setup/import. print_warning(" Gateway service install did not complete.") print_info(" You can retry manually: hermes gateway install") except Exception as e: @@ -3886,23 +3862,15 @@ def _spawn_deferred_launchd_reload( f"launchctl bootout {q_target} 2>/dev/null; " # Wait for the OLD gateway to exit: bootout only SIGTERMs and every bootstrap during the drain fails EIO. f"_wait_deadline=$(($(date +%s) + {_reload_budget})); " - f"while kill -0 {gateway_pid} 2>/dev/null; do " - f" if [ $(date +%s) -ge $_wait_deadline ]; then " + f"while kill -0 {gateway_pid} 2>/dev/null; do if [ $(date +%s) -ge $_wait_deadline ]; then " f" echo \"[{stamp}] old gateway pid {gateway_pid} still alive after {_reload_budget}s drain wait — bootstrapping anyway\" >> {q_log}; " - f" break; " - f" fi; " - f" sleep 1; " - f"done; " + f" break; fi; sleep 1; done; " # Let launchd finish unregistering the label after the process exits. - f"sleep 1; " - f"_deadline=$(($(date +%s) + {_reload_budget})); " - f"while :; do " + f"sleep 1; _deadline=$(($(date +%s) + {_reload_budget})); while :; do " f" launchctl bootstrap {shlex.quote(domain)} {shlex.quote(str(plist_path))} 2>/dev/null; " f" if {listed}; then break; fi; " f" echo \"[{stamp}] bootstrap not yet registered for {q_target} — retrying\" >> {q_log}; " - f" if [ $(date +%s) -ge $_deadline ]; then break; fi; " - f" sleep 2; " - f"done; " + f" if [ $(date +%s) -ge $_deadline ]; then break; fi; sleep 2; done; " f"if ! {listed}; then " f" echo \"[{stamp}] FAILED launchd reload for {q_target} — service NOT registered after {_reload_budget}s of retries\" >> {q_log}; " f"fi; " From 8a2896c0a5378b092ff7e7741019e21208f8cbea Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Wed, 2 Sep 2026 21:57:13 -0700 Subject: [PATCH 2/2] refactor(hermes_cli): gateway.py drop blank lines after local imports in service region --- hermes_cli/gateway.py | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/hermes_cli/gateway.py b/hermes_cli/gateway.py index 591d464a1c..60424ad017 100644 --- a/hermes_cli/gateway.py +++ b/hermes_cli/gateway.py @@ -1230,7 +1230,6 @@ def _systemd_main_pid(system: bool = False) -> int | None: def _read_gateway_runtime_status() -> dict | None: try: from gateway.status import read_runtime_status - state = read_runtime_status() except Exception: return None @@ -1264,7 +1263,6 @@ def _wait_for_systemd_service_restart( sub_state = props.get("SubState", "") try: from gateway.status import get_running_pid - new_pid = get_running_pid() except Exception: new_pid = None @@ -1323,7 +1321,6 @@ def _wait_for_systemd_service_restart( def _systemd_restart_wait_timeout(system: bool = False) -> float: """Cover systemd's relaunch delays before applying the runtime wait floor.""" from gateway.shutdown_forensics import parse_systemd_duration_to_us - props = _read_systemd_unit_properties(system=system, properties=("RestartUSec", "TimeoutStartUSec")) supervisor_budget = 0.0 for name in ("RestartUSec", "TimeoutStartUSec"): @@ -1472,7 +1469,6 @@ def _probe_launchd_service_running() -> bool: def _s6_gateway_snapshot(gateway_pids: tuple[int, ...]) -> GatewayRuntimeSnapshot | None: """Snapshot for an s6-supervised container gateway, or None when s6 isn't the service manager.""" from hermes_cli.service_manager import detect_service_manager, get_service_manager - if detect_service_manager() != "s6": return None service_name = f"gateway-{_profile_suffix() or 'default'}" @@ -1505,7 +1501,6 @@ def get_gateway_runtime_snapshot(system: bool = False) -> GatewayRuntimeSnapshot return GatewayRuntimeSnapshot(manager="Termux / manual process", gateway_pids=gateway_pids) from hermes_constants import is_container - if is_linux() and is_container(): # Report s6 supervision under our /init; other container runtimes keep "docker (foreground)". try: @@ -1573,7 +1568,6 @@ def _print_other_profiles_gateway_status() -> None: """Print other profiles' running gateways at the bottom of ``hermes gateway status``.""" try: from hermes_cli.profiles import get_active_profile_name - current = get_active_profile_name() other_processes = [p for p in find_profile_gateway_processes() if p.profile != current] if not other_processes: @@ -1610,7 +1604,6 @@ def _gateway_list() -> None: pid = None try: from gateway.status import get_running_pid - pid = get_running_pid(prof.path / "gateway.pid", cleanup_stale=False) except Exception: pass @@ -1637,7 +1630,6 @@ def kill_gateway_processes( if _capture_gateway_argv(pid) is None: continue from gateway.status import get_process_start_time - expected_start_time = get_process_start_time(pid) terminate_pid(pid, force=force, expected_start_time=expected_start_time) killed += 1 @@ -1665,7 +1657,6 @@ def _reaper_candidate_is_supervisor_owned(pid: int) -> bool: return False try: import psutil # type: ignore - parent = psutil.Process(pid).parent() for _ in range(_REAPER_SUPERVISOR_WALK_LIMIT): if parent is None: @@ -1702,7 +1693,6 @@ def _reap_unsupervised_gateway_orphans(extra_exclude: set | None = None) -> bool if is_windows(): try: from hermes_cli.gateway_windows import get_task_name # profile-aware task name - _task_name = get_task_name() except Exception: _task_name = "Hermes_Gateway" @@ -1710,7 +1700,6 @@ def _reap_unsupervised_gateway_orphans(extra_exclude: set | None = None) -> bool return False from gateway.status import _pid_exists, get_process_start_time, write_planned_stop_marker - own = _reaper_exclusion_pids(extra_exclude) try: # On Windows also drop Task Scheduler-owned candidates (the pidfile-less gap). @@ -1769,7 +1758,6 @@ def _reaper_exclusion_pids(extra_exclude: set | None) -> set[int]: # one process; a false-negative kills a live gateway. try: from gateway.status import _pid_from_record, _read_gateway_lock_record, _read_pid_record, get_running_pid - recorded_pids = {_pid_from_record(rec) for rec in (_read_pid_record(), _read_gateway_lock_record())} recorded_pids.add(get_running_pid(cleanup_stale=False)) for recorded in recorded_pids: @@ -1778,7 +1766,6 @@ def _reaper_exclusion_pids(extra_exclude: set | None) -> set[int]: own.add(recorded) try: import psutil # type: ignore - parent = psutil.Process(recorded).parent() while parent is not None: own.add(parent.pid) @@ -1839,7 +1826,6 @@ def _mark_planned_stop(pid: int | None = None) -> None: """Best-effort planned-stop marker for ``pid`` (default: the recorded gateway PID).""" try: from gateway.status import get_running_pid, write_planned_stop_marker - if pid is None: pid = get_running_pid(cleanup_stale=False) if pid is not None: @@ -1874,7 +1860,6 @@ def stop_profile_gateway() -> bool: # ``_pid_exists``, NOT ``os.kill(pid, 0)`` (TerminateProcess on Windows). from gateway.status import _pid_exists - for _ in range(20): if not _pid_exists(pid): break @@ -1935,7 +1920,6 @@ def is_windows() -> bool: def _gw_windows(): """Lazily import :mod:`hermes_cli.gateway_windows` (Windows-only service backend).""" from hermes_cli import gateway_windows - return gateway_windows @@ -1996,7 +1980,6 @@ def _windows_console_window_attached() -> bool | None: return None try: import ctypes - return bool(ctypes.windll.kernel32.GetConsoleWindow()) # type: ignore[attr-defined] except (OSError, AttributeError): return None @@ -2007,7 +1990,6 @@ def _windows_gateway_breakaway_state() -> bool | None: if not is_windows(): return None from hermes_cli._subprocess_compat import _WINDOWS_GATEWAY_BREAKAWAY_ENV - return {"1": True, "0": False}.get(os.environ.pop(_WINDOWS_GATEWAY_BREAKAWAY_ENV, None)) @@ -2022,7 +2004,6 @@ SERVICE_DESCRIPTION = "Hermes Agent Gateway - Messaging Platform Integration" def _profile_name_from_home(home: Path, default: Path) -> str | None: """Profile name when ``home`` is ``/profiles/`` with a service-safe name, else None.""" import re - try: parts = home.relative_to((default / "profiles").resolve()).parts except ValueError: @@ -2037,7 +2018,6 @@ def _profile_suffix() -> str: ``/profiles/``, else a short hash of the path.""" import hashlib from hermes_constants import get_default_hermes_root - home = get_hermes_home().resolve() default = get_default_hermes_root().resolve() if home == default: @@ -2053,7 +2033,6 @@ def _profile_arg(hermes_home: str | None = None, default_root: str | Path | None ``get_hermes_home()``/``get_default_hermes_root()`` would otherwise refer to root. """ from hermes_constants import get_default_hermes_root - home = Path(hermes_home or str(get_hermes_home())).resolve() default = Path(default_root).resolve() if default_root else get_default_hermes_root().resolve() if home == default: @@ -2187,7 +2166,6 @@ def _preflight_user_systemd(*, auto_enable_linger: bool = True) -> None: return import getpass - username = getpass.getuser() linger_enabled, linger_detail = get_systemd_linger_status() sudo_hint = f" sudo loginctl enable-linger {username}" @@ -2441,7 +2419,6 @@ def _system_service_identity(run_as_user: str | None = None) -> tuple[str, str, import getpass import grp import pwd - username = ( run_as_user or os.getenv("SUDO_USER") or os.getenv("USER") or os.getenv("LOGNAME") or getpass.getuser() ).strip() @@ -2531,7 +2508,6 @@ def ensure_gateway_service(context: str = "setup") -> bool: True when a service is installed and running. """ from hermes_constants import is_container - if is_container(): # Containers use restart policies, not service managers. print_info("Start the gateway to bring your bots online:") @@ -2601,7 +2577,6 @@ def get_systemd_linger_status() -> tuple[bool | None, str]: if not username: try: import pwd - username = pwd.getpwuid(os.getuid()).pw_name # windows-footgun: ok — POSIX loginctl helper, never invoked on Windows except Exception: return None, "could not determine current user" @@ -2628,7 +2603,6 @@ def get_systemd_linger_status() -> tuple[bool | None, str]: def get_launchd_plist_path() -> Path: """``~/Library/LaunchAgents/ai.hermes.gateway[-].plist`` under the real account home.""" import pwd - suffix = _profile_suffix() name = f"ai.hermes.gateway-{suffix}" if suffix else "ai.hermes.gateway" # Real account home: profile mode may point HOME at a profile dir. @@ -2646,7 +2620,6 @@ def launchd_gateway_labels_for_install() -> list[str]: import re as _re from hermes_cli.profiles import list_profiles - root_label: list[str] = [] profile_labels: list[str] = [] for profile in list_profiles(): @@ -2678,7 +2651,6 @@ def get_python_path() -> str: # Update-boundary: a gateway restarted mid-update can hold a stale hermes_constants # without this symbol; see _reload_hermes_constants() in hermes_cli/managed_uv.py. from hermes_cli.managed_uv import _reload_hermes_constants - venv_python_path = _reload_hermes_constants().venv_python_path venv_python = venv_python_path(venv, windows=is_windows()) @@ -2810,7 +2782,6 @@ def _systemd_watchdog_seconds(hermes_home: str | Path | None = None) -> int: override_token = reset_home_override = None if hermes_home is not None: from hermes_constants import (reset_hermes_home_override, set_hermes_home_override) - override_token = set_hermes_home_override(hermes_home) reset_home_override = reset_hermes_home_override try: @@ -2832,7 +2803,6 @@ def _append_node_dir_for_service(path_entries: list[str], hermes_root: Path | No Managed dirs are profile-scoped. PATH lookup is only the fallback when no managed Node exists. """ from hermes_constants import (hermes_managed_node_tree_present, iter_hermes_node_dirs) - managed_node_present = hermes_managed_node_tree_present(hermes_root) for directory in iter_hermes_node_dirs(hermes_root) if managed_node_present else (): entry = str(directory) @@ -2979,7 +2949,6 @@ def _normalize_launchd_plist_for_comparison(text: str) -> str: """Normalize plist text for staleness checks, ignoring the PATH payload: the generated PATH is captured from the invoking shell and varies across shells.""" import re - return re.sub( r"(PATH\s*)(.*?)()", r"\1__HERMES_PATH__\3", _normalize_service_definition(text), flags=re.S, @@ -3013,7 +2982,6 @@ def _temp_home_in_service_definition(definition: str) -> str | None: """ import re import tempfile - candidates = re.findall(r'HERMES_HOME=([^"\n]+)', definition) candidates += re.findall(r"HERMES_HOME\s*(.*?)", definition, flags=re.S) temp_roots = { @@ -3089,7 +3057,6 @@ def _ensure_linger_enabled() -> None: return import getpass - username = getpass.getuser() if Path(f"/var/lib/systemd/linger/{username}").exists(): print("✓ Systemd linger is enabled (service survives logout)") @@ -3314,7 +3281,6 @@ def systemd_restart(system: bool = False): # persists for the get_running_pid / drain-timeout reads below. refresh_systemd_unit_if_needed(system=system) from gateway.status import get_running_pid - pid = get_running_pid() or _systemd_main_pid(system=system) if pid is not None and probe_gateway_loop_liveness(pid) == GATEWAY_LOOP_WEDGED: # Event loop provably dead: SIGUSR1 can never drain it, so escalate (SIGTERM grace → @@ -3582,7 +3548,6 @@ def _append_launchd_reload_log(message: str) -> None: try: path.parent.mkdir(parents=True, exist_ok=True) from datetime import datetime as _dt - stamp = _dt.now().astimezone().strftime("%Y-%m-%d %H:%M:%S %z") with path.open("a", encoding="utf-8") as fh: fh.write(f"[{stamp}] {message}\n") @@ -3641,7 +3606,6 @@ def _launchd_unsupported_marker_path() -> Path: def _write_launchd_unsupported_marker() -> None: """Persist that launchd cannot supervise the gateway on this host.""" from datetime import datetime, timezone - payload = { "written_at": datetime.now(timezone.utc).isoformat(), "reason": "launchd domain unsupported (exit 5/125)", @@ -3685,7 +3649,6 @@ def _spawn_detached_gateway() -> bool: """Launch the gateway detached (launchd fallback for macOS 26+). CLI-managed nohup equivalent: stdout → gateway.log, timestamped stderr → gateway.error.log, PID via gateway.pid so stop/status work.""" from hermes_cli._subprocess_compat import windows_detach_popen_kwargs - log_dir = get_hermes_home() / "logs" log_dir.mkdir(parents=True, exist_ok=True) try: @@ -3704,7 +3667,6 @@ def _launchd_fallback_to_detached(reason: str, *, exit_on_failure: bool = True) """Start the gateway detached when launchd can't manage it; on failure print the manual workaround and (by default) exit 1.""" from hermes_constants import display_hermes_home as _dhh - _write_launchd_unsupported_marker() print(f"⚠ launchd cannot manage the gateway on this macOS version ({reason}).") if _spawn_detached_gateway(): @@ -3752,7 +3714,6 @@ def generate_launchd_plist() -> str: nofile_block = "" try: from hermes_cli.resource_limits import configured_nofile_soft_limit - nofile_target = configured_nofile_soft_limit() except Exception: nofile_target = None @@ -3994,7 +3955,6 @@ def launchd_install(force: bool = False): print("Next steps:") print(" hermes gateway status # Check status") from hermes_constants import display_hermes_home as _dhh - print(f" tail -f {_dhh()}/logs/gateway.log # View logs") @@ -4074,7 +4034,6 @@ def _wait_for_gateway_exit(timeout: float = 10.0, force_after: float | None = 5. """Wait up to ``timeout`` s for the gateway (by gateway.pid, not launchd labels, so multiple HERMES_HOMEs work) to exit; SIGKILL it after ``force_after`` s of graceful waiting.""" from gateway.status import get_process_start_time, get_running_pid - deadline = time.monotonic() + timeout force_deadline = (time.monotonic() + force_after) if force_after is not None else None force_sent = False @@ -4131,7 +4090,6 @@ def launchd_restart(): domain = _launchd_domain() target = f"{domain}/{label}" from gateway.status import get_running_pid - try: pid = get_running_pid() if pid is not None and _request_gateway_self_restart(pid):