Skip to content

fix(feishu): render all markdown via Card 2.0 interactive cards - #46472

Closed
wait4xx wants to merge 1 commit into
NousResearch:mainfrom
wait4xx:fix/feishu-markdown-interactive-cards
Closed

fix(feishu): render all markdown via Card 2.0 interactive cards#46472
wait4xx wants to merge 1 commit into
NousResearch:mainfrom
wait4xx:fix/feishu-markdown-interactive-cards

Conversation

@wait4xx

@wait4xx wait4xx commented Jun 15, 2026

Copy link
Copy Markdown

Fixes #46470
Closes #25452
Closes #26841
Closes #29471
Supersedes #25453

Summary

Render all Feishu outbound text and Markdown through a Card 2.0 markdown element, replacing the limited post-message md renderer. This fixes GFM table rendering and keeps streaming sends and edits on one stable interactive message type.

Why Card 2.0 Markdown

Live Feishu card probes showed that Card 2.0 Markdown renders tables, fenced code blocks, lists, headings, blockquotes, and GFM alignment directly. The native table component rejects alignment and requires substantially more parsing code, so this PR keeps the content as Markdown and lets Feishu render it.

Changes

  • Add _build_markdown_card_payload() with one Card 2.0 markdown element.
  • Route all outbound content to interactive by default, including plain text, so streaming send/edit paths keep the same msg_type.
  • Add platforms.feishu.extra.interactive_cards: false as a documented rollback to the legacy text/post routing.
  • Reuse Hermes's existing fence-aware message splitter instead of maintaining a Feishu-specific card packer.
  • Measure Feishu chunks in UTF-8 bytes so Chinese and emoji remain safely below the 30KB card JSON limit.
  • Fall back to raw text when a new card cannot be sent; rejected edits retry as a simplified interactive card to preserve message type.
  • Update the Feishu user guide.

Review-driven scope reduction

  • Removed Mermaid-to-image rendering.
  • Removed the default mermaid.ink external fallback and all implicit content export.
  • Removed the custom card block parser, table counter, pagination headers, and 30KB bin-packer.
  • Removed generic gateway/platforms/base.py and gateway/run.py language-tag changes.
  • Removed the Feishu-specific system-prompt instruction.

Mermaid remains a normal fenced code block. A future renderer, if pursued, should be a separate opt-in change with explicit privacy documentation.

Tests

  • 17 focused Feishu tests passed through scripts/run_tests.sh, covering Card 2.0 routing, tables, multibyte fenced content, legacy rollback, send/edit fallback, post helpers, and the pinned lark-oapi extra_ua_tags contract.
  • Rebased onto current upstream/main.
  • CI green.

Related work

PR Approach Relationship
#25453 Native table component on the old adapter path Superseded by this focused Card 2.0 implementation
#45036 / #45907 Card 2.0 Markdown Same rendering primitive; this PR also keeps streaming message types stable and adds rollback/tests
#40445 Native table component More parsing and no alignment support
#45583 / #38867 Post-message Markdown Uses the limited post md renderer rather than Card 2.0 Markdown

Co-authored-by: gpt-5.6-sol <215057067+openai-codex[bot]@users.noreply.github.com>

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery platform/feishu Feishu / Lark adapter duplicate This issue or pull request already exists labels Jun 15, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Duplicate of #12114 — enters the saturated Feishu Card 2.0 (tag:"markdown") rendering cluster (canonical #12114, consolidation issue #27469, 70+ competing PRs). This PR is a clean, well-tested implementation but the same core approach. Supersedes your own #25453.

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review Summary

Verdict: Approved

Good improvement to Feishu markdown rendering: adds Card 2.0 interactive card support with a 100KB size limit for graceful degradation to plain text. The Card 2.0 tag supports full CommonMark including GFM tables.

Looks Good

  • Clean, well-scoped fix
  • Good size-limit guard to prevent oversized payloads
  • No security or stability concerns

Reviewed by Hermes Agent

@wait4xx

wait4xx commented Jun 15, 2026

Copy link
Copy Markdown
Author

Duplicate of #12114 — enters the saturated Feishu Card 2.0 (tag:"markdown") rendering cluster (canonical #12114, consolidation issue #27469, 70+ competing PRs). This PR is a clean, well-tested implementation but the same core approach. Supersedes your own #25453.

Yes, I have consolidated all issues related to Feishu message rendering. Building upon the issue I previously opened, I have made further refinements and optimizations. I tested two table rendering approaches and ultimately retained the more concise solution. The Feishu message rendering issue remains unresolved on the latest main branch, and I hope it can be fixed as soon as possible.

