66 getSlackTargetChannels ,
77 getSlackAppToken ,
88 getSlackBotTokens ,
9+ invalidateOdeConfigCache ,
910 getChannelAgentProvider ,
1011 getChannelModel ,
1112 getOpenCodeModels ,
@@ -29,6 +30,7 @@ import { getSlackActionApiUrl } from "./config";
2930import { createThrottledMessageUpdater } from "./message-updates" ;
3031import { fetchThreadHistoryByClient } from "./message-history" ;
3132import { registerSlackMessageRouter } from "./message-router" ;
33+ import { syncSlackWorkspace } from "@/core/web/local-settings" ;
3234
3335export interface MessageContext {
3436 channelId : string ;
@@ -44,6 +46,7 @@ let app: App | null = null;
4446
4547type 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>();
5659const enterpriseAuthMap = new Map < string , WorkspaceAuth > ( ) ;
5760const channelWorkspaceMap = new Map < string , string > ( ) ;
5861const channelBotTokenMap = new Map < string , string > ( ) ;
62+ const backgroundWorkspaceSyncInFlight = new Set < string > ( ) ;
5963
6064export 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
332372export 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