fix(slack): split media and block action sends

This commit is contained in:
Peter Steinberger
2026-05-02 03:58:09 +01:00
parent 689a1cd21d
commit 2dfa2663ec
5 changed files with 106 additions and 23 deletions

View File

@@ -427,19 +427,48 @@ describe("handleSlackAction", () => {
);
});
it("rejects blocks combined with mediaUrl", async () => {
await expect(
handleSlackAction(
{
action: "sendMessage",
to: "channel:C123",
content: "hello",
mediaUrl: "https://example.com/file.png",
blocks: JSON.stringify([{ type: "divider" }]),
},
slackConfig(),
),
).rejects.toThrow(/does not support blocks with mediaUrl/i);
it("sends media before a separate blocks message", async () => {
sendSlackMessage.mockResolvedValueOnce({ channelId: "C123" });
sendSlackMessage.mockResolvedValueOnce({ channelId: "C123" });
const result = await handleSlackAction(
{
action: "sendMessage",
to: "channel:C123",
content: "hello",
mediaUrl: "https://example.com/file.png",
blocks: JSON.stringify([{ type: "divider" }]),
},
slackConfig(),
);
expect(sendSlackMessage).toHaveBeenCalledTimes(2);
expect(sendSlackMessage).toHaveBeenNthCalledWith(
1,
"channel:C123",
"",
expect.objectContaining({
cfg: expect.any(Object),
mediaUrl: "https://example.com/file.png",
threadTs: undefined,
}),
);
expect(sendSlackMessage.mock.calls[0]?.[2]).not.toHaveProperty("blocks");
expect(sendSlackMessage).toHaveBeenNthCalledWith(
2,
"channel:C123",
"hello",
expect.objectContaining({
cfg: expect.any(Object),
blocks: [{ type: "divider" }],
threadTs: undefined,
}),
);
expect(sendSlackMessage.mock.calls[1]?.[2]).not.toHaveProperty("mediaUrl");
expect(result.details).toEqual({
ok: true,
result: { channelId: "C123" },
});
});
it.each([

View File

@@ -244,22 +244,34 @@ export async function handleSlackAction(
if (!content && !mediaUrl && !blocks) {
throw new Error("Slack sendMessage requires content, blocks, or mediaUrl.");
}
if (mediaUrl && blocks) {
throw new Error("Slack sendMessage does not support blocks with mediaUrl.");
}
const threadTs = resolveThreadTsFromContext(
readStringParam(params, "threadTs"),
to,
context,
);
const result = await slackActionRuntime.sendSlackMessage(to, content ?? "", {
const sendOpts = {
...writeOpts,
mediaUrl: mediaUrl ?? undefined,
mediaLocalRoots: context?.mediaLocalRoots,
mediaReadFile: context?.mediaReadFile,
threadTs: threadTs ?? undefined,
blocks,
});
};
const result =
mediaUrl && blocks
? await (async () => {
await slackActionRuntime.sendSlackMessage(to, "", {
...sendOpts,
mediaUrl,
});
return await slackActionRuntime.sendSlackMessage(to, content ?? "", {
...sendOpts,
blocks,
});
})()
: await slackActionRuntime.sendSlackMessage(to, content ?? "", {
...sendOpts,
mediaUrl: mediaUrl ?? undefined,
blocks,
});
if (threadTs && result.channelId && account.accountId) {
slackActionRuntime.recordSlackThreadParticipation(

View File

@@ -99,6 +99,50 @@ describe("handleSlackMessageAction", () => {
]);
});
it("passes media and rendered interactive blocks through for split Slack delivery", async () => {
const invoke = createInvokeSpy();
await handleSlackMessageAction({
providerId: "slack",
ctx: {
action: "send",
cfg: {},
params: {
to: "channel:C1",
message: "Approval required",
media: "https://example.com/report.md",
interactive: {
blocks: [
{
type: "buttons",
buttons: [{ label: "Approve", value: "approve" }],
},
],
},
},
} as never,
invoke: invoke as never,
});
expect(invoke).toHaveBeenCalledOnce();
expect(invoke).toHaveBeenCalledWith(
expect.objectContaining({
action: "sendMessage",
to: "channel:C1",
content: "Approval required",
mediaUrl: "https://example.com/report.md",
blocks: [
expect.objectContaining({
type: "actions",
elements: [expect.objectContaining({ value: "approve" })],
}),
],
}),
expect.any(Object),
undefined,
);
});
it("maps upload-file to the internal uploadFile action", async () => {
const invoke = createInvokeSpy();

View File

@@ -58,9 +58,6 @@ export async function handleSlackMessageAction(params: {
if (!content && !mediaUrl && !blocks) {
throw new Error("Slack send requires message, blocks, or media.");
}
if (mediaUrl && blocks) {
throw new Error("Slack send does not support blocks with media.");
}
const threadId = readStringParam(actionParams, "threadId");
const replyTo = readStringParam(actionParams, "replyTo");
return await invoke(