diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f43010d716..c92c41fdff6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,7 @@ Docs: https://docs.openclaw.ai - 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. - Slack/exec approvals: cap native approval metadata context to Slack's element and text limits, so large approval details no longer make Slack reject the approval card. Thanks @slackapi. +- Slack/exec approvals: cap native approval update fallback text to Slack's message limit while preserving the rendered approval blocks, so long commands no longer make resolved or expired approval cards stay stale after `chat.update` rejects `msg_too_long`. Thanks @slackapi. - Slack/commands: cap native command argument-menu fallback rows to Slack's message block limit, so large plugin choice lists no longer make Slack reject the generated menu. Thanks @slackapi. - Slack/commands: drop fallback command argument buttons whose encoded values exceed Slack's button-value limit, so one oversized plugin choice no longer makes Slack reject the whole menu. Thanks @slackapi. - Slack/messages: merge message-tool presentation and interactive blocks on Slack sends, so buttons and selects are no longer dropped when a structured message body is also present. Thanks @slackapi. diff --git a/extensions/slack/src/approval-handler.runtime.test.ts b/extensions/slack/src/approval-handler.runtime.test.ts index 5e53fa1b3de..90741f63c22 100644 --- a/extensions/slack/src/approval-handler.runtime.test.ts +++ b/extensions/slack/src/approval-handler.runtime.test.ts @@ -1,10 +1,11 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { slackApprovalNativeRuntime } from "./approval-handler.runtime.js"; type SlackPayload = { text: string; blocks?: unknown; }; +const SLACK_TEXT_LIMIT = 8000; function findSlackActionsBlock(blocks: Array<{ type?: string; elements?: unknown[] }>) { return blocks.find((block) => block.type === "actions"); @@ -114,6 +115,53 @@ describe("slackApprovalNativeRuntime", () => { ).toBe(false); }); + it("caps resolved update fallback text while preserving approval blocks", async () => { + const blocks = [ + { + type: "section", + text: { + type: "mrkdwn", + text: "*Command*\n```short preview```", + }, + }, + ]; + const chatUpdate = vi.fn(async (_payload: { text: string; blocks: typeof blocks }) => ({})); + + await slackApprovalNativeRuntime.transport.updateEntry?.({ + cfg: {} as never, + accountId: "default", + context: { + app: { + client: { + chat: { + update: chatUpdate, + }, + }, + }, + config: {}, + } as never, + entry: { + channelId: "C123", + messageTs: "1712345678.999999", + }, + payload: { + text: `*Exec approval: Allowed once*\n\n*Command*\n${"a".repeat(9000)}`, + blocks, + }, + phase: "resolved", + }); + + expect(chatUpdate).toHaveBeenCalledWith( + expect.objectContaining({ + channel: "C123", + ts: "1712345678.999999", + text: expect.stringMatching(/…$/), + blocks, + }), + ); + expect(chatUpdate.mock.calls[0]?.[0].text).toHaveLength(SLACK_TEXT_LIMIT); + }); + it("keeps pending metadata context within Slack Block Kit limits", async () => { const payload = (await slackApprovalNativeRuntime.presentation.buildPendingPayload({ cfg: {} as never, diff --git a/extensions/slack/src/approval-handler.runtime.ts b/extensions/slack/src/approval-handler.runtime.ts index 42404e239ef..dad73c9cde6 100644 --- a/extensions/slack/src/approval-handler.runtime.ts +++ b/extensions/slack/src/approval-handler.runtime.ts @@ -17,8 +17,10 @@ import { shouldHandleSlackExecApprovalRequest, normalizeSlackApproverId, } from "./exec-approvals.js"; +import { SLACK_TEXT_LIMIT } from "./limits.js"; import { resolveSlackReplyBlocks } from "./reply-blocks.js"; import { sendMessageSlack } from "./send.js"; +import { truncateSlackText } from "./truncate.js"; type SlackBlock = Block | KnownBlock; type SlackPendingApproval = { @@ -229,7 +231,7 @@ async function updateMessage(params: { await params.app.client.chat.update({ channel: params.channelId, ts: params.messageTs, - text: params.text, + text: truncateSlackText(params.text, SLACK_TEXT_LIMIT), blocks: params.blocks, }); } catch (err) {