@@ -654,35 +654,60 @@ async def _upload_comp(comp) -> object | None:
654654
655655 return await super ().send_by_session (session , message_chain )
656656
657- async def _resolve_parent_note (
657+ async def _resolve_reply_target (
658658 self ,
659659 current : dict [str , Any ],
660- depth : int ,
661- ) -> tuple [dict [str , Any ] | None , str | None ]:
662- """根据当前 note 解析父帖。返回 (parent_dict, relation_label) 或 (None, None)。
660+ ) -> dict [str , Any ] | None :
661+ """解析当前 note 的 reply 目标(被回复的原帖)。
663662
664- depth==0 时优先用 payload 中已展开的对象(reply / renote);缺失时回退 API。
665- depth>=1 时通常 payload 不再嵌套展开,主要靠 API 拉取。
666- reply 和 renote 同时存在(reply-with-quote)时返回 reply,调用方需要单独处理 renote。
663+ 优先用 payload 中已展开的 `reply` 对象;缺失时通过 `replyId`
664+ 走一次 notes/show API 回退。两者皆无返回 None。
667665 """
668666 reply_obj = current .get ("reply" )
669667 if isinstance (reply_obj , dict ):
670- return reply_obj , "被回复的原帖"
668+ return reply_obj
671669 reply_id = current .get ("replyId" )
672670 if reply_id and self .api :
673671 fetched = await self .api .get_note (str (reply_id ))
674672 if isinstance (fetched , dict ):
675- return fetched , "被回复的原帖"
673+ return fetched
674+ return None
675+
676+ async def _resolve_renote_target (
677+ self ,
678+ current : dict [str , Any ],
679+ ) -> dict [str , Any ] | None :
680+ """解析当前 note 的 renote 目标(被引用/转发的原帖)。
676681
682+ 优先用 payload 中已展开的 `renote` 对象;缺失时通过 `renoteId`
683+ 走一次 notes/show API 回退。两者皆无返回 None。
684+ """
677685 renote_obj = current .get ("renote" )
678686 if isinstance (renote_obj , dict ):
679- return renote_obj , "被引用/转发的原帖"
687+ return renote_obj
680688 renote_id = current .get ("renoteId" )
681689 if renote_id and self .api :
682690 fetched = await self .api .get_note (str (renote_id ))
683691 if isinstance (fetched , dict ):
684- return fetched , "被引用/转发的原帖"
692+ return fetched
693+ return None
685694
695+ async def _resolve_parent_note (
696+ self ,
697+ current : dict [str , Any ],
698+ ) -> tuple [dict [str , Any ] | None , str | None ]:
699+ """解析当前 note 的父帖(按优先级返回首个候选)。
700+
701+ 优先返回 reply 目标(被回复的原帖);reply 不存在时回退到 renote 目标
702+ (被引用/转发的原帖)。reply-with-quote 场景:返回 reply,调用方需要
703+ 再单独走 _resolve_renote_target 取引用帖。
704+ """
705+ reply_parent = await self ._resolve_reply_target (current )
706+ if reply_parent is not None :
707+ return reply_parent , "被回复的原帖"
708+ renote_parent = await self ._resolve_renote_target (current )
709+ if renote_parent is not None :
710+ return renote_parent , "被引用/转发的原帖"
686711 return None , None
687712
688713 async def _build_parent_note_context (
@@ -694,17 +719,30 @@ async def _build_parent_note_context(
694719 - depth=0 时如果同时存在 reply + renote(reply-with-quote),两个都注入。
695720 - 顶层(depth=0)父帖作者是机器人自己时整段跳过,避免反馈循环。
696721 - 链中循环或 API 失败时静默截断,不阻断消息处理。
722+ - 返回值会被作为后缀拼到 ``message_str`` 末尾,因此自带前导分隔符
723+ ``\\ n\\ n---\\ n``,让 LLM 看到的 prompt 形如「用户文本 \\ n--- 父帖摘要」。
724+ 放尾部而非头部是为了不破坏 wake_prefix 与命令前缀的 startswith 匹配。
697725 """
698726 if self .reply_context_max_depth <= 0 :
699727 return ""
700728
729+ # 既无 reply/replyId 又无 renote/renoteId 的独立帖子,没有父帖可追,直接退出,
730+ # 避免空循环以及无谓的 API 调用。
731+ if not (
732+ raw_data .get ("reply" )
733+ or raw_data .get ("replyId" )
734+ or raw_data .get ("renote" )
735+ or raw_data .get ("renoteId" )
736+ ):
737+ return ""
738+
701739 blocks : list [str ] = []
702740 visited : set [str ] = set ()
703741 current = raw_data
704742 labelled_by_depth = self .reply_context_max_depth > 1
705743
706744 for depth in range (self .reply_context_max_depth ):
707- parent , relation = await self ._resolve_parent_note (current , depth )
745+ parent , relation = await self ._resolve_parent_note (current )
708746 if not isinstance (parent , dict ):
709747 break
710748
@@ -728,31 +766,31 @@ async def _build_parent_note_context(
728766 label = f"{ label } - 第{ depth + 1 } 层"
729767 blocks .append (f"[{ label } ]\n { summary } " )
730768
731- # depth=0 且当前是 reply:如果还有 renote(reply-with-quote),也补上
732- if (
733- depth == 0
734- and relation == "被回复的原帖"
735- and isinstance (current .get ("renote" ), dict )
736- ):
737- renote_obj = current ["renote" ]
738- renote_id = str (renote_obj .get ("id" ) or "" )
739- if renote_id and renote_id not in visited :
740- visited .add (renote_id )
741- quote_summary = summarize_note_for_context (
742- renote_obj ,
743- max_text_length = self .reply_context_max_text_length ,
744- )
745- if quote_summary :
746- quote_label = "被引用/转发的原帖"
747- if labelled_by_depth :
748- quote_label = f"{ quote_label } - 第1层"
749- blocks .append (f"[{ quote_label } ]\n { quote_summary } " )
769+ # depth=0 且当前是 reply:如果还有 renote(reply-with-quote),也补上。
770+ # 走 _resolve_renote_target 而不是只检查 isinstance(current.get("renote")),
771+ # 这样 payload 仅给 renoteId 时也能通过 API 回退拉取引用帖。
772+ if depth == 0 and relation == "被回复的原帖" :
773+ renote_parent = await self ._resolve_renote_target (current )
774+ if isinstance (renote_parent , dict ):
775+ renote_id = str (renote_parent .get ("id" ) or "" )
776+ if renote_id and renote_id not in visited :
777+ visited .add (renote_id )
778+ quote_summary = summarize_note_for_context (
779+ renote_parent ,
780+ max_text_length = self .reply_context_max_text_length ,
781+ )
782+ if quote_summary :
783+ quote_label = "被引用/转发的原帖"
784+ if labelled_by_depth :
785+ quote_label = f"{ quote_label } - 第1层"
786+ blocks .append (f"[{ quote_label } ]\n { quote_summary } " )
750787
751788 current = parent
752789
753790 if not blocks :
754791 return ""
755- return "\n \n " .join (blocks ) + "\n ---\n "
792+ # 作为 message_str 的后缀返回,前导分隔符确保与用户原文有清晰边界
793+ return "\n \n ---\n " + "\n \n " .join (blocks )
756794
757795 async def convert_message (self , raw_data : dict [str , Any ]) -> AstrBotMessage :
758796 """将 Misskey 贴文数据转换为 AstrBotMessage 对象"""
@@ -771,16 +809,18 @@ async def convert_message(self, raw_data: dict[str, Any]) -> AstrBotMessage:
771809 is_chat = False ,
772810 )
773811
774- # 评论区原帖上下文:作为前缀注入到消息链与 message_str
812+ # 评论区原帖上下文:拼到 message_str 尾部,避免破坏 wake_prefix / 命令
813+ # 前缀 startswith 匹配(waking_check 与 star.filter.command 都是头部匹配)。
814+ # LLM 主路径直接读 message_str(astr_main_agent / agent third_party 都遍历
815+ # message chain 时只取多模态 Comp,忽略 Comp.Plain),所以这里不再把
816+ # parent_ctx 加到 message.message —— 那会变成读不到的死代码。
775817 parent_ctx = ""
776818 if self .include_reply_context :
777819 try :
778820 parent_ctx = await self ._build_parent_note_context (raw_data )
779821 except Exception as e :
780822 logger .warning (f"[Misskey] 构建父帖上下文失败: { e } " )
781823 parent_ctx = ""
782- if parent_ctx :
783- message .message .append (Comp .Plain (parent_ctx ))
784824
785825 message_parts = []
786826 raw_text = raw_data .get ("text" , "" )
@@ -811,7 +851,7 @@ async def convert_message(self, raw_data: dict[str, Any]) -> AstrBotMessage:
811851 if message_parts
812852 else ""
813853 )
814- message .message_str = parent_ctx + body if parent_ctx else body
854+ message .message_str = body + parent_ctx if parent_ctx else body
815855 return message
816856
817857 async def convert_chat_message (self , raw_data : dict [str , Any ]) -> AstrBotMessage :
0 commit comments