From d3e4640bedf5988372fee0823c847a489d47ca0a Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 28 Apr 2026 00:12:27 +0100 Subject: [PATCH] fix(acpx): ignore Codex ACP timeout config --- CHANGELOG.md | 1 + extensions/acpx/src/runtime.test.ts | 62 +++++++++++++++++++++++++++++ extensions/acpx/src/runtime.ts | 57 ++++++++++++++------------ 3 files changed, 94 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa1a155963d..2fe7cfae27d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Agents/ACPX: stop forwarding Codex ACP timeout config controls that Codex rejects while preserving OpenClaw's run-timeout watchdog for ACP subagents. Fixes #73052. Thanks @pfrederiksen and @richa65. - Docs/tools: clarify that `tools.profile: "messaging"` is intentionally narrow and that `tools.profile: "full"` is the unrestricted baseline for broader command/control access. Carries forward #39954. Thanks @posigit. - Control UI/Agents: redact tool-call args, partial/final results, derived exec output, and configured custom secret patterns before streaming tool events to the Control UI, so tool output cannot expose provider or channel credentials. Fixes #72283. (#72319) Thanks @volcano303 and @BunsDev. - Agents/sessions: keep `sessions_history` recall redaction enabled even when general log redaction is disabled, and clarify that safety-boundary UI/tool/diagnostic payloads still redact independently of `logging.redactSensitive`. Carries forward #72319. Thanks @volcano303 and @BunsDev. diff --git a/extensions/acpx/src/runtime.test.ts b/extensions/acpx/src/runtime.test.ts index ab0048295a0..efc540f232c 100644 --- a/extensions/acpx/src/runtime.test.ts +++ b/extensions/acpx/src/runtime.test.ts @@ -353,6 +353,68 @@ describe("AcpxRuntime fresh reset wrapper", () => { }); }); + it("ignores unsupported Codex ACP timeout config controls", async () => { + const baseStore: TestSessionStore = { + load: vi.fn(async () => ({ + acpxRecordId: "agent:codex:acp:test", + agentCommand: CODEX_ACP_COMMAND, + })), + save: vi.fn(async () => {}), + }; + const { runtime, delegate } = makeRuntime(baseStore); + const setConfigOption = vi.spyOn(delegate, "setConfigOption").mockResolvedValue(undefined); + const handle: Parameters>[0]["handle"] = { + sessionKey: "agent:codex:acp:test", + backend: "acpx", + runtimeSessionName: "agent:codex:acp:test", + acpxRecordId: "agent:codex:acp:test", + }; + + await runtime.setConfigOption({ + handle, + key: "timeout", + value: "60000", + }); + await runtime.setConfigOption({ + handle, + key: "Timeout_Seconds", + value: "60", + }); + + expect(setConfigOption).not.toHaveBeenCalled(); + }); + + it("forwards timeout config controls for non-Codex ACP agents", async () => { + const baseStore: TestSessionStore = { + load: vi.fn(async () => ({ + acpxRecordId: "agent:claude:acp:test", + agentCommand: "npx @agentclientprotocol/claude-agent-acp", + })), + save: vi.fn(async () => {}), + }; + const { runtime, delegate } = makeRuntime(baseStore); + const setConfigOption = vi.spyOn(delegate, "setConfigOption").mockResolvedValue(undefined); + const handle: Parameters>[0]["handle"] = { + sessionKey: "agent:claude:acp:test", + backend: "acpx", + runtimeSessionName: "agent:claude:acp:test", + acpxRecordId: "agent:claude:acp:test", + }; + + await runtime.setConfigOption({ + handle, + key: "timeout", + value: "60", + }); + + expect(setConfigOption).toHaveBeenCalledOnce(); + expect(setConfigOption).toHaveBeenCalledWith({ + handle, + key: "timeout", + value: "60", + }); + }); + it("keeps stale persistent loads hidden until a fresh record is saved", async () => { const baseStore: TestSessionStore = { load: vi.fn(async () => ({ acpxRecordId: "stale" }) as never), diff --git a/extensions/acpx/src/runtime.ts b/extensions/acpx/src/runtime.ts index 9acdf7fe201..4bc2dcc0287 100644 --- a/extensions/acpx/src/runtime.ts +++ b/extensions/acpx/src/runtime.ts @@ -510,36 +510,41 @@ export class AcpxRuntime implements AcpRuntime { ): Promise { const delegate = await this.resolveDelegateForHandle(input.handle); const command = await this.resolveCommandForHandle(input.handle); - if ( - (input.key === "model" || - input.key === "thinking" || - input.key === "thought_level" || - input.key === "reasoning_effort") && - isCodexAcpCommand(command) - ) { - const override = - input.key === "model" - ? normalizeCodexAcpModelOverride(input.value) - : normalizeCodexAcpModelOverride(undefined, input.value); - if (!override && input.key !== "model") { + const key = input.key.trim().toLowerCase(); + if (isCodexAcpCommand(command)) { + if (key === "timeout" || key === "timeout_seconds") { return; } - if (override) { - if (override.model) { - await delegate.setConfigOption({ - ...input, - key: "model", - value: override.model, - }); + if ( + key === "model" || + key === "thinking" || + key === "thought_level" || + key === "reasoning_effort" + ) { + const override = + key === "model" + ? normalizeCodexAcpModelOverride(input.value) + : normalizeCodexAcpModelOverride(undefined, input.value); + if (!override && key !== "model") { + return; } - if (override.reasoningEffort) { - await delegate.setConfigOption({ - ...input, - key: "reasoning_effort", - value: override.reasoningEffort, - }); + if (override) { + if (override.model) { + await delegate.setConfigOption({ + ...input, + key: "model", + value: override.model, + }); + } + if (override.reasoningEffort) { + await delegate.setConfigOption({ + ...input, + key: "reasoning_effort", + value: override.reasoningEffort, + }); + } + return; } - return; } } await delegate.setConfigOption(input);