diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a18f2baa55..ba5eedf459e 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 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. +- 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 clear retry feedback when creation is blocked, refreshing, or returns no new session. 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 47627f96980..3771efb71d9 100644 --- a/ui/src/ui/app-render.helpers.node.test.ts +++ b/ui/src/ui/app-render.helpers.node.test.ts @@ -660,12 +660,29 @@ describe("createChatSession", () => { ); }); - it("shows retry feedback when creation is skipped without a session error", async () => { + it("shows creation failure feedback when creation is skipped without a session error", async () => { const state = createChatSessionState({ lastError: "previous error" }); createSessionAndRefreshMock.mockResolvedValue(null); await createChatSession(state); + expect(createSessionAndRefreshMock).toHaveBeenCalledTimes(1); + expect(state.sessionKey).toBe("agent:ops:main"); + expect(state.chatMessage).toBe("draft prompt"); + expect(state.sessionsError).toBeNull(); + expect(state.lastError).toBe("New Chat could not create a new session. Try again in a moment."); + expect(loadChatHistoryMock).not.toHaveBeenCalled(); + }); + + it("keeps refresh feedback when a queued session refresh skips creation", async () => { + const state = createChatSessionState({ lastError: "previous error" }); + createSessionAndRefreshMock.mockImplementation(async () => { + state.sessionsLoading = true; + return null; + }); + + await createChatSession(state); + expect(createSessionAndRefreshMock).toHaveBeenCalledTimes(1); expect(state.sessionKey).toBe("agent:ops:main"); expect(state.chatMessage).toBe("draft prompt"); diff --git a/ui/src/ui/app-render.helpers.ts b/ui/src/ui/app-render.helpers.ts index 3bf262892bf..f40824a2678 100644 --- a/ui/src/ui/app-render.helpers.ts +++ b/ui/src/ui/app-render.helpers.ts @@ -132,6 +132,8 @@ const NEW_CHAT_ACTIVE_RUN_MESSAGE = "Start a new session after the active run or queued messages finish."; const NEW_CHAT_SESSIONS_LOADING_MESSAGE = "Session list is still refreshing. Try New Chat again in a moment."; +const NEW_CHAT_CREATE_FAILED_MESSAGE = + "New Chat could not create a new session. Try again in a moment."; export function renderTab(state: AppViewState, tab: Tab, opts?: { collapsed?: boolean }) { const href = pathForTab(tab, state.basePath); @@ -641,7 +643,11 @@ export async function createChatSession(state: AppViewState) { !canSwitchToNewChatSession(state) ) { if (!nextSessionKey) { - state.lastError = state.sessionsError ?? NEW_CHAT_SESSIONS_LOADING_MESSAGE; + state.lastError = + state.sessionsError ?? + (state.sessionsLoading + ? NEW_CHAT_SESSIONS_LOADING_MESSAGE + : NEW_CHAT_CREATE_FAILED_MESSAGE); } return; }