fix(router): escape Discord markdown delimiters instead of stripping
Previously formatOutbound stripped `*`, `_`, `~`, `|`, `` ` ``, `#`, `>`
from prose so they wouldn't trigger Discord formatting. That worked but
silently lost information — `**DONE**`, `STEP_DONE`, ~~strike~~ all
arrived in Discord with the source characters missing.
Switch to backslash-escape so the literal source text shows up while
still suppressing the formatting interpretation. Triple-backtick fenced
code blocks are still preserved verbatim.
Escape is non-idempotent (running it twice double-escapes backslashes),
so split the pipeline:
- sanitizeForOutbound: strip internal tags + tool-call leaks + redact
secrets. Use this for intermediate text that will pass through
another formatOutbound call downstream (work-item storage, channel
wrappers, session-command output).
- formatOutbound: sanitize + neutralizeStrayMarkdown. Reserved for the
single Discord-send boundary in channels/discord.ts.
Internal callers (message-turn-controller, session-commands, the
sendFormattedChannelMessage / sendFormattedTrackedChannelMessage /
editFormattedTrackedChannelMessage wrappers in index.ts, and the cron
reviewer-bot path) now use sanitizeForOutbound so the markdown escape
runs exactly once at the channel boundary.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
10
src/index.ts
10
src/index.ts
@@ -34,7 +34,7 @@ import { startIpcWatcher } from './ipc.js';
|
||||
import {
|
||||
findChannel,
|
||||
findChannelByName,
|
||||
formatOutbound,
|
||||
sanitizeForOutbound,
|
||||
normalizeMessageForDedupe,
|
||||
resolveChannelForDeliveryRole,
|
||||
} from './router.js';
|
||||
@@ -98,7 +98,7 @@ export async function sendFormattedChannelMessage(
|
||||
logger.warn({ jid }, 'No channel owns JID, cannot send message');
|
||||
return;
|
||||
}
|
||||
const text = formatOutbound(rawText);
|
||||
const text = sanitizeForOutbound(rawText);
|
||||
if (text) await channel.sendMessage(jid, text);
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ export async function sendFormattedTrackedChannelMessage(
|
||||
logger.warn({ jid }, 'No channel owns JID, cannot send tracked message');
|
||||
return null;
|
||||
}
|
||||
const text = formatOutbound(rawText);
|
||||
const text = sanitizeForOutbound(rawText);
|
||||
if (!text || !channel.sendAndTrack) return null;
|
||||
return channel.sendAndTrack(jid, text);
|
||||
}
|
||||
@@ -128,7 +128,7 @@ export async function editFormattedTrackedChannelMessage(
|
||||
logger.warn({ jid }, 'No channel owns JID, cannot edit tracked message');
|
||||
return;
|
||||
}
|
||||
const text = formatOutbound(rawText);
|
||||
const text = sanitizeForOutbound(rawText);
|
||||
if (!text || !channel.editMessage) return;
|
||||
await channel.editMessage(jid, messageId, text);
|
||||
}
|
||||
@@ -388,7 +388,7 @@ async function main(): Promise<void> {
|
||||
sendFormattedChannelMessage(channels, jid, rawText),
|
||||
sendMessageViaReviewerBot: reviewerChannelForCron
|
||||
? async (jid, rawText) => {
|
||||
const text = formatOutbound(rawText);
|
||||
const text = sanitizeForOutbound(rawText);
|
||||
if (text) await reviewerChannelForCron.sendMessage(jid, text);
|
||||
}
|
||||
: undefined,
|
||||
|
||||
Reference in New Issue
Block a user