From 98d1b4686af49fc3d0af3cb2eb8df2006f757cb4 Mon Sep 17 00:00:00 2001 From: ZouYonghe <1259085392z@gmail.com> Date: Thu, 27 Nov 2025 23:03:45 +0800 Subject: [PATCH 1/5] Add flexible fallback handling for session waiter --- astrbot/core/utils/session_waiter.py | 34 ++++++++++++++++++++++++++++ packages/session_controller/main.py | 10 ++++---- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/astrbot/core/utils/session_waiter.py b/astrbot/core/utils/session_waiter.py index 33b7cb17a..297d5286c 100644 --- a/astrbot/core/utils/session_waiter.py +++ b/astrbot/core/utils/session_waiter.py @@ -29,6 +29,27 @@ def __init__(self): self.history_chains: list[list[Comp.BaseMessageComponent]] = [] + def fallback_to_llm( + self, + event_queue: asyncio.Queue, + event: AstrMessageEvent, + *, + stop_session: bool = True, + ) -> AstrMessageEvent: + """将当前事件重新入队,由默认 LLM 流程处理,适用于非预期输入的兜底。 + + Args: + event_queue: 事件队列 + event: 当前事件 + stop_session: 是否结束当前 SessionWaiter。False 时仅兜底当前输入,继续等待后续输入。 + """ + new_event = _clone_event_for_llm(event) + event_queue.put_nowait(new_event) + event.stop_event() + if stop_session: + self.stop() + return new_event + def stop(self, error: Exception = None): """立即结束这个会话""" if not self.future.done(): @@ -85,6 +106,19 @@ def get_history_chains(self) -> list[list[Comp.BaseMessageComponent]]: return self.history_chains +def _clone_event_for_llm(event: AstrMessageEvent) -> AstrMessageEvent: + """复制并重置事件状态,确保能够重新进入默认 LLM 流程。""" + new_event = copy.copy(event) + new_event.clear_result() + new_event._extras = {} + new_event._has_send_oper = False + new_event.call_llm = False + new_event.is_wake = False + new_event.is_at_or_wake_command = False + new_event.plugins_name = None + return new_event + + class SessionFilter: """如何界定一个会话""" diff --git a/packages/session_controller/main.py b/packages/session_controller/main.py index 4d4a42528..bf4eb8f67 100644 --- a/packages/session_controller/main.py +++ b/packages/session_controller/main.py @@ -1,4 +1,3 @@ -import copy from sys import maxsize import astrbot.api.message_components as Comp @@ -96,11 +95,10 @@ async def empty_mention_waiter( 0, Comp.At(qq=event.get_self_id(), name=event.get_self_id()), ) - new_event = copy.copy(event) - # 重新推入事件队列 - self.context.get_event_queue().put_nowait(new_event) - event.stop_event() - controller.stop() + controller.fallback_to_llm( + self.context.get_event_queue(), + event, + ) try: await empty_mention_waiter(event) From 9023363869e0001af780a34a05f148ab0ff54221 Mon Sep 17 00:00:00 2001 From: ZouYonghe <1259085392z@gmail.com> Date: Fri, 28 Nov 2025 09:53:06 +0800 Subject: [PATCH 2/5] fix: avoid session fallback swallowing events --- astrbot/core/utils/session_waiter.py | 12 +++++++++--- packages/session_controller/main.py | 4 ++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/astrbot/core/utils/session_waiter.py b/astrbot/core/utils/session_waiter.py index 297d5286c..d6efdc6b2 100644 --- a/astrbot/core/utils/session_waiter.py +++ b/astrbot/core/utils/session_waiter.py @@ -181,11 +181,15 @@ def _cleanup(self, error: Exception = None): self.session_controller.stop(error) @classmethod - async def trigger(cls, session_id: str, event: AstrMessageEvent): - """外部输入触发会话处理""" + async def trigger(cls, session_id: str, event: AstrMessageEvent) -> bool: + """外部输入触发会话处理 + + Returns: + bool: 是否成功触发处理。False 表示会话不存在或已结束。 + """ session = USER_SESSIONS.get(session_id) if not session or session.session_controller.future.done(): - return + return False async with session._lock: if not session.session_controller.future.done(): @@ -198,6 +202,8 @@ async def trigger(cls, session_id: str, event: AstrMessageEvent): await session.handler(session.session_controller, event) except Exception as e: session.session_controller.stop(e) + return True + return False def session_waiter(timeout: int = 30, record_history_chains: bool = False): diff --git a/packages/session_controller/main.py b/packages/session_controller/main.py index bf4eb8f67..d18ed2627 100644 --- a/packages/session_controller/main.py +++ b/packages/session_controller/main.py @@ -24,8 +24,8 @@ async def handle_session_control_agent(self, event: AstrMessageEvent): """会话控制代理""" for session_filter in FILTERS: session_id = session_filter.filter(event) - if session_id in USER_SESSIONS: - await SessionWaiter.trigger(session_id, event) + handled = await SessionWaiter.trigger(session_id, event) + if handled: event.stop_event() @filter.event_message_type(filter.EventMessageType.ALL, priority=maxsize - 1) From b1ff47d58ef4fc5373c85e4d77be88ae5908ad5b Mon Sep 17 00:00:00 2001 From: ZouYonghe <1259085392z@gmail.com> Date: Fri, 28 Nov 2025 10:16:56 +0800 Subject: [PATCH 3/5] chore: remove unused session waiter import --- packages/session_controller/main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/session_controller/main.py b/packages/session_controller/main.py index d18ed2627..b668bda43 100644 --- a/packages/session_controller/main.py +++ b/packages/session_controller/main.py @@ -6,7 +6,6 @@ from astrbot.api.star import Context, Star from astrbot.core.utils.session_waiter import ( FILTERS, - USER_SESSIONS, SessionController, SessionWaiter, session_waiter, From 3b306ec4e99ea487c7b043778cc94e656c88f1dc Mon Sep 17 00:00:00 2001 From: ZouYonghe <1259085392z@gmail.com> Date: Fri, 28 Nov 2025 10:19:45 +0800 Subject: [PATCH 4/5] refactor: expose llm clone helper and guard fallback --- astrbot/core/platform/astr_message_event.py | 15 ++++++++++++++ astrbot/core/utils/session_waiter.py | 22 ++++++++------------- packages/session_controller/main.py | 2 ++ 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/astrbot/core/platform/astr_message_event.py b/astrbot/core/platform/astr_message_event.py index 6402aeaed..f233f39e7 100644 --- a/astrbot/core/platform/astr_message_event.py +++ b/astrbot/core/platform/astr_message_event.py @@ -1,5 +1,6 @@ import abc import asyncio +import copy import hashlib import re import uuid @@ -278,6 +279,20 @@ def clear_result(self): """清除消息事件的结果。""" self._result = None + def clone_for_llm(self) -> "AstrMessageEvent": + """浅拷贝并重置状态,以便重新走默认 LLM 流程。""" + new_event: AstrMessageEvent = copy.copy(self) + new_event.clear_result() + new_event._extras = {} + new_event._has_send_oper = False + new_event.call_llm = False + new_event.is_wake = False + new_event.is_at_or_wake_command = False + new_event.plugins_name = None + # 可选的绕过标记,避免被 SessionWaiter 再次截获 + new_event._bypass_session_waiter = False + return new_event + """消息链相关""" def make_result(self) -> MessageEventResult: diff --git a/astrbot/core/utils/session_waiter.py b/astrbot/core/utils/session_waiter.py index d6efdc6b2..c42a81ffb 100644 --- a/astrbot/core/utils/session_waiter.py +++ b/astrbot/core/utils/session_waiter.py @@ -9,6 +9,7 @@ from typing import Any import astrbot.core.message.components as Comp +from astrbot import logger from astrbot.core.platform import AstrMessageEvent USER_SESSIONS: dict[str, "SessionWaiter"] = {} # 存储 SessionWaiter 实例 @@ -43,7 +44,13 @@ def fallback_to_llm( event: 当前事件 stop_session: 是否结束当前 SessionWaiter。False 时仅兜底当前输入,继续等待后续输入。 """ - new_event = _clone_event_for_llm(event) + if not stop_session: + logger.warning( + "fallback_to_llm(stop_session=False) 会保留当前会话,默认会话拦截可能导致兜底无效," + "建议谨慎使用或在后续输入中自行终止会话。", + ) + new_event = event.clone_for_llm() + new_event._bypass_session_waiter = not stop_session event_queue.put_nowait(new_event) event.stop_event() if stop_session: @@ -106,19 +113,6 @@ def get_history_chains(self) -> list[list[Comp.BaseMessageComponent]]: return self.history_chains -def _clone_event_for_llm(event: AstrMessageEvent) -> AstrMessageEvent: - """复制并重置事件状态,确保能够重新进入默认 LLM 流程。""" - new_event = copy.copy(event) - new_event.clear_result() - new_event._extras = {} - new_event._has_send_oper = False - new_event.call_llm = False - new_event.is_wake = False - new_event.is_at_or_wake_command = False - new_event.plugins_name = None - return new_event - - class SessionFilter: """如何界定一个会话""" diff --git a/packages/session_controller/main.py b/packages/session_controller/main.py index b668bda43..038118c2e 100644 --- a/packages/session_controller/main.py +++ b/packages/session_controller/main.py @@ -21,6 +21,8 @@ def __init__(self, context: Context): @filter.event_message_type(filter.EventMessageType.ALL, priority=maxsize) async def handle_session_control_agent(self, event: AstrMessageEvent): """会话控制代理""" + if getattr(event, "_bypass_session_waiter", False): + return for session_filter in FILTERS: session_id = session_filter.filter(event) handled = await SessionWaiter.trigger(session_id, event) From b797cbe9aa06116eb42e8fab67ba26e3a26259c2 Mon Sep 17 00:00:00 2001 From: zouyonghe <1259085392z@gmail.com> Date: Sun, 30 Nov 2025 23:31:38 +0900 Subject: [PATCH 5/5] Improve AstrMessageEvent cloning safety --- astrbot/core/platform/astr_message_event.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/astrbot/core/platform/astr_message_event.py b/astrbot/core/platform/astr_message_event.py index f233f39e7..7818efbe5 100644 --- a/astrbot/core/platform/astr_message_event.py +++ b/astrbot/core/platform/astr_message_event.py @@ -30,6 +30,9 @@ class AstrMessageEvent(abc.ABC): + # extras 中可安全清理的瞬态字段清单;子类可按需扩展 + TRANSIENT_EXTRA_KEYS: set[str] = set() + def __init__( self, message_str: str, @@ -72,6 +75,8 @@ def __init__( # back_compability self.platform = platform_meta + # 可选的绕过标记,避免被 SessionWaiter 再次截获 + self._bypass_session_waiter = False def get_platform_name(self): """获取这个事件所属的平台的类型(如 aiocqhttp, slack, discord 等)。 @@ -283,13 +288,15 @@ def clone_for_llm(self) -> "AstrMessageEvent": """浅拷贝并重置状态,以便重新走默认 LLM 流程。""" new_event: AstrMessageEvent = copy.copy(self) new_event.clear_result() - new_event._extras = {} + # 保留非瞬态 extras,避免跨管线上下文丢失 + new_event._extras = self._extras.copy() + for key in self.TRANSIENT_EXTRA_KEYS: + new_event._extras.pop(key, None) new_event._has_send_oper = False new_event.call_llm = False new_event.is_wake = False new_event.is_at_or_wake_command = False new_event.plugins_name = None - # 可选的绕过标记,避免被 SessionWaiter 再次截获 new_event._bypass_session_waiter = False return new_event