From 7e780f4832ed8c34a23dd292b522df3e9705bd0a Mon Sep 17 00:00:00 2001 From: asheriif Date: Sun, 3 May 2026 14:50:00 +0000 Subject: [PATCH] fix(tui): run plugin slash commands live --- tests/tui_gateway/test_protocol.py | 30 ++++++++++++++++++++++++++++++ tui_gateway/server.py | 24 +++++++++++++++++++++--- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/tests/tui_gateway/test_protocol.py b/tests/tui_gateway/test_protocol.py index 2e54bb93ea..96df9823a6 100644 --- a/tests/tui_gateway/test_protocol.py +++ b/tests/tui_gateway/test_protocol.py @@ -391,6 +391,36 @@ def test_slash_exec_rejects_skill_commands(server): assert "skill command" in resp["error"]["message"] +def test_slash_exec_handles_plugin_commands_in_live_gateway(server): + """Plugin slash commands return normal slash.exec output without using the worker.""" + sid = "test-session" + + class Worker: + def __init__(self): + self.calls = [] + + def run(self, cmd): + self.calls.append(cmd) + return f"worker:{cmd}" + + worker = Worker() + server._sessions[sid] = {"session_key": sid, "agent": None, "slash_worker": worker} + + with patch( + "hermes_cli.plugins.get_plugin_command_handler", + lambda name: (lambda arg: f"plugin:{arg}") if name == "plugin-cmd" else None, + ): + resp = server.handle_request({ + "id": "r-plugin-slash", + "method": "slash.exec", + "params": {"command": "plugin-cmd hello", "session_id": sid}, + }) + + assert "error" not in resp + assert resp["result"] == {"output": "plugin:hello"} + assert worker.calls == [] + + @pytest.mark.parametrize("cmd", ["retry", "queue hello", "q hello", "steer fix the test", "plan"]) def test_slash_exec_rejects_pending_input_commands(server, cmd): """slash.exec must reject commands that use _pending_input in the CLI.""" diff --git a/tui_gateway/server.py b/tui_gateway/server.py index fe66d3798d..c59d358d74 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -5165,9 +5165,13 @@ def _(rid, params: dict) -> dict: return _err(rid, 4004, "empty command") # Skill slash commands and _pending_input commands must NOT go through the - # slash worker — see _PENDING_INPUT_COMMANDS definition above. - _cmd_parts = cmd.split() if not cmd.startswith("/") else cmd.lstrip("/").split() - _cmd_base = _cmd_parts[0] if _cmd_parts else "" + # slash worker — see _PENDING_INPUT_COMMANDS definition above. Plugin + # commands must also avoid the worker, but unlike skills/pending-input they + # still return normal slash.exec output so the TUI keeps the pager path. + _cmd_text = cmd.lstrip("/") if cmd.startswith("/") else cmd + _cmd_parts = _cmd_text.split(maxsplit=1) + _cmd_base = (_cmd_parts[0] if _cmd_parts else "").lower() + _cmd_arg = _cmd_parts[1] if len(_cmd_parts) > 1 else "" if _cmd_base in _PENDING_INPUT_COMMANDS: return _err( @@ -5185,6 +5189,20 @@ def _(rid, params: dict) -> dict: except Exception: pass + try: + from hermes_cli.plugins import ( + get_plugin_command_handler, + resolve_plugin_command_result, + ) + + if _cmd_base: + plugin_handler = get_plugin_command_handler(_cmd_base) + if plugin_handler: + result = resolve_plugin_command_result(plugin_handler(_cmd_arg)) + return _ok(rid, {"output": str(result or "(no output)")}) + except Exception as e: + return _err(rid, 4018, f"plugin command error: {e}") + worker = session.get("slash_worker") if not worker: try: