From 3391723cf4c0c3f338b6ea06e2730bb54553788b Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Wed, 15 Apr 2026 19:27:37 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E5=8F=91=E9=80=81=E7=9A=84?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E6=B3=A8=E5=85=A5=E5=9B=9Emaisaka=E5=8E=86?= =?UTF-8?q?=E5=8F=B2=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/emoji_system/maisaka_tool.py | 2 + src/maisaka/builtin_tool/context.py | 36 +------------ src/maisaka/builtin_tool/reply.py | 7 +-- src/maisaka/builtin_tool/send_emoji.py | 4 +- src/maisaka/runtime.py | 37 +++++++++++++ src/plugin_runtime/capabilities/core.py | 20 +++++++ src/services/send_service.py | 72 ++++++++++++++++++++++++- 7 files changed, 134 insertions(+), 44 deletions(-) diff --git a/src/emoji_system/maisaka_tool.py b/src/emoji_system/maisaka_tool.py index bbdc072d..d9a9cca7 100644 --- a/src/emoji_system/maisaka_tool.py +++ b/src/emoji_system/maisaka_tool.py @@ -335,6 +335,8 @@ async def send_emoji_for_maisaka( storage_message=True, set_reply=False, reply_message=None, + sync_to_maisaka_history=True, + maisaka_source_kind="guided_reply", ) sent = sent_message is not None except Exception as exc: diff --git a/src/maisaka/builtin_tool/context.py b/src/maisaka/builtin_tool/context.py index e221ba11..4cf37986 100644 --- a/src/maisaka/builtin_tool/context.py +++ b/src/maisaka/builtin_tool/context.py @@ -4,7 +4,7 @@ from __future__ import annotations from base64 import b64decode from datetime import datetime -from typing import Any, Dict, List, Optional, TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Dict, List, Optional from src.chat.utils.utils import process_llm_response from src.common.data_models.message_component_data_model import EmojiComponent, MessageSequence, TextComponent @@ -12,13 +12,10 @@ from src.config.config import global_config from src.core.tooling import ToolExecutionResult from ..context_messages import SessionBackedMessage -from ..history_utils import build_prefixed_message_sequence, build_session_message_visible_text from ..message_adapter import format_speaker_content from ..planner_message_utils import build_planner_prefix, build_session_backed_text_message if TYPE_CHECKING: - from src.chat.message_receive.message import SessionMessage - from ..reasoning_engine import MaisakaReasoningEngine from ..runtime import MaisakaHeartFlowChatting @@ -139,37 +136,6 @@ class BuiltinToolRuntimeContext: return self.engine._get_runtime_manager() - @staticmethod - def _build_visible_text_from_sent_message(message: "SessionMessage") -> str: - """将已发送消息转换为 Maisaka 可见文本。""" - - return build_session_message_visible_text(message) - - def append_sent_message_to_chat_history( - self, - message: "SessionMessage", - *, - source_kind: str = "guided_reply", - ) -> None: - """将真实已发送消息同步到 Maisaka 历史。""" - - user_info = message.message_info.user_info - speaker_name = user_info.user_cardname or user_info.user_nickname or user_info.user_id - planner_prefix = build_planner_prefix( - timestamp=message.timestamp, - user_name=speaker_name, - group_card=user_info.user_cardname or "", - message_id=message.message_id, - include_message_id=not message.is_notify and bool(message.message_id), - ) - history_message = SessionBackedMessage.from_session_message( - message, - raw_message=build_prefixed_message_sequence(message.raw_message, planner_prefix), - visible_text=self._build_visible_text_from_sent_message(message), - source_kind=source_kind, - ) - self.runtime._chat_history.append(history_message) - def append_guided_reply_to_chat_history(self, reply_text: str) -> None: """将引导回复写回 Maisaka 历史。""" diff --git a/src/maisaka/builtin_tool/reply.py b/src/maisaka/builtin_tool/reply.py index debee914..fa182401 100644 --- a/src/maisaka/builtin_tool/reply.py +++ b/src/maisaka/builtin_tool/reply.py @@ -160,7 +160,6 @@ async def handle_tool( combined_reply_text = "".join(reply_segments) try: sent = False - sent_messages = [] if tool_ctx.runtime.chat_stream.platform == CLI_PLATFORM_NAME: for segment in reply_segments: render_cli_message(segment) @@ -174,11 +173,12 @@ async def handle_tool( reply_message=target_message if set_quote and index == 0 else None, selected_expressions=reply_result.selected_expression_ids or None, typing=index > 0, + sync_to_maisaka_history=True, + maisaka_source_kind="guided_reply", ) sent = sent_message is not None if not sent: break - sent_messages.append(sent_message) except Exception: logger.exception( f"{tool_ctx.runtime.log_prefix} 发送文字消息时发生异常,目标消息编号={target_message_id}" @@ -206,9 +206,6 @@ async def handle_tool( if tool_ctx.runtime.chat_stream.platform == CLI_PLATFORM_NAME: tool_ctx.append_guided_reply_to_chat_history(combined_reply_text) - else: - for sent_message in sent_messages: - tool_ctx.append_sent_message_to_chat_history(sent_message) tool_ctx.runtime._record_reply_sent() return tool_ctx.build_success_result( invocation.tool_name, diff --git a/src/maisaka/builtin_tool/send_emoji.py b/src/maisaka/builtin_tool/send_emoji.py index b02b75f7..b1853452 100644 --- a/src/maisaka/builtin_tool/send_emoji.py +++ b/src/maisaka/builtin_tool/send_emoji.py @@ -446,9 +446,7 @@ async def handle_tool( f"描述={send_result.description!r} 情绪标签={send_result.emotions} " f"命中情绪={send_result.matched_emotion!r}" ) - if send_result.sent_message is not None: - tool_ctx.append_sent_message_to_chat_history(send_result.sent_message) - else: + if send_result.sent_message is None: tool_ctx.append_sent_emoji_to_chat_history( emoji_base64=send_result.emoji_base64, success_message=_EMOJI_SUCCESS_MESSAGE, diff --git a/src/maisaka/runtime.py b/src/maisaka/runtime.py index 5de81cf2..37c79180 100644 --- a/src/maisaka/runtime.py +++ b/src/maisaka/runtime.py @@ -183,6 +183,43 @@ class MaisakaHeartFlowChatting: self._talk_frequency_adjust = max(0.01, float(frequency)) self._schedule_message_turn() + def append_sent_message_to_chat_history( + self, + message: SessionMessage, + *, + source_kind: str = "guided_reply", + ) -> bool: + """将一条已发送成功的消息同步到 Maisaka 内部历史。""" + + try: + from .context_messages import SessionBackedMessage + from .history_utils import build_prefixed_message_sequence, build_session_message_visible_text + from .planner_message_utils import build_planner_prefix + + user_info = message.message_info.user_info + speaker_name = user_info.user_cardname or user_info.user_nickname or user_info.user_id + planner_prefix = build_planner_prefix( + timestamp=message.timestamp, + user_name=speaker_name, + group_card=user_info.user_cardname or "", + message_id=message.message_id, + include_message_id=not message.is_notify and bool(message.message_id), + ) + history_message = SessionBackedMessage.from_session_message( + message, + raw_message=build_prefixed_message_sequence(message.raw_message, planner_prefix), + visible_text=build_session_message_visible_text(message), + source_kind=source_kind, + ) + self._chat_history.append(history_message) + return True + except Exception as exc: + logger.warning( + f"{self.log_prefix} 同步已发送消息到 Maisaka 历史失败: " + f"message_id={message.message_id} error={exc}" + ) + return False + async def register_message(self, message: SessionMessage) -> None: """缓存一条新消息并唤醒主循环。""" if self._running: diff --git a/src/plugin_runtime/capabilities/core.py b/src/plugin_runtime/capabilities/core.py index 843b8ce0..25f2a6b9 100644 --- a/src/plugin_runtime/capabilities/core.py +++ b/src/plugin_runtime/capabilities/core.py @@ -75,6 +75,8 @@ class RuntimeCoreCapabilityMixin: text = str(args.get("text", "")) stream_id = str(args.get("stream_id", "")) + sync_to_maisaka_history = bool(args.get("sync_to_maisaka_history", False)) + maisaka_source_kind = str(args.get("maisaka_source_kind", "plugin_send") or "plugin_send") if not text or not stream_id: return {"success": False, "error": "缺少必要参数 text 或 stream_id"} @@ -85,6 +87,8 @@ class RuntimeCoreCapabilityMixin: typing=bool(args.get("typing", False)), set_reply=bool(args.get("set_reply", False)), storage_message=bool(args.get("storage_message", True)), + sync_to_maisaka_history=sync_to_maisaka_history, + maisaka_source_kind=maisaka_source_kind, ) return {"success": result} except Exception as exc: @@ -107,6 +111,8 @@ class RuntimeCoreCapabilityMixin: emoji_base64 = str(args.get("emoji_base64", "")) stream_id = str(args.get("stream_id", "")) + sync_to_maisaka_history = bool(args.get("sync_to_maisaka_history", False)) + maisaka_source_kind = str(args.get("maisaka_source_kind", "plugin_send") or "plugin_send") if not emoji_base64 or not stream_id: return {"success": False, "error": "缺少必要参数 emoji_base64 或 stream_id"} @@ -115,6 +121,8 @@ class RuntimeCoreCapabilityMixin: emoji_base64=emoji_base64, stream_id=stream_id, storage_message=bool(args.get("storage_message", True)), + sync_to_maisaka_history=sync_to_maisaka_history, + maisaka_source_kind=maisaka_source_kind, ) return {"success": result} except Exception as exc: @@ -137,6 +145,8 @@ class RuntimeCoreCapabilityMixin: image_base64 = str(args.get("image_base64", "")) stream_id = str(args.get("stream_id", "")) + sync_to_maisaka_history = bool(args.get("sync_to_maisaka_history", False)) + maisaka_source_kind = str(args.get("maisaka_source_kind", "plugin_send") or "plugin_send") if not image_base64 or not stream_id: return {"success": False, "error": "缺少必要参数 image_base64 或 stream_id"} @@ -145,6 +155,8 @@ class RuntimeCoreCapabilityMixin: image_base64=image_base64, stream_id=stream_id, storage_message=bool(args.get("storage_message", True)), + sync_to_maisaka_history=sync_to_maisaka_history, + maisaka_source_kind=maisaka_source_kind, ) return {"success": result} except Exception as exc: @@ -167,6 +179,8 @@ class RuntimeCoreCapabilityMixin: command = str(args.get("command", "")) stream_id = str(args.get("stream_id", "")) + sync_to_maisaka_history = bool(args.get("sync_to_maisaka_history", False)) + maisaka_source_kind = str(args.get("maisaka_source_kind", "plugin_send") or "plugin_send") if not command or not stream_id: return {"success": False, "error": "缺少必要参数 command 或 stream_id"} @@ -177,6 +191,8 @@ class RuntimeCoreCapabilityMixin: stream_id=stream_id, storage_message=bool(args.get("storage_message", True)), display_message=str(args.get("display_message", "")), + sync_to_maisaka_history=sync_to_maisaka_history, + maisaka_source_kind=maisaka_source_kind, ) return {"success": result} except Exception as exc: @@ -202,6 +218,8 @@ class RuntimeCoreCapabilityMixin: if content is None: content = args.get("data", "") stream_id = str(args.get("stream_id", "")) + sync_to_maisaka_history = bool(args.get("sync_to_maisaka_history", False)) + maisaka_source_kind = str(args.get("maisaka_source_kind", "plugin_send") or "plugin_send") if not message_type or not stream_id: return {"success": False, "error": "缺少必要参数 message_type 或 stream_id"} @@ -213,6 +231,8 @@ class RuntimeCoreCapabilityMixin: display_message=str(args.get("display_message", "")), typing=bool(args.get("typing", False)), storage_message=bool(args.get("storage_message", True)), + sync_to_maisaka_history=sync_to_maisaka_history, + maisaka_source_kind=maisaka_source_kind, ) return {"success": result} except Exception as exc: diff --git a/src/services/send_service.py b/src/services/send_service.py index 22d30e8f..34b3f6d9 100644 --- a/src/services/send_service.py +++ b/src/services/send_service.py @@ -707,6 +707,28 @@ async def _notify_memory_automation_on_message_sent(message: SessionMessage) -> logger.warning(f"[{session_id}] 长期记忆人物事实写回注册失败: {exc}") +def _sync_sent_message_to_maisaka_history( + message: SessionMessage, + *, + source_kind: str, +) -> None: + """将已发送成功的消息同步到当前会话对应的 Maisaka 历史。""" + + session_id = str(message.session_id or "").strip() + if not session_id: + return + + try: + from src.chat.heart_flow.heartflow_manager import heartflow_manager + + runtime = heartflow_manager.heartflow_chat_list.get(session_id) + if runtime is None: + return + runtime.append_sent_message_to_chat_history(message, source_kind=source_kind) + except Exception as exc: + logger.warning(f"[SendService] 同步消息到 Maisaka 历史失败: session_id={session_id} error={exc}") + + def _log_platform_io_failures(delivery_batch: DeliveryBatch) -> None: """输出 Platform IO 批量发送失败详情。 @@ -837,13 +859,15 @@ async def send_session_message_with_message( reply_message_id: Optional[str] = None, storage_message: bool = True, show_log: bool = True, + sync_to_maisaka_history: bool = False, + maisaka_source_kind: str = "outbound_send", ) -> Optional[SessionMessage]: """统一发送一条内部消息,并返回最终发送成功的消息对象。""" if not message.message_id: logger.error("[SendService] 消息缺少 message_id,无法发送") raise ValueError("消息缺少 message_id,无法发送") - return await _send_via_platform_io( + sent_message = await _send_via_platform_io( message, typing=typing, set_reply=set_reply, @@ -851,6 +875,12 @@ async def send_session_message_with_message( storage_message=storage_message, show_log=show_log, ) + if sent_message is not None and sync_to_maisaka_history: + _sync_sent_message_to_maisaka_history( + sent_message, + source_kind=str(maisaka_source_kind or "outbound_send"), + ) + return sent_message async def send_session_message( @@ -861,6 +891,8 @@ async def send_session_message( reply_message_id: Optional[str] = None, storage_message: bool = True, show_log: bool = True, + sync_to_maisaka_history: bool = False, + maisaka_source_kind: str = "outbound_send", ) -> bool: """统一发送一条内部消息。 @@ -893,6 +925,8 @@ async def send_session_message( reply_message_id=reply_message_id, storage_message=storage_message, show_log=show_log, + sync_to_maisaka_history=sync_to_maisaka_history, + maisaka_source_kind=maisaka_source_kind, ) is not None ) @@ -908,6 +942,8 @@ async def _send_to_target( storage_message: bool = True, show_log: bool = True, selected_expressions: Optional[List[int]] = None, + sync_to_maisaka_history: bool = False, + maisaka_source_kind: str = "outbound_send", ) -> bool: """向指定目标构建并发送消息,并返回是否发送成功。""" return ( @@ -921,6 +957,8 @@ async def _send_to_target( storage_message=storage_message, show_log=show_log, selected_expressions=selected_expressions, + sync_to_maisaka_history=sync_to_maisaka_history, + maisaka_source_kind=maisaka_source_kind, ) is not None ) @@ -936,6 +974,8 @@ async def _send_to_target_with_message( storage_message: bool = True, show_log: bool = True, selected_expressions: Optional[List[int]] = None, + sync_to_maisaka_history: bool = False, + maisaka_source_kind: str = "outbound_send", ) -> Optional[SessionMessage]: """向指定目标构建并发送消息。 @@ -998,6 +1038,8 @@ async def _send_to_target_with_message( reply_message_id=reply_message.message_id if reply_message is not None else None, storage_message=storage_message, show_log=show_log, + sync_to_maisaka_history=sync_to_maisaka_history, + maisaka_source_kind=maisaka_source_kind, ) if sent_message is not None: logger.debug(f"[SendService] 成功发送消息到 {stream_id}") @@ -1019,6 +1061,8 @@ async def text_to_stream_with_message( reply_message: Optional[MaiMessage] = None, storage_message: bool = True, selected_expressions: Optional[List[int]] = None, + sync_to_maisaka_history: bool = False, + maisaka_source_kind: str = "outbound_send", ) -> Optional[SessionMessage]: """向指定流发送文本消息,并返回发送成功后的消息对象。""" return await _send_to_target_with_message( @@ -1030,6 +1074,8 @@ async def text_to_stream_with_message( reply_message=reply_message, storage_message=storage_message, selected_expressions=selected_expressions, + sync_to_maisaka_history=sync_to_maisaka_history, + maisaka_source_kind=maisaka_source_kind, ) @@ -1041,6 +1087,8 @@ async def text_to_stream( reply_message: Optional[MaiMessage] = None, storage_message: bool = True, selected_expressions: Optional[List[int]] = None, + sync_to_maisaka_history: bool = False, + maisaka_source_kind: str = "outbound_send", ) -> bool: """向指定流发送文本消息。 @@ -1065,6 +1113,8 @@ async def text_to_stream( reply_message=reply_message, storage_message=storage_message, selected_expressions=selected_expressions, + sync_to_maisaka_history=sync_to_maisaka_history, + maisaka_source_kind=maisaka_source_kind, ) is not None ) @@ -1076,6 +1126,8 @@ async def emoji_to_stream_with_message( storage_message: bool = True, set_reply: bool = False, reply_message: Optional[MaiMessage] = None, + sync_to_maisaka_history: bool = False, + maisaka_source_kind: str = "outbound_send", ) -> Optional[SessionMessage]: """向指定流发送表情消息,并返回发送成功后的消息对象。""" return await _send_to_target_with_message( @@ -1086,6 +1138,8 @@ async def emoji_to_stream_with_message( storage_message=storage_message, set_reply=set_reply, reply_message=reply_message, + sync_to_maisaka_history=sync_to_maisaka_history, + maisaka_source_kind=maisaka_source_kind, ) @@ -1095,6 +1149,8 @@ async def emoji_to_stream( storage_message: bool = True, set_reply: bool = False, reply_message: Optional[MaiMessage] = None, + sync_to_maisaka_history: bool = False, + maisaka_source_kind: str = "outbound_send", ) -> bool: """向指定流发送表情消息。 @@ -1115,6 +1171,8 @@ async def emoji_to_stream( storage_message=storage_message, set_reply=set_reply, reply_message=reply_message, + sync_to_maisaka_history=sync_to_maisaka_history, + maisaka_source_kind=maisaka_source_kind, ) is not None ) @@ -1126,6 +1184,8 @@ async def image_to_stream( storage_message: bool = True, set_reply: bool = False, reply_message: Optional[MaiMessage] = None, + sync_to_maisaka_history: bool = False, + maisaka_source_kind: str = "outbound_send", ) -> bool: """向指定流发送图片消息。 @@ -1147,6 +1207,8 @@ async def image_to_stream( storage_message=storage_message, set_reply=set_reply, reply_message=reply_message, + sync_to_maisaka_history=sync_to_maisaka_history, + maisaka_source_kind=maisaka_source_kind, ) @@ -1160,6 +1222,8 @@ async def custom_to_stream( set_reply: bool = False, storage_message: bool = True, show_log: bool = True, + sync_to_maisaka_history: bool = False, + maisaka_source_kind: str = "outbound_send", ) -> bool: """向指定流发送自定义类型消息。 @@ -1186,6 +1250,8 @@ async def custom_to_stream( set_reply=set_reply, storage_message=storage_message, show_log=show_log, + sync_to_maisaka_history=sync_to_maisaka_history, + maisaka_source_kind=maisaka_source_kind, ) @@ -1198,6 +1264,8 @@ async def custom_reply_set_to_stream( set_reply: bool = False, storage_message: bool = True, show_log: bool = True, + sync_to_maisaka_history: bool = False, + maisaka_source_kind: str = "outbound_send", ) -> bool: """向指定流发送消息组件序列。 @@ -1223,4 +1291,6 @@ async def custom_reply_set_to_stream( set_reply=set_reply, storage_message=storage_message, show_log=show_log, + sync_to_maisaka_history=sync_to_maisaka_history, + maisaka_source_kind=maisaka_source_kind, )