mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
fix(tui): review follow-up — /retry, /plan, ANSI truncation, caching
- /retry: use session['history'] instead of non-existent agent.conversation_history; truncate history at last user message to match CLI retry_last() behavior; add history_lock safety - /plan: pass user instruction (arg) to build_plan_path instead of session_key; add runtime_note so agent knows where to save the plan - ANSI tool results: render full text via <Ansi wrap=truncate-end> instead of slicing raw ANSI through compactPreview (which cuts mid-escape-sequence producing garbled output) - Move _PENDING_INPUT_COMMANDS frozenset to module level - Use get_skill_commands() (cached) instead of scan_skill_commands() (rescans disk) in slash.exec skill interception - Add 3 retry tests: happy path with history truncation verification, empty history error, multipart content extraction - Update test mock target from scan_skill_commands to get_skill_commands
This commit is contained in:
parent
abc95338c2
commit
656c375855
3 changed files with 135 additions and 31 deletions
|
|
@ -245,7 +245,7 @@ def test_slash_exec_rejects_skill_commands(server):
|
|||
# Mock scan_skill_commands to return a known skill
|
||||
fake_skills = {"/hermes-agent-dev": {"name": "hermes-agent-dev", "description": "Dev workflow"}}
|
||||
|
||||
with patch("agent.skill_commands.scan_skill_commands", return_value=fake_skills):
|
||||
with patch("agent.skill_commands.get_skill_commands", return_value=fake_skills):
|
||||
resp = server.handle_request({
|
||||
"id": "r1",
|
||||
"method": "slash.exec",
|
||||
|
|
@ -324,6 +324,90 @@ def test_command_dispatch_steer_fallback_sends_message(server):
|
|||
assert result["message"] == "focus on testing"
|
||||
|
||||
|
||||
def test_command_dispatch_retry_finds_last_user_message(server):
|
||||
"""command.dispatch /retry walks session['history'] to find the last user message."""
|
||||
sid = "test-session"
|
||||
history = [
|
||||
{"role": "user", "content": "first question"},
|
||||
{"role": "assistant", "content": "first answer"},
|
||||
{"role": "user", "content": "second question"},
|
||||
{"role": "assistant", "content": "second answer"},
|
||||
]
|
||||
server._sessions[sid] = {
|
||||
"session_key": sid,
|
||||
"agent": None,
|
||||
"history": history,
|
||||
"history_lock": threading.Lock(),
|
||||
"history_version": 0,
|
||||
}
|
||||
|
||||
resp = server.handle_request({
|
||||
"id": "r4",
|
||||
"method": "command.dispatch",
|
||||
"params": {"name": "retry", "session_id": sid},
|
||||
})
|
||||
|
||||
assert "error" not in resp
|
||||
result = resp["result"]
|
||||
assert result["type"] == "send"
|
||||
assert result["message"] == "second question"
|
||||
# Verify history was truncated: everything from last user message onward removed
|
||||
assert len(server._sessions[sid]["history"]) == 2
|
||||
assert server._sessions[sid]["history"][-1]["role"] == "assistant"
|
||||
assert server._sessions[sid]["history_version"] == 1
|
||||
|
||||
|
||||
def test_command_dispatch_retry_empty_history(server):
|
||||
"""command.dispatch /retry with empty history returns error."""
|
||||
sid = "test-session"
|
||||
server._sessions[sid] = {
|
||||
"session_key": sid,
|
||||
"agent": None,
|
||||
"history": [],
|
||||
"history_lock": threading.Lock(),
|
||||
"history_version": 0,
|
||||
}
|
||||
|
||||
resp = server.handle_request({
|
||||
"id": "r5",
|
||||
"method": "command.dispatch",
|
||||
"params": {"name": "retry", "session_id": sid},
|
||||
})
|
||||
|
||||
assert "error" in resp
|
||||
assert resp["error"]["code"] == 4018
|
||||
|
||||
|
||||
def test_command_dispatch_retry_handles_multipart_content(server):
|
||||
"""command.dispatch /retry extracts text from multipart content lists."""
|
||||
sid = "test-session"
|
||||
history = [
|
||||
{"role": "user", "content": [
|
||||
{"type": "text", "text": "analyze this"},
|
||||
{"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}
|
||||
]},
|
||||
{"role": "assistant", "content": "I see the image."},
|
||||
]
|
||||
server._sessions[sid] = {
|
||||
"session_key": sid,
|
||||
"agent": None,
|
||||
"history": history,
|
||||
"history_lock": threading.Lock(),
|
||||
"history_version": 0,
|
||||
}
|
||||
|
||||
resp = server.handle_request({
|
||||
"id": "r6",
|
||||
"method": "command.dispatch",
|
||||
"params": {"name": "retry", "session_id": sid},
|
||||
})
|
||||
|
||||
assert "error" not in resp
|
||||
result = resp["result"]
|
||||
assert result["type"] == "send"
|
||||
assert result["message"] == "analyze this"
|
||||
|
||||
|
||||
def test_command_dispatch_returns_skill_payload(server):
|
||||
"""command.dispatch returns structured skill payload for the TUI to send()."""
|
||||
sid = "test-session"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue