feat: add ack reaction defaults
This commit is contained in:
@@ -5,6 +5,7 @@ import { monitorSlackProvider } from "./monitor.js";
|
||||
const sendMock = vi.fn();
|
||||
const replyMock = vi.fn();
|
||||
const updateLastRouteMock = vi.fn();
|
||||
const reactMock = vi.fn();
|
||||
let config: Record<string, unknown> = {};
|
||||
const getSlackHandlers = () =>
|
||||
(
|
||||
@@ -12,6 +13,8 @@ const getSlackHandlers = () =>
|
||||
__slackHandlers?: Map<string, (args: unknown) => Promise<void>>;
|
||||
}
|
||||
).__slackHandlers;
|
||||
const getSlackClient = () =>
|
||||
(globalThis as { __slackClient?: Record<string, unknown> }).__slackClient;
|
||||
|
||||
vi.mock("../config/config.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../config/config.js")>();
|
||||
@@ -39,20 +42,25 @@ vi.mock("@slack/bolt", () => {
|
||||
const handlers = new Map<string, (args: unknown) => Promise<void>>();
|
||||
(globalThis as { __slackHandlers?: typeof handlers }).__slackHandlers =
|
||||
handlers;
|
||||
const client = {
|
||||
auth: { test: vi.fn().mockResolvedValue({ user_id: "bot-user" }) },
|
||||
conversations: {
|
||||
info: vi.fn().mockResolvedValue({
|
||||
channel: { name: "dm", is_im: true },
|
||||
}),
|
||||
},
|
||||
users: {
|
||||
info: vi.fn().mockResolvedValue({
|
||||
user: { profile: { display_name: "Ada" } },
|
||||
}),
|
||||
},
|
||||
reactions: {
|
||||
add: (...args: unknown[]) => reactMock(...args),
|
||||
},
|
||||
};
|
||||
(globalThis as { __slackClient?: typeof client }).__slackClient = client;
|
||||
class App {
|
||||
client = {
|
||||
auth: { test: vi.fn().mockResolvedValue({ user_id: "bot-user" }) },
|
||||
conversations: {
|
||||
info: vi.fn().mockResolvedValue({
|
||||
channel: { name: "dm", is_im: true },
|
||||
}),
|
||||
},
|
||||
users: {
|
||||
info: vi.fn().mockResolvedValue({
|
||||
user: { profile: { display_name: "Ada" } },
|
||||
}),
|
||||
},
|
||||
};
|
||||
client = client;
|
||||
event(name: string, handler: (args: unknown) => Promise<void>) {
|
||||
handlers.set(name, handler);
|
||||
}
|
||||
@@ -76,13 +84,18 @@ async function waitForEvent(name: string) {
|
||||
|
||||
beforeEach(() => {
|
||||
config = {
|
||||
messages: { responsePrefix: "PFX" },
|
||||
messages: {
|
||||
responsePrefix: "PFX",
|
||||
ackReaction: "👀",
|
||||
ackReactionScope: "group-mentions",
|
||||
},
|
||||
slack: { dm: { enabled: true }, groupDm: { enabled: false } },
|
||||
routing: { allowFrom: [] },
|
||||
};
|
||||
sendMock.mockReset().mockResolvedValue(undefined);
|
||||
replyMock.mockReset();
|
||||
updateLastRouteMock.mockReset();
|
||||
reactMock.mockReset();
|
||||
});
|
||||
|
||||
describe("monitorSlackProvider tool results", () => {
|
||||
@@ -201,4 +214,48 @@ describe("monitorSlackProvider tool results", () => {
|
||||
expect(sendMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMock.mock.calls[0][2]).toMatchObject({ threadTs: "456" });
|
||||
});
|
||||
|
||||
it("reacts to mention-gated room messages when ackReaction is enabled", async () => {
|
||||
replyMock.mockResolvedValue(undefined);
|
||||
const client = getSlackClient();
|
||||
if (!client) throw new Error("Slack client not registered");
|
||||
const conversations = client.conversations as {
|
||||
info: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
conversations.info.mockResolvedValueOnce({
|
||||
channel: { name: "general", is_channel: true },
|
||||
});
|
||||
|
||||
const controller = new AbortController();
|
||||
const run = monitorSlackProvider({
|
||||
botToken: "bot-token",
|
||||
appToken: "app-token",
|
||||
abortSignal: controller.signal,
|
||||
});
|
||||
|
||||
await waitForEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
type: "message",
|
||||
user: "U1",
|
||||
text: "<@bot-user> hello",
|
||||
ts: "456",
|
||||
channel: "C1",
|
||||
channel_type: "channel",
|
||||
},
|
||||
});
|
||||
|
||||
await flush();
|
||||
controller.abort();
|
||||
await run;
|
||||
|
||||
expect(reactMock).toHaveBeenCalledWith({
|
||||
channel: "C1",
|
||||
timestamp: "456",
|
||||
name: "👀",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,6 +30,7 @@ import { getChildLogger } from "../logging.js";
|
||||
import { detectMime } from "../media/mime.js";
|
||||
import { saveMediaBuffer } from "../media/store.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { reactSlackMessage } from "./actions.js";
|
||||
import { sendMessageSlack } from "./send.js";
|
||||
import { resolveSlackAppToken, resolveSlackBotToken } from "./token.js";
|
||||
|
||||
@@ -384,6 +385,8 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
||||
);
|
||||
const textLimit = resolveTextChunkLimit(cfg, "slack");
|
||||
const mentionRegexes = buildMentionRegexes(cfg);
|
||||
const ackReaction = (cfg.messages?.ackReaction ?? "").trim();
|
||||
const ackReactionScope = cfg.messages?.ackReactionScope ?? "group-mentions";
|
||||
const mediaMaxBytes =
|
||||
(opts.mediaMaxMb ?? cfg.slack?.mediaMaxMb ?? 20) * 1024 * 1024;
|
||||
|
||||
@@ -628,6 +631,30 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
||||
});
|
||||
const rawBody = (message.text ?? "").trim() || media?.placeholder || "";
|
||||
if (!rawBody) return;
|
||||
const shouldAckReaction = () => {
|
||||
if (!ackReaction) return false;
|
||||
if (ackReactionScope === "all") return true;
|
||||
if (ackReactionScope === "direct") return isDirectMessage;
|
||||
const isGroupChat = isRoom || isGroupDm;
|
||||
if (ackReactionScope === "group-all") return isGroupChat;
|
||||
if (ackReactionScope === "group-mentions") {
|
||||
if (!isRoom) return false;
|
||||
if (!channelConfig?.requireMention) return false;
|
||||
if (!canDetectMention) return false;
|
||||
return wasMentioned || shouldBypassMention;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (shouldAckReaction() && message.ts) {
|
||||
reactSlackMessage(message.channel, message.ts, ackReaction, {
|
||||
token: botToken,
|
||||
client: app.client,
|
||||
}).catch((err) => {
|
||||
logVerbose(
|
||||
`slack react failed for channel ${message.channel}: ${String(err)}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const roomLabel = channelName ? `#${channelName}` : `#${message.channel}`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user