refactor(messaging): 拆分用户交互模块到 application/messaging 层

- 新增 application/messaging 交互层:router.py 统一会话优先级与回调分发,
  site/subscribe/skill/media/plugin 各交互状态与视图从 Chain 迁出
- MessageChain 改为通过 InteractionRouter 派发文本会话与按钮回调,
  新增结构化 callback_data 通道(兼容 CALLBACK: 文本前缀)
- Transfer 失败重试/AI 接管回调归入 TransferChain
- MediaInteractionChain 拆出为 app/chain/interaction.py(旧路径保留兼容别名)
- WebAgent Endpoint 去重,统一使用 agent.py 回调协议函数
- 删除 app/chain/skills.py(交互逻辑并入 SkillInteractionHandler)
- 同步更新架构文档与测试,全量 4476 通过
This commit is contained in:
jxxghp
2026-08-15 16:36:39 +08:00
parent 5a1808592a
commit dd38c16400
30 changed files with 4894 additions and 4230 deletions
+30 -19
View File
@@ -3,11 +3,17 @@ from unittest.mock import patch
import pytest
from app.chain.message import MediaInteractionChain, MessageChain
from app.chain.message import MessageChain
from app.chain.interaction import MediaInteractionChain
from app.runtime.events import EventManager
from app.domain.context import Context, MediaInfo, TorrentInfo
from app.domain.meta.metabase import MetaBase
from app.application.messaging.interaction import media_interaction_manager, plugin_input_interaction_manager
from app.application.messaging.interaction import InteractionContext
from app.application.messaging.media import media_interaction_manager
from app.application.messaging.plugin import (
PluginInputInteractionHandler,
plugin_input_interaction_manager,
)
from app.schemas import CommingMessage, TransferDirectoryConf
from app.schemas.types import EventType, MediaSource, MediaType, MessageChannel
@@ -175,7 +181,7 @@ def test_message_routes_text_reply_to_media_interaction_before_ai():
assert request is not None
with patch.object(chain, "_record_user_message"), patch(
"app.chain.message.MediaInteractionChain.handle_text_interaction",
"app.chain.interaction.MediaInteractionChain.handle_text_interaction",
return_value=True,
) as handle_text, patch.object(chain, "_handle_ai_message") as handle_ai:
chain.handle_message(
@@ -270,8 +276,9 @@ def test_handle_message_keeps_legacy_positional_images_argument():
chain = MessageChain()
images = [CommingMessage.MessageImage(ref="tg://file_id/photo-1")]
with patch.object(
chain, "_handle_plugin_input_interaction", return_value=False
with patch(
"app.chain.message.PluginInputInteractionHandler.handle_text",
return_value=False,
), patch.object(
chain, "_mark_message_processing_started", return_value=None
), patch.object(
@@ -319,7 +326,7 @@ def test_plugin_input_session_captures_plain_text_before_media_interaction():
)
with patch.object(chain, "_record_user_message"), patch(
"app.chain.message.MediaInteractionChain.handle_text_interaction",
"app.chain.interaction.MediaInteractionChain.handle_text_interaction",
return_value=True,
) as handle_media, patch.object(chain.eventmanager, "send_event") as send_event:
chain.handle_message(
@@ -518,11 +525,13 @@ def test_plugin_input_session_ignores_none_text_messages():
)
image = CommingMessage.MessageImage(ref="https://example.invalid/image.jpg")
handled = chain._handle_plugin_input_interaction(
channel=MessageChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
handled = PluginInputInteractionHandler(messenger=chain).handle_text(
context=InteractionContext(
channel=MessageChannel.Telegram,
source="telegram-test",
user_id="10001",
username="tester",
),
text=None,
images=[image],
)
@@ -1547,7 +1556,7 @@ def test_noai_prefix_starts_traditional_search_when_global_ai_enabled():
"app.chain.media.MediaChain.search",
return_value=(meta, medias),
) as search_media, patch(
"app.chain.message.MediaInteractionChain.post_medias_message"
"app.chain.interaction.MediaInteractionChain.post_medias_message"
) as post_medias_message, patch.object(
chain, "_handle_ai_message"
) as handle_ai:
@@ -1591,7 +1600,7 @@ def test_noai_prefix_preserves_traditional_interaction_priority_after_search():
), patch(
"app.chain.message.settings.AI_AGENT_GLOBAL", True
), patch(
"app.chain.message.MediaInteractionChain.handle_text_interaction",
"app.chain.interaction.MediaInteractionChain.handle_text_interaction",
return_value=True,
) as handle_text, patch.object(chain, "_handle_ai_message") as handle_ai:
chain.handle_message(
@@ -1622,15 +1631,17 @@ def test_callback_routes_to_media_interaction_chain():
)
with patch(
"app.chain.message.MediaInteractionChain.handle_callback_interaction",
"app.chain.interaction.MediaInteractionChain.handle_callback_interaction",
return_value=True,
) as handle_callback:
chain._handle_callback(
text=f"CALLBACK:media:{request.request_id}:page-next",
channel=MessageChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
callback_data=f"media:{request.request_id}:page-next",
context=InteractionContext(
channel=MessageChannel.Telegram,
source="telegram-test",
user_id="10001",
username="tester",
),
)
handle_callback.assert_called_once()