From 19d91aaa8f93587ea086c0b9b4673d9b7b874f74 Mon Sep 17 00:00:00 2001 From: adzendo Date: Wed, 25 Mar 2026 18:43:15 -0500 Subject: [PATCH] fix: make buttons schema optional in message tool (#54418) Merged via squash. Prepared head SHA: 0805c095e930d669ba1b3aedc09baec80882ce45 Co-authored-by: adzendo <246828680+adzendo@users.noreply.github.com> Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com> Reviewed-by: @altaywtf --- CHANGELOG.md | 1 + docs/.generated/plugin-sdk-api-baseline.json | 2 +- docs/.generated/plugin-sdk-api-baseline.jsonl | 2 +- src/agents/tools/message-tool.test.ts | 18 +++++++++++++++++ src/plugin-sdk/channel-actions.ts | 20 ++++++++++--------- 5 files changed, 32 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77e4661c151..85765563e49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -150,6 +150,7 @@ Docs: https://docs.openclaw.ai - Discord/commands: trim overlong slash-command descriptions to Discord's 100-character limit and map rejected deploy indexes from Discord validation payloads back to command names/descriptions, so deploys stop failing on long descriptions and startup logs identify the rejected commands. (#54118) thanks @huntharo - Agents/cooldowns: scope rate-limit cooldowns per model so one 429 no longer blocks every model on the same auth profile, replace the exponential 1 min → 1 h escalation with a stepped 30 s / 1 min / 5 min ladder, and surface a user-facing countdown message when all models are rate-limited. (#49834) Thanks @kiranvk-2011. - Config/web fetch: allow the documented `tools.web.fetch.maxResponseBytes` setting in runtime schema validation so valid configs no longer fail with unrecognized-key errors. (#53401) Thanks @erhhung. +- Message tool/buttons: keep the shared `buttons` schema optional in merged tool definitions so plain `action=send` calls stop failing validation when no buttons are provided. (#54418) Thanks @adzendo. ## 2026.3.23 diff --git a/docs/.generated/plugin-sdk-api-baseline.json b/docs/.generated/plugin-sdk-api-baseline.json index 702b8fcc59f..aeb01b07d87 100644 --- a/docs/.generated/plugin-sdk-api-baseline.json +++ b/docs/.generated/plugin-sdk-api-baseline.json @@ -914,7 +914,7 @@ "exportName": "createMessageToolCardSchema", "kind": "function", "source": { - "line": 27, + "line": 29, "path": "src/plugin-sdk/channel-actions.ts" } }, diff --git a/docs/.generated/plugin-sdk-api-baseline.jsonl b/docs/.generated/plugin-sdk-api-baseline.jsonl index c0fed7fd019..389afd6a133 100644 --- a/docs/.generated/plugin-sdk-api-baseline.jsonl +++ b/docs/.generated/plugin-sdk-api-baseline.jsonl @@ -99,7 +99,7 @@ {"declaration":"export type CompiledAllowlist = CompiledAllowlist;","entrypoint":"allow-from","exportName":"CompiledAllowlist","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"type","recordType":"export","sourceLine":19,"sourcePath":"src/channels/allowlist-match.ts"} {"category":"channel","entrypoint":"channel-actions","importSpecifier":"openclaw/plugin-sdk/channel-actions","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/channel-actions.ts"} {"declaration":"export function createMessageToolButtonsSchema(): TSchema;","entrypoint":"channel-actions","exportName":"createMessageToolButtonsSchema","importSpecifier":"openclaw/plugin-sdk/channel-actions","kind":"function","recordType":"export","sourceLine":11,"sourcePath":"src/plugin-sdk/channel-actions.ts"} -{"declaration":"export function createMessageToolCardSchema(): TSchema;","entrypoint":"channel-actions","exportName":"createMessageToolCardSchema","importSpecifier":"openclaw/plugin-sdk/channel-actions","kind":"function","recordType":"export","sourceLine":27,"sourcePath":"src/plugin-sdk/channel-actions.ts"} +{"declaration":"export function createMessageToolCardSchema(): TSchema;","entrypoint":"channel-actions","exportName":"createMessageToolCardSchema","importSpecifier":"openclaw/plugin-sdk/channel-actions","kind":"function","recordType":"export","sourceLine":29,"sourcePath":"src/plugin-sdk/channel-actions.ts"} {"declaration":"export function createUnionActionGate(accounts: readonly TAccount[], createGate: (account: TAccount) => OptionalDefaultGate): OptionalDefaultGate;","entrypoint":"channel-actions","exportName":"createUnionActionGate","importSpecifier":"openclaw/plugin-sdk/channel-actions","kind":"function","recordType":"export","sourceLine":13,"sourcePath":"src/channels/plugins/actions/shared.ts"} {"declaration":"export function listTokenSourcedAccounts(accounts: readonly TAccount[]): TAccount[];","entrypoint":"channel-actions","exportName":"listTokenSourcedAccounts","importSpecifier":"openclaw/plugin-sdk/channel-actions","kind":"function","recordType":"export","sourceLine":7,"sourcePath":"src/channels/plugins/actions/shared.ts"} {"declaration":"export function resolveReactionMessageId(params: { args: Record; toolContext?: ReactionToolContext | undefined; }): string | number | undefined;","entrypoint":"channel-actions","exportName":"resolveReactionMessageId","importSpecifier":"openclaw/plugin-sdk/channel-actions","kind":"function","recordType":"export","sourceLine":7,"sourcePath":"src/channels/plugins/actions/reaction-message-id.ts"} diff --git a/src/agents/tools/message-tool.test.ts b/src/agents/tools/message-tool.test.ts index 8e75c5bc393..cf71b7730ed 100644 --- a/src/agents/tools/message-tool.test.ts +++ b/src/agents/tools/message-tool.test.ts @@ -479,6 +479,24 @@ describe("message tool schema scoping", () => { }, ); + it("keeps buttons schema optional so plain sends do not require buttons", () => { + setActivePluginRegistry( + createTestRegistry([{ pluginId: "telegram", source: "test", plugin: telegramPlugin }]), + ); + + const tool = createMessageTool({ + config: {} as never, + currentChannelProvider: "telegram", + }); + const schema = tool.parameters as { + properties?: Record; + required?: string[]; + }; + + expect(schema.properties?.buttons).toBeDefined(); + expect(schema.required ?? []).not.toContain("buttons"); + }); + it("hides telegram poll extras when telegram polls are disabled in scoped mode", () => { const telegramPluginWithConfig = createChannelPlugin({ id: "telegram", diff --git a/src/plugin-sdk/channel-actions.ts b/src/plugin-sdk/channel-actions.ts index 068df1389de..c7a74c89fb9 100644 --- a/src/plugin-sdk/channel-actions.ts +++ b/src/plugin-sdk/channel-actions.ts @@ -9,17 +9,19 @@ import { stringEnum } from "../agents/schema/typebox.js"; /** Schema helper for channels that expose button rows on the shared `message` tool. */ export function createMessageToolButtonsSchema(): TSchema { - return Type.Array( + return Type.Optional( Type.Array( - Type.Object({ - text: Type.String(), - callback_data: Type.String(), - style: Type.Optional(stringEnum(["danger", "success", "primary"])), - }), + Type.Array( + Type.Object({ + text: Type.String(), + callback_data: Type.String(), + style: Type.Optional(stringEnum(["danger", "success", "primary"])), + }), + ), + { + description: "Button rows for channels that support button-style actions.", + }, ), - { - description: "Button rows for channels that support button-style actions.", - }, ); }