fix(ui): reuse persisted text during chat streaming (#146831)

This commit is contained in:
Vincent Koc 2026-09-13 19:26:58 +08:00 • committed by GitHub
parent 9ca900f3b3
commit dee64cddf3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 55 additions and 3 deletions

View file

@ -824,6 +824,58 @@ describe("handleChatGatewayEvent", () => {
expect(state.chatStream).toBe(expected);
});
it("reuses persisted text across live deltas and refreshes replaced messages", () => {
const persistedMessage = (id: string, text: string) => {
const readContent = vi.fn(() => [{ type: "text", text }]);
return {
readContent,
message: {
role: "assistant",
get content() {
return readContent();
},
__openclaw: { id, runId: "run-1" },
},
};
};
const first = persistedMessage("part-a", "A");
const second = persistedMessage("part-b", "B");
const state = createState({
chatRunId: "run-1",
chatMessages: [first.message, second.message],
});
const receiveDelta = (text: string) => {
expect(
handleChatGatewayEvent(state, {
runId: "run-1",
sessionKey: "main",
state: "delta",
message: createTextChatMessage("assistant", text),
}),
).toBe("delta");
return visibleCurrentAssistantStreamTail(state, () => false);
};
expect(receiveDelta("ABC")).toBe("C");
expect(first.readContent).toHaveBeenCalled();
expect(second.readContent).toHaveBeenCalled();
first.readContent.mockClear();
second.readContent.mockClear();
for (const text of ["ABCD", "ABCDE", "ABCDEF"]) {
expect(receiveDelta(text)).toBe(text.slice(2));
}
expect(first.readContent).not.toHaveBeenCalled();
expect(second.readContent).not.toHaveBeenCalled();
const replacement = persistedMessage("part-b", "BC");
state.chatMessages = [first.message, replacement.message];
expect(receiveDelta("ABCDEFG")).toBe("DEFG");
expect(replacement.readContent).toHaveBeenCalled();
expect(first.readContent).not.toHaveBeenCalled();
expect(second.readContent).not.toHaveBeenCalled();
});
it("adopts the run id for selected-session live deltas observed from another channel", () => {
const state = createState({
sessionKey: "agent:main:feishu:direct:peer-1",

View file

@ -9,7 +9,7 @@ import {
streamSegmentUsesAccumulatedText,
type ChatStreamSegment,
} from "../../lib/chat/chat-types.ts";
import { extractText } from "../../lib/chat/message-extract.ts";
import { extractText, extractTextCached } from "../../lib/chat/message-extract.ts";
import { userTurnRunId } from "./chat-thread-items.ts";
import { isKeyedAssistantStreamFallbackMessage } from "./chat-thread-run-identity.ts";
@ -236,7 +236,7 @@ export function resolveCumulativeAssistantTail(
const identity = readSessionMessageIdentity(message);
const persistedText =
identity?.runId === runId && !isKeyedAssistantStreamFallbackMessage(message)
? extractText(message)
? extractTextCached(message)
: null;
if (
identity?.role === "assistant" &&
@ -255,7 +255,7 @@ export function resolveCumulativeAssistantTail(
return identity?.role === "assistant" &&
(!identity.runId || identity.runId === runId) &&
!isKeyedAssistantStreamFallbackMessage(message)
? extractText(message)
? extractTextCached(message)
: null;
});
return resolveAssistantTextTail(persistedTexts, cumulativeText);