fix: scope whatsapp self-chat response prefix
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
);
|
||||
|
||||
@@ -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<void>)
|
||||
| undefined;
|
||||
const reply = vi.fn();
|
||||
const listenerFactory = async (opts: {
|
||||
onMessage: (msg: import("./inbound.js").WebInboundMessage) => Promise<void>;
|
||||
}) => {
|
||||
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: ["*"] } },
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user