* Handle channel messages (not mentions) for HITL proposed responses * * When Addie sees a message in a channel it's in, it uses the router to * determine if/how to respond. Responses are queued for admin approval.
({
event,
context,
}: SlackEventMiddlewareArgs<'message'> & { context: { botUserId?: string } })
| 3706 | * determine if/how to respond. Responses are queued for admin approval. |
| 3707 | */ |
| 3708 | async function handleChannelMessage({ |
| 3709 | event, |
| 3710 | context, |
| 3711 | }: SlackEventMiddlewareArgs<'message'> & { context: { botUserId?: string } }): Promise<void> { |
| 3712 | // Skip if not initialized |
| 3713 | if (!claudeClient || !addieDb || !addieRouter) { |
| 3714 | return; |
| 3715 | } |
| 3716 | |
| 3717 | // Skip bot messages (including our own) |
| 3718 | if ('bot_id' in event && event.bot_id) { |
| 3719 | return; |
| 3720 | } |
| 3721 | |
| 3722 | // Skip subtypes (edits, deletes, etc.) - but only for non-DM messages |
| 3723 | // DMs can have forwarded messages where text is empty but attachments have content |
| 3724 | const hasText = 'text' in event && event.text; |
| 3725 | const hasAttachments = 'attachments' in event && Array.isArray(event.attachments) && event.attachments.length > 0; |
| 3726 | const hasFiles = 'files' in event && Array.isArray(event.files) && event.files.length > 0; |
| 3727 | const hasSubtype = 'subtype' in event && event.subtype; |
| 3728 | |
| 3729 | const userId = 'user' in event ? event.user : undefined; |
| 3730 | |
| 3731 | // Handle message_deleted for any channel type (DMs included) |
| 3732 | if (hasSubtype && 'subtype' in event && event.subtype === 'message_deleted') { |
| 3733 | const deletedTs = 'deleted_ts' in event ? (event as { deleted_ts: string }).deleted_ts : undefined; |
| 3734 | if (deletedTs) { |
| 3735 | const externalId = `${event.channel}:${deletedTs}`; |
| 3736 | const threadService = getThreadService(); |
| 3737 | const deleted = await threadService.markSlackDeleted('slack', externalId); |
| 3738 | if (deleted) { |
| 3739 | logger.info({ channelId: event.channel, deletedTs }, 'Addie Bolt: Marked thread slack_deleted for deleted Slack message'); |
| 3740 | } |
| 3741 | } |
| 3742 | return; |
| 3743 | } |
| 3744 | |
| 3745 | // Handle DMs differently - route to the user message handler |
| 3746 | // For DMs, allow messages with attachments or files even if text is empty |
| 3747 | if (event.channel_type === 'im') { |
| 3748 | // Log detailed info for DM debugging (use same check as middleware for consistency) |
| 3749 | const hasThreadTs = 'thread_ts' in event && event.thread_ts !== undefined; |
| 3750 | logger.debug({ |
| 3751 | channelId: event.channel, |
| 3752 | userId, |
| 3753 | hasText, |
| 3754 | hasAttachments, |
| 3755 | hasFiles, |
| 3756 | hasSubtype, |
| 3757 | subtype: 'subtype' in event ? event.subtype : undefined, |
| 3758 | hasThreadTs, |
| 3759 | threadTs: 'thread_ts' in event ? event.thread_ts : undefined, |
| 3760 | ts: 'ts' in event ? event.ts : undefined, |
| 3761 | }, 'Addie Bolt: Received DM in handleChannelMessage'); |
| 3762 | |
| 3763 | // DMs with thread_ts are handled by the Assistant framework (handleUserMessage). |
| 3764 | // Only handle first DMs (no thread_ts) here to avoid sending duplicate responses. |
| 3765 | if (hasThreadTs) { |
nothing calls this directly
no test coverage detected