diff --git a/CHANGELOG.md b/CHANGELOG.md index a49a78997d3..9d08877a0a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ Docs: https://docs.openclaw.ai - Discord/timeouts: send a visible timeout reply when the inbound Discord worker times out before a final reply starts, including created auto-thread targets and queued-run ordering. (#53823) Thanks @Kimbo7870. - Models/google: normalize bare Google Generative AI API roots for custom provider names, and keep built-in Google model-id rewrites working when `api` is declared only on individual models, so custom Google lanes and older configs stop missing `/v1beta` or preview-id normalization. (#44969) Thanks @Kathie-yu. - Gateway/ports: parse Docker Compose-style `OPENCLAW_GATEWAY_PORT` host publish values correctly without reviving the legacy `CLAWDBOT_GATEWAY_PORT` override. (#44083) Thanks @bebule. +- Feishu/MSTeams message tool: keep provider-native `card` payloads optional in merged tool schemas so media-only sends stop failing validation before channel runtime dispatch. (#53715) Thanks @lndyzwdxhs. ## 2026.3.23 diff --git a/src/agents/tools/message-tool.test.ts b/src/agents/tools/message-tool.test.ts index 66410aea2e8..02369744d9b 100644 --- a/src/agents/tools/message-tool.test.ts +++ b/src/agents/tools/message-tool.test.ts @@ -3,7 +3,10 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite import type { ChannelMessageCapability } from "../../channels/plugins/message-capabilities.js"; import type { ChannelMessageActionName, ChannelPlugin } from "../../channels/plugins/types.js"; import type { MessageActionRunResult } from "../../infra/outbound/message-action-runner.js"; -import { createMessageToolButtonsSchema } from "../../plugin-sdk/channel-actions.js"; +import { + createMessageToolButtonsSchema, + createMessageToolCardSchema, +} from "../../plugin-sdk/channel-actions.js"; type CreateMessageTool = typeof import("./message-tool.js").createMessageTool; type SetActivePluginRegistry = typeof import("../../plugins/runtime.js").setActivePluginRegistry; type CreateTestRegistry = typeof import("../../test-utils/channel-plugins.js").createTestRegistry; @@ -418,6 +421,38 @@ describe("message tool schema scoping", () => { expect(actionEnum).toContain("poll"); }); + it("keeps provider card schema optional after merging into the message tool schema", () => { + const feishuPlugin = createChannelPlugin({ + id: "feishu", + label: "Feishu", + docsPath: "/channels/feishu", + blurb: "Feishu test plugin.", + actions: ["send"], + capabilities: ["cards"], + toolSchema: () => ({ + properties: { + card: createMessageToolCardSchema(), + }, + }), + }); + + setActivePluginRegistry( + createTestRegistry([{ pluginId: "feishu", source: "test", plugin: feishuPlugin }]), + ); + + const tool = createMessageTool({ + config: {} as never, + currentChannelProvider: "feishu", + }); + const schema = tool.parameters as { + properties?: Record; + required?: string[]; + }; + + expect(schema.properties?.card).toBeDefined(); + expect(schema.required ?? []).not.toContain("card"); + }); + it("hides telegram poll extras when telegram polls are disabled in scoped mode", () => { const telegramPluginWithConfig = createChannelPlugin({ id: "telegram",