refactor: centralize cron session metadata keys

This commit is contained in:
chengyongru
2026-06-12 11:43:23 +08:00
parent 0e3a57b371
commit d9d481bc15
18 changed files with 116 additions and 62 deletions
+36
View File
@@ -8,6 +8,7 @@ from nanobot.agent.context import ContextBuilder
from nanobot.agent.loop import AgentLoop
from nanobot.bus.events import InboundMessage
from nanobot.bus.queue import MessageBus
from nanobot.cron.automation import AUTOMATION_HISTORY_META, AUTOMATION_TRIGGER_META
from nanobot.providers.base import LLMResponse
from nanobot.session.goal_state import GOAL_STATE_KEY
from nanobot.session.manager import Session, SessionManager
@@ -65,6 +66,41 @@ def test_agent_loop_llm_runtime_reflects_current_provider_and_model(tmp_path: Pa
assert runtime.model == "next-model"
def test_persist_automation_turn_uses_distinct_history_marker(tmp_path: Path) -> None:
loop = _make_full_loop(tmp_path)
session = loop.sessions.get_or_create("websocket:auto")
prompt_ref = {"id": "cron.agent_turn.reminder", "version": 1, "sha256": "abc"}
persisted = loop._persist_user_message_early(
InboundMessage(
channel="websocket",
sender_id="cron",
chat_id="auto",
content="Automation: internal prompt",
metadata={
AUTOMATION_TRIGGER_META: {
"job_id": "job-1",
"job_name": "Daily check",
"run_id": "job-1:1",
"prompt_ref": prompt_ref,
"persist_content": "Scheduled automation triggered: Daily check",
}
},
),
session,
)
assert persisted is True
message = session.messages[-1]
assert message["content"] == "Scheduled automation triggered: Daily check"
assert message[AUTOMATION_HISTORY_META] is True
assert AUTOMATION_TRIGGER_META not in message
assert message["automation_id"] == "job-1"
assert message["automation_name"] == "Daily check"
assert message["automation_run_id"] == "job-1:1"
assert message["automation_prompt_ref"] == prompt_ref
def test_clean_generated_title_strips_reasoning_tags() -> None:
assert clean_generated_title("<think>reasoning</think> WebUI polish") == "WebUI polish"
assert clean_generated_title("Title: <think> The user said hello") == ""
+1 -1
View File
@@ -592,8 +592,8 @@ async def test_waiting_dispatch_does_not_replace_active_pending_queue(tmp_path):
@pytest.mark.asyncio
async def test_followup_routed_to_pending_queue(tmp_path):
"""Unified-session follow-ups should route into the active pending queue."""
from nanobot.agent.loop import UNIFIED_SESSION_KEY
from nanobot.bus.events import InboundMessage
from nanobot.session.keys import UNIFIED_SESSION_KEY
loop = _make_loop(tmp_path)
loop._unified_session = True
+1 -1
View File
@@ -11,10 +11,10 @@ from urllib.parse import urlencode
import httpx
import pytest
from nanobot.agent.loop import UNIFIED_SESSION_KEY
from nanobot.channels.websocket import WebSocketChannel, WebSocketConfig
from nanobot.cron.service import CronService
from nanobot.cron.types import CronJob, CronPayload, CronSchedule
from nanobot.session.keys import UNIFIED_SESSION_KEY
from nanobot.session.manager import Session, SessionManager
from nanobot.webui.gateway_services import GatewayServices, build_gateway_services
+16 -11
View File
@@ -11,11 +11,13 @@ from typer.testing import CliRunner
from nanobot.bus.events import InboundMessage, OutboundMessage
from nanobot.cli.commands import _proactive_delivery_metadata, app
from nanobot.config.schema import Config
from nanobot.cron.automation import AUTOMATION_DEFER_UNTIL_IDLE_META, AUTOMATION_TRIGGER_META
from nanobot.cron.types import CronJob, CronPayload
from nanobot.providers.factory import ProviderSnapshot, make_provider
from nanobot.providers.openai_codex_provider import _strip_model_prefix
from nanobot.providers.registry import find_by_name
from nanobot.session.routing import SESSION_ROUTING_METADATA_KEY
from nanobot.webui.metadata import WEBUI_MESSAGE_SOURCE_METADATA_KEY, WEBUI_TURN_METADATA_KEY
runner = CliRunner()
@@ -23,7 +25,7 @@ runner = CliRunner()
def test_proactive_websocket_delivery_gets_fresh_turn_id() -> None:
metadata = {
"webui": True,
"webui_turn_id": "turn-that-created-the-reminder",
WEBUI_TURN_METADATA_KEY: "turn-that-created-the-reminder",
"workspace_scope": {"mode": "default"},
}
@@ -36,9 +38,9 @@ def test_proactive_websocket_delivery_gets_fresh_turn_id() -> None:
assert out["webui"] is True
assert out["workspace_scope"] == {"mode": "default"}
assert out["webui_turn_id"].startswith("cron:drink-water:")
assert out["webui_turn_id"] != metadata["webui_turn_id"]
assert out["_webui_message_source"] == {"kind": "cron", "label": "drink water"}
assert out[WEBUI_TURN_METADATA_KEY].startswith("cron:drink-water:")
assert out[WEBUI_TURN_METADATA_KEY] != metadata[WEBUI_TURN_METADATA_KEY]
assert out[WEBUI_MESSAGE_SOURCE_METADATA_KEY] == {"kind": "cron", "label": "drink water"}
def _fake_provider():
@@ -1350,7 +1352,7 @@ def test_gateway_cron_evaluator_receives_scheduled_reminder_context(
to="chat-1",
channel_meta={
"webui": True,
"webui_turn_id": old_turn_id,
WEBUI_TURN_METADATA_KEY: old_turn_id,
"workspace_scope": {"mode": "default"},
},
),
@@ -1365,9 +1367,9 @@ def test_gateway_cron_evaluator_receives_scheduled_reminder_context(
assert delivered.chat_id == "chat-1"
assert delivered.metadata["webui"] is True
assert delivered.metadata["workspace_scope"] == {"mode": "default"}
assert delivered.metadata["webui_turn_id"].startswith("cron:drink-water:")
assert delivered.metadata["webui_turn_id"] != old_turn_id
assert delivered.metadata["_webui_message_source"] == {
assert delivered.metadata[WEBUI_TURN_METADATA_KEY].startswith("cron:drink-water:")
assert delivered.metadata[WEBUI_TURN_METADATA_KEY] != old_turn_id
assert delivered.metadata[WEBUI_MESSAGE_SOURCE_METADATA_KEY] == {
"kind": "cron",
"label": "drink water",
}
@@ -1653,14 +1655,17 @@ def test_gateway_bound_cron_runs_as_session_turn(
assert "Automation: Check repository health." in msg.content
assert msg.metadata["webui"] is True
assert msg.metadata["workspace_scope"]["project_path"] == str(tmp_path)
assert msg.metadata["_webui_message_source"] == {"kind": "cron", "label": "Repo check"}
trigger = msg.metadata["_automation_trigger"]
assert msg.metadata[WEBUI_MESSAGE_SOURCE_METADATA_KEY] == {
"kind": "cron",
"label": "Repo check",
}
trigger = msg.metadata[AUTOMATION_TRIGGER_META]
assert trigger["job_id"] == "repo-check"
assert trigger["job_name"] == "Repo check"
assert trigger["persist_content"] == (
"Scheduled automation triggered: Repo check\n\nCheck repository health."
)
assert msg.metadata["_defer_until_session_idle"] is True
assert msg.metadata[AUTOMATION_DEFER_UNTIL_IDLE_META] is True
statuses = [record["status"] for _run_id, record in seen["run_records"]]
assert statuses == ["queued", "ok"]
assert seen["run_records"][0][0] == seen["run_records"][1][0]
+1 -1
View File
@@ -347,7 +347,7 @@ def test_add_job_requires_session_key(tmp_path) -> None:
tool = _make_tool(tmp_path)
tool.set_context(RequestContext(channel="telegram", chat_id="chat-1"))
result = tool._add_job(None, "Background refresh", 60, None, None, None, deliver=False)
result = tool._add_job(None, "Background refresh", 60, None, None, None)
assert result == "Error: scheduled automations must be created from a chat session"
assert tool._cron.list_jobs() == []
+3 -2
View File
@@ -10,6 +10,7 @@ from nanobot.agent.tools.cron import CronTool
from nanobot.agent.tools.message import MessageTool
from nanobot.agent.tools.spawn import SpawnTool
from nanobot.cron.service import CronService
from nanobot.session.keys import UNIFIED_SESSION_KEY
@pytest.mark.asyncio
@@ -262,7 +263,7 @@ async def test_webui_cron_tool_uses_unified_session_when_enabled(tmp_path) -> No
"websocket",
"chat-123",
metadata={"webui": True},
session_key="unified:default",
session_key=UNIFIED_SESSION_KEY,
)
result = await tool.execute(action="add", message="standup", every_seconds=300)
@@ -270,7 +271,7 @@ async def test_webui_cron_tool_uses_unified_session_when_enabled(tmp_path) -> No
jobs = tool._cron.list_jobs()
assert len(jobs) == 1
assert jobs[0].payload.session_key == "unified:default"
assert jobs[0].payload.session_key == UNIFIED_SESSION_KEY
@pytest.mark.asyncio