fix: use root home for gateway health status
This commit is contained in:
parent
c7272dbfc9
commit
4366daba24
2 changed files with 129 additions and 6 deletions
|
|
@ -21,9 +21,14 @@ gateway already writes that file on every tick.
|
|||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
_GATEWAY_PID_FILE = "gateway.pid"
|
||||
_GATEWAY_RUNTIME_STATUS_FILE = "gateway_state.json"
|
||||
|
||||
|
||||
# Two cron ticks (~60s each). Chosen to avoid false negatives during brief
|
||||
# gateway restarts while still surfacing a true outage within a couple of
|
||||
|
|
@ -91,6 +96,69 @@ def _gateway_status_module():
|
|||
return importlib.import_module("gateway.status")
|
||||
|
||||
|
||||
def _gateway_root_pid_path() -> Path | None:
|
||||
"""Return the root Hermes gateway PID path.
|
||||
|
||||
Gateway runtime files are root-level singletons. A profile-scoped WebUI
|
||||
process may have HERMES_HOME=<root>/profiles/<name>, but gateway.pid,
|
||||
gateway.lock, and gateway_state.json still live under <root>.
|
||||
"""
|
||||
try:
|
||||
from hermes_constants import get_default_hermes_root
|
||||
return get_default_hermes_root() / _GATEWAY_PID_FILE
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _read_runtime_status_path(path: Path) -> dict[str, Any] | None:
|
||||
try:
|
||||
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, UnicodeDecodeError, json.JSONDecodeError):
|
||||
return None
|
||||
if isinstance(payload, dict):
|
||||
return payload
|
||||
return None
|
||||
|
||||
|
||||
def _read_gateway_runtime_status(gateway_status: Any, pid_path: Path | None) -> dict[str, Any] | None:
|
||||
read_runtime_status = gateway_status.read_runtime_status
|
||||
if pid_path is not None:
|
||||
try:
|
||||
return read_runtime_status(pid_path=pid_path)
|
||||
except TypeError:
|
||||
try:
|
||||
return read_runtime_status(pid_path)
|
||||
except TypeError:
|
||||
if getattr(gateway_status, "__name__", "") == "gateway.status" or hasattr(
|
||||
gateway_status,
|
||||
"_read_json_file",
|
||||
):
|
||||
runtime_status_file = str(
|
||||
getattr(gateway_status, "_RUNTIME_STATUS_FILE", _GATEWAY_RUNTIME_STATUS_FILE)
|
||||
)
|
||||
runtime_status = _read_runtime_status_path(pid_path.with_name(runtime_status_file))
|
||||
if runtime_status is not None:
|
||||
return runtime_status
|
||||
return read_runtime_status()
|
||||
|
||||
|
||||
def _gateway_running_pid(gateway_status: Any, pid_path: Path | None) -> int | None:
|
||||
get_running_pid = gateway_status.get_running_pid
|
||||
if pid_path is not None:
|
||||
try:
|
||||
return get_running_pid(pid_path=pid_path, cleanup_stale=False)
|
||||
except TypeError:
|
||||
try:
|
||||
return get_running_pid(pid_path, cleanup_stale=False)
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
return get_running_pid(cleanup_stale=False)
|
||||
except TypeError:
|
||||
# Older agent versions may not expose cleanup_stale. Keep compatibility.
|
||||
return get_running_pid()
|
||||
|
||||
|
||||
def _runtime_detail_subset(runtime_status: dict[str, Any] | None) -> dict[str, Any]:
|
||||
"""Return only non-sensitive runtime fields for the browser.
|
||||
|
||||
|
|
@ -154,17 +222,16 @@ def build_agent_health_payload() -> dict[str, Any]:
|
|||
},
|
||||
}
|
||||
|
||||
gateway_pid_path = _gateway_root_pid_path()
|
||||
|
||||
runtime_status = None
|
||||
try:
|
||||
runtime_status = gateway_status.read_runtime_status()
|
||||
runtime_status = _read_gateway_runtime_status(gateway_status, gateway_pid_path)
|
||||
except Exception:
|
||||
runtime_status = None
|
||||
|
||||
try:
|
||||
running_pid = gateway_status.get_running_pid(cleanup_stale=False)
|
||||
except TypeError:
|
||||
# Older agent versions may not expose cleanup_stale. Keep compatibility.
|
||||
running_pid = gateway_status.get_running_pid()
|
||||
running_pid = _gateway_running_pid(gateway_status, gateway_pid_path)
|
||||
except Exception:
|
||||
running_pid = None
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import pathlib
|
||||
|
||||
import sys
|
||||
import types
|
||||
|
||||
REPO_ROOT = pathlib.Path(__file__).parent.parent
|
||||
|
||||
UI_JS = (REPO_ROOT / "static" / "ui.js").read_text(encoding="utf-8")
|
||||
INDEX_HTML = (REPO_ROOT / "static" / "index.html").read_text(encoding="utf-8")
|
||||
STYLE_CSS = (REPO_ROOT / "static" / "style.css").read_text(encoding="utf-8")
|
||||
|
|
@ -25,6 +28,33 @@ class _FakeGatewayStatus:
|
|||
return self._running_pid
|
||||
|
||||
|
||||
class _PathSensitiveGatewayStatus:
|
||||
_RUNTIME_STATUS_FILE = "gateway_state.json"
|
||||
|
||||
def __init__(self, root_home: pathlib.Path):
|
||||
self.root_home = root_home
|
||||
self.runtime_pid_path = None
|
||||
self.running_pid_path = None
|
||||
|
||||
def read_runtime_status(self, pid_path=None):
|
||||
self.runtime_pid_path = pathlib.Path(pid_path) if pid_path is not None else None
|
||||
if self.runtime_pid_path:
|
||||
base = self.runtime_pid_path.parent
|
||||
else:
|
||||
base = self.root_home / "profiles" / "troubleshooting"
|
||||
path = base / self._RUNTIME_STATUS_FILE
|
||||
if not path.exists():
|
||||
return None
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
def get_running_pid(self, pid_path=None, cleanup_stale=False):
|
||||
assert cleanup_stale is False
|
||||
self.running_pid_path = pathlib.Path(pid_path) if pid_path is not None else None
|
||||
if self.running_pid_path == self.root_home / "gateway.pid":
|
||||
return 98765
|
||||
return None
|
||||
|
||||
|
||||
def _runtime_status(**overrides):
|
||||
payload = {
|
||||
"gateway_state": "running",
|
||||
|
|
@ -45,6 +75,32 @@ def _runtime_status(**overrides):
|
|||
return payload
|
||||
|
||||
|
||||
def test_agent_health_uses_root_gateway_state_when_hermes_home_is_profile(monkeypatch, tmp_path):
|
||||
from api import agent_health
|
||||
|
||||
root_home = tmp_path / "root-home"
|
||||
profile_home = root_home / "profiles" / "troubleshooting"
|
||||
profile_home.mkdir(parents=True)
|
||||
(root_home / "gateway.pid").write_text(json.dumps({"pid": 98765}), encoding="utf-8")
|
||||
(root_home / "gateway_state.json").write_text(json.dumps(_runtime_status()), encoding="utf-8")
|
||||
fake_gateway_status = _PathSensitiveGatewayStatus(root_home)
|
||||
|
||||
monkeypatch.setenv("HERMES_HOME", str(profile_home))
|
||||
monkeypatch.setitem(
|
||||
sys.modules,
|
||||
"hermes_constants",
|
||||
types.SimpleNamespace(get_default_hermes_root=lambda: root_home),
|
||||
)
|
||||
monkeypatch.setattr(agent_health, "_gateway_status_module", lambda: fake_gateway_status)
|
||||
|
||||
payload = agent_health.build_agent_health_payload()
|
||||
|
||||
assert payload["alive"] is True
|
||||
assert payload["details"]["state"] == "alive"
|
||||
assert fake_gateway_status.runtime_pid_path == root_home / "gateway.pid"
|
||||
assert fake_gateway_status.running_pid_path == root_home / "gateway.pid"
|
||||
|
||||
|
||||
def test_agent_health_payload_alive_uses_safe_runtime_details(monkeypatch):
|
||||
from api import agent_health
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue