From 14bbcad1695de811e37faea9ed445a6a5684265f Mon Sep 17 00:00:00 2001 From: Pejman Pour-Moezzi Date: Mon, 9 Mar 2026 09:50:38 -0700 Subject: [PATCH] fix(acp): propagate setSessionMode gateway errors to client (#41185) * fix(acp): propagate setSessionMode gateway errors to client * fix: add changelog entry for ACP setSessionMode propagation (#41185) (thanks @pejmanjohn) --------- Co-authored-by: Pejman Pour-Moezzi <481729+pejmanjohn@users.noreply.github.com> Co-authored-by: Onur --- CHANGELOG.md | 1 + src/acp/translator.set-session-mode.test.ts | 61 +++++++++++++++++++++ src/acp/translator.ts | 1 + 3 files changed, 63 insertions(+) create mode 100644 src/acp/translator.set-session-mode.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 375d906bd34..1e51ea3a0a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai - Agents/embedded runner: bound compaction retry waiting and drain embedded runs during SIGUSR1 restart so session lanes recover instead of staying blocked behind compaction. (#40324) thanks @cgdusek. - ACP/sessions.patch: allow `spawnedBy` and `spawnDepth` lineage fields on ACP session keys so `sessions_spawn` with `runtime: "acp"` no longer fails during child-session setup. Fixes #40971. (#40995) thanks @xaeon2026. - ACP/stop reason mapping: resolve gateway chat `state: "error"` completions as ACP `end_turn` instead of `refusal` so transient backend failures are not surfaced as deliberate refusals. (#41187) thanks @pejmanjohn. +- ACP/setSessionMode: propagate gateway `sessions.patch` failures back to ACP clients so rejected mode changes no longer return silent success. (#41185) thanks @pejmanjohn. ## 2026.3.8 diff --git a/src/acp/translator.set-session-mode.test.ts b/src/acp/translator.set-session-mode.test.ts new file mode 100644 index 00000000000..53e8db0e5e5 --- /dev/null +++ b/src/acp/translator.set-session-mode.test.ts @@ -0,0 +1,61 @@ +import type { SetSessionModeRequest } from "@agentclientprotocol/sdk"; +import { describe, expect, it, vi } from "vitest"; +import type { GatewayClient } from "../gateway/client.js"; +import { createInMemorySessionStore } from "./session.js"; +import { AcpGatewayAgent } from "./translator.js"; +import { createAcpConnection, createAcpGateway } from "./translator.test-helpers.js"; + +function createSetSessionModeRequest(modeId: string): SetSessionModeRequest { + return { + sessionId: "session-1", + modeId, + } as unknown as SetSessionModeRequest; +} + +function createAgentWithSession(request: GatewayClient["request"]) { + const sessionStore = createInMemorySessionStore(); + sessionStore.createSession({ + sessionId: "session-1", + sessionKey: "agent:main:main", + cwd: "/tmp", + }); + return new AcpGatewayAgent(createAcpConnection(), createAcpGateway(request), { + sessionStore, + }); +} + +describe("acp setSessionMode", () => { + it("setSessionMode propagates gateway error", async () => { + const request = vi.fn(async () => { + throw new Error("gateway rejected mode change"); + }) as GatewayClient["request"]; + const agent = createAgentWithSession(request); + + await expect(agent.setSessionMode(createSetSessionModeRequest("high"))).rejects.toThrow( + "gateway rejected mode change", + ); + expect(request).toHaveBeenCalledWith("sessions.patch", { + key: "agent:main:main", + thinkingLevel: "high", + }); + }); + + it("setSessionMode succeeds when gateway accepts", async () => { + const request = vi.fn(async () => ({ ok: true })) as GatewayClient["request"]; + const agent = createAgentWithSession(request); + + await expect(agent.setSessionMode(createSetSessionModeRequest("low"))).resolves.toEqual({}); + expect(request).toHaveBeenCalledWith("sessions.patch", { + key: "agent:main:main", + thinkingLevel: "low", + }); + }); + + it("setSessionMode returns early for empty modeId", async () => { + const request = vi.fn(async () => ({ ok: true })) as GatewayClient["request"]; + const agent = createAgentWithSession(request); + + await expect(agent.setSessionMode(createSetSessionModeRequest(""))).resolves.toEqual({}); + expect(request).not.toHaveBeenCalled(); + }); +}); diff --git a/src/acp/translator.ts b/src/acp/translator.ts index 7cf556f3e75..d399228afa6 100644 --- a/src/acp/translator.ts +++ b/src/acp/translator.ts @@ -256,6 +256,7 @@ export class AcpGatewayAgent implements Agent { this.log(`setSessionMode: ${session.sessionId} -> ${params.modeId}`); } catch (err) { this.log(`setSessionMode error: ${String(err)}`); + throw err; } return {}; }