This commit is contained in:
zerone0x 2026-01-29 11:33:16 -05:00 committed by GitHub
commit c09c36485b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 58 additions and 3 deletions

View File

@ -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).

View File

@ -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 = {

View File

@ -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");
}

View File

@ -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,