Skip to content

fix(discord): auto-convert markdown tables to bullet groups - #53284

Merged
kshitijk4poor merged 4 commits into
NousResearch:mainfrom
kshitijk4poor:salvage/45781-discord-table-bullets
Jun 26, 2026
Merged

fix(discord): auto-convert markdown tables to bullet groups#53284
kshitijk4poor merged 4 commits into
NousResearch:mainfrom
kshitijk4poor:salvage/45781-discord-table-bullets

Conversation

@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Summary

Discord doesn't render GFM pipe tables — raw pipe characters show as garbage text. This PR adds automatic table-to-bullet conversion in the Discord adapter's format_message, matching the pattern Telegram already uses, and extracts the shared table-detection primitives into gateway/platforms/helpers.py.

Salvage of #45781 (@yashiels). Closes #21168. Closes #45781.

Changes

  • gateway/platforms/helpers.py: Added TABLE_SEPARATOR_RE, is_table_row, split_markdown_table_row, _render_table_block, and convert_table_to_bullets — shared table-to-bullet conversion logic.
  • plugins/platforms/discord/adapter.py: format_message now calls convert_table_to_bullets() instead of returning content as-is. Import moved to top-level.
  • plugins/platforms/telegram/adapter.py: Replaced local _TABLE_SEPARATOR_RE, _is_table_row, _split_markdown_table_row with imports from shared helpers. Telegram-specific MarkdownV2 renderer stays local.
  • tests/gateway/test_table_helpers.py: 18 new tests for shared helpers.
  • tests/gateway/test_discord_format.py: 4 new tests for Discord formatting.

Salvage notes

  • PR was 1086 commits behind main. Cherry-picked all 3 contributor commits.
  • Resolved conflict: Telegram adapter migrated from gateway/platforms/telegram.py to plugins/platforms/telegram/adapter.py since the PR's base. Kept the chunk-indicator code that landed on main, applied the shared-import refactor on top.
  • Fixed minor issue: moved function-level import in Discord adapter to top-level (file already imports from gateway.platforms.helpers at line 105).
  • @yashiels's authorship preserved on all 3 commits.

Validation

Before After
Discord tables Raw pipe garbage Bullet groups
Tests 0 Discord format tests 22 new (18 helper + 4 Discord)
Existing Telegram tests 106 106 (all pass)
Ruff All checks passed

@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins platform/discord Discord bot adapter platform/telegram Telegram bot adapter P2 Medium — degraded but workaround exists labels Jun 26, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Salvage of #45781 (@yashiels) onto current main, authorship preserved. Same bullet-group mechanism as the earlier canonical #45781 (still open) and closed #53067. Not a duplicate — #45781 remains the canonical predecessor; maintainers should pick one to merge.

kshitijk4poor added a commit that referenced this pull request Jun 26, 2026
PR #53284 salvage (discord markdown table-to-bullet conversion; #21168)
yashiels and others added 4 commits June 27, 2026 03:47
Move table-detection regex, row-splitting, and table-to-bullet
conversion into gateway/platforms/helpers.py so both Discord and
Telegram adapters can share them.

Co-authored-by: Yashiel Sookdeo <yashiel@skyner.co.za>
Discord does not render GFM pipe tables — raw pipe characters display
as garbage text. format_message now rewrites tables into bold-heading +
bullet groups using the shared helpers.

Fixes NousResearch#21168

Co-authored-by: Yashiel Sookdeo <yashiel@skyner.co.za>
…pers.py

Replace local _TABLE_SEPARATOR_RE, _is_table_row, and
_split_markdown_table_row with imports from the shared module.
Telegram-specific rendering stays local.

Co-authored-by: Yashiel Sookdeo <yashiel@skyner.co.za>
The PR's original refactor commit only replaced the primitives (regex,
is_table_row, split_markdown_table_row) with shared imports but left the
verbatim-copied renderer (_render_table_block_for_telegram) and driver
(_wrap_markdown_tables) in place. Both are logic-identical to the shared
convert_table_to_bullets in gateway/platforms/helpers.py.

Replace both with a direct import alias. _TABLE_SEPARATOR_RE is still
imported separately because it's used by the rich-message routing logic
(lines 1024, 1044) to detect whether content contains tables.

