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

View file

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

View file

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

View file

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