refactor: store cron origin delivery context

This commit is contained in:
chengyongru
2026-06-12 15:07:25 +08:00
parent b232a52794
commit 5ae907bc2f
10 changed files with 171 additions and 96 deletions
+19
View File
@@ -1611,6 +1611,8 @@ def test_gateway_bound_cron_runs_as_session_turn(
payload=CronPayload(
message="Check repository health.",
session_key="websocket:chat-1",
origin_channel="websocket",
origin_chat_id="chat-1",
),
)
@@ -1646,6 +1648,13 @@ def test_gateway_bound_cron_runs_as_session_turn(
payload=CronPayload(
message="Check the Discord thread.",
session_key="discord:456:thread:777",
origin_channel="discord",
origin_chat_id="777",
origin_metadata={
"context_chat_id": "456",
"parent_channel_id": "456",
"thread_id": "777",
},
),
)
@@ -1667,6 +1676,9 @@ def test_gateway_bound_cron_runs_as_session_turn(
payload=CronPayload(
message="Check the Telegram topic.",
session_key="telegram:-100123:topic:42",
origin_channel="telegram",
origin_chat_id="-100123",
origin_metadata={"message_thread_id": 42},
),
)
@@ -1686,6 +1698,13 @@ def test_gateway_bound_cron_runs_as_session_turn(
payload=CronPayload(
message="Check the Feishu topic.",
session_key="feishu:oc_abc:om_root123",
origin_channel="feishu",
origin_chat_id="oc_abc",
origin_metadata={
"chat_type": "group",
"message_id": "om_root123",
"thread_id": "om_root123",
},
),
)
+40
View File
@@ -87,6 +87,37 @@ def test_list_bound_agent_jobs_excludes_legacy_delivery_payloads(tmp_path) -> No
assert service.list_bound_cron_jobs_for_session("websocket:chat-1") == [bound]
def test_add_job_preserves_origin_delivery_context(tmp_path) -> None:
service = CronService(tmp_path / "cron" / "jobs.json")
metadata = {"slack": {"thread_ts": "1234567890.123456", "channel_type": "channel"}}
job = service.add_job(
name="bound thread",
schedule=CronSchedule(kind="every", every_ms=60_000),
message="hello",
session_key="slack:C123:1234567890.123456",
origin_channel="slack",
origin_chat_id="C123",
origin_metadata=metadata,
)
assert job.payload.origin_channel == "slack"
assert job.payload.origin_chat_id == "C123"
assert job.payload.origin_metadata == metadata
raw = json.loads((tmp_path / "cron" / "action.jsonl").read_text(encoding="utf-8"))
payload = raw["params"]["payload"]
assert payload["origin_channel"] == "slack"
assert payload["origin_chat_id"] == "C123"
assert payload["origin_metadata"] == metadata
reloaded = service.get_job(job.id)
assert reloaded is not None
assert reloaded.payload.origin_channel == "slack"
assert reloaded.payload.origin_chat_id == "C123"
assert reloaded.payload.origin_metadata == metadata
@pytest.mark.asyncio
async def test_channel_meta_and_session_key_survive_store_reload(tmp_path) -> None:
store_path = tmp_path / "cron" / "jobs.json"
@@ -103,6 +134,9 @@ async def test_channel_meta_and_session_key_survive_store_reload(tmp_path) -> No
to="C123",
channel_meta=meta,
session_key="slack:C123:1234567890.123456",
origin_channel="slack",
origin_chat_id="C123",
origin_metadata=meta,
)
finally:
service.stop()
@@ -111,11 +145,17 @@ async def test_channel_meta_and_session_key_survive_store_reload(tmp_path) -> No
payload = raw["jobs"][0]["payload"]
assert payload["channelMeta"] == meta
assert payload["sessionKey"] == "slack:C123:1234567890.123456"
assert payload["originChannel"] == "slack"
assert payload["originChatId"] == "C123"
assert payload["originMetadata"] == meta
reloaded = CronService(store_path).get_job(job.id)
assert reloaded is not None
assert reloaded.payload.channel_meta == meta
assert reloaded.payload.session_key == "slack:C123:1234567890.123456"
assert reloaded.payload.origin_channel == "slack"
assert reloaded.payload.origin_chat_id == "C123"
assert reloaded.payload.origin_metadata == meta
@pytest.mark.asyncio
+8 -2
View File
@@ -339,6 +339,9 @@ def test_add_job_binds_current_session_key(tmp_path) -> None:
assert result.startswith("Created job")
job = tool._cron.list_jobs()[0]
assert job.payload.session_key == "telegram:chat-1"
assert job.payload.origin_channel == "telegram"
assert job.payload.origin_chat_id == "chat-1"
assert job.payload.origin_metadata == {}
assert job.payload.channel is None
assert job.payload.to is None
@@ -392,8 +395,8 @@ def test_add_job_empty_message_returns_actionable_error(tmp_path) -> None:
assert "Retry including message=" in result
def test_add_job_captures_only_session_key(tmp_path) -> None:
"""CronTool stores the canonical session key without legacy delivery fields."""
def test_add_job_captures_owner_and_origin_without_legacy_delivery_fields(tmp_path) -> None:
"""CronTool stores owner/session identity separately from origin delivery context."""
tool = _make_tool(tmp_path)
meta = {"slack": {"thread_ts": "111.222", "channel_type": "channel"}}
tool.set_context(RequestContext(
@@ -406,6 +409,9 @@ def test_add_job_captures_only_session_key(tmp_path) -> None:
jobs = tool._cron.list_jobs()
assert len(jobs) == 1
assert jobs[0].payload.session_key == "slack:C99:111.222"
assert jobs[0].payload.origin_channel == "slack"
assert jobs[0].payload.origin_chat_id == "C99"
assert jobs[0].payload.origin_metadata == meta
assert jobs[0].payload.channel is None
assert jobs[0].payload.to is None
assert jobs[0].payload.channel_meta == {}
+37 -38
View File
@@ -1,45 +1,44 @@
import pytest
from nanobot.cron.session_delivery import bound_session_inbound_context
from nanobot.cron.session_delivery import origin_delivery_context
from nanobot.cron.types import CronJob, CronPayload
@pytest.mark.parametrize(
("session_key", "expected"),
[
("websocket:chat-1", ("websocket", "chat-1", {})),
(
"discord:456:thread:777",
(
"discord",
"777",
{
"context_chat_id": "456",
"parent_channel_id": "456",
"thread_id": "777",
},
),
def test_origin_delivery_context_uses_explicit_origin_fields() -> None:
metadata = {
"context_chat_id": "456",
"parent_channel_id": "456",
"thread_id": "777",
}
job = CronJob(
id="thread-check",
name="Thread check",
payload=CronPayload(
message="check",
session_key="discord:456:thread:777",
origin_channel="discord",
origin_chat_id="777",
origin_metadata=metadata,
),
(
"feishu:oc_abc:om_root123",
(
"feishu",
"oc_abc",
{
"chat_type": "group",
"message_id": "om_root123",
"thread_id": "om_root123",
},
),
)
channel, chat_id, returned_metadata = origin_delivery_context(job)
assert channel == "discord"
assert chat_id == "777"
assert returned_metadata == metadata
assert returned_metadata is not metadata
def test_origin_delivery_context_rejects_missing_origin_fields() -> None:
job = CronJob(
id="old-bound",
name="Old bound job",
payload=CronPayload(
message="check",
session_key="websocket:chat-1",
),
("slack:C123:1700.42", ("slack", "C123", {"slack": {"thread_ts": "1700.42"}})),
("telegram:-100123:topic:42", ("telegram", "-100123", {"message_thread_id": 42})),
("dingtalk:group:conv-1:user-1", ("dingtalk", "group:conv-1", {})),
],
)
def test_bound_session_inbound_context(session_key, expected) -> None:
assert bound_session_inbound_context(session_key) == expected
)
def test_bound_session_inbound_context_rejects_invalid_key() -> None:
with pytest.raises(ValueError):
bound_session_inbound_context("unified")
with pytest.raises(ValueError, match="missing origin delivery context"):
origin_delivery_context(job)
+12
View File
@@ -123,6 +123,10 @@ async def test_cron_tool_keeps_task_local_context(tmp_path) -> None:
jobs = tool._cron.list_jobs()
assert {job.payload.session_key for job in jobs} == {"feishu:chat-a", "email:chat-b"}
assert {(job.payload.origin_channel, job.payload.origin_chat_id) for job in jobs} == {
("feishu", "chat-a"),
("email", "chat-b"),
}
# --- Basic single-task regression tests ---
@@ -243,6 +247,8 @@ async def test_cron_tool_basic_set_context_and_execute(tmp_path) -> None:
jobs = tool._cron.list_jobs()
assert len(jobs) == 1
assert jobs[0].payload.session_key == "wechat:user-789"
assert jobs[0].payload.origin_channel == "wechat"
assert jobs[0].payload.origin_chat_id == "user-789"
@pytest.mark.asyncio
@@ -272,6 +278,9 @@ async def test_webui_cron_tool_uses_origin_session_when_unified_enabled(tmp_path
jobs = tool._cron.list_jobs()
assert len(jobs) == 1
assert jobs[0].payload.session_key == "websocket:chat-123"
assert jobs[0].payload.origin_channel == "websocket"
assert jobs[0].payload.origin_chat_id == "chat-123"
assert jobs[0].payload.origin_metadata == {"webui": True}
@pytest.mark.asyncio
@@ -293,6 +302,9 @@ async def test_cron_tool_preserves_thread_scoped_session_key(tmp_path) -> None:
jobs = tool._cron.list_jobs()
assert len(jobs) == 1
assert jobs[0].payload.session_key == "slack:C123:1700.42"
assert jobs[0].payload.origin_channel == "slack"
assert jobs[0].payload.origin_chat_id == "C123"
assert jobs[0].payload.origin_metadata == {"slack": {"thread_ts": "1700.42"}}
@pytest.mark.asyncio