Found by 3-agent parallel code-reuse review.
@kshitijk4poor
kshitijk4poor force-pushed the salvage/45781-discord-table-bullets branch from 0fc9456 to 97d0b84 Compare June 26, 2026 22:17
@kshitijk4poor
kshitijk4poor merged commit 6326d5c into NousResearch:main Jun 26, 2026
27 checks passed
@lkaino

lkaino commented Jun 27, 2026

Copy link
Copy Markdown

I tested this out and it does not look good at all, hopefully it is made into a table like layout at some point.

pai-scaffolde pushed a commit to Scaffolde/hermes-agent that referenced this pull request Jun 28, 2026
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
PR NousResearch#53284 salvage (discord markdown table-to-bullet conversion; NousResearch#21168)
habarmc1223-sudo pushed a commit to habarmc1223-sudo/hermes-agent-fluxmem that referenced this pull request Jul 8, 2026
PR NousResearch#53284 salvage (discord markdown table-to-bullet conversion; NousResearch#21168)
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
PR NousResearch#53284 salvage (discord markdown table-to-bullet conversion; NousResearch#21168)
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
PR NousResearch#53284 salvage (discord markdown table-to-bullet conversion; NousResearch#21168)
sam7894604 added a commit to sam7894604/hermes-agent that referenced this pull request Jul 22, 2026
LINE's text bubble renders zero Markdown, so a GFM pipe table from the model
landed in the chat as literal "| Item | Cost |" / "|------|" rows. Discord and
Telegram already solve this with the shared convert_table_to_bullets() helper
(gateway/platforms/helpers.py, added in PR NousResearch#53284) — LINE was simply never
wired into it.

Wire LINE into the same shared converter rather than growing a LINE-specific
table implementation. The call goes at the top of strip_markdown_preserving_urls(),
which every outbound send path already funnels through (postback reply,
_send_text_chunks, format_message, standalone send), so all of them are covered
by one hook.

Order matters: the converter runs BEFORE the existing code-fence un-fencing.
The shared converter deliberately skips fenced blocks, but once the fences are
stripped a table inside a code block would look like a real table and be wrongly
converted. Running it first preserves fence-skipping.

The two steps compose cleanly: the converter emits "**heading**" + "• field: value";
the existing strip removes the bold markers, and the "•" bullets pass through
_MD_BULLET_RE untouched (it only matches -/*/+ markers).

+3 tests (table → bullets with no raw pipes; fenced table left verbatim;
no-table behaviour unchanged). 213 LINE + table-helper tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sam7894604 added a commit to sam7894604/hermes-agent that referenced this pull request Jul 22, 2026
LINE's text bubble renders zero Markdown, so a GFM pipe table from the model
landed in the chat as literal "| Item | Cost |" / "|------|" rows. Discord and
Telegram already solve this with the shared convert_table_to_bullets() helper
(gateway/platforms/helpers.py, added in PR NousResearch#53284) — LINE was simply never
wired into it.

Wire LINE into the same shared converter rather than growing a LINE-specific
table implementation. The call goes at the top of strip_markdown_preserving_urls(),
which every outbound send path already funnels through (postback reply,
_send_text_chunks, format_message, standalone send), so all of them are covered
by one hook.

Order matters: the converter runs BEFORE the existing code-fence un-fencing.
The shared converter deliberately skips fenced blocks, but once the fences are
stripped a table inside a code block would look like a real table and be wrongly
converted. Running it first preserves fence-skipping.

The two steps compose cleanly: the converter emits "**heading**" + "• field: value";
the existing strip removes the bold markers, and the "•" bullets pass through
_MD_BULLET_RE untouched (it only matches -/*/+ markers).

+3 tests (table → bullets with no raw pipes; fenced table left verbatim;
no-table behaviour unchanged). 213 LINE + table-helper tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kshitijk4poor
kshitijk4poor deleted the salvage/45781-discord-table-bullets branch August 5, 2026 07:10
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
PR NousResearch#53284 salvage (discord markdown table-to-bullet conversion; NousResearch#21168)
sam7894604 added a commit to sam7894604/hermes-agent that referenced this pull request Aug 9, 2026
LINE's text bubble renders zero Markdown, so a GFM pipe table from the model
landed in the chat as literal "| Item | Cost |" / "|------|" rows. Discord and
Telegram already solve this with the shared convert_table_to_bullets() helper
(gateway/platforms/helpers.py, added in PR NousResearch#53284) — LINE was simply never
wired into it.

Wire LINE into the same shared converter rather than growing a LINE-specific
table implementation. The call goes at the top of strip_markdown_preserving_urls(),
which every outbound send path already funnels through (postback reply,
_send_text_chunks, format_message, standalone send), so all of them are covered
by one hook.

Order matters: the converter runs BEFORE the existing code-fence un-fencing.
The shared converter deliberately skips fenced blocks, but once the fences are
stripped a table inside a code block would look like a real table and be wrongly
converted. Running it first preserves fence-skipping.

The two steps compose cleanly: the converter emits "**heading**" + "• field: value";
the existing strip removes the bold markers, and the "•" bullets pass through
_MD_BULLET_RE untouched (it only matches -/*/+ markers).

+3 tests (table → bullets with no raw pipes; fenced table left verbatim;
no-table behaviour unchanged). 213 LINE + table-helper tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sam7894604 added a commit to sam7894604/hermes-agent that referenced this pull request Aug 11, 2026
LINE's text bubble renders zero Markdown, so a GFM pipe table from the model
landed in the chat as literal "| Item | Cost |" / "|------|" rows. Discord and
Telegram already solve this with the shared convert_table_to_bullets() helper
(gateway/platforms/helpers.py, added in PR NousResearch#53284) — LINE was simply never
wired into it.

Wire LINE into the same shared converter rather than growing a LINE-specific
table implementation. The call goes at the top of strip_markdown_preserving_urls(),
which every outbound send path already funnels through (postback reply,
_send_text_chunks, format_message, standalone send), so all of them are covered
by one hook.

Order matters: the converter runs BEFORE the existing code-fence un-fencing.
The shared converter deliberately skips fenced blocks, but once the fences are
stripped a table inside a code block would look like a real table and be wrongly
converted. Running it first preserves fence-skipping.

The two steps compose cleanly: the converter emits "**heading**" + "• field: value";
the existing strip removes the bold markers, and the "•" bullets pass through
_MD_BULLET_RE untouched (it only matches -/*/+ markers).

+3 tests (table → bullets with no raw pipes; fenced table left verbatim;
no-table behaviour unchanged). 213 LINE + table-helper tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sam7894604 added a commit to sam7894604/hermes-agent that referenced this pull request Sep 2, 2026
LINE's text bubble renders zero Markdown, so a GFM pipe table from the model
landed in the chat as literal "| Item | Cost |" / "|------|" rows. Discord and
Telegram already solve this with the shared convert_table_to_bullets() helper
(gateway/platforms/helpers.py, added in PR NousResearch#53284) — LINE was simply never
wired into it.

Wire LINE into the same shared converter rather than growing a LINE-specific
table implementation. The call goes at the top of strip_markdown_preserving_urls(),
which every outbound send path already funnels through (postback reply,
_send_text_chunks, format_message, standalone send), so all of them are covered
by one hook.

Order matters: the converter runs BEFORE the existing code-fence un-fencing.
The shared converter deliberately skips fenced blocks, but once the fences are
stripped a table inside a code block would look like a real table and be wrongly
converted. Running it first preserves fence-skipping.

The two steps compose cleanly: the converter emits "**heading**" + "• field: value";
the existing strip removes the bold markers, and the "•" bullets pass through
_MD_BULLET_RE untouched (it only matches -/*/+ markers).

+3 tests (table → bullets with no raw pipes; fenced table left verbatim;
no-table behaviour unchanged). 213 LINE + table-helper tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
melon-xf added a commit to melon-xf/hermes-agent that referenced this pull request Sep 3, 2026
PR NousResearch#53284 salvage (discord markdown table-to-bullet conversion; NousResearch#21168)
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/telegram Telegram bot adapter type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Discord markdown tables render as garbage — need auto code-fence wrapping

4 participants