mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:00:42 +00:00
fix: bound slack approval metadata
This commit is contained in:
@@ -47,6 +47,7 @@ Docs: https://docs.openclaw.ai
|
||||
- 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.
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
@@ -113,4 +113,52 @@ describe("slackApprovalNativeRuntime", () => {
|
||||
(payload.blocks as Array<{ type?: string }>).some((block) => block.type === "actions"),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps pending metadata context within Slack Block Kit limits", async () => {
|
||||
const payload = (await slackApprovalNativeRuntime.presentation.buildPendingPayload({
|
||||
cfg: {} as never,
|
||||
accountId: "default",
|
||||
context: {
|
||||
app: {} as never,
|
||||
config: {} as never,
|
||||
},
|
||||
request: {
|
||||
id: "req-1",
|
||||
request: {
|
||||
command: "echo hi",
|
||||
},
|
||||
createdAtMs: 0,
|
||||
expiresAtMs: 60_000,
|
||||
},
|
||||
approvalKind: "exec",
|
||||
nowMs: 0,
|
||||
view: {
|
||||
approvalKind: "exec",
|
||||
approvalId: "req-1",
|
||||
commandText: "echo hi",
|
||||
metadata: Array.from({ length: 12 }, (_entry, index) => ({
|
||||
label: `Metadata ${index + 1}`,
|
||||
value: index === 0 ? "x".repeat(3100) : `value-${index + 1}`,
|
||||
})),
|
||||
actions: [
|
||||
{
|
||||
decision: "allow-once",
|
||||
label: "Allow Once",
|
||||
command: "/approve req-1 allow-once",
|
||||
style: "success",
|
||||
},
|
||||
],
|
||||
} as never,
|
||||
})) as SlackPayload;
|
||||
|
||||
const contextBlock = (payload.blocks as Array<{ type?: string; elements?: unknown[] }>).find(
|
||||
(block) => block.type === "context",
|
||||
);
|
||||
const elements = contextBlock?.elements as Array<{ text?: string }> | undefined;
|
||||
|
||||
expect(elements).toHaveLength(10);
|
||||
expect(elements?.[0]?.text).toHaveLength(3000);
|
||||
expect(elements?.[0]?.text?.endsWith("…")).toBe(true);
|
||||
expect(elements?.at(-1)?.text).toBe("…+3 more");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,6 +30,9 @@ type SlackPendingDelivery = {
|
||||
blocks: SlackBlock[];
|
||||
};
|
||||
|
||||
const SLACK_CONTEXT_ELEMENTS_MAX = 10;
|
||||
const SLACK_TEXT_OBJECT_MAX = 3000;
|
||||
|
||||
type SlackExecApprovalConfig = NonNullable<
|
||||
NonNullable<NonNullable<OpenClawConfig["channels"]>["slack"]>["execApprovals"]
|
||||
>;
|
||||
@@ -80,6 +83,21 @@ function buildSlackMetadataLines(metadata: readonly { label: string; value: stri
|
||||
return metadata.map((item) => formatSlackMetadataLine(item.label, item.value));
|
||||
}
|
||||
|
||||
function buildSlackMetadataContextElements(metadata: readonly { label: string; value: string }[]) {
|
||||
const lines = buildSlackMetadataLines(metadata);
|
||||
const visibleLines =
|
||||
lines.length > SLACK_CONTEXT_ELEMENTS_MAX
|
||||
? [
|
||||
...lines.slice(0, SLACK_CONTEXT_ELEMENTS_MAX - 1),
|
||||
`…+${lines.length - (SLACK_CONTEXT_ELEMENTS_MAX - 1)} more`,
|
||||
]
|
||||
: lines;
|
||||
return visibleLines.map((line) => ({
|
||||
type: "mrkdwn" as const,
|
||||
text: truncateSlackMrkdwn(line, SLACK_TEXT_OBJECT_MAX),
|
||||
}));
|
||||
}
|
||||
|
||||
function resolveSlackApprovalDecisionLabel(
|
||||
decision: "allow-once" | "allow-always" | "deny",
|
||||
): string {
|
||||
@@ -104,7 +122,7 @@ function buildSlackPendingApprovalText(view: ExecApprovalPendingView): string {
|
||||
}
|
||||
|
||||
function buildSlackPendingApprovalBlocks(view: ExecApprovalPendingView): SlackBlock[] {
|
||||
const metadataLines = buildSlackMetadataLines(view.metadata);
|
||||
const metadataElements = buildSlackMetadataContextElements(view.metadata);
|
||||
const interactiveBlocks =
|
||||
resolveSlackReplyBlocks({
|
||||
text: "",
|
||||
@@ -125,14 +143,11 @@ function buildSlackPendingApprovalBlocks(view: ExecApprovalPendingView): SlackBl
|
||||
text: `*Command*\n${buildSlackCodeBlock(truncateSlackMrkdwn(view.commandText, 2600))}`,
|
||||
},
|
||||
},
|
||||
...(metadataLines.length > 0
|
||||
...(metadataElements.length > 0
|
||||
? [
|
||||
{
|
||||
type: "context",
|
||||
elements: metadataLines.map((line) => ({
|
||||
type: "mrkdwn" as const,
|
||||
text: line,
|
||||
})),
|
||||
elements: metadataElements,
|
||||
} satisfies SlackBlock,
|
||||
]
|
||||
: []),
|
||||
|
||||
Reference in New Issue
Block a user