From 19bcbf85df5d0a6928a9baecfc98b4ff99f3a004 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 16 Jan 2026 10:53:32 +0000 Subject: [PATCH] fix: scope whatsapp self-chat response prefix --- CHANGELOG.md | 3 +- docs/channels/whatsapp.md | 8 ++- docs/gateway/configuration.md | 4 +- src/channels/plugins/onboarding/whatsapp.ts | 28 +---------- ...efixes-body-same-phone-marker-from.test.ts | 49 +++++++++++++++++++ src/web/auto-reply/monitor/process-message.ts | 21 ++++++-- 6 files changed, 74 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8362803c9..ef0b93563 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2026.1.15 +## 2026.1.16 (unreleased) ### Highlights - Plugins: add provider auth registry + `clawdbot models auth login` for plugin-driven OAuth/API key flows. @@ -47,6 +47,7 @@ - Discord: allow emoji/sticker uploads + channel actions in config defaults. (#870) — thanks @JDIVE. ### Fixes +- WhatsApp: default response prefix only for self-chat, using identity name when set. - Fix: list model picker entries as provider/model pairs for explicit selection. (#970) — thanks @mcinteerj. - Fix: align OpenAI image-gen defaults with DALL-E 3 standard quality and document output formats. (#880) — thanks @mkbehr. - Fix: persist `gateway.mode=local` after selecting Local run mode in `clawdbot configure`, even if no other sections are chosen. diff --git a/docs/channels/whatsapp.md b/docs/channels/whatsapp.md index 97b089981..57041fdfa 100644 --- a/docs/channels/whatsapp.md +++ b/docs/channels/whatsapp.md @@ -82,15 +82,13 @@ When the wizard asks for your personal WhatsApp number, enter the phone you will "selfChatMode": true, "dmPolicy": "allowlist", "allowFrom": ["+15551234567"] - }, - "messages": { - "responsePrefix": "[clawdbot]" } } ``` -Tip: set `messages.responsePrefix` explicitly if you want a consistent bot prefix -on outbound replies. +Self-chat replies default to `[{identity.name}]` when set (otherwise `[clawdbot]`) +if `messages.responsePrefix` is unset. Set it explicitly to customize or disable +the prefix (use `""` to remove it). ### Number sourcing tips - **Local eSIM** from your country's mobile carrier (most reliable) diff --git a/docs/gateway/configuration.md b/docs/gateway/configuration.md index feecff35c..49afda5a4 100644 --- a/docs/gateway/configuration.md +++ b/docs/gateway/configuration.md @@ -1271,7 +1271,9 @@ See [Messages](/concepts/messages) for queueing, sessions, and streaming context `responsePrefix` is applied to **all outbound replies** (tool summaries, block streaming, final replies) across channels unless already present. -If `messages.responsePrefix` is unset, no prefix is applied by default. +If `messages.responsePrefix` is unset, no prefix is applied by default. WhatsApp self-chat +replies are the exception: they default to `[{identity.name}]` when set, otherwise +`[clawdbot]`, so same-phone conversations stay legible. Set it to `"auto"` to derive `[{identity.name}]` for the routed agent (when set). #### Template variables diff --git a/src/channels/plugins/onboarding/whatsapp.ts b/src/channels/plugins/onboarding/whatsapp.ts index b45dbd623..21e78c509 100644 --- a/src/channels/plugins/onboarding/whatsapp.ts +++ b/src/channels/plugins/onboarding/whatsapp.ts @@ -27,16 +27,6 @@ function setWhatsAppAllowFrom(cfg: ClawdbotConfig, allowFrom?: string[]): Clawdb return mergeWhatsAppConfig(cfg, { allowFrom }, { unsetOnUndefined: ["allowFrom"] }); } -function setMessagesResponsePrefix(cfg: ClawdbotConfig, responsePrefix?: string): ClawdbotConfig { - return { - ...cfg, - messages: { - ...cfg.messages, - responsePrefix, - }, - }; -} - function setWhatsAppSelfChatMode(cfg: ClawdbotConfig, selfChatMode: boolean): ClawdbotConfig { return mergeWhatsAppConfig(cfg, { selfChatMode }); } @@ -65,7 +55,6 @@ async function promptWhatsAppAllowFrom( const existingPolicy = cfg.channels?.whatsapp?.dmPolicy ?? "pairing"; const existingAllowFrom = cfg.channels?.whatsapp?.allowFrom ?? []; const existingLabel = existingAllowFrom.length > 0 ? existingAllowFrom.join(", ") : "unset"; - const existingResponsePrefix = cfg.messages?.responsePrefix; if (options?.forceAllowlist) { await prompter.note( @@ -96,17 +85,8 @@ async function promptWhatsAppAllowFrom( let next = setWhatsAppSelfChatMode(cfg, true); next = setWhatsAppDmPolicy(next, "allowlist"); next = setWhatsAppAllowFrom(next, unique); - if (existingResponsePrefix === undefined) { - next = setMessagesResponsePrefix(next, "[clawdbot]"); - } await prompter.note( - [ - "Allowlist mode enabled.", - `- allowFrom includes ${normalized}`, - existingResponsePrefix === undefined - ? "- responsePrefix set to [clawdbot]" - : "- responsePrefix left unchanged", - ].join("\n"), + ["Allowlist mode enabled.", `- allowFrom includes ${normalized}`].join("\n"), "WhatsApp allowlist", ); return next; @@ -163,17 +143,11 @@ async function promptWhatsAppAllowFrom( let next = setWhatsAppSelfChatMode(cfg, true); next = setWhatsAppDmPolicy(next, "allowlist"); next = setWhatsAppAllowFrom(next, unique); - if (existingResponsePrefix === undefined) { - next = setMessagesResponsePrefix(next, "[clawdbot]"); - } await prompter.note( [ "Personal phone mode enabled.", "- dmPolicy set to allowlist (pairing skipped)", `- allowFrom includes ${normalized}`, - existingResponsePrefix === undefined - ? "- responsePrefix set to [clawdbot]" - : "- responsePrefix left unchanged", ].join("\n"), "WhatsApp personal phone", ); diff --git a/src/web/auto-reply.web-auto-reply.prefixes-body-same-phone-marker-from.test.ts b/src/web/auto-reply.web-auto-reply.prefixes-body-same-phone-marker-from.test.ts index 6b7cd7a97..4ccf7cd5d 100644 --- a/src/web/auto-reply.web-auto-reply.prefixes-body-same-phone-marker-from.test.ts +++ b/src/web/auto-reply.web-auto-reply.prefixes-body-same-phone-marker-from.test.ts @@ -258,6 +258,55 @@ describe("web auto-reply", () => { expect(reply).toHaveBeenCalledWith("🦞 hello there"); resetLoadConfigMock(); }); + it("defaults responsePrefix for self-chat replies when unset", async () => { + setLoadConfigMock(() => ({ + agents: { + list: [ + { + id: "main", + default: true, + identity: { name: "Mainbot", emoji: "🦞", theme: "space lobster" }, + }, + ], + }, + channels: { whatsapp: { allowFrom: ["+1555"] } }, + messages: { + messagePrefix: undefined, + responsePrefix: undefined, + }, + })); + + let capturedOnMessage: + | ((msg: import("./inbound.js").WebInboundMessage) => Promise) + | undefined; + const reply = vi.fn(); + const listenerFactory = async (opts: { + onMessage: (msg: import("./inbound.js").WebInboundMessage) => Promise; + }) => { + capturedOnMessage = opts.onMessage; + return { close: vi.fn() }; + }; + + const resolver = vi.fn().mockResolvedValue({ text: "hello there" }); + + await monitorWebChannel(false, listenerFactory, false, resolver); + expect(capturedOnMessage).toBeDefined(); + + await capturedOnMessage?.({ + body: "hi", + from: "+1555", + to: "+1555", + selfE164: "+1555", + chatType: "direct", + id: "msg1", + sendComposing: vi.fn(), + reply, + sendMedia: vi.fn(), + }); + + expect(reply).toHaveBeenCalledWith("[Mainbot] hello there"); + resetLoadConfigMock(); + }); it("does not deliver HEARTBEAT_OK responses", async () => { setLoadConfigMock(() => ({ channels: { whatsapp: { allowFrom: ["*"] } }, diff --git a/src/web/auto-reply/monitor/process-message.ts b/src/web/auto-reply/monitor/process-message.ts index 205fd5699..02643fc40 100644 --- a/src/web/auto-reply/monitor/process-message.ts +++ b/src/web/auto-reply/monitor/process-message.ts @@ -1,4 +1,8 @@ -import { resolveEffectiveMessagesConfig, resolveIdentityName } from "../../../agents/identity.js"; +import { + resolveEffectiveMessagesConfig, + resolveIdentityName, + resolveIdentityNamePrefix, +} from "../../../agents/identity.js"; import { extractShortModelName, type ResponsePrefixContext, @@ -172,10 +176,17 @@ export async function processMessage(params: { const textLimit = params.maxMediaTextChunkLimit ?? resolveTextChunkLimit(params.cfg, "whatsapp"); let didLogHeartbeatStrip = false; let didSendReply = false; - const responsePrefix = resolveEffectiveMessagesConfig( - params.cfg, - params.route.agentId, - ).responsePrefix; + const configuredResponsePrefix = params.cfg.messages?.responsePrefix; + const resolvedMessages = resolveEffectiveMessagesConfig(params.cfg, params.route.agentId); + const isSelfChat = + params.msg.chatType !== "group" && + Boolean(params.msg.selfE164) && + normalizeE164(params.msg.from) === normalizeE164(params.msg.selfE164 ?? ""); + const responsePrefix = + resolvedMessages.responsePrefix ?? + (configuredResponsePrefix === undefined && isSelfChat + ? resolveIdentityNamePrefix(params.cfg, params.route.agentId) ?? "[clawdbot]" + : undefined); // Create mutable context for response prefix template interpolation let prefixContext: ResponsePrefixContext = {