diff --git a/src/channels/slack.ts b/src/channels/slack.ts index 587a426..cd4f4c5 100644 --- a/src/channels/slack.ts +++ b/src/channels/slack.ts @@ -43,6 +43,7 @@ export class SlackChannel implements Channel { private ownerUserId: string | null; private phantomName: string; private rejectedUsers = new Set(); + private participatedThreads = new Set(); constructor(config: SlackChannelConfig) { this.app = new App({ @@ -150,6 +151,11 @@ export class SlackChannel implements Channel { lastTs = result.ts ?? ""; } + // Track thread participation so we can respond to replies without @ mention + if (replyThreadTs) { + this.participatedThreads.add(`${channel}:${replyThreadTs}`); + } + return { id: lastTs || randomUUID(), channelId: this.id, @@ -282,6 +288,10 @@ export class SlackChannel implements Channel { } } + trackThreadParticipation(channelId: string, threadTs: string): void { + this.participatedThreads.add(`${channelId}:${threadTs}`); + } + private registerEventHandlers(): void { this.app.event("app_mention", async ({ event, client: _client }) => { if (!this.messageHandler) return; @@ -334,7 +344,13 @@ export class SlackChannel implements Channel { if (this.botUserId && userId === this.botUserId) return; const channelType = msg.channel_type as string | undefined; - if (channelType !== "im") return; + if (channelType !== "im") { + // In channels, only respond to thread replies in threads we've participated in + const incomingThreadTs = msg.thread_ts as string | undefined; + if (!incomingThreadTs) return; + const threadKey = `${msg.channel as string}:${incomingThreadTs}`; + if (!this.participatedThreads.has(threadKey)) return; + } if (userId && !this.isOwner(userId)) { console.log(`[slack] Ignoring DM from non-owner: ${userId}`); diff --git a/src/index.ts b/src/index.ts index a6e0066..f049178 100644 --- a/src/index.ts +++ b/src/index.ts @@ -450,12 +450,16 @@ async function main(): Promise { if (progressStream) { // Slack: update the progress message with the final response + feedback buttons await progressStream.finish(response.text); + if (slackChannel && slackChannelId && slackThreadTs) { + slackChannel.trackThreadParticipation(slackChannelId, slackThreadTs); + } } else if (isSlack && slackChannel && slackChannelId && slackThreadTs) { // Slack fallback: send direct reply with feedback const thinkingTs = await slackChannel.postThinking(slackChannelId, slackThreadTs); if (thinkingTs) { await slackChannel.updateWithFeedback(slackChannelId, thinkingTs, response.text); } + slackChannel.trackThreadParticipation(slackChannelId, slackThreadTs); } else { // All other channels: send via router await router.send(msg.channelId, msg.conversationId, {