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
+15 -15
View File
@@ -5,12 +5,12 @@ from app.application.messaging.agent import register_channel_admin_resolver, res
from app.runtime.log import logger
from app.modules import _ModuleBase, _MessageBase
from app.modules.feishu.feishu import Feishu
from app.schemas import CommingMessage, MessageChannel, MessageResponse, Notification
from app.schemas import IncomingMessage, NotificationChannel, MessageResponse, Message
from app.schemas.types import ModuleType
register_channel_admin_resolver(
MessageChannel.Feishu,
NotificationChannel.Feishu,
lambda config: resolve_config_principal_ids(
config, "FEISHU_ADMINS", "FEISHU_OPEN_ID"
),
@@ -20,7 +20,7 @@ register_channel_admin_resolver(
class FeishuModule(_ModuleBase, _MessageBase[Feishu]):
def init_module(self) -> None:
super().init_service(service_name=Feishu.__name__.lower(), service_type=Feishu)
self._channel = MessageChannel.Feishu
self._channel = NotificationChannel.Feishu
@staticmethod
def get_name() -> str:
@@ -31,8 +31,8 @@ class FeishuModule(_ModuleBase, _MessageBase[Feishu]):
return ModuleType.Notification
@staticmethod
def get_subtype() -> MessageChannel:
return MessageChannel.Feishu
def get_subtype() -> NotificationChannel:
return NotificationChannel.Feishu
@staticmethod
def get_priority() -> int:
@@ -61,7 +61,7 @@ class FeishuModule(_ModuleBase, _MessageBase[Feishu]):
@staticmethod
def _resolve_message_target(
message: Notification,
message: Message,
) -> Tuple[Optional[str], Optional[str], Optional[str]]:
"""解析发送目标:交互式回复优先回到原会话(群聊@回复必须回原群),其次 open_id,最后回退 user_id 或 chat_id。"""
userid = str(message.userid).strip() if message.userid else None
@@ -94,7 +94,7 @@ class FeishuModule(_ModuleBase, _MessageBase[Feishu]):
def message_parser(
self, source: str, body: Any, form: Any, args: Any
) -> Optional[CommingMessage]:
) -> Optional[IncomingMessage]:
client_config = self.get_config(source)
if not client_config:
return None
@@ -103,7 +103,7 @@ class FeishuModule(_ModuleBase, _MessageBase[Feishu]):
return None
return client.parse_message(body)
def post_message(self, message: Notification, **kwargs) -> None:
def post_message(self, message: Message, **kwargs) -> None:
for conf in self.get_configs().values():
if not self.check_message(message, conf.name):
continue
@@ -156,7 +156,7 @@ class FeishuModule(_ModuleBase, _MessageBase[Feishu]):
original_message_id=str(message.original_message_id) if message.original_message_id else None,
)
def post_medias_message(self, message: Notification, medias: List[MediaInfo]) -> None:
def post_medias_message(self, message: Message, medias: List[MediaInfo]) -> None:
for conf in self.get_configs().values():
if not self.check_message(message, conf.name):
continue
@@ -171,7 +171,7 @@ class FeishuModule(_ModuleBase, _MessageBase[Feishu]):
receive_id_type=receive_id_type,
)
def post_torrents_message(self, message: Notification, torrents: List[Context]) -> None:
def post_torrents_message(self, message: Message, torrents: List[Context]) -> None:
for conf in self.get_configs().values():
if not self.check_message(message, conf.name):
continue
@@ -188,7 +188,7 @@ class FeishuModule(_ModuleBase, _MessageBase[Feishu]):
def edit_message(
self,
channel: MessageChannel,
channel: NotificationChannel,
source: str,
message_id: Union[str, int],
chat_id: Union[str, int],
@@ -214,7 +214,7 @@ class FeishuModule(_ModuleBase, _MessageBase[Feishu]):
return True
return False
def send_direct_message(self, message: Notification) -> Optional[MessageResponse]:
def send_direct_message(self, message: Message) -> Optional[MessageResponse]:
for conf in self.get_configs().values():
if not self.check_message(message, conf.name):
continue
@@ -285,7 +285,7 @@ class FeishuModule(_ModuleBase, _MessageBase[Feishu]):
return MessageResponse(
message_id=result.get("message_id"),
chat_id=result.get("chat_id"),
channel=MessageChannel.Feishu,
channel=NotificationChannel.Feishu,
source=conf.name,
metadata=result.get("metadata"),
success=True,
@@ -395,7 +395,7 @@ class FeishuModule(_ModuleBase, _MessageBase[Feishu]):
def mark_message_processing_started(
self,
channel: MessageChannel,
channel: NotificationChannel,
source: str,
userid: Optional[Union[str, int]] = None,
message_id: Optional[Union[str, int]] = None,
@@ -431,7 +431,7 @@ class FeishuModule(_ModuleBase, _MessageBase[Feishu]):
def mark_message_processing_finished(
self,
channel: MessageChannel,
channel: NotificationChannel,
source: str,
userid: Optional[Union[str, int]] = None,
message_id: Optional[Union[str, int]] = None,
+26 -26
View File
@@ -55,8 +55,8 @@ from app.domain.context import Context, MediaInfo
from app.db.oper.user import UserOper
from app.application.messaging.agent import matches_channel_admin
from app.runtime.log import logger
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
from app.adapters.network.http import RequestUtils
@@ -116,7 +116,7 @@ class Feishu:
if not self._admins:
return False
return not matches_channel_admin(
MessageChannel.Feishu,
NotificationChannel.Feishu,
{
"FEISHU_ADMINS": ",".join(self._admins),
"FEISHU_OPEN_ID": self._default_open_id,
@@ -299,8 +299,8 @@ class Feishu:
@staticmethod
def _parse_message_content(message) -> Tuple[
str, Optional[List[CommingMessage.MessageImage]], Optional[List[str]], Optional[
List[CommingMessage.MessageAttachment]]]:
str, Optional[List[IncomingMessage.MessageImage]], Optional[List[str]], Optional[
List[IncomingMessage.MessageAttachment]]]:
"""从飞书事件消息体中提取文本、图片、音频和文件引用。"""
raw_content = getattr(message, "content", None)
if not raw_content:
@@ -323,9 +323,9 @@ class Feishu:
image_key = str(content.get("image_key") or "").strip()
if image_key:
if message_id:
images = [CommingMessage.MessageImage(ref=f"feishu://image/{message_id}/{image_key}")]
images = [IncomingMessage.MessageImage(ref=f"feishu://image/{message_id}/{image_key}")]
else:
images = [CommingMessage.MessageImage(ref=f"feishu://image/{image_key}")]
images = [IncomingMessage.MessageImage(ref=f"feishu://image/{image_key}")]
elif message_type in {"audio", "media", "file"}:
file_key = str(content.get("file_key") or "").strip()
file_name = str(content.get("file_name") or "").strip() or None
@@ -336,7 +336,7 @@ class Feishu:
else:
resource_path = f"{message_id}/{file_key}" if message_id else file_key
files = [
CommingMessage.MessageAttachment(
IncomingMessage.MessageAttachment(
ref=f"feishu://file/{resource_path}/{file_name or 'attachment'}",
name=file_name,
)
@@ -397,7 +397,7 @@ class Feishu:
def _parse_post_message_content(
content: dict,
message_id: Optional[str] = None,
) -> Tuple[str, Optional[List[CommingMessage.MessageImage]]]:
) -> Tuple[str, Optional[List[IncomingMessage.MessageImage]]]:
"""从飞书富文本消息中提取可转发的文本和图片引用。"""
post_body = Feishu._resolve_post_message_body(content)
if not post_body:
@@ -421,9 +421,9 @@ class Feishu:
image_key = str(element.get("image_key") or "").strip()
if element.get("tag") == "img" and image_key:
if message_id:
images.append(CommingMessage.MessageImage(ref=f"feishu://image/{message_id}/{image_key}"))
images.append(IncomingMessage.MessageImage(ref=f"feishu://image/{message_id}/{image_key}"))
else:
images.append(CommingMessage.MessageImage(ref=f"feishu://image/{image_key}"))
images.append(IncomingMessage.MessageImage(ref=f"feishu://image/{image_key}"))
element_text = Feishu._parse_post_element_text(element)
if element_text:
row_parts.append(element_text)
@@ -648,7 +648,7 @@ class Feishu:
if self._ws_thread and self._ws_thread.is_alive():
self._ws_thread.join(timeout=5)
def parse_message(self, body: Any) -> Optional[CommingMessage]:
def parse_message(self, body: Any) -> Optional[IncomingMessage]:
"""解析飞书转发到消息入口的 JSON 报文。"""
try:
message = json.loads(body) if isinstance(body, (str, bytes, bytearray)) else body
@@ -685,13 +685,13 @@ class Feishu:
receive_id_type="open_id" if open_id else "user_id",
)
return None
return CommingMessage(
channel=MessageChannel.Feishu,
return IncomingMessage(
channel=NotificationChannel.Feishu,
source=self._name,
userid=userid,
username=username,
is_channel_admin=matches_channel_admin(
MessageChannel.Feishu,
NotificationChannel.Feishu,
{
"FEISHU_ADMINS": ",".join(self._admins),
"FEISHU_OPEN_ID": self._default_open_id,
@@ -707,7 +707,7 @@ class Feishu:
)
text = (message.get("text") or "").strip()
images = CommingMessage.MessageImage.normalize_list(message.get("images"))
images = IncomingMessage.MessageImage.normalize_list(message.get("images"))
audio_refs = None
if isinstance(message.get("audio_refs"), list):
audio_refs = [str(item).strip() for item in message.get("audio_refs") if str(item).strip()] or None
@@ -716,7 +716,7 @@ class Feishu:
normalized_files = []
for item in message.get("files"):
if isinstance(item, dict) and item.get("ref"):
normalized_files.append(CommingMessage.MessageAttachment(**item))
normalized_files.append(IncomingMessage.MessageAttachment(**item))
files = normalized_files or None
if not text and not images and not audio_refs and not files:
@@ -731,13 +731,13 @@ class Feishu:
)
return None
return CommingMessage(
channel=MessageChannel.Feishu,
return IncomingMessage(
channel=NotificationChannel.Feishu,
source=self._name,
userid=userid,
username=username,
is_channel_admin=matches_channel_admin(
MessageChannel.Feishu,
NotificationChannel.Feishu,
{
"FEISHU_ADMINS": ",".join(self._admins),
"FEISHU_OPEN_ID": self._default_open_id,
@@ -1739,7 +1739,7 @@ class Feishu:
def send_notification(
self,
message: Notification,
message: Message,
userid: Optional[str] = None,
chat_id: Optional[str] = None,
receive_id_type: Optional[str] = None,
@@ -1747,7 +1747,7 @@ class Feishu:
) -> Optional[dict]:
"""发送通知消息,优先使用交互卡片承载按钮。"""
is_streaming_agent_text = (
message.mtype == NotificationType.Agent
message.mtype == MessageType.Agent
and not message.buttons
and not message.link
)
@@ -1942,7 +1942,7 @@ class Feishu:
def send_medias_message(
self,
message: Notification,
message: Message,
medias: List[MediaInfo],
userid: Optional[str] = None,
chat_id: Optional[str] = None,
@@ -1956,7 +1956,7 @@ class Feishu:
image = media.get_message_image()
title = getattr(media, "title_year", None) or getattr(media, "title", None) or "未知媒体"
lines.append(f"{index}. {title}")
proxy_message = Notification(
proxy_message = Message(
title=message.title,
text="\n".join(lines),
image=image,
@@ -1974,7 +1974,7 @@ class Feishu:
def send_torrents_message(
self,
message: Notification,
message: Message,
torrents: List[Context],
userid: Optional[str] = None,
chat_id: Optional[str] = None,
@@ -1986,7 +1986,7 @@ class Feishu:
torrent_info = getattr(torrent, "torrent_info", None)
title = getattr(torrent_info, "title", None) or getattr(torrent_info, "site_name", None) or "未知种子"
lines.append(f"{index}. {title}")
proxy_message = Notification(
proxy_message = Message(
title=message.title,
text="\n".join(lines),
link=message.link,