feishu: fix schema 2.0 card config in interactive card UX functions (#53395)

Merged via squash.

Prepared head SHA: 31f2396404
Co-authored-by: drvoss <3031622+drvoss@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
This commit is contained in:
jason 2026-04-04 21:38:37 +09:00 committed by GitHub
parent 375bd73ce1
commit 6e28bd2eb6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 98 additions and 4 deletions

View File

@ -201,6 +201,7 @@ Docs: https://docs.openclaw.ai
- Mattermost/slash commands: harden native slash-command callback token validation to use constant-time secret comparison, matching the existing interaction-token path.
- Agents/scheduling: route delayed follow-up requests toward cron only when cron is actually available, while keeping background `exec`/`process` guidance scoped to work that starts now. (#60811) Thanks @vincentkoc.
- Cron/security: reject unsafe custom `sessionTarget: "session:..."` IDs earlier during cron add, update, and execution so malformed custom session keys fail closed with clear errors.
- Feishu/cards: replace the legacy `wide_screen_mode` schema 1.x config with schema 2.0 `width_mode: "fill"` in interactive approval, launcher, markdown, and structured card builders so Feishu card sends stop failing with parse-card errors while preserving wide-card rendering. (#53395) Thanks @drvoss
## 2026.4.1

View File

@ -195,6 +195,9 @@ describe("Feishu Card Action Handler", () => {
to: "chat:chat1",
accountId: "main",
card: expect.objectContaining({
config: expect.objectContaining({
width_mode: "fill",
}),
header: expect.objectContaining({
title: expect.objectContaining({ content: "Confirm action" }),
}),
@ -220,6 +223,21 @@ describe("Feishu Card Action Handler", () => {
}),
}),
);
const firstSendArg = (sendCardFeishuMock.mock.calls as unknown[][]).at(0)?.[0] as
| {
card?: {
config?: {
width_mode?: string;
wide_screen_mode?: boolean;
enable_forward?: boolean;
};
};
}
| undefined;
const sentCard = firstSendArg?.card;
expect(sentCard).toBeDefined();
expect(sentCard?.config?.wide_screen_mode).toBeUndefined();
expect(sentCard?.config?.enable_forward).toBeUndefined();
expect(handleFeishuMessage).not.toHaveBeenCalled();
});

View File

@ -21,7 +21,7 @@ export function createApprovalCard(params: {
return {
schema: "2.0",
config: {
wide_screen_mode: true,
width_mode: "fill",
},
header: {
title: {

View File

@ -32,6 +32,11 @@ describe("feishu quick-action launcher", () => {
expiresAt: 123,
sessionKey: "agent:codex:feishu:chat:chat1",
}) as {
config: {
width_mode?: string;
enable_forward?: boolean;
wide_screen_mode?: boolean;
};
body: {
elements: Array<{
tag: string;
@ -40,6 +45,9 @@ describe("feishu quick-action launcher", () => {
};
};
expect(card.config.width_mode).toBe("fill");
expect(card.config.enable_forward).toBeUndefined();
expect(card.config.wide_screen_mode).toBeUndefined();
const actionBlock = card.body.elements.find((entry) => entry.tag === "action");
expect(actionBlock?.actions).toHaveLength(3);
expect(actionBlock?.actions?.[0]?.value?.oc).toBe("ocf1");
@ -64,6 +72,9 @@ describe("feishu quick-action launcher", () => {
to: "user:u123",
accountId: "main",
card: expect.objectContaining({
config: expect.objectContaining({
width_mode: "fill",
}),
body: expect.objectContaining({
elements: expect.arrayContaining([
expect.objectContaining({
@ -83,6 +94,21 @@ describe("feishu quick-action launcher", () => {
}),
}),
);
const firstSendArg = (sendCardFeishuMock.mock.calls as unknown[][]).at(0)?.[0] as
| {
card?: {
config?: {
width_mode?: string;
wide_screen_mode?: boolean;
enable_forward?: boolean;
};
};
}
| undefined;
const sentCard = firstSendArg?.card;
expect(sentCard).toBeDefined();
expect(sentCard?.config?.wide_screen_mode).toBeUndefined();
expect(sentCard?.config?.enable_forward).toBeUndefined();
});
it("falls back to legacy menu handling when launcher send fails", async () => {

View File

@ -23,7 +23,7 @@ export function createQuickActionLauncherCard(params: {
return {
schema: "2.0",
config: {
wide_screen_mode: true,
width_mode: "fill",
},
header: {
title: {

View File

@ -143,6 +143,9 @@ describe("Feishu bot menu handler", () => {
expect.objectContaining({
to: "user:ou_user1",
card: expect.objectContaining({
config: expect.objectContaining({
width_mode: "fill",
}),
header: expect.objectContaining({
title: expect.objectContaining({ content: "Quick actions" }),
}),
@ -212,5 +215,20 @@ describe("Feishu bot menu handler", () => {
}),
);
});
const firstSendArg = (sendCardFeishuMock.mock.calls as unknown[][]).at(0)?.[0] as
| {
card?: {
config?: {
width_mode?: string;
wide_screen_mode?: boolean;
enable_forward?: boolean;
};
};
}
| undefined;
const sentCard = firstSendArg?.card;
expect(sentCard).toBeDefined();
expect(sentCard?.config?.wide_screen_mode).toBeUndefined();
expect(sentCard?.config?.enable_forward).toBeUndefined();
});
});

View File

@ -1,5 +1,6 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { ClawdbotConfig } from "../runtime-api.js";
import { buildMarkdownCard } from "./send.js";
const {
mockConvertMarkdownTables,
@ -406,6 +407,20 @@ describe("resolveFeishuCardTemplate", () => {
});
describe("buildStructuredCard", () => {
it("uses schema-2.0 width config instead of legacy wide screen mode", () => {
const card = buildStructuredCard("hello") as {
config: {
width_mode?: string;
enable_forward?: boolean;
wide_screen_mode?: boolean;
};
};
expect(card.config.width_mode).toBe("fill");
expect(card.config.enable_forward).toBeUndefined();
expect(card.config.wide_screen_mode).toBeUndefined();
});
it("falls back to blue when the header template is unsupported", () => {
const card = buildStructuredCard("hello", {
header: {
@ -424,3 +439,19 @@ describe("buildStructuredCard", () => {
);
});
});
describe("buildMarkdownCard", () => {
it("uses schema-2.0 width config instead of legacy wide screen mode", () => {
const card = buildMarkdownCard("hello") as {
config: {
width_mode?: string;
enable_forward?: boolean;
wide_screen_mode?: boolean;
};
};
expect(card.config.width_mode).toBe("fill");
expect(card.config.enable_forward).toBeUndefined();
expect(card.config.wide_screen_mode).toBeUndefined();
});
});

View File

@ -587,7 +587,7 @@ export function buildMarkdownCard(text: string): Record<string, unknown> {
return {
schema: "2.0",
config: {
wide_screen_mode: true,
width_mode: "fill",
},
body: {
elements: [
@ -634,7 +634,7 @@ export function buildStructuredCard(
}
const card: Record<string, unknown> = {
schema: "2.0",
config: { wide_screen_mode: true },
config: { width_mode: "fill" },
body: { elements },
};
if (options?.header) {