feat(feishu): auto-convert Markdown tables to interactive card tables - #47697
feat(feishu): auto-convert Markdown tables to interactive card tables#47697brokenarrow2099 wants to merge 21 commits into
Conversation
|
Duplicate of #12114 — same card-based approach (detect markdown table at gateway send(), convert to a Feishu interactive card |
- Add _send_feishu_card() in send_message_tool.py for standalone card sending - Add send_interactive_card() method to FeishuAdapter in feishu.py - Support card parameter in send_message tool schema (type: object) - Handle lark_oapi response.success() as method, with .code==0 fallback - Correct receive_id_type to 'chat_id' for oc_ prefixed chat IDs - Strip await from _send_feishu_card call (non-async context)
… in gateway send() When the agent sends a message containing a Markdown table to Feishu, the gateway now automatically: 1. Extracts the Markdown table from the message 2. Converts it to a Feishu interactive card with table component 3. Sends the card separately via send_interactive_card() 4. Sends the remaining text through the normal pipeline This eliminates the need for the agent to manually construct card JSON and fixes the poor rendering of Markdown tables on Feishu client. Key changes: - _markdown_table_to_card(): standalone converter function - send() method: intercepts Markdown tables, routes to card path - Proper error handling and fallback to normal text path
The original regex only matched header+separator lines, causing _markdown_table_to_card to receive a 2-row table with no data and return None. Updated regex to require header+separator+data rows, so the full table content is captured. Root cause: _MARKDOWN_TABLE_RE matched only '| header |\n|---|' but not the data rows '| value |', so the converter had nothing to work with.
Calculate column width based on max content length (header + data rows), converting char count to pixel width (12px/char + 20px padding), clamped to [80, 300]px. This significantly reduces the Feishu-native truncation behavior where cells with insufficient space show ellipsis.
When a streaming response is finalized (edit_message finalize=True), check if the accumulated text contains Markdown tables. If so, send the table as a separate interactive card and replace the streaming message with a brief notice. This ensures tables in streaming responses are properly converted to cards, not just the first non-streaming message.
Instead of only converting the first Markdown table, recursively call send() for the remaining content after each table. This ensures all tables in a long response are converted to cards, not just the first one. Also sends the 'before' text separately from the 'after' text so that text preceding a table is delivered before the card.
Replace recursive send() with asyncio.gather to send all table cards in parallel, reducing latency from N×500ms to ~500ms regardless of table count. After all cards are sent, deliver remaining text.
Use _display_width() to estimate display width (CJK=2, ASCII=1, emoji=2) instead of raw char count. Increase pixel multiplier from 12 to 16 and max width from 300 to 400px. This fixes truncation of Chinese content like '零样本语音合成' which was too narrow at 116px.
User feedback: columns were too wide. Adjusted from display_width*16+30 to display_width*10+20, max 350px instead of 400px.
Integrates PR NousResearch#37476 (gateway-status-card) into our feishu-table-card branch. Adds format_hermes_status_card() for compact status display with: - Version, git commit, gateway/system uptime - Model, fallbacks, tokens, cache hit rate - Context usage, compactions, active tasks, queue depth
3d8a69f to
60b3083
Compare
- _status_context_limit: also check model.context_length, skip
non-numeric top-level context (which is a dict like {engine: ...})
- _format_context: show 'N used' when limit is unknown but current
is available, instead of always returning 'unknown'
…nknown When last_prompt_tokens is 0 (agent hasn't run yet), fallback to 0 instead of None so _format_context renders '0/131.1k (0%)' rather than 'unknown'.
- Read context_tokens/context_limit from running agent's context_compressor for accurate real-time values (128K window). - Fallback to persisted session_entry.last_prompt_tokens when no agent is active. - _status_model_label now also reads 'default' key (for model.default in config.yaml) and never exposes api_key/base_url.
…uardrails - Add _output_indicates_task_failure() to detect task-level failures (HTTP errors, tracebacks, etc.) in terminal/execute_code output even when exit code is 0 - Extend no_progress detection to ALL tools (not just idempotent), preventing infinite loops on mutating tools like terminal and execute_code - Add repeated tool call prevention guidance to system prompt - Update tests to reflect new behavior
teknium1
left a comment
There was a problem hiding this comment.
Thanks for pursuing a native Feishu table presentation. The underlying limitation remains on current main: plugins/platforms/feishu/adapter.py:4524-4530 explicitly sends Markdown tables as plain text because Feishu post Markdown does not render them.
Problems
- The table implementation changes
gateway/platforms/feishu.py, but commit5600105478ffde29d7566b45421b100eaa29c4efmoved the live adapter toplugins/platforms/feishu/adapter.py; current outbound delivery isFeishuAdapter.send()atplugins/platforms/feishu/adapter.py:1887-1942. This cannot be cleanly cherry-picked. - The diff also includes unrelated guardrail,
/status, DingTalk, and media-delivery changes. In particular,tools/send_message_tool.pyadds a cache-copy step before path filtering specifically so outside-root files pass validation; that should not ride with a table-rendering feature.
Suggested changes
- Rework only the card-table feature against
plugins/platforms/feishu/adapter.py, with focused adapter tests for conversion, fallback, ordering, multiple tables, and finalization. - Split the unrelated changes out, and retain the existing media path-validation boundary.
This is an automated hermes-sweeper review.
| @@ -1778,11 +1883,108 @@ async def send( | |||
| reply_to: Optional[str] = None, | |||
There was a problem hiding this comment.
Current main moved the live Feishu adapter to plugins/platforms/feishu/adapter.py in 5600105478ffde29d7566b45421b100eaa29c4ef; please rework this feature against that plugin surface rather than the removed inline adapter.
| @@ -380,6 +388,12 @@ def _handle_send(args): | |||
| force_document_attachments = "[[as_document]]" in message | |||
There was a problem hiding this comment.
This copies source paths into an allowed cache before filter_media_delivery_paths(), intentionally making paths outside the allowed roots pass validation. Please remove this unrelated validation bypass from the table feature.
Feishu Interactive Card Table Support / 飞书交互卡片表格支持
Problem / 问题
Feishu bot messages do not support Markdown tables — they render as plain text with poor formatting. Users need structured tabular data (model status, progress tables, etc.) displayed natively in Feishu.
飞书机器人消息不支持 Markdown 表格渲染,表格会以纯文本形式展示,格式混乱。用户需要以原生卡片表格的形式展示结构化数据(模型状态、进度表等)。
Solution / 解决方案
This PR adds automatic Markdown table to Feishu interactive card conversion at the Gateway level. When the agent sends a message containing a Markdown table to Feishu, the Gateway now:
send()methodtablecomponentsend_interactive_card()API本 PR 在 Gateway 层面实现了 Markdown 表格到飞书交互卡片表格的自动转换。当 agent 发送包含 Markdown 表格的消息到飞书时,Gateway 会:
send()方法中通过正则检测 Markdown 表格table组件)send_interactive_card()API 发送卡片Changes / 改动
Commit 1:
1a6edfc89— feat(feishu): add interactive card support to send_message tool_send_feishu_card()insend_message_tool.py: standalone function to send Feishu interactive cards via the toolsend_interactive_card()infeishu.py: new adapter method for card deliveryawaitfrom sync_send_feishu_cardcallresponse.successtoresponse.success()(method call, not property)receive_id_type:oc_prefix chat_ids usechat_idtypeCommit 2:
c030138b7— feat(feishu): auto-convert Markdown tables in gateway send()_markdown_table_to_card(): standalone converter function (Markdown table string to Feishu card JSON)send()method: intercepts Markdown tables, routes to card pathCommit 3:
3b9b35e9b— fix(feishu): fix regex to capture full table including data rows_MARKDOWN_TABLE_REto require header plus separator plus data rowsFiles Changed / 修改的文件
tools/send_message_tool.py_send_feishu_card()— tool-level card sendinggateway/platforms/feishu.pysend_interactive_card()+ auto-convert insend()+_markdown_table_to_card()Usage / 使用方式
Automatic (recommended) / 自动方式(推荐):
Just write Markdown tables in your response — the Gateway converts them automatically.
直接在回复中写 Markdown 表格,Gateway 会自动转换。
Manual via send_message tool / 手动方式:
send_message(target="feishu", card={"header": {...}, "elements": [{"tag": "table", ...}]})Conversion script / 转换脚本:
echo "| A | B |\n|---|---|\n| 1 | 2 |" | python3 ~/.hermes/scripts/md_to_feishu_card.py --title "Title" --compactTesting / 测试
send_messagetool workssend()path works