opentui(phase3): launcher integration — HERMES_TUI_ENGINE dual-engine

hermes --tui launches the native OpenTUI engine (Bun) when
HERMES_TUI_ENGINE=opentui (env) or display.tui_engine=opentui (config);
Ink stays the default and the shipping path is untouched.

- _resolve_tui_engine() (env > config > ink); refuses opentui on
  Windows/Termux (no Bun) -> falls back to ink with a notice.
- _make_opentui_argv() -> [bun, src/entry.real.tsx] (no build step).
- _bun_bin() with HERMES_BUN override.
- Branch at top of _make_tui_argv BEFORE _ensure_tui_node (Bun-only host
  must not bootstrap Node).
- Gate _launch_tui NODE_OPTIONS/--max-old-space-size on engine==ink (Bun
  is JSC; the V8 flag errors/ignores).

Verified end-to-end via tmux: real hermes --tui -> Bun -> OpenTUI ->
real Python gateway streamed a real reply. No-flag default still ink.
This commit is contained in:
alt-glitch
2026-06-08 11:11:54 +00:00
parent 24f74eb888
commit 2bd9c9b881
741 changed files with 17733 additions and 79889 deletions
@@ -201,91 +201,6 @@ class TestWebhookEndpoints:
r = self.client.post("/api/webhooks", json={"name": "gh", "deliver": "log"})
assert r.status_code == 400
def test_enable_platform_starts_gateway_restart(self, monkeypatch):
import hermes_cli.web_server as ws
from hermes_cli.config import load_config
ws._ACTION_PROCS.pop("gateway-restart", None)
restart_calls = []
class FakeRestartProc:
pid = 4242
def fake_spawn_action(subcommand, name):
restart_calls.append((subcommand, name))
return FakeRestartProc()
monkeypatch.setattr(ws, "_spawn_hermes_action", fake_spawn_action)
r = self.client.post("/api/webhooks/enable")
assert r.status_code == 200
assert r.json() == {
"ok": True,
"platform": "webhook",
"enabled": True,
"needs_restart": False,
"restart_started": True,
"restart_action": "gateway-restart",
"restart_pid": 4242,
}
assert restart_calls == [(["gateway", "restart"], "gateway-restart")]
assert load_config()["platforms"]["webhook"]["enabled"] is True
assert self.client.get("/api/webhooks").json()["enabled"] is True
def test_enable_platform_reports_restart_failure_after_save(self, monkeypatch):
import hermes_cli.web_server as ws
from hermes_cli.config import load_config
ws._ACTION_PROCS.pop("gateway-restart", None)
def fail_spawn_action(subcommand, name):
assert subcommand == ["gateway", "restart"]
assert name == "gateway-restart"
raise RuntimeError("supervisor unavailable")
monkeypatch.setattr(ws, "_spawn_hermes_action", fail_spawn_action)
r = self.client.post("/api/webhooks/enable")
assert r.status_code == 200
data = r.json()
assert data["ok"] is True
assert data["platform"] == "webhook"
assert data["enabled"] is True
assert data["needs_restart"] is True
assert data["restart_started"] is False
assert "supervisor unavailable" in data["restart_error"]
assert load_config()["platforms"]["webhook"]["enabled"] is True
def test_enable_platform_reuses_inflight_gateway_restart(self, monkeypatch):
import hermes_cli.web_server as ws
from hermes_cli.config import load_config
ws._ACTION_PROCS.pop("gateway-restart", None)
class FakeRunningProc:
pid = 5151
def poll(self):
return None
monkeypatch.setitem(ws._ACTION_PROCS, "gateway-restart", FakeRunningProc())
def fail_spawn_action(subcommand, name):
raise AssertionError("must not spawn a second concurrent restart")
monkeypatch.setattr(ws, "_spawn_hermes_action", fail_spawn_action)
r = self.client.post("/api/webhooks/enable")
assert r.status_code == 200
data = r.json()
assert data["needs_restart"] is False
assert data["restart_started"] is True
assert data["restart_pid"] == 5151
assert load_config()["platforms"]["webhook"]["enabled"] is True
class TestOpsEndpoints:
@pytest.fixture(autouse=True)
@@ -707,10 +622,6 @@ class TestAdminEndpointsAuthGate:
resp = self.client.get(path)
assert resp.status_code in (401, 403)
def test_webhooks_enable_post_gated(self):
resp = self.client.post("/api/webhooks/enable")
assert resp.status_code in (401, 403)
class TestUpdateCheckEndpoint:
"""``GET /api/hermes/update/check`` reports availability without applying.
@@ -790,37 +701,6 @@ class TestUpdateCheckEndpoint:
assert body["update_available"] is False
assert body["message"]
def test_git_behind_includes_commits(self, monkeypatch):
import hermes_cli.web_server as ws
import hermes_cli.banner as banner
monkeypatch.setattr(ws, "detect_install_method", lambda *a, **k: "git")
monkeypatch.setattr(banner, "check_for_updates", lambda: 3)
monkeypatch.setattr(
ws,
"_recent_upstream_commits",
lambda n=20: [
{"sha": "abc1234", "summary": "feat: x", "author": "a", "at": 1},
],
)
body = self.client.get("/api/hermes/update/check").json()
# The desktop overlay renders this as the "what's changed" list.
assert isinstance(body["commits"], list)
assert body["commits"][0]["sha"] == "abc1234"
assert body["commits"][0]["summary"] == "feat: x"
def test_up_to_date_omits_commits(self, monkeypatch):
import hermes_cli.web_server as ws
import hermes_cli.banner as banner
monkeypatch.setattr(ws, "detect_install_method", lambda *a, **k: "git")
monkeypatch.setattr(banner, "check_for_updates", lambda: 0)
body = self.client.get("/api/hermes/update/check").json()
# No commits list when there's nothing to show (additive, non-breaking).
assert body.get("commits", []) == []
class TestDebugShareEndpoint:
"""POST /api/ops/debug-share returns the paste URLs synchronously so the
@@ -1042,3 +922,4 @@ class TestToolsConfigEndpoints:
kwargs["json"] = payload
r = fn(path, **kwargs)
assert r.status_code == 401, f"{method} {path} not gated"