diff --git a/CHANGELOG.md b/CHANGELOG.md index ddbfed2c2a1..821777455c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ Docs: https://docs.openclaw.ai - Agents/Codex: flush accepted debounced steering messages before normal app-server turn cleanup, so inbound follow-ups acknowledged as queued are not dropped when the turn completes before the debounce fires. Thanks @vincentkoc. - Slack/interactive replies: keep rendered buttons and selects within Slack Block Kit value and count limits, and align command argument select values with Slack's option limit, so overlong agent-authored choices no longer make Slack reject the whole block payload. Thanks @slackapi. - Slack/interactive replies: drop overlong Block Kit button URLs while preserving valid callback values, so malformed link buttons no longer make Slack reject the whole interactive reply. Thanks @slackapi. +- Slack/commands: truncate native command argument-menu confirmation text to Slack's dialog limit, so long plugin arg names no longer make fallback buttons render invalid Block Kit payloads. Thanks @slackapi. - Channels/WhatsApp: require Baileys outbound message ids before marking auto-replies delivered, so transcript text and ack reactions no longer make failed group replies look sent. Fixes #49225. Thanks @TinyTb. - CLI/update: scope packaged Node compile caches by OpenClaw version and install metadata, so global installs no longer reuse stale compiled chunks after package updates. Thanks @pashpashpash. - Channels/Voice call: keep pre-auth webhook in-flight limiting active when socket remote address metadata is missing, so slow-body requests from stripped-IP proxy paths still share the fallback bucket. (#74453) Thanks @davidangularme. diff --git a/extensions/slack/src/monitor/slash.test.ts b/extensions/slack/src/monitor/slash.test.ts index 8ebb9f9fe77..4c851bfe670 100644 --- a/extensions/slack/src/monitor/slash.test.ts +++ b/extensions/slack/src/monitor/slash.test.ts @@ -9,6 +9,7 @@ vi.mock("./slash-commands.runtime.js", () => { const reportLongCommand = { key: "reportlong", nativeName: "reportlong" }; const reportLongButtonCommand = { key: "reportlongbutton", nativeName: "reportlongbutton" }; const unsafeConfirmCommand = { key: "unsafeconfirm", nativeName: "unsafeconfirm" }; + const longConfirmCommand = { key: "longconfirm", nativeName: "longconfirm" }; const statusAliasCommand = { key: "status", nativeName: "status" }; const periodArg = { name: "period", description: "period" }; const baseReportPeriodChoices = [ @@ -78,6 +79,9 @@ vi.mock("./slash-commands.runtime.js", () => { if (normalized === "unsafeconfirm") { return unsafeConfirmCommand; } + if (normalized === "longconfirm") { + return longConfirmCommand; + } if (normalized === "agentstatus") { return statusAliasCommand; } @@ -126,6 +130,12 @@ vi.mock("./slash-commands.runtime.js", () => { acceptsArgs: true, args: [], }, + { + name: "longconfirm", + description: "LongConfirm", + acceptsArgs: true, + args: [], + }, { name: "agentstatus", description: "Status", @@ -179,6 +189,15 @@ vi.mock("./slash-commands.runtime.js", () => { ], }; } + if (params.command?.key === "longconfirm") { + return { + arg: { name: `mode_${"x".repeat(320)}`, description: "mode" }, + choices: [ + { value: "on", label: "on" }, + { value: "off", label: "off" }, + ], + }; + } if (params.command?.key !== "usage") { return null; } @@ -428,6 +447,7 @@ describe("Slack native command argument menus", () => { let reportLongHandler: (args: unknown) => Promise; let reportLongButtonHandler: (args: unknown) => Promise; let unsafeConfirmHandler: (args: unknown) => Promise; + let longConfirmHandler: (args: unknown) => Promise; let agentStatusHandler: (args: unknown) => Promise; let argMenuHandler: (args: unknown) => Promise; let argMenuOptionsHandler: (args: unknown) => Promise; @@ -446,6 +466,7 @@ describe("Slack native command argument menus", () => { "/reportlongbutton", ); unsafeConfirmHandler = requireHandler(harness.commands, "/unsafeconfirm", "/unsafeconfirm"); + longConfirmHandler = requireHandler(harness.commands, "/longconfirm", "/longconfirm"); agentStatusHandler = requireHandler(harness.commands, "/agentstatus", "/agentstatus"); argMenuHandler = requireHandler(harness.actions, /^openclaw_cmdarg/, "arg-menu action"); argMenuOptionsHandler = requireHandler(harness.options, "openclaw_cmdarg", "arg-menu options"); @@ -596,6 +617,16 @@ describe("Slack native command argument menus", () => { ); }); + it("truncates confirm dialog text when long args force button fallback", async () => { + const element = (await getFirstActionElementFromCommand(longConfirmHandler)) as + | { type?: string; confirm?: { text?: { text?: string } } } + | undefined; + const confirmText = element?.confirm?.text?.text; + expect(element?.type).toBe("button"); + expect(confirmText).toHaveLength(300); + expect(confirmText?.endsWith("…")).toBe(true); + }); + it("dispatches the command when a menu button is clicked", async () => { await runArgMenuAction(argMenuHandler, { action: { diff --git a/extensions/slack/src/monitor/slash.ts b/extensions/slack/src/monitor/slash.ts index 8769da337f0..b551bd1f85a 100644 --- a/extensions/slack/src/monitor/slash.ts +++ b/extensions/slack/src/monitor/slash.ts @@ -55,6 +55,7 @@ const SLACK_COMMAND_ARG_SELECT_OPTIONS_MAX = 100; const SLACK_COMMAND_ARG_SELECT_OPTION_TEXT_MAX = 75; const SLACK_COMMAND_ARG_SELECT_OPTION_VALUE_MAX = 75; const SLACK_COMMAND_ARG_BUTTON_TEXT_MAX = 75; +const SLACK_COMMAND_ARG_CONFIRM_TEXT_MAX = 300; const SLACK_HEADER_TEXT_MAX = 150; let slashCommandsRuntimePromise: Promise | null = null; @@ -142,7 +143,10 @@ function buildSlackArgMenuConfirm(params: { command: string; arg: string }) { title: { type: "plain_text", text: "Confirm selection" }, text: { type: "mrkdwn", - text: `Run */${command}* with *${arg}* set to this value?`, + text: truncateSlackText( + `Run */${command}* with *${arg}* set to this value?`, + SLACK_COMMAND_ARG_CONFIRM_TEXT_MAX, + ), }, confirm: { type: "plain_text", text: "Run command" }, deny: { type: "plain_text", text: "Cancel" },