Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions tests/test_tui_gateway_entry_hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from __future__ import annotations

import pytest

from tui_gateway import entry


def test_prepare_tui_gateway_hooks_registers_shell_hooks(monkeypatch):
calls = []

def fake_discover_plugins():
calls.append(("discover_plugins",))

def fake_load_config():
calls.append(("load_config",))
return {"hooks": {"post_llm_call": [{"command": "echo {}", "timeout": 5}]}}

def fake_register_from_config(cfg, *, accept_hooks=False):
calls.append(("register_from_config", cfg, accept_hooks))

monkeypatch.setattr("hermes_cli.plugins.discover_plugins", fake_discover_plugins)
monkeypatch.setattr("hermes_cli.config.load_config", fake_load_config)
monkeypatch.setattr(
"agent.shell_hooks.register_from_config",
fake_register_from_config,
)

entry._prepare_tui_gateway_hooks()

assert calls == [
("discover_plugins",),
("load_config",),
(
"register_from_config",
{"hooks": {"post_llm_call": [{"command": "echo {}", "timeout": 5}]}},
False,
),
]


def test_main_prepares_hooks_before_gateway_ready(monkeypatch):
events = []

monkeypatch.setattr(
entry,
"_install_sidecar_publisher",
lambda: events.append("sidecar"),
)
monkeypatch.setattr(
entry,
"_prepare_tui_gateway_hooks",
lambda: events.append("hooks"),
)
monkeypatch.setattr(entry, "resolve_skin", lambda: "default")
monkeypatch.setattr(
"hermes_cli.config.read_raw_config",
lambda: {"mcp_servers": {}},
)
monkeypatch.setattr(
entry,
"_log_exit",
lambda reason: events.append(("exit", reason)),
)

def fake_write_json(payload):
events.append(("write_json", payload["params"]["type"]))
return False

monkeypatch.setattr(entry, "write_json", fake_write_json)

with pytest.raises(SystemExit):
entry.main()

assert events[:3] == [
"sidecar",
"hooks",
("write_json", "gateway.ready"),
]
24 changes: 24 additions & 0 deletions tui_gateway/entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,32 @@ def _log_exit(reason: str) -> None:
print(f"[gateway-exit] {reason}", file=sys.stderr, flush=True)


def _prepare_tui_gateway_hooks() -> None:
"""Register plugins and declarative shell hooks in the TUI gateway process.

The outer ``hermes --tui`` launcher is a different Python process from this
gateway subprocess. Agent turns run here, so conversation hooks such as
``post_llm_call`` must be registered here as well; registering them only in
the launcher leaves TUI responses with an empty hook manager.
"""
try:
from hermes_cli.plugins import discover_plugins

discover_plugins()
except Exception:
pass
try:
from hermes_cli.config import load_config
from agent.shell_hooks import register_from_config

register_from_config(load_config(), accept_hooks=False)
except Exception:
pass


def main():
_install_sidecar_publisher()
_prepare_tui_gateway_hooks()

# MCP tool discovery — inline is safe here: TUI entry is a plain
# sync loop with no asyncio event loop to block. Previously ran as
Expand Down
Loading