UI: stop dashboard chat history reload storm
This commit is contained in:
parent
868fd32ee7
commit
1fd89805b6
@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Dashboard/chat UI: stop reloading full chat history on every live tool result in dashboard v2 so tool-heavy runs no longer trigger UI freeze/re-render storms while the final event still refreshes persisted history. Thanks @BunsDev.
|
||||
- Telegram/webhook auth: validate the Telegram webhook secret before reading or parsing request bodies, so unauthenticated requests are rejected immediately instead of consuming up to 1 MB first. Thanks @space08.
|
||||
- Build/plugin-sdk bundling: bundle plugin-sdk subpath entries in one shared build pass so published packages stop duplicating shared chunks and avoid the recent plugin-sdk memory blow-up. (#45426) Thanks @TarasShyn.
|
||||
- Browser/existing-session: accept text-only `list_pages` and `new_page` responses from Chrome DevTools MCP so live-session tab discovery and new-tab open flows keep working when the server omits structured page metadata.
|
||||
|
||||
@ -3,6 +3,8 @@ import { GATEWAY_EVENT_UPDATE_AVAILABLE } from "../../../src/gateway/events.js";
|
||||
import { ConnectErrorDetailCodes } from "../../../src/gateway/protocol/connect-error-details.js";
|
||||
import { connectGateway, resolveControlUiClientVersion } from "./app-gateway.ts";
|
||||
|
||||
const loadChatHistoryMock = vi.hoisted(() => vi.fn(async () => undefined));
|
||||
|
||||
type GatewayClientMock = {
|
||||
start: ReturnType<typeof vi.fn>;
|
||||
stop: ReturnType<typeof vi.fn>;
|
||||
@ -70,6 +72,14 @@ vi.mock("./gateway.ts", () => {
|
||||
return { GatewayBrowserClient, resolveGatewayErrorDetailCode };
|
||||
});
|
||||
|
||||
vi.mock("./controllers/chat.ts", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./controllers/chat.ts")>();
|
||||
return {
|
||||
...actual,
|
||||
loadChatHistory: loadChatHistoryMock,
|
||||
};
|
||||
});
|
||||
|
||||
function createHost() {
|
||||
return {
|
||||
settings: {
|
||||
@ -106,7 +116,15 @@ function createHost() {
|
||||
assistantAgentId: null,
|
||||
serverVersion: null,
|
||||
sessionKey: "main",
|
||||
chatMessages: [],
|
||||
chatToolMessages: [],
|
||||
chatStreamSegments: [],
|
||||
chatStream: null,
|
||||
chatStreamStartedAt: null,
|
||||
chatRunId: null,
|
||||
toolStreamById: new Map(),
|
||||
toolStreamOrder: [],
|
||||
toolStreamSyncTimer: null,
|
||||
refreshSessionsAfterChat: new Set<string>(),
|
||||
execApprovalQueue: [],
|
||||
execApprovalError: null,
|
||||
@ -117,6 +135,7 @@ function createHost() {
|
||||
describe("connectGateway", () => {
|
||||
beforeEach(() => {
|
||||
gatewayClientInstances.length = 0;
|
||||
loadChatHistoryMock.mockClear();
|
||||
});
|
||||
|
||||
it("ignores stale client onGap callbacks after reconnect", () => {
|
||||
@ -294,6 +313,73 @@ describe("connectGateway", () => {
|
||||
expect(host.lastError).toContain("gateway token mismatch");
|
||||
expect(host.lastErrorCode).toBe("AUTH_TOKEN_MISMATCH");
|
||||
});
|
||||
|
||||
it("does not reload chat history for each live tool result event", () => {
|
||||
const host = createHost();
|
||||
|
||||
connectGateway(host);
|
||||
const client = gatewayClientInstances[0];
|
||||
expect(client).toBeDefined();
|
||||
|
||||
client.emitEvent({
|
||||
event: "agent",
|
||||
payload: {
|
||||
runId: "engine-run-1",
|
||||
seq: 1,
|
||||
stream: "tool",
|
||||
ts: 1,
|
||||
sessionKey: "main",
|
||||
data: {
|
||||
toolCallId: "tool-1",
|
||||
name: "fetch",
|
||||
phase: "result",
|
||||
result: { text: "ok" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(loadChatHistoryMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reloads chat history once after the final chat event when tool output was used", () => {
|
||||
const host = createHost();
|
||||
|
||||
connectGateway(host);
|
||||
const client = gatewayClientInstances[0];
|
||||
expect(client).toBeDefined();
|
||||
|
||||
client.emitEvent({
|
||||
event: "agent",
|
||||
payload: {
|
||||
runId: "engine-run-1",
|
||||
seq: 1,
|
||||
stream: "tool",
|
||||
ts: 1,
|
||||
sessionKey: "main",
|
||||
data: {
|
||||
toolCallId: "tool-1",
|
||||
name: "fetch",
|
||||
phase: "result",
|
||||
result: { text: "ok" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
client.emitEvent({
|
||||
event: "chat",
|
||||
payload: {
|
||||
runId: "engine-run-1",
|
||||
sessionKey: "main",
|
||||
state: "final",
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Done" }],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(loadChatHistoryMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveControlUiClientVersion", () => {
|
||||
|
||||
@ -339,17 +339,6 @@ function handleGatewayEventUnsafe(host: GatewayHost, evt: GatewayEventFrame) {
|
||||
host as unknown as Parameters<typeof handleAgentEvent>[0],
|
||||
evt.payload as AgentEventPayload | undefined,
|
||||
);
|
||||
// Reload history after each tool result so the persisted text + tool
|
||||
// output replaces any truncated streaming fragments.
|
||||
const agentPayload = evt.payload as AgentEventPayload | undefined;
|
||||
const toolData = agentPayload?.data;
|
||||
if (
|
||||
agentPayload?.stream === "tool" &&
|
||||
typeof toolData?.phase === "string" &&
|
||||
toolData.phase === "result"
|
||||
) {
|
||||
void loadChatHistory(host as unknown as OpenClawApp);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user