mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:40:44 +00:00
fix(ui): cap responsiveness event logs
This commit is contained in:
@@ -60,6 +60,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Control UI/Talk: make failed Talk startup errors dismissable and clear the stale Talk error state when dismissed, so missing realtime voice provider configuration does not leave a permanent chat banner. Fixes #77071. Thanks @ijoshdavis.
|
||||
- Control UI/performance: cap long-task and long-animation-frame diagnostics in the shared event log, so slow-render telemetry does not evict gateway/plugin events from the Debug and Overview views. Thanks @vincentkoc.
|
||||
- Web fetch: late-bind `web_fetch` config and provider fallback metadata from the active runtime snapshot, matching `web_search` so long-lived tools do not use stale fetch provider settings. Thanks @vincentkoc.
|
||||
- Discord: clear stale startup probe bot/application status when the async bot probe throws, not just when it returns a degraded probe result. Thanks @vincentkoc.
|
||||
- Web search: scope explicit bundled `web_search` provider runtime loading through manifest ownership, so selecting DuckDuckGo/Gemini/etc. does not import unrelated bundled providers or log their optional dependency failures. Thanks @vincentkoc.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { EventLogEntry } from "./app-events.ts";
|
||||
import {
|
||||
recordControlUiPerformanceEvent,
|
||||
startControlUiResponsivenessObserver,
|
||||
@@ -46,8 +47,8 @@ function installPerformanceObserverMock(options: {
|
||||
function createHost() {
|
||||
return {
|
||||
tab: "chat" as const,
|
||||
eventLog: [] as Array<{ payload: Record<string, unknown> }>,
|
||||
eventLogBuffer: [] as Array<{ payload: Record<string, unknown> }>,
|
||||
eventLog: [] as EventLogEntry[],
|
||||
eventLogBuffer: [] as EventLogEntry[],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -161,6 +162,35 @@ describe("startControlUiResponsivenessObserver", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("caps responsiveness events so gateway events stay visible", () => {
|
||||
vi.spyOn(console, "warn").mockImplementation(() => undefined);
|
||||
const mock = installPerformanceObserverMock({
|
||||
supportedEntryTypes: ["longtask"],
|
||||
});
|
||||
const host = createHost();
|
||||
|
||||
for (let i = 0; i < 225; i += 1) {
|
||||
recordControlUiPerformanceEvent(host, "gateway.event", { i }, { console: false });
|
||||
}
|
||||
|
||||
startControlUiResponsivenessObserver(host);
|
||||
for (let i = 0; i < 80; i += 1) {
|
||||
mock.emit([
|
||||
{
|
||||
name: "self",
|
||||
startTime: i,
|
||||
duration: 51,
|
||||
} as unknown as PerformanceEntry,
|
||||
]);
|
||||
}
|
||||
|
||||
expect(host.eventLogBuffer).toHaveLength(250);
|
||||
expect(
|
||||
host.eventLogBuffer.filter((entry) => entry.event === "control-ui.longtask"),
|
||||
).toHaveLength(50);
|
||||
expect(host.eventLogBuffer.some((entry) => entry.event === "gateway.event")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns null when responsiveness entries are unsupported or observe fails", () => {
|
||||
installPerformanceObserverMock({ supportedEntryTypes: [] });
|
||||
expect(startControlUiResponsivenessObserver(createHost())).toBeNull();
|
||||
|
||||
@@ -22,6 +22,7 @@ export type ControlUiRefreshRun = {
|
||||
const EVENT_LOG_LIMIT = 250;
|
||||
const SLOW_RPC_MS = 1_000;
|
||||
const RESPONSIVENESS_ENTRY_MS = 50;
|
||||
const RESPONSIVENESS_EVENT_LOG_LIMIT = 50;
|
||||
|
||||
type ControlUiResponsivenessObserver = {
|
||||
disconnect: () => void;
|
||||
@@ -86,11 +87,19 @@ export function recordControlUiPerformanceEvent(
|
||||
host: ControlUiPerformanceHost,
|
||||
event: string,
|
||||
payload: Record<string, unknown>,
|
||||
opts?: { warn?: boolean; console?: boolean },
|
||||
opts?: { warn?: boolean; console?: boolean; maxBufferedEventsForType?: number },
|
||||
) {
|
||||
const entry: EventLogEntry = { ts: Date.now(), event, payload };
|
||||
if (Array.isArray(host.eventLogBuffer)) {
|
||||
host.eventLogBuffer = [entry, ...host.eventLogBuffer].slice(0, EVENT_LOG_LIMIT);
|
||||
const existingBuffer =
|
||||
typeof opts?.maxBufferedEventsForType === "number"
|
||||
? keepLatestBufferedEventsForType(
|
||||
host.eventLogBuffer,
|
||||
event,
|
||||
Math.max(0, opts.maxBufferedEventsForType - 1),
|
||||
)
|
||||
: host.eventLogBuffer;
|
||||
host.eventLogBuffer = [entry, ...existingBuffer].slice(0, EVENT_LOG_LIMIT);
|
||||
if (host.tab === "debug" || host.tab === "overview") {
|
||||
host.eventLog = host.eventLogBuffer;
|
||||
}
|
||||
@@ -101,6 +110,26 @@ export function recordControlUiPerformanceEvent(
|
||||
logPerformanceEvent(event, payload, opts?.warn === true);
|
||||
}
|
||||
|
||||
function keepLatestBufferedEventsForType(
|
||||
entries: unknown[],
|
||||
event: string,
|
||||
maxExistingForType: number,
|
||||
): unknown[] {
|
||||
let keptForType = 0;
|
||||
return entries.filter((entry) => {
|
||||
if (
|
||||
!entry ||
|
||||
typeof entry !== "object" ||
|
||||
!("event" in entry) ||
|
||||
(entry as { event?: unknown }).event !== event
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
keptForType += 1;
|
||||
return keptForType <= maxExistingForType;
|
||||
});
|
||||
}
|
||||
|
||||
export function scheduleControlUiTabVisibleTiming(
|
||||
host: ControlUiPerformanceHost,
|
||||
previousTab: Tab,
|
||||
@@ -256,7 +285,7 @@ function recordResponsivenessEntry(
|
||||
scriptCount: Array.isArray(entry.scripts) ? entry.scripts.length : undefined,
|
||||
topScript: getTopLongAnimationFrameScript(entry.scripts),
|
||||
},
|
||||
{ warn: true },
|
||||
{ warn: true, maxBufferedEventsForType: RESPONSIVENESS_EVENT_LOG_LIMIT },
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user