Skip to content

fix(gateway): reliable MEDIA file delivery — salvage 6 contributor fixes + punctuation/inline-code variants - #72170

Merged
teknium1 merged 10 commits into
mainfrom
hermes/hermes-86086c99
Jul 26, 2026
Merged

fix(gateway): reliable MEDIA file delivery — salvage 6 contributor fixes + punctuation/inline-code variants#72170
teknium1 merged 10 commits into
mainfrom
hermes/hermes-86086c99

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

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):

New fixes on top:

  • Sentence-final punctuation: MEDIA:/x/data.csv. now extracts (period accepted as boundary only before whitespace/EOL; .tar.gz stays intact)
  • Inline-code-wrapped tags: `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 masked
  • Sibling Discord tests updated from singular file= to plural files=[...] expectation

Validation

Scenario Before After
**MEDIA:/x.csv** / *…* / _…_ silent drop delivered
`MEDIA:/x.csv` (real file) silent drop delivered
MEDIA:/x.csv. (sentence end) silent drop delivered
MEDIA:/a.csv MEDIA:/b.csv (one line) both dropped both delivered
MEDIA:/x.xlsx[[as_document]] silent drop delivered
Discord PDF/video upload race intermittent zero-attachment "success" path-based upload + fail-loud + user notice
.py / extensionless real files delivered (unchanged, #36060) delivered
Duplicate tags in one response double upload single upload
Non-existent / denylisted paths left visible left visible (unchanged)
  • 18-case live E2E matrix against real files on disk: 18/18
  • Targeted suites: 518 passed, 0 failed (platform_base, discord_send, send_image_file, media_tag_* , media_extraction, post_stream, tts_media_routing, background_command, media_delivery_validation, discord_document_handling)
  • send_message tool suites: 184 passed, 0 failed

liuhao1024 and others added 10 commits July 26, 2026 12:24
)

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=[...].
@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on 5b90502

ℹ️ Info

Desktop E2E visual evidence · View test artifacts · View job

1 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)

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).
@webtecnica

Copy link
Copy Markdown
Contributor

Closing — this fix was absorbed as part of the MEDIA delivery hardening salvaged by @teknium1 in #72170 with authorship preserved. The punctuation variant and inline-code fixes were cherry-picked onto main as part of the broader MEDIA cleanup cluster.

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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins P2 Medium — degraded but workaround exists platform/discord Discord bot adapter platform/feishu Feishu / Lark adapter sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

8 participants