Skip to content

fix(feishu): render markdown tables as Schema 2.0 interactive cards - #29630

Open
1040097629 wants to merge 1 commit into
NousResearch:mainfrom
1040097629:feishu-table-schema2.0
Open

fix(feishu): render markdown tables as Schema 2.0 interactive cards#29630
1040097629 wants to merge 1 commit into
NousResearch:mainfrom
1040097629:feishu-table-schema2.0

Conversation

@1040097629

Copy link
Copy Markdown

Problem

Feishu (Lark) post/tag:md renderer cannot parse markdown tables. Sending table content as post causes a blank message on the client. The previous workaround downgraded the entire message to plain text (msg_type: text), losing ALL formatting.

Additionally, Feishu interactive cards without "schema": "2.0" fall back to Schema 1.0, which has severely limited markdown support (no headings, blockquotes, or inline code).

Solution

When a message contains a markdown table, render it as a Schema 2.0 interactive card (white background, full width) with pipe-separated headers and data rows. Non-table content renders natively via the card's tag:markdown element (headings, blockquotes, inline code, code blocks all work in Schema 2.0).

Changes

  • _render_table_as_md(): parse markdown tables into pipe-separated text with plain-text headers (no bold - card markdown does not render bold when | pipe chars are on the same line)
  • _build_content_card_payload(): wrap content in Schema 2.0 interactive card with wide_screen_mode enabled
  • _build_outbound_payload(): tables - interactive card; markdown hints - post/tag:md (unchanged); plain text - text (unchanged)
  • send(): fallback chain interactive - post/md - text if API rejects
  • _TABLE_SEPARATOR_RE: module-level regex for detecting table separator rows

Testing

All 198 existing Feishu adapter tests continue to pass. Tested interactively on Feishu DM with messages containing tables, headings, blockquotes, inline code, code blocks, bold, links, and lists.

Problem
- Feishu's post/tag:md renderer cannot parse markdown tables; causes blank message
- Previous workaround downgraded entire message to plain text (msg_type: text)
- Schema 1.0 cards lack markdown support for headings, blockquotes, inline code

Solution
- Tables: render as Schema 2.0 interactive card (white bg, full width)
- Pipe-separated headers + data rows (plain text, no **bold** in pipes)
- Non-table content renders natively via card tag:markdown
- Fallback chain: interactive -> post/md -> text on API error

Changes
- _render_table_as_md(): parse tables into pipe-separated text
- _build_content_card_payload(): Schema 2.0 interactive card wrapper
- _build_outbound_payload(): tables -> interactive; rest unchanged
- send(): fallback for interactive card API failures
- _TABLE_SEPARATOR_RE: module-level regex for table separator rows
@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery platform/feishu Feishu / Lark adapter duplicate This issue or pull request already exists P2 Medium — degraded but workaround exists labels May 21, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Duplicate of #12114 — another Feishu markdown table → interactive card Schema 2.0 PR. Part of the massive 70+ PR cluster tracked by #27469.

@1040097629

Copy link
Copy Markdown
Author

Thanks for the review, but I don't believe this is a duplicate — the two PRs take fundamentally different approaches:

PR #12114 renders tables using Feishu's native tag:"table" component. In our testing with the lark-oapi 1.5.3 SDK, this component was rejected by the API (errors 200906/200914/200915: "table columns is empty", "table rows is invalid") and does not actually work on the current SDK version. It also lacks any fallback mechanism when the API refuses the payload.

This PR (#29630) uses tag:"markdown" + Schema 2.0 to present tables as pipe-formatted markdown text, with all content (headings, blockquotes, code blocks, tables) in a single markdown element. It has been verified working on real Feishu/Lark clients, and includes a triple fallback chain (interactive → post/md → text) so messages are never lost.

As @Crazy-FuQing noted in the #12114 comments, the approach closest to ours is actually #27990 (which uses card markdown elements rather than native table components), not #12114 itself. Since the native tag:"table" component has SDK compatibility issues, the tag:"markdown" approach appears to be the more reliable path forward.

@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 the table-to-plain-text degradation; the underlying current-main behavior is still present at plugins/platforms/feishu/adapter.py:4524-4533.

Problems

  • The PR now targets the removed gateway/platforms/feishu.py; Feishu moved to plugins/platforms/feishu/adapter.py in 5600105478ffde29d7566b45421b100eaa29c4ef, so this needs a considered port rather than a clean cherry-pick.
  • The stated three-stage fallback is incomplete for exceptions: the post retry at gateway/platforms/feishu.py:1870 runs inside the interactive exception handler. If that retry raises, it returns a send failure instead of reaching text fallback.
  • edit_message() also derives its message type from _build_outbound_payload() (plugins/platforms/feishu/adapter.py:1958 on current main), and stream delivery calls it (gateway/stream_consumer.py:1771). The PR adds no interactive-card edit fallback or coverage.
  • The PR changes no tests despite introducing table parsing, an interactive payload, and new fallback paths.

Suggested changes

  • Port the work to the plugin adapter and cover send and edit/streaming routing.
  • Centralize and test exception and failed-response transitions for interactive → post → text.
  • Validate the CardKit payload through the real Feishu API path; related PR #27990 was closed after reporting a 230099 failure for its card-markdown route.

This is an automated hermes-sweeper review.

chunk = _render_table_as_md(chunk)
msg_type = "post"
payload = _build_markdown_post_payload(chunk)
response = await self._feishu_send_with_retry(

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.

If this post retry raises, it escapes the interactive exception handler to the outer send failure path and never reaches the text fallback. Route the retry through a common fallback sequence, or catch this retry failure explicitly, and add an exception-path regression test.

@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 duplicate This issue or pull request already exists P2 Medium — degraded but workaround exists 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/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants