Skip to content

Commit da00ead

Browse files
fix(feishu): parse code blocks and share_chat messages (#28591) thanks @kevinWangSheng
Verified: - pnpm install --frozen-lockfile - pnpm build - pnpm check - pnpm test:macmini Co-authored-by: kevinWangSheng <[email protected]> Co-authored-by: Tak Hoffman <[email protected]>
1 parent 89669a3 commit da00ead

File tree

3 files changed

+90
-0
lines changed

3 files changed

+90
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai
2222
- Feishu/Typing backoff: re-throw Feishu typing add/remove rate-limit and quota errors (`429`, `99991400`, `99991403`) and detect SDK non-throwing backoff responses so the typing keepalive circuit breaker can stop retries instead of looping indefinitely. (#28494)
2323
- Feishu/Probe status caching: cache successful `probeFeishu()` bot-info results for 10 minutes (bounded cache with per-account keying) to reduce repeated status/onboarding probe API calls, while bypassing cache for failures and exceptions. (#28907) Thanks @Glucksberg.
2424
- Feishu/Opus media send type: send `.opus` attachments with `msg_type: "audio"` (instead of `"media"`) so Feishu voice messages deliver correctly while `.mp4` remains `msg_type: "media"` and documents remain `msg_type: "file"`. (#28269) Thanks @Glucksberg.
25+
- Feishu/Inbound rich-text parsing: preserve `share_chat` payload summaries when available and add explicit parsing for rich-text `code`/`code_block`/`pre` tags so forwarded and code-heavy messages keep useful context in agent input. (#28591) Thanks @kevinWangSheng.
2526
- Feishu/Local media sends: propagate `mediaLocalRoots` through Feishu outbound media sending into `loadWebMedia` so local path attachments work with post-CVE local-root enforcement. (#27884) Thanks @joelnishanth.
2627
- Security/Feishu webhook ingress: bound unauthenticated webhook rate-limit state with stale-window pruning and a hard key cap to prevent unbounded pre-auth memory growth from rotating source keys. (#26050) Thanks @bmendonca3.
2728
- Security/Compaction audit: remove the post-compaction audit injection message. (#28507) Thanks @fuller-stack-dev and @vincentkoc.

extensions/feishu/src/bot.checkBotMentioned.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,20 @@ function makePostEvent(content: unknown) {
3636
};
3737
}
3838

39+
function makeShareChatEvent(content: unknown) {
40+
return {
41+
sender: { sender_id: { user_id: "u1", open_id: "ou_sender" } },
42+
message: {
43+
message_id: "msg_1",
44+
chat_id: "oc_chat1",
45+
chat_type: "group",
46+
message_type: "share_chat",
47+
content: JSON.stringify(content),
48+
mentions: [],
49+
},
50+
};
51+
}
52+
3953
describe("parseFeishuMessageEvent – mentionedBot", () => {
4054
const BOT_OPEN_ID = "ou_bot_123";
4155

@@ -127,4 +141,36 @@ describe("parseFeishuMessageEvent – mentionedBot", () => {
127141
const ctx = parseFeishuMessageEvent(event as any, "ou_bot_123");
128142
expect(ctx.mentionedBot).toBe(false);
129143
});
144+
145+
it("preserves post code and code_block content", () => {
146+
const event = makePostEvent({
147+
content: [
148+
[
149+
{ tag: "text", text: "before " },
150+
{ tag: "code", text: "inline()" },
151+
],
152+
[{ tag: "code_block", language: "ts", text: "const x = 1;" }],
153+
],
154+
});
155+
const ctx = parseFeishuMessageEvent(event as any, "ou_bot_123");
156+
expect(ctx.content).toContain("before `inline()`");
157+
expect(ctx.content).toContain("```ts\nconst x = 1;\n```");
158+
});
159+
160+
it("uses share_chat body when available", () => {
161+
const event = makeShareChatEvent({
162+
body: "Merged and Forwarded Message",
163+
share_chat_id: "sc_abc123",
164+
});
165+
const ctx = parseFeishuMessageEvent(event as any, "ou_bot_123");
166+
expect(ctx.content).toBe("Merged and Forwarded Message");
167+
});
168+
169+
it("falls back to share_chat identifier when body is unavailable", () => {
170+
const event = makeShareChatEvent({
171+
share_chat_id: "sc_abc123",
172+
});
173+
const ctx = parseFeishuMessageEvent(event as any, "ou_bot_123");
174+
expect(ctx.content).toBe("[Forwarded message: sc_abc123]");
175+
});
130176
});

extensions/feishu/src/bot.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,26 @@ function parseMessageContent(content: string, messageType: string): string {
188188
const { textContent } = parsePostContent(content);
189189
return textContent;
190190
}
191+
if (messageType === "share_chat") {
192+
// Preserve available summary text for merged/forwarded chat messages.
193+
if (parsed && typeof parsed === "object") {
194+
const share = parsed as {
195+
body?: unknown;
196+
summary?: unknown;
197+
share_chat_id?: unknown;
198+
};
199+
if (typeof share.body === "string" && share.body.trim().length > 0) {
200+
return share.body.trim();
201+
}
202+
if (typeof share.summary === "string" && share.summary.trim().length > 0) {
203+
return share.summary.trim();
204+
}
205+
if (typeof share.share_chat_id === "string" && share.share_chat_id.trim().length > 0) {
206+
return `[Forwarded message: ${share.share_chat_id.trim()}]`;
207+
}
208+
}
209+
return "[Forwarded message]";
210+
}
191211
return content;
192212
} catch {
193213
return content;
@@ -293,6 +313,29 @@ function parsePostContent(content: string): {
293313
if (imageKey) {
294314
imageKeys.push(imageKey);
295315
}
316+
} else if (element.tag === "code") {
317+
// Inline code
318+
const code =
319+
typeof element.text === "string"
320+
? element.text
321+
: typeof element.content === "string"
322+
? element.content
323+
: "";
324+
if (code) {
325+
textContent += `\`${code}\``;
326+
}
327+
} else if (element.tag === "code_block" || element.tag === "pre") {
328+
// Multiline code block
329+
const lang = typeof element.language === "string" ? element.language : "";
330+
const code =
331+
typeof element.text === "string"
332+
? element.text
333+
: typeof element.content === "string"
334+
? element.content
335+
: "";
336+
if (code) {
337+
textContent += `\n\`\`\`${lang}\n${code}\n\`\`\`\n`;
338+
}
296339
}
297340
}
298341
textContent += "\n";

0 commit comments

Comments
 (0)