feat(ui): refresh session list after chat commands in Web UI

This commit is contained in:
Tyler Yust
2026-01-30 14:29:04 -08:00
parent 34bdbdb405
commit 0b7aa8cf1d
6 changed files with 29 additions and 16 deletions

View File

@@ -92,6 +92,7 @@ Status: stable.
- TTS: read OPENAI_TTS_BASE_URL at runtime instead of module load to honor config.env. (#3341) Thanks @hclsys.
- macOS: auto-scroll to bottom when sending a new message while scrolled up. (#2471) Thanks @kennyklee.
- Web UI: auto-expand the chat compose textarea while typing (with sensible max height). (#2950) Thanks @shivamraut101.
- Web UI: refresh sessions after queued /new or /reset commands once the run completes.
- Gateway: prevent crashes on transient network errors (fetch failures, timeouts, DNS). Added fatal error detection to only exit on truly critical errors. Fixes #2895, #2879, #2873. (#2980) Thanks @elliotsecops.
- Agents: guard channel tool listActions to avoid plugin crashes. (#2859) Thanks @mbelinky.
- Discord: stop resolveDiscordTarget from passing directory params into messaging target parsers. Fixes #3167. Thanks @thewilloftheshadow.

View File

@@ -21,7 +21,7 @@ type ChatHost = {
basePath: string;
hello: GatewayHelloOk | null;
chatAvatarUrl: string | null;
refreshSessionsAfterChat: boolean;
refreshSessionsAfterChat: Set<string>;
};
export function isChatBusy(host: ChatHost) {
@@ -56,7 +56,12 @@ export async function handleAbortChat(host: ChatHost) {
await abortChatRun(host as unknown as OpenClawApp);
}
function enqueueChatMessage(host: ChatHost, text: string, attachments?: ChatAttachment[]) {
function enqueueChatMessage(
host: ChatHost,
text: string,
attachments?: ChatAttachment[],
refreshSessions?: boolean,
) {
const trimmed = text.trim();
const hasAttachments = Boolean(attachments && attachments.length > 0);
if (!trimmed && !hasAttachments) return;
@@ -67,6 +72,7 @@ function enqueueChatMessage(host: ChatHost, text: string, attachments?: ChatAtta
text: trimmed,
createdAt: Date.now(),
attachments: hasAttachments ? attachments?.map((att) => ({ ...att })) : undefined,
refreshSessions,
},
];
}
@@ -84,7 +90,8 @@ async function sendChatMessageNow(
},
) {
resetToolStream(host as unknown as Parameters<typeof resetToolStream>[0]);
const ok = await sendChatMessage(host as unknown as OpenClawApp, message, opts?.attachments);
const runId = await sendChatMessage(host as unknown as OpenClawApp, message, opts?.attachments);
const ok = Boolean(runId);
if (!ok && opts?.previousDraft != null) {
host.chatMessage = opts.previousDraft;
}
@@ -104,8 +111,8 @@ async function sendChatMessageNow(
if (ok && !host.chatRunId) {
void flushChatQueue(host);
}
if (ok && opts?.refreshSessions) {
host.refreshSessionsAfterChat = true;
if (ok && opts?.refreshSessions && runId) {
host.refreshSessionsAfterChat.add(runId);
}
return ok;
}
@@ -115,7 +122,10 @@ async function flushChatQueue(host: ChatHost) {
const [next, ...rest] = host.chatQueue;
if (!next) return;
host.chatQueue = rest;
const ok = await sendChatMessageNow(host, next.text, { attachments: next.attachments });
const ok = await sendChatMessageNow(host, next.text, {
attachments: next.attachments,
refreshSessions: next.refreshSessions,
});
if (!ok) {
host.chatQueue = [next, ...host.chatQueue];
}
@@ -153,7 +163,7 @@ export async function handleSendChat(
}
if (isChatBusy(host)) {
enqueueChatMessage(host, message, attachmentsToSend);
enqueueChatMessage(host, message, attachmentsToSend, refreshSessions);
return;
}

View File

@@ -51,7 +51,7 @@ type GatewayHost = {
assistantAgentId: string | null;
sessionKey: string;
chatRunId: string | null;
refreshSessionsAfterChat: boolean;
refreshSessionsAfterChat: Set<string>;
execApprovalQueue: ExecApprovalRequest[];
execApprovalError: string | null;
};
@@ -196,8 +196,9 @@ function handleGatewayEventUnsafe(host: GatewayHost, evt: GatewayEventFrame) {
void flushChatQueueForEvent(
host as unknown as Parameters<typeof flushChatQueueForEvent>[0],
);
if (host.refreshSessionsAfterChat) {
host.refreshSessionsAfterChat = false;
const runId = payload?.runId;
if (runId && host.refreshSessionsAfterChat.has(runId)) {
host.refreshSessionsAfterChat.delete(runId);
if (state === "final") {
void loadSessions(host as unknown as OpenClawApp, { activeMinutes: 0 });
}

View File

@@ -258,7 +258,7 @@ export class OpenClawApp extends LitElement {
private logsScrollFrame: number | null = null;
private toolStreamById = new Map<string, ToolStreamEntry>();
private toolStreamOrder: string[] = [];
refreshSessionsAfterChat = false;
refreshSessionsAfterChat = new Set<string>();
basePath = "";
private popStateHandler = () =>
onPopStateInternal(

View File

@@ -55,11 +55,11 @@ export async function sendChatMessage(
state: ChatState,
message: string,
attachments?: ChatAttachment[],
): Promise<boolean> {
if (!state.client || !state.connected) return false;
): Promise<string | null> {
if (!state.client || !state.connected) return null;
const msg = message.trim();
const hasAttachments = attachments && attachments.length > 0;
if (!msg && !hasAttachments) return false;
if (!msg && !hasAttachments) return null;
const now = Date.now();
@@ -117,7 +117,7 @@ export async function sendChatMessage(
idempotencyKey: runId,
attachments: apiAttachments,
});
return true;
return runId;
} catch (err) {
const error = String(err);
state.chatRunId = null;
@@ -132,7 +132,7 @@ export async function sendChatMessage(
timestamp: Date.now(),
},
];
return false;
return null;
} finally {
state.chatSending = false;
}

View File

@@ -9,6 +9,7 @@ export type ChatQueueItem = {
text: string;
createdAt: number;
attachments?: ChatAttachment[];
refreshSessions?: boolean;
};
export const CRON_CHANNEL_LAST = "last";