From fa0bfe9d707f963c0c3ec85bc54bae572daa5979 Mon Sep 17 00:00:00 2001 From: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 14:34:27 +0000 Subject: [PATCH] fix(webchat): create dashboard sessions from New Chat --- CHANGELOG.md | 2 +- ui/src/ui/app-render.helpers.node.test.ts | 23 +++++++++++++++++++++++ ui/src/ui/app-render.helpers.ts | 4 ++-- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cced587a0ba..8a18f2baa55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -423,7 +423,7 @@ Docs: https://docs.openclaw.ai - Outbound/security: strip known internal runtime scaffolding such as `` and `` at the final channel delivery boundary and keep Discord output on targeted tag stripping, so degraded harness replies cannot leak those tags to users. Fixes #73595. Thanks @gabrielexito-stack and @martingarramon. - Security/Telegram: load Telegram security adapters in read-only audit/doctor, audit malformed Telegram DM `allowFrom` entries even when groups are disabled, and keep allowlist DM audits from counting stale pairing-store senders, so public/shared-DM risk checks stay accurate. Refs #73698. Thanks @xace1825. - Plugins: remove hidden manifest, provider-owner, bootstrap, and channel metadata caches so plugin installs, manifest edits, and bundled-root changes are visible on the next metadata read while keeping runtime/module loader caches for actual plugin code. Thanks @shakkernerd. -- Control UI/WebChat: create a fresh dashboard session from the New Chat button instead of resetting the current transcript with `/new`, while keeping explicit `/new` reset behavior, preserving in-progress composer state when creation cannot safely switch sessions, and showing retry feedback while sessions are already refreshing. Carries forward #52042 and #52746. Thanks @bobashopcashier and @vincentkoc. +- Control UI/WebChat: create a fresh dashboard session from the New Chat button instead of resetting the current transcript with `/new`, while keeping explicit `/new` reset behavior, preserving in-progress composer edits during delayed session creation or when creation cannot safely switch sessions, and showing retry feedback while sessions are already refreshing. Carries forward #52042 and #52746. Thanks @bobashopcashier and @vincentkoc. - CLI/plugins: use plugin metadata snapshots for install slot selection and add opt-in plugin lifecycle timing traces, so plugin install avoids runtime-loading the plugin registry for metadata-only decisions. Thanks @shakkernerd. - fix(plugins): restrict bundled plugin dir resolution to trusted package roots. (#73275) Thanks @pgondhi987. - fix(security): prevent workspace PATH injection via service env and trash helpers. (#73264) Thanks @pgondhi987. diff --git a/ui/src/ui/app-render.helpers.node.test.ts b/ui/src/ui/app-render.helpers.node.test.ts index 6f456e1ac47..47627f96980 100644 --- a/ui/src/ui/app-render.helpers.node.test.ts +++ b/ui/src/ui/app-render.helpers.node.test.ts @@ -589,6 +589,29 @@ describe("createChatSession", () => { expect(loadChatHistoryMock).toHaveBeenCalledWith(state); }); + it("preserves draft and attachment edits made while session creation is in flight", async () => { + const state = createChatSessionState(); + const updatedAttachments = [ + { id: "att-2", mimeType: "image/png", dataUrl: "data:image/png;base64,BBB" }, + ]; + createSessionAndRefreshMock.mockImplementation(async () => { + state.chatMessage = "updated draft"; + state.chatAttachments = updatedAttachments; + return "agent:ops:dashboard:new-chat"; + }); + refreshChatAvatarMock.mockResolvedValue(undefined); + refreshSlashCommandsMock.mockResolvedValue(undefined); + loadChatHistoryMock.mockResolvedValue(undefined); + loadSessionsMock.mockResolvedValue(undefined); + + await createChatSession(state); + + expect(state.sessionKey).toBe("agent:ops:dashboard:new-chat"); + expect(state.chatMessage).toBe("updated draft"); + expect(state.chatAttachments).toBe(updatedAttachments); + expect(loadChatHistoryMock).toHaveBeenCalledWith(state); + }); + it("ignores a stale create response after the active session changes", async () => { const state = createChatSessionState(); createSessionAndRefreshMock.mockImplementation(async () => { diff --git a/ui/src/ui/app-render.helpers.ts b/ui/src/ui/app-render.helpers.ts index 5e410dda68b..3bf262892bf 100644 --- a/ui/src/ui/app-render.helpers.ts +++ b/ui/src/ui/app-render.helpers.ts @@ -617,8 +617,6 @@ export async function createChatSession(state: AppViewState) { state.lastError = null; const previousSessionKey = state.sessionKey; - const preservedDraft = state.chatMessage; - const preservedAttachments = state.chatAttachments; const parentSessionKey = state.sessionsResult?.sessions.some( (row) => row.key === previousSessionKey, ) @@ -648,6 +646,8 @@ export async function createChatSession(state: AppViewState) { return; } + const preservedDraft = state.chatMessage; + const preservedAttachments = state.chatAttachments; switchChatSession(state, nextSessionKey); state.chatMessage = preservedDraft; state.chatAttachments = preservedAttachments;