fix(control-ui): preserve optimistic messages on empty history

This commit is contained in:
Peter Steinberger
2026-04-26 03:44:57 +01:00
parent 97ae1c7c2e
commit 07877d71cd
3 changed files with 37 additions and 1 deletions

View File

@@ -76,6 +76,7 @@ Docs: https://docs.openclaw.ai
- ACP: wait for the configured runtime backend to become healthy before startup identity reconciliation, avoiding transient acpx warnings during Gateway boot. Fixes #40566.
- Channels/ACP bindings: time out configured binding readiness checks instead of letting Discord preflight hang forever when an ACP target never settles. Fixes #68776.
- Control UI: hide the chat loading skeleton during background history reloads when existing messages or active stream content are already visible, avoiding reload flashes on high-latency local gateways. Fixes #71844. Thanks @WolvenRA.
- Control UI: keep locally optimistic chat messages visible when a history reload temporarily returns empty, avoiding lost first-turn messages on high-latency gateways. Fixes #71878. Thanks @WolvenRA.
- Agents/images: scrub old `[media attached: ...]`, `[Image: source: ...]`,
and `media://inbound/...` markers from pruned model replay context so stale
media refs are not rehydrated as fresh prompt images. Fixes #71868. Thanks

View File

@@ -818,6 +818,33 @@ describe("loadChatHistory", () => {
expect(state.chatStream).toBeNull();
});
it("keeps local optimistic messages when history reload returns empty", async () => {
const optimisticUser = {
role: "user",
content: [{ type: "text", text: "first ask" }],
timestamp: 10,
};
const optimisticAssistant = {
role: "assistant",
content: [{ type: "text", text: "first answer" }],
timestamp: 11,
};
const request = vi.fn().mockResolvedValue({
messages: [],
thinkingLevel: "low",
});
const state = createState({
connected: true,
client: { request } as unknown as ChatState["client"],
chatMessages: [optimisticUser, optimisticAssistant],
});
await loadChatHistory(state);
expect(state.chatMessages).toEqual([optimisticUser, optimisticAssistant]);
expect(state.chatStream).toBeNull();
});
it("does not duplicate optimistic tail messages after history catches up", async () => {
const optimisticUser = {
role: "user",

View File

@@ -268,9 +268,17 @@ function preserveOptimisticTailMessages(
historyMessages: unknown[],
previousMessages: unknown[],
): unknown[] {
if (historyMessages.length === 0 || previousMessages.length === 0) {
if (previousMessages.length === 0) {
return historyMessages;
}
if (historyMessages.length === 0) {
const optimisticMessages = previousMessages.filter(
(message) => isLocallyOptimisticHistoryMessage(message) && !shouldHideHistoryMessage(message),
);
return optimisticMessages.length === previousMessages.length
? previousMessages
: historyMessages;
}
const historySignatures = new Set(
historyMessages
.map((message) => messageDisplaySignature(message))