From 9d742ba51f01281b95e9fd10a3fcd4c3654b68cf Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 24 Jan 2026 13:52:27 +0000 Subject: [PATCH] fix: hide message_id hints in web chat --- src/gateway/chat-sanitize.test.ts | 42 +++++++++++++++++++++++++++++++ src/gateway/chat-sanitize.ts | 15 ++++++++--- 2 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 src/gateway/chat-sanitize.test.ts diff --git a/src/gateway/chat-sanitize.test.ts b/src/gateway/chat-sanitize.test.ts new file mode 100644 index 000000000..29c3f3e9a --- /dev/null +++ b/src/gateway/chat-sanitize.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, test } from "vitest"; +import { stripEnvelopeFromMessage } from "./chat-sanitize.js"; + +describe("stripEnvelopeFromMessage", () => { + test("removes message_id hint lines from user messages", () => { + const input = { + role: "user", + content: "[WhatsApp 2026-01-24 13:36] yolo\n[message_id: 7b8b]", + }; + const result = stripEnvelopeFromMessage(input) as { content?: string }; + expect(result.content).toBe("yolo"); + }); + + test("removes message_id hint lines from text content arrays", () => { + const input = { + role: "user", + content: [{ type: "text", text: "hi\n[message_id: abc123]" }], + }; + const result = stripEnvelopeFromMessage(input) as { + content?: Array<{ type: string; text?: string }>; + }; + expect(result.content?.[0]?.text).toBe("hi"); + }); + + test("does not strip inline message_id text that is part of a line", () => { + const input = { + role: "user", + content: "I typed [message_id: 123] on purpose", + }; + const result = stripEnvelopeFromMessage(input) as { content?: string }; + expect(result.content).toBe("I typed [message_id: 123] on purpose"); + }); + + test("does not strip assistant messages", () => { + const input = { + role: "assistant", + content: "note\n[message_id: 123]", + }; + const result = stripEnvelopeFromMessage(input) as { content?: string }; + expect(result.content).toBe("note\n[message_id: 123]"); + }); +}); diff --git a/src/gateway/chat-sanitize.ts b/src/gateway/chat-sanitize.ts index 398fabf1f..c4bded4fc 100644 --- a/src/gateway/chat-sanitize.ts +++ b/src/gateway/chat-sanitize.ts @@ -14,6 +14,8 @@ const ENVELOPE_CHANNELS = [ "BlueBubbles", ]; +const MESSAGE_ID_LINE = /^\s*\[message_id:\s*[^\]]+\]\s*$/i; + function looksLikeEnvelopeHeader(header: string): boolean { if (/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}Z\b/.test(header)) return true; if (/\d{4}-\d{2}-\d{2} \d{2}:\d{2}\b/.test(header)) return true; @@ -28,13 +30,20 @@ export function stripEnvelope(text: string): string { return text.slice(match[0].length); } +function stripMessageIdHints(text: string): string { + if (!text.includes("[message_id:")) return text; + const lines = text.split(/\r?\n/); + const filtered = lines.filter((line) => !MESSAGE_ID_LINE.test(line)); + return filtered.length === lines.length ? text : filtered.join("\n"); +} + function stripEnvelopeFromContent(content: unknown[]): { content: unknown[]; changed: boolean } { let changed = false; const next = content.map((item) => { if (!item || typeof item !== "object") return item; const entry = item as Record; if (entry.type !== "text" || typeof entry.text !== "string") return item; - const stripped = stripEnvelope(entry.text); + const stripped = stripMessageIdHints(stripEnvelope(entry.text)); if (stripped === entry.text) return item; changed = true; return { @@ -55,7 +64,7 @@ export function stripEnvelopeFromMessage(message: unknown): unknown { const next: Record = { ...entry }; if (typeof entry.content === "string") { - const stripped = stripEnvelope(entry.content); + const stripped = stripMessageIdHints(stripEnvelope(entry.content)); if (stripped !== entry.content) { next.content = stripped; changed = true; @@ -67,7 +76,7 @@ export function stripEnvelopeFromMessage(message: unknown): unknown { changed = true; } } else if (typeof entry.text === "string") { - const stripped = stripEnvelope(entry.text); + const stripped = stripMessageIdHints(stripEnvelope(entry.text)); if (stripped !== entry.text) { next.text = stripped; changed = true;