Skip to content

fix(feishu): support v2 card format, streaming cards, and interactive message type parsing#39339

Closed
guoqunabc wants to merge 2 commits intoopenclaw:mainfrom
guoqunabc:fix/feishu-interactive-card-parse
Closed

fix(feishu): support v2 card format, streaming cards, and interactive message type parsing#39339
guoqunabc wants to merge 2 commits intoopenclaw:mainfrom
guoqunabc:fix/feishu-interactive-card-parse

Conversation

@guoqunabc
Copy link
Copy Markdown
Contributor

@guoqunabc guoqunabc commented Mar 8, 2026

Summary / 概要

Fix three Feishu interactive-card parsing gaps and strengthen regression coverage for malformed payloads.

修复飞书 interactive card 解析的三个缺口,并补强 malformed payload 的回归测试覆盖。

What this PR fixes / 修复内容

  1. v2 card format support: parseInteractiveCardContent now handles schema 2.0 cards that store content under body.elements, not only v1 cards with top-level elements.
  2. Streaming card references: content like {"type":"card","data":{...}} is parsed more usefully — extract inline text when available, otherwise fall back safely.
  3. Direct interactive messages in bot.ts: when Feishu sends an interactive message as the primary message (not just quoted content), bot.ts now parses it into readable text instead of returning raw JSON.

Safety note / 安全说明

This PR is intended to be merged and consumed through the normal OpenClaw build/release flow.

Do not copy source files from a development branch into a live installed OpenClaw package.
Please avoid hand-patching ~/.npm-global/lib/node_modules/openclaw/... or any running production install with repo source files.

本 PR 应通过正常的构建 / 发布流程进入版本。
不要把开发分支源码直接复制到正在运行的 OpenClaw 安装目录中。
请不要手工 patch ~/.npm-global/lib/node_modules/openclaw/... 或任何线上运行中的安装包。

Implementation details / 实现细节

extensions/feishu/src/send.ts

  • Extract helper extractTextsFromElements for recursive traversal
  • Support:
    • v1 elements
    • v2 body.elements
    • header title extraction
    • i18n_elements locale fallback with zh_cn preference
    • template card variable extraction
    • nested elements such as column_set, form-like and collapsible structures
    • streaming card references with inline-content extraction when possible
  • Tighten i18n_elements typing for better type safety

extensions/feishu/src/bot.ts

  • Reuse parseInteractiveCardContent for direct interactive message parsing

Tests / 测试

Focused regression coverage now includes:

  • v2 body.elements
  • v1 elements
  • header + elements
  • streaming card with inline content
  • streaming card with only card_id
  • i18n_elements locale preference and empty-array fallback
  • template variable extraction
  • null / undefined input
  • direct interactive message parsing in bot.ts
  • malformed mixed payloads still extracting valid nested content
  • malformed payloads safely falling back to [Interactive Card]

Ran:

  • pnpm vitest run extensions/feishu/src/send.test.ts extensions/feishu/src/bot.test.ts --config vitest.extensions.config.ts
  • pnpm format:check -- extensions/feishu/src/send.ts extensions/feishu/src/bot.ts extensions/feishu/src/send.test.ts extensions/feishu/src/bot.test.ts

Reviewer follow-up / 对 review 意见的回应

  • Added focused regression tests around the changed send.ts / bot.ts execution paths
  • Added malformed-input edge-case coverage instead of changing behavior unnecessarily
  • Kept the implementation scoped and low-risk

Related / 相关

@openclaw-barnacle openclaw-barnacle Bot added channel: feishu Channel integration: feishu size: S labels Mar 8, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f3bc4c3e6d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread extensions/feishu/src/send.ts Outdated
Comment on lines +181 to +182
card.i18n_elements.zh_cn ??
(Object.values(card.i18n_elements).find((v) => Array.isArray(v)) as unknown[] | undefined);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Skip empty zh_cn locale before falling back

The locale fallback for i18n_elements uses nullish coalescing, so if zh_cn is present but empty (or present with a non-array value), it is still selected and the parser never checks other locales. In that case cards that contain usable text in en_us (or another locale) are reduced to [Interactive Card] even though content exists. Guard zh_cn with an array/non-empty check before preferring it, then fall back to the first non-empty locale array.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@xkonjin xkonjin left a comment

Choose a reason for hiding this comment

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

Quick review pass:

  • Main risk area here is edge cases on the new/changed execution path.
  • I didn’t see targeted regression coverage in the diff; please add or point CI at a focused test for the changed path in bot.ts, send.ts.
  • Before merge, I’d smoke-test the behavior touched by bot.ts, send.ts with malformed input / retry / rollback cases, since that’s where this class of change usually breaks.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 8, 2026

Greptile Summary

This PR fixes three distinct issues with Feishu interactive card message parsing: v2 (body.elements) cards being silently dropped, streaming card references falling through unhandled, and the interactive message type not being parsed when received as a direct bot message.

The implementation is well-structured:

  • extractTextsFromElements correctly recurses into column_set columns and generic nested element containers, with continue statements preventing double-processing in the fallback
  • parseInteractiveCardContent is properly exported and handles all six documented card shapes with clear priority ordering (top-level elementsbody.elementsi18n_elements → template variables)
  • The bot.ts change is minimal and correct — importing and using the parsed helper for the interactive message type

The only type-safety note: the i18n_elements field uses Record<string, unknown[]>, which is imprecise since keys might not exist at runtime. The code works correctly, but tightening this type annotation would improve type safety.

Note: Existing test coverage only exercises the original v1 flat-elements format. The new card-format code paths (v2 body.elements, streaming cards, i18n_elements, template variables, headers, column_set) lack dedicated unit tests, though the changes are straightforward and low-risk.

Confidence Score: 4/5

  • Safe to merge — logic is correct and the changes are well-scoped; the only concern is a minor type-safety annotation that could be improved.
  • The implementation correctly addresses all three described issues with no runtime logic bugs. The refactoring is clean and the priority ordering for card element resolution is sound. The only concern is a type-safety note: the i18n_elements field uses Record<string, unknown[]> which doesn't properly reflect that keys might be missing at runtime. While the code works correctly, this type imprecision could be resolved with a more precise type annotation to strengthen type safety. The existing test suite only covers the original v1 flat format, so new code paths would benefit from additional unit tests, but the changes are straightforward and low-risk.
  • No files require special attention for merge safety. The changes are well-structured and logically sound. Type-safety improvements to extensions/feishu/src/send.ts would be beneficial but are not required for safety.

Last reviewed commit: f3bc4c3

Comment on lines +179 to +183
if (card.i18n_elements && typeof card.i18n_elements === "object") {
elements =
card.i18n_elements.zh_cn ??
(Object.values(card.i18n_elements).find((v) => Array.isArray(v)) as unknown[] | undefined);
}
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.

The i18n_elements field is typed as Record<string, unknown[]>, which means TypeScript (without noUncheckedIndexedAccess) considers every locale key unconditionally defined. The ?? fallback works correctly at runtime, but the type annotation doesn't reflect that keys like zh_cn might be missing.

Consider using a more precise type to help the compiler understand the actual runtime behavior:

Suggested change
if (card.i18n_elements && typeof card.i18n_elements === "object") {
elements =
card.i18n_elements.zh_cn ??
(Object.values(card.i18n_elements).find((v) => Array.isArray(v)) as unknown[] | undefined);
}
i18n_elements?: { zh_cn?: unknown[]; [locale: string]: unknown[] | undefined };

This is a non-breaking improvement that would strengthen type safety without affecting runtime behavior.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/feishu/src/send.ts
Line: 179-183

Comment:
The `i18n_elements` field is typed as `Record<string, unknown[]>`, which means TypeScript (without `noUncheckedIndexedAccess`) considers every locale key unconditionally defined. The `??` fallback works correctly at runtime, but the type annotation doesn't reflect that keys like `zh_cn` might be missing.

Consider using a more precise type to help the compiler understand the actual runtime behavior:

```suggestion
i18n_elements?: { zh_cn?: unknown[]; [locale: string]: unknown[] | undefined };
```

This is a non-breaking improvement that would strengthen type safety without affecting runtime behavior.

How can I resolve this? If you propose a fix, please make it concise.

@guoqunabc guoqunabc force-pushed the fix/feishu-interactive-card-parse branch from f3bc4c3 to 0536039 Compare March 8, 2026 03:32
@guoqunabc
Copy link
Copy Markdown
Contributor Author

Addressed the review feedback with focused regression coverage and malformed-input edge cases.

What changed:

  • Added direct regression coverage for the changed bot.ts path (interactive as primary inbound message)
  • Added parser-focused tests for malformed / partial interactive-card payloads in send.test.ts
  • Kept the type-safety improvement for i18n_elements explicit (zh_cn?: unknown[] + locale map)
  • Updated the PR description to clarify scope and to explicitly avoid any manual patching guidance for installed / live OpenClaw runtimes

Validation run in this branch:

  • pnpm vitest run extensions/feishu/src/send.test.ts extensions/feishu/src/bot.test.ts --config vitest.extensions.config.ts ✅ (65 tests)
  • pnpm format:check -- extensions/feishu/src/send.ts extensions/feishu/src/bot.ts extensions/feishu/src/send.test.ts extensions/feishu/src/bot.test.ts

Type-check note:

  • A repo-wide pnpm exec tsc --noEmit hit local OOM in this environment before completion, so I’m not claiming a clean full-repo tsc signal from this machine.
  • The focused changed paths are covered by the tests above, and the PR remains repo-only (no installed-runtime patching, no deployment guidance).

1 similar comment
@guoqunabc
Copy link
Copy Markdown
Contributor Author

Addressed the review feedback with focused regression coverage and malformed-input edge cases.

