From 085e500908fd87c255750759a140ac69a24cf52e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 14:39:00 +0000 Subject: [PATCH] Bridge Hermes invocation through WSL when only installed there The dashboard runs as a native Windows process, but Hermes' official installer is Bash-only and typically only gets set up inside WSL - a plain PATH lookup for 'hermes' on Windows will never find it there. Add hermes_cli_args(), which checks the native PATH first (so Mac/ Linux/WSL-native setups are unaffected) and falls back to routing through 'wsl -e bash -lc' (a login shell, so PATH additions like uv's ~/.local/bin are sourced) only when hermes isn't found natively but wsl.exe is available. Wire both the chat/dispatch invocation and the agent-health check through it, replacing the plain shutil.which check that always reported Hermes offline in this setup. --- server.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/server.py b/server.py index 1539908..b68d2a3 100644 --- a/server.py +++ b/server.py @@ -8,6 +8,7 @@ import asyncio import json import os import re +import shlex import shutil import signal import subprocess @@ -131,14 +132,32 @@ def append_audit(entry: dict): # ─── Agent Discovery (instant filesystem checks) ──────────────────── +def hermes_cli_args(*args: str) -> list: + """Build the command to invoke Hermes, bridging through WSL if it's only installed there. + + The dashboard commonly runs as a native Windows process while Hermes (whose official + installer is Bash-only) lives inside WSL - a plain PATH lookup on Windows will never find it. + """ + if shutil.which("hermes") is not None or shutil.which("wsl") is None: + return ["hermes", *args] + quoted = " ".join(shlex.quote(a) for a in args) + return ["wsl", "-e", "bash", "-lc", f"hermes {quoted}"] + +def hermes_available() -> bool: + try: + r = subprocess.run(hermes_cli_args("--version"), capture_output=True, text=True, timeout=10) + return r.returncode == 0 + except Exception: + return False + def check_agent(name: str) -> dict: - """Instant filesystem-based check. No subprocess needed.""" + """Filesystem-based check for opencode/gemini; hermes needs a real subprocess since it may live inside WSL.""" try: if name == "opencode": exists = shutil.which("opencode") is not None status = "online" if exists else "offline" elif name == "hermes": - exists = shutil.which("hermes") is not None + exists = hermes_available() status = "online" if exists else "offline" elif name == "gemini": # Gemini has valid OAuth tokens logged in @@ -588,7 +607,7 @@ def execute_agent(agent: str, message: str) -> str: elif agent == "hermes": try: - code, out, err = run_cli(["hermes", "chat", "-q", message], timeout=180) + code, out, err = run_cli(hermes_cli_args("chat", "-q", message), timeout=180) except subprocess.TimeoutExpired: return f"⏱ Hermes timed out.\n\nThe model took too long to respond. Try a shorter query or check your OpenRouter rate limits.\n\n**Message:** {message[:100]}" if code == 0: