From eccfacb02cc5cf015b8cd97c90c748602a00fad6 Mon Sep 17 00:00:00 2001 From: Dr Rushindra Sinha Date: Tue, 16 Jun 2026 00:35:28 +0100 Subject: [PATCH] fix(whatsapp): stop markdownToWhatsApp dropping code spans followed by a digit (#93409) The inline-code/fence restore step matched the placeholder index with a greedy `(\d+)`, so a digit in user text immediately after a code span (e.g. `code`5) was absorbed into the index, resolved to undefined, and `?? ""` deleted both the code span and the digit. Terminate the placeholder index with the existing NUL marker so the index boundary is unambiguous. Co-authored-by: Dr Rushindra Sinha <5796457+rushindrasinha@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 --- extensions/whatsapp/src/targets-runtime.ts | 12 ++++++++---- extensions/whatsapp/src/text-runtime.test.ts | 11 +++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/extensions/whatsapp/src/targets-runtime.ts b/extensions/whatsapp/src/targets-runtime.ts index 5a476bcf448e..ef4810c431ba 100644 --- a/extensions/whatsapp/src/targets-runtime.ts +++ b/extensions/whatsapp/src/targets-runtime.ts @@ -8,6 +8,9 @@ import { CONFIG_DIR, resolveUserPath } from "openclaw/plugin-sdk/text-utility-ru const WHATSAPP_FENCE_PLACEHOLDER = "\x00FENCE"; const WHATSAPP_INLINE_CODE_PLACEHOLDER = "\x00CODE"; +// Terminates the numeric index in a placeholder so the restore regex cannot +// absorb a digit from adjacent user text (e.g. `code`5) into the index. +const WHATSAPP_PLACEHOLDER_TERMINATOR = "\x00"; export type WebChannel = "web"; @@ -197,25 +200,26 @@ export function markdownToWhatsApp(text: string): string { const fences: string[] = []; let result = text.replace(/```[\s\S]*?```/g, (match) => { fences.push(match); - return `${WHATSAPP_FENCE_PLACEHOLDER}${fences.length - 1}`; + return `${WHATSAPP_FENCE_PLACEHOLDER}${fences.length - 1}${WHATSAPP_PLACEHOLDER_TERMINATOR}`; }); const inlineCodes: string[] = []; result = result.replace(/`[^`\n]+`/g, (match) => { inlineCodes.push(match); - return `${WHATSAPP_INLINE_CODE_PLACEHOLDER}${inlineCodes.length - 1}`; + return `${WHATSAPP_INLINE_CODE_PLACEHOLDER}${inlineCodes.length - 1}${WHATSAPP_PLACEHOLDER_TERMINATOR}`; }); result = result.replace(/\*\*(.+?)\*\*/g, "*$1*"); result = result.replace(/__(.+?)__/g, "*$1*"); result = result.replace(/~~(.+?)~~/g, "~$1~"); + const terminator = escapeRegExp(WHATSAPP_PLACEHOLDER_TERMINATOR); result = result.replace( - new RegExp(`${escapeRegExp(WHATSAPP_INLINE_CODE_PLACEHOLDER)}(\\d+)`, "g"), + new RegExp(`${escapeRegExp(WHATSAPP_INLINE_CODE_PLACEHOLDER)}(\\d+)${terminator}`, "g"), (_, idx) => inlineCodes[Number(idx)] ?? "", ); result = result.replace( - new RegExp(`${escapeRegExp(WHATSAPP_FENCE_PLACEHOLDER)}(\\d+)`, "g"), + new RegExp(`${escapeRegExp(WHATSAPP_FENCE_PLACEHOLDER)}(\\d+)${terminator}`, "g"), (_, idx) => fences[Number(idx)] ?? "", ); return result; diff --git a/extensions/whatsapp/src/text-runtime.test.ts b/extensions/whatsapp/src/text-runtime.test.ts index f1e48623e72f..b8c39906f885 100644 --- a/extensions/whatsapp/src/text-runtime.test.ts +++ b/extensions/whatsapp/src/text-runtime.test.ts @@ -41,6 +41,12 @@ describe("markdownToWhatsApp", () => { ["returns empty string for empty input", "", ""], ["returns plain text unchanged", "no formatting here", "no formatting here"], ["handles bold inside a sentence", "This is **very** important", "This is *very* important"], + // Regression: a digit immediately after an inline-code span must not be + // absorbed into the placeholder index (which previously dropped both). + ["preserves inline code immediately followed by a digit", "`a`5", "`a`5"], + ["preserves inline code followed by a number", "`status`200 done", "`status`200 done"], + ["preserves two adjacent code+digit spans", "`x`1 and `y`2", "`x`1 and `y`2"], + ["preserves inline code with a space before a digit", "`a` 5", "`a` 5"], ] as const)("handles markdown-to-whatsapp conversion: %s", (_name, input, expected) => { expect(markdownToWhatsApp(input)).toBe(expected); }); @@ -50,6 +56,11 @@ describe("markdownToWhatsApp", () => { expect(markdownToWhatsApp(input)).toBe(input); }); + it("preserves a fenced code block immediately followed by a digit", () => { + const input = "```code```7 done"; + expect(markdownToWhatsApp(input)).toBe(input); + }); + it("preserves code block with formatting inside", () => { const input = "Before ```**bold** and ~~strike~~``` after **real bold**"; expect(markdownToWhatsApp(input)).toBe(