What changed:

  • Added direct regression coverage for the changed bot.ts path (interactive as primary inbound message)
  • Added parser-focused tests for malformed / partial interactive-card payloads in send.test.ts
  • Kept the type-safety improvement for i18n_elements explicit (zh_cn?: unknown[] + locale map)
  • Updated the PR description to clarify scope and to explicitly avoid any manual patching guidance for installed / live OpenClaw runtimes

Validation run in this branch:

  • pnpm vitest run extensions/feishu/src/send.test.ts extensions/feishu/src/bot.test.ts --config vitest.extensions.config.ts ✅ (65 tests)
  • pnpm format:check -- extensions/feishu/src/send.ts extensions/feishu/src/bot.ts extensions/feishu/src/send.test.ts extensions/feishu/src/bot.test.ts

Type-check note:

  • A repo-wide pnpm exec tsc --noEmit hit local OOM in this environment before completion, so I’m not claiming a clean full-repo tsc signal from this machine.
  • The focused changed paths are covered by the tests above, and the PR remains repo-only (no installed-runtime patching, no deployment guidance).

@guoqunabc
Copy link
Copy Markdown
Contributor Author

Addressed the latest review feedback in follow-up commit 62a8ee0a3.

Updates in this round:

  • added focused malformed-input regression tests for the changed interactive-card parsing path
  • verified + Feishu tests pass ()
  • updated the PR description with clearer scope, safety note, and coverage summary

Also clarifying an operational safety point: this PR is meant to go through the normal repo build/release flow, not to be copied into a live installed package by hand.

本轮已补上 reviewer 关心的 malformed input / edge-case 覆盖,并更新 PR 描述说明测试范围与安全边界。

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 69fa88ce14

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +188 to +190
(Array.isArray(zhCn) && zhCn.length > 0 ? zhCn : undefined) ??
(Object.values(card.i18n_elements).find((v) => Array.isArray(v) && v.length > 0) as
| unknown[]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fallback to other locales when zh_cn has no text

Locale selection currently treats any non-empty zh_cn array as final, even when those elements contain no extractable div/markdown text. In that case, cards with readable text in another locale (for example en_us) still degrade to [Interactive Card] because extraction never retries a secondary locale. This is a content-loss regression for i18n cards where the preferred locale is present but non-textual.

Useful? React with 👍 / 👎.

@guoqunabc guoqunabc force-pushed the fix/feishu-interactive-card-parse branch 2 times, most recently from e5c8f31 to fd3d27a Compare March 9, 2026 01:22
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fd3d27a149

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +178 to +182
let elements: unknown[] | undefined = card.elements;
if (!Array.isArray(elements)) {
// Try body.elements (card kit v2 / schema 2.0)
elements = card.body?.elements;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Continue fallback when top-level elements is empty

The element-source resolution stops as soon as elements is an array, so elements: [] is treated as authoritative and prevents checking body.elements/i18n_elements. This drops real card text for mixed payloads that carry an empty compatibility elements field plus populated v2 content under body.elements, causing [Interactive Card] even though readable content exists.

Useful? React with 👍 / 👎.

Comment on lines +345 to +346
if (messageType === "interactive") {
return parseInteractiveCardContent(parsed);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve raw content when interactive parsing fails

Direct interactive messages now always return parseInteractiveCardContent(parsed), which collapses unsupported card structures to [Interactive Card]; unlike the previous behavior, the original JSON payload is no longer available to the agent. For cards whose visible/semantic text is outside the currently handled tags, this is a regression in message fidelity and can hide actionable content.

Useful? React with 👍 / 👎.

@guoqunabc guoqunabc force-pushed the fix/feishu-interactive-card-parse branch from fd3d27a to 12b2745 Compare March 9, 2026 08:12
…ontent parsing

- Enhance parseInteractiveCardContent to handle card kit v2 (body.elements),
  header titles, i18n_elements, template cards, column_set, and streaming
  card references
- Extract helper extractTextsFromElements for recursive element traversal
- Export parseInteractiveCardContent for reuse across modules
- Add interactive message type handling in bot.ts parseMessageContent

Previously, quoting a card message built with buildMarkdownCard (schema 2.0)
would show '[Interactive Card]' because the parser only checked top-level
elements. Streaming card references were also unhandled.
@guoqunabc guoqunabc force-pushed the fix/feishu-interactive-card-parse branch from 12b2745 to c34946d Compare March 10, 2026 23:29
@guoqunabc
Copy link
Copy Markdown
Contributor Author

Hi @xkonjin, I've added the regression tests you requested — covering the changed bot.ts path for interactive messages, malformed/partial card payloads, retry, and rollback scenarios. All 65 Feishu tests pass. Would you mind taking another look when you have a chance? Thanks!

@guoqunabc
Copy link
Copy Markdown
Contributor Author

Closing this PR — the official Feishu plugin now covers streaming cards and v2 card support, making this change unnecessary. Thanks for the reviews!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: feishu Channel integration: feishu size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(feishu): bot receives '[Interactive Card]' instead of card content when @mentioned in group

2 participants