fix(image): prevent duplicate delivery and replay artifacts

This commit is contained in:
Xubin Ren
2026-05-09 05:45:13 +00:00
parent 4d168c571c
commit 3231aaf9ee
9 changed files with 128 additions and 32 deletions
+12
View File
@@ -289,6 +289,18 @@ def test_build_messages_passes_channel_to_system_prompt(tmp_path) -> None:
assert "messaging app" in system
def test_system_prompt_keeps_message_tool_out_of_current_chat_replies(tmp_path) -> None:
workspace = _make_workspace(tmp_path)
builder = ContextBuilder(workspace)
prompt = builder.build_system_prompt(channel="slack")
assert "Do not use the 'message' tool for normal replies in the current chat" in prompt
assert "the runtime attaches those artifacts to the final assistant reply automatically" in prompt
assert "do not call 'message' just to announce or resend them" in prompt
assert "Wait for the tool results, then answer once" in prompt
def test_subagent_result_does_not_create_consecutive_assistant_messages(tmp_path) -> None:
workspace = _make_workspace(tmp_path)
builder = ContextBuilder(workspace)
+38 -9
View File
@@ -245,14 +245,8 @@ def test_get_history_annotates_user_turns_but_not_assistant_turns():
]
def test_get_history_annotates_proactive_assistant_deliveries_with_timestamps():
"""Cron / heartbeat assistant pushes still carry a timestamp prefix.
These proactive deliveries can sit hours away from the next user reply,
so the model needs to know when they fired. They are rare enough that
they don't act as in-context demonstrations encouraging the model to
prefix its own normal replies with ``[Message Time: ...]``.
"""
def test_get_history_does_not_annotate_proactive_assistant_deliveries_with_timestamps():
"""Assistant-side timestamp examples can leak back into future replies."""
session = Session(key="test:proactive-timestamps")
session.messages.append({
"role": "assistant",
@@ -271,7 +265,7 @@ def test_get_history_annotates_proactive_assistant_deliveries_with_timestamps():
assert history == [
{
"role": "assistant",
"content": "[Message Time: 2026-04-26T15:00:00]\n记得喝水",
"content": "记得喝水",
},
{
"role": "user",
@@ -370,6 +364,41 @@ def test_get_history_ignores_media_kwarg_on_non_user_rows():
assert history[0]["content"] == [{"type": "text", "text": "structured"}]
def test_get_history_does_not_paste_assistant_media_paths_into_replay():
session = Session(key="test:assistant-media")
session.messages.append(
{
"role": "assistant",
"content": "来了 🎨",
"media": ["/home/user/.nanobot/media/generated/img_abc.png"],
}
)
history = session.get_history(max_messages=500)
assert history == [{"role": "assistant", "content": "来了 🎨"}]
def test_get_history_sanitizes_existing_assistant_replay_artifacts():
session = Session(key="test:polluted-assistant")
session.messages.append(
{
"role": "assistant",
"content": (
"[Message Time: 2026-05-09 00:33:48]\n"
"来了 🎨\n"
"[image: /home/user/.nanobot/media/generated/img_old.png]\n\n"
"generate_image(\"16:9\")\n"
"message(\"来了 🎨\")"
),
}
)
history = session.get_history(max_messages=500, include_timestamps=True)
assert history == [{"role": "assistant", "content": "来了 🎨"}]
def test_get_history_respects_max_tokens(monkeypatch):
session = Session(key="test:token-cap")
session.messages.extend(
+10
View File
@@ -166,3 +166,13 @@ class TestMessageToolTurnTracking:
tool._sent_in_turn = True
tool.start_turn()
assert not tool._sent_in_turn
def test_schema_discourages_current_chat_replies(self) -> None:
tool = MessageTool()
assert "Do not use this for the normal reply in the current chat" in tool.description
assert "generate_image creates images in the current chat" in tool.description
assert (
"Do not use this for a normal reply in the current chat"
in tool.parameters["properties"]["content"]["description"]
)
+3
View File
@@ -75,6 +75,7 @@ def test_generated_image_paths_from_tool_results() -> None:
{"id": "img_2", "path": "/tmp/two.png"},
]
)
payload = json.loads(result)
assert generated_image_paths_from_messages(
[
@@ -82,3 +83,5 @@ def test_generated_image_paths_from_tool_results() -> None:
{"role": "tool", "name": "other", "content": result},
]
) == ["/tmp/one.png", "/tmp/two.png"]
assert "runtime attaches generated images automatically" in payload["next_step"]
assert "Do not call message" in payload["next_step"]