@wait4xx

wait4xx commented Jun 17, 2026

Copy link
Copy Markdown
Author

Update — additional fixes layered on this branch (fix/feishu-markdown-interactive-cards):

  1. Mermaid → image: Feishu does not render mermaid natively (the official card docs list it under "unsupported"). ```mermaid blocks now render to a transparent PNG via the local mmdc CLI (Chromium/Chrome auto-detected via PUPPETEER_EXECUTABLE_PATH), with a tiered fallback — external mermaid.ink for browser-less / Docker hosts, then a fenced code block — so diagrams render on every deployment. Rendering runs off the event loop (asyncio.to_thread).
  2. Mermaid extraction bug fix: the closing-fence regex lacked re.MULTILINE, so _extract_mermaid_blocks never matched the closing fence → every mermaid block silently fell back to a code block. Fixed (_MERMAID_CLOSE_RE).
  3. Table limit correction: each card is a single markdown element, which Feishu limits to 4 GFM tables (official card docs). The code was splitting at 5 — the limit for the native tag:"table" component, which this path does not use. Now respects the correct 4-per-element limit.

Verified by sending real cards to Feishu (transparent 2× mermaid PNG, multi-table splitting).

Co-authored-by: GLM 5.2

@wait4xx
wait4xx force-pushed the fix/feishu-markdown-interactive-cards branch from fbc266e to 0f840c0 Compare June 17, 2026 15:11
@wait4xx
wait4xx force-pushed the fix/feishu-markdown-interactive-cards branch from 0f840c0 to e4784ac Compare June 21, 2026 03:54
@wait4xx

wait4xx commented Jun 21, 2026

Copy link
Copy Markdown
Author

Update — rebased onto latest main + code-block language tags (fix/feishu-markdown-interactive-cards):

  1. Rebased onto current main — the branch was 74 commits behind. Since then main relocated gateway/platforms/feishu.py to plugins/platforms/feishu/adapter.py (the platforms-to-plugins refactor); the rebase applied cleanly with no conflicts, so this PR is now MERGEABLE against main (previously it would have hit the module-relocation conflict on merge).
  2. Fixed stale test imports — 14 test imports still referenced the old gateway.platforms.feishu path and raised ModuleNotFoundError after the migration; all updated to plugins.platforms.feishu.adapter.
  3. Code-block language tags — added a code_block_language_tag adapter attribute (default empty string, safe for platforms like Slack whose mrkdwn would render a tag as literal text). FeishuAdapter sets it to bash, so terminal-command previews in tool-progress messages now use a bash-tagged code fence — Card 2.0's markdown element honours the language tag for syntax highlighting. gateway/run.py reads the attribute when building tool-progress code blocks; an empty tag preserves the previous bare-fence behaviour for every other platform.

All 218 feishu tests pass. Ready for re-review after the force-push.

Co-authored-by: GLM 5.2

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for consolidating a real Feishu rendering defect: current main still explicitly routes Markdown tables to text at plugins/platforms/feishu/adapter.py:4524-4534, so the Card 2.0 direction remains relevant.

Problems

  • The added Mermaid fallback sends diagram source to mermaid.ink by default when local mmdc is unavailable (plugins/platforms/feishu/adapter.py, PR commit e4784ac83384). That is a third-party export of agent/user content without an affirmative opt-in; the added test also asserts this request occurs.
  • The Mermaid/card-splitting subsystem substantially expands a Markdown-routing fix. Oversized fenced code is passed to paragraph splitting in the PR, which can separate its opening and closing fences (plugins/platforms/feishu/adapter.py, e4784ac83384).
  • website/docs/user-guide/messaging/feishu.md:415-421 still documents post/text routing and does not cover the default Card 2.0 change or the new platform-extra flags.

Suggested changes

  • Keep the focused Card 2.0 routing/streaming fix, but remove Mermaid rendering from this PR or require a documented opt-in for any external renderer.
  • Add docs for the changed delivery behavior and configuration, and test oversized fenced/multibyte content if splitting remains.
  • Consider separating the unrelated generic code_block_language_tag core change (gateway/platforms/base.py, gateway/run.py) from this platform rendering fix.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 2026
@wait4xx
wait4xx force-pushed the fix/feishu-markdown-interactive-cards branch from e4784ac to c189e4b Compare July 15, 2026 15:39
@wait4xx

wait4xx commented Jul 15, 2026

Copy link
Copy Markdown
Author

Addressed the review feedback in f89941ea0 and force-updated this PR after rebasing onto the latest upstream/main.

Changes made

  • Removed Mermaid image rendering entirely, including both local mmdc execution and the default mermaid.ink fallback. This PR no longer exports user or agent content to an external renderer.
  • Removed the Feishu-specific block parser, table counting, card pagination headers, and custom 30KB bin-packer.
  • Reused Hermes's existing fence-aware message splitter and changed Feishu length measurement to UTF-8 bytes. Oversized Chinese/emoji fenced content is split with each code fence closed and reopened correctly.
  • Removed the unrelated generic code_block_language_tag changes from gateway/platforms/base.py and gateway/run.py.
  • Removed the Feishu-specific system-prompt instruction.
  • Added documentation for the Card 2.0 default and the rollback setting: platforms.feishu.extra.interactive_cards: false.
  • Kept edits on the interactive message type when retrying a rejected streaming update.

Verification

  • 17 focused Feishu tests pass through scripts/run_tests.sh.
  • Coverage includes Card 2.0 routing, GFM tables, multibyte fenced content, legacy rollback, send/edit fallback, existing post helpers, and the pinned lark-oapi extra_ua_tags contract.
  • The local full-file run still exposes an unrelated WebSocket-close test failure that reproduces unchanged on current main under Python 3.13; no full-suite pass is claimed here. CI is pending.

The resulting production change is limited to the Feishu adapter, its tests, and the Feishu user guide. Mermaid rendering can be considered separately later as an explicit opt-in feature with privacy documentation.

Co-authored-by: gpt-5.6-sol <215057067+openai-codex[bot]@users.noreply.github.com>

Replace the legacy post-type tag:md renderer with a Card 2.0 markdown element so tables, fenced code blocks, lists, headings, links, and plain text share one consistent outbound message type.

- Route all outbound content to interactive cards by default so streaming send/edit paths keep the same msg_type.
- Add platforms.feishu.extra.interactive_cards=false as a documented rollback to the legacy text/post routing.
- Reuse the existing fence-aware message splitter and measure Feishu chunks in UTF-8 bytes, keeping multibyte content safely below the 30KB card limit.
- Fall back to raw text for failed sends and retry rejected edits with a simplified interactive card.
- Add focused coverage for Card 2.0 routing, GFM tables, multibyte fenced content, legacy rollback, send/edit fallback, and the pinned lark-oapi extra_ua_tags contract.
- Update the Feishu user guide with the new rendering behavior and configuration.

Keep this fix focused: Mermaid rendering, external renderers, custom card packing, prompt changes, and generic gateway language-tag hooks are intentionally excluded.

Fixes NousResearch#25452
Supersedes NousResearch#25453

Co-authored-by: gpt-5.6-sol <215057067+openai-codex[bot]@users.noreply.github.com>
@wait4xx
wait4xx force-pushed the fix/feishu-markdown-interactive-cards branch from c189e4b to f89941e Compare July 15, 2026 16:16
@alt-glitch alt-glitch removed type/bug Something isn't working duplicate This issue or pull request already exists comp/gateway Gateway runner, session dispatch, delivery labels Jul 16, 2026
@alt-glitch alt-glitch added type/feature New feature or request comp/plugins Plugin system and bundled plugins needs-decision Awaiting maintainer decision before any implementation and removed sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows labels Jul 16, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Correction: this is not being treated as a duplicate of #12114. After the rebase and scope reduction, it remains a related, salvageable Card 2.0 implementation; maintainers should choose among the open Feishu rendering approaches.

@wait4xx

wait4xx commented Jul 25, 2026

Copy link
Copy Markdown
Author

Closing in favor of #68121 (merged 2026-07-20), which resolved #25452.

#68121 took the minimal route — removed the _MARKDOWN_TABLE_RE force-downgrade branch and let table content flow through the post + md path, trusting Feishu's 2026 server-side GFM table rendering. That's a different direction from this PR's Card 2.0 interactive-card approach, and the maintainers clearly preferred the smaller blast radius. This PR was also mergeable_state=dirty on the old gateway/platforms/feishu.py path and carried scope (mermaid, external renderer, card packing) that was already stripped in response to earlier review.

If a real gap shows up in #68121's tenant-downgrade behavior (the posttext fallback when a lagging tenant rejects the payload), I'll file a separate issue rather than reopen this.

Co-authored-by: GLM 5.2

@wait4xx wait4xx closed this Jul 25, 2026
@wait4xx
wait4xx deleted the fix/feishu-markdown-interactive-cards branch July 25, 2026 10:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/plugins Plugin system and bundled plugins needs-decision Awaiting maintainer decision before any implementation 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

4 participants