fix(whatsapp): normalize literal \n escape sequences in outbound messages
Some LLM providers output literal \n characters (backslash-n) instead of actual newlines. This causes WhatsApp to display the escape sequences as text rather than rendering line breaks. Add normalizeOutboundTextNewlines() to convert literal \n, \r\n, and \r escape sequences to actual newlines before sending messages via the WhatsApp adapter. Fixes #3082 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
93c2d65398
commit
861be9c936
@ -6,6 +6,7 @@ Docs: https://docs.molt.bot
|
||||
Status: beta.
|
||||
|
||||
### Changes
|
||||
- WhatsApp: normalize literal `\n` escape sequences to actual newlines in outbound messages. (#3082)
|
||||
- Rebrand: rename the npm package/CLI to `moltbot`, add a `moltbot` compatibility shim, and move extensions to the `@moltbot/*` scope.
|
||||
- Commands: group /help and /commands output with Telegram paging. (#2504) Thanks @hougangdev.
|
||||
- macOS: limit project-local `node_modules/.bin` PATH preference to debug builds (reduce PATH hijacking risk).
|
||||
|
||||
@ -15,7 +15,10 @@ import {
|
||||
shouldSkipDuplicateInbound,
|
||||
} from "./reply/inbound-dedupe.js";
|
||||
import { formatInboundBodyWithSenderMeta } from "./reply/inbound-sender-meta.js";
|
||||
import { normalizeInboundTextNewlines } from "./reply/inbound-text.js";
|
||||
import {
|
||||
normalizeInboundTextNewlines,
|
||||
normalizeOutboundTextNewlines,
|
||||
} from "./reply/inbound-text.js";
|
||||
import { resolveGroupRequireMention } from "./reply/groups.js";
|
||||
import {
|
||||
buildMentionRegexes,
|
||||
@ -69,6 +72,40 @@ describe("normalizeInboundTextNewlines", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeOutboundTextNewlines", () => {
|
||||
it("keeps real newlines", () => {
|
||||
expect(normalizeOutboundTextNewlines("a\nb")).toBe("a\nb");
|
||||
});
|
||||
|
||||
it("converts literal \\n to real newlines", () => {
|
||||
expect(normalizeOutboundTextNewlines("Hello\\nWorld")).toBe("Hello\nWorld");
|
||||
});
|
||||
|
||||
it("converts multiple literal \\n sequences", () => {
|
||||
expect(normalizeOutboundTextNewlines("Line1\\n\\nLine2")).toBe("Line1\n\nLine2");
|
||||
});
|
||||
|
||||
it("converts literal \\r\\n to real newlines", () => {
|
||||
expect(normalizeOutboundTextNewlines("a\\r\\nb")).toBe("a\nb");
|
||||
});
|
||||
|
||||
it("converts literal \\r to real newlines", () => {
|
||||
expect(normalizeOutboundTextNewlines("a\\rb")).toBe("a\nb");
|
||||
});
|
||||
|
||||
it("handles empty string", () => {
|
||||
expect(normalizeOutboundTextNewlines("")).toBe("");
|
||||
});
|
||||
|
||||
it("handles text without escape sequences", () => {
|
||||
expect(normalizeOutboundTextNewlines("Hello World")).toBe("Hello World");
|
||||
});
|
||||
|
||||
it("handles mixed real and literal newlines", () => {
|
||||
expect(normalizeOutboundTextNewlines("Real\nand\\nliteral")).toBe("Real\nand\nliteral");
|
||||
});
|
||||
});
|
||||
|
||||
describe("finalizeInboundContext", () => {
|
||||
it("fills BodyForAgent/BodyForCommands and normalizes newlines", () => {
|
||||
const ctx: MsgContext = {
|
||||
|
||||
@ -1,3 +1,17 @@
|
||||
export function normalizeInboundTextNewlines(input: string): string {
|
||||
return input.replaceAll("\r\n", "\n").replaceAll("\r", "\n").replaceAll("\\n", "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize outbound text newlines before sending to channels.
|
||||
* Converts literal `\n` escape sequences to actual newlines.
|
||||
* Some LLM providers may output literal `\n` in their responses,
|
||||
* which causes WhatsApp (and potentially other channels) to display
|
||||
* the escape sequence as text instead of rendering a line break.
|
||||
*/
|
||||
export function normalizeOutboundTextNewlines(input: string): string {
|
||||
if (!input) return input;
|
||||
// Convert literal \n (backslash-n) sequences to actual newlines.
|
||||
// Also handle \r\n and \r for consistency.
|
||||
return input.replaceAll("\\r\\n", "\n").replaceAll("\\r", "\n").replaceAll("\\n", "\n");
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { chunkText } from "../../../auto-reply/chunk.js";
|
||||
import { normalizeOutboundTextNewlines } from "../../../auto-reply/reply/inbound-text.js";
|
||||
import { shouldLogVerbose } from "../../../globals.js";
|
||||
import { sendPollWhatsApp } from "../../../web/outbound.js";
|
||||
import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "../../../whatsapp/normalize.js";
|
||||
@ -60,7 +61,8 @@ export const whatsappOutbound: ChannelOutboundAdapter = {
|
||||
sendText: async ({ to, text, accountId, deps, gifPlayback }) => {
|
||||
const send =
|
||||
deps?.sendWhatsApp ?? (await import("../../../web/outbound.js")).sendMessageWhatsApp;
|
||||
const result = await send(to, text, {
|
||||
const normalizedText = normalizeOutboundTextNewlines(text);
|
||||
const result = await send(to, normalizedText, {
|
||||
verbose: false,
|
||||
accountId: accountId ?? undefined,
|
||||
gifPlayback,
|
||||
@ -70,7 +72,8 @@ export const whatsappOutbound: ChannelOutboundAdapter = {
|
||||
sendMedia: async ({ to, text, mediaUrl, accountId, deps, gifPlayback }) => {
|
||||
const send =
|
||||
deps?.sendWhatsApp ?? (await import("../../../web/outbound.js")).sendMessageWhatsApp;
|
||||
const result = await send(to, text, {
|
||||
const normalizedText = normalizeOutboundTextNewlines(text);
|
||||
const result = await send(to, normalizedText, {
|
||||
verbose: false,
|
||||
mediaUrl,
|
||||
accountId: accountId ?? undefined,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user