Skip to content

feat(discord): preserve UID in resolved mentions as <@UID|Name> #460

@thepagent

Description

@thepagent

Description

resolve_mentions() currently replaces <@UID> with @DisplayName, discarding the UID. Change it to emit <@UID|DisplayName> instead, so the LLM sees a human-readable name while the UID is preserved for mention-back in replies.

Inbound (Discord → ACP): resolve other-user mentions to <@UID|DisplayName>:

// current (line 491-495)
let display = format!("@{}", label);

// proposed
let display = format!("<@{}|{}>", user.id, label);

Outbound (ACP → Discord): strip the |Name suffix before sending to Discord, since Discord does not support Slack-style <@UID|Name> syntax — it only renders <@UID>:

// before sending reply to Discord
let out = regex::Regex::new(r"<@(\d+)\|[^>]+>")
    .unwrap()
    .replace_all(&reply, "<@$1>");

openab/src/discord.rs

Lines 481 to 501 in 6887db6

fn resolve_mentions(content: &str, bot_id: UserId, mentions: &[User]) -> String {
// 1. Strip the bot's own trigger mention
let mut out = content
.replace(&format!("<@{}>", bot_id), "")
.replace(&format!("<@!{}>", bot_id), "");
// 2. Resolve known user mentions to @DisplayName
for user in mentions {
if user.id == bot_id {
continue;
}
let label = user.global_name.as_deref().unwrap_or(&user.name);
let display = format!("@{}", label);
out = out
.replace(&format!("<@{}>", user.id), &display)
.replace(&format!("<@!{}>", user.id), &display);
}
// 3. Fallback: replace any remaining unresolved mentions
let out = ROLE_MENTION_RE.replace_all(&out, "@(role)");
let out = USER_MENTION_RE.replace_all(&out, "@(user)").to_string();
out.trim().to_string()
}

Investigation: How Other Projects Handle This

We investigated how OpenClaw and Hermes handle Discord mentions:

Bot own mention Other user mentions
OpenAB (current) Stripped <@UID>@DisplayName (UID lost)
Hermes (NousResearch/hermes-agent) Stripped Passed through as raw <@UID> — LLM sees Discord syntax but no human-readable name
OpenClaw (openclaw/openclaw) Stripped via stripPatterns: ["<@!?\\d+>"] All mentions stripped — LLM sees neither UID nor name
  • Hermes (gateway/platforms/discord.py line ~2992): only strips the bot own mention, leaves other <@UID> raw in message.content passed to the agent.
  • OpenClaw (extensions/discord/src/channel.ts): declares stripPatterns: () => ["<@!?\\d+>"] which removes all user mentions before the message reaches the LLM. Outbound mentions are reconstructed via a directory lookup (mentions.tsformatMention()).

Neither project preserves both UID and display name for the agent. The <@UID|DisplayName> format (borrowed from Slack native syntax) gives the LLM human-readable context while keeping the UID available for mention-back.

Use Case

When a user mentions another user (e.g. @AgentDealer) in a message to the bot, the agent receives @AgentDealer but has no way to mention that user back in its reply because the UID is lost. Preserving both UID and display name enables the agent to reference users in responses that Discord can render as proper mentions.

The outbound regex cleanup ensures Discord renders <@UID> as a clickable mention rather than showing the raw <@UID|Name> as literal text.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions