mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-05-19 16:12:30 +00:00
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:
parent
8b724d510e
commit
26665823e3
@ -564,6 +564,7 @@ class AgentLoop:
|
||||
self,
|
||||
msg: InboundMessage,
|
||||
session: Session,
|
||||
**kwargs: Any,
|
||||
) -> bool:
|
||||
"""Persist the triggering user message before the turn starts.
|
||||
|
||||
@ -573,6 +574,7 @@ class AgentLoop:
|
||||
has_text = isinstance(msg.content, str) and msg.content.strip()
|
||||
if has_text or media_paths:
|
||||
extra: dict[str, Any] = {"media": list(media_paths)} if media_paths else {}
|
||||
extra.update(kwargs)
|
||||
text = msg.content if isinstance(msg.content, str) else ""
|
||||
session.add_message("user", text, **extra)
|
||||
self._mark_pending_user_turn(session)
|
||||
@ -1268,6 +1270,19 @@ class AgentLoop:
|
||||
result = await self.commands.dispatch(cmd_ctx)
|
||||
if result is not None:
|
||||
ctx.outbound = result
|
||||
# Shortcut commands skip BUILD and SAVE, so we must persist the
|
||||
# turn here so WebUI history hydration after _turn_end sees the
|
||||
# message. Mark messages with _command so get_history can filter
|
||||
# them out of LLM context. /new is excluded because it
|
||||
# intentionally clears the session.
|
||||
if raw.lower() != "/new":
|
||||
ctx.user_persisted_early = self._persist_user_message_early(
|
||||
ctx.msg, ctx.session, _command=True
|
||||
)
|
||||
ctx.session.add_message(
|
||||
"assistant", result.content, _command=True
|
||||
)
|
||||
self.sessions.save(ctx.session)
|
||||
return "shortcut"
|
||||
return "dispatch"
|
||||
|
||||
|
||||
@ -139,6 +139,8 @@ class Session:
|
||||
|
||||
out: list[dict[str, Any]] = []
|
||||
for message in sliced:
|
||||
if message.get("_command"):
|
||||
continue
|
||||
content = message.get("content", "")
|
||||
role = message.get("role")
|
||||
if role == "assistant" and isinstance(content, str):
|
||||
|
||||
@ -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."""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user