Skip to content

Commit eb2b75b

Browse files
committed
fix(ui): clear pending run state on cross-run final to fix stuck typing indicator
When a subagent completes and sends a final event with a different runId, the WebChat UI appends the message but did not clear chatRunId / chatStream / chatStreamStartedAt. This left the parent agent visually stuck in a typing / processing state even after the final response was already visible. Refreshing the page cleared it. Now clear the pending run state when a cross-run final carries a real (non-NO_REPLY) assistant message. NO_REPLY / empty finals still preserve the parent run state since the parent agent may still be processing. Closes #57795 Assisted-by: Claude Code
1 parent 910134b commit eb2b75b

2 files changed

Lines changed: 14 additions & 6 deletions

File tree

ui/src/ui/controllers/chat.test.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ describe("handleChatEvent", () => {
9999
expect(state.chatStream).toBe("Hello");
100100
});
101101

102-
it("appends final payload from another run without clearing active stream", () => {
102+
it("appends final payload from another run and clears pending run state", () => {
103103
const state = createState({
104104
sessionKey: "main",
105105
chatRunId: "run-user",
@@ -115,10 +115,12 @@ describe("handleChatEvent", () => {
115115
content: [{ type: "text", text: "Sub-agent findings" }],
116116
},
117117
};
118-
expect(handleChatEvent(state, payload)).toBe(null);
119-
expect(state.chatRunId).toBe("run-user");
120-
expect(state.chatStream).toBe("Working...");
121-
expect(state.chatStreamStartedAt).toBe(123);
118+
expect(handleChatEvent(state, payload)).toBe("final");
119+
// Pending run state must be cleared so the UI exits typing indicator.
120+
// See https://github.com/openclaw/openclaw/issues/57795
121+
expect(state.chatRunId).toBe(null);
122+
expect(state.chatStream).toBe(null);
123+
expect(state.chatStreamStartedAt).toBe(null);
122124
expect(state.chatMessages).toHaveLength(1);
123125
expect(state.chatMessages[0]).toEqual(payload.message);
124126
});

ui/src/ui/controllers/chat.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,13 @@ export function handleChatEvent(state: ChatState, payload?: ChatEventPayload) {
285285
const finalMessage = normalizeFinalAssistantMessage(payload.message);
286286
if (finalMessage && !isAssistantSilentReply(finalMessage)) {
287287
state.chatMessages = [...state.chatMessages, finalMessage];
288-
return null;
288+
// Clear pending run state so the UI exits the typing/processing
289+
// indicator. Without this, the parent agent appears stuck in typing
290+
// state after subagent completion even though the final response is
291+
// already visible. See #57795.
292+
state.chatRunId = null;
293+
state.chatStream = null;
294+
state.chatStreamStartedAt = null;
289295
}
290296
return "final";
291297
}

0 commit comments

Comments
 (0)