From c0e5e8db227e420d09013ecc821b12a65a1901d2 Mon Sep 17 00:00:00 2001 From: Tyler Yust Date: Thu, 12 Mar 2026 01:10:50 -0700 Subject: [PATCH] fix: hide injected timestamp prefixes in chat ui --- .../reply/strip-inbound-meta.test.ts | 26 +++++++++++++++++++ src/auto-reply/reply/strip-inbound-meta.ts | 14 ++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/auto-reply/reply/strip-inbound-meta.test.ts b/src/auto-reply/reply/strip-inbound-meta.test.ts index cfc2c622f7f..9bdb20edcee 100644 --- a/src/auto-reply/reply/strip-inbound-meta.test.ts +++ b/src/auto-reply/reply/strip-inbound-meta.test.ts @@ -120,6 +120,32 @@ Hello from user`; }); }); +describe("timestamp prefix stripping", () => { + it("strips a leading injected timestamp prefix", () => { + expect(stripInboundMetadata("[Wed 2026-03-11 23:51 PDT] hello")).toBe("hello"); + }); + + it("strips timestamp prefix with UTC timezone", () => { + expect(stripInboundMetadata("[Thu 2026-03-12 07:00 UTC] what time is it?")).toBe( + "what time is it?", + ); + }); + + it("leaves non timestamp brackets alone", () => { + expect(stripInboundMetadata("[some note] hello")).toBe("[some note] hello"); + }); + + it("strips timestamp prefix and inbound metadata blocks together", () => { + const input = `[Wed 2026-03-11 23:51 PDT] Conversation info (untrusted metadata): +\`\`\`json +{"message_id":"msg-1","sender":"+1555"} +\`\`\` + +Hello`; + expect(stripInboundMetadata(input)).toBe("Hello"); + }); +}); + describe("extractInboundSenderLabel", () => { it("returns the sender label block when present", () => { const input = `${CONV_BLOCK}\n\n${SENDER_BLOCK}\n\nHello from user`; diff --git a/src/auto-reply/reply/strip-inbound-meta.ts b/src/auto-reply/reply/strip-inbound-meta.ts index 16630cb7488..80e12a3fc20 100644 --- a/src/auto-reply/reply/strip-inbound-meta.ts +++ b/src/auto-reply/reply/strip-inbound-meta.ts @@ -7,8 +7,13 @@ * etc.) directly to the stored user message content so the LLM can access * them. These blocks are AI-facing only and must never surface in user-visible * chat history. + * + * Also strips the timestamp prefix injected by `injectTimestamp` so UI surfaces + * do not show AI-facing envelope metadata as user text. */ +const LEADING_TIMESTAMP_PREFIX_RE = /^\[[A-Za-z]{3} \d{4}-\d{2}-\d{2} \d{2}:\d{2}[^\]]*\] */; + /** * Sentinel strings that identify the start of an injected metadata block. * Must stay in sync with `buildInboundUserContextPrefix` in `inbound-meta.ts`. @@ -121,11 +126,16 @@ function stripTrailingUntrustedContextSuffix(lines: string[]): string[] { * (fast path — zero allocation). */ export function stripInboundMetadata(text: string): string { - if (!text || !SENTINEL_FAST_RE.test(text)) { + if (!text) { return text; } - const lines = text.split("\n"); + const withoutTimestamp = text.replace(LEADING_TIMESTAMP_PREFIX_RE, ""); + if (!SENTINEL_FAST_RE.test(withoutTimestamp)) { + return withoutTimestamp; + } + + const lines = withoutTimestamp.split("\n"); const result: string[] = []; let inMetaBlock = false; let inFencedJson = false;