Skip to content

Commit 48408c6

Browse files
committed
feat: sync Slack workspace when bot joins channel
1 parent 7d5f697 commit 48408c6

File tree

2 files changed

+85
-7
lines changed

2 files changed

+85
-7
lines changed

packages/config/local/ode.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,11 +324,12 @@ export function getSlackAppToken(): string {
324324
return tokens[0]!;
325325
}
326326

327-
export function getSlackBotTokens(): Array<{ token: string; workspaceName?: string }> {
327+
export function getSlackBotTokens(): Array<{ token: string; workspaceId: string; workspaceName?: string }> {
328328
const active = getWorkspaces().filter((workspace) => workspace.status === "active");
329329
const candidates = active.length > 0 ? active : getWorkspaces();
330330
return candidates.map((workspace) => ({
331331
token: workspace.slackBotToken,
332+
workspaceId: workspace.id,
332333
workspaceName: workspace.name,
333334
}));
334335
}

packages/ims/slack/client.ts

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
getSlackTargetChannels,
77
getSlackAppToken,
88
getSlackBotTokens,
9+
invalidateOdeConfigCache,
910
getChannelAgentProvider,
1011
getChannelModel,
1112
getOpenCodeModels,
@@ -29,6 +30,7 @@ import { getSlackActionApiUrl } from "./config";
2930
import { createThrottledMessageUpdater } from "./message-updates";
3031
import { fetchThreadHistoryByClient } from "./message-history";
3132
import { registerSlackMessageRouter } from "./message-router";
33+
import { syncSlackWorkspace } from "@/core/web/local-settings";
3234

3335
export interface MessageContext {
3436
channelId: string;
@@ -44,6 +46,7 @@ let app: App | null = null;
4446

4547
type WorkspaceAuth = {
4648
botToken: string;
49+
workspaceId: string;
4750
workspaceName: string;
4851
teamId: string | null;
4952
enterpriseId: string | null;
@@ -56,6 +59,7 @@ const teamAuthMap = new Map<string, WorkspaceAuth>();
5659
const enterpriseAuthMap = new Map<string, WorkspaceAuth>();
5760
const channelWorkspaceMap = new Map<string, string>();
5861
const channelBotTokenMap = new Map<string, string>();
62+
const backgroundWorkspaceSyncInFlight = new Set<string>();
5963

6064
export function clearSlackAuthState(): void {
6165
teamAuthMap.clear();
@@ -297,12 +301,48 @@ async function postGitHubLauncher(
297301
});
298302
}
299303

300-
async function fetchWorkspaceAuth(botToken: string, workspaceName: string): Promise<WorkspaceAuth | null> {
304+
function syncWorkspaceInBackground(workspace: WorkspaceAuth, channelId: string): void {
305+
if (backgroundWorkspaceSyncInFlight.has(workspace.workspaceId)) {
306+
log.debug("Skipping Slack workspace sync; already in flight", {
307+
workspaceId: workspace.workspaceId,
308+
channelId,
309+
});
310+
return;
311+
}
312+
313+
backgroundWorkspaceSyncInFlight.add(workspace.workspaceId);
314+
void syncSlackWorkspace(workspace.workspaceId)
315+
.then((updatedWorkspace) => {
316+
invalidateOdeConfigCache();
317+
log.info("Slack workspace synced after bot joined channel", {
318+
workspaceId: workspace.workspaceId,
319+
workspaceName: updatedWorkspace.name,
320+
channelId,
321+
});
322+
})
323+
.catch((error) => {
324+
log.warn("Slack workspace sync failed after bot joined channel", {
325+
workspaceId: workspace.workspaceId,
326+
channelId,
327+
error: String(error),
328+
});
329+
})
330+
.finally(() => {
331+
backgroundWorkspaceSyncInFlight.delete(workspace.workspaceId);
332+
});
333+
}
334+
335+
async function fetchWorkspaceAuth(
336+
botToken: string,
337+
workspaceId: string,
338+
workspaceName: string
339+
): Promise<WorkspaceAuth | null> {
301340
try {
302341
const client = new WebClient(botToken);
303342
const auth = await client.auth.test();
304343
return {
305344
botToken,
345+
workspaceId,
306346
workspaceName,
307347
teamId: (auth as any).team_id ?? null,
308348
enterpriseId: (auth as any).enterprise_id ?? null,
@@ -330,24 +370,28 @@ function registerWorkspaceAuth(auth: WorkspaceAuth): void {
330370
}
331371

332372
export async function initializeWorkspaceAuth(): Promise<void> {
333-
const combined = new Map<string, string | null>();
373+
const combined = new Map<string, { workspaceId: string; workspaceName: string }>();
334374

335375
for (const record of getSlackBotTokens()) {
336-
combined.set(record.token, record.workspaceName ?? "config");
376+
combined.set(record.token, {
377+
workspaceId: record.workspaceId,
378+
workspaceName: record.workspaceName ?? "config",
379+
});
337380
}
338381

339382
if (combined.size === 0) {
340383
log.warn("No Slack bot tokens configured", { mode: "local" });
341384
}
342385

343-
for (const [botToken, workspaceName] of combined.entries()) {
386+
for (const [botToken, workspace] of combined.entries()) {
344387
if (!botToken) continue;
345-
const name = workspaceName ?? "unknown";
346-
const auth = await fetchWorkspaceAuth(botToken, name);
388+
const name = workspace.workspaceName ?? "unknown";
389+
const auth = await fetchWorkspaceAuth(botToken, workspace.workspaceId, name);
347390
if (!auth) continue;
348391
registerWorkspaceAuth(auth);
349392
log.info("Registered Slack workspace auth", {
350393
workspace: name,
394+
workspaceId: auth.workspaceId,
351395
teamId: auth.teamId,
352396
enterpriseId: auth.enterpriseId,
353397
botUserId: auth.botUserId,
@@ -501,4 +545,37 @@ export function setupMessageHandlers(): void {
501545
handleIncomingMessage: (context, text) => coreRuntime.handleIncomingMessage(context, text),
502546
});
503547

548+
const slackApp = getApp();
549+
slackApp.event("member_joined_channel", async ({ event, context, client }: any) => {
550+
const channelId = event?.channel as string | undefined;
551+
const memberId = event?.user as string | undefined;
552+
if (!channelId || !memberId) return;
553+
554+
const workspaceAuth = resolveWorkspaceAuth(
555+
event?.team as string | undefined,
556+
(event?.enterprise_id as string | undefined) ?? (context?.enterpriseId as string | undefined)
557+
);
558+
if (!workspaceAuth || memberId !== workspaceAuth.botUserId) return;
559+
560+
registerChannelBotToken(channelId, workspaceAuth.botToken ?? client?.token);
561+
if (workspaceAuth.workspaceName) {
562+
channelWorkspaceMap.set(channelId, workspaceAuth.workspaceName);
563+
}
564+
565+
if (!workspaceAuth.workspaceId) {
566+
log.warn("Bot added to channel but workspace id is missing; skipping sync", {
567+
workspaceName: workspaceAuth.workspaceName,
568+
channelId,
569+
});
570+
return;
571+
}
572+
573+
log.info("Bot added to channel; syncing Slack workspace", {
574+
workspaceId: workspaceAuth.workspaceId,
575+
workspaceName: workspaceAuth.workspaceName,
576+
channelId,
577+
});
578+
syncWorkspaceInBackground(workspaceAuth, channelId);
579+
});
580+
504581
}

0 commit comments

Comments
 (0)