feat(slack): make stripping the bot's own mention configurable (slack.strip_bot_mentions) - #27
Merged
Merged
Conversation
… visibility - Introduced `strip_bot_mentions` setting to allow toggling bot mention visibility in agent-readable Slack messages. - Default behavior (`true`) continues to remove the bot's own mention (`<@U_ID>`) before passing text to the agent. - Setting it to `false` retains the mention, rendered as `@BotName`, enabling distinction between explicit tags and thread-based wake-ups. - Extended `config.yaml` with `strip_bot_mentions` and environmental support (`SLACK_STR
Author
|
HERMES pr: NousResearch#83905 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Makes the deletion of the bot's own Slack mention optional, behind a new
slack.strip_bot_mentionsconfig field (defaulttrue— exactly today's behavior).The adapter deletes the bot's own
<@U…>token from the text before the agent reads it, and reports nothing in its place. That is fine while the adapter alone decides who gets an answer, but a thread keeps waking the bot after the first mention (_mentioned_threads, withthread_require_mentionoff by default), so every delivered turn then looks identical: the agent cannot tell "someone just tagged me" from "I was woken by thread routing".That distinction is what an agent needs to decide for itself whether a turn deserves an answer at all — a stay-silent contract can only be described to it if the two cases look different in the text. With
strip_bot_mentions: falsean explicit tag stays visible where the author put it, and its absence means the bot was woken by channel or thread routing. The asymmetry itself is the signal; no marker text is injected.The mention is rendered as
@BotName, the same shape_humanize_user_mentions()already gives mentions of other participants, so the agent reads an ordinary tag rather than a raw<@U…>id or a synthetic(directed at you)prefix. The name comes from_team_bot_names/_bot_display_name, both resolved at connect time — no extra Slack API call per message. If the display name is not resolved yet, the raw token is left in place rather than deleted.Routing is untouched:
is_mentioned,_mentioned_threads,require_mention,strict_mention,thread_require_mentionall keep their current meaning and are all evaluated before this. Command parsing is untouched too — it runs off the separatemention_strippedvariable, so@bot /statusand@bot !newdispatch identically in both states.Related Issue
None — needed for our Slack deployment.
Type of Change
Changes Made
plugins/platforms/slack/adapter.py:_slack_strip_bot_mentions()next to its four siblings (_slack_require_mention,_slack_strict_mention,_slack_ignore_other_user_mentions,_slack_thread_require_mention) — same resolution order,config.extra→SLACK_STRIP_BOT_MENTIONS→ defaulttrue, same explicit-false parsing.plugins/platforms/slack/adapter.py:_own_bot_name()(the display name already in memory, workspace-scoped) and_render_own_mention()(<@U123>/<@U123|label>→@BotName, in place, callablere.subreplacement so a backslash in a display name cannot raisere.error).plugins/platforms/slack/adapter.py: the strip site in_handle_slack_message()is now gated — strip, or render in place.plugins/platforms/slack/adapter.py: thread history follows the same policy, so past turns don't read as "nobody ever tagged me" —_render_message_text()takesstrip_bot_mention/bot_name,_format_thread_context()passes the resolved flag through both of its strip sites.plugins/platforms/slack/adapter.py: Block Kit dedupe is fed the pre-render text, andbot_uidonly while stripping. The dedupe deletes<@bot_uid>from both sides to bridge the strip below it; with the mention kept, comparing a rendered@BotNameagainst the block's raw token never matches (every mentioned thread message would be appended twice, raw token included), and deleting the token from both sides reduces a mention-only block to the empty string, which reads as new content and appends the mention a second time. Both were reproduced directly and are covered by tests.plugins/platforms/slack/adapter.py:_fetch_thread_parent_text()forwards the caller'sstrip_bot_mentioninto the render on a cache miss. That path is the root-mention wake check (fix(slack): route replies from mentioned thread parents NousResearch/hermes-agent#24848), which greps the parent for the raw<@id>— previously the render deleted the token unconditionally, so on a cold cache the check could never match, in either flag state.hermes_cli/config_defaults.py:slack.strip_bot_mentions: Truewith a comment.plugins/platforms/slack/plugin.yaml:optional_enventry for theSLACK_STRIP_BOT_MENTIONSmirror.website/docs/user-guide/messaging/slack.md,website/docs/reference/environment-variables.md: the new key, its default, and what it explicitly does not change.tests/gateway/test_slack_strip_bot_mentions.py: 52 tests, most parameterized over both flag states.How to Test
@hermes what's up?still arrives aswhat's up?, and no existing test needed editing.config.yaml:hey @hermes lookarrives ashey @hermes look— tag in place, not a raw<@U…>, not moved to the front.@hermes /statusand@hermes !newstill dispatch as/status//new, in both states.@hermesalone) nor a message Slack mirrors into arich_textblock.scripts/run_tests.sh tests/gateway/test_slack_strip_bot_mentions.py tests/gateway/test_slack.py tests/gateway/test_slack_mention.py tests/gateway/test_slack_mention_humanization.py tests/test_slack_thread_require_mention.py tests/gateway/test_config.py— 388 passed.scripts/run_tests.sh tests/gateway/test_slack_block_kit.py tests/gateway/test_slack_block_kit_adapter.py tests/gateway/test_slack_ignore_other_user_mentions.py tests/gateway/test_slack_require_mention_channels.py tests/gateway/test_slack_peer_agent_smoke.py— 41 passed.scripts/run_tests.sh tests/gateway/— 5009 passed (6 pre-existing environment failures on macOS: the optional XML dependency for wecom, Linux-only abstract sockets, api_server health/readiness).Checklist
Code
Documentation & Housekeeping
cli-config.yaml.example— N/A, the Slack block lives inhermes_cli/config_defaults.py, which is updatedCONTRIBUTING.md/AGENTS.md— N/A, no architecture or workflow changeNotes
_build_identity_prompt) is deliberately left as it is in both states. It says the routing mention "may have been stripped", which is modal and stays true either way; whether a message deserves an answer is stated in the operator's own prompt, not here.@bot 2,@bot always,@bot y) no longer exact-matches the clarify-choice / slash-confirm / pending-/updatebranches ingateway/run.py. Those are unreachable in our deployment (theclarifytoolset is disabled,approvals.mode: "off",/updateunused), and the same exposure exists for any approach that keeps the mention in the text.section/header/contextblocks and legacyattachmentsare deduped against the post-strip text, so an app post whose block mirrors a flat text containing the bot token is appended twice on the default too.