feat(slack): render markdown tables as column-padded monospace blocks (#26947) - #26950
feat(slack): render markdown tables as column-padded monospace blocks (#26947)#26950xxxigm wants to merge 3 commits into
Conversation
Slack's mrkdwn has no native renderer for GitHub-flavored markdown tables, and Block Kit has no public ``table`` block, so the pipe characters and dashed separators that Hermes emits today survive as literal text — columns smear together and the message is unreadable. This patch teaches the Slack adapter to detect GFM tables in outbound content and convert each one into a column-padded triple-backtick block before the message reaches ``chat.postMessage``. Triple-backtick text is rendered as fixed-width preformatted code by Slack, so the columns line up the way the user intended. The conversion runs as ``step 0`` of ``format_message`` (before existing fenced-code protection) so the generated code fence is then treated like any other code block and survives the later mrkdwn passes intact. Detection requirements (deliberately strict, to avoid false positives on prose that happens to contain pipes): * A "table" needs at least two rows. * The second row must be a separator row of dashes with optional ``:`` alignment markers and ``>=3`` dashes per cell. * The header must have ``>=2`` columns (single-column "tables" are almost always bulleted lists with piped items, not real tables). * Tables that fall inside an existing fenced code block are left untouched — we never reformat a user's code sample. Alignment hints (``:---``, ``---:``, ``:---:``) are honoured by left- / right- / center-padding the generated cells. Ragged rows are padded to the column count. Backslash-escaped pipes inside cells round-trip correctly. Operators who already post-process agent output for Slack can opt out via ``SLACK_RENDER_MARKDOWN_TABLES=false`` without redeploying code. Closes NousResearch#26947
26 focused tests against the new converter and its integration into ``format_message``: * Basic shape — minimal repro from NousResearch#26947 round-trips with a code fence, correct separator row, preserved body. * Column padding — cells are padded to the widest column. * Alignment hints — ``:---:`` centers, ``---:`` right-aligns. * Headerless / no-leading-pipe form is supported (valid GFM variant). * Backslash-escaped pipes inside cells survive as literal pipes. * Ragged rows are padded to the header's column count. * Empty-body tables (header + separator only) still render. * Prose with pipes is left untouched. * One-column ``tables`` are rejected (almost always false positives). * Plain text with no pipes short-circuits. * Empty / ``None`` inputs pass through. * Tables inside an existing fenced code block come back byte-identical. * A row of pipes without a proper dash separator is not a table. * Two consecutive tables are each converted independently. * Larger messages convert in place without disturbing surrounding text. * ``format_message`` invokes the converter and leaves the generated fence un-mangled by later mrkdwn passes (bold inside a cell stays literal because the code fence protects it). * Non-table content is byte-identical to the pre-fix path. * Off-switch via ``SLACK_RENDER_MARKDOWN_TABLES`` accepts ``false`` / ``0`` / ``no`` / ``off`` (case-insensitive); default, empty, and garbage values all leave the feature on.
Reference table for the new opt-out env var introduced alongside the markdown-table → monospace conversion for Slack (NousResearch#26947).
|
Duplicate of #16648 (which supersedes #15854) — same monospace code block approach for Slack markdown tables. Also note that the issue this fixes (#26947) was already triaged as a duplicate of #18918. Related PRs in this space: #15854 (original monospace approach), #16648 (monospace + CJK alignment, supersedes #15854), #24267 (closed as dup of #15854), #18920 (Block Kit alternative), #8554 (broader Block Kit migration). |
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
|
this would be really nice with google chat as well. |
|
Superseded by #56102 (#56102), a broader opt-in Slack Block Kit renderer that includes native table blocks (per-column alignment, inline-formatted cells, monospace fallback over Slack's limits) and closes #18918. This was one of several independent fixes for the same area — thanks for contributing it. |
What does this PR do?
Teaches the Slack adapter to render markdown tables as actual aligned tables instead of leaving raw pipe characters as plain text.
Issue #26947 reported that when Hermes sends a markdown table to Slack (e.g.
| col1 | col2 |), the pipes and dashed separator survive as literal characters and the columns smear together. The root cause is that:mrkdwnhas no native renderer for GitHub-flavored markdown tables.tableblock either (the issue's "Block Kit table" is a misnomer — even Slack's own docs only suggest preformatted text as the workaround).The universally-compatible fix used by every other agent that handles this is to detect markdown tables on the way out and convert them to a column-padded triple-backtick block. Triple-backtick text is rendered as fixed-width preformatted code by Slack, so the columns line up the way the user intended.
This PR implements that, inline in
format_message:The conversion runs as
step 0offormat_message— before the existing fenced-code-block protection pass — so the generated code fence is then treated like any other code block and survives later mrkdwn rewrites (bold, italics, link conversion, header conversion, etc.) intact.Related Issue
Closes #26947
Type of Change
Changes Made
gateway/platforms/slack.py— add module-level_convert_markdown_tables_to_monospace(plus helpers_split_md_table_row,_parse_md_alignments,_pad_cell,_render_md_table_as_monospace) and an env-driven toggle_markdown_tables_enabled. Wire the converter intoformat_messageas step 0 so the generated code fences are then protected by the existing fenced-block placeholder pass. No existing send path is touched; tables that don't match the strict separator-row regex are passed through byte-identical.tests/gateway/test_slack_markdown_tables.py— 26 new regression tests covering basic shape, column padding, alignment hints (:---:/---:), headerless/no-leading-pipe form, escaped pipes, ragged rows, empty-body tables, prose with pipes, single-column rejection, no-pipe short-circuit, empty/Noneinputs, fenced-block exclusion (we never reformat a user's code sample), pipe rows without a valid separator, two consecutive tables, in-place conversion inside a larger message, integration with the rest offormat_message(bold/italics outside the table still convert, fence inside the table protects literals), no-table content byte-identity, and the off-switch (case-insensitivefalse/0/no/off; default / empty / garbage value → on).website/docs/reference/environment-variables.md— documentSLACK_RENDER_MARKDOWN_TABLESalongside the other Slack toggles.Detection rules (deliberately strict)
To avoid false positives on prose that happens to contain pipes:
:alignment markers) and>=3dashes per cell.>=2columns (single-column "tables" are almost always bulleted lists with piped items, not real tables).Alignment hints (
:---,---:,:---:) are honoured by left-/right-/center-padding the generated cells. Ragged rows are padded to the column count. Backslash-escaped pipes inside cells survive as literal pipes.Opt-out
Operators who already post-process agent output for Slack can disable the feature with
SLACK_RENDER_MARKDOWN_TABLES=false(also accepts0/no/off, case-insensitive). Default is on — most users want their tables to actually render.How to Test
Expected output of step 3:
Posted into Slack with
mrkdwn=True(the existing send path), the bold heading renders as bold and the code block renders as an aligned monospace table — exactly the "real Slack table" the issue asked for.