From 32d8a1dd7b25d08f06a4b1e3de55c5c50cb3f88b Mon Sep 17 00:00:00 2001 From: chengyongru Date: Fri, 12 Jun 2026 18:17:28 +0800 Subject: [PATCH] fix: hide internal cron prompts from webui --- nanobot/session/webui_turns.py | 3 ++ nanobot/webui/session_list_index.py | 5 +++ nanobot/webui/transcript.py | 3 ++ tests/agent/test_loop_save_turn.py | 25 +++++++++++++ tests/channels/test_websocket_channel.py | 46 ++++++++++++++++++++++++ tests/webui/test_session_list_index.py | 15 ++++++++ 6 files changed, 97 insertions(+) diff --git a/nanobot/session/webui_turns.py b/nanobot/session/webui_turns.py index 8d4163f32..8c6072bb9 100644 --- a/nanobot/session/webui_turns.py +++ b/nanobot/session/webui_turns.py @@ -22,6 +22,7 @@ from nanobot.bus.runtime_events import ( TurnCompleted, TurnRunStatusChanged, ) +from nanobot.cron.session_turns import CRON_HISTORY_META from nanobot.providers.base import LLMProvider from nanobot.session.goal_state import goal_state_ws_blob from nanobot.session.manager import Session, SessionManager @@ -68,6 +69,8 @@ def _title_inputs(session: Session) -> tuple[str, str]: for message in session.messages: if message.get("_command") is True: continue + if message.get(CRON_HISTORY_META) is True: + continue role = message.get("role") content = message.get("content") if not isinstance(content, str) or not content.strip(): diff --git a/nanobot/webui/session_list_index.py b/nanobot/webui/session_list_index.py index 082ce5300..4fba2ca14 100644 --- a/nanobot/webui/session_list_index.py +++ b/nanobot/webui/session_list_index.py @@ -14,6 +14,7 @@ from typing import Any from loguru import logger +from nanobot.cron.session_turns import CRON_HISTORY_META from nanobot.session.manager import ( _SESSION_LIST_PREVIEW_MAX_CHARS, _SESSION_LIST_PREVIEW_MAX_RECORDS, @@ -142,6 +143,8 @@ def _preview_from_messages(messages: list[dict[str, Any]]) -> str: or scanned_chars > _SESSION_LIST_PREVIEW_MAX_CHARS ): break + if item.get(CRON_HISTORY_META) is True: + continue text = _message_preview_text(item) if not text: continue @@ -193,6 +196,8 @@ def _scan_session_row(session_manager: SessionManager, path: Path) -> dict[str, item = json.loads(line) if item.get("_type") == "metadata": continue + if item.get(CRON_HISTORY_META) is True: + continue text = _message_preview_text(item) if not text: continue diff --git a/nanobot/webui/transcript.py b/nanobot/webui/transcript.py index e3a8f1dfc..3c433e20f 100644 --- a/nanobot/webui/transcript.py +++ b/nanobot/webui/transcript.py @@ -17,6 +17,7 @@ from urllib.parse import unquote, urlparse from loguru import logger from nanobot.config.paths import get_webui_dir +from nanobot.cron.session_turns import CRON_HISTORY_META from nanobot.session.manager import SessionManager from nanobot.webui.metadata import WEBUI_MESSAGE_SOURCE_METADATA_KEY, WEBUI_TURN_METADATA_KEY @@ -854,6 +855,8 @@ def _session_user_event( ) -> dict[str, Any] | None: if message.get("role") != "user": return None + if message.get(CRON_HISTORY_META) is True: + return None content = message.get("content") text = content if isinstance(content, str) else "" media = message.get("media") diff --git a/tests/agent/test_loop_save_turn.py b/tests/agent/test_loop_save_turn.py index f5862cc86..a5dd40426 100644 --- a/tests/agent/test_loop_save_turn.py +++ b/tests/agent/test_loop_save_turn.py @@ -181,6 +181,31 @@ async def test_generate_webui_title_ignores_command_only_sessions(tmp_path: Path loop.provider.chat_with_retry.assert_not_awaited() +@pytest.mark.asyncio +async def test_generate_webui_title_ignores_cron_internal_turns(tmp_path: Path) -> None: + loop = _make_full_loop(tmp_path) + session = loop.sessions.get_or_create("websocket:cron-title") + session.metadata[WEBUI_SESSION_METADATA_KEY] = True + session.add_message( + "user", + "Scheduled cron job triggered: 30s-test\n\nInternal reminder prompt", + **{CRON_HISTORY_META: True}, + ) + session.add_message("assistant", "提醒已经到期。") + loop.sessions.save(session) + + generated = await maybe_generate_webui_title( + sessions=loop.sessions, + session_key="websocket:cron-title", + provider=loop.provider, + model=loop.model, + ) + + assert generated is False + assert WEBUI_TITLE_METADATA_KEY not in session.metadata + loop.provider.chat_with_retry.assert_not_awaited() + + def test_webui_title_update_uses_captured_llm_runtime( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, diff --git a/tests/channels/test_websocket_channel.py b/tests/channels/test_websocket_channel.py index b8ee27a76..44eae486d 100644 --- a/tests/channels/test_websocket_channel.py +++ b/tests/channels/test_websocket_channel.py @@ -2866,3 +2866,49 @@ def test_handle_webui_thread_get_backfills_legacy_missing_user_rows( "legacy question", "legacy answer", ] + + +def test_handle_webui_thread_get_does_not_backfill_cron_internal_prompt( + tmp_path, + monkeypatch, +) -> None: + from urllib.parse import quote + + from websockets.datastructures import Headers + from websockets.http11 import Request + + from nanobot.cron.session_turns import CRON_HISTORY_META + from nanobot.webui.transcript import append_transcript_object + + monkeypatch.setattr("nanobot.config.paths.get_data_dir", lambda: tmp_path) + workspace = tmp_path / "workspace" + sessions = SessionManager(workspace) + key = "websocket:c-cron" + session = sessions.get_or_create(key) + session.add_message( + "user", + "Scheduled cron job triggered: 30s-test\n\nInternal reminder prompt", + **{CRON_HISTORY_META: True}, + ) + session.add_message("assistant", "提醒已经到期。") + sessions.save(session) + append_transcript_object( + key, + {"event": "message", "chat_id": "c-cron", "text": "提醒已经到期。"}, + ) + + bus = MagicMock() + channel = WebSocketChannel( + {"enabled": True, "allowFrom": ["*"]}, + bus, + gateway=_basic_handler(bus, session_manager=sessions, workspace_path=workspace), + ) + channel.gateway.tokens.api_tokens["tok"] = time.monotonic() + 300.0 + enc = quote(key, safe="") + req = Request(f"/api/sessions/{enc}/webui-thread", Headers([("Authorization", "Bearer tok")])) + resp = channel.gateway.http._handle_webui_thread_get(req, enc) + + assert resp.status_code == 200 + body = json.loads(resp.body.decode()) + assert [message["role"] for message in body["messages"]] == ["assistant"] + assert [message["content"] for message in body["messages"]] == ["提醒已经到期。"] diff --git a/tests/webui/test_session_list_index.py b/tests/webui/test_session_list_index.py index aea32b3e7..c8b0adf6c 100644 --- a/tests/webui/test_session_list_index.py +++ b/tests/webui/test_session_list_index.py @@ -3,6 +3,7 @@ from __future__ import annotations from pathlib import Path import nanobot.webui.session_list_index as session_list_index +from nanobot.cron.session_turns import CRON_HISTORY_META from nanobot.session.manager import SessionManager @@ -71,5 +72,19 @@ def test_webui_session_list_drops_deleted_index_rows(tmp_path: Path) -> None: assert list_webui_sessions(manager) == [] +def test_webui_session_list_skips_cron_internal_user_preview(tmp_path: Path) -> None: + manager = SessionManager(tmp_path) + session = manager.get_or_create("websocket:cron-preview") + session.add_message( + "user", + "Scheduled cron job triggered: 30s-test\n\nInternal reminder prompt", + **{CRON_HISTORY_META: True}, + ) + session.add_message("assistant", "提醒已经到期。") + manager.save(session) + + assert list_webui_sessions(manager)[0]["preview"] == "提醒已经到期。" + + def list_webui_sessions(manager: SessionManager) -> list[dict]: return session_list_index.list_webui_sessions(manager)