* Handle replies in threads where Addie has already participated. * * When a user replies to a thread where Addie has already responded, * we treat it as an implicit @mention and respond directly. This creates * natural conversational flow - users don't need to explicitly @mention * Addie to co
({
event,
context,
channelId,
userId,
messageText,
threadTs,
startTime,
threadService,
slackThreadMessages,
}: {
event: SlackEventMiddlewareArgs<'message'>['event'];
context: { botUserId?: string };
channelId: string;
userId: string;
messageText: string;
threadTs: string;
startTime: number;
threadService: ReturnType<typeof getThreadService>;
slackThreadMessages: Awaited<ReturnType<typeof getThreadReplies>>;
})
| 3336 | * This is similar to DM handling but with thread context included. |
| 3337 | */ |
| 3338 | async function handleActiveThreadReply({ |
| 3339 | event, |
| 3340 | context, |
| 3341 | channelId, |
| 3342 | userId, |
| 3343 | messageText, |
| 3344 | threadTs, |
| 3345 | startTime, |
| 3346 | threadService, |
| 3347 | slackThreadMessages, |
| 3348 | }: { |
| 3349 | event: SlackEventMiddlewareArgs<'message'>['event']; |
| 3350 | context: { botUserId?: string }; |
| 3351 | channelId: string; |
| 3352 | userId: string; |
| 3353 | messageText: string; |
| 3354 | threadTs: string; |
| 3355 | startTime: number; |
| 3356 | threadService: ReturnType<typeof getThreadService>; |
| 3357 | slackThreadMessages: Awaited<ReturnType<typeof getThreadReplies>>; |
| 3358 | }): Promise<void> { |
| 3359 | if (!claudeClient || !boltApp) { |
| 3360 | logger.warn('Addie Bolt: Not initialized for active thread reply'); |
| 3361 | return; |
| 3362 | } |
| 3363 | |
| 3364 | // Build external ID for thread: channel_id:thread_ts |
| 3365 | const externalId = `${channelId}:${threadTs}`; |
| 3366 | |
| 3367 | // Sanitize input |
| 3368 | const inputValidation = sanitizeInput(messageText); |
| 3369 | |
| 3370 | // Fetch channel context (includes working group if channel is linked to one) |
| 3371 | let channelContext: ThreadContext | undefined; |
| 3372 | try { |
| 3373 | channelContext = await buildChannelContext(channelId); |
| 3374 | } catch (error) { |
| 3375 | logger.debug({ error, channelId }, 'Addie Bolt: Could not get channel context for active thread reply'); |
| 3376 | } |
| 3377 | |
| 3378 | // Build thread context from the messages already fetched (avoid duplicate API call) |
| 3379 | const MAX_THREAD_CONTEXT_MESSAGES = 25; |
| 3380 | let threadContext = ''; |
| 3381 | |
| 3382 | if (slackThreadMessages.length > 0) { |
| 3383 | // Include all messages (including Addie's) for full context |
| 3384 | // but exclude the current message |
| 3385 | const filteredMessages = slackThreadMessages |
| 3386 | .filter(msg => msg.ts !== event.ts) // Exclude current message |
| 3387 | .filter(msg => (msg.text || '').trim().length > 0) |
| 3388 | .slice(-MAX_THREAD_CONTEXT_MESSAGES); |
| 3389 | |
| 3390 | // Collect user IDs for display name lookup |
| 3391 | const mentionedUserIds = new Set<string>(); |
| 3392 | for (const msg of filteredMessages) { |
| 3393 | if (msg.user && msg.user !== context.botUserId) { |
| 3394 | mentionedUserIds.add(msg.user); |
| 3395 | } |
no test coverage detected