fix(feishu): parse interactive card post-format fallback content#60383
fix(feishu): parse interactive card post-format fallback content#60383lskun wants to merge 1 commit intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR extends
Confidence Score: 3/5Safe to merge after fixing the trailing-whitespace bug in the anchor-tag test; the core parsing logic is sound. The PR logic is correct for all described scenarios, but there is a concrete test failure in the anchor-tag test: input text "See: " (trailing space) will not match the asserted "See:" because individual texts are pushed untrimmed. The PR's stated test coverage is not actually green. extensions/feishu/src/send.ts line 220-221 — tag:"text" node text should be trimmed before pushing. extensions/feishu/src/send.test.ts line 257/275 — input and expected value are inconsistent. Prompt To Fix All With AIThis is a comment left during a code review.
Path: extensions/feishu/src/send.test.ts
Line: 257-275
Comment:
**Test/code mismatch — trailing whitespace in text causes assertion failure**
The test input at line 257 has `{ tag: "text", text: "See: " }` — note the trailing space. The production code in `send.ts` pushes `node.text` directly (not trimmed):
```typescript
if (node.tag === "text" && typeof node.text === "string" && node.text.trim()) {
texts.push(node.text); // pushes "See: " with the trailing space intact
}
```
After `texts.join("\n")`, the result is `"Report\nSee: \nWeekly Report (...)"` — but the assertion expects `"Report\nSee:\nWeekly Report (...)"` (no trailing space). The `.trim()` call at the end only strips the leading/trailing edges of the whole string, not interior segments.
This test will fail as written. The fix is to trim the individual text value before pushing in `send.ts`:
```suggestion
if (node.tag === "text" && typeof node.text === "string" && node.text.trim()) {
texts.push(node.text.trim());
}
```
This is also better UX — stray trailing spaces in card text are noise and shouldn't appear in the extracted content.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(feishu): parse interactive card post..." | Re-trigger Greptile |
| { tag: "text", text: "See: " }, | ||
| { tag: "a", text: "Weekly Report", href: "https://example.com/report" }, | ||
| ], | ||
| ], | ||
| }), | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| }); | ||
|
|
||
| const result = await getMessageFeishu({ | ||
| cfg: {} as ClawdbotConfig, | ||
| messageId: "om_card_links", | ||
| }); | ||
|
|
||
| expect(result).toEqual( | ||
| expect.objectContaining({ | ||
| content: "Report\nSee:\nWeekly Report (https://example.com/report)", |
There was a problem hiding this comment.
Test/code mismatch — trailing whitespace in text causes assertion failure
The test input at line 257 has { tag: "text", text: "See: " } — note the trailing space. The production code in send.ts pushes node.text directly (not trimmed):
if (node.tag === "text" && typeof node.text === "string" && node.text.trim()) {
texts.push(node.text); // pushes "See: " with the trailing space intact
}After texts.join("\n"), the result is "Report\nSee: \nWeekly Report (...)" — but the assertion expects "Report\nSee:\nWeekly Report (...)" (no trailing space). The .trim() call at the end only strips the leading/trailing edges of the whole string, not interior segments.
This test will fail as written. The fix is to trim the individual text value before pushing in send.ts:
| { tag: "text", text: "See: " }, | |
| { tag: "a", text: "Weekly Report", href: "https://example.com/report" }, | |
| ], | |
| ], | |
| }), | |
| }, | |
| }, | |
| ], | |
| }, | |
| }); | |
| const result = await getMessageFeishu({ | |
| cfg: {} as ClawdbotConfig, | |
| messageId: "om_card_links", | |
| }); | |
| expect(result).toEqual( | |
| expect.objectContaining({ | |
| content: "Report\nSee:\nWeekly Report (https://example.com/report)", | |
| if (node.tag === "text" && typeof node.text === "string" && node.text.trim()) { | |
| texts.push(node.text.trim()); | |
| } |
This is also better UX — stray trailing spaces in card text are noise and shouldn't appear in the extracted content.
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/feishu/src/send.test.ts
Line: 257-275
Comment:
**Test/code mismatch — trailing whitespace in text causes assertion failure**
The test input at line 257 has `{ tag: "text", text: "See: " }` — note the trailing space. The production code in `send.ts` pushes `node.text` directly (not trimmed):
```typescript
if (node.tag === "text" && typeof node.text === "string" && node.text.trim()) {
texts.push(node.text); // pushes "See: " with the trailing space intact
}
```
After `texts.join("\n")`, the result is `"Report\nSee: \nWeekly Report (...)"` — but the assertion expects `"Report\nSee:\nWeekly Report (...)"` (no trailing space). The `.trim()` call at the end only strips the leading/trailing edges of the whole string, not interior segments.
This test will fail as written. The fix is to trim the individual text value before pushing in `send.ts`:
```suggestion
if (node.tag === "text" && typeof node.text === "string" && node.text.trim()) {
texts.push(node.text.trim());
}
```
This is also better UX — stray trailing spaces in card text are noise and shouldn't appear in the extracted content.
How can I resolve this? If you propose a fix, please make it concise.|
Codex review: needs changes before merge. Summary Reproducibility: yes. A mocked getMessageFeishu() response with msg_type "interactive" and body.content containing {"title":"...","elements":[[{"tag":"text","text":"..."}]]} follows current main to the placeholder because nested arrays are ignored and the post fallback requires a post/content payload. Next step before merge Security Review findings
Review detailsBest possible solution: Land a narrow Feishu parser/test fix that preserves #72397 empty-array fallback semantics while adding top-level nested fallback title/text/link extraction and an Unreleased changelog entry. Do we have a high-confidence way to reproduce the issue? Yes. A mocked getMessageFeishu() response with msg_type "interactive" and body.content containing {"title":"...","elements":[[{"tag":"text","text":"..."}]]} follows current main to the placeholder because nested arrays are ignored and the post fallback requires a post/content payload. Is this the best way to solve the issue? No, not as currently patched. The narrow extraction approach is right, but the implementation should first find nonempty element text across candidate arrays and only prepend or return title text after preserving the existing empty-array fallthrough behavior. Full review comments:
Overall correctness: patch is incorrect Acceptance criteria:
What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against cc8a8f1df1cd. |
db76951 to
a380eb1
Compare
Summary
Fix
parseInteractiveCardContent()to handle Feishu API post-format fallback content for interactive card messages.Fixes #60380
Related: #41609 #48281
Problem
When reading card messages via
GET /im/v1/messages/{message_id}, Feishu returnsbody.contentin a post-style fallback format — nested arrays withtag:"text"elements — instead of the original card template JSON. The existing parser only handled card template format (div/markdowntags), causing all fallback content to silently return"[Interactive Card]".Before: Any
message(action=read)on a card →"[Interactive Card]"After: Extracts title + text content from both card template and post-format fallback
Example
API returns this for a card message:
{ "title": "🤖 clawdbot", "elements": [[ {"tag": "img", "image_key": "img_v3_02ad_..."}, {"tag": "text", "text": "Daily report summary"}, {"tag": "text", "text": "13 people missing reports"} ]] }"[Interactive Card]""🤖 clawdbot\nDaily report summary\n13 people missing reports"Changes
extensions/feishu/src/send.tselements(post-format fallback[[...]])tag:"text"andtag:"a"elements from fallback contentheader.title.content(template) or top-leveltitle(fallback)tag:"plain_text"elementsextensions/feishu/src/send.test.tsheader.title.contenttag:"a"elements with hrefTesting