Compare commits

...

2 Commits

Author SHA1 Message Date
teknium1
28563d6152 fix: Rich markup crash on [/HINT], [/NOTE], etc. in agent responses
Rich Panel() interprets [brackets] as markup tags. When the agent's
response contained text like [/HINT] or [WARNING], Rich threw:
  'closing tag [/HINT] at position N doesn't match any open tag'

Fix: wrap response in Text.from_ansi() before passing to Panel().
This preserves ANSI color codes from the response while treating
all bracket content as literal text.

Fixed in both the main response panel and background task panel.
2026-03-13 02:00:01 -07:00
teknium1
6873c9f7db fix: hermes update restarts gateway via PID file (HERMES_HOME-scoped)
Root cause of duplicate gateways: hermes update only restarted
the systemd service, leaving manually-started gateway processes
alive. The two ran simultaneously on different PIDs.

Fix: cmd_update now uses get_running_pid() from gateway.status
which reads the PID file scoped to HERMES_HOME. This kills only
the gateway for THIS installation — safe with multiple Hermes
installations on the same machine. Then restarts the systemd
service if active.

The PID file approach (vs ps aux pattern matching) ensures we
never accidentally kill a gateway from a different installation.
2026-03-12 20:03:10 -07:00
2 changed files with 54 additions and 20 deletions

6
cli.py
View File

@@ -3016,9 +3016,10 @@ class HermesCLI:
label = "⚕ Hermes" label = "⚕ Hermes"
_resp_color = "#CD7F32" _resp_color = "#CD7F32"
from rich.text import Text as _RichText
_chat_console = ChatConsole() _chat_console = ChatConsole()
_chat_console.print(Panel( _chat_console.print(Panel(
response, _RichText.from_ansi(response),
title=f"[bold]{label} (background #{task_num})[/bold]", title=f"[bold]{label} (background #{task_num})[/bold]",
title_align="left", title_align="left",
border_style=_resp_color, border_style=_resp_color,
@@ -3687,9 +3688,10 @@ class HermesCLI:
label = "⚕ Hermes" label = "⚕ Hermes"
_resp_color = "#CD7F32" _resp_color = "#CD7F32"
from rich.text import Text as _RichText
_chat_console = ChatConsole() _chat_console = ChatConsole()
_chat_console.print(Panel( _chat_console.print(Panel(
response, _RichText.from_ansi(response),
title=f"[bold]{label}[/bold]", title=f"[bold]{label}[/bold]",
title_align="left", title_align="left",
border_style=_resp_color, border_style=_resp_color,

View File

@@ -1881,15 +1881,44 @@ def cmd_update(args):
print() print()
print("✓ Update complete!") print("✓ Update complete!")
# Auto-restart gateway if it's running as a systemd service # Auto-restart gateway if it's running.
# Uses the PID file (scoped to HERMES_HOME) to find this
# installation's gateway — safe with multiple installations.
try:
from gateway.status import get_running_pid, remove_pid_file
import signal as _signal
existing_pid = get_running_pid()
has_systemd_service = False
try: try:
check = subprocess.run( check = subprocess.run(
["systemctl", "--user", "is-active", "hermes-gateway"], ["systemctl", "--user", "is-active", "hermes-gateway"],
capture_output=True, text=True, timeout=5, capture_output=True, text=True, timeout=5,
) )
if check.stdout.strip() == "active": has_systemd_service = check.stdout.strip() == "active"
except (FileNotFoundError, subprocess.TimeoutExpired):
pass
if existing_pid or has_systemd_service:
print() print()
print("→ Gateway service is running — restarting to pick up changes...")
# Kill the PID-file-tracked process (may be manual or systemd)
if existing_pid:
try:
os.kill(existing_pid, _signal.SIGTERM)
print(f"→ Stopped gateway process (PID {existing_pid})")
except ProcessLookupError:
pass # Already gone
except PermissionError:
print(f"⚠ Permission denied killing gateway PID {existing_pid}")
remove_pid_file()
# Restart the systemd service (starts a fresh process)
if has_systemd_service:
import time as _time
_time.sleep(1) # Brief pause for port/socket release
print("→ Restarting gateway service...")
restart = subprocess.run( restart = subprocess.run(
["systemctl", "--user", "restart", "hermes-gateway"], ["systemctl", "--user", "restart", "hermes-gateway"],
capture_output=True, text=True, timeout=15, capture_output=True, text=True, timeout=15,
@@ -1899,8 +1928,11 @@ def cmd_update(args):
else: else:
print(f"⚠ Gateway restart failed: {restart.stderr.strip()}") print(f"⚠ Gateway restart failed: {restart.stderr.strip()}")
print(" Try manually: hermes gateway restart") print(" Try manually: hermes gateway restart")
except (FileNotFoundError, subprocess.TimeoutExpired): elif existing_pid:
pass # No systemd (macOS, WSL1, etc.) — skip silently print(" Gateway was running manually (not as a service).")
print(" Restart it with: hermes gateway run")
except Exception as e:
logger.debug("Gateway restart during update failed: %s", e)
print() print()
print("Tip: You can now select a provider and model:") print("Tip: You can now select a provider and model:")