refactor(schemas): 统一 message/notification 命名边界,旧名收敛至兼容映射表

- notification 域:渠道能力(MessageChannel→NotificationChannel、ChannelCapability* 迁入 notification.py)
- message 域:消息收发(Notification→Message、NotificationType→MessageType、CommingMessage→IncomingMessage、NotificationHistoryItem→MessageHistoryItem、NotificationClear*→MessageClear*)
- Agent 工具契约:send_notification_message→send_message、notification_callback→message_callback
- 源码不保留旧名物理别名,旧导入经 app/runtime/compat/manifest.py SYMBOL_ALIASES 惰性解析
- API 路径与持久化键冻结不变,前端零改动
- 新增兼容守护测试与 docs/rules/07 命名边界规范
This commit is contained in:
jxxghp
2026-08-16 19:32:20 +08:00
parent 98276a68a8
commit 240a4dffe6
96 changed files with 1975 additions and 1751 deletions
+4 -4
View File
@@ -3,7 +3,7 @@ import unittest
from unittest.mock import AsyncMock, patch
from app.agent.tools.impl.add_subscribe import AddSubscribeTool
from app.schemas.types import MessageChannel
from app.schemas.types import NotificationChannel
class TestAgentAddSubscribeTool(unittest.TestCase):
@@ -19,7 +19,7 @@ class TestAgentAddSubscribeTool(unittest.TestCase):
def test_tv_subscription_without_season_reports_default_first_season(self):
tool = AddSubscribeTool(session_id="session-1", user_id="10001")
tool.set_message_attr(
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-main",
username="tg_display_name",
)
@@ -46,7 +46,7 @@ class TestAgentAddSubscribeTool(unittest.TestCase):
def test_subscription_falls_back_to_channel_username_when_no_binding_exists(self):
tool = AddSubscribeTool(session_id="session-1", user_id="10001")
tool.set_message_attr(
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-main",
username="tg_display_name",
)
@@ -72,7 +72,7 @@ class TestAgentAddSubscribeTool(unittest.TestCase):
def test_feishu_subscription_uses_pre_resolved_username_when_openid_lookup_misses(self):
tool = AddSubscribeTool(session_id="session-1", user_id="ou_feishu_user")
tool.set_message_attr(
channel=MessageChannel.Feishu.value,
channel=NotificationChannel.Feishu.value,
source="feishu-main",
username="moviepilot-user",
)
+10 -10
View File
@@ -15,7 +15,7 @@ from app.modules.vocechat import VoceChatModule
from app.modules.wechat import WechatModule
from app.modules.wechat.wechatbot import WeChatBot
from app.modules.wechatclawbot import WechatClawBotModule
from app.schemas.types import MessageChannel
from app.schemas.types import NotificationChannel
def _parse_module_message(module, *, config: dict, body, client=None, form=None):
@@ -52,19 +52,19 @@ def test_resolve_config_principal_ids_uses_nonempty_stable_values(config, expect
("channel", "config", "principal_ids", "expected"),
[
(
MessageChannel.Telegram,
NotificationChannel.Telegram,
{"TELEGRAM_ADMINS": "other", "TELEGRAM_CHAT_ID": "10001"},
(10001,),
True,
),
(
MessageChannel.Feishu,
NotificationChannel.Feishu,
{"FEISHU_ADMINS": "other", "FEISHU_OPEN_ID": "ou_owner"},
("ou_owner",),
True,
),
(
MessageChannel.Wechat,
NotificationChannel.Wechat,
{
"WECHAT_MODE": "bot",
"WECHAT_ADMINS": "other",
@@ -74,7 +74,7 @@ def test_resolve_config_principal_ids_uses_nonempty_stable_values(config, expect
True,
),
(
MessageChannel.Wechat,
NotificationChannel.Wechat,
{
"WECHAT_MODE": "app",
"WECHAT_ADMINS": "other",
@@ -84,7 +84,7 @@ def test_resolve_config_principal_ids_uses_nonempty_stable_values(config, expect
False,
),
(
MessageChannel.WechatClawBot,
NotificationChannel.WechatClawBot,
{
"WECHATCLAWBOT_ADMINS": "other",
"WECHATCLAWBOT_DEFAULT_TARGET": "wxid_owner",
@@ -93,25 +93,25 @@ def test_resolve_config_principal_ids_uses_nonempty_stable_values(config, expect
True,
),
(
MessageChannel.QQ,
NotificationChannel.QQ,
{"QQBOT_ADMINS": "other", "QQ_OPENID": "qq_owner"},
("qq_owner",),
True,
),
(
MessageChannel.Telegram,
NotificationChannel.Telegram,
{"TELEGRAM_ADMINS": "other", "TELEGRAM_CHAT_ID": "-10001"},
(10001,),
False,
),
(
MessageChannel.Feishu,
NotificationChannel.Feishu,
{"FEISHU_ADMINS": "other", "FEISHU_CHAT_ID": "oc_group"},
("ou_user",),
False,
),
(
MessageChannel.QQ,
NotificationChannel.QQ,
{"QQBOT_ADMINS": "other", "QQ_GROUP_OPENID": "qq_group"},
("qq_member",),
False,
+2 -2
View File
@@ -4,7 +4,7 @@ from app.agent import MoviePilotAgent
from app.agent.llm import AgentCapabilityManager, LLMHelper
from app.chain.message import MessageChain
from app.runtime.config import settings
from app.schemas.types import MessageChannel
from app.schemas.types import NotificationChannel
def test_llm_supports_image_input_uses_model_catalog_text_only(monkeypatch):
@@ -76,7 +76,7 @@ def test_handle_ai_message_routes_text_only_model_images_to_files(monkeypatch):
):
chain._handle_ai_message(
text="/ai 帮我看看这张图",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
+36 -36
View File
@@ -27,8 +27,8 @@ from app.modules.synologychat import SynologyChatModule
from app.modules.vocechat import VoceChatModule
from app.modules.wechat import WechatModule
from app.modules.wechat.wechatbot import WeChatBot
from app.schemas import CommingMessage, Notification
from app.schemas.types import MessageChannel, NotificationType
from app.schemas import IncomingMessage, Message
from app.schemas.types import NotificationChannel, MessageType
class AgentImageSupportTest(unittest.TestCase):
@@ -135,8 +135,8 @@ class AgentImageSupportTest(unittest.TestCase):
def test_process_allows_image_only_message(self):
chain = MessageChain()
message = CommingMessage(
channel=MessageChannel.Telegram,
message = IncomingMessage(
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -154,8 +154,8 @@ class AgentImageSupportTest(unittest.TestCase):
def test_process_allows_audio_only_message(self):
chain = MessageChain()
message = CommingMessage(
channel=MessageChannel.Telegram,
message = IncomingMessage(
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -173,13 +173,13 @@ class AgentImageSupportTest(unittest.TestCase):
def test_process_allows_file_only_message(self):
chain = MessageChain()
message = CommingMessage(
channel=MessageChannel.Telegram,
message = IncomingMessage(
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
files=[
CommingMessage.MessageAttachment(
IncomingMessage.MessageAttachment(
ref="tg://document_file_id/doc-1",
name="note.txt",
mime_type="text/plain",
@@ -210,7 +210,7 @@ class AgentImageSupportTest(unittest.TestCase):
settings, "AI_AGENT_GLOBAL", False
):
chain.handle_message(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -232,7 +232,7 @@ class AgentImageSupportTest(unittest.TestCase):
settings, "AI_AGENT_GLOBAL", False
):
chain.handle_message(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -258,13 +258,13 @@ class AgentImageSupportTest(unittest.TestCase):
settings, "AI_AGENT_GLOBAL", False
):
chain.handle_message(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
text="",
files=[
CommingMessage.MessageAttachment(
IncomingMessage.MessageAttachment(
ref="tg://document_file_id/doc-1",
name="report.txt",
mime_type="text/plain",
@@ -306,7 +306,7 @@ class AgentImageSupportTest(unittest.TestCase):
) as transcribe_bytes:
result = chain._transcribe_audio_refs(
audio_refs=audio_refs,
channel=MessageChannel.Slack,
channel=NotificationChannel.Slack,
source="mixed-source",
)
@@ -341,7 +341,7 @@ class AgentImageSupportTest(unittest.TestCase):
agent = MoviePilotAgent(
session_id="session-1",
user_id="user-1",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
username="tester",
)
@@ -362,7 +362,7 @@ class AgentImageSupportTest(unittest.TestCase):
agent = MoviePilotAgent(
session_id="session-1",
user_id="user-1",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
username="tester",
)
@@ -400,7 +400,7 @@ class AgentImageSupportTest(unittest.TestCase):
agent = MoviePilotAgent(
session_id="session-1",
user_id="user-1",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
username="tester",
)
@@ -458,7 +458,7 @@ class AgentImageSupportTest(unittest.TestCase):
) as run_coroutine_threadsafe:
chain._handle_ai_message(
text="/ai 帮我看看这张图",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -491,7 +491,7 @@ class AgentImageSupportTest(unittest.TestCase):
):
chain._handle_ai_message(
text="帮我推荐一部电影",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -510,7 +510,7 @@ class AgentImageSupportTest(unittest.TestCase):
) as run_module:
images = chain._download_attachments_to_data_urls(
attachments=["https://files.slack.com/files-pri/T1-F1/test.png"],
channel=MessageChannel.Slack,
channel=NotificationChannel.Slack,
source="slack-test",
)
@@ -574,7 +574,7 @@ class AgentImageSupportTest(unittest.TestCase):
async def _run():
tool = SendMessageTool(session_id="session-1", user_id="10001")
tool.set_message_attr(
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
username="tester",
)
@@ -594,8 +594,8 @@ class AgentImageSupportTest(unittest.TestCase):
notification = async_post_message.await_args.args[0]
self.assertEqual(result, "消息已发送")
self.assertEqual(notification.mtype, NotificationType.Other)
self.assertEqual(notification.channel, MessageChannel.Telegram)
self.assertEqual(notification.mtype, MessageType.Other)
self.assertEqual(notification.channel, NotificationChannel.Telegram)
self.assertEqual(notification.source, "telegram-test")
self.assertEqual(notification.title, "智能体通知")
self.assertEqual(notification.text, "处理完成")
@@ -608,7 +608,7 @@ class AgentImageSupportTest(unittest.TestCase):
async def _run():
tool = SendMessageTool(session_id="session-1", user_id="10001")
tool.set_message_attr(
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
username="tester",
)
@@ -638,7 +638,7 @@ class AgentImageSupportTest(unittest.TestCase):
agent_context = {}
tool.set_agent_context(agent_context)
tool.set_message_attr(
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
username="tester",
)
@@ -738,7 +738,7 @@ class AgentImageSupportTest(unittest.TestCase):
module, "get_instance", return_value=client
):
response = module.send_direct_message(
Notification(title="hi", userid="user-1")
Message(title="hi", userid="user-1")
)
self.assertIsNotNone(response)
@@ -755,7 +755,7 @@ class AgentImageSupportTest(unittest.TestCase):
) as run_module:
images = chain._download_attachments_to_data_urls(
attachments=["wxwork://media_id/media-1"],
channel=MessageChannel.Wechat,
channel=NotificationChannel.Wechat,
source="wechat-test",
)
@@ -776,12 +776,12 @@ class AgentImageSupportTest(unittest.TestCase):
) as run_module:
data_urls = chain._download_attachments_to_data_urls(
attachments=[
CommingMessage.MessageImage(
IncomingMessage.MessageImage(
ref="feishu://image/img_v2_xxx",
mime_type="image/png",
)
],
channel=MessageChannel.Feishu,
channel=NotificationChannel.Feishu,
source="feishu-test",
)
@@ -798,7 +798,7 @@ class AgentImageSupportTest(unittest.TestCase):
with patch.object(chain, "run_module", return_value=b"feishu-file") as run_module:
content = chain._download_message_file_bytes(
file_ref="feishu://file/file_xxx/report.pdf",
channel=MessageChannel.Feishu,
channel=NotificationChannel.Feishu,
source="feishu-test",
)
@@ -1064,7 +1064,7 @@ class AgentImageSupportTest(unittest.TestCase):
module, "get_instance", return_value=client
):
module.post_message(
Notification(
Message(
title="poster",
image="https://example.com/poster.png",
targets={"vocechat_userid": "UID#100"},
@@ -1097,7 +1097,7 @@ class AgentImageSupportTest(unittest.TestCase):
module, "get_instance", return_value=client
):
module.post_message(
Notification(
Message(
title="手册",
text="请下载",
file_path=str(file_path),
@@ -1132,7 +1132,7 @@ class AgentImageSupportTest(unittest.TestCase):
module, "get_instance", return_value=client
):
module.post_message(
Notification(
Message(
title="手册",
text="请下载",
file_path=str(file_path),
@@ -1336,13 +1336,13 @@ class AgentImageSupportTest(unittest.TestCase):
prepared = chain._prepare_agent_files(
session_id="session-1",
files=[
CommingMessage.MessageAttachment(
IncomingMessage.MessageAttachment(
ref="tg://document_file_id/doc-1",
name="note.txt",
mime_type="text/plain",
)
],
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
)
@@ -1367,7 +1367,7 @@ class AgentImageSupportTest(unittest.TestCase):
module, "get_instance", return_value=client
):
module.post_message(
Notification(
Message(
title="报告",
text="请下载",
file_path=str(file_path),
+18 -18
View File
@@ -17,7 +17,7 @@ from app.application.messaging.agent import (
from app.application.messaging.interaction import InteractionContext
from app.chain.message import MessageChain
from app.runtime.config import settings
from app.schemas.types import MessageChannel
from app.schemas.types import NotificationChannel
class TestAgentInteraction(unittest.TestCase):
@@ -26,13 +26,13 @@ class TestAgentInteraction(unittest.TestCase):
def test_prompt_injects_choice_tool_hint_only_for_button_channels(self):
telegram_prompt = prompt_manager.get_agent_prompt(
channel=MessageChannel.Telegram.value
channel=NotificationChannel.Telegram.value
)
web_agent_prompt = prompt_manager.get_agent_prompt(
channel=MessageChannel.WebAgent.value
channel=NotificationChannel.WebAgent.value
)
wechat_prompt = prompt_manager.get_agent_prompt(
channel=MessageChannel.Wechat.value
channel=NotificationChannel.Wechat.value
)
self.assertIn("ask_user_choice", telegram_prompt)
@@ -43,10 +43,10 @@ class TestAgentInteraction(unittest.TestCase):
def test_prompt_does_not_inject_send_message_html_hint(self):
telegram_prompt = prompt_manager.get_agent_prompt(
channel=MessageChannel.Telegram.value
channel=NotificationChannel.Telegram.value
)
wechat_prompt = prompt_manager.get_agent_prompt(
channel=MessageChannel.Wechat.value
channel=NotificationChannel.Wechat.value
)
self.assertNotIn("parse_mode=\"HTML\"", telegram_prompt)
@@ -61,21 +61,21 @@ class TestAgentInteraction(unittest.TestCase):
telegram_tools = MoviePilotToolFactory.create_tools(
session_id="session-1",
user_id="10001",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
username="tester",
)
web_agent_tools = MoviePilotToolFactory.create_tools(
session_id="session-web",
user_id="10001",
channel=MessageChannel.WebAgent.value,
channel=NotificationChannel.WebAgent.value,
source="web-agent",
username="tester",
)
wechat_tools = MoviePilotToolFactory.create_tools(
session_id="session-2",
user_id="10001",
channel=MessageChannel.Wechat.value,
channel=NotificationChannel.Wechat.value,
source="wechat-test",
username="tester",
)
@@ -101,7 +101,7 @@ class TestAgentInteraction(unittest.TestCase):
def test_choice_tool_sends_buttons_and_registers_pending_request(self):
tool = AskUserChoiceTool(session_id="session-1", user_id="10001")
tool.set_message_attr(
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
username="tester",
)
@@ -141,7 +141,7 @@ class TestAgentInteraction(unittest.TestCase):
def test_choice_tool_blocks_after_feedback_quality_rejection(self):
tool = AskUserChoiceTool(session_id="session-feedback", user_id="10001")
tool.set_message_attr(
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
username="tester",
)
@@ -177,7 +177,7 @@ class TestAgentInteraction(unittest.TestCase):
request = agent_interaction_manager.create_request(
session_id="session-choice",
user_id="10001",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
username="tester",
title="需要你的选择",
@@ -204,7 +204,7 @@ class TestAgentInteraction(unittest.TestCase):
handled = chain._handle_callback(
callback_data=f"agent_interaction:choice:{request.request_id}:1",
context=InteractionContext(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
user_id="10001",
username="tester",
@@ -215,7 +215,7 @@ class TestAgentInteraction(unittest.TestCase):
self.assertTrue(handled)
edit_message.assert_called_once_with(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
message_id=123,
chat_id="456",
@@ -226,7 +226,7 @@ class TestAgentInteraction(unittest.TestCase):
kwargs = process_message.call_args.kwargs
self.assertEqual(kwargs["message"], "我选择电影")
self.assertEqual(kwargs["session_id"], "session-choice")
self.assertEqual(kwargs["channel"], MessageChannel.Telegram.value)
self.assertEqual(kwargs["channel"], NotificationChannel.Telegram.value)
self.assertEqual(kwargs["source"], "telegram-test")
self.assertNotIn("processing_status", kwargs)
message_put.assert_not_called()
@@ -237,7 +237,7 @@ class TestAgentInteraction(unittest.TestCase):
request = agent_interaction_manager.create_request(
session_id="session-choice",
user_id="10001",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
username="tester",
title=None,
@@ -251,7 +251,7 @@ class TestAgentInteraction(unittest.TestCase):
chain._handle_callback(
callback_data=f"agent_choice:{request.request_id}:1",
context=InteractionContext(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
user_id="10001",
username="tester",
@@ -266,7 +266,7 @@ class TestAgentInteraction(unittest.TestCase):
MessageChain._user_sessions["10001"] = ("session-secret", datetime.now())
try:
for channel in (MessageChannel.Telegram, MessageChannel.Feishu):
for channel in (NotificationChannel.Telegram, NotificationChannel.Feishu):
with patch(
"app.chain.message.agent_manager.matches_secret_confirmation",
return_value=True,
+13 -13
View File
@@ -6,8 +6,8 @@ from types import SimpleNamespace
from unittest.mock import Mock, patch
from app.runtime.config import settings
from app.schemas.message import ChannelCapability, ChannelCapabilityManager
from app.schemas.types import MessageChannel
from app.schemas.notification import ChannelCapability, ChannelCapabilityManager
from app.schemas.types import NotificationChannel
from app.agent.llm import capability as capability_module
from app.agent.llm.capability import (
@@ -171,12 +171,12 @@ class AgentCapabilityManagerTest(unittest.TestCase):
)
self.assertTrue(
AgentCapabilityManager.supports_native_voice_reply(
MessageChannel.Telegram.value, None
NotificationChannel.Telegram.value, None
)
)
self.assertTrue(
AgentCapabilityManager.supports_native_voice_reply(
MessageChannel.Feishu.value, None
NotificationChannel.Feishu.value, None
)
)
self.assertTrue(
@@ -184,7 +184,7 @@ class AgentCapabilityManagerTest(unittest.TestCase):
)
self.assertTrue(
AgentCapabilityManager.supports_native_voice_reply(
MessageChannel.WebAgent.value, None
NotificationChannel.WebAgent.value, None
)
)
self.assertFalse(
@@ -204,27 +204,27 @@ class AgentCapabilityManagerTest(unittest.TestCase):
):
self.assertTrue(
AgentCapabilityManager.supports_native_voice_reply(
MessageChannel.Wechat.value, "wechat-app"
NotificationChannel.Wechat.value, "wechat-app"
)
)
self.assertFalse(
AgentCapabilityManager.supports_native_voice_reply(
MessageChannel.Wechat.value, "wechat-bot"
NotificationChannel.Wechat.value, "wechat-bot"
)
)
self.assertFalse(
AgentCapabilityManager.supports_native_voice_reply(
MessageChannel.Wechat.value, "missing"
NotificationChannel.Wechat.value, "missing"
)
)
def test_channel_capability_marks_voice_output_channels(self):
"""校验消息渠道能力显式声明原生语音输出支持。"""
for channel in (
MessageChannel.Telegram,
MessageChannel.Feishu,
MessageChannel.Wechat,
MessageChannel.WebAgent,
NotificationChannel.Telegram,
NotificationChannel.Feishu,
NotificationChannel.Wechat,
NotificationChannel.WebAgent,
):
self.assertTrue(
ChannelCapabilityManager.supports_capability(
@@ -233,7 +233,7 @@ class AgentCapabilityManagerTest(unittest.TestCase):
)
self.assertFalse(
ChannelCapabilityManager.supports_capability(
MessageChannel.Slack, ChannelCapability.AUDIO_OUTPUT
NotificationChannel.Slack, ChannelCapability.AUDIO_OUTPUT
)
)
+13 -13
View File
@@ -15,7 +15,7 @@ from app.db.models.message import Message
from app.application.messaging.agent import AgentInteractionOption, agent_interaction_manager
from app.application.messaging.interaction import InteractionContext
from app.application.messaging.media import media_interaction_manager
from app.schemas.types import MessageChannel, NotificationType
from app.schemas.types import NotificationChannel, MessageType
def _clear_messages() -> None:
@@ -31,7 +31,7 @@ def test_explicit_ai_message_bypasses_pending_media_interaction():
media_interaction_manager.clear()
media_interaction_manager.create_or_replace(
user_id="10001",
channel=MessageChannel.Wechat,
channel=NotificationChannel.Wechat,
source="wechat-test",
username="tester",
action="Search",
@@ -47,7 +47,7 @@ def test_explicit_ai_message_bypasses_pending_media_interaction():
chain, "_handle_ai_message", return_value=True
) as handle_ai_message:
chain.handle_message(
channel=MessageChannel.Wechat,
channel=NotificationChannel.Wechat,
source="wechat-test",
userid="10001",
username="tester",
@@ -74,7 +74,7 @@ def test_explicit_ai_message_is_not_recorded_to_message_history():
side_effect=lambda coro, _loop: (coro.close(), Mock())[1],
):
chain.handle_message(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -97,7 +97,7 @@ def test_message_chain_passes_stable_channel_admin_principal_to_agent():
side_effect=lambda coro, _loop: (coro.close(), Mock())[1],
):
chain.handle_message(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="renamed-user",
@@ -120,7 +120,7 @@ def test_message_chain_does_not_trust_channel_display_username():
side_effect=lambda coro, _loop: (coro.close(), Mock())[1],
):
chain.handle_message(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10002",
username="admin",
@@ -143,7 +143,7 @@ def test_message_chain_uses_same_admin_contract_for_slack():
side_effect=lambda coro, _loop: (coro.close(), Mock())[1],
):
chain.handle_message(
channel=MessageChannel.Slack,
channel=NotificationChannel.Slack,
source="slack-test",
userid="UADMIN",
username="renamed-user",
@@ -159,7 +159,7 @@ def test_ask_user_choice_message_is_not_recorded_to_message_history():
_clear_messages()
tool = AskUserChoiceTool(session_id="session-choice", user_id="10001")
tool.set_message_attr(
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
username="tester",
)
@@ -198,7 +198,7 @@ def test_agent_final_reply_disables_notification_history():
agent = MoviePilotAgent(
session_id="session-agent-reply",
user_id="10001",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
username="tester",
)
@@ -210,7 +210,7 @@ def test_agent_final_reply_disables_notification_history():
asyncio.run(agent.send_agent_message("已完成处理"))
notification = async_post_message.await_args.args[0]
assert notification.mtype == NotificationType.Agent
assert notification.mtype == MessageType.Agent
assert notification.save_history is False
@@ -218,7 +218,7 @@ def test_send_message_tool_disables_notification_history():
"""Agent 主动发消息工具发送的通知不保存通知历史。"""
tool = SendMessageTool(session_id="session-send-message", user_id="10001")
tool.set_message_attr(
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
username="tester",
)
@@ -242,7 +242,7 @@ def test_agent_choice_callback_is_not_recorded_to_message_history():
request = agent_interaction_manager.create_request(
session_id="session-choice",
user_id="10001",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
username="tester",
title="需要你的选择",
@@ -268,7 +268,7 @@ def test_agent_choice_callback_is_not_recorded_to_message_history():
chain._handle_callback(
callback_data=f"agent_interaction:choice:{request.request_id}:1",
context=InteractionContext(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
user_id="10001",
username="tester",
+3 -3
View File
@@ -2,18 +2,18 @@ from unittest.mock import patch
from app.agent.prompt import prompt_manager
from app.runtime.config import settings
from app.schemas.types import MessageChannel
from app.schemas.types import NotificationChannel
def test_progress_prompt_is_independent_from_tool_display_mode() -> None:
"""进度沟通规则不应随工具逐条或汇总展示模式变化。"""
with patch.object(settings, "AI_AGENT_VERBOSE", False):
summary_mode_prompt = prompt_manager.get_agent_prompt(
channel=MessageChannel.WebAgent.value
channel=NotificationChannel.WebAgent.value
)
with patch.object(settings, "AI_AGENT_VERBOSE", True):
verbose_mode_prompt = prompt_manager.get_agent_prompt(
channel=MessageChannel.WebAgent.value
channel=NotificationChannel.WebAgent.value
)
assert summary_mode_prompt == verbose_mode_prompt
@@ -17,7 +17,7 @@ from app.agent import MoviePilotAgent
from app.runtime.config import settings
from app.modules.feishu import FeishuModule
from app.modules.telegram import TelegramModule
from app.schemas.types import MessageChannel
from app.schemas.types import NotificationChannel
# 渠道模块在导入时注册管理员解析器,权限回查测试需显式加载对应模块。
@@ -350,7 +350,7 @@ def test_channel_agent_admin_user_id_does_not_bypass_user_lookup():
agent = MoviePilotAgent(
session_id="session-1",
user_id="admin",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-main",
username="normal-user",
)
@@ -371,7 +371,7 @@ def test_channel_agent_rejects_local_admin_username_without_trusted_principal():
agent = MoviePilotAgent(
session_id="session-1",
user_id="10002",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-main",
username="admin",
)
@@ -394,7 +394,7 @@ def test_channel_agent_accepts_trusted_admin_principal_without_local_user():
agent = MoviePilotAgent(
session_id="session-1",
user_id="10001",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-main",
username="renamed-user",
)
@@ -413,7 +413,7 @@ def test_tool_explicit_non_admin_context_does_not_fallback_to_channel_lookup():
"""Agent 已判定为非管理员时,工具不得通过旧权限查询重新授权。"""
tool = QuerySitesTool(session_id="session-1", user_id="10002")
tool.set_message_attr(
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-main",
username="admin",
)
@@ -434,7 +434,7 @@ def test_channel_primary_id_defaults_to_admin_without_admin_list():
"""渠道主ID未配置到管理员名单时仍默认为管理员。"""
tool = QuerySitesTool(session_id="session-1", user_id="10001")
tool.set_message_attr(
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-main",
username="owner",
)
@@ -457,7 +457,7 @@ def test_channel_primary_id_mismatch_remains_non_admin():
"""非主ID用户且不在管理员名单时不能获得管理员权限。"""
tool = QuerySitesTool(session_id="session-1", user_id="10002")
tool.set_message_attr(
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-main",
username="other",
)
@@ -480,7 +480,7 @@ def test_feishu_primary_open_id_defaults_to_admin():
"""飞书渠道主ID使用默认接收人 OPEN_ID 判断管理员身份。"""
tool = QuerySitesTool(session_id="session-1", user_id="ou_owner")
tool.set_message_attr(
channel=MessageChannel.Feishu.value,
channel=NotificationChannel.Feishu.value,
source="feishu-main",
username="owner",
)
@@ -503,7 +503,7 @@ def test_channel_primary_id_still_prefers_admin_list():
"""管理员名单命中优先于主ID兜底。"""
tool = QuerySitesTool(session_id="session-1", user_id="10001")
tool.set_message_attr(
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-main",
username="owner",
)
+18 -18
View File
@@ -13,7 +13,7 @@ from app.agent import MoviePilotAgent, ReplyMode, agent_manager
from app.agent.middleware.policy import AgentPolicyMiddleware
from app.agent.policy import AuthSource, PrincipalType, ToolOrigin, ToolPolicyContext
from app.agent.tools.impl.query_system_settings import QuerySystemSettingsTool
from app.schemas.types import MessageChannel
from app.schemas.types import NotificationChannel
class _ToolCallingFakeModel(FakeMessagesListChatModel):
@@ -33,7 +33,7 @@ def _policy_context(agent_context: dict) -> ToolPolicyContext:
principal_type=PrincipalType.HUMAN,
auth_source=AuthSource.CHANNEL,
agent_context=agent_context,
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
)
@@ -175,7 +175,7 @@ def test_confirm_executes_once_without_model_or_history() -> None:
agent = MoviePilotAgent(
session_id="session-secret",
user_id="1",
channel=MessageChannel.WebAgent.value,
channel=NotificationChannel.WebAgent.value,
source="web-agent",
username="admin",
replay_mode=ReplyMode.CAPTURE_ONLY,
@@ -184,7 +184,7 @@ def test_confirm_executes_once_without_model_or_history() -> None:
)
tool = QuerySystemSettingsTool(session_id="session-secret", user_id="1")
tool.set_message_attr(
channel=MessageChannel.WebAgent.value,
channel=NotificationChannel.WebAgent.value,
source="web-agent",
username="admin",
)
@@ -229,7 +229,7 @@ def test_cancel_clears_pending_without_executing_tool() -> None:
agent = MoviePilotAgent(
session_id="session-secret",
user_id="1",
channel=MessageChannel.WebAgent.value,
channel=NotificationChannel.WebAgent.value,
source="web-agent",
username="admin",
replay_mode=ReplyMode.CAPTURE_ONLY,
@@ -266,7 +266,7 @@ def test_expired_confirmation_reaches_agent_expiry_receipt() -> None:
agent = MoviePilotAgent(
session_id="session-expired-secret",
user_id="1",
channel=MessageChannel.WebAgent.value,
channel=NotificationChannel.WebAgent.value,
source="web-agent",
username="admin",
replay_mode=ReplyMode.CAPTURE_ONLY,
@@ -287,7 +287,7 @@ def test_expired_confirmation_reaches_agent_expiry_receipt() -> None:
assert agent_manager.matches_secret_confirmation(
agent.session_id,
"1",
channel=MessageChannel.WebAgent.value,
channel=NotificationChannel.WebAgent.value,
source="web-agent",
)
return await agent.process("确认")
@@ -328,7 +328,7 @@ def test_message_channel_receives_confirmation_prompt_once() -> None:
agent = MoviePilotAgent(
session_id="session-secret",
user_id="1",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-main",
username="admin",
original_chat_id="chat-1",
@@ -362,7 +362,7 @@ def test_message_channel_does_not_register_pending_when_private_delivery_fails()
agent = MoviePilotAgent(
session_id="session-secret",
user_id="1",
channel=MessageChannel.Feishu.value,
channel=NotificationChannel.Feishu.value,
source="feishu-main",
username="admin",
original_chat_id="group-1",
@@ -394,7 +394,7 @@ def test_private_delivery_requests_literal_plain_text() -> None:
agent = MoviePilotAgent(
session_id="session-secret",
user_id="1",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-main",
username="admin",
original_chat_id="group-1",
@@ -423,7 +423,7 @@ def test_pending_secret_read_keeps_actor_and_action_across_chat_targets() -> Non
agent = MoviePilotAgent(
session_id="session-secret",
user_id="1",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-main",
username="admin",
original_chat_id="chat-1",
@@ -475,7 +475,7 @@ def test_confirm_reports_result_delivery_failure_without_secret() -> None:
agent = MoviePilotAgent(
session_id="session-secret",
user_id="1",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-main",
username="admin",
original_chat_id="group-1",
@@ -524,7 +524,7 @@ def test_web_protected_callback_failure_returns_ordinary_safe_notice() -> None:
agent = MoviePilotAgent(
session_id="session-secret",
user_id="1",
channel=MessageChannel.WebAgent.value,
channel=NotificationChannel.WebAgent.value,
source="web-agent",
username="admin",
replay_mode=ReplyMode.CAPTURE_ONLY,
@@ -562,7 +562,7 @@ def test_confirm_reuses_policy_lifecycle() -> None:
agent = MoviePilotAgent(
session_id="session-secret",
user_id="1",
channel=MessageChannel.WebAgent.value,
channel=NotificationChannel.WebAgent.value,
source="web-agent",
username="admin",
replay_mode=ReplyMode.CAPTURE_ONLY,
@@ -613,7 +613,7 @@ def test_confirm_respects_policy_denial_without_running_tool() -> None:
agent = MoviePilotAgent(
session_id="session-secret",
user_id="1",
channel=MessageChannel.WebAgent.value,
channel=NotificationChannel.WebAgent.value,
source="web-agent",
username="admin",
replay_mode=ReplyMode.CAPTURE_ONLY,
@@ -647,7 +647,7 @@ def test_policy_denial_delivery_failure_reports_not_executed() -> None:
agent = MoviePilotAgent(
session_id="session-secret",
user_id="1",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-main",
username="admin",
original_chat_id="group-1",
@@ -690,7 +690,7 @@ def test_confirm_records_policy_failure_and_returns_protected_error() -> None:
agent = MoviePilotAgent(
session_id="session-secret",
user_id="1",
channel=MessageChannel.WebAgent.value,
channel=NotificationChannel.WebAgent.value,
source="web-agent",
username="admin",
replay_mode=ReplyMode.CAPTURE_ONLY,
@@ -748,7 +748,7 @@ def test_execution_failure_delivery_failure_reports_safe_notice() -> None:
agent = MoviePilotAgent(
session_id="session-secret",
user_id="1",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-main",
username="admin",
original_chat_id="group-1",
+3 -3
View File
@@ -10,7 +10,7 @@ from langchain_core.messages import AIMessage
from app.agent.middleware.usage import UsageMiddleware
from app.agent import AgentManager
from app.chain.message import MessageChain
from app.schemas.types import MessageChannel
from app.schemas.types import NotificationChannel
class TestAgentSessionStatus(unittest.TestCase):
@@ -100,7 +100,7 @@ class TestAgentSessionStatus(unittest.TestCase):
patch.object(chain, "post_message") as post_message,
):
chain.remote_session_status(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
userid="10001",
source="telegram-test",
)
@@ -121,7 +121,7 @@ class TestAgentSessionStatus(unittest.TestCase):
with patch.object(chain, "post_message") as post_message:
chain.remote_session_status(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
userid="10001",
source="telegram-test",
)
+8 -8
View File
@@ -8,11 +8,11 @@ from app.agent.tools.impl.ask_user_choice import (
UserChoiceOptionInput,
)
from app.agent.tools.impl.send_message import SendMessageTool
from app.schemas import Notification
from app.schemas.types import MessageChannel
from app.schemas import Message
from app.schemas.types import NotificationChannel
def _run_choice_tool(agent_context: dict, channel: str, source: str) -> Notification:
def _run_choice_tool(agent_context: dict, channel: str, source: str) -> Message:
"""运行按钮选择工具并返回其发送的通知。"""
tool = AskUserChoiceTool(session_id="session-1", user_id="ou_xxx")
tool.set_message_attr(
@@ -41,7 +41,7 @@ def test_choice_tool_backfills_original_chat_id_from_session_context():
"""群聊场景下按钮选择通知应回填会话上下文中的 original_chat_id。"""
notification = _run_choice_tool(
agent_context={"original_chat_id": "oc_group_123"},
channel=MessageChannel.Feishu.value,
channel=NotificationChannel.Feishu.value,
source="feishu-test",
)
@@ -53,7 +53,7 @@ def test_choice_tool_keeps_explicit_original_chat_id():
"""按钮选择通知已显式携带原会话 ID 时不应被上下文覆盖。"""
notification = _run_choice_tool(
agent_context={"original_chat_id": "oc_group_zzz"},
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
)
@@ -64,7 +64,7 @@ def test_choice_tool_no_context_does_not_backfill():
"""会话上下文未携带原会话 ID 时,通知保持原有发送目标。"""
notification = _run_choice_tool(
agent_context={},
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
)
@@ -92,7 +92,7 @@ def test_send_tool_message_backfills_original_chat_id():
"""send_tool_message 工具消息同样应回填原会话 ID。"""
tool = SendMessageTool(session_id="session-1", user_id="ou_xxx")
tool.set_message_attr(
channel=MessageChannel.Feishu.value,
channel=NotificationChannel.Feishu.value,
source="feishu-test",
username="tester",
)
@@ -115,7 +115,7 @@ def test_tool_context_includes_original_chat_id():
agent = MoviePilotAgent(
session_id="session-1",
user_id="ou_xxx",
channel=MessageChannel.Feishu.value,
channel=NotificationChannel.Feishu.value,
source="feishu-test",
username="tester",
original_chat_id="oc_group_123",
+11 -11
View File
@@ -33,7 +33,7 @@ from app.agent.tools.impl.query_system_settings import QuerySystemSettingsTool
from app.agent.tools.impl.send_local_file import SendLocalFileTool
from app.agent.tools.impl.send_voice_message import SendVoiceMessageTool
from app.agent.tools.manager import MoviePilotToolsManager
from app.schemas.types import MessageChannel
from app.schemas.types import NotificationChannel
class _EchoInput(BaseModel):
@@ -270,43 +270,43 @@ def test_policy_context_maps_trusted_host_origins() -> None:
"""各入口必须由宿主稳定映射 origin、主体类型和认证来源。"""
cases = [
(
{"channel": MessageChannel.Web.value, "source": "openai"},
{"channel": NotificationChannel.Web.value, "source": "openai"},
ToolOrigin.AGENT_API,
PrincipalType.SYSTEM_ADMIN_INTEGRATION,
AuthSource.API_TOKEN,
),
(
{"channel": MessageChannel.Web.value, "source": "openai.responses"},
{"channel": NotificationChannel.Web.value, "source": "openai.responses"},
ToolOrigin.AGENT_API,
PrincipalType.SYSTEM_ADMIN_INTEGRATION,
AuthSource.API_TOKEN,
),
(
{"channel": MessageChannel.Web.value, "source": "anthropic"},
{"channel": NotificationChannel.Web.value, "source": "anthropic"},
ToolOrigin.AGENT_API,
PrincipalType.SYSTEM_ADMIN_INTEGRATION,
AuthSource.API_TOKEN,
),
(
{"channel": MessageChannel.Web.value, "source": "browser"},
{"channel": NotificationChannel.Web.value, "source": "browser"},
ToolOrigin.AGENT_INTERACTIVE,
PrincipalType.HUMAN,
AuthSource.WEB_SESSION,
),
(
{"channel": MessageChannel.WebAgent.value, "source": "web-agent"},
{"channel": NotificationChannel.WebAgent.value, "source": "web-agent"},
ToolOrigin.AGENT_INTERACTIVE,
PrincipalType.HUMAN,
AuthSource.WEB_SESSION,
),
(
{"channel": MessageChannel.Telegram.value, "source": "telegram"},
{"channel": NotificationChannel.Telegram.value, "source": "telegram"},
ToolOrigin.AGENT_INTERACTIVE,
PrincipalType.HUMAN,
AuthSource.CHANNEL,
),
(
{"channel": MessageChannel.Feishu.value, "source": "feishu"},
{"channel": NotificationChannel.Feishu.value, "source": "feishu"},
ToolOrigin.AGENT_INTERACTIVE,
PrincipalType.HUMAN,
AuthSource.CHANNEL,
@@ -333,7 +333,7 @@ def test_policy_context_maps_trusted_host_origins() -> None:
subagent_context = agent_module.MoviePilotAgent(
session_id="subagent-session",
user_id="user-1",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram",
)._build_policy_context().for_subagent()
assert subagent_context.origin is ToolOrigin.SUBAGENT
@@ -571,7 +571,7 @@ def test_agent_admin_safe_read_keeps_legacy_authorization_authority(
user_id="user-1",
)
tool.set_message_attr(
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="user",
username="member",
)
@@ -806,7 +806,7 @@ def test_main_agent_preserves_activity_log_middleware_order() -> None:
agent = agent_module.MoviePilotAgent(
session_id="session-1",
user_id="user-1",
channel=MessageChannel.WebAgent.value,
channel=NotificationChannel.WebAgent.value,
source="web-agent",
)
fake_llm = _AgentFactoryLLM()
+23 -23
View File
@@ -15,7 +15,7 @@ from app.agent.tools.impl.send_voice_message import SendVoiceMessageTool
from app.api.endpoints.openai import _OpenAIStreamingHandler
from app.runtime.config import settings
from app.schemas.message import MessageResponse
from app.schemas.types import MessageChannel, NotificationType
from app.schemas.types import NotificationChannel, MessageType
def test_think_tag_stripper_waits_for_partial_open_tag():
@@ -344,7 +344,7 @@ class TestAgentToolStreaming:
def test_flush_sends_direct_message_via_threadpool(self):
"""校验刷新时通过线程池发送首条直连消息。"""
handler = StreamingHandler()
handler._channel = MessageChannel.Telegram.value
handler._channel = NotificationChannel.Telegram.value
handler._source = "telegram"
handler._user_id = "10001"
handler._username = "tester"
@@ -365,13 +365,13 @@ class TestAgentToolStreaming:
assert run_in_threadpool_mock.await_count == 1
assert run_in_threadpool_mock.await_args.args[0].__name__ == "send_direct_message"
assert run_in_threadpool_mock.await_args.args[1].mtype == NotificationType.Agent
assert run_in_threadpool_mock.await_args.args[1].mtype == MessageType.Agent
assert handler.has_sent_message
def test_flush_edits_message_via_threadpool(self):
"""校验刷新时通过线程池编辑已有消息。"""
handler = StreamingHandler()
handler._channel = MessageChannel.Telegram.value
handler._channel = NotificationChannel.Telegram.value
handler._source = "telegram"
handler._streaming_enabled = True
handler._message_response = MessageResponse(
@@ -398,7 +398,7 @@ class TestAgentToolStreaming:
"""校验停止流式输出会等待首条消息发送完成再编辑。"""
async def _run():
handler = StreamingHandler()
handler._channel = MessageChannel.Feishu.value
handler._channel = NotificationChannel.Feishu.value
handler._source = "feishu-main"
handler._user_id = "ou_user"
handler._streaming_enabled = True
@@ -416,7 +416,7 @@ class TestAgentToolStreaming:
return MessageResponse(
message_id="om_stream",
chat_id="oc_stream",
channel=MessageChannel.Feishu,
channel=NotificationChannel.Feishu,
source="feishu-main",
success=True,
)
@@ -459,7 +459,7 @@ class TestAgentToolStreaming:
handler._message_response = MessageResponse(
message_id="om_stream",
chat_id="oc_stream",
channel=MessageChannel.Feishu,
channel=NotificationChannel.Feishu,
source="feishu-main",
metadata={"feishu_streaming": {"card_id": "card_stream", "sequence": 2}},
success=True,
@@ -523,7 +523,7 @@ class TestAgentToolStreaming:
def test_flush_passes_original_message_context_to_send_direct_message(self):
"""校验刷新发送时保留原始消息上下文。"""
handler = StreamingHandler()
handler._channel = MessageChannel.Feishu.value
handler._channel = NotificationChannel.Feishu.value
handler._source = "feishu-main"
handler._user_id = "ou_user"
handler._username = "tester"
@@ -603,7 +603,7 @@ class TestAgentToolStreaming:
def test_send_voice_message_uses_native_voice_for_supported_channels(self):
"""校验支持语音输出的渠道会发送原生语音消息。"""
async def _run(channel: MessageChannel):
async def _run(channel: NotificationChannel):
"""运行指定渠道的语音发送工具。"""
tool = SendVoiceMessageTool(session_id="session-1", user_id="10001")
tool.set_message_attr(
@@ -625,22 +625,22 @@ class TestAgentToolStreaming:
) as synthesize_speech,
patch.object(
SendVoiceMessageTool,
"send_notification_message",
"send_message",
new_callable=AsyncMock,
) as send_notification_message,
) as send_message,
):
result = await tool.run("你好")
return result, synthesize_speech, send_notification_message
return result, synthesize_speech, send_message
for channel in (MessageChannel.Telegram, MessageChannel.Feishu, MessageChannel.WebAgent):
result, synthesize_speech, send_notification_message = asyncio.run(
for channel in (NotificationChannel.Telegram, NotificationChannel.Feishu, NotificationChannel.WebAgent):
result, synthesize_speech, send_message = asyncio.run(
_run(channel)
)
notification = send_notification_message.await_args.args[-1]
notification = send_message.await_args.args[-1]
assert result == "语音回复已发送"
synthesize_speech.assert_called_once_with("你好")
send_notification_message.assert_awaited_once()
send_message.assert_awaited_once()
assert notification.channel == channel
assert notification.voice_path == "/tmp/reply.opus"
assert notification.voice_caption == "你好"
@@ -655,7 +655,7 @@ class TestAgentToolStreaming:
"""运行不支持语音输出渠道的语音发送工具。"""
tool = SendVoiceMessageTool(session_id="session-1", user_id="10001")
tool.set_message_attr(
channel=MessageChannel.Slack.value, source="slack-main", username="tester"
channel=NotificationChannel.Slack.value, source="slack-main", username="tester"
)
with (
@@ -669,18 +669,18 @@ class TestAgentToolStreaming:
) as synthesize_speech,
patch.object(
SendVoiceMessageTool,
"send_notification_message",
"send_message",
new_callable=AsyncMock,
) as send_notification_message,
) as send_message,
):
result = await tool.run("你好")
return result, synthesize_speech, send_notification_message
return result, synthesize_speech, send_message
result, synthesize_speech, send_notification_message = asyncio.run(_run())
notification = send_notification_message.await_args.args[-1]
result, synthesize_speech, send_message = asyncio.run(_run())
notification = send_message.await_args.args[-1]
assert result == "当前渠道不支持语音回复,已自动回退为文字回复"
synthesize_speech.assert_not_called()
send_notification_message.assert_awaited_once()
send_message.assert_awaited_once()
assert notification.text == "你好"
assert notification.voice_path is None
+2 -2
View File
@@ -44,7 +44,7 @@ def _load_downloader_base():
schema_types_module.ModuleType = Enum("ModuleType", {"Downloader": "downloader"})
schema_types_module.DownloaderType = Enum("DownloaderType", {"Qbittorrent": "Qbittorrent"})
schema_types_module.MediaServerType = Enum("MediaServerType", {"Emby": "Emby"})
schema_types_module.MessageChannel = Enum("MessageChannel", {"Telegram": "telegram"})
schema_types_module.NotificationChannel = Enum("NotificationChannel", {"Telegram": "telegram"})
schema_types_module.OtherModulesType = Enum("OtherModulesType", {"Subtitle": "subtitle"})
schema_types_module.MediaRecognizeType = Enum(
"MediaRecognizeType", {"TheMovieDb": "themoviedb"}
@@ -60,7 +60,7 @@ def _load_downloader_base():
service_module.ServiceConfigHelper = _ServiceConfigHelper
mixins_module.ConfigReloadMixin = _ConfigReloadMixin
schemas_module.Notification = object
schemas_module.Message = object
schemas_module.NotificationConf = object
schemas_module.MediaServerConf = object
schemas_module.DownloaderConf = object
+41 -41
View File
@@ -14,13 +14,13 @@ ensure_optional_stub("Pinyin2Hanzi", is_pinyin=lambda value: False)
from app.modules.feishu import FeishuModule
from app.modules.feishu.feishu import Feishu
from app.schemas import Notification
from app.schemas import Message
from app.schemas.message import (
ChannelCapability,
ChannelCapabilityManager,
MessageResponse,
)
from app.schemas.types import MessageChannel, NotificationType
from app.schemas.types import NotificationChannel, MessageType
class TestFeishu(unittest.TestCase):
@@ -156,7 +156,7 @@ class TestFeishu(unittest.TestCase):
)
self.assertIsNotNone(result)
self.assertEqual(result.channel, MessageChannel.Feishu)
self.assertEqual(result.channel, NotificationChannel.Feishu)
self.assertEqual(result.userid, "ou_user_1")
self.assertEqual(result.text, "CALLBACK:approve")
self.assertTrue(result.is_callback)
@@ -281,7 +281,7 @@ class TestFeishu(unittest.TestCase):
)
result = client.send_notification(
Notification(
Message(
title="测试标题",
text="测试正文",
buttons=[[{"text": "确认", "callback_data": "confirm"}]],
@@ -316,7 +316,7 @@ class TestFeishu(unittest.TestCase):
)
result = client.send_notification(
Notification(
Message(
title="普通通知",
text="海报:![poster](https://example.com/poster.jpg)",
),
@@ -348,7 +348,7 @@ class TestFeishu(unittest.TestCase):
with patch("app.modules.feishu.feishu.RequestUtils") as request_utils:
request_utils.return_value.get_res.return_value = response
result = client.send_notification(
Notification(
Message(
title="测试标题",
text="测试正文",
image="https://example.com/poster.png",
@@ -381,7 +381,7 @@ class TestFeishu(unittest.TestCase):
)
client.send_notification(
Notification(title="测试标题", text="测试正文"),
Message(title="测试标题", text="测试正文"),
userid="u_user_4",
receive_id_type="user_id",
)
@@ -426,7 +426,7 @@ class TestFeishu(unittest.TestCase):
)
result = client.send_notification(
Notification(title="回复标题", text="回复正文"),
Message(title="回复标题", text="回复正文"),
userid="ou_user_9",
original_message_id="om_origin",
)
@@ -475,8 +475,8 @@ class TestFeishu(unittest.TestCase):
)
result = client.send_notification(
Notification(
mtype=NotificationType.Agent,
Message(
mtype=MessageType.Agent,
title="MoviePilot助手",
text="第一帧内容",
),
@@ -523,8 +523,8 @@ class TestFeishu(unittest.TestCase):
with patch("app.modules.feishu.feishu.RequestUtils") as request_utils:
request_utils.return_value.get_res.return_value = response
result = client.send_notification(
Notification(
mtype=NotificationType.Agent,
Message(
mtype=MessageType.Agent,
title="MoviePilot助手",
text="找到海报 ![poster](https://example.com/poster.jpg)\n[详情](https://example.com/detail)",
),
@@ -568,8 +568,8 @@ class TestFeishu(unittest.TestCase):
with patch("app.modules.feishu.feishu.RequestUtils") as request_utils:
request_utils.return_value.get_res.return_value = response
result = client.send_notification(
Notification(
mtype=NotificationType.Agent,
Message(
mtype=MessageType.Agent,
title="MoviePilot助手",
text="第一帧内容",
image="https://example.com/agent.png",
@@ -606,8 +606,8 @@ class TestFeishu(unittest.TestCase):
)
result = client.send_notification(
Notification(
mtype=NotificationType.Agent,
Message(
mtype=MessageType.Agent,
title="MoviePilot助手",
text="第一帧内容",
),
@@ -988,13 +988,13 @@ class TestFeishu(unittest.TestCase):
def test_feishu_channel_capabilities_enable_images_and_files(self):
self.assertTrue(
ChannelCapabilityManager.supports_capability(
MessageChannel.Feishu,
NotificationChannel.Feishu,
ChannelCapability.IMAGES,
)
)
self.assertTrue(
ChannelCapabilityManager.supports_capability(
MessageChannel.Feishu,
NotificationChannel.Feishu,
ChannelCapability.FILE_SENDING,
)
)
@@ -1121,7 +1121,7 @@ class TestFeishu(unittest.TestCase):
def test_module_send_direct_message_prefers_open_id_target(self):
module = FeishuModule()
module._channel = MessageChannel.Feishu
module._channel = NotificationChannel.Feishu
conf = SimpleNamespace(name="feishu-main")
client = MagicMock()
client.send_notification.return_value = {
@@ -1136,7 +1136,7 @@ class TestFeishu(unittest.TestCase):
patch.object(module, "get_instance", return_value=client),
):
response = module.send_direct_message(
Notification(
Message(
targets={
"feishu_userid": "u_target",
"feishu_openid": "ou_target",
@@ -1158,7 +1158,7 @@ class TestFeishu(unittest.TestCase):
def test_module_plain_direct_message_uses_literal_text_transport(self):
"""纯文本直发不得进入会解释密钥字符的 Markdown 卡片路径。"""
module = FeishuModule()
module._channel = MessageChannel.Feishu
module._channel = NotificationChannel.Feishu
conf = SimpleNamespace(name="feishu-main")
client = MagicMock()
client.send_text.return_value = {
@@ -1174,8 +1174,8 @@ class TestFeishu(unittest.TestCase):
patch.object(module, "get_instance", return_value=client),
):
response = module.send_direct_message(
Notification(
channel=MessageChannel.Feishu,
Message(
channel=NotificationChannel.Feishu,
source="feishu-main",
userid="ou_target",
text=literal_text,
@@ -1336,7 +1336,7 @@ class TestFeishu(unittest.TestCase):
def test_module_processing_status_uses_reaction_helpers(self):
module = FeishuModule()
module._channel = MessageChannel.Feishu
module._channel = NotificationChannel.Feishu
with (
patch.object(
@@ -1351,7 +1351,7 @@ class TestFeishu(unittest.TestCase):
) as delete_reaction,
):
status = module.mark_message_processing_started(
channel=MessageChannel.Feishu,
channel=NotificationChannel.Feishu,
source="feishu-main",
userid="ou_x",
message_id="om_x",
@@ -1359,7 +1359,7 @@ class TestFeishu(unittest.TestCase):
text="hello",
)
deleted = module.mark_message_processing_finished(
channel=MessageChannel.Feishu,
channel=NotificationChannel.Feishu,
source="feishu-main",
userid="ou_x",
status=status,
@@ -1380,7 +1380,7 @@ class TestFeishu(unittest.TestCase):
def test_module_finalize_message_closes_streaming_card(self):
module = FeishuModule()
module._channel = MessageChannel.Feishu
module._channel = NotificationChannel.Feishu
client = MagicMock()
client.close_streaming_card.return_value = True
@@ -1394,7 +1394,7 @@ class TestFeishu(unittest.TestCase):
MessageResponse(
message_id="om_stream",
chat_id="oc_stream",
channel=MessageChannel.Feishu,
channel=NotificationChannel.Feishu,
source="feishu-main",
metadata={
"feishu_streaming": {
@@ -1422,7 +1422,7 @@ class TestFeishu(unittest.TestCase):
patch.object(module, "get_instance", return_value=client),
):
module.post_message(
Notification(
Message(
file_path="/tmp/demo.txt",
text="说明",
title="标题",
@@ -1430,7 +1430,7 @@ class TestFeishu(unittest.TestCase):
)
)
module.post_message(
Notification(
Message(
voice_path="/tmp/demo.opus",
voice_caption="语音说明",
userid="ou_user",
@@ -1451,7 +1451,7 @@ class TestFeishu(unittest.TestCase):
patch.object(module, "get_instance", return_value=client),
):
module.post_message(
Notification(
Message(
file_path="/tmp/demo.txt",
file_name="demo.txt",
image="https://example.com/poster.png",
@@ -1471,7 +1471,7 @@ class TestFeishu(unittest.TestCase):
def test_module_send_direct_message_sends_image_card_before_file_attachment(self):
module = FeishuModule()
module._channel = MessageChannel.Feishu
module._channel = NotificationChannel.Feishu
conf = SimpleNamespace(name="feishu-main")
client = MagicMock()
client.send_notification.return_value = {
@@ -1487,8 +1487,8 @@ class TestFeishu(unittest.TestCase):
patch.object(module, "get_instance", return_value=client),
):
response = module.send_direct_message(
Notification(
channel=MessageChannel.Feishu,
Message(
channel=NotificationChannel.Feishu,
source="feishu-main",
file_path="/tmp/demo.txt",
file_name="demo.txt",
@@ -1515,7 +1515,7 @@ class TestFeishu(unittest.TestCase):
patch.object(module, "get_instance", return_value=client),
):
module.post_message(
Notification(
Message(
title="标题",
text="正文",
userid="ou_user",
@@ -1533,7 +1533,7 @@ class TestFeishu(unittest.TestCase):
def test_module_resolve_message_target_prefers_original_chat_id(self):
"""群聊@回复必须优先发回原会话,而不是按 open_id 发到私聊。"""
userid, chat_id, receive_id_type = FeishuModule._resolve_message_target(
Notification(userid="ou_user", original_chat_id="oc_group")
Message(userid="ou_user", original_chat_id="oc_group")
)
self.assertIsNone(userid)
self.assertEqual(chat_id, "oc_group")
@@ -1541,7 +1541,7 @@ class TestFeishu(unittest.TestCase):
# 非回复类消息仍按原有逻辑优先 open_id。
userid, chat_id, receive_id_type = FeishuModule._resolve_message_target(
Notification(userid="ou_user")
Message(userid="ou_user")
)
self.assertEqual(userid, "ou_user")
self.assertIsNone(chat_id)
@@ -1549,7 +1549,7 @@ class TestFeishu(unittest.TestCase):
# 无用户ID时回退 targets 中的飞书字段。
userid, chat_id, receive_id_type = FeishuModule._resolve_message_target(
Notification(targets={"feishu_chat_id": "oc_config"})
Message(targets={"feishu_chat_id": "oc_config"})
)
self.assertIsNone(userid)
self.assertEqual(chat_id, "oc_config")
@@ -1557,7 +1557,7 @@ class TestFeishu(unittest.TestCase):
def test_module_private_delivery_ignores_original_group_chat(self):
"""私聊投递只保留用户身份,并让客户端按已记录 ID 类型发送。"""
userid, chat_id, receive_id_type = FeishuModule._resolve_message_target(
Notification(
Message(
userid="user_target",
original_chat_id="oc_group",
private_delivery=True,
@@ -1580,7 +1580,7 @@ class TestFeishu(unittest.TestCase):
patch.object(module, "get_instance", return_value=client),
):
module.post_message(
Notification(
Message(
title="标题",
text="正文",
userid="ou_user",
@@ -1609,7 +1609,7 @@ class TestFeishu(unittest.TestCase):
}
result = client.send_notification(
Notification(title="插件通知", text="无目标通知")
Message(title="插件通知", text="无目标通知")
)
self.assertTrue(result["success"])
+2 -2
View File
@@ -8,7 +8,7 @@ ensure_optional_stub("Pinyin2Hanzi", is_pinyin=lambda value: False)
from app.domain.context import MediaInfo
from app.modules.feishu.feishu import Feishu
from app.schemas import Notification
from app.schemas import Message
def _build_feishu_client() -> Feishu:
@@ -39,7 +39,7 @@ def test_send_medias_message_passes_first_available_image() -> None:
return_value={"success": True},
) as send_notification:
result = client.send_medias_message(
message=Notification(title="搜索结果", userid="ou_test"),
message=Message(title="搜索结果", userid="ou_test"),
medias=[first_media, second_media],
)
+3 -3
View File
@@ -22,13 +22,13 @@ from app.application.messaging.router import (
)
from app.application.messaging.site import site_interaction_manager
from app.application.messaging.skill import skill_interaction_manager
from app.schemas.types import MessageChannel
from app.schemas.types import NotificationChannel
def _context(user_id="10001") -> InteractionContext:
"""构造最小交互上下文。"""
return InteractionContext(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
user_id=user_id,
username="tester",
@@ -181,7 +181,7 @@ class TestHasPendingInteraction(unittest.TestCase):
site_interaction_manager.create_or_replace(
user_id="10001",
command="/sites",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
username="tester",
)
+10 -2
View File
@@ -20,6 +20,7 @@ from app.runtime.compat.manifest import (
SYMBOL_ALIASES,
VIRTUAL_PACKAGES,
ModuleAlias,
_MESSAGE_NOTIFICATION_SYMBOL_ALIASES,
)
@@ -316,7 +317,7 @@ def test_plugin_scan_reports_moved_symbol_import(tmp_path: Path):
def test_symbol_alias_manifest_covers_all_moved_public_symbols():
"""符号级映射清单应覆盖媒体身份整理工作项的旧入口。"""
"""符号级映射清单应覆盖媒体身份整理工作项与消息/通知命名统一的旧入口。"""
assert set(SYMBOL_ALIASES["app.domain.media"]) == {
"MEDIA_SOURCE_ALIASES",
"MEDIA_SOURCE_PREFIXES",
@@ -329,8 +330,15 @@ def test_symbol_alias_manifest_covers_all_moved_public_symbols():
assert set(SYMBOL_ALIASES["app.schemas"]) == {
"TransferTask",
"TransferQueue",
}
} | set(_MESSAGE_NOTIFICATION_SYMBOL_ALIASES)
assert set(SYMBOL_ALIASES["app.schemas.transfer"]) == {
"TransferTask",
"TransferQueue",
}
assert set(SYMBOL_ALIASES["app.schemas.types"]) == {
"MessageChannel",
"NotificationType",
}
assert set(SYMBOL_ALIASES["app.schemas.message"]) == set(
_MESSAGE_NOTIFICATION_SYMBOL_ALIASES
)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,113 @@
"""
message/notification 命名统一的兼容守护测试
边界定义notification 表示通知渠道能力message 表示各渠道发送或接收的消息
canonical 源码不保留任何旧名别名旧名一律经由 runtime/compat 映射表
SYMBOL_ALIASES在导入器层惰性解析供存量插件继续使用
"""
import importlib
from app.runtime.compat.manifest import SYMBOL_ALIASES
from app.schemas.message import (
IncomingMessage,
Message,
MessageClearBefore,
MessageClearData,
MessageClearScope,
MessageHistoryItem,
)
from app.schemas.notification import (
ChannelCapabilities,
ChannelCapability,
ChannelCapabilityManager,
)
from app.schemas.types import MessageType, NotificationChannel
# 旧名 -> canonical 对象的期望映射,覆盖全部登记的兼容入口
_EXPECTED_RESOLUTIONS = {
("app.schemas.types", "MessageChannel"): NotificationChannel,
("app.schemas.types", "NotificationType"): MessageType,
("app.schemas.message", "Notification"): Message,
("app.schemas.message", "CommingMessage"): IncomingMessage,
("app.schemas.message", "NotificationHistoryItem"): MessageHistoryItem,
("app.schemas.message", "NotificationClearScope"): MessageClearScope,
("app.schemas.message", "NotificationClearBefore"): MessageClearBefore,
("app.schemas.message", "NotificationClearData"): MessageClearData,
("app.schemas.message", "ChannelCapability"): ChannelCapability,
("app.schemas.message", "ChannelCapabilities"): ChannelCapabilities,
("app.schemas.message", "ChannelCapabilityManager"): ChannelCapabilityManager,
}
def test_canonical_modules_do_not_define_legacy_names():
"""canonical 模块自身不得保留旧名物理别名,旧名只能来自兼容映射。"""
types_module = importlib.import_module("app.schemas.types")
message_module = importlib.import_module("app.schemas.message")
for legacy_name in ("MessageChannel", "NotificationType"):
assert legacy_name not in types_module.__dict__
for legacy_name in (
"Notification",
"CommingMessage",
"NotificationHistoryItem",
"NotificationClearScope",
"NotificationClearBefore",
"NotificationClearData",
):
assert legacy_name not in message_module.__dict__
def test_manifest_registers_all_legacy_message_notification_symbols():
"""映射表必须登记全部旧名,且目标指向当前 canonical 符号。"""
for (module_name, legacy_name), canonical in _EXPECTED_RESOLUTIONS.items():
alias = SYMBOL_ALIASES[module_name][legacy_name]
target = getattr(importlib.import_module(alias.target_module), alias.target_name)
assert target is canonical, (module_name, legacy_name)
def test_legacy_symbol_imports_resolve_through_compat_hook():
"""插件旧导入路径应经兼容钩子解析到 canonical 对象。"""
for (module_name, legacy_name), canonical in _EXPECTED_RESOLUTIONS.items():
module = importlib.import_module(module_name)
assert getattr(module, legacy_name) is canonical, (module_name, legacy_name)
def test_schemas_package_level_legacy_imports_resolve():
"""from app.schemas import 旧名 的插件写法应继续可用。"""
schemas_package = importlib.import_module("app.schemas")
assert schemas_package.MessageChannel is NotificationChannel
assert schemas_package.NotificationType is MessageType
assert schemas_package.Notification is Message
assert schemas_package.CommingMessage is IncomingMessage
assert schemas_package.NotificationHistoryItem is MessageHistoryItem
assert schemas_package.ChannelCapabilityManager is ChannelCapabilityManager
def test_legacy_from_import_statement_works():
"""from ... import 语句形式的旧导入应正常执行。"""
scope: dict = {}
exec(
"from app.schemas import Notification, MessageChannel, NotificationType\n"
"from app.schemas.message import CommingMessage\n"
"from app.schemas.types import MessageChannel as LegacyChannel\n",
scope,
)
assert scope["Notification"] is Message
assert scope["MessageChannel"] is NotificationChannel
assert scope["NotificationType"] is MessageType
assert scope["CommingMessage"] is IncomingMessage
assert scope["LegacyChannel"] is NotificationChannel
def test_legacy_and_canonical_instances_interchangeable():
"""旧名构造的实例应与 canonical 类型互相兼容(同一类对象)。"""
legacy_message = importlib.import_module("app.schemas").Notification(title="t")
assert isinstance(legacy_message, Message)
assert isinstance(
Message(title="t"),
importlib.import_module("app.schemas.message").Notification,
)
+20 -20
View File
@@ -8,11 +8,11 @@ from app.domain.context import Context, MediaInfo, TorrentInfo
from app.domain.meta.metabase import MetaBase
from app.db import AsyncSessionFactory, SessionFactory
from app.db.oper.message import MessageOper
from app.db.models.message import Message
from app.db.models.message import Message as MessageModel
from app.db.oper.systemconfig import SystemConfigOper
from app.application.messaging.message import MessageHelper
from app.schemas import Notification, NotificationClearScope
from app.schemas.types import MediaType, NotificationType, SystemConfigKey
from app.schemas import Message, MessageClearScope
from app.schemas.types import MediaType, MessageType, SystemConfigKey
def _clear_messages() -> None:
@@ -20,7 +20,7 @@ def _clear_messages() -> None:
清空消息表隔离通知测试数据
"""
with SessionFactory() as db:
db.query(Message).delete()
db.query(MessageModel).delete()
db.commit()
SystemConfigOper().delete(SystemConfigKey.NotificationClearBefore)
@@ -39,7 +39,7 @@ def _set_message_time(title: str, reg_time: str) -> None:
调整测试消息时间避免消息写入时的当前秒影响清理边界断言
"""
with SessionFactory() as db:
db.query(Message).filter(Message.title == title).update({"reg_time": reg_time})
db.query(MessageModel).filter(MessageModel.title == title).update({"reg_time": reg_time})
db.commit()
@@ -49,9 +49,9 @@ def test_notification_history_only_lists_sent_messages() -> None:
"""
_clear_messages()
oper = MessageOper()
oper.add(title="系统通知", text="下载完成", action=1, mtype=NotificationType.Download)
oper.add(title="系统通知", text="下载完成", action=1, mtype=MessageType.Download)
oper.add(title="用户消息", text="帮我搜索", action=0)
oper.add(title="智能体回复", text="已处理", action=1, mtype=NotificationType.Agent)
oper.add(title="智能体回复", text="已处理", action=1, mtype=MessageType.Agent)
messages = MessageOper().list_by_page(page=1, count=10)
assert [message.title for message in messages if message.action == 1] == ["智能体回复", "系统通知"]
@@ -63,9 +63,9 @@ def test_web_message_history_returns_all_messages() -> None:
"""
_clear_messages()
oper = MessageOper()
oper.add(title="智能体回复", text="已处理", action=1, mtype=NotificationType.Agent)
oper.add(title="智能体回复", text="已处理", action=1, mtype=MessageType.Agent)
oper.add(title="用户消息", text="/ai 帮我处理", action=0)
oper.add(title="普通通知", text="下载完成", action=1, mtype=NotificationType.Download)
oper.add(title="普通通知", text="下载完成", action=1, mtype=MessageType.Download)
messages = MessageOper().list_by_page(page=1, count=10)
assert [message.title for message in messages] == ["普通通知", "用户消息", "智能体回复"]
@@ -81,7 +81,7 @@ def test_notification_clear_marker_filters_history_across_requests() -> None:
title="旧系统通知",
text="任务失败",
action=1,
mtype=NotificationType.Other,
mtype=MessageType.Other,
)
oper.add(
title="旧媒体通知",
@@ -92,7 +92,7 @@ def test_notification_clear_marker_filters_history_across_requests() -> None:
_set_message_time("旧系统通知", "2026-01-01 00:00:00")
_set_message_time("旧媒体通知", "2026-01-01 00:00:00")
asyncio.run(clear_notification_message(scope=NotificationClearScope.Media))
asyncio.run(clear_notification_message(scope=MessageClearScope.Media))
oper.add(
title="新媒体通知",
@@ -183,8 +183,8 @@ def test_notification_post_message_is_persisted_without_sse_queue() -> None:
chain.eventmanager.send_event = Mock()
chain.post_message(
Notification(
mtype=NotificationType.Download,
Message(
mtype=MessageType.Download,
title="下载完成",
text="影片已加入下载器",
)
@@ -193,7 +193,7 @@ def test_notification_post_message_is_persisted_without_sse_queue() -> None:
messages = MessageOper().list_by_page(page=1, count=10)
assert len(messages) == 1
assert messages[0].title == "下载完成"
assert messages[0].mtype == NotificationType.Download.value
assert messages[0].mtype == MessageType.Download.value
assert helper.get() is None
chain.messagequeue.send_message.assert_called_once()
@@ -211,8 +211,8 @@ def test_agent_notification_post_message_is_persisted_without_sse_queue() -> Non
chain.eventmanager.send_event = Mock()
chain.post_message(
Notification(
mtype=NotificationType.Agent,
Message(
mtype=MessageType.Agent,
title="MoviePilot助手",
text="已完成处理",
)
@@ -221,7 +221,7 @@ def test_agent_notification_post_message_is_persisted_without_sse_queue() -> Non
messages = MessageOper().list_by_page(page=1, count=10)
assert len(messages) == 1
assert messages[0].title == "MoviePilot助手"
assert messages[0].mtype == NotificationType.Agent.value
assert messages[0].mtype == MessageType.Agent.value
assert helper.get() is None
chain.messagequeue.send_message.assert_called_once()
@@ -237,7 +237,7 @@ def test_transient_notification_post_message_skips_history_but_dispatches() -> N
chain.eventmanager.send_event = Mock()
chain.post_message(
Notification(
Message(
title="请选择下载目录",
text="1. 默认目录",
save_history=False,
@@ -270,11 +270,11 @@ def test_transient_media_and_torrent_lists_skip_history_but_dispatch() -> None:
chain.messagequeue.send_message = Mock()
chain.post_medias_message(
Notification(title="请选择媒体", save_history=False),
Message(title="请选择媒体", save_history=False),
medias=[media],
)
chain.post_torrents_message(
Notification(title="请选择资源", save_history=False),
Message(title="请选择资源", save_history=False),
torrents=[torrent],
)
+14 -14
View File
@@ -9,20 +9,20 @@ from app.agent import _finish_processing_status
from app.modules.discord import DiscordModule
from app.modules.discord.discord import Discord
from app.modules.slack import SlackModule
from app.schemas.message import ChannelCapability, ChannelCapabilityManager
from app.schemas.types import MessageChannel
from app.schemas.notification import ChannelCapability, ChannelCapabilityManager
from app.schemas.types import NotificationChannel
class TestMessageProcessingStatus(unittest.TestCase):
def test_processing_status_capability_only_enabled_for_supported_channels(self):
supported = {
MessageChannel.Telegram,
MessageChannel.Feishu,
MessageChannel.Slack,
MessageChannel.Discord,
NotificationChannel.Telegram,
NotificationChannel.Feishu,
NotificationChannel.Slack,
NotificationChannel.Discord,
}
for channel in MessageChannel:
for channel in NotificationChannel:
self.assertEqual(
ChannelCapabilityManager.supports_capability(
channel, ChannelCapability.PROCESSING_STATUS
@@ -32,7 +32,7 @@ class TestMessageProcessingStatus(unittest.TestCase):
def test_slack_processing_status_uses_reaction(self):
module = SlackModule()
module._channel = MessageChannel.Slack
module._channel = NotificationChannel.Slack
client = MagicMock()
client.add_reaction.return_value = True
client.remove_reaction.return_value = True
@@ -44,7 +44,7 @@ class TestMessageProcessingStatus(unittest.TestCase):
patch.object(module, "get_instance", return_value=client),
):
status = module.mark_message_processing_started(
channel=MessageChannel.Slack,
channel=NotificationChannel.Slack,
source="slack-main",
userid="U01",
message_id="1710000000.000100",
@@ -52,7 +52,7 @@ class TestMessageProcessingStatus(unittest.TestCase):
text="hello",
)
removed = module.mark_message_processing_finished(
channel=MessageChannel.Slack,
channel=NotificationChannel.Slack,
source="slack-main",
userid="U01",
status=status,
@@ -99,7 +99,7 @@ class TestMessageProcessingStatus(unittest.TestCase):
def test_discord_processing_status_starts_and_stops_typing(self):
module = DiscordModule()
module._channel = MessageChannel.Discord
module._channel = NotificationChannel.Discord
client = MagicMock()
client.start_typing.return_value = True
client.stop_typing.return_value = True
@@ -111,7 +111,7 @@ class TestMessageProcessingStatus(unittest.TestCase):
patch.object(module, "get_instance", return_value=client),
):
status = module.mark_message_processing_started(
channel=MessageChannel.Discord,
channel=NotificationChannel.Discord,
source="discord-main",
userid="10001",
message_id="20002",
@@ -119,7 +119,7 @@ class TestMessageProcessingStatus(unittest.TestCase):
text="hello",
)
finished = module.mark_message_processing_finished(
channel=MessageChannel.Discord,
channel=NotificationChannel.Discord,
source="discord-main",
userid="10001",
status=status,
@@ -132,7 +132,7 @@ class TestMessageProcessingStatus(unittest.TestCase):
def test_agent_finish_processing_status_uses_module_interface(self):
status = {
"channel": MessageChannel.Telegram.value,
"channel": NotificationChannel.Telegram.value,
"source": "telegram-main",
"userid": "10001",
"message_id": None,
+7 -7
View File
@@ -12,7 +12,7 @@ import pytest
from app.chain.notification import NotificationChain
from app.modules.wechatclawbot import WechatClawBotModule
from app.schemas.types import MessageChannel, NotificationAction
from app.schemas.types import NotificationChannel, NotificationAction
@pytest.fixture
@@ -23,7 +23,7 @@ def module():
def test_channel_manage_routes_only_matching_channel(module):
"""非本渠道的管理请求返回 None,run_module 分发将继续执行其它模块。"""
result = module.channel_manage(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
action=NotificationAction.STATUS,
)
assert result is None
@@ -32,7 +32,7 @@ def test_channel_manage_routes_only_matching_channel(module):
def test_channel_manage_rejects_unknown_action(module):
"""动作词汇表之外的请求返回统一错误结构。"""
result = module.channel_manage(
channel=MessageChannel.WechatClawBot,
channel=NotificationChannel.WechatClawBot,
action="not_an_action",
)
assert result["success"] is False
@@ -43,7 +43,7 @@ def test_channel_manage_requires_saved_config_without_form_params(module, monkey
"""无任何配置且未提供表单参数时,返回提示保存配置的错误。"""
monkeypatch.setattr(module, "get_instance", lambda name=None: None)
result = module.channel_manage(
channel=MessageChannel.WechatClawBot,
channel=NotificationChannel.WechatClawBot,
action=NotificationAction.TEST_CONNECTION,
)
assert result["success"] is False
@@ -67,7 +67,7 @@ def test_channel_manage_builds_temporary_client_from_form_params(module, monkeyp
)
result = module.channel_manage(
channel=MessageChannel.WechatClawBot,
channel=NotificationChannel.WechatClawBot,
action=NotificationAction.TEST_CONNECTION,
source="预览渠道",
WECHATCLAWBOT_BASE_URL="http://127.0.0.1:1",
@@ -91,7 +91,7 @@ def test_channel_manage_prefers_saved_instance(module, monkeypatch):
monkeypatch.setattr(module, "get_instance", lambda name=None: saved)
result = module.channel_manage(
channel=MessageChannel.WechatClawBot,
channel=NotificationChannel.WechatClawBot,
action=NotificationAction.STATUS,
source="已保存",
)
@@ -113,7 +113,7 @@ def test_channel_manage_migrate_cache_dispatches_without_client(module, monkeypa
)
result = module.channel_manage(
channel=MessageChannel.WechatClawBot,
channel=NotificationChannel.WechatClawBot,
action=NotificationAction.MIGRATE_CACHE,
old_name="旧名",
new_name="新名",
+3 -3
View File
@@ -21,7 +21,7 @@ from app.domain.context import MUSIC_ENTITY_ALBUM, MusicInfo
from app.domain.meta.metamusic import MetaMusic
from app.db.oper.systemconfig import SystemConfigOper
from app.application.messaging.message import MessageTemplateHelper, TemplateContextBuilder, TemplateHelper
from app.schemas.message import Notification
from app.schemas.message import Message
from app.schemas.types import ContentType, SystemConfigKey
MUSIC_ORGANIZE_TEMPLATE = """
@@ -184,7 +184,7 @@ def test_message_renders_from_db_config(notification_templates: SystemConfigOper
SystemConfigKey.NotificationTemplates,
{"organizeSuccess": MUSIC_ORGANIZE_TEMPLATE},
)
message = Notification(ctype=ContentType.OrganizeSuccess)
message = Message(ctype=ContentType.OrganizeSuccess)
MessageTemplateHelper.render(message, **MUSIC_CONTEXT)
@@ -199,7 +199,7 @@ def test_message_without_template_config_stays_unchanged(
数据库中没有模板配置时消息应保持原样不应渲染也不应报错
"""
notification_templates.set(SystemConfigKey.NotificationTemplates, None)
message = Notification(ctype=ContentType.OrganizeSuccess)
message = Message(ctype=ContentType.OrganizeSuccess)
MessageTemplateHelper.render(message, **MUSIC_CONTEXT)
+26 -26
View File
@@ -23,7 +23,7 @@ from app.agent.skills.registry import (
SkillMarketSource,
settings as skill_settings,
)
from app.schemas.types import MessageChannel
from app.schemas.types import NotificationChannel
def _build_skill_zip(skill_dir: str, skill_name: str) -> bytes:
@@ -62,7 +62,7 @@ class TestSkillsCommand(unittest.TestCase):
chain = MessageChain()
skill_interaction_manager.create_or_replace(
user_id="10001",
channel=MessageChannel.Wechat,
channel=NotificationChannel.Wechat,
source="wechat-test",
username="tester",
)
@@ -72,7 +72,7 @@ class TestSkillsCommand(unittest.TestCase):
return_value=True,
) as handle_text, patch.object(chain, "_handle_ai_message") as handle_ai:
chain.handle_message(
channel=MessageChannel.Wechat,
channel=NotificationChannel.Wechat,
source="wechat-test",
userid="10001",
username="tester",
@@ -86,14 +86,14 @@ class TestSkillsCommand(unittest.TestCase):
chain = SkillInteractionHandler(messenger=MessageChain())
skill_interaction_manager.create_or_replace(
user_id="10001",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
username="tester",
)
with patch.object(chain._messenger, "post_message") as post_message:
handled = chain.handle_text_interaction(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -110,7 +110,7 @@ class TestSkillsCommand(unittest.TestCase):
chain = MessageChain()
request = skill_interaction_manager.create_or_replace(
user_id="10001",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
username="tester",
)
@@ -122,7 +122,7 @@ class TestSkillsCommand(unittest.TestCase):
chain._handle_callback(
callback_data=f"skills:{request.request_id}:market",
context=InteractionContext(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
user_id="10001",
username="tester",
@@ -390,7 +390,7 @@ class TestSkillsCommand(unittest.TestCase):
chain = SkillInteractionHandler(messenger=MessageChain())
request = skill_interaction_manager.create_or_replace(
user_id="10001",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
username="tester",
)
@@ -423,7 +423,7 @@ class TestSkillsCommand(unittest.TestCase):
chain = SkillInteractionHandler(messenger=MessageChain())
request = skill_interaction_manager.create_or_replace(
user_id="10001",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
username="tester",
)
@@ -467,7 +467,7 @@ class TestSkillsCommand(unittest.TestCase):
chain = SkillInteractionHandler(messenger=MessageChain())
request = skill_interaction_manager.create_or_replace(
user_id="10001",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
username="tester",
)
@@ -510,7 +510,7 @@ class TestSkillsCommand(unittest.TestCase):
chain = SkillInteractionHandler(messenger=MessageChain())
request = skill_interaction_manager.create_or_replace(
user_id="10001",
channel=MessageChannel.WebAgent,
channel=NotificationChannel.WebAgent,
source="web-agent",
username="tester",
)
@@ -552,7 +552,7 @@ class TestSkillsCommand(unittest.TestCase):
chain = SkillInteractionHandler(messenger=MessageChain())
request = skill_interaction_manager.create_or_replace(
user_id="10001",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
username="tester",
)
@@ -560,7 +560,7 @@ class TestSkillsCommand(unittest.TestCase):
with patch.object(chain, "_render_interaction") as render:
handled = chain.handle_callback_interaction(
callback_data=f"skills:{request.request_id}:search",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -575,7 +575,7 @@ class TestSkillsCommand(unittest.TestCase):
chain = SkillInteractionHandler(messenger=MessageChain())
request = skill_interaction_manager.create_or_replace(
user_id="10001",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
username="tester",
)
@@ -583,7 +583,7 @@ class TestSkillsCommand(unittest.TestCase):
with patch.object(chain, "_render_interaction") as render:
handled = chain.handle_text_interaction(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -600,7 +600,7 @@ class TestSkillsCommand(unittest.TestCase):
chain = SkillInteractionHandler(messenger=MessageChain())
request = skill_interaction_manager.create_or_replace(
user_id="10001",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
username="tester",
)
@@ -609,7 +609,7 @@ class TestSkillsCommand(unittest.TestCase):
with patch.object(chain, "_render_interaction") as render:
handled = chain.handle_text_interaction(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -625,7 +625,7 @@ class TestSkillsCommand(unittest.TestCase):
chain = SkillInteractionHandler(messenger=MessageChain())
request = skill_interaction_manager.create_or_replace(
user_id="10001",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
username="tester",
)
@@ -633,7 +633,7 @@ class TestSkillsCommand(unittest.TestCase):
with patch.object(chain, "_render_interaction") as render:
handled = chain.handle_callback_interaction(
callback_data=f"skills:{request.request_id}:source-add",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -648,7 +648,7 @@ class TestSkillsCommand(unittest.TestCase):
chain = SkillInteractionHandler(messenger=MessageChain())
request = skill_interaction_manager.create_or_replace(
user_id="10001",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
username="tester",
)
@@ -663,7 +663,7 @@ class TestSkillsCommand(unittest.TestCase):
chain._messenger, "post_message"
) as post_message:
handled = chain.handle_text_interaction(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -680,7 +680,7 @@ class TestSkillsCommand(unittest.TestCase):
chain = SkillInteractionHandler(messenger=MessageChain())
request = skill_interaction_manager.create_or_replace(
user_id="10001",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
username="tester",
)
@@ -693,7 +693,7 @@ class TestSkillsCommand(unittest.TestCase):
chain._messenger, "post_message"
) as post_message:
handled = chain.handle_text_interaction(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -710,7 +710,7 @@ class TestSkillsCommand(unittest.TestCase):
chain = SkillInteractionHandler(messenger=MessageChain())
request = skill_interaction_manager.create_or_replace(
user_id="10001",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
username="tester",
)
@@ -754,7 +754,7 @@ class TestSkillsCommand(unittest.TestCase):
chain._messenger, "post_message"
) as post_message:
chain._update_or_post_message(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -766,7 +766,7 @@ class TestSkillsCommand(unittest.TestCase):
)
edit_message.assert_called_once_with(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
message_id=123,
chat_id="456",
+18 -18
View File
@@ -15,7 +15,7 @@ from app.application.messaging.interaction import InteractionContext
from app.chain.site import SiteChain, site_interaction_manager
from app.application.messaging.skill import skill_interaction_manager
from app.chain.subscribe import SubscribeChain, subscribe_interaction_manager
from app.schemas.types import MessageChannel
from app.schemas.types import NotificationChannel
class TestSlashCommandInteractions(unittest.TestCase):
@@ -28,14 +28,14 @@ class TestSlashCommandInteractions(unittest.TestCase):
chain = MessageChain()
skill_interaction_manager.create_or_replace(
user_id="10001",
channel=MessageChannel.Wechat,
channel=NotificationChannel.Wechat,
source="wechat-test",
username="tester",
)
site_interaction_manager.create_or_replace(
user_id="10001",
command="/sites",
channel=MessageChannel.Wechat,
channel=NotificationChannel.Wechat,
source="wechat-test",
username="tester",
)
@@ -47,7 +47,7 @@ class TestSlashCommandInteractions(unittest.TestCase):
"app.chain.message.SkillInteractionHandler.handle_text_interaction"
) as handle_skills:
chain.handle_message(
channel=MessageChannel.Wechat,
channel=NotificationChannel.Wechat,
source="wechat-test",
userid="10001",
username="tester",
@@ -62,14 +62,14 @@ class TestSlashCommandInteractions(unittest.TestCase):
site_interaction_manager.create_or_replace(
user_id="10001",
command="/sites",
channel=MessageChannel.Wechat,
channel=NotificationChannel.Wechat,
source="wechat-test",
username="tester",
)
subscribe_interaction_manager.create_or_replace(
user_id="10001",
command="/subscribes",
channel=MessageChannel.Wechat,
channel=NotificationChannel.Wechat,
source="wechat-test",
username="tester",
)
@@ -81,7 +81,7 @@ class TestSlashCommandInteractions(unittest.TestCase):
"app.chain.message.SiteChain.handle_text_interaction"
) as handle_sites:
chain.handle_message(
channel=MessageChannel.Wechat,
channel=NotificationChannel.Wechat,
source="wechat-test",
userid="10001",
username="tester",
@@ -96,7 +96,7 @@ class TestSlashCommandInteractions(unittest.TestCase):
request = site_interaction_manager.create_or_replace(
user_id="10001",
command="/sites",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
username="tester",
)
@@ -108,7 +108,7 @@ class TestSlashCommandInteractions(unittest.TestCase):
chain._handle_callback(
callback_data=f"sites:{request.request_id}:refresh",
context=InteractionContext(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
user_id="10001",
username="tester",
@@ -122,7 +122,7 @@ class TestSlashCommandInteractions(unittest.TestCase):
request = subscribe_interaction_manager.create_or_replace(
user_id="10001",
command="/subscribes",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
username="tester",
)
@@ -134,7 +134,7 @@ class TestSlashCommandInteractions(unittest.TestCase):
chain._handle_callback(
callback_data=f"subscribes:{request.request_id}:refresh",
context=InteractionContext(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
user_id="10001",
username="tester",
@@ -148,14 +148,14 @@ class TestSlashCommandInteractions(unittest.TestCase):
site_interaction_manager.create_or_replace(
user_id="10001",
command="/sites",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
username="tester",
)
with patch.object(chain, "post_message") as post_message:
handled = chain.handle_text_interaction(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -173,14 +173,14 @@ class TestSlashCommandInteractions(unittest.TestCase):
subscribe_interaction_manager.create_or_replace(
user_id="10001",
command="/subscribes",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
username="tester",
)
with patch.object(chain, "post_message") as post_message:
handled = chain.handle_text_interaction(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -210,7 +210,7 @@ class TestSlashCommandInteractions(unittest.TestCase):
with patch("app.chain.site.SiteOper.list", return_value=fake_sites), patch.object(
chain, "post_message"
) as post_message:
chain.remote_list(channel=MessageChannel.Web, userid="u1", source="web")
chain.remote_list(channel=NotificationChannel.Web, userid="u1", source="web")
notification = post_message.call_args[0][0]
self.assertIn("| ID | 站点 | 状态 | Cookie | 渲染 | 域名 |", notification.text)
@@ -234,7 +234,7 @@ class TestSlashCommandInteractions(unittest.TestCase):
with patch(
"app.chain.subscribe.SubscribeOper.list", return_value=fake_subscribes
), patch.object(chain, "post_message") as post_message:
chain.remote_list(channel=MessageChannel.Web, userid="u1", source="web")
chain.remote_list(channel=NotificationChannel.Web, userid="u1", source="web")
notification = post_message.call_args[0][0]
self.assertIn("| ID | 名称 | 类型 | 年份 | 季/进度 | 状态 |", notification.text)
@@ -256,7 +256,7 @@ class TestUpdateOrPostMessage(unittest.TestCase):
update_or_post_message(
chain=chain,
channel=MessageChannel.Feishu,
channel=NotificationChannel.Feishu,
source="feishu-main",
userid="ou_user",
username="tester",
+1 -1
View File
@@ -242,7 +242,7 @@ def _load_subscribe_chain_class():
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
schemas_module.Notification = _Notification
schemas_module.Message = _Notification
schemas_module.Subscribe = _SubscribeSchema
schemas_module.NotExistMediaInfo = _NotExistMediaInfo
schemas_module.SubscribeEpisodeInfo = _SubscribeEpisodeInfo
+2 -2
View File
@@ -13,7 +13,7 @@ from app.db.oper.message import MessageOper
from app.modules.indexer import IndexerModule
from app.modules.indexer.parser.sunnypt import SunnyPTSiteUserInfo
from app.modules.indexer.spider.sunnypt import SunnyPTSpider
from app.schemas import MediaSource, MediaType, NotificationType
from app.schemas import MediaSource, MediaType, MessageType
class _FakeResponse:
@@ -319,7 +319,7 @@ def test_site_messages_are_deduplicated_by_persisted_source(monkeypatch):
duplicate_source = "sunnypt-message:9001-dedup-test"
MessageOper().add(
source=duplicate_source,
mtype=NotificationType.SiteMessage,
mtype=MessageType.SiteMessage,
title="existing",
text="existing",
)
+3 -3
View File
@@ -12,7 +12,7 @@ sys.modules.setdefault("psutil", ModuleType("psutil"))
from app.chain.message import MessageChain
from app.application.messaging.message import MessageQueueManager
from app.schemas import Notification
from app.schemas import Message
from app.foundation.identity import (
SYSTEM_INTERNAL_USER_ID,
is_internal_user_id,
@@ -29,7 +29,7 @@ class TestSystemNotificationDispatch(unittest.TestCase):
def test_post_message_normalizes_internal_userid_before_queueing(self):
chain = MessageChain()
message = Notification(
message = Message(
userid=SYSTEM_INTERNAL_USER_ID,
username="admin",
title="后台报告",
@@ -54,7 +54,7 @@ class TestSystemNotificationDispatch(unittest.TestCase):
def test_send_direct_message_normalizes_internal_userid(self):
chain = MessageChain()
message = Notification(
message = Message(
userid=SYSTEM_INTERNAL_USER_ID,
username="admin",
title="后台报告",
+16 -16
View File
@@ -13,8 +13,8 @@ from app.domain.context import MediaInfo, Context, TorrentInfo
from app.domain.metainfo import MetaInfo
from app.modules.telegram import TelegramModule
from app.modules.telegram.telegram import Telegram
from app.schemas import Notification
from app.schemas.types import MessageChannel
from app.schemas import Message
from app.schemas.types import NotificationChannel
from app.schemas.types import MediaType
@@ -346,8 +346,8 @@ def test_telegram_module_passes_parse_mode_to_client():
module, "get_instance", return_value=client
):
module.post_message(
Notification(
channel=MessageChannel.Telegram,
Message(
channel=NotificationChannel.Telegram,
source="telegram-test",
title="HTML",
text="<b>正文</b>",
@@ -374,8 +374,8 @@ def test_telegram_module_plain_post_message_keeps_chat_without_editing_source_me
module, "get_instance", return_value=client
):
module.post_message(
Notification(
channel=MessageChannel.Telegram,
Message(
channel=NotificationChannel.Telegram,
source="telegram-test",
title="Agent 回复",
text="处理完成",
@@ -406,8 +406,8 @@ def test_telegram_module_passes_force_reply_to_client():
module, "get_instance", return_value=client
):
module.post_message(
Notification(
channel=MessageChannel.Telegram,
Message(
channel=NotificationChannel.Telegram,
source="telegram-test",
title="请输入目录",
text="回复目录路径",
@@ -441,8 +441,8 @@ def test_telegram_module_force_reply_sends_new_prompt_message():
module, "get_instance", return_value=client
):
module.post_message(
Notification(
channel=MessageChannel.Telegram,
Message(
channel=NotificationChannel.Telegram,
source="telegram-test",
title="请输入目录",
text="回复目录路径",
@@ -480,8 +480,8 @@ def test_telegram_module_direct_force_reply_sends_new_prompt_message():
module, "get_instance", return_value=client
):
response = module.send_direct_message(
Notification(
channel=MessageChannel.Telegram,
Message(
channel=NotificationChannel.Telegram,
source="telegram-test",
title="请输入目录",
text="回复目录路径",
@@ -521,8 +521,8 @@ def test_telegram_module_direct_buttons_keep_new_message_behavior():
module, "get_instance", return_value=client
):
response = module.send_direct_message(
Notification(
channel=MessageChannel.Telegram,
Message(
channel=NotificationChannel.Telegram,
source="telegram-test",
title="请选择",
text="请选择一个操作",
@@ -560,8 +560,8 @@ def test_telegram_module_plain_direct_message_keeps_userid_target():
module, "get_instance", return_value=client
):
response = module.send_direct_message(
Notification(
channel=MessageChannel.Telegram,
Message(
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
title="普通通知",
+26 -26
View File
@@ -11,7 +11,7 @@ from app.chain.message import MessageChain
from app.command import Command, _finish_command_processing_status
from app.modules.telegram import TelegramModule
from app.modules.telegram.telegram import Telegram
from app.schemas.types import MessageChannel
from app.schemas.types import NotificationChannel
def _wait_until(predicate, timeout: float = 1.0) -> bool:
@@ -156,7 +156,7 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
Telegram 通过模块处理状态接口启动 typing 保活
"""
module = TelegramModule()
module._channel = MessageChannel.Telegram
module._channel = NotificationChannel.Telegram
client = Mock()
client.start_typing.return_value = True
@@ -164,7 +164,7 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
module, "get_config", return_value=SimpleNamespace(name="telegram-test")
), patch.object(module, "get_instance", return_value=client):
status = module.mark_message_processing_started(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
chat_id="-100",
@@ -178,7 +178,7 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
chain = MessageChain.__new__(MessageChain)
chain.eventmanager = Mock()
status = MessageChain._ProcessingStatus(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
chat_id="-100",
@@ -191,7 +191,7 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
chain, "_mark_message_processing_finished"
) as finish_status:
chain.handle_message(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -217,10 +217,10 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
event_data={
"cmd": "/sites",
"user": "10001",
"channel": MessageChannel.Telegram,
"channel": NotificationChannel.Telegram,
"source": "telegram-test",
"processing_status": {
"channel": MessageChannel.Telegram.value,
"channel": NotificationChannel.Telegram.value,
"source": "telegram-test",
"userid": "10001",
"chat_id": "-100",
@@ -240,7 +240,7 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
def test_finish_command_processing_status_uses_module_interface(self):
status = {
"channel": MessageChannel.Telegram.value,
"channel": NotificationChannel.Telegram.value,
"source": "telegram-test",
"userid": "10001",
"chat_id": "-100",
@@ -272,7 +272,7 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
chain, "_mark_message_processing_finished"
) as finish_status:
chain.handle_message(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -286,7 +286,7 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
self.assertNotIn("processing_status", process_message.call_args.kwargs)
self.assertEqual(
process_message.call_args.kwargs["channel"],
MessageChannel.Telegram.value,
NotificationChannel.Telegram.value,
)
self.assertEqual(process_message.call_args.kwargs["source"], "telegram-test")
self.assertEqual(process_message.call_args.kwargs["original_chat_id"], "-100")
@@ -298,12 +298,12 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
session_id="session-1",
user_id="10001",
message="第一条",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
original_chat_id="-100",
)
status = {
"channel": MessageChannel.Telegram.value,
"channel": NotificationChannel.Telegram.value,
"source": "telegram-test",
"userid": "10001",
"chat_id": "-100",
@@ -328,13 +328,13 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
session_id="session-1",
user_id="10001",
message="第一条",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
original_message_id="10",
original_chat_id="-100",
)
status = {
"channel": MessageChannel.Telegram.value,
"channel": NotificationChannel.Telegram.value,
"source": "telegram-test",
"userid": "10001",
"message_id": "10",
@@ -352,7 +352,7 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
result = await _async_start_processing_status(task)
self.assertEqual(calls, [{
"channel": MessageChannel.Telegram,
"channel": NotificationChannel.Telegram,
"source": "telegram-test",
"userid": "10001",
"message_id": "10",
@@ -366,7 +366,7 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
def test_callback_stops_typing_when_message_handler_returns(self):
chain = MessageChain.__new__(MessageChain)
status = MessageChain._ProcessingStatus(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
chat_id="-100",
@@ -379,7 +379,7 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
chain, "_mark_message_processing_finished"
) as finish_status:
chain.handle_message(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -388,7 +388,7 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
)
finish_status.assert_called_once_with(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
status=status,
@@ -399,7 +399,7 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
def test_chain_finishes_processing_through_module_interface(self):
chain = MessageChain.__new__(MessageChain)
status = MessageChain._ProcessingStatus(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
chat_id="-100",
@@ -408,7 +408,7 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
with patch.object(chain, "finish_message_processing_status") as finish_status:
chain._mark_message_processing_finished(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
status=status,
@@ -417,7 +417,7 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
finish_status.assert_called_once_with(
status=status.to_dict(),
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
message_id=None,
@@ -428,7 +428,7 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
async def _run():
manager = AgentManager()
status = {
"channel": MessageChannel.Telegram.value,
"channel": NotificationChannel.Telegram.value,
"source": "telegram-test",
"userid": "10001",
"chat_id": "-100",
@@ -457,14 +457,14 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
manager = AgentManager()
manager._session_queues["session-1"] = asyncio.Queue()
first_status = {
"channel": MessageChannel.Telegram.value,
"channel": NotificationChannel.Telegram.value,
"source": "telegram-test",
"userid": "10001",
"chat_id": "-100",
"metadata": {"kind": "typing", "seq": 1},
}
second_status = {
"channel": MessageChannel.Telegram.value,
"channel": NotificationChannel.Telegram.value,
"source": "telegram-test",
"userid": "10001",
"chat_id": "-100",
@@ -474,7 +474,7 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
session_id="session-1",
user_id="10001",
message="第一条",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
original_chat_id="-100",
))
@@ -482,7 +482,7 @@ class TestTelegramTypingLifecycle(unittest.TestCase):
session_id="session-1",
user_id="10001",
message="第二条",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-test",
original_chat_id="-100",
))
+7 -7
View File
@@ -15,7 +15,7 @@ from app.chain.message import MessageChain
from app.chain.transfer import TransferChain
from app.application.messaging.interaction import InteractionContext
from app.runtime.config import settings
from app.schemas.types import MessageChannel
from app.schemas.types import NotificationChannel
class TestTransferFailedRetryButtons(unittest.TestCase):
@@ -42,7 +42,7 @@ class TestTransferFailedRetryButtons(unittest.TestCase):
with patch.object(chain, "post_message") as post_message:
chain.remote_transfer(
"12",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
userid="10001",
source="telegram-test",
)
@@ -59,7 +59,7 @@ class TestTransferFailedRetryButtons(unittest.TestCase):
chain._handle_callback(
callback_data="transfer_retry_12",
context=InteractionContext(
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
user_id="10001",
username="tester",
@@ -68,7 +68,7 @@ class TestTransferFailedRetryButtons(unittest.TestCase):
transfer_cls.return_value.handle_failed_transfer_callback.assert_called_once_with(
callback_data="transfer_retry_12",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -81,7 +81,7 @@ class TestTransferFailedRetryButtons(unittest.TestCase):
with patch.object(chain, "post_message") as post_message:
handled = chain.handle_failed_transfer_callback(
callback_data="transfer_retry_12",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -141,7 +141,7 @@ class TestTransferFailedRetryButtons(unittest.TestCase):
with patch.object(chain, "post_message") as post_message:
chain.handle_failed_transfer_callback(
callback_data="transfer_ai_retry_34",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
@@ -216,7 +216,7 @@ class TestTransferFailedRetryButtons(unittest.TestCase):
):
chain.handle_failed_transfer_callback(
callback_data="transfer_ai_retry_35",
channel=MessageChannel.Telegram,
channel=NotificationChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
+2 -2
View File
@@ -5,7 +5,7 @@ from app.domain.context import MediaInfo
from app.domain.meta.metabase import MetaBase
from app.schemas import TransferInfo
from app.schemas.tmdb import TmdbEpisode
from app.schemas.types import ContentType, MediaType, NotificationType
from app.schemas.types import ContentType, MediaType, MessageType
def test_send_transfer_message_passes_episode_info_to_template_context() -> None:
@@ -41,7 +41,7 @@ def test_send_transfer_message_passes_episode_info_to_template_context() -> None
)
message = post_message.call_args.args[0]
assert message.mtype == NotificationType.Organize
assert message.mtype == MessageType.Organize
assert message.ctype == ContentType.OrganizeSuccess
assert post_message.call_args.kwargs["episodes_info"] is episodes_info
assert post_message.call_args.kwargs["season_episode"] == "S01 E01"
+62 -62
View File
@@ -12,17 +12,17 @@ from app.api.endpoints.agent import (
_WebAgentMoviePilotAgent,
_WebAgentEventPublisher,
_WEB_AGENT_FILE_REGISTRY,
_WEB_AGENT_NOTICE_QUEUES,
_WEB_AGENT_MESSAGE_QUEUES,
_apply_web_agent_display_event,
_build_web_agent_input_attachments,
_build_web_agent_notification_events,
_build_web_agent_message_events,
_build_web_agent_command_items,
_build_web_agent_session_id,
_build_web_agent_traditional_callback_payload,
_build_web_agent_display_message_from_events,
_collect_web_agent_traditional_events,
_dispatch_web_agent_notice_event,
_extract_web_agent_notification_from_event_data,
_dispatch_web_agent_message_event,
_extract_web_agent_message_from_event_data,
_has_web_agent_traditional_interaction,
_prepare_web_agent_audio_attachment_path,
_transcribe_web_agent_audio_refs,
@@ -37,8 +37,8 @@ from app.application.messaging.agent import build_web_agent_message_update_event
from app.application.messaging.agent import AgentInteractionOption, agent_interaction_manager
from app.application.messaging.skill import skill_interaction_manager
from app.chain.message import MessageChain
from app.schemas.message import ChannelCapability, ChannelCapabilityManager
from app.schemas.types import EventType, MessageChannel, NotificationType
from app.schemas.notification import ChannelCapability, ChannelCapabilityManager
from app.schemas.types import EventType, NotificationChannel, MessageType
def test_split_web_agent_output_extracts_verbose_tool_message():
@@ -140,7 +140,7 @@ def test_build_web_agent_session_id_reuses_accessible_history():
session_id="telegram-session",
user_id="telegram-user",
username="tester",
channel=MessageChannel.Telegram.value,
channel=NotificationChannel.Telegram.value,
source="telegram-main",
messages=[],
title="Telegram 会话",
@@ -345,7 +345,7 @@ def test_has_web_agent_traditional_interaction_detects_pending_skills():
try:
skill_interaction_manager.create_or_replace(
user_id="1",
channel=MessageChannel.WebAgent,
channel=NotificationChannel.WebAgent,
source="web-agent",
username="admin",
)
@@ -361,7 +361,7 @@ def test_web_agent_admin_context_uses_current_user_id():
agent = _WebAgentMoviePilotAgent(
session_id="web-agent:session",
user_id="7",
channel=MessageChannel.WebAgent.value,
channel=NotificationChannel.WebAgent.value,
source="web-agent",
username="normal-user",
replay_mode=ReplyMode.CAPTURE_ONLY,
@@ -397,7 +397,7 @@ def test_web_agent_output_callback_receives_only_new_text():
agent = _WebAgentMoviePilotAgent(
session_id="web-agent:incremental-output",
user_id="7",
channel=MessageChannel.WebAgent.value,
channel=NotificationChannel.WebAgent.value,
source="web-agent",
username="admin",
replay_mode=ReplyMode.CAPTURE_ONLY,
@@ -417,7 +417,7 @@ def test_web_agent_tool_summary_is_emitted_before_following_text():
agent = _WebAgentMoviePilotAgent(
session_id="web-agent:tool-order",
user_id="7",
channel=MessageChannel.WebAgent.value,
channel=NotificationChannel.WebAgent.value,
source="web-agent",
username="admin",
replay_mode=ReplyMode.CAPTURE_ONLY,
@@ -433,31 +433,31 @@ def test_web_agent_tool_summary_is_emitted_before_following_text():
def test_web_agent_channel_supports_streaming_and_attachments():
"""WebAgent 渠道应声明流式、多媒体和文件发送能力。"""
assert ChannelCapabilityManager.supports_capability(
MessageChannel.WebAgent, ChannelCapability.INLINE_BUTTONS
NotificationChannel.WebAgent, ChannelCapability.INLINE_BUTTONS
)
assert ChannelCapabilityManager.supports_capability(
MessageChannel.WebAgent, ChannelCapability.CALLBACK_QUERIES
NotificationChannel.WebAgent, ChannelCapability.CALLBACK_QUERIES
)
assert ChannelCapabilityManager.supports_capability(
MessageChannel.WebAgent, ChannelCapability.MESSAGE_EDITING
NotificationChannel.WebAgent, ChannelCapability.MESSAGE_EDITING
)
assert ChannelCapabilityManager.supports_capability(
MessageChannel.WebAgent, ChannelCapability.IMAGES
NotificationChannel.WebAgent, ChannelCapability.IMAGES
)
assert ChannelCapabilityManager.supports_capability(
MessageChannel.WebAgent, ChannelCapability.AUDIO_OUTPUT
NotificationChannel.WebAgent, ChannelCapability.AUDIO_OUTPUT
)
assert ChannelCapabilityManager.supports_capability(
MessageChannel.WebAgent, ChannelCapability.FILE_SENDING
NotificationChannel.WebAgent, ChannelCapability.FILE_SENDING
)
def test_build_web_agent_notification_events_extracts_image():
def test_build_web_agent_message_events_extracts_image():
"""Agent 工具发送图片消息时应转换为图片附件事件。"""
events = _build_web_agent_notification_events(
schemas.Notification(
channel=MessageChannel.WebAgent,
mtype=NotificationType.Agent,
events = _build_web_agent_message_events(
schemas.Message(
channel=NotificationChannel.WebAgent,
mtype=MessageType.Agent,
title="海报",
text="已找到图片",
image="https://example.com/poster.jpg",
@@ -479,44 +479,44 @@ def test_build_web_agent_notification_events_extracts_image():
]
def test_extract_web_agent_notification_supports_wrapped_message_event():
"""NoticeMessage 包装 Notification 时应仍能解析为 WebAgent 通知。"""
notification = schemas.Notification(
channel=MessageChannel.WebAgent,
def test_extract_web_agent_message_supports_wrapped_message_event():
"""NoticeMessage 包装 Message 时应仍能解析为 WebAgent 通知。"""
message = schemas.Message(
channel=NotificationChannel.WebAgent,
source="web-agent",
title="会话状态",
userid="1",
)
extracted = _extract_web_agent_notification_from_event_data(
{"message": notification, "current_time": "2026-06-26 09:18:38"}
extracted = _extract_web_agent_message_from_event_data(
{"message": message, "current_time": "2026-06-26 09:18:38"}
)
assert extracted == notification
assert extracted == message
def test_dispatch_web_agent_notice_event_accepts_wrapped_message_event():
def test_dispatch_web_agent_message_event_accepts_wrapped_message_event():
"""WebAgent 等待队列应接收 message 包装格式的 NoticeMessage 事件。"""
notice_queue = Queue()
_WEB_AGENT_NOTICE_QUEUES["1"] = [notice_queue]
notification = schemas.Notification(
channel=MessageChannel.WebAgent,
_WEB_AGENT_MESSAGE_QUEUES["1"] = [notice_queue]
message = schemas.Message(
channel=NotificationChannel.WebAgent,
source="web-agent",
title="会话状态",
userid="1",
)
try:
_dispatch_web_agent_notice_event(
_dispatch_web_agent_message_event(
Event(
EventType.NoticeMessage,
{"message": notification, "current_time": "2026-06-26 09:18:38"},
{"message": message, "current_time": "2026-06-26 09:18:38"},
)
)
finally:
_WEB_AGENT_NOTICE_QUEUES.pop("1", None)
_WEB_AGENT_MESSAGE_QUEUES.pop("1", None)
assert notice_queue.get_nowait() == notification
assert notice_queue.get_nowait() == message
def test_collect_web_agent_traditional_events_does_not_emit_submit_hint():
@@ -542,15 +542,15 @@ def test_collect_web_agent_traditional_events_does_not_emit_submit_hint():
assert events == []
def test_build_web_agent_notification_events_registers_local_file(tmp_path):
def test_build_web_agent_message_events_registers_local_file(tmp_path):
"""Agent 工具发送本地文件时应生成可下载附件事件。"""
file_path = tmp_path / "report.txt"
file_path.write_text("hello", encoding="utf-8")
events = _build_web_agent_notification_events(
schemas.Notification(
channel=MessageChannel.WebAgent,
mtype=NotificationType.Agent,
events = _build_web_agent_message_events(
schemas.Message(
channel=NotificationChannel.WebAgent,
mtype=MessageType.Agent,
file_path=str(file_path),
file_name="report.txt",
)
@@ -566,15 +566,15 @@ def test_build_web_agent_notification_events_registers_local_file(tmp_path):
assert attachment["url"].startswith("message/agent/file/")
def test_build_web_agent_notification_events_registers_voice_attachment(tmp_path):
def test_build_web_agent_message_events_registers_voice_attachment(tmp_path):
"""Agent 工具发送语音时应转换为可播放的音频附件事件。"""
voice_path = tmp_path / "reply.wav"
voice_path.write_bytes(b"wav-bytes")
events = _build_web_agent_notification_events(
schemas.Notification(
channel=MessageChannel.WebAgent,
mtype=NotificationType.Agent,
events = _build_web_agent_message_events(
schemas.Message(
channel=NotificationChannel.WebAgent,
mtype=MessageType.Agent,
text="你好",
voice_path=str(voice_path),
)
@@ -688,9 +688,9 @@ def test_web_agent_stream_binds_session_to_agent_manager():
"""更新当前 SSE 受保护输出回调。"""
self.protected_output_callback = protected_output_callback
def set_notification_callback(self, notification_callback):
def set_message_callback(self, message_callback):
"""更新当前 SSE 通知回调。"""
self.notification_callback = notification_callback
self.message_callback = message_callback
async def process(self, message, **kwargs):
"""模拟一次 WebAgent 推理输出。"""
@@ -755,7 +755,7 @@ def test_web_agent_stream_emits_secret_result_only_as_protected_event():
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
self._pending_secret_confirmation = SimpleNamespace(
channel=MessageChannel.WebAgent.value,
channel=NotificationChannel.WebAgent.value,
source="web-agent",
original_chat_id="",
)
@@ -767,8 +767,8 @@ def test_web_agent_stream_emits_secret_result_only_as_protected_event():
def set_output_callback(self, output_callback):
self.output_callback = output_callback
def set_notification_callback(self, notification_callback):
self.notification_callback = notification_callback
def set_message_callback(self, message_callback):
self.message_callback = message_callback
def set_protected_output_callback(self, protected_output_callback):
self.protected_output_callback = protected_output_callback
@@ -789,7 +789,7 @@ def test_web_agent_stream_emits_secret_result_only_as_protected_event():
session_id=session_id,
user_id="1",
username="admin",
channel=MessageChannel.WebAgent.value,
channel=NotificationChannel.WebAgent.value,
source="web-agent",
messages=existing_messages,
client_session_id=payload.session_id,
@@ -797,7 +797,7 @@ def test_web_agent_stream_emits_secret_result_only_as_protected_event():
agent_manager.active_agents[session_id] = FakeProtectedAgent(
session_id=session_id,
user_id="1",
channel=MessageChannel.WebAgent.value,
channel=NotificationChannel.WebAgent.value,
source="web-agent",
username="admin",
)
@@ -858,7 +858,7 @@ def test_web_agent_cancel_keeps_existing_display_history():
session_id=session_id,
user_id="1",
username="admin",
channel=MessageChannel.WebAgent.value,
channel=NotificationChannel.WebAgent.value,
source="web-agent",
messages=existing_messages,
client_session_id=payload.session_id,
@@ -977,7 +977,7 @@ def test_web_agent_stream_drops_secret_result_after_disconnect():
session_id=session_id,
user_id="1",
username="admin",
channel=MessageChannel.WebAgent.value,
channel=NotificationChannel.WebAgent.value,
source="web-agent",
messages=existing_messages,
client_session_id=payload.session_id,
@@ -1298,12 +1298,12 @@ async def _collect_streaming_response(response):
return chunks
def test_build_web_agent_notification_events_extracts_choice_card():
def test_build_web_agent_message_events_extracts_choice_card():
"""Agent 按钮通知应转换为 Web 选择卡片事件而非普通文本。"""
events = _build_web_agent_notification_events(
schemas.Notification(
channel=MessageChannel.WebAgent,
mtype=NotificationType.Agent,
events = _build_web_agent_message_events(
schemas.Message(
channel=NotificationChannel.WebAgent,
mtype=MessageType.Agent,
title="需要你的选择",
text="请选择要执行的操作",
buttons=[
@@ -1368,7 +1368,7 @@ def test_resolve_web_agent_choice_payload_returns_next_message():
request = agent_interaction_manager.create_request(
session_id="web-agent:session",
user_id="1",
channel=MessageChannel.WebAgent.value,
channel=NotificationChannel.WebAgent.value,
source="web-agent",
username="admin",
title="需要你的选择",