fix(chat): dedupe optimistic user message against Gateway-prefixed echo (#887)

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Haze <hazeone@users.noreply.github.com>
This commit is contained in:
Haze
2026-04-23 20:40:59 +08:00
committed by GitHub
parent 430d1d0603
commit 31d4531327
3 changed files with 116 additions and 2 deletions

View File

@@ -230,8 +230,31 @@ function normalizeStreamingMessage(message: unknown): unknown {
: rawMessage;
}
/**
* Strip Gateway-injected metadata that does NOT exist on the renderer's
* optimistic user message but is echoed back when the Gateway persists it:
* - leading timestamp `[Wed 2026-04-22 10:30 GMT+8] `
* - `[message_id: uuid]` tags sprinkled throughout the text
* - `[media attached: path (mime) | path]` references appended when the
* renderer sends attachments via `chat:sendWithMedia`
* - Gateway-injected "Conversation info (untrusted metadata): ..." blocks
*
* Keeping this aligned with `cleanUserText` in `pages/Chat/message-utils.ts`
* is important: the user bubble renders the cleaned text, so the comparison
* used to dedupe optimistic vs server echoes must operate on the same
* cleaned form — otherwise the same visible message renders twice.
*/
function stripGatewayUserMetadata(text: string): string {
return text
.replace(/^\s*\[(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s+\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}\s+[^\]]+\]\s*/i, '')
.replace(/\s*\[media attached:[^\]]*\]/g, '')
.replace(/\s*\[message_id:\s*[^\]]+\]/g, '')
.replace(/^Conversation info\s*\([^)]*\):\s*```[a-z]*\n[\s\S]*?```\s*/i, '')
.replace(/^Conversation info\s*\([^)]*\):\s*\{[\s\S]*?\}\s*/i, '');
}
function normalizeComparableUserText(content: unknown): string {
return getMessageText(content)
return stripGatewayUserMetadata(getMessageText(content))
.replace(/\s+/g, ' ')
.trim();
}

View File

@@ -128,8 +128,31 @@ function normalizeStreamingMessage(message: unknown): unknown {
: rawMessage;
}
/**
* Strip Gateway-injected metadata that does NOT exist on the renderer's
* optimistic user message but is echoed back when the Gateway persists it:
* - leading timestamp `[Wed 2026-04-22 10:30 GMT+8] `
* - `[message_id: uuid]` tags sprinkled throughout the text
* - `[media attached: path (mime) | path]` references appended when the
* renderer sends attachments via `chat:sendWithMedia`
* - Gateway-injected "Conversation info (untrusted metadata): ..." blocks
*
* Keeping this aligned with `cleanUserText` in `pages/Chat/message-utils.ts`
* is important: the user bubble renders the cleaned text, so the comparison
* used to dedupe optimistic vs server echoes must operate on the same
* cleaned form — otherwise the same visible message renders twice.
*/
function stripGatewayUserMetadata(text: string): string {
return text
.replace(/^\s*\[(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s+\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}\s+[^\]]+\]\s*/i, '')
.replace(/\s*\[media attached:[^\]]*\]/g, '')
.replace(/\s*\[message_id:\s*[^\]]+\]/g, '')
.replace(/^Conversation info\s*\([^)]*\):\s*```[a-z]*\n[\s\S]*?```\s*/i, '')
.replace(/^Conversation info\s*\([^)]*\):\s*\{[\s\S]*?\}\s*/i, '');
}
function normalizeComparableUserText(content: unknown): string {
return getMessageText(content)
return stripGatewayUserMetadata(getMessageText(content))
.replace(/\s+/g, ' ')
.trim();
}