From 4cba08df01ea6691d68046fb3f3297d152ae0219 Mon Sep 17 00:00:00 2001 From: Marcus Castro <7562095+mcaxtr@users.noreply.github.com> Date: Wed, 29 Apr 2026 20:03:58 -0300 Subject: [PATCH] fix(whatsapp): remove exposeErrorText config (#74642) * fix(whatsapp): remove exposeErrorText config * fix(whatsapp): mark internal system events trusted --- CHANGELOG.md | 2 +- docs/.generated/config-baseline.sha256 | 4 +-- docs/channels/whatsapp.md | 18 +--------- extensions/whatsapp/src/accounts.ts | 2 -- extensions/whatsapp/src/auto-reply/monitor.ts | 3 +- .../monitor/inbound-dispatch.test.ts | 36 ++----------------- .../auto-reply/monitor/inbound-dispatch.ts | 11 ++---- extensions/whatsapp/src/config-schema.test.ts | 14 -------- extensions/whatsapp/src/config-ui-hints.ts | 4 --- .../src/outbound-adapter.sendpayload.test.ts | 33 ++--------------- extensions/whatsapp/src/outbound-base.ts | 7 +--- ...ndled-channel-config-metadata.generated.ts | 10 ------ src/config/types.whatsapp.ts | 2 -- .../zod-schema.providers-whatsapp.test.ts | 15 -------- src/config/zod-schema.providers-whatsapp.ts | 1 - 15 files changed, 13 insertions(+), 149 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c41bf96e819..8824c3bd705 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -230,7 +230,7 @@ Docs: https://docs.openclaw.ai - Channels/WhatsApp: detect explicit group `@mentions` again when the bot's own E.164 is in `allowFrom`, so shared-number setups no longer skip group pings that directly mention the bot. Fixes #49317. (#73453) Thanks @juan-flores077. - WhatsApp/reliability: publish real transport-liveness into WhatsApp channel status and force earlier reconnects on silent transport stalls, so quiet healthy sessions stay connected while wedged sockets recover before the later remote 408 path. (#72656) Thanks @Sathvik-1007. - Core/channels: tighten selected runtime, media, and plugin edge-case handling while preserving existing behavior. Thanks @jesse-merhi. -- Channels/WhatsApp: strip leaked plural tool-call XML wrappers on every WhatsApp-visible outbound path and allow `channels.whatsapp.exposeErrorText` to suppress visible error text per channel or account. (#71830) Thanks @rubencu. +- Channels/WhatsApp: strip leaked plural tool-call XML wrappers on every WhatsApp-visible outbound path and keep channel error payloads out of WhatsApp chats. (#71830) Thanks @rubencu. - Agents/embedded-runner: inject the resolved OAuth bearer (and forward the run abort signal) on the boundary-aware embedded stream fallback so models that route through `openai-codex-responses` and other boundary-aware transports stop failing with `401 Unauthorized: Missing bearer or basic authentication in header`. Fixes #73559. (#73588) Thanks @openperf. - Telegram/gateway: bound outbound Bot API calls and cache bundled plugin alias lookup so slow Telegram sends or WSL2 filesystem scans no longer wedge gateway replies. (#74210) Thanks @obviyus. - Configure/GitHub Copilot: reuse existing Copilot auth during configure and show the provider's manifest model catalog in the model picker. (#74276) Thanks @obviyus. diff --git a/docs/.generated/config-baseline.sha256 b/docs/.generated/config-baseline.sha256 index 7c9ecdbd608..21e0608e96d 100644 --- a/docs/.generated/config-baseline.sha256 +++ b/docs/.generated/config-baseline.sha256 @@ -1,4 +1,4 @@ -bfd8b3ddcac047e486e9c43fdedc002cb9bf87b659f6563f9f11c850c5b2aaef config-baseline.json +2af6bef21f530dc64e0379f7631bed410aee1d5c86604ef9fb149f546cfcb0e8 config-baseline.json 8d75df355b7f6e44b9c2f195d9df86130beb697e26061469df7d60b7e8a2f204 config-baseline.core.json -9f5fad66a49fa618d64a963470aa69fed9fe4b4639cc4321f9ec04bfb2f8aa50 config-baseline.channel.json +fab66aa304db5697e87259165ad261006719eb6e6cdbd25f957fcba2b7b324e9 config-baseline.channel.json c4231c2194206547af8ad94342dc00aadb734f43cb49cc79d4c46bdbb80c3f95 config-baseline.plugin.json diff --git a/docs/channels/whatsapp.md b/docs/channels/whatsapp.md index 18289cfa88d..32e31d5a6bc 100644 --- a/docs/channels/whatsapp.md +++ b/docs/channels/whatsapp.md @@ -398,22 +398,6 @@ When the linked self number is also present in `allowFrom`, WhatsApp self-chat s -## Error visibility - -`channels.whatsapp.exposeErrorText` controls whether agent/provider error text is delivered back into WhatsApp. The default is `true`. Set it to `false` to keep failures quiet on WhatsApp while preserving other channel behavior. - -```json5 -{ - channels: { - whatsapp: { - exposeErrorText: false, - }, - }, -} -``` - -Per-account overrides use `channels.whatsapp.accounts..exposeErrorText`. - ## Reply quoting WhatsApp supports native reply quoting, where outbound replies visibly quote the inbound message. Control it with `channels.whatsapp.replyToMode`. @@ -681,7 +665,7 @@ Primary reference: High-signal WhatsApp fields: - access: `dmPolicy`, `allowFrom`, `groupPolicy`, `groupAllowFrom`, `groups` -- delivery: `textChunkLimit`, `chunkMode`, `mediaMaxMb`, `sendReadReceipts`, `ackReaction`, `reactionLevel`, `exposeErrorText` +- delivery: `textChunkLimit`, `chunkMode`, `mediaMaxMb`, `sendReadReceipts`, `ackReaction`, `reactionLevel` - multi-account: `accounts..enabled`, `accounts..authDir`, account-level overrides - operations: `configWrites`, `debounceMs`, `web.enabled`, `web.heartbeatSeconds`, `web.reconnect.*`, `web.whatsapp.*` - session behavior: `session.dmScope`, `historyLimit`, `dmHistoryLimit`, `dms..historyLimit` diff --git a/extensions/whatsapp/src/accounts.ts b/extensions/whatsapp/src/accounts.ts index 1e10209ab17..e6c8101f725 100644 --- a/extensions/whatsapp/src/accounts.ts +++ b/extensions/whatsapp/src/accounts.ts @@ -45,7 +45,6 @@ export type ResolvedWhatsAppAccount = { direct?: WhatsAppAccountConfig["direct"]; debounceMs?: number; replyToMode?: ReplyToMode; - exposeErrorText?: boolean; }; export const DEFAULT_WHATSAPP_MEDIA_MAX_MB = 50; @@ -157,7 +156,6 @@ export function resolveWhatsAppAccount(params: { direct: merged.direct, debounceMs: merged.debounceMs, replyToMode: merged.replyToMode, - exposeErrorText: merged.exposeErrorText, }; } diff --git a/extensions/whatsapp/src/auto-reply/monitor.ts b/extensions/whatsapp/src/auto-reply/monitor.ts index 595b52ff534..f92b6e22c9f 100644 --- a/extensions/whatsapp/src/auto-reply/monitor.ts +++ b/extensions/whatsapp/src/auto-reply/monitor.ts @@ -176,7 +176,6 @@ export async function monitorWebChannel( mediaMaxMb: account.mediaMaxMb, blockStreaming: account.blockStreaming, groups: account.groups, - exposeErrorText: account.exposeErrorText, }, }, } satisfies ReturnType; @@ -444,6 +443,7 @@ export async function monitorWebChannel( }); enqueueSystemEvent(`WhatsApp gateway connected${selfE164 ? ` as ${selfE164}` : ""}.`, { sessionKey: connectRoute.sessionKey, + trusted: true, }); const normalizedAccountId = normalizeReconnectAccountId(account.accountId); @@ -503,6 +503,7 @@ export async function monitorWebChannel( `WhatsApp gateway disconnected (status ${decision.normalized.statusLabel})`, { sessionKey: connectRoute.sessionKey, + trusted: true, }, ); diff --git a/extensions/whatsapp/src/auto-reply/monitor/inbound-dispatch.test.ts b/extensions/whatsapp/src/auto-reply/monitor/inbound-dispatch.test.ts index 2b367f38ce9..31c886ff852 100644 --- a/extensions/whatsapp/src/auto-reply/monitor/inbound-dispatch.test.ts +++ b/extensions/whatsapp/src/auto-reply/monitor/inbound-dispatch.test.ts @@ -522,43 +522,11 @@ describe("whatsapp inbound dispatch", () => { expect(rememberSentText).not.toHaveBeenCalled(); }); - it("suppresses error payload text when exposeErrorText is false", async () => { + it("suppresses error payload text", async () => { const deliverReply = vi.fn(async () => undefined); const rememberSentText = vi.fn(); - await dispatchBufferedReply({ - cfg: { channels: { whatsapp: { exposeErrorText: false } } } as never, - deliverReply, - rememberSentText, - }); - - const deliver = getCapturedDeliver(); - expect(deliver).toBeTypeOf("function"); - - await deliver?.({ text: "provider exploded", isError: true }, { kind: "final" }); - - expect(deliverReply).not.toHaveBeenCalled(); - expect(rememberSentText).not.toHaveBeenCalled(); - }); - - it("honors account-level exposeErrorText overrides for error payloads", async () => { - const deliverReply = vi.fn(async () => undefined); - const rememberSentText = vi.fn(); - - await dispatchBufferedReply({ - cfg: { - channels: { - whatsapp: { - accounts: { - work: { exposeErrorText: false }, - }, - }, - }, - } as never, - deliverReply, - rememberSentText, - route: makeRoute({ accountId: "work" }), - }); + await dispatchBufferedReply({ deliverReply, rememberSentText }); const deliver = getCapturedDeliver(); expect(deliver).toBeTypeOf("function"); diff --git a/extensions/whatsapp/src/auto-reply/monitor/inbound-dispatch.ts b/extensions/whatsapp/src/auto-reply/monitor/inbound-dispatch.ts index d8ecd820c2e..6a01d0f7cd7 100644 --- a/extensions/whatsapp/src/auto-reply/monitor/inbound-dispatch.ts +++ b/extensions/whatsapp/src/auto-reply/monitor/inbound-dispatch.ts @@ -1,4 +1,3 @@ -import { resolveMergedWhatsAppAccountConfig } from "../../account-config.js"; import { type DeliverableWhatsAppOutboundPayload, normalizeWhatsAppOutboundPayload, @@ -89,12 +88,11 @@ function resolveWhatsAppDisableBlockStreaming(cfg: ReturnType): bo function resolveWhatsAppDeliverablePayload( payload: ReplyPayload, info: { kind: ReplyLifecycleKind }, - options?: { exposeErrorText?: boolean }, ): ReplyPayload | null { if (payload.isReasoning === true || payload.isCompactionNotice === true) { return null; } - if (payload.isError === true && options?.exposeErrorText === false) { + if (payload.isError === true) { return null; } if (info.kind === "tool") { @@ -314,9 +312,6 @@ export async function dispatchWhatsAppBufferedReply(params: { }); const mediaLocalRoots = getAgentScopedMediaLocalRoots(params.cfg, params.route.agentId); const disableBlockStreaming = resolveWhatsAppDisableBlockStreaming(params.cfg); - const exposeErrorText = - resolveMergedWhatsAppAccountConfig({ cfg: params.cfg, accountId: params.route.accountId }) - .exposeErrorText !== false; let didSendReply = false; let didLogHeartbeatStrip = false; @@ -333,9 +328,7 @@ export async function dispatchWhatsAppBufferedReply(params: { } }, deliver: async (payload: ReplyPayload, info: { kind: ReplyLifecycleKind }) => { - const deliveryPayload = resolveWhatsAppDeliverablePayload(payload, info, { - exposeErrorText, - }); + const deliveryPayload = resolveWhatsAppDeliverablePayload(payload, info); if (!deliveryPayload) { return; } diff --git a/extensions/whatsapp/src/config-schema.test.ts b/extensions/whatsapp/src/config-schema.test.ts index 9bedf6cd7ee..ce1e089866e 100644 --- a/extensions/whatsapp/src/config-schema.test.ts +++ b/extensions/whatsapp/src/config-schema.test.ts @@ -63,20 +63,6 @@ describe("whatsapp config schema", () => { } }); - it("accepts exposeErrorText at channel and account scope", () => { - const res = expectWhatsAppConfigValid({ - exposeErrorText: false, - accounts: { - work: { exposeErrorText: true }, - }, - }); - - if (res.success) { - expect(res.data.exposeErrorText).toBe(false); - expect(res.data.accounts?.work?.exposeErrorText).toBe(true); - } - }); - it("accepts enabled", () => { expectWhatsAppConfigValid({ enabled: true, diff --git a/extensions/whatsapp/src/config-ui-hints.ts b/extensions/whatsapp/src/config-ui-hints.ts index a891cec3ee2..c32d14a1d79 100644 --- a/extensions/whatsapp/src/config-ui-hints.ts +++ b/extensions/whatsapp/src/config-ui-hints.ts @@ -21,8 +21,4 @@ export const whatsAppChannelConfigUiHints = { label: "WhatsApp Config Writes", help: "Allow WhatsApp to write config in response to channel events/commands (default: true).", }, - exposeErrorText: { - label: "WhatsApp Error Text", - help: "Deliver user-visible agent/provider error text into WhatsApp (default: true). Disable to keep failures quiet on WhatsApp.", - }, } satisfies Record; diff --git a/extensions/whatsapp/src/outbound-adapter.sendpayload.test.ts b/extensions/whatsapp/src/outbound-adapter.sendpayload.test.ts index 5e09de52122..4403a981e5e 100644 --- a/extensions/whatsapp/src/outbound-adapter.sendpayload.test.ts +++ b/extensions/whatsapp/src/outbound-adapter.sendpayload.test.ts @@ -167,11 +167,11 @@ describe("whatsappOutbound sendPayload", () => { expect(sendWhatsApp).not.toHaveBeenCalled(); }); - it("suppresses routed error payloads when error text is hidden", async () => { + it("suppresses routed error payloads", async () => { const sendWhatsApp = vi.fn(); const result = await whatsappOutbound.sendPayload!({ - cfg: { channels: { whatsapp: { exposeErrorText: false } } }, + cfg: {}, to: "5511999999999@c.us", text: "", payload: { text: "provider exploded", isError: true }, @@ -182,35 +182,6 @@ describe("whatsappOutbound sendPayload", () => { expect(sendWhatsApp).not.toHaveBeenCalled(); }); - it("uses account-level error text visibility for routed payloads", async () => { - const sendWhatsApp = vi.fn(async () => ({ messageId: "wa-1", toJid: "jid" })); - - await whatsappOutbound.sendPayload!({ - cfg: { - channels: { - whatsapp: { - exposeErrorText: false, - accounts: { - work: { exposeErrorText: true }, - }, - }, - }, - }, - accountId: "work", - to: "5511999999999@c.us", - text: "", - payload: { text: "provider exploded", isError: true }, - deps: { sendWhatsApp }, - }); - - expect(sendWhatsApp).toHaveBeenCalledTimes(1); - expect(sendWhatsApp).toHaveBeenCalledWith( - "5511999999999@c.us", - "provider exploded", - expect.any(Object), - ); - }); - it("sanitizes HTML-only text to whitespace-only payload", () => { expect( whatsappOutbound diff --git a/extensions/whatsapp/src/outbound-base.ts b/extensions/whatsapp/src/outbound-base.ts index edeb8e43e4c..52db72b1941 100644 --- a/extensions/whatsapp/src/outbound-base.ts +++ b/extensions/whatsapp/src/outbound-base.ts @@ -11,7 +11,6 @@ import { import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types"; import { resolveOutboundSendDep } from "openclaw/plugin-sdk/outbound-send-deps"; import { sendTextMediaPayload } from "openclaw/plugin-sdk/reply-payload"; -import { resolveMergedWhatsAppAccountConfig } from "./account-config.js"; import { normalizeWhatsAppOutboundPayload, normalizeWhatsAppPayloadText, @@ -220,11 +219,7 @@ export function createWhatsAppOutboundBase({ return { ...outbound, sendPayload: async (ctx) => { - if ( - ctx.payload.isError === true && - resolveMergedWhatsAppAccountConfig({ cfg: ctx.cfg, accountId: ctx.accountId }) - .exposeErrorText === false - ) { + if (ctx.payload.isError === true) { return { channel: "whatsapp", messageId: "" }; } const payload = normalizeWhatsAppOutboundPayload(ctx.payload, { normalizeText }); diff --git a/src/config/bundled-channel-config-metadata.generated.ts b/src/config/bundled-channel-config-metadata.generated.ts index 68576795da7..73370f35aad 100644 --- a/src/config/bundled-channel-config-metadata.generated.ts +++ b/src/config/bundled-channel-config-metadata.generated.ts @@ -15962,9 +15962,6 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ }, ], }, - exposeErrorText: { - type: "boolean", - }, heartbeat: { type: "object", properties: { @@ -16253,9 +16250,6 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ }, ], }, - exposeErrorText: { - type: "boolean", - }, heartbeat: { type: "object", properties: { @@ -16344,10 +16338,6 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ label: "WhatsApp Config Writes", help: "Allow WhatsApp to write config in response to channel events/commands (default: true).", }, - exposeErrorText: { - label: "WhatsApp Error Text", - help: "Deliver user-visible agent/provider error text into WhatsApp (default: true). Disable to keep failures quiet on WhatsApp.", - }, }, unsupportedSecretRefSurfacePatterns: [ "channels.whatsapp.accounts.*.creds.json", diff --git a/src/config/types.whatsapp.ts b/src/config/types.whatsapp.ts index 8e7d7fec5aa..0d52fee9dea 100644 --- a/src/config/types.whatsapp.ts +++ b/src/config/types.whatsapp.ts @@ -105,8 +105,6 @@ type WhatsAppSharedConfig = { debounceMs?: number; /** Reply threading mode for auto-replies (off|first|all|batched). */ replyToMode?: ReplyToMode; - /** Whether WhatsApp should deliver user-visible error text. Default: true. */ - exposeErrorText?: boolean; /** Heartbeat visibility settings. */ heartbeat?: ChannelHeartbeatVisibilityConfig; /** Channel health monitor overrides for this channel/account. */ diff --git a/src/config/zod-schema.providers-whatsapp.test.ts b/src/config/zod-schema.providers-whatsapp.test.ts index 0b3d693d1b8..d45b9a93378 100644 --- a/src/config/zod-schema.providers-whatsapp.test.ts +++ b/src/config/zod-schema.providers-whatsapp.test.ts @@ -72,21 +72,6 @@ describe("WhatsApp prompt config Zod validation", () => { } }); - it("validates exposeErrorText at root and account scope", () => { - const result = WhatsAppConfigSchema.safeParse({ - exposeErrorText: false, - accounts: { - work: { exposeErrorText: true }, - }, - }); - - expect(result.success).toBe(true); - if (result.success) { - expect(result.data.exposeErrorText).toBe(false); - expect(result.data.accounts?.work?.exposeErrorText).toBe(true); - } - }); - it("validates WhatsAppAccountSchema directly", () => { const accountConfig = { name: "Personal Account", diff --git a/src/config/zod-schema.providers-whatsapp.ts b/src/config/zod-schema.providers-whatsapp.ts index a859ee0c9d8..f1b1fb0516b 100644 --- a/src/config/zod-schema.providers-whatsapp.ts +++ b/src/config/zod-schema.providers-whatsapp.ts @@ -83,7 +83,6 @@ function buildWhatsAppCommonShape(params: { useDefaults: boolean }) { ? z.number().int().nonnegative().optional().default(0) : z.number().int().nonnegative().optional(), replyToMode: ReplyToModeSchema.optional(), - exposeErrorText: z.boolean().optional(), heartbeat: ChannelHeartbeatVisibilitySchema, healthMonitor: ChannelHealthMonitorSchema, };