From ab4cf98d7f8aeac960a117febb68ccb73c665232 Mon Sep 17 00:00:00 2001 From: Mason Huang Date: Fri, 24 Apr 2026 21:55:54 +0800 Subject: [PATCH] test(gateway): cover chat.send lifecycle error link --- CHANGELOG.md | 5 +++ src/gateway/server-chat.agent-events.test.ts | 38 ++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63ec6666755..f8390b65071 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -350,6 +350,11 @@ Docs: https://docs.openclaw.ai - Channels/thread routing: keep outbound replies in existing Slack, Mattermost, Matrix, Telegram, Discord, and QA-channel thread sessions by sharing the Plugin SDK thread-aware route builder across bundled plugins. - Agents/replay: normalize restored assistant text content before provider replay and prompt submission, so legacy or repaired sessions no longer crash on `assistantMsg.content.flatMap`. (#69850) Thanks @fuller-stack-dev. +### Fixes + +- Gateway/chat: register chat.send runs in the chat run registry so lifecycle error events reach the client instead of being silently dropped, fixing stuck 'waiting' state and /abort reporting no active run. (#69747) Thanks @wangshu94. +- fix(gateway): surface chat.send lifecycle errors to clients (#69747). Thanks @wangshu94 + ## 2026.4.20 ### Changes diff --git a/src/gateway/server-chat.agent-events.test.ts b/src/gateway/server-chat.agent-events.test.ts index 0e5886aca49..70674a5fff5 100644 --- a/src/gateway/server-chat.agent-events.test.ts +++ b/src/gateway/server-chat.agent-events.test.ts @@ -1326,6 +1326,44 @@ describe("agent event handler", () => { expect(agentRunSeq.has("run-chat-send")).toBe(false); }); + it("emits lifecycle chat errors for active chat.send runs with a chat run link", () => { + vi.useFakeTimers(); + const { broadcast, chatRunState, clearAgentRunContext, agentRunSeq, handler } = createHarness({ + resolveSessionKeyForRun: () => "session-chat-send", + lifecycleErrorRetryGraceMs: 100, + isChatSendRunActive: (runId) => runId === "run-chat-send", + }); + chatRunState.registry.add("run-chat-send", { + sessionKey: "session-chat-send", + clientRunId: "run-chat-send", + }); + registerAgentRunContext("run-chat-send", { sessionKey: "session-chat-send" }); + + handler({ + runId: "run-chat-send", + seq: 1, + stream: "lifecycle", + ts: Date.now(), + data: { phase: "error", error: "chat.send failed" }, + }); + + vi.advanceTimersByTime(100); + + const chatErrors = chatBroadcastCalls(broadcast).filter( + ([, payload]) => (payload as { state?: string }).state === "error", + ); + expect(chatErrors).toHaveLength(1); + expect(chatErrors[0]?.[1]).toMatchObject({ + runId: "run-chat-send", + sessionKey: "session-chat-send", + state: "error", + errorMessage: "chat.send failed", + }); + expect(chatRunState.registry.peek("run-chat-send")).toBeUndefined(); + expect(clearAgentRunContext).toHaveBeenCalledWith("run-chat-send"); + expect(agentRunSeq.has("run-chat-send")).toBe(false); + }); + it("suppresses chat and node session events for non-control-UI-visible runs", () => { const { broadcast, nodeSendToSession, handler } = createHarness({ resolveSessionKeyForRun: () => "session-hidden",