fix(gateway): reliable MEDIA file delivery — salvage 6 contributor fixes + punctuation/inline-code variants - #72170
Merged
Merged
Conversation
) MEDIA_TAG_CLEANUP_RE failed to match when a directive like [[as_document]] was concatenated directly to the file extension without whitespace (e.g., MEDIA:/home/user/report.xlsx[[as_document]]). This caused the file to be silently not delivered while the gateway reported success. Root cause: the lookahead character class [\s`",;:)\}\]|$) did not include [, so [[as_document]] immediately after the extension broke the lookahead assertion. Fix: add \[ to the lookahead class so directives can follow the path without whitespace. This is safe because [ is already stripped elsewhere in the same file via .replace("[[as_document]]", ""). Added regression tests covering: - Directive glued to extension (issue case) - Directive with whitespace (baseline) - Tag at end of string ($ anchor)
Models routinely present a file to the user with the delivery tag wrapped in
Markdown emphasis — `**MEDIA:/path.pptx**`, `*MEDIA:/path*`, `_MEDIA:/path_`.
MEDIA_TAG_CLEANUP_RE only tolerated a single leading/trailing quote or backtick
(`[`"']?`), and its closing lookahead set excluded `*` and `_`, so an
emphasis-wrapped tag never matched. The file was then silently never delivered
and the literal `MEDIA:/path` text leaked into the chat instead — the user sees
a path, not the attachment.
Allow a short run of emphasis/quote markers (`[`"'*_]{0,3}`) on both sides of
the tag and add `*`/`_` to the closing lookahead. Code-block, inline-code and
blockquote contexts are still neutralised earlier by `_mask_protected_spans`
(#35695), so documentation/example tags remain non-deliverable; the
absolute-path anchor still rejects relative paths; `_` inside a filename is
unaffected.
Adds regression coverage in TestExtractMedia for bold/italic/underscore
wrapping, mid-prose bold, emphasis-wrapped .html, underscore-in-filename, and
emphasis-wrapped relative-path rejection.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…xt (#68773) Two MEDIA: path tags emitted back-to-back without a separator merged into a single invalid path and were silently dropped. The same happened for extension-less tags. Root cause: both regexes in gateway/platforms/base.py used greedy quantifiers in their path class, causing adjacent tags to be absorbed. Fix: make both regexes non-greedy (add ? to quantifiers) and add MEDIA: to the trailing lookahead boundary set so the next MEDIA: keyword stops the current match cleanly.
When the same file is referenced multiple times in one message (common when the agent emits MEDIA tags both inline and in a summary footer), the platform adapter uploads/sends the same file twice — visible as duplicate attachments in Telegram, duplicate posts in Slack, etc. Set-based dedup on the expanded path, preserving first-occurrence order.
Outbound MEDIA video/document tags on the Discord non-streaming path were extracted (stripped from the visible text) but never delivered: no attachment, no error, and no dispatch log line (#66797). The open-handle discord.File plus singular file= form could race Discord's multipart encoder after an earlier image batch on the same channel and return a successful message carrying zero attachments. Fix _send_file_attachment (used by send_video/send_document/ send_image_file) to: - use a path-based discord.File via the plural files=[...] kwarg, the same pattern as the working send_multiple_images batch path; - pre-flight os.path.isfile and return a File not found result instead of raising deep inside discord.File; - fail loud when Discord accepts the message but attaches nothing, so the dispatch loop surfaces a warning rather than a silent drop; - add INFO dispatch logs (base non-image MEDIA fan-out, video send, and file attachment) so a MEDIA:.mp4 cannot vanish without a trace. Tests (tests/gateway/test_discord_send.py): - path-based files=[...] kwarg used, singular file= unset; - fail-loud when the returned message has no attachments; - missing file fails fast without resolving the channel; - forum-parent delivery routes through create_thread with files=[...]; - end-to-end image+video response routes the mp4 to send_video while images still batch. Fixes #66797
Surface a user-visible delivery notice when non-streaming media dispatch gets success=False after MEDIA tags were stripped, and validate forum starter-message attachments the same way as direct channel sends (#66797).
MEDIA_DELIVERY_EXTS in gateway/platforms/base.py omitted .3gp, causing MEDIA: tags with .3gp files to leak as plain text instead of being extracted for native video delivery. _VIDEO_EXTS in tools/send_message_tool.py and _MIGRATION_VIDEO_EXTS in the Feishu adapter omitted .webm, causing .webm files to be classified as documents instead of video on Telegram and other platforms. Both extensions are already present in every gateway-side _VIDEO_EXTS definition (run.py, kanban_watchers.py, weixin.py, base.py local). Closes #71621, Closes #71603
…inline-code wrapping Two remaining formatting variants that silently killed file delivery: - A trailing sentence period (MEDIA:/x/data.csv.) failed the boundary lookahead, so the tag neither extracted nor stripped. The period is now accepted as a boundary only when followed by whitespace/EOL, keeping multi-part extensions (.tar.gz) intact. - A whole tag wrapped in inline code (`MEDIA:/x/data.csv`) was masked as a prose example (#35695). Models routinely format paths as inline code, eating real deliveries. Inline-code tags now deliver when the path validates on disk; non-existent example paths stay masked and fenced code blocks remain fully masked. Adds a regression matrix covering both plus the salvaged contributor fixes (emphasis wrap, glued tags, glued [[as_document]], dedupe, unknown-extension and extensionless delivery).
… files= kwarg Sibling tests pinned the old singular file= handle form that #66797 replaced with path-based File via files=[...].
Contributor
૮ >ﻌ< ა ci reviewran on 5b90502 ℹ️ InfoDesktop E2E visual evidence · View test artifacts · View job1 visual diff. inline evidence upload failed. Failed to upload diff-665a0833239e-onboarding-overlay-diff.png with gh image (exit code 1): Error uploading /home/runner/work/_temp/e2e-evidence/diff-665a0833239e-onboarding-overlay-diff.png: step 0 (get upload token): uploadToken not found on repo page — do you have write access to NousResearch/hermes-agent? (or, if NousResearch enforces SAML SSO, authorize at https://github.com/orgs/NousResearch/sso) |
This was referenced Jul 26, 2026
teknium1
added a commit
that referenced
this pull request
Jul 26, 2026
… display strip Follow-up wave to #72170 resolving the remaining open MEDIA-delivery gaps: - #24032: add .kmz/.kml/.geojson/.gpx to MEDIA_DELIVERY_EXTS, and recover unknown-extension paths containing spaces via _match_extensionless_path — validation-gated forward extension across single spaces (bounded at 8 tokens, stops at newline / next MEDIA: keyword). The regex itself stays non-greedy and whitespace-bounded so the #68773 absorption bug class cannot return; the on-disk file check is the oracle. - #16434 (streaming half): _strip_media_tag_directives now uses the same mask-as-locator pattern as extract_media, so MEDIA tags inside fenced code blocks, inline-code examples, and JSON string values survive in streamed display text instead of being mangled. Display and delivery now agree on every protected-span rule. - Updated three stream_consumer display expectations that pinned the old inconsistent behavior (backtick/double-quote tags stripped from display while delivery never attempted them).
Contributor
randlee
pushed a commit
to randlee/hermes-agent
that referenced
this pull request
Aug 11, 2026
… display strip Follow-up wave to NousResearch#72170 resolving the remaining open MEDIA-delivery gaps: - NousResearch#24032: add .kmz/.kml/.geojson/.gpx to MEDIA_DELIVERY_EXTS, and recover unknown-extension paths containing spaces via _match_extensionless_path — validation-gated forward extension across single spaces (bounded at 8 tokens, stops at newline / next MEDIA: keyword). The regex itself stays non-greedy and whitespace-bounded so the NousResearch#68773 absorption bug class cannot return; the on-disk file check is the oracle. - NousResearch#16434 (streaming half): _strip_media_tag_directives now uses the same mask-as-locator pattern as extract_media, so MEDIA tags inside fenced code blocks, inline-code examples, and JSON string values survive in streamed display text instead of being mangled. Display and delivery now agree on every protected-span rule. - Updated three stream_consumer display expectations that pinned the old inconsistent behavior (backtick/double-quote tags stripped from display while delivery never attempted them).
prmartinow
pushed a commit
to prmartinow/hermes-agent
that referenced
this pull request
Aug 26, 2026
… display strip Follow-up wave to NousResearch#72170 resolving the remaining open MEDIA-delivery gaps: - NousResearch#24032: add .kmz/.kml/.geojson/.gpx to MEDIA_DELIVERY_EXTS, and recover unknown-extension paths containing spaces via _match_extensionless_path — validation-gated forward extension across single spaces (bounded at 8 tokens, stops at newline / next MEDIA: keyword). The regex itself stays non-greedy and whitespace-bounded so the NousResearch#68773 absorption bug class cannot return; the on-disk file check is the oracle. - NousResearch#16434 (streaming half): _strip_media_tag_directives now uses the same mask-as-locator pattern as extract_media, so MEDIA tags inside fenced code blocks, inline-code examples, and JSON string values survive in streamed display text instead of being mangled. Display and delivery now agree on every protected-span rule. - Updated three stream_consumer display expectations that pinned the old inconsistent behavior (backtick/double-quote tags stripped from display while delivery never attempted them).
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
Reliable
MEDIA:file delivery on all platforms, any extension — consolidates six open contributor fixes for silent attachment drops (formatting-variant regex misses on every platform + a Discord upload race) plus two new fixes, with contributor authorship preserved via cherry-pick.Reported symptom (Discord): CSV files never uploaded; PDFs delivered intermittently. Root causes verified live: the extraction regex missed common LLM formatting variants (tag stripped, file never sent, no error), and Discord's file-handle upload form raced the multipart encoder producing "successful" messages with zero attachments.
Changes
Salvaged (cherry-picked, original authorship kept):
gateway/platforms/base.py: glued[[as_document]]boundary (fix(gateway): allow [[as_document]] glued directly to MEDIA path #63644, @liuhao1024, fixes MEDIA_TAG_CLEANUP_RE fails to match when [[as_document]] is glued directly to the path — file silently not delivered #63632)gateway/platforms/base.py: Markdown-emphasis-wrapped tags**MEDIA:/x.csv**(fix(gateway): deliver MEDIA: tags wrapped in Markdown emphasis #45773, @Julientalbot)gateway/platforms/base.py: back-to-back tags on one line no longer absorb into one bogus path (fix(gateway): stop MEDIA tag regex from absorbing following tag or text (#68773) #68837, @webtecnica, fixes Agent fails to append separators after MEDIA tags, URLs, and file paths, causing cascading delivery and truncation failures #68773; drive-byprofiles.pychange dropped)gateway/platforms/base.py: dedupe identical tags — same file no longer uploads twice (fix(extract_media): dedupe identical MEDIA tags #29197, @StartupBros-com)plugins/platforms/discord/adapter.py+gateway/platforms/base.py: path-baseddiscord.Fileviafiles=[...], fail-loud when Discord returns a message with no attachments, user-visible delivery-failure notice (fix(discord): deliver MEDIA video attachments instead of silent drop #67054, @piyushbag, fixes [Bug]: Discord outbound MEDIA video attachments silently dropped (images in same response deliver fine) #66797)gateway/platforms/base.py,tools/send_message_tool.py,plugins/platforms/feishu/adapter.py:.3gp/.webmextension-set drift (fix(gateway,tools): add missing .3gp and .webm to video extension sets #71683, @Linux2010, fixes bug(gateway): .3gp missing from MEDIA_DELIVERY_EXTS — MEDIA tags not delivered #71621)New fixes on top:
MEDIA:/x/data.csv.now extracts (period accepted as boundary only before whitespace/EOL;.tar.gzstays intact)`MEDIA:/x/data.csv`delivers when the path validates on disk; non-existent example paths stay masked (Bug: extract_media() false-positives on example paths in quoted text / code blocks #35695 preserved), fenced code blocks always maskedfile=to pluralfiles=[...]expectationValidation
**MEDIA:/x.csv**/*…*/_…_`MEDIA:/x.csv`(real file)MEDIA:/x.csv.(sentence end)MEDIA:/a.csv MEDIA:/b.csv(one line)MEDIA:/x.xlsx[[as_document]].py/ extensionless real files