Fix Slack canvas action types

This commit is contained in:
Alfonso 2026-01-26 00:26:05 -05:00
parent 6ddef0e869
commit d421bbeb48
6 changed files with 48 additions and 32 deletions

View File

@ -131,10 +131,7 @@ describe("handleSlackAction", () => {
it("requires canvases to be enabled", async () => {
const cfg = { channels: { slack: { botToken: "tok" } } } as ClawdbotConfig;
await expect(
handleSlackAction(
{ action: "createCanvas", title: "Test", content: "hello" },
cfg,
),
handleSlackAction({ action: "createCanvas", title: "Test", content: "hello" }, cfg),
).rejects.toThrow(/Slack canvases are disabled/);
});
@ -142,10 +139,7 @@ describe("handleSlackAction", () => {
const cfg = {
channels: { slack: { botToken: "tok", actions: { canvases: true } } },
} as ClawdbotConfig;
await handleSlackAction(
{ action: "createCanvas", title: "Test", content: "hello" },
cfg,
);
await handleSlackAction({ action: "createCanvas", title: "Test", content: "hello" }, cfg);
expect(createSlackCanvas).toHaveBeenCalledWith(
{ title: "Test", content: "hello", channelId: undefined },
undefined,
@ -156,10 +150,7 @@ describe("handleSlackAction", () => {
const cfg = {
channels: { slack: { botToken: "tok", actions: { canvases: true } } },
} as ClawdbotConfig;
await handleSlackAction(
{ action: "updateCanvas", canvasId: "C123", content: "update" },
cfg,
);
await handleSlackAction({ action: "updateCanvas", canvasId: "C123", content: "update" }, cfg);
expect(updateSlackCanvas).toHaveBeenCalledWith(
{ canvasId: "C123", channelId: undefined, content: "update", updateMode: undefined },
undefined,

View File

@ -47,6 +47,8 @@ export const CHANNEL_MESSAGE_ACTION_NAMES = [
"timeout",
"kick",
"ban",
"canvas-create",
"canvas-update",
] as const;
export type ChannelMessageActionName = (typeof CHANNEL_MESSAGE_ACTION_NAMES)[number];

View File

@ -56,11 +56,12 @@ describe("extractCanvasRefsFromEvent", () => {
describe("fetchSlackCanvasContent", () => {
it("downloads and parses JSON canvas content", async () => {
const fetchMock = vi.fn(async () =>
new Response(JSON.stringify({ text: "hello canvas" }), {
status: 200,
headers: { "content-type": "application/json" },
}),
const fetchMock = vi.fn(
async () =>
new Response(JSON.stringify({ text: "hello canvas" }), {
status: 200,
headers: { "content-type": "application/json" },
}),
);
vi.stubGlobal("fetch", fetchMock);
@ -127,11 +128,12 @@ describe("fetchSlackCanvasContent", () => {
});
it("truncates large payloads", async () => {
const fetchMock = vi.fn(async () =>
new Response(JSON.stringify({ text: "1234567890" }), {
status: 200,
headers: { "content-type": "application/json" },
}),
const fetchMock = vi.fn(
async () =>
new Response(JSON.stringify({ text: "1234567890" }), {
status: 200,
headers: { "content-type": "application/json" },
}),
);
vi.stubGlobal("fetch", fetchMock);

View File

@ -173,7 +173,10 @@ function collectJsonText(
}
}
function extractTextFromJson(payload: unknown, maxChars: number): { text: string; truncated: boolean } {
function extractTextFromJson(
payload: unknown,
maxChars: number,
): { text: string; truncated: boolean } {
const results: string[] = [];
const seen = new Set<string>();
collectJsonText(payload, { maxChars, results, seen, depth: 0 });
@ -197,10 +200,22 @@ export async function fetchSlackCanvasContent(params: {
return { ok: false, error: "no_file_id" };
}
let fileInfo: { file?: SlackFile & { title?: string; name?: string; url_private?: string; url_private_download?: string } };
let fileInfo: {
file?: SlackFile & {
title?: string;
name?: string;
url_private?: string;
url_private_download?: string;
};
};
try {
fileInfo = (await client.files.info({ file: fileId })) as {
file?: SlackFile & { title?: string; name?: string; url_private?: string; url_private_download?: string };
file?: SlackFile & {
title?: string;
name?: string;
url_private?: string;
url_private_download?: string;
};
};
} catch (err) {
const label = buildErrorLabel(err);
@ -242,7 +257,11 @@ export async function fetchSlackCanvasContent(params: {
let extracted: { text: string; truncated: boolean };
let rawFormat: SlackCanvasContent["rawFormat"] = "unknown";
if (contentType.includes("application/json") || text.trim().startsWith("{") || text.trim().startsWith("[")) {
if (
contentType.includes("application/json") ||
text.trim().startsWith("{") ||
text.trim().startsWith("[")
) {
rawFormat = "json";
try {
const payload = JSON.parse(text);

View File

@ -151,11 +151,12 @@ describe("slack prepareSlackMessage inbound contract", () => {
});
it("appends canvas content to the inbound body", async () => {
const fetchMock = vi.fn(async () =>
new Response(JSON.stringify({ title: "Canvas", text: "hello canvas" }), {
status: 200,
headers: { "content-type": "application/json" },
}),
const fetchMock = vi.fn(
async () =>
new Response(JSON.stringify({ title: "Canvas", text: "hello canvas" }), {
status: 200,
headers: { "content-type": "application/json" },
}),
);
vi.stubGlobal("fetch", fetchMock);

View File

@ -371,7 +371,8 @@ export async function prepareSlackMessage(params: {
}
const canvasContext = canvasBlocks.length > 0 ? canvasBlocks.join("\n\n") : "";
const baseBody = (message.text ?? "").trim() || media?.placeholder || (canvasContext ? "[Slack canvas]" : "");
const baseBody =
(message.text ?? "").trim() || media?.placeholder || (canvasContext ? "[Slack canvas]" : "");
if (!baseBody) return null;
const rawBody = baseBody;
const rawBodyWithCanvas = canvasContext ? `${rawBody}\n\n${canvasContext}` : rawBody;