fix: stop stale agent runtime after source updates

This commit is contained in:
pxxD1998 committed 2026-07-16 22:28:35 +08:00
1 parent 6ed474ca54
commit 5df6b1fe8a
8 files changed
+232 -23

No files matched your search

+79
View File
@@ -0,0 +1,79 @@
"""Fail-closed guard for in-process Hermes Agent source revisions.
Hermes WebUI currently imports ``run_agent.AIAgent`` into its long-lived server
process. If the Agent checkout changes while that process is alive, Python may
combine already-cached modules with newly-read source. Refuse to reuse that
mixed runtime and require a clean WebUI restart instead.
"""
from __future__ import annotations
from pathlib import Path
import subprocess
import threading
from api.config import _AGENT_DIR
_RESTART_MESSAGE = (
"Hermes Agent was updated while Hermes WebUI was running. "
"Restart Hermes WebUI before retrying this action."
)
def _read_agent_revision(agent_dir: Path) -> str | None:
"""Return the checkout HEAD, or ``None`` for a non-Git/unavailable source."""
try:
result = subprocess.run(
["git", "-C", str(agent_dir), "rev-parse", "--verify", "HEAD"],
check=False,
capture_output=True,
text=True,
timeout=2,
)
except (OSError, subprocess.TimeoutExpired):
return None
revision = result.stdout.strip()
return revision if result.returncode == 0 and revision else None
_AGENT_REVISION = _read_agent_revision(_AGENT_DIR)
_AIAgent = None
_RUNTIME_LOCK = threading.Lock()
class AgentRuntimeChangedError(RuntimeError):
"""Raised when the loaded Agent runtime no longer matches its source tree."""
def ensure_agent_runtime_current() -> None:
"""Reject a known Git checkout change instead of mixing Python modules."""
if _AGENT_REVISION is None:
return
if _read_agent_revision(_AGENT_DIR) != _AGENT_REVISION:
raise AgentRuntimeChangedError(_RESTART_MESSAGE)
def require_ai_agent_class():
"""Import ``AIAgent`` after proving the loaded source revision is current."""
ensure_agent_runtime_current()
from run_agent import AIAgent # noqa: PLC0415
return AIAgent
def get_ai_agent_class():
"""Return ``AIAgent`` while preserving the existing lazy-import retry."""
global _AIAgent, _AGENT_REVISION
with _RUNTIME_LOCK:
ensure_agent_runtime_current()
if _AIAgent is None:
try:
agent_class = require_ai_agent_class()
except ImportError:
return None
_AIAgent = agent_class
if _AGENT_REVISION is None:
_AGENT_REVISION = _read_agent_revision(_AGENT_DIR)
return _AIAgent