Skip to content

feat(feishu): send GFM tables via Schema 2.0 interactive card - #35781

Open
unstoppablesssss wants to merge 4 commits into
NousResearch:mainfrom
unstoppablesssss:main
Open

feat(feishu): send GFM tables via Schema 2.0 interactive card#35781
unstoppablesssss wants to merge 4 commits into
NousResearch:mainfrom
unstoppablesssss:main

Conversation

@unstoppablesssss

Copy link
Copy Markdown

Problem

Feishu post-type md elements do not render GFM tables. Previously, _build_outbound_payload detected tables and force-downgraded the entire message to plain text, losing all markdown formatting (headings, bold, links, code blocks).

Solution

When GFM tables are detected, send a Schema 2.0 interactive card (msg_type="interactive") instead. The JSON 2.0 tag: markdown component supports CommonMark tables natively, preserving all surrounding formatting.

Changes

  • New _build_interactive_card_payload(content) — builds a Schema 2.0 card with header + body.elements
  • Modified _build_outbound_payload() — returns ("interactive", card_payload) when tables are detected, instead of force-text
  • Long content is split into multiple tag: markdown elements (~4K chars each) with newline-aware breaks

Testing

Verified with uv run python -c '...':

  • _MARKDOWN_TABLE_RE.search(content) matches correctly
  • Card payload has "schema": "2.0" and correct body.elements structure
  • Sent via hermes send to Feishu — table renders natively in Schema 2.0 card
  • Telemetry relay: confirmed via gateway log

Feishu post-type 'md' elements do not render GFM tables. Previously,
_build_outbound_payload detected tables and forced a plain-text fallback,
which lost ALL markdown formatting (headings, bold, links, etc.).

Now _convert_gfm_tables_to_list() runs in send() and edit_message() BEFORE
the outbound payload is built, converting GFM tables to bullet-list items:
- Header row: - **col1** · **col2**
- Data rows:  - **col1** val1 · **col2** val2

Tables inside fenced code blocks are left untouched.
After conversion, _MARKDOWN_TABLE_RE no longer matches, so the message
goes through the normal post (md) pipeline with all formatting preserved.
The post-type 'md' renderer does not support GFM tables. Previously the
adapter detected tables and force-downgraded the whole message to plain
text (losing all formatting).

Now _build_outbound_payload returns msg_type='interactive' with a
Schema 2.0 interactive card payload when GFM tables are detected.
The JSON 2.0 tag:markdown component renders CommonMark tables natively,
preserving surrounding formatting.
@unstoppablesssss
unstoppablesssss requested a review from a team May 31, 2026 08:42
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery platform/feishu Feishu / Lark adapter labels May 31, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Duplicate of #33310 (and part of the saturated Feishu markdown rendering cluster: #23861, #32488, #27469, #12114). Same approach: Schema 2.0 interactive card with tag: markdown for GFM tables. Also note: this PR includes unrelated files (state.db, hermes_cli/tools_config.py, web/package-lock.json) that should be removed.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing a real Feishu formatting limitation. Current main still sends detected tables as plain text at plugins/platforms/feishu/adapter.py:4524-4534, but this PR needs substantial salvage before it can be evaluated on current code.

Problems

  • The changed adapter path was moved to plugins/platforms/feishu/adapter.py by 5600105478ffde29d7566b45421b100eaa29c4ef; gateway/platforms/feishu.py is no longer present on current main.
  • gateway/platforms/feishu.py:212 splits content into multiple elements of one card. The related #33310 discussion reports a five-table limit per card, so this does not handle messages with more tables.
  • gateway/platforms/feishu.py:4365 routes to interactive, but current equivalent fallback handling only covers post failures at plugins/platforms/feishu/adapter.py:1913-1936 and :1963-1971.
  • No Feishu tests are included. The diff also contains unrelated cua-driver proxy logic plus state.db and web/package-lock.json, matching @alt-glitch's earlier note.

Suggested changes

  • Port the focused implementation to the bundled plugin, handle card-limit splitting and interactive-to-text fallback for send and edit, and add focused regression tests.
  • Remove the unrelated files from the salvage.

This is an automated hermes-sweeper review.

},
"body": {
"elements": [
{"tag": "markdown", "content": seg} for seg in segments

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This creates several markdown elements in one card, but it never splits into multiple cards. The related #33310 discussion records that Feishu rejects a card with more than five tables even when the tables are in separate elements; please enforce the per-card limit and return multiple outbound messages.

if _MARKDOWN_TABLE_RE.search(content):
text_payload = {"text": content}
return "text", json.dumps(text_payload, ensure_ascii=False)
return "interactive", _build_interactive_card_payload(content)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Routing tables to interactive also needs an interactive-to-text fallback. The existing send/edit fallbacks only recognize rejected post payloads, so a card-permission or schema rejection would now fail delivery where this path previously sent visible plain text.

# internal curl downloads go through it. GitHub Releases via a proxy
# is dramatically more reliable than a direct connection on this network.
env = os.environ.copy()
proxy = "http://127.0.0.1:7897"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hard-coded local proxy probe is unrelated to Feishu table rendering and changes cua-driver installation behavior for every user. Please remove it from this PR and submit it separately if it is independently needed.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 13, 2026
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 P3 Low — cosmetic, nice to have platform/feishu Feishu / Lark adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants