mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-05 22:52:54 +00:00
perf: trim tui startup and refresh work
This commit is contained in:
@@ -190,6 +190,50 @@ describe("tui session actions", () => {
|
||||
expect(listSessions).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("skips UI work when session refresh metadata is unchanged", async () => {
|
||||
const listSessions = vi.fn().mockResolvedValue({
|
||||
ts: Date.now(),
|
||||
path: "/tmp/sessions.json",
|
||||
count: 1,
|
||||
defaults: {},
|
||||
sessions: [
|
||||
{
|
||||
key: "agent:main:main",
|
||||
model: "sonnet-4.6",
|
||||
modelProvider: "anthropic",
|
||||
totalTokens: 42,
|
||||
updatedAt: 200,
|
||||
},
|
||||
],
|
||||
});
|
||||
const state = createBaseState({
|
||||
sessionInfo: {
|
||||
model: "sonnet-4.6",
|
||||
modelProvider: "anthropic",
|
||||
totalTokens: 42,
|
||||
updatedAt: 100,
|
||||
},
|
||||
});
|
||||
const updateFooter = vi.fn();
|
||||
const updateAutocompleteProvider = vi.fn();
|
||||
const requestRender = vi.fn();
|
||||
|
||||
const { refreshSessionInfo } = createTestSessionActions({
|
||||
client: { listSessions } as unknown as TuiBackend,
|
||||
state,
|
||||
updateFooter,
|
||||
updateAutocompleteProvider,
|
||||
tui: { requestRender } as unknown as import("@earendil-works/pi-tui").TUI,
|
||||
});
|
||||
|
||||
await refreshSessionInfo();
|
||||
|
||||
expect(state.sessionInfo.updatedAt).toBe(200);
|
||||
expect(updateAutocompleteProvider).not.toHaveBeenCalled();
|
||||
expect(updateFooter).not.toHaveBeenCalled();
|
||||
expect(requestRender).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps patched model selection when a refresh returns an older snapshot", async () => {
|
||||
const listSessions = vi.fn().mockResolvedValue({
|
||||
ts: Date.now(),
|
||||
|
||||
@@ -49,6 +49,46 @@ type SessionInfoEntry = SessionInfo & {
|
||||
providerOverride?: string;
|
||||
};
|
||||
|
||||
function thinkingLevelsEqual(
|
||||
left?: Array<{ id: string; label: string }>,
|
||||
right?: Array<{ id: string; label: string }>,
|
||||
): boolean {
|
||||
if (left === right) {
|
||||
return true;
|
||||
}
|
||||
if (!left || !right || left.length !== right.length) {
|
||||
return false;
|
||||
}
|
||||
return left.every((level, index) => {
|
||||
const other = right[index];
|
||||
return other?.id === level.id && other.label === level.label;
|
||||
});
|
||||
}
|
||||
|
||||
function goalEquals(left: SessionInfo["goal"], right: SessionInfo["goal"]): boolean {
|
||||
return left === right || JSON.stringify(left ?? null) === JSON.stringify(right ?? null);
|
||||
}
|
||||
|
||||
function sessionInfoUiEquals(left: SessionInfo, right: SessionInfo): boolean {
|
||||
return (
|
||||
left.thinkingLevel === right.thinkingLevel &&
|
||||
thinkingLevelsEqual(left.thinkingLevels, right.thinkingLevels) &&
|
||||
left.fastMode === right.fastMode &&
|
||||
left.verboseLevel === right.verboseLevel &&
|
||||
left.traceLevel === right.traceLevel &&
|
||||
left.reasoningLevel === right.reasoningLevel &&
|
||||
left.model === right.model &&
|
||||
left.modelProvider === right.modelProvider &&
|
||||
left.contextTokens === right.contextTokens &&
|
||||
left.inputTokens === right.inputTokens &&
|
||||
left.outputTokens === right.outputTokens &&
|
||||
left.totalTokens === right.totalTokens &&
|
||||
left.responseUsage === right.responseUsage &&
|
||||
left.displayName === right.displayName &&
|
||||
goalEquals(left.goal, right.goal)
|
||||
);
|
||||
}
|
||||
|
||||
export function createSessionActions(context: SessionActionContext) {
|
||||
const {
|
||||
client,
|
||||
@@ -225,10 +265,17 @@ export function createSessionActions(context: SessionActionContext) {
|
||||
next.model = selection.model;
|
||||
}
|
||||
|
||||
const previous = state.sessionInfo;
|
||||
const uiChanged = !sessionInfoUiEquals(previous, next);
|
||||
if (!uiChanged && previous.updatedAt === next.updatedAt) {
|
||||
return;
|
||||
}
|
||||
state.sessionInfo = next;
|
||||
updateAutocompleteProvider();
|
||||
updateFooter();
|
||||
tui.requestRender();
|
||||
if (uiChanged) {
|
||||
updateAutocompleteProvider();
|
||||
updateFooter();
|
||||
tui.requestRender();
|
||||
}
|
||||
};
|
||||
|
||||
const runRefreshSessionInfo = async () => {
|
||||
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
import { getSlashCommands } from "./commands.js";
|
||||
import { ChatLog } from "./components/chat-log.js";
|
||||
import { CustomEditor } from "./components/custom-editor.js";
|
||||
import { GatewayChatClient } from "./gateway-chat.js";
|
||||
import { resolveLocalRunShutdownGraceMs } from "./local-run-shutdown.js";
|
||||
import { editorTheme, theme } from "./theme/theme.js";
|
||||
import type { TuiBackend } from "./tui-backend.js";
|
||||
@@ -522,6 +521,8 @@ export async function runTui(opts: RunTuiOptions): Promise<TuiResult> {
|
||||
let dynamicSlashCommandsKey: string | null = null;
|
||||
let dynamicSlashCommandsInFlightKey: string | null = null;
|
||||
let dynamicSlashCommandsRequestId = 0;
|
||||
let dynamicSlashCommandsReady = false;
|
||||
let dynamicSlashCommandsRefreshTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let lastCtrlCAt = 0;
|
||||
let exitRequested = false;
|
||||
let exitResult: TuiResult = { exitReason: "exit" };
|
||||
@@ -714,6 +715,7 @@ export async function runTui(opts: RunTuiOptions): Promise<TuiResult> {
|
||||
const { EmbeddedTuiBackend } = await import("./embedded-backend.js");
|
||||
client = new EmbeddedTuiBackend();
|
||||
} else {
|
||||
const { GatewayChatClient } = await import("./gateway-chat.js");
|
||||
client = await GatewayChatClient.connect({
|
||||
url: opts.url,
|
||||
token: opts.token,
|
||||
@@ -769,9 +771,19 @@ export async function runTui(opts: RunTuiOptions): Promise<TuiResult> {
|
||||
);
|
||||
};
|
||||
|
||||
const clearDynamicSlashCommandsRefreshTimer = () => {
|
||||
if (!dynamicSlashCommandsRefreshTimer) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(dynamicSlashCommandsRefreshTimer);
|
||||
dynamicSlashCommandsRefreshTimer = null;
|
||||
};
|
||||
|
||||
const refreshDynamicSlashCommands = () => {
|
||||
clearDynamicSlashCommandsRefreshTimer();
|
||||
const key = resolveDynamicSlashCommandsKey();
|
||||
if (
|
||||
!dynamicSlashCommandsReady ||
|
||||
!isConnected ||
|
||||
!client.listCommands ||
|
||||
dynamicSlashCommandsKey === key ||
|
||||
@@ -807,9 +819,21 @@ export async function runTui(opts: RunTuiOptions): Promise<TuiResult> {
|
||||
});
|
||||
};
|
||||
|
||||
const scheduleDynamicSlashCommandsRefresh = () => {
|
||||
if (
|
||||
!dynamicSlashCommandsReady ||
|
||||
dynamicSlashCommandsRefreshTimer ||
|
||||
dynamicSlashCommandsKey === resolveDynamicSlashCommandsKey()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
dynamicSlashCommandsRefreshTimer = setTimeout(refreshDynamicSlashCommands, 0);
|
||||
dynamicSlashCommandsRefreshTimer.unref?.();
|
||||
};
|
||||
|
||||
const updateAutocompleteProvider = () => {
|
||||
applyAutocompleteProvider();
|
||||
refreshDynamicSlashCommands();
|
||||
scheduleDynamicSlashCommandsRefresh();
|
||||
};
|
||||
|
||||
tui.addChild(root);
|
||||
@@ -1474,6 +1498,8 @@ export async function runTui(opts: RunTuiOptions): Promise<TuiResult> {
|
||||
4000,
|
||||
);
|
||||
tui.requestRender();
|
||||
dynamicSlashCommandsReady = true;
|
||||
scheduleDynamicSlashCommandsRefresh();
|
||||
if (!autoMessageSent && autoMessage) {
|
||||
autoMessageSent = true;
|
||||
await sendMessage(autoMessage);
|
||||
@@ -1494,6 +1520,8 @@ export async function runTui(opts: RunTuiOptions): Promise<TuiResult> {
|
||||
dynamicSlashCommands = [];
|
||||
dynamicSlashCommandsKey = null;
|
||||
dynamicSlashCommandsInFlightKey = null;
|
||||
dynamicSlashCommandsReady = false;
|
||||
clearDynamicSlashCommandsRefreshTimer();
|
||||
dynamicSlashCommandsRequestId += 1;
|
||||
updateAutocompleteProvider();
|
||||
pauseStreamingWatchdog();
|
||||
|
||||
Reference in New Issue
Block a user