From 6565ec2e5372dcbe2ac45c291ea05f22e310fed6 Mon Sep 17 00:00:00 2001 From: Rodrigo Uroz Date: Sun, 15 Feb 2026 12:31:11 -0300 Subject: [PATCH] gateway: return actionable error for send channel webchat (openclaw#15703) thanks @rodrigouroz Verified: - pnpm build - pnpm check (fails on current main with unrelated type errors in src/memory/embedding-manager.test-harness.ts) - pnpm test:macmini (not run after pnpm check failure) - pnpm test -- src/gateway/server-methods/send.test.ts Co-authored-by: rodrigouroz <165576107+rodrigouroz@users.noreply.github.com> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> --- CHANGELOG.md | 1 + src/gateway/server-methods/send.test.ts | 35 ++++++++++++++++++++++++- src/gateway/server-methods/send.ts | 12 +++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3bf751cfbd..efdffbe5594 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai - TUI: suppress false `(no output)` placeholders for non-local empty final events during concurrent runs, preventing external-channel replies from showing empty assistant bubbles while a local run is still streaming. (#5782) Thanks @LagWizard and @vignesh07. - Auto-reply/WhatsApp/TUI/Web: when a final assistant message is `NO_REPLY` and a messaging tool send succeeded, mirror the delivered messaging-tool text into session-visible assistant output so TUI/Web no longer show `NO_REPLY` placeholders. (#7010) Thanks @Morrowind-Xie. - Gateway/Chat: harden `chat.send` inbound message handling by rejecting null bytes, stripping unsafe control characters, and normalizing Unicode to NFC before dispatch. (#8593) Thanks @fr33d3m0n. +- Gateway/Send: return an actionable error when `send` targets internal-only `webchat`, guiding callers to use `chat.send` or a deliverable channel. (#15703) Thanks @rodrigouroz. - Gateway/Security: redact sensitive session/path details from `status` responses for non-admin clients; full details remain available to `operator.admin`. (#8590) Thanks @fr33d3m0n. - Agents: return an explicit timeout error reply when an embedded run times out before producing any payloads, preventing silent dropped turns during slow cache-refresh transitions. (#16659) Thanks @liaosvcaf and @vignesh07. - Agents/OpenAI: force `store=true` for direct OpenAI Responses/Codex runs to preserve multi-turn server-side conversation state, while leaving proxy/non-OpenAI endpoints unchanged. (#16803) Thanks @mark9232 and @vignesh07. diff --git a/src/gateway/server-methods/send.test.ts b/src/gateway/server-methods/send.test.ts index 96743976bf2..96b3f940797 100644 --- a/src/gateway/server-methods/send.test.ts +++ b/src/gateway/server-methods/send.test.ts @@ -19,7 +19,7 @@ vi.mock("../../config/config.js", async () => { vi.mock("../../channels/plugins/index.js", () => ({ getChannelPlugin: () => ({ outbound: {} }), - normalizeChannelId: (value: string) => value, + normalizeChannelId: (value: string) => (value === "webchat" ? null : value), })); vi.mock("../../infra/outbound/targets.js", () => ({ @@ -108,6 +108,39 @@ describe("gateway send mirroring", () => { ); }); + it("returns actionable guidance when channel is internal webchat", async () => { + const respond = vi.fn(); + await sendHandlers.send({ + params: { + to: "x", + message: "hi", + channel: "webchat", + idempotencyKey: "idem-webchat", + }, + respond, + context: makeContext(), + req: { type: "req", id: "1", method: "send" }, + client: null, + isWebchatConnect: () => false, + }); + + expect(mocks.deliverOutboundPayloads).not.toHaveBeenCalled(); + expect(respond).toHaveBeenCalledWith( + false, + undefined, + expect.objectContaining({ + message: expect.stringContaining("unsupported channel: webchat"), + }), + ); + expect(respond).toHaveBeenCalledWith( + false, + undefined, + expect.objectContaining({ + message: expect.stringContaining("Use `chat.send`"), + }), + ); + }); + it("does not mirror when delivery returns no results", async () => { mocks.deliverOutboundPayloads.mockResolvedValue([]); diff --git a/src/gateway/server-methods/send.ts b/src/gateway/server-methods/send.ts index 268fd90892f..0abccf60385 100644 --- a/src/gateway/server-methods/send.ts +++ b/src/gateway/server-methods/send.ts @@ -106,6 +106,18 @@ export const sendHandlers: GatewayRequestHandlers = { const channelInput = typeof request.channel === "string" ? request.channel : undefined; const normalizedChannel = channelInput ? normalizeChannelId(channelInput) : null; if (channelInput && !normalizedChannel) { + const normalizedInput = channelInput.trim().toLowerCase(); + if (normalizedInput === "webchat") { + respond( + false, + undefined, + errorShape( + ErrorCodes.INVALID_REQUEST, + "unsupported channel: webchat (internal-only). Use `chat.send` for WebChat UI messages or choose a deliverable channel.", + ), + ); + return; + } respond( false, undefined,