mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-22 10:32:00 +00:00
Merge pull request #48953 from kshitijk4poor/salvage/issue-48848
Some checks are pending
Deploy Site / deploy-vercel (push) Waiting to run
Deploy Site / deploy-docs (push) Waiting to run
Docker Build and Publish / build-amd64 (push) Waiting to run
Docker Build and Publish / build-arm64 (push) Waiting to run
Docker Build and Publish / merge (push) Blocked by required conditions
Lint (ruff + ty) / ruff + ty diff (push) Waiting to run
Lint (ruff + ty) / ruff enforcement (blocking) (push) Waiting to run
Lint (ruff + ty) / Windows footguns (blocking) (push) Waiting to run
OSV-Scanner / Scan lockfiles (push) Waiting to run
Tests / test (1) (push) Waiting to run
Tests / test (2) (push) Waiting to run
Tests / test (3) (push) Waiting to run
Tests / test (4) (push) Waiting to run
Tests / test (5) (push) Waiting to run
Tests / test (6) (push) Waiting to run
Tests / save-durations (push) Blocked by required conditions
Tests / e2e (push) Waiting to run
Typecheck / typecheck (apps/bootstrap-installer) (push) Waiting to run
Typecheck / typecheck (apps/desktop) (push) Waiting to run
Typecheck / typecheck (apps/shared) (push) Waiting to run
Typecheck / typecheck (ui-tui) (push) Waiting to run
Typecheck / typecheck (web) (push) Waiting to run
Typecheck / desktop-build (push) Waiting to run
uv.lock check / uv lock --check (push) Waiting to run
Some checks are pending
Deploy Site / deploy-vercel (push) Waiting to run
Deploy Site / deploy-docs (push) Waiting to run
Docker Build and Publish / build-amd64 (push) Waiting to run
Docker Build and Publish / build-arm64 (push) Waiting to run
Docker Build and Publish / merge (push) Blocked by required conditions
Lint (ruff + ty) / ruff + ty diff (push) Waiting to run
Lint (ruff + ty) / ruff enforcement (blocking) (push) Waiting to run
Lint (ruff + ty) / Windows footguns (blocking) (push) Waiting to run
OSV-Scanner / Scan lockfiles (push) Waiting to run
Tests / test (1) (push) Waiting to run
Tests / test (2) (push) Waiting to run
Tests / test (3) (push) Waiting to run
Tests / test (4) (push) Waiting to run
Tests / test (5) (push) Waiting to run
Tests / test (6) (push) Waiting to run
Tests / save-durations (push) Blocked by required conditions
Tests / e2e (push) Waiting to run
Typecheck / typecheck (apps/bootstrap-installer) (push) Waiting to run
Typecheck / typecheck (apps/desktop) (push) Waiting to run
Typecheck / typecheck (apps/shared) (push) Waiting to run
Typecheck / typecheck (ui-tui) (push) Waiting to run
Typecheck / typecheck (web) (push) Waiting to run
Typecheck / desktop-build (push) Waiting to run
uv.lock check / uv lock --check (push) Waiting to run
fix(tui): route pending-input commands via command.dispatch (#48848)
This commit is contained in:
commit
df4ca2c5ca
3 changed files with 55 additions and 18 deletions
|
|
@ -185,15 +185,17 @@ def test_goal_requires_session(server):
|
|||
# ── slash.exec /goal routing ──────────────────────────────────────────
|
||||
|
||||
|
||||
def test_slash_exec_rejects_goal_routes_to_command_dispatch(server, session):
|
||||
"""slash.exec must reject /goal with 4018 so the TUI client falls through
|
||||
to command.dispatch. Without this, the HermesCLI slash-worker subprocess
|
||||
would set the goal but silently drop the kickoff — the queue is in-proc."""
|
||||
def test_slash_exec_routes_goal_to_command_dispatch(server, session):
|
||||
"""slash.exec must route /goal directly to command.dispatch internally
|
||||
instead of returning an error. Previously the 4018 error required the
|
||||
TUI client to retry via command.dispatch, but some clients failed the
|
||||
fallback, leaving the command empty ("empty command")."""
|
||||
sid, _, _ = session
|
||||
r = _call(server, "slash.exec", command="goal status", session_id=sid)
|
||||
assert "error" in r
|
||||
assert r["error"]["code"] == 4018
|
||||
assert "command.dispatch" in r["error"]["message"]
|
||||
# Should succeed by routing to command.dispatch internally
|
||||
assert "result" in r
|
||||
assert r["result"]["type"] == "exec"
|
||||
assert "No active goal" in r["result"]["output"]
|
||||
|
||||
|
||||
def test_pending_input_commands_includes_goal(server):
|
||||
|
|
|
|||
|
|
@ -1121,20 +1121,45 @@ def test_slash_exec_plugin_handler_error_returns_output(server):
|
|||
|
||||
|
||||
@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."""
|
||||
sid = "test-session"
|
||||
server._sessions[sid] = {"session_key": sid, "agent": None}
|
||||
def test_slash_exec_routes_pending_input_commands_to_dispatch(server, cmd):
|
||||
"""slash.exec must route _pending_input commands to command.dispatch
|
||||
internally instead of returning the old 4018 "use command.dispatch"
|
||||
fallback error (#48848). Some TUI clients failed that client-side
|
||||
fallback, dropping the input and surfacing "empty command".
|
||||
|
||||
resp = server.handle_request({
|
||||
The contract is that slash.exec produces exactly the response
|
||||
command.dispatch would for the same command — no fragile retry hop.
|
||||
"""
|
||||
base, _, arg = cmd.partition(" ")
|
||||
|
||||
def fresh_session():
|
||||
return {"session_key": "test-session", "agent": None}
|
||||
|
||||
sid = "test-session"
|
||||
|
||||
# Response from the (new) internal routing in slash.exec.
|
||||
server._sessions[sid] = fresh_session()
|
||||
routed = server.handle_request({
|
||||
"id": "r1",
|
||||
"method": "slash.exec",
|
||||
"params": {"command": cmd, "session_id": sid},
|
||||
})
|
||||
|
||||
assert "error" in resp
|
||||
assert resp["error"]["code"] == 4018
|
||||
assert "pending-input command" in resp["error"]["message"]
|
||||
# Response from calling command.dispatch directly with the parsed parts.
|
||||
server._sessions[sid] = fresh_session()
|
||||
direct = server.handle_request({
|
||||
"id": "r1",
|
||||
"method": "command.dispatch",
|
||||
"params": {"name": base, "arg": arg, "session_id": sid},
|
||||
})
|
||||
|
||||
# slash.exec must no longer emit the old client-fallback rejection.
|
||||
if "error" in routed:
|
||||
assert "pending-input command" not in routed["error"]["message"]
|
||||
|
||||
# Internal routing must yield the same payload as command.dispatch.
|
||||
assert routed.get("result") == direct.get("result")
|
||||
assert routed.get("error") == direct.get("error")
|
||||
|
||||
|
||||
def test_command_dispatch_queue_sends_message(server):
|
||||
|
|
|
|||
|
|
@ -8462,7 +8462,9 @@ _TUI_EXTRA: list[tuple[str, str, str]] = [
|
|||
|
||||
# Commands that queue messages onto _pending_input in the CLI.
|
||||
# In the TUI the slash worker subprocess has no reader for that queue,
|
||||
# so slash.exec rejects them → TUI falls through to command.dispatch.
|
||||
# so slash.exec routes them to command.dispatch internally (which handles
|
||||
# them and returns a structured payload) instead of erroring out and
|
||||
# relying on a client-side fallback. See #48848.
|
||||
_PENDING_INPUT_COMMANDS: frozenset[str] = frozenset(
|
||||
{
|
||||
"retry",
|
||||
|
|
@ -9729,8 +9731,16 @@ def _(rid, params: dict) -> dict:
|
|||
_cmd_arg = _cmd_parts[1] if len(_cmd_parts) > 1 else ""
|
||||
|
||||
if _cmd_base in _PENDING_INPUT_COMMANDS:
|
||||
return _err(
|
||||
rid, 4018, f"pending-input command: use command.dispatch for /{_cmd_base}"
|
||||
# Route directly to command.dispatch instead of returning an error
|
||||
# that requires the frontend to retry. Some TUI clients fail the
|
||||
# fallback, leaving the command empty and showing "empty command".
|
||||
return _methods["command.dispatch"](
|
||||
rid,
|
||||
{
|
||||
"name": _cmd_base,
|
||||
"arg": _cmd_arg,
|
||||
"session_id": params.get("session_id", ""),
|
||||
},
|
||||
)
|
||||
|
||||
if _cmd_base in _WORKER_BLOCKED_COMMANDS:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue