fix: scope whatsapp self-chat response prefix
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 2026.1.15
|
## 2026.1.16 (unreleased)
|
||||||
|
|
||||||
### Highlights
|
### Highlights
|
||||||
- Plugins: add provider auth registry + `clawdbot models auth login` for plugin-driven OAuth/API key flows.
|
- 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.
|
- Discord: allow emoji/sticker uploads + channel actions in config defaults. (#870) — thanks @JDIVE.
|
||||||
|
|
||||||
### Fixes
|
### 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: 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: 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.
|
- 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,
|
"selfChatMode": true,
|
||||||
"dmPolicy": "allowlist",
|
"dmPolicy": "allowlist",
|
||||||
"allowFrom": ["+15551234567"]
|
"allowFrom": ["+15551234567"]
|
||||||
},
|
|
||||||
"messages": {
|
|
||||||
"responsePrefix": "[clawdbot]"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Tip: set `messages.responsePrefix` explicitly if you want a consistent bot prefix
|
Self-chat replies default to `[{identity.name}]` when set (otherwise `[clawdbot]`)
|
||||||
on outbound replies.
|
if `messages.responsePrefix` is unset. Set it explicitly to customize or disable
|
||||||
|
the prefix (use `""` to remove it).
|
||||||
|
|
||||||
### Number sourcing tips
|
### Number sourcing tips
|
||||||
- **Local eSIM** from your country's mobile carrier (most reliable)
|
- **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
|
`responsePrefix` is applied to **all outbound replies** (tool summaries, block
|
||||||
streaming, final replies) across channels unless already present.
|
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).
|
Set it to `"auto"` to derive `[{identity.name}]` for the routed agent (when set).
|
||||||
|
|
||||||
#### Template variables
|
#### Template variables
|
||||||
|
|||||||
@@ -27,16 +27,6 @@ function setWhatsAppAllowFrom(cfg: ClawdbotConfig, allowFrom?: string[]): Clawdb
|
|||||||
return mergeWhatsAppConfig(cfg, { allowFrom }, { unsetOnUndefined: ["allowFrom"] });
|
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 {
|
function setWhatsAppSelfChatMode(cfg: ClawdbotConfig, selfChatMode: boolean): ClawdbotConfig {
|
||||||
return mergeWhatsAppConfig(cfg, { selfChatMode });
|
return mergeWhatsAppConfig(cfg, { selfChatMode });
|
||||||
}
|
}
|
||||||
@@ -65,7 +55,6 @@ async function promptWhatsAppAllowFrom(
|
|||||||
const existingPolicy = cfg.channels?.whatsapp?.dmPolicy ?? "pairing";
|
const existingPolicy = cfg.channels?.whatsapp?.dmPolicy ?? "pairing";
|
||||||
const existingAllowFrom = cfg.channels?.whatsapp?.allowFrom ?? [];
|
const existingAllowFrom = cfg.channels?.whatsapp?.allowFrom ?? [];
|
||||||
const existingLabel = existingAllowFrom.length > 0 ? existingAllowFrom.join(", ") : "unset";
|
const existingLabel = existingAllowFrom.length > 0 ? existingAllowFrom.join(", ") : "unset";
|
||||||
const existingResponsePrefix = cfg.messages?.responsePrefix;
|
|
||||||
|
|
||||||
if (options?.forceAllowlist) {
|
if (options?.forceAllowlist) {
|
||||||
await prompter.note(
|
await prompter.note(
|
||||||
@@ -96,17 +85,8 @@ async function promptWhatsAppAllowFrom(
|
|||||||
let next = setWhatsAppSelfChatMode(cfg, true);
|
let next = setWhatsAppSelfChatMode(cfg, true);
|
||||||
next = setWhatsAppDmPolicy(next, "allowlist");
|
next = setWhatsAppDmPolicy(next, "allowlist");
|
||||||
next = setWhatsAppAllowFrom(next, unique);
|
next = setWhatsAppAllowFrom(next, unique);
|
||||||
if (existingResponsePrefix === undefined) {
|
|
||||||
next = setMessagesResponsePrefix(next, "[clawdbot]");
|
|
||||||
}
|
|
||||||
await prompter.note(
|
await prompter.note(
|
||||||
[
|
["Allowlist mode enabled.", `- allowFrom includes ${normalized}`].join("\n"),
|
||||||
"Allowlist mode enabled.",
|
|
||||||
`- allowFrom includes ${normalized}`,
|
|
||||||
existingResponsePrefix === undefined
|
|
||||||
? "- responsePrefix set to [clawdbot]"
|
|
||||||
: "- responsePrefix left unchanged",
|
|
||||||
].join("\n"),
|
|
||||||
"WhatsApp allowlist",
|
"WhatsApp allowlist",
|
||||||
);
|
);
|
||||||
return next;
|
return next;
|
||||||
@@ -163,17 +143,11 @@ async function promptWhatsAppAllowFrom(
|
|||||||
let next = setWhatsAppSelfChatMode(cfg, true);
|
let next = setWhatsAppSelfChatMode(cfg, true);
|
||||||
next = setWhatsAppDmPolicy(next, "allowlist");
|
next = setWhatsAppDmPolicy(next, "allowlist");
|
||||||
next = setWhatsAppAllowFrom(next, unique);
|
next = setWhatsAppAllowFrom(next, unique);
|
||||||
if (existingResponsePrefix === undefined) {
|
|
||||||
next = setMessagesResponsePrefix(next, "[clawdbot]");
|
|
||||||
}
|
|
||||||
await prompter.note(
|
await prompter.note(
|
||||||
[
|
[
|
||||||
"Personal phone mode enabled.",
|
"Personal phone mode enabled.",
|
||||||
"- dmPolicy set to allowlist (pairing skipped)",
|
"- dmPolicy set to allowlist (pairing skipped)",
|
||||||
`- allowFrom includes ${normalized}`,
|
`- allowFrom includes ${normalized}`,
|
||||||
existingResponsePrefix === undefined
|
|
||||||
? "- responsePrefix set to [clawdbot]"
|
|
||||||
: "- responsePrefix left unchanged",
|
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
"WhatsApp personal phone",
|
"WhatsApp personal phone",
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -258,6 +258,55 @@ describe("web auto-reply", () => {
|
|||||||
expect(reply).toHaveBeenCalledWith("🦞 hello there");
|
expect(reply).toHaveBeenCalledWith("🦞 hello there");
|
||||||
resetLoadConfigMock();
|
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 () => {
|
it("does not deliver HEARTBEAT_OK responses", async () => {
|
||||||
setLoadConfigMock(() => ({
|
setLoadConfigMock(() => ({
|
||||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { resolveEffectiveMessagesConfig, resolveIdentityName } from "../../../agents/identity.js";
|
import {
|
||||||
|
resolveEffectiveMessagesConfig,
|
||||||
|
resolveIdentityName,
|
||||||
|
resolveIdentityNamePrefix,
|
||||||
|
} from "../../../agents/identity.js";
|
||||||
import {
|
import {
|
||||||
extractShortModelName,
|
extractShortModelName,
|
||||||
type ResponsePrefixContext,
|
type ResponsePrefixContext,
|
||||||
@@ -172,10 +176,17 @@ export async function processMessage(params: {
|
|||||||
const textLimit = params.maxMediaTextChunkLimit ?? resolveTextChunkLimit(params.cfg, "whatsapp");
|
const textLimit = params.maxMediaTextChunkLimit ?? resolveTextChunkLimit(params.cfg, "whatsapp");
|
||||||
let didLogHeartbeatStrip = false;
|
let didLogHeartbeatStrip = false;
|
||||||
let didSendReply = false;
|
let didSendReply = false;
|
||||||
const responsePrefix = resolveEffectiveMessagesConfig(
|
const configuredResponsePrefix = params.cfg.messages?.responsePrefix;
|
||||||
params.cfg,
|
const resolvedMessages = resolveEffectiveMessagesConfig(params.cfg, params.route.agentId);
|
||||||
params.route.agentId,
|
const isSelfChat =
|
||||||
).responsePrefix;
|
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
|
// Create mutable context for response prefix template interpolation
|
||||||
let prefixContext: ResponsePrefixContext = {
|
let prefixContext: ResponsePrefixContext = {
|
||||||
|
|||||||
Reference in New Issue
Block a user