refactor: preserve origin session routing for cron
This commit is contained in:
@@ -30,7 +30,6 @@ def _make_handler(
|
||||
workspace_path: Path | None = None,
|
||||
runtime_model_name: Any | None = None,
|
||||
cron_service: CronService | None = None,
|
||||
unified_session: bool = False,
|
||||
) -> GatewayServices:
|
||||
config = WebSocketConfig.model_validate(cfg) if isinstance(cfg, dict) else cfg
|
||||
workspace = workspace_path or Path.cwd()
|
||||
@@ -44,7 +43,6 @@ def _make_handler(
|
||||
runtime_model_name=runtime_model_name,
|
||||
runtime_surface="browser",
|
||||
runtime_capabilities_overrides=None,
|
||||
unified_session=unified_session,
|
||||
cron_service=cron_service,
|
||||
)
|
||||
|
||||
@@ -58,7 +56,6 @@ def _ch(
|
||||
port: int = _PORT,
|
||||
runtime_model_name: Any | None = None,
|
||||
cron_service: CronService | None = None,
|
||||
unified_session: bool = False,
|
||||
**extra: Any,
|
||||
) -> WebSocketChannel:
|
||||
cfg: dict[str, Any] = {
|
||||
@@ -77,7 +74,6 @@ def _ch(
|
||||
workspace_path=workspace_path,
|
||||
runtime_model_name=runtime_model_name,
|
||||
cron_service=cron_service,
|
||||
unified_session=unified_session,
|
||||
)
|
||||
return WebSocketChannel(cfg, bus, gateway=gateway)
|
||||
|
||||
@@ -243,7 +239,7 @@ async def test_session_automations_route_filters_by_webui_session(
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_session_automations_route_uses_origin_owner_when_unified_enabled(
|
||||
async def test_session_automations_route_ignores_unified_owner(
|
||||
bus: MagicMock, tmp_path: Path
|
||||
) -> None:
|
||||
cron = CronService(tmp_path / "cron" / "jobs.json")
|
||||
@@ -264,7 +260,6 @@ async def test_session_automations_route_uses_origin_owner_when_unified_enabled(
|
||||
bus,
|
||||
session_manager=_seed_session(tmp_path, key="websocket:abc"),
|
||||
cron_service=cron,
|
||||
unified_session=True,
|
||||
port=29917,
|
||||
)
|
||||
server_task = asyncio.create_task(channel.start())
|
||||
@@ -823,7 +818,6 @@ async def test_session_delete_blocks_origin_automation_when_unified_enabled(
|
||||
bus,
|
||||
session_manager=sm,
|
||||
cron_service=cron,
|
||||
unified_session=True,
|
||||
port=29918,
|
||||
)
|
||||
server_task = asyncio.create_task(channel.start())
|
||||
|
||||
@@ -1622,7 +1622,7 @@ def test_gateway_bound_cron_runs_as_session_turn(
|
||||
assert msg.channel == "websocket"
|
||||
assert msg.chat_id == "chat-1"
|
||||
assert msg.sender_id == "cron"
|
||||
assert msg.session_key_override is None
|
||||
assert msg.session_key_override == "websocket:chat-1"
|
||||
assert "Cron job: Check repository health." in msg.content
|
||||
assert msg.metadata["webui"] is True
|
||||
assert msg.metadata[WEBUI_MESSAGE_SOURCE_METADATA_KEY] == {
|
||||
@@ -1645,7 +1645,7 @@ def test_gateway_bound_cron_runs_as_session_turn(
|
||||
name="Thread check",
|
||||
payload=CronPayload(
|
||||
message="Check the Discord thread.",
|
||||
session_key="discord:777",
|
||||
session_key="discord:456:thread:777",
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1656,7 +1656,49 @@ def test_gateway_bound_cron_runs_as_session_turn(
|
||||
assert isinstance(msg, InboundMessage)
|
||||
assert msg.channel == "discord"
|
||||
assert msg.chat_id == "777"
|
||||
assert msg.session_key_override is None
|
||||
assert msg.session_key_override == "discord:456:thread:777"
|
||||
assert msg.metadata["context_chat_id"] == "456"
|
||||
assert msg.metadata["parent_channel_id"] == "456"
|
||||
assert msg.metadata["thread_id"] == "777"
|
||||
|
||||
telegram_job = CronJob(
|
||||
id="telegram-topic",
|
||||
name="Telegram topic",
|
||||
payload=CronPayload(
|
||||
message="Check the Telegram topic.",
|
||||
session_key="telegram:-100123:topic:42",
|
||||
),
|
||||
)
|
||||
|
||||
response = asyncio.run(cron.on_job(telegram_job))
|
||||
|
||||
assert response == "Checked the repo."
|
||||
msg = seen["cron_msg"]
|
||||
assert isinstance(msg, InboundMessage)
|
||||
assert msg.channel == "telegram"
|
||||
assert msg.chat_id == "-100123"
|
||||
assert msg.session_key_override == "telegram:-100123:topic:42"
|
||||
assert msg.metadata["message_thread_id"] == 42
|
||||
|
||||
feishu_job = CronJob(
|
||||
id="feishu-topic",
|
||||
name="Feishu topic",
|
||||
payload=CronPayload(
|
||||
message="Check the Feishu topic.",
|
||||
session_key="feishu:oc_abc:om_root123",
|
||||
),
|
||||
)
|
||||
|
||||
response = asyncio.run(cron.on_job(feishu_job))
|
||||
|
||||
assert response == "Checked the repo."
|
||||
msg = seen["cron_msg"]
|
||||
assert isinstance(msg, InboundMessage)
|
||||
assert msg.channel == "feishu"
|
||||
assert msg.chat_id == "oc_abc"
|
||||
assert msg.session_key_override == "feishu:oc_abc:om_root123"
|
||||
assert msg.metadata["message_id"] == "om_root123"
|
||||
assert msg.metadata["thread_id"] == "om_root123"
|
||||
|
||||
|
||||
def test_gateway_cron_job_suppresses_intermediate_progress(
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import pytest
|
||||
|
||||
from nanobot.cron.session_delivery import bound_session_inbound_context
|
||||
|
||||
|
||||
@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",
|
||||
},
|
||||
),
|
||||
),
|
||||
(
|
||||
"feishu:oc_abc:om_root123",
|
||||
(
|
||||
"feishu",
|
||||
"oc_abc",
|
||||
{
|
||||
"chat_type": "group",
|
||||
"message_id": "om_root123",
|
||||
"thread_id": "om_root123",
|
||||
},
|
||||
),
|
||||
),
|
||||
("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")
|
||||
@@ -274,6 +274,27 @@ async def test_webui_cron_tool_uses_origin_session_when_unified_enabled(tmp_path
|
||||
assert jobs[0].payload.session_key == "websocket:chat-123"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cron_tool_preserves_thread_scoped_session_key(tmp_path) -> None:
|
||||
"""Channel-provided thread session keys should remain the cron owner."""
|
||||
tool = CronTool(CronService(tmp_path / "jobs.json"))
|
||||
tool.set_context(
|
||||
RequestContext(
|
||||
channel="slack",
|
||||
chat_id="C123",
|
||||
metadata={"slack": {"thread_ts": "1700.42"}},
|
||||
session_key="slack:C123:1700.42",
|
||||
)
|
||||
)
|
||||
|
||||
result = await tool.execute(action="add", message="check thread", every_seconds=300)
|
||||
assert result.startswith("Created job")
|
||||
|
||||
jobs = tool._cron.list_jobs()
|
||||
assert len(jobs) == 1
|
||||
assert jobs[0].payload.session_key == "slack:C123:1700.42"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cron_tool_no_context_returns_error(tmp_path) -> None:
|
||||
"""Without set_context, add should fail with a clear error."""
|
||||
|
||||
Reference in New Issue
Block a user