Neutralize accidental @everyone/@here in outbound agent text

Agent-authored replies pass through sanitizeForOutbound before Discord
send, but it did not touch mass-mention tokens — so a reply that merely
*mentioned* "@everyone" while explaining a feature pinged the whole
channel. Add neutralizeMassMentions (zero-width space after @) to the
central sanitizer so normal reply/progress/edit text can never mass-ping;
intentional broadcasts (e.g. the disk-usage alert) use a dedicated path.
Leaves <@id> mentions and emails intact. Covered by unit tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Codex
2026-08-24 10:58:53 +09:00
parent 8349a020ce
commit 23c9e5101f
2 changed files with 63 additions and 3 deletions

View File

@@ -146,10 +146,24 @@ export function neutralizeStrayMarkdown(text: string): string {
/** @deprecated Kept for back-compat; use neutralizeStrayMarkdown. */
export const neutralizeStrayBackticks = neutralizeStrayMarkdown;
/**
* Prevent accidental mass pings. Agent-authored text that literally contains
* `@everyone` / `@here` must never notify the whole channel — that is only ever
* intended via a dedicated broadcast path (e.g. the disk-usage alert), not via a
* normal reply/progress/edit message. A zero-width space (U+200B) after the `@`
* keeps the text readable ("@everyone" still reads the same) while stopping
* Discord from parsing it as a mention. Idempotent, and leaves `<@id>` user/role
* mentions and ordinary emails (`me@example.com`) untouched.
*/
export function neutralizeMassMentions(text: string): string {
return text.replace(/@(everyone|here)\b/g, '@\u200b$1');
}
/**
* Sanitize raw agent output for internal use (storage, IPC, intermediate
* channel buffers). Strips internal tags + tool-call leaks and redacts
* secrets, but does NOT touch markdown delimiters.
* channel buffers). Strips internal tags + tool-call leaks, redacts secrets,
* and neutralizes accidental @everyone/@here pings, but does NOT touch markdown
* delimiters.
*
* Use this when the text will pass through another `formatOutbound` call
* downstream (e.g., the Discord channel boundary). Applying the markdown
@@ -161,7 +175,8 @@ export function sanitizeForOutbound(rawText: string): string {
if (!text) return '';
text = stripToolCallLeaks(text);
if (!text) return '';
return redactSecrets(text);
text = redactSecrets(text);
return neutralizeMassMentions(text);
}
/**