refactor: centralize cron session metadata keys
This commit is contained in:
@@ -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") == ""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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]
|
||||
|
||||
@@ -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() == []
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user