fix(agent): persist shortcut commands without polluting LLM context

Shortcut commands (e.g. /help, /pairing) skip BUILD and SAVE states,
so their turns were never persisted to the session.  This caused WebUI
chats to appear empty after _turn_end because history hydration reads
from the session file.

Fix by persisting the user message and assistant response inside
_state_command, but tag them with _command=True so Session.get_history
filters them out of LLM context.  /new is excluded because it
intentionally clears the session.

- AgentLoop._persist_user_message_early now accepts **kwargs so
  _state_command can pass _command=True for the user turn.
- Session.get_history skips messages with _command=True.
This commit is contained in:
chengyongru
2026-05-14 23:51:58 +08:00
committed by Xubin Ren
parent 8b724d510e
commit 26665823e3
3 changed files with 51 additions and 0 deletions
+34
View File
@@ -418,6 +418,40 @@ class TestAutoCompactIdleDetection:
assert len(session_after.messages) == 0
await loop.close_mcp()
@pytest.mark.asyncio
async def test_shortcut_command_persisted_with_command_flag(self, tmp_path):
"""Shortcut commands (e.g. /help) are persisted so WebUI can show them,
but tagged with _command so they don't leak into LLM context."""
loop = _make_loop(tmp_path)
msg = InboundMessage(channel="cli", sender_id="user", chat_id="test", content="/help")
response = await loop._process_message(msg)
assert response is not None
session_after = loop.sessions.get_or_create("cli:test")
assert len(session_after.messages) == 2
assert session_after.messages[0]["role"] == "user"
assert session_after.messages[0]["content"] == "/help"
assert session_after.messages[0].get("_command") is True
assert session_after.messages[1]["role"] == "assistant"
assert session_after.messages[1].get("_command") is True
await loop.close_mcp()
@pytest.mark.asyncio
async def test_shortcut_command_excluded_from_get_history(self, tmp_path):
"""Messages marked _command are invisible to get_history (LLM context)."""
loop = _make_loop(tmp_path)
session = loop.sessions.get_or_create("cli:test")
session.add_message("user", "real question")
session.add_message("assistant", "real answer")
session.add_message("user", "/help", _command=True)
session.add_message("assistant", "help text", _command=True)
history = session.get_history()
assert len(history) == 2
assert all(m["content"] != "/help" for m in history)
assert all(m["content"] != "help text" for m in history)
await loop.close_mcp()
class TestAutoCompactSystemMessages:
"""Test that auto-new also works for system messages."""