Skip to content

Commit 4d1654c

Browse files
LIU9293Joshua Chittick
andauthored
Sync Slack workspace on mention (#70)
* sync workspace on mention for unseen channels * allow slack mentions from any channel --------- Co-authored-by: Joshua Chittick <josh@smqx.org>
1 parent f4dced3 commit 4d1654c

File tree

4 files changed

+61
-62
lines changed

4 files changed

+61
-62
lines changed

.agents/skills/slack-developer-researcher/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ Ask clarifying questions if you need to focus on specific endpoints, scopes, or
1313

1414
## Sources
1515
- https://docs.slack.dev/apis/
16+
- https://docs.slack.dev/reference/events
1617
- https://docs.slack.dev/ai/developing-ai-apps

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ode",
3-
"version": "0.0.42",
3+
"version": "0.0.43",
44
"description": "Ode - OpenCode chat controller for Slack",
55
"module": "packages/core/index.ts",
66
"type": "module",

packages/ims/slack/client.ts

Lines changed: 32 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -352,35 +352,46 @@ async function postGitHubLauncher(
352352
});
353353
}
354354

355-
function syncWorkspaceInBackground(workspace: WorkspaceAuth, channelId: string): void {
355+
async function syncWorkspaceAfterMention(
356+
channelId: string,
357+
workspace: { workspaceId?: string; workspaceName?: string } | undefined
358+
): Promise<boolean> {
359+
if (!workspace?.workspaceId) {
360+
log.warn("Skipping Slack workspace sync; workspace id missing", {
361+
channelId,
362+
workspaceName: workspace?.workspaceName,
363+
});
364+
return false;
365+
}
366+
356367
if (backgroundWorkspaceSyncInFlight.has(workspace.workspaceId)) {
357368
log.debug("Skipping Slack workspace sync; already in flight", {
358369
workspaceId: workspace.workspaceId,
359370
channelId,
360371
});
361-
return;
372+
return false;
362373
}
363374

364375
backgroundWorkspaceSyncInFlight.add(workspace.workspaceId);
365-
void syncSlackWorkspace(workspace.workspaceId)
366-
.then((updatedWorkspace) => {
367-
invalidateOdeConfigCache();
368-
log.info("Slack workspace synced after bot joined channel", {
369-
workspaceId: workspace.workspaceId,
370-
workspaceName: updatedWorkspace.name,
371-
channelId,
372-
});
373-
})
374-
.catch((error) => {
375-
log.warn("Slack workspace sync failed after bot joined channel", {
376-
workspaceId: workspace.workspaceId,
377-
channelId,
378-
error: String(error),
379-
});
380-
})
381-
.finally(() => {
382-
backgroundWorkspaceSyncInFlight.delete(workspace.workspaceId);
376+
try {
377+
const updatedWorkspace = await syncSlackWorkspace(workspace.workspaceId);
378+
invalidateOdeConfigCache();
379+
log.info("Slack workspace synced after mention in unseen channel", {
380+
workspaceId: workspace.workspaceId,
381+
workspaceName: updatedWorkspace.name,
382+
channelId,
383+
});
384+
return true;
385+
} catch (error) {
386+
log.warn("Slack workspace sync failed after mention in unseen channel", {
387+
workspaceId: workspace.workspaceId,
388+
channelId,
389+
error: String(error),
383390
});
391+
return false;
392+
} finally {
393+
backgroundWorkspaceSyncInFlight.delete(workspace.workspaceId);
394+
}
384395
}
385396

386397
async function fetchWorkspaceAuth(
@@ -583,6 +594,7 @@ export function setupMessageHandlers(): void {
583594
app: slackApp,
584595
isAuthorizedChannel,
585596
resolveWorkspaceAuth,
597+
syncWorkspaceForChannel: syncWorkspaceAfterMention,
586598
getChannelWorkspaceName: (channelId) => channelWorkspaceMap.get(channelId),
587599
setChannelWorkspaceName: (channelId, workspaceName) => {
588600
channelWorkspaceMap.set(channelId, workspaceName);
@@ -607,37 +619,6 @@ export function setupMessageHandlers(): void {
607619
handleIncomingMessage: (context, text) => coreRuntime.handleIncomingMessage(context, text),
608620
});
609621

610-
slackApp.event("member_joined_channel", async ({ event, context }: any) => {
611-
const channelId = event?.channel as string | undefined;
612-
const memberId = event?.user as string | undefined;
613-
if (!channelId || !memberId) return;
614-
615-
const workspaceAuth = resolveWorkspaceAuth(
616-
event?.team as string | undefined,
617-
(event?.enterprise_id as string | undefined) ?? (context?.enterpriseId as string | undefined)
618-
);
619-
if (!workspaceAuth || memberId !== workspaceAuth.botUserId) return;
620-
621-
channelWorkspaceAuthMap.set(channelId, workspaceAuth);
622-
if (workspaceAuth.workspaceName) {
623-
channelWorkspaceMap.set(channelId, workspaceAuth.workspaceName);
624-
}
625-
626-
if (!workspaceAuth.workspaceId) {
627-
log.warn("Bot added to channel but workspace id is missing; skipping sync", {
628-
workspaceName: workspaceAuth.workspaceName,
629-
channelId,
630-
});
631-
return;
632-
}
633-
634-
log.info("Bot added to channel; syncing Slack workspace", {
635-
workspaceId: workspaceAuth.workspaceId,
636-
workspaceName: workspaceAuth.workspaceName,
637-
channelId,
638-
});
639-
syncWorkspaceInBackground(workspaceAuth, channelId);
640-
});
641622
}
642623

643624
}

packages/ims/slack/message-router.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ type RouterDeps = {
66
resolveWorkspaceAuth: (
77
teamId?: string,
88
enterpriseId?: string
9-
) => { workspaceName?: string; botToken?: string; [key: string]: unknown } | undefined;
9+
) => { workspaceId?: string; workspaceName?: string; botToken?: string; [key: string]: unknown } | undefined;
10+
syncWorkspaceForChannel: (
11+
channelId: string,
12+
workspaceAuth: { workspaceId?: string; workspaceName?: string; botToken?: string; [key: string]: unknown } | undefined
13+
) => Promise<boolean>;
1014
getChannelWorkspaceName: (channelId: string) => string | undefined;
1115
setChannelWorkspaceName: (channelId: string, workspaceName: string) => void;
1216
setChannelWorkspaceAuth: (
1317
channelId: string,
14-
auth: { workspaceName?: string; botToken?: string; [key: string]: unknown } | undefined
18+
auth: { workspaceId?: string; workspaceName?: string; botToken?: string; [key: string]: unknown } | undefined
1519
) => void;
1620
isThreadActive: (channelId: string, threadId: string) => boolean;
1721
markThreadActive: (channelId: string, threadId: string) => void;
@@ -100,6 +104,18 @@ async function maybeHandleStopCommand(
100104
return true;
101105
}
102106

107+
async function maybeRefreshWorkspaceForMention(params: {
108+
deps: RouterDeps;
109+
channelId: string;
110+
isMention: boolean;
111+
workspaceAuth: WorkspaceAuth;
112+
}): Promise<void> {
113+
const { deps, channelId, isMention, workspaceAuth } = params;
114+
if (!isMention) return;
115+
if (deps.isAuthorizedChannel(channelId)) return;
116+
await deps.syncWorkspaceForChannel(channelId, workspaceAuth);
117+
}
118+
103119
async function maybeNotifySettingsIssues(
104120
deps: RouterDeps,
105121
channelId: string,
@@ -155,14 +171,9 @@ export function registerSlackMessageRouter(deps: RouterDeps): void {
155171

156172
const { channelId, userId, text, threadId, messageId } = incoming;
157173

158-
if (!deps.isAuthorizedChannel(channelId)) {
159-
log.info("[DROP] Unauthorized channel", { channelId });
160-
return;
161-
}
162-
163174
const authResult = await client.auth.test();
164175
const currentBotUserId = authResult.user_id as string;
165-
syncWorkspaceAuth(
176+
const workspaceAuth = syncWorkspaceAuth(
166177
deps,
167178
channelId,
168179
authResult.team_id,
@@ -174,11 +185,17 @@ export function registerSlackMessageRouter(deps: RouterDeps): void {
174185
return;
175186
}
176187

188+
const isMention = currentBotUserId ? text.includes(`<@${currentBotUserId}>`) : false;
189+
await maybeRefreshWorkspaceForMention({
190+
deps,
191+
channelId,
192+
isMention,
193+
workspaceAuth,
194+
});
195+
177196
if (await maybeHandleStopCommand(deps, text, channelId, threadId, say)) {
178197
return;
179198
}
180-
181-
const isMention = currentBotUserId ? text.includes(`<@${currentBotUserId}>`) : false;
182199
const threadActive = deps.isThreadActive(channelId, threadId);
183200

184201
if (shouldDropForThreadContext(isMention, threadActive)) {

0 commit comments

Comments
 (0)