From 9868d5cd8b0b77ec388764b91fa7f5826a2130cb Mon Sep 17 00:00:00 2001 From: Vignesh Natarajan Date: Sat, 28 Feb 2026 13:00:05 -0800 Subject: [PATCH] Gateway: allow control-ui session deletion --- CHANGELOG.md | 1 + src/gateway/server-methods/sessions.ts | 4 ++ ...sessions.gateway-server-sessions-a.test.ts | 48 +++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e30b633994..0a95d7fd09c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Dashboard/Sessions: allow authenticated Control UI clients to delete and patch sessions while still blocking regular webchat clients from session mutation RPCs, fixing Dashboard session delete failures. (#21264) Thanks @jskoiz. - Podman/Quadlet setup: fix `sed` escaping and UID mismatch in Podman Quadlet setup. (#26414) Thanks @KnHack and @vincentkoc. - Browser/Navigate: resolve the correct `targetId` in navigate responses after renderer swaps. (#25326) Thanks @stone-jin and @vincentkoc. - Agents/Ollama discovery: skip Ollama discovery when explicit models are configured. (#28827) Thanks @Kansodata and @vincentkoc. diff --git a/src/gateway/server-methods/sessions.ts b/src/gateway/server-methods/sessions.ts index c426a4aee27..d9ee2aea47d 100644 --- a/src/gateway/server-methods/sessions.ts +++ b/src/gateway/server-methods/sessions.ts @@ -23,6 +23,7 @@ import { normalizeAgentId, parseAgentSessionKey, } from "../../routing/session-key.js"; +import { GATEWAY_CLIENT_IDS } from "../protocol/client-info.js"; import { ErrorCodes, errorShape, @@ -86,6 +87,9 @@ function rejectWebchatSessionMutation(params: { if (!params.client?.connect || !params.isWebchatConnect(params.client.connect)) { return false; } + if (params.client.connect.client.id === GATEWAY_CLIENT_IDS.CONTROL_UI) { + return false; + } params.respond( false, undefined, diff --git a/src/gateway/server.sessions.gateway-server-sessions-a.test.ts b/src/gateway/server.sessions.gateway-server-sessions-a.test.ts index 0d8996cbf47..244b0adce1b 100644 --- a/src/gateway/server.sessions.gateway-server-sessions-a.test.ts +++ b/src/gateway/server.sessions.gateway-server-sessions-a.test.ts @@ -1234,4 +1234,52 @@ describe("gateway server sessions", () => { ws.close(); }); + + test("control-ui client can delete sessions even in webchat mode", async () => { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-control-ui-delete-")); + const storePath = path.join(dir, "sessions.json"); + testState.sessionStorePath = storePath; + + await writeSessionStore({ + entries: { + main: { + sessionId: "sess-main", + updatedAt: Date.now(), + }, + "discord:group:dev": { + sessionId: "sess-group", + updatedAt: Date.now(), + }, + }, + }); + + const ws = new WebSocket(`ws://127.0.0.1:${harness.port}`, { + headers: { origin: `http://127.0.0.1:${harness.port}` }, + }); + trackConnectChallengeNonce(ws); + await new Promise((resolve) => ws.once("open", resolve)); + await connectOk(ws, { + client: { + id: GATEWAY_CLIENT_IDS.CONTROL_UI, + version: "1.0.0", + platform: "test", + mode: GATEWAY_CLIENT_MODES.WEBCHAT, + }, + scopes: ["operator.admin"], + }); + + const deleted = await rpcReq<{ ok: true; deleted: boolean }>(ws, "sessions.delete", { + key: "agent:main:discord:group:dev", + }); + expect(deleted.ok).toBe(true); + expect(deleted.payload?.deleted).toBe(true); + + const store = JSON.parse(await fs.readFile(storePath, "utf-8")) as Record< + string, + { sessionId?: string } + >; + expect(store["agent:main:discord:group:dev"]).toBeUndefined(); + + ws.close(); + }); });