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 <onur@textcortex.com>
This commit is contained in:
Pejman Pour-Moezzi
2026-03-09 09:50:38 -07:00
committed by Vincent Koc
parent ae824ab269
commit 162232ae2f
3 changed files with 63 additions and 0 deletions

View File

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

View File

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

View File

@@ -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 {};
}