Skip to content

Commit 42ce92d

Browse files
author
Joshua Chittick
committed
refactor: harden slack message routing hot path
1 parent b56949d commit 42ce92d

File tree

2 files changed

+258
-72
lines changed

2 files changed

+258
-72
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { describe, expect, it, mock } from "bun:test";
2+
import { registerSlackMessageRouter } from "./message-router";
3+
4+
function createDeps(overrides: Partial<Parameters<typeof registerSlackMessageRouter>[0]> = {}) {
5+
const handleIncomingMessage = mock(async () => {});
6+
7+
return {
8+
app: {
9+
message: (handler: unknown) => handler,
10+
},
11+
isAuthorizedChannel: () => true,
12+
resolveWorkspaceAuth: () => undefined,
13+
syncWorkspaceForChannel: async () => false,
14+
getChannelWorkspaceName: () => "workspace",
15+
setChannelWorkspaceName: () => {},
16+
setChannelWorkspaceAuth: () => {},
17+
isThreadActive: () => false,
18+
markThreadActive: () => {},
19+
isGeneralSettingsCommand: () => false,
20+
postGeneralSettingsLauncher: async () => {},
21+
describeSettingsIssues: () => [],
22+
getChannelAgentProvider: () => "opencode" as const,
23+
handleStopCommand: async () => false,
24+
handleIncomingMessage,
25+
...overrides,
26+
};
27+
}
28+
29+
describe("registerSlackMessageRouter", () => {
30+
it("caches bot identity and avoids auth.test on every message", async () => {
31+
let registeredHandler: ((args: any) => Promise<void>) | undefined;
32+
const authTest = mock(async () => ({ user_id: "U_BOT", team_id: "T1", enterprise_id: "E1" }));
33+
const handleIncomingMessage = mock(async () => {});
34+
const deps = createDeps({
35+
app: {
36+
message: (handler: (args: any) => Promise<void>) => {
37+
registeredHandler = handler;
38+
},
39+
},
40+
handleIncomingMessage,
41+
});
42+
43+
registerSlackMessageRouter(deps);
44+
expect(registeredHandler).toBeDefined();
45+
46+
const say = mock(async () => {});
47+
48+
await registeredHandler!({
49+
message: {
50+
channel: "C1",
51+
user: "U1",
52+
text: "<@U_BOT> hello",
53+
ts: "1710000000.000001",
54+
},
55+
client: {
56+
auth: {
57+
test: authTest,
58+
},
59+
},
60+
context: { teamId: "T1", enterpriseId: "E1" },
61+
body: { team_id: "T1", enterprise_id: "E1" },
62+
say,
63+
});
64+
65+
await registeredHandler!({
66+
message: {
67+
channel: "C1",
68+
user: "U2",
69+
text: "<@U_BOT> again",
70+
ts: "1710000000.000002",
71+
},
72+
client: {
73+
auth: {
74+
test: authTest,
75+
},
76+
},
77+
context: { teamId: "T1", enterpriseId: "E1" },
78+
body: { team_id: "T1", enterprise_id: "E1" },
79+
say,
80+
});
81+
82+
expect(authTest).toHaveBeenCalledTimes(1);
83+
expect(handleIncomingMessage).toHaveBeenCalledTimes(2);
84+
});
85+
86+
it("handles message processing errors with a thread reply", async () => {
87+
let registeredHandler: ((args: any) => Promise<void>) | undefined;
88+
const deps = createDeps({
89+
app: {
90+
message: (handler: (args: any) => Promise<void>) => {
91+
registeredHandler = handler;
92+
},
93+
},
94+
handleIncomingMessage: async () => {
95+
throw new Error("boom");
96+
},
97+
});
98+
99+
registerSlackMessageRouter(deps);
100+
expect(registeredHandler).toBeDefined();
101+
102+
const say = mock(async () => {});
103+
104+
await registeredHandler!({
105+
message: {
106+
channel: "C1",
107+
user: "U1",
108+
text: "<@U_BOT> fail",
109+
ts: "1710000000.000003",
110+
},
111+
client: {
112+
auth: {
113+
test: async () => ({ user_id: "U_BOT", team_id: "T1" }),
114+
},
115+
},
116+
context: { teamId: "T1" },
117+
body: { team_id: "T1" },
118+
say,
119+
});
120+
121+
expect(say).toHaveBeenCalledWith({
122+
text: "I hit an internal error while handling that message. Please try again.",
123+
thread_ts: "1710000000.000003",
124+
});
125+
});
126+
});

packages/ims/slack/message-router.ts

Lines changed: 132 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ type RouterDeps = {
3636

3737
type WorkspaceAuth = ReturnType<RouterDeps["resolveWorkspaceAuth"]>;
3838

39+
type BotIdentity = {
40+
botUserId: string;
41+
teamId?: string;
42+
enterpriseId?: string;
43+
};
44+
3945
type IncomingMessageData = {
4046
channelId: string;
4147
userId: string;
@@ -157,90 +163,144 @@ async function maybeHandleLauncherCommand(params: {
157163
return true;
158164
}
159165

160-
export function registerSlackMessageRouter(deps: RouterDeps): void {
161-
const slackApp = deps.app;
162-
163-
slackApp.message(async ({ message, say, client }: any) => {
164-
const incoming = extractIncomingMessageData(message);
165-
if (!incoming) return;
166-
167-
const { channelId, userId, text, threadId, messageId } = incoming;
168-
169-
const authResult = await client.auth.test();
170-
const currentBotUserId = authResult.user_id as string;
171-
const workspaceAuth = syncWorkspaceAuth(
172-
deps,
173-
channelId,
174-
authResult.team_id,
175-
authResult.enterprise_id ?? undefined
176-
);
166+
function getCacheKey(teamId?: string, enterpriseId?: string): string {
167+
if (teamId) return `team:${teamId}`;
168+
if (enterpriseId) return `enterprise:${enterpriseId}`;
169+
return "default";
170+
}
177171

178-
if (userId === currentBotUserId) {
179-
log.debug("[DROP] Message from bot user", { channelId, userId });
180-
return;
181-
}
172+
async function getBotIdentity(params: {
173+
client: any;
174+
cache: Map<string, BotIdentity>;
175+
teamId?: string;
176+
enterpriseId?: string;
177+
}): Promise<BotIdentity> {
178+
const { client, cache, teamId, enterpriseId } = params;
179+
const key = getCacheKey(teamId, enterpriseId);
180+
const cached = cache.get(key);
181+
if (cached) {
182+
return cached;
183+
}
182184

183-
const isMention = currentBotUserId ? text.includes(`<@${currentBotUserId}>`) : false;
184-
await maybeRefreshWorkspaceForMention({
185-
deps,
186-
channelId,
187-
isMention,
188-
workspaceAuth,
189-
});
185+
const authResult = await client.auth.test();
186+
const identity: BotIdentity = {
187+
botUserId: (authResult.user_id as string) || "",
188+
teamId: (authResult.team_id as string | undefined) ?? teamId,
189+
enterpriseId: (authResult.enterprise_id as string | undefined) ?? enterpriseId,
190+
};
190191

191-
if (await maybeHandleStopCommand(deps, text, channelId, threadId, say)) {
192-
return;
193-
}
194-
const threadActive = deps.isThreadActive(channelId, threadId);
192+
cache.set(key, identity);
193+
return identity;
194+
}
195195

196-
if (shouldDropForThreadContext(isMention, threadActive)) {
197-
log.debug("[DROP] Not mentioned and thread inactive", { channelId, threadId });
198-
return;
199-
}
196+
export function registerSlackMessageRouter(deps: RouterDeps): void {
197+
const slackApp = deps.app;
198+
const botIdentityCache = new Map<string, BotIdentity>();
199+
200+
slackApp.message(async ({ message, say, client, context, body }: any) => {
201+
let contextData: IncomingMessageData | null = null;
202+
203+
try {
204+
const incoming = extractIncomingMessageData(message);
205+
if (!incoming) return;
206+
contextData = incoming;
207+
208+
const { channelId, userId, text, threadId, messageId } = incoming;
209+
const identity = await getBotIdentity({
210+
client,
211+
cache: botIdentityCache,
212+
teamId: (context?.teamId as string | undefined) ?? (body?.team_id as string | undefined),
213+
enterpriseId: (context?.enterpriseId as string | undefined) ?? (body?.enterprise_id as string | undefined),
214+
});
200215

201-
if (shouldDropForOtherMentions(text, isMention)) {
202-
log.info("[DROP] Mentions other user", { channelId, threadId });
203-
return;
204-
}
216+
const currentBotUserId = identity.botUserId;
217+
const workspaceAuth = syncWorkspaceAuth(
218+
deps,
219+
channelId,
220+
identity.teamId,
221+
identity.enterpriseId
222+
);
223+
224+
if (userId === currentBotUserId) {
225+
log.debug("[DROP] Message from bot user", { channelId, userId });
226+
return;
227+
}
228+
229+
const isMention = currentBotUserId ? text.includes(`<@${currentBotUserId}>`) : false;
230+
await maybeRefreshWorkspaceForMention({
231+
deps,
232+
channelId,
233+
isMention,
234+
workspaceAuth,
235+
});
205236

206-
deps.markThreadActive(channelId, threadId);
237+
if (await maybeHandleStopCommand(deps, text, channelId, threadId, say)) {
238+
return;
239+
}
240+
const threadActive = deps.isThreadActive(channelId, threadId);
207241

208-
const cleanText = stripBotMention(text, currentBotUserId);
242+
if (shouldDropForThreadContext(isMention, threadActive)) {
243+
log.debug("[DROP] Not mentioned and thread inactive", { channelId, threadId });
244+
return;
245+
}
209246

210-
if (await maybeHandleLauncherCommand({
211-
deps,
212-
cleanText,
213-
isMention,
214-
channelId,
215-
userId,
216-
client,
217-
})) {
218-
return;
219-
}
247+
if (shouldDropForOtherMentions(text, isMention)) {
248+
log.info("[DROP] Mentions other user", { channelId, threadId });
249+
return;
250+
}
220251

221-
if (await maybeNotifySettingsIssues(deps, channelId, threadId, userId, client, say)) {
222-
return;
223-
}
252+
deps.markThreadActive(channelId, threadId);
224253

225-
const workspaceName = deps.getChannelWorkspaceName(channelId) || "unknown";
226-
if (!cleanText) {
227-
await say({
228-
text: "Hi! How can I help you? Just ask me anything.",
229-
thread_ts: threadId,
230-
});
231-
return;
232-
}
254+
const cleanText = stripBotMention(text, currentBotUserId);
233255

234-
await deps.handleIncomingMessage(
235-
{
256+
if (await maybeHandleLauncherCommand({
257+
deps,
258+
cleanText,
259+
isMention,
236260
channelId,
237-
replyThreadId: threadId,
238-
threadId,
239261
userId,
240-
messageId,
241-
workspaceName,
242-
},
243-
cleanText
244-
);
262+
client,
263+
})) {
264+
return;
265+
}
266+
267+
if (await maybeNotifySettingsIssues(deps, channelId, threadId, userId, client, say)) {
268+
return;
269+
}
270+
271+
const workspaceName = deps.getChannelWorkspaceName(channelId) || "unknown";
272+
if (!cleanText) {
273+
await say({
274+
text: "Hi! How can I help you? Just ask me anything.",
275+
thread_ts: threadId,
276+
});
277+
return;
278+
}
279+
280+
await deps.handleIncomingMessage(
281+
{
282+
channelId,
283+
replyThreadId: threadId,
284+
threadId,
285+
userId,
286+
messageId,
287+
workspaceName,
288+
},
289+
cleanText
290+
);
291+
} catch (error) {
292+
log.error("Slack message router failed", {
293+
channelId: contextData?.channelId,
294+
threadId: contextData?.threadId,
295+
messageId: contextData?.messageId,
296+
error: String(error),
297+
});
298+
if (contextData) {
299+
await say({
300+
text: "I hit an internal error while handling that message. Please try again.",
301+
thread_ts: contextData.threadId,
302+
});
303+
}
304+
}
245305
});
246306
}

0 commit comments

Comments
 (0)