diff --git a/CHANGELOG.md b/CHANGELOG.md index 6999498501d..4bc264865e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Docs: https://docs.openclaw.ai - Gateway/install: carry env-backed config SecretRefs such as `channels.discord.token` into generated service environments when they are present only in the installing shell, while keeping gateway auth SecretRefs non-persisted. Fixes #67817; supersedes #73426. Thanks @wdimaculangan and @ztexydt-cqh. - Auto-reply/commands: stop bare `/reset` and `/new` after reset hooks acknowledge the command, so non-ACP channels no longer fall through into empty provider calls while `/reset ` and `/new ` still seed the next model turn. Fixes #73367 and #73412. Thanks @hoyanhan, @wenxu007, and @amdhelper. - Providers/DeepSeek: backfill DeepSeek V4 `reasoning_content` on plain assistant replay messages as well as tool-call turns, so thinking sessions with prior tool use no longer fail follow-up requests with missing reasoning content. Fixes #73417; refs #71372. Thanks @34262315716 and @Bartok9. +- Agents/gateway tool: strip full config payloads from `config.patch` and `config.apply` tool responses while preserving direct RPC responses, so config-heavy sessions no longer replay large redacted configs into transcript history. Fixes #47610; supersedes #73439. Thanks @HanenVit and @juan-flores077. - Auto-reply: preserve voice-note media from silent turns while continuing to suppress text and non-voice media, so `NO_REPLY` TTS replies still deliver the requested audio bubble. (#73406) Thanks @zqchris. - Channels/Mattermost: stop enqueueing regular inbound posts as system events, so Mattermost user messages reach the model only as user-role inbound-envelope content instead of also appearing as `System: Mattermost message...` directives. Fixes #71795. Thanks @juan-flores077. - Agents/media: qualify bare `agents.defaults.imageModel` and `pdfModel` refs from unique configured image-capable providers, so Ollama vision models such as `moondream` and `qwen2.5vl:7b` do not fall through to the default provider. Fixes #38816; supersedes #73396. Thanks @alainasclaw and @vincentkoc. diff --git a/src/agents/openclaw-gateway-tool.test.ts b/src/agents/openclaw-gateway-tool.test.ts index 9daffdeb3cc..1819ace83a1 100644 --- a/src/agents/openclaw-gateway-tool.test.ts +++ b/src/agents/openclaw-gateway-tool.test.ts @@ -187,16 +187,48 @@ describe("gateway tool", () => { }); it("passes config.apply through gateway call", async () => { + vi.mocked(callGatewayTool).mockImplementation(async (method: string) => { + if (method === "config.get") { + return { + hash: "hash-1", + config: { + tools: { + exec: { + ask: "on-miss", + security: "allowlist", + }, + }, + }, + }; + } + if (method === "config.apply") { + return { + ok: true, + path: "/tmp/openclaw.json", + config: { agents: { defaults: { systemPromptOverride: "You are a terse assistant." } } }, + restart: { ok: true, config: "nested field preserved" }, + }; + } + return { ok: true }; + }); const sessionKey = "agent:main:whatsapp:dm:+15555550123"; const tool = requireGatewayTool(sessionKey); const raw = '{\n agents: { defaults: { systemPromptOverride: "You are a terse assistant." } },\n tools: { exec: { ask: "on-miss", security: "allowlist" } }\n}\n'; - await tool.execute("call2", { + const result = await tool.execute("call2", { action: "config.apply", raw, }); + expect(result.details).toEqual({ + ok: true, + result: { + ok: true, + path: "/tmp/openclaw.json", + restart: { ok: true, config: "nested field preserved" }, + }, + }); expectConfigMutationCall({ callGatewayTool: vi.mocked(callGatewayTool), action: "config.apply", @@ -206,15 +238,47 @@ describe("gateway tool", () => { }); it("passes config.patch through gateway call", async () => { + vi.mocked(callGatewayTool).mockImplementation(async (method: string) => { + if (method === "config.get") { + return { + hash: "hash-1", + config: { + tools: { + exec: { + ask: "on-miss", + security: "allowlist", + }, + }, + }, + }; + } + if (method === "config.patch") { + return { + ok: true, + noop: true, + path: "/tmp/openclaw.json", + config: { channels: { telegram: { groups: {} } } }, + }; + } + return { ok: true }; + }); const sessionKey = "agent:main:whatsapp:dm:+15555550123"; const tool = requireGatewayTool(sessionKey); const raw = '{\n channels: { telegram: { groups: { "*": { requireMention: false } } } }\n}\n'; - await tool.execute("call4", { + const result = await tool.execute("call4", { action: "config.patch", raw, }); + expect(result.details).toEqual({ + ok: true, + result: { + ok: true, + noop: true, + path: "/tmp/openclaw.json", + }, + }); expectConfigMutationCall({ callGatewayTool: vi.mocked(callGatewayTool), action: "config.patch", diff --git a/src/agents/tools/gateway-tool.ts b/src/agents/tools/gateway-tool.ts index f029e485f77..5b57bcbdedb 100644 --- a/src/agents/tools/gateway-tool.ts +++ b/src/agents/tools/gateway-tool.ts @@ -89,6 +89,17 @@ function getSnapshotConfig(snapshot: unknown): Record { return config as Record; } +// Direct RPC callers need the validated config echoed after writes; the +// agent-facing gateway tool does not, and replaying it bloats transcripts. +function stripConfigWriteResultPayload(result: unknown): unknown { + if (!isPlainObject(result) || !Object.hasOwn(result, "config")) { + return result; + } + const stripped = { ...result }; + delete stripped.config; + return stripped; +} + function parseGatewayConfigMutationRaw( raw: string, action: "config.apply" | "config.patch", @@ -481,7 +492,7 @@ export function createGatewayTool(opts?: { note, restartDelayMs, }); - return jsonResult({ ok: true, result }); + return jsonResult({ ok: true, result: stripConfigWriteResultPayload(result) }); } if (action === "config.patch") { const { raw, baseHash, snapshotConfig, sessionKey, note, restartDelayMs } = @@ -498,7 +509,7 @@ export function createGatewayTool(opts?: { note, restartDelayMs, }); - return jsonResult({ ok: true, result }); + return jsonResult({ ok: true, result: stripConfigWriteResultPayload(result) }); } if (action === "update.run") { const { sessionKey, note, restartDelayMs } = resolveGatewayWriteMeta();