From d330b595dea11bdc759a58233650c0d6920aa8df Mon Sep 17 00:00:00 2001 From: whatevertogo Date: Fri, 13 Mar 2026 02:18:26 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20LegacyConversationM?= =?UTF-8?q?anager=20=E5=92=8C=20Peer=20=E7=9A=84=E5=BC=82=E6=AD=A5?= =?UTF-8?q?=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=EF=BC=8C=E7=A1=AE=E4=BF=9D?= =?UTF-8?q?=E6=AD=A3=E7=A1=AE=E5=A4=84=E7=90=86=E5=8F=96=E6=B6=88=E5=92=8C?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E4=BA=8B=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 2 ++ CLAUDE.md | 2 ++ src-new/astrbot_sdk/_legacy_api.py | 4 +-- .../astrbot_sdk/protocol/legacy_adapter.py | 3 ++- src-new/astrbot_sdk/runtime/peer.py | 27 ++++++++++++------- 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index add07dc4e..476a900fc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,6 +5,8 @@ - 2026-03-12: `src/astrbot_sdk/tests/start_client.py` and `benchmark_8_plugins_resource_usage.py` still reference legacy `astrbot_sdk.runtime.galaxy`, but `src-new/astrbot_sdk/runtime/galaxy.py` no longer exists. Treat `tests_v4/test_script_migrations.py` as the maintained replacement instead of reviving the old Galaxy path. - 2026-03-12: Legacy `src/astrbot_sdk/api/event/filter.py` exported a much larger decorator surface than `src-new/astrbot_sdk/api/event/filter.py`. Current compat coverage is enough for `command` / `regex` / `permission` and the exercised migration tests, but it is not a full drop-in replacement for every historical filter helper. - 2026-03-13: Transport-pair startup tests for `SupervisorRuntime` must start a real peer on the opposite transport and provide an `initialize` response. Wiring only the supervisor side drops or captures the outgoing initialize message without replying, and `Peer.initialize()` then waits forever. +- 2026-03-13: `CapabilityRouter.register(..., stream_handler=...)` uses the signature `(request_id, payload, cancel_token)`, not the peer-level `(message, token)`. Reusing the peer handler shape in router tests causes an immediate failed stream event before the test logic runs. +- 2026-03-13: `Peer` had an early-cancel race for inbound invokes: if `CancelMessage` arrived before the invoke task executed its first line, the task could be cancelled before sending any terminal event, leaving the caller's stream iterator waiting forever. Preserve a per-request start event and pre-check the cancel token at the top of `_handle_invoke`. # 开发命令 diff --git a/CLAUDE.md b/CLAUDE.md index add07dc4e..476a900fc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,6 +5,8 @@ - 2026-03-12: `src/astrbot_sdk/tests/start_client.py` and `benchmark_8_plugins_resource_usage.py` still reference legacy `astrbot_sdk.runtime.galaxy`, but `src-new/astrbot_sdk/runtime/galaxy.py` no longer exists. Treat `tests_v4/test_script_migrations.py` as the maintained replacement instead of reviving the old Galaxy path. - 2026-03-12: Legacy `src/astrbot_sdk/api/event/filter.py` exported a much larger decorator surface than `src-new/astrbot_sdk/api/event/filter.py`. Current compat coverage is enough for `command` / `regex` / `permission` and the exercised migration tests, but it is not a full drop-in replacement for every historical filter helper. - 2026-03-13: Transport-pair startup tests for `SupervisorRuntime` must start a real peer on the opposite transport and provide an `initialize` response. Wiring only the supervisor side drops or captures the outgoing initialize message without replying, and `Peer.initialize()` then waits forever. +- 2026-03-13: `CapabilityRouter.register(..., stream_handler=...)` uses the signature `(request_id, payload, cancel_token)`, not the peer-level `(message, token)`. Reusing the peer handler shape in router tests causes an immediate failed stream event before the test logic runs. +- 2026-03-13: `Peer` had an early-cancel race for inbound invokes: if `CancelMessage` arrived before the invoke task executed its first line, the task could be cancelled before sending any terminal event, leaving the caller's stream iterator waiting forever. Preserve a per-request start event and pre-check the cancel token at the top of `_handle_invoke`. # 开发命令 diff --git a/src-new/astrbot_sdk/_legacy_api.py b/src-new/astrbot_sdk/_legacy_api.py index 89e20fea4..53b4e9624 100644 --- a/src-new/astrbot_sdk/_legacy_api.py +++ b/src-new/astrbot_sdk/_legacy_api.py @@ -305,9 +305,7 @@ class LegacyConversationManager: Deprecated: 请使用 update_conversation() 的 title 参数。 """ - await self.update_conversation( - unified_msg_origin, conversation_id, title=title - ) + await self.update_conversation(unified_msg_origin, conversation_id, title=title) async def update_conversation_persona_id( self, diff --git a/src-new/astrbot_sdk/protocol/legacy_adapter.py b/src-new/astrbot_sdk/protocol/legacy_adapter.py index 63edafbb5..d65f4502a 100644 --- a/src-new/astrbot_sdk/protocol/legacy_adapter.py +++ b/src-new/astrbot_sdk/protocol/legacy_adapter.py @@ -173,7 +173,8 @@ class LegacyAdapter: self._coerce_error_payload(error) ), ) - return EventMessage(id=request_id, phase="completed") + # completed phase 需要 output 字段,提供空字典作为默认值 + return EventMessage(id=request_id, phase="completed", output={"done": True}) if method == "cancel": return CancelMessage( diff --git a/src-new/astrbot_sdk/runtime/peer.py b/src-new/astrbot_sdk/runtime/peer.py index d9657f2e4..199d8f6b2 100644 --- a/src-new/astrbot_sdk/runtime/peer.py +++ b/src-new/astrbot_sdk/runtime/peer.py @@ -65,7 +65,9 @@ class Peer: self._unusable = False self._pending_results: dict[str, asyncio.Future[ResultMessage]] = {} self._pending_streams: dict[str, asyncio.Queue[Any]] = {} - self._inbound_tasks: dict[str, tuple[asyncio.Task[None], CancelToken]] = {} + self._inbound_tasks: dict[ + str, tuple[asyncio.Task[None], CancelToken, asyncio.Event] + ] = {} self._remote_initialized = asyncio.Event() def set_initialize_handler(self, handler: InitializeHandler) -> None: @@ -99,7 +101,7 @@ class Peer: self._pending_streams.clear() # 取消所有入站任务 - for task, token in list(self._inbound_tasks.values()): + for task, token, _started in list(self._inbound_tasks.values()): token.cancel() task.cancel() self._inbound_tasks.clear() @@ -287,9 +289,10 @@ class Peer: await self._handle_initialize(message) return if isinstance(message, InvokeMessage): - task = asyncio.create_task(self._handle_invoke(message)) token = CancelToken() - self._inbound_tasks[message.id] = (task, token) + started = asyncio.Event() + task = asyncio.create_task(self._handle_invoke(message, token, started)) + self._inbound_tasks[message.id] = (task, token, started) task.add_done_callback( lambda _task, request_id=message.id: self._inbound_tasks.pop( request_id, None @@ -332,11 +335,16 @@ class Peer: ) self._remote_initialized.set() - async def _handle_invoke(self, message: InvokeMessage) -> None: + async def _handle_invoke( + self, + message: InvokeMessage, + token: CancelToken, + started: asyncio.Event, + ) -> None: """处理远端发起的能力调用,并按流式或非流式协议返回结果。""" - active = self._inbound_tasks.get(message.id) - token = active[1] if active is not None else CancelToken() try: + started.set() + token.raise_if_cancelled() if self._invoke_handler is None: raise AstrBotError.capability_not_found(message.capability) execution = await self._invoke_handler(message, token) @@ -382,11 +390,12 @@ class Peer: inbound = self._inbound_tasks.get(message.id) if inbound is None: return - task, token = inbound + task, token, started = inbound token.cancel() if self._cancel_handler is not None: await self._cancel_handler(message.id) - task.cancel() + if started.is_set(): + task.cancel() async def _handle_result(self, message: ResultMessage) -> None: """处理非流式结果消息并唤醒等待中的调用方。"""