diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b65f36efcc..501905b424e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- fix: harden backend message action gateway routing [AI]. (#76374) Thanks @pgondhi987. - Gate QQBot streaming command auth [AI]. (#76375) Thanks @pgondhi987. - Plugins/release: make the published npm runtime verifier reject blank `openclaw.runtimeExtensions` entries instead of treating them as absent and passing via inferred outputs. Thanks @vincentkoc. - Web fetch: scope provider fallback cache entries by the selected fetch provider so config reloads cannot reuse another provider's cached fallback payload. Thanks @vincentkoc. diff --git a/src/infra/outbound/message-action-runner.plugin-dispatch.test.ts b/src/infra/outbound/message-action-runner.plugin-dispatch.test.ts index edf83823d21..3740a6e2355 100644 --- a/src/infra/outbound/message-action-runner.plugin-dispatch.test.ts +++ b/src/infra/outbound/message-action-runner.plugin-dispatch.test.ts @@ -10,6 +10,7 @@ import type { import type { OpenClawConfig } from "../../config/config.js"; import { getActivePluginRegistry, setActivePluginRegistry } from "../../plugins/runtime.js"; import { createTestRegistry } from "../../test-utils/channel-plugins.js"; +import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../../utils/message-channel.js"; import { runMessageAction } from "./message-action-runner.js"; import { extractToolPayload } from "./tool-payload.js"; @@ -439,6 +440,66 @@ describe("runMessageAction plugin dispatch", () => { }); }); + it("ignores gateway url overrides for backend plugin actions", async () => { + const gatewayPlugin = createGatewayActionPlugin({ + pluginId: "gatewaychat", + label: "Gateway Chat", + blurb: "Gateway Chat backend action test plugin.", + actions: ["react"], + capabilities: { chatTypes: ["direct"], reactions: true }, + handleAction: vi.fn(async () => jsonResult({ ok: true, local: true })), + }); + setActivePluginRegistry( + createTestRegistry([ + { + pluginId: "gatewaychat", + source: "test", + plugin: gatewayPlugin, + }, + ]), + ); + mocks.callGatewayLeastPrivilege.mockResolvedValue({ + ok: true, + added: "ok", + }); + + await runMessageAction({ + cfg: { + channels: { + gatewaychat: { + enabled: true, + }, + }, + } as OpenClawConfig, + action: "react", + params: { + channel: "gatewaychat", + to: "+15551234567", + chatJid: "+15551234567", + messageId: "wamid.1", + emoji: "ok", + }, + gateway: { + url: "ws://127.0.0.1:18789", + token: "configured-token", + timeoutMs: 5000, + clientName: GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT, + mode: GATEWAY_CLIENT_MODES.BACKEND, + }, + dryRun: false, + }); + + expect(mocks.callGatewayLeastPrivilege).toHaveBeenCalledWith( + expect.objectContaining({ + url: undefined, + token: "configured-token", + timeoutMs: 5000, + clientName: GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT, + mode: GATEWAY_CLIENT_MODES.BACKEND, + }), + ); + }); + it("routes gateway-executed plugin sends through gateway RPC instead of local dispatch", async () => { const handleAction = vi.fn(async () => jsonResult({ ok: true, local: true })); const gatewayPlugin = createGatewayActionPlugin({ diff --git a/src/infra/outbound/message-action-runner.ts b/src/infra/outbound/message-action-runner.ts index 144d5d09dff..69f3c66498c 100644 --- a/src/infra/outbound/message-action-runner.ts +++ b/src/infra/outbound/message-action-runner.ts @@ -172,8 +172,13 @@ export function getToolResult( } function resolveGatewayActionOptions(gateway?: MessageActionRunnerGateway) { + const url = + gateway?.mode === GATEWAY_CLIENT_MODES.BACKEND || + gateway?.clientName === GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT + ? undefined + : gateway?.url; return { - url: gateway?.url, + url, token: gateway?.token, timeoutMs: typeof gateway?.timeoutMs === "number" && Number.isFinite(gateway.timeoutMs)