fix: cap slack approval update text

This commit is contained in:
Peter Steinberger
2026-04-30 05:16:25 +01:00
parent c4f9cf1a27
commit 395ad91323
3 changed files with 53 additions and 2 deletions

View File

@@ -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.

View File

@@ -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,

View File

@@ -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) {