From 47449152acfe25b597086ebe4227240177d6c5bf Mon Sep 17 00:00:00 2001 From: Paperclip Dev Date: Fri, 20 Mar 2026 16:38:55 +0000 Subject: [PATCH] fix(issues): normalize HTML entities in @mention tokens before agent lookup (#1255) Rich-text comments store entities like after @names; strip them before matching agents so issue_comment_mentioned and wake injection work. Made-with: Cursor --- .../normalize-agent-mention-token.test.ts | 24 +++++++++++++++++++ server/src/services/issues.ts | 12 +++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 server/src/__tests__/normalize-agent-mention-token.test.ts diff --git a/server/src/__tests__/normalize-agent-mention-token.test.ts b/server/src/__tests__/normalize-agent-mention-token.test.ts new file mode 100644 index 00000000..786e484d --- /dev/null +++ b/server/src/__tests__/normalize-agent-mention-token.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from "vitest"; +import { normalizeAgentMentionToken } from "../services/issues.ts"; + +describe("normalizeAgentMentionToken", () => { + it("strips hex numeric entities such as space ( )", () => { + expect(normalizeAgentMentionToken("Baba ")).toBe("Baba"); + }); + + it("strips decimal numeric entities", () => { + expect(normalizeAgentMentionToken("Baba ")).toBe("Baba"); + }); + + it("strips common named entities", () => { + expect(normalizeAgentMentionToken("Baba ")).toBe("Baba"); + }); + + it("returns plain names unchanged", () => { + expect(normalizeAgentMentionToken("Baba")).toBe("Baba"); + }); + + it("trims after stripping entities", () => { + expect(normalizeAgentMentionToken("Baba ")).toBe("Baba"); + }); +}); diff --git a/server/src/services/issues.ts b/server/src/services/issues.ts index 1f1a4961..610f9325 100644 --- a/server/src/services/issues.ts +++ b/server/src/services/issues.ts @@ -196,6 +196,13 @@ function unreadForUserCondition(companyId: string, userId: string) { `; } +const HTML_ENTITY_IN_MENTION = /&#x[0-9a-fA-F]+;|&#[0-9]+;|&[a-z]+;/gi; + +/** Strips common HTML entities from a raw @mention capture so UI-encoded bodies still match agent names. */ +export function normalizeAgentMentionToken(raw: string): string { + return raw.replace(HTML_ENTITY_IN_MENTION, "").trim(); +} + export function deriveIssueUserContext( issue: IssueUserContextInput, userId: string, @@ -1446,7 +1453,10 @@ export function issueService(db: Db) { const re = /\B@([^\s@,!?.]+)/g; const tokens = new Set(); let m: RegExpExecArray | null; - while ((m = re.exec(body)) !== null) tokens.add(m[1].toLowerCase()); + while ((m = re.exec(body)) !== null) { + const normalized = normalizeAgentMentionToken(m[1]); + if (normalized) tokens.add(normalized.toLowerCase()); + } if (tokens.size === 0) return []; const rows = await db.select({ id: agents.id, name: agents.name }) .from(agents).where(eq(agents.companyId, companyId));