Gateway: allow control-ui session deletion

This commit is contained in:
Vignesh Natarajan
2026-02-28 13:00:05 -08:00
parent 62179c861b
commit 9868d5cd8b
3 changed files with 53 additions and 0 deletions

View File

@@ -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.

View File

@@ -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,

View File

@@ -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<void>((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();
});
});