fix(slack): extract Block Kit and attachment text in thread context and mention detection - #69316
Merged
Conversation
Closes #52387 Slack messages can carry the bot @mention only inside Block Kit `blocks` (a `rich_text` section with a `user` element), with the flat top-level `text` containing just a fallback string. Both mention gates read only the flat `text`: - the `allow_bots: mentions` bot-message filter, and - the `is_mentioned` channel router (`routing_text = original_text`) so such messages were silently dropped — `allow_bots: mentions` was effectively non-functional for Block-Kit senders, and the same blind spot hit `require_mention` / `strict_mention`. Add `_collect_slack_block_mentions()` (walks blocks, recovers `<@uid>` tokens from non-quoted rich-text `user` elements) and `_slack_mention_detection_text()` (flat text + recovered mentions). Both gates now use the merged detection text. Mentions nested in `rich_text_quote` are deliberately ignored, preserving the existing contract that quoted/forwarded content can't trick the bot. Also emit a debug line when a bot message is dropped so silent drops are diagnosable. Tests: 4 new cases in tests/gateway/test_slack_mention.py (Block-Kit mention recovered, flat-text passthrough, no-mention, quoted-mention ignored). 275 slack tests pass.
`_fetch_thread_context` and `_fetch_thread_parent_text` only read each message's plain `text` field, so messages posted by apps (Alertmanager, Grafana, PagerDuty, CI bots) — which carry their content in legacy `attachments` or Block Kit `blocks` with an empty `text` — were dropped entirely. When such a message *starts* a thread (e.g. an alert), a bot mentioned mid-thread to investigate sees an empty thread and can only ask "what should I investigate?". Fall back to the existing `_extract_text_from_slack_blocks` and a new `_extract_text_from_slack_attachments` helper when `text` is empty, so app-posted alerts and notifications are visible in fetched thread history. Adds TestThreadContextAppMessages (attachment-only, blocks-only, and empty-message cases).
Bot-posted alerts (Honeycomb, PagerDuty, Datadog, GitHub bot, etc.) carry
their actionable content — section text, button URLs — in Block Kit
blocks, while the plain text field holds only the alert title.
_fetch_thread_context and _fetch_thread_parent_text only read
msg.get('text'), so that content never reached the agent.
Add a _render_message_text helper that merges top-level text with
readable block content, section/header/context text, actionable URLs,
and (folded in from #61261 during conflict resolution) legacy
attachment fields, and use it for thread-context and parent-text
rendering.
Salvaged from #29541.
Contributor
૮ >ﻌ< ა ci reviewrunning on 5906b80 CI timingsCI timings · View jobWall time 7m29s vs 7m46s (-3.6%). 9 job(s) slower, 11 faster,
|
This was referenced Jul 22, 2026
7 tasks
19 tasks
chenwei791129
added a commit
to chenwei791129/hermes-agent
that referenced
this pull request
Aug 16, 2026
…ments NousResearch#52387 was fixed for the carrier Slack's WYSIWYG composer produces — a rich_text tree with a structured `user` element. Two other carriers are still dropped, so a bot that explicitly @-mentions the gateway stays invisible to the allow_bots="mentions" gate (adapter.py:5329) and to is_mentioned routing (adapter.py:5603). Root cause, two independent gaps: 1. `_collect_slack_block_mentions` recurses only through ("elements", "element") and appends only for `type == "user"` nodes. A section/header/context block carries its content under "text" (a dict) or "fields" (a list), neither of which is walked; and a hand-built app writes the mention as a raw <@uid> substring, so there is no `user` node to match even once the subtree is entered. 2. Detection returns early when `event["blocks"]` is falsy and never consults `attachments`, so a mention living in an attachment field or in attachment-nested blocks is invisible. NousResearch#69316 added `_extract_text_from_slack_attachments` for exactly these apps (Alertmanager, Grafana, PagerDuty, CI) but applied it to display only. The walker also descends "text"/"fields" and harvests raw tokens from string values; a new `_collect_slack_attachment_mentions` covers the legacy carrier including attachment-nested blocks. A shared `_SLACK_USER_MENTION_RE` normalizes the labelled `<@u123|alice>` form to the bare token the gates compare against, with a deliberately permissive ID class: the gates substring-compare against whatever auth.test returned, so a narrower class would silently drop the mention this recovers. Extraction is factored into `_extract_mention_tokens`, so the carve-outs below live in one place and apply to every carrier. Recovered mentions are returned as a list by `_slack_recovered_mentions` rather than spliced into the routing text. Splicing would corrupt the two other consumers of that text: `_slack_message_addressed_to_other_user` reads its *first* token — and with an empty top-level text (the alert-bot shape this fixes) the appended tail becomes that token, so the message is dropped as "addressed to someone else" — while user-configured wake-word regexes are matched with `.search`, so an anchored pattern like `^hey hermes$` stops matching the moment a tail is appended. The gates now consume `_slack_event_mentions_bot` / `_slack_mention_gate_inputs`, which keep the routing text byte-identical to `event["text"]` and report recovered mentions through `is_mentioned` instead. The quote carve-out from NousResearch#52390 is preserved and extended to the carriers the structured `rich_text_quote` check cannot see: - a leading mrkdwn blockquote marker (`>` / the escaped `>`) in any scanned string, so a peer app quoting an earlier request as context does not re-summon the bot; - attachments flagged `is_msg_unfurl` or `is_share`, mirroring the skip the agent-text path already performs at adapter.py:5483, so pasting a permalink to an old bot request does not wake it; - `fallback` is not scanned at all — Slack never renders it, so a mention living only there is invisible in the channel and notifies nobody. Defensive handling is per-attachment rather than around the whole loop: one malformed sibling now skips itself instead of discarding the genuine mentions already collected, which would otherwise reintroduce the exact silent drop this commit fixes. The thread-parent wake check (NousResearch#24848) is migrated too. Its cached branch returned the raw `msg["text"]` — empty for app-authored parents — so a plain follow-up reply in an alert thread was dropped after a restart. Tests: 28 new cases in tests/gateway/test_slack_mention.py (27 -> 55) covering all seven carriers, the labelled form, dedupe, six malformed payloads, each carve-out, and the two routing-text regressions. The gating simulation and the new gate cases call the production predicates directly, so the tests cannot pass while the real gate diverges. They fail on unmodified main and pass with the fix. All 27 tests/gateway/test_slack*.py files pass (337 tests).
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.
Summary
Slack messages from apps/bots (Grafana, Datadog, Alertmanager, CI) carry their content in Block Kit
blocksor legacyattachmentswith empty top-leveltext— the adapter silently dropped that content in thread-context fetch and mention gating. This PR makes both paths read blocks and attachments.Fixes #68138, #52387, #33469.
Changes
plugins/platforms/slack/adapter.py:_render_message_text(msg)— unified helper merging top-level text, rich-text blocks, section/header/context text, legacy attachment fields (pretext/title/text/fields/nested blocks/fallback), and button URLs; used by_fetch_thread_contextand_fetch_thread_parent_text._slack_mention_detection_text(event)— recovers<@UID>mentions authored only inside non-quoted Block Kit blocks; applied to theallow_bots=mentionsbot filter and the channel-routingis_mentionedcheck. Quoted/forwarded mentions deliberately do NOT count (response-hijack safety).Note: the live-message handler already extracted blocks/attachments on main — only thread-context, parent-text, and the mention gates were still broken.
Credits
Salvaged with authorship preserved: #29541 (@bpross, earliest), #61261 (@xcompass), #52390 (@Bartok9).
Supersedes #68160, #52404, #52219, #33493, #32022, #30765.
Validation
scripts/run_tests.sh tests/gateway/ -q -k slackInfographic