diff --git a/docs/discord.md b/docs/discord.md index be3e0c5..f89a3b3 100644 --- a/docs/discord.md +++ b/docs/discord.md @@ -127,6 +127,16 @@ Empty (default) = any bot can pass through (subject to the mode check). Role mentions are ignored because they are shared across bots and cause false positives in multi-bot setups. This is intentional since v0.7.8-beta.3 (#420, #440). +### User mention UIDs + +When a user mentions another user (e.g. `@SomeUser`) in a message to the bot, the raw Discord mention `<@UID>` is preserved in the prompt sent to the LLM. This means: + +- The LLM can copy `<@UID>` into its reply to produce a clickable Discord mention +- The bot's own mention is stripped (so the bot doesn't see itself being triggered) +- Role mentions are replaced with `@(role)` placeholder + +To help the LLM know who each UID refers to, provide a UID→name mapping via system prompt or context entry (see [Multi-Bot Setup](#multi-bot-setup) below). + --- ## Thread Behavior @@ -172,6 +182,22 @@ To enable bots to collaborate (e.g. code review → deploy handoff): allow_bot_messages = "mentions" ``` +### Ice-breaking: teaching bots who's in the room + +Since user mentions are preserved as raw `<@UID>`, bots need a UID→name mapping to know who is who. Add an ice-breaking greeting to each bot's system prompt or context entry: + +``` +We have 3 participants in this room: + +MY_NICIKNAME <@MY_NAME> +BOT1_NICKNAME <@BOT1> +BOT2_NICKNAME <@BOT2> + +Always use <@UID> format to mention someone in your messages. +``` + +This lets each bot build the mapping in its own context from the start and correctly mention others using `<@UID>`. + See [multi-agent.md](multi-agent.md) for detailed examples. --- diff --git a/src/discord.rs b/src/discord.rs index d935550..e4eb4a7 100644 --- a/src/discord.rs +++ b/src/discord.rs @@ -10,7 +10,6 @@ use serenity::http::Http; use serenity::model::channel::{AutoArchiveDuration, Message, ReactionType}; use serenity::model::gateway::Ready; use serenity::model::id::{ChannelId, MessageId, UserId}; -use serenity::model::user::User; use serenity::prelude::*; use std::collections::{HashMap, HashSet}; use std::sync::{Arc, OnceLock}; @@ -321,11 +320,7 @@ impl EventHandler for Handler { return; } - let prompt = if is_mentioned { - resolve_mentions(&msg.content, bot_id, &msg.mentions) - } else { - msg.content.trim().to_string() - }; + let prompt = resolve_mentions(&msg.content, bot_id); // No text and no attachments → skip if prompt.is_empty() && msg.attachments.is_empty() { @@ -474,28 +469,14 @@ async fn get_or_create_thread( static ROLE_MENTION_RE: LazyLock = LazyLock::new(|| { regex::Regex::new(r"<@&\d+>").unwrap() }); -static USER_MENTION_RE: LazyLock = LazyLock::new(|| { - regex::Regex::new(r"<@!?\d+>").unwrap() -}); -fn resolve_mentions(content: &str, bot_id: UserId, mentions: &[User]) -> String { +fn resolve_mentions(content: &str, bot_id: UserId) -> String { // 1. Strip the bot's own trigger mention - let mut out = content + let 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(); + // 2. Other user mentions: keep <@UID> as-is so the LLM can mention back + // 3. Fallback: replace role mentions only (user mentions are preserved) + let out = ROLE_MENTION_RE.replace_all(&out, "@(role)").to_string(); out.trim().to_string() }