@@ -135,11 +135,16 @@ def start_conversation(
135135 def _register_handlers (self ) -> None :
136136 """Register Slack event handlers."""
137137
138- @self .app .event ("app_mention" )
139- async def handle_app_mention ( # pyright: ignore[reportUnusedFunction]
140- event : Dict [str , Any ], say : AsyncSay , logger : logging .Logger
138+ async def process_message (
139+ event : Dict [str , Any ], logger : logging .Logger , is_mention : bool = False
141140 ) -> None :
142- """Handle app mentions by processing through conversation tree."""
141+ """Common message processing logic for both mentions and thread replies.
142+
143+ Args:
144+ event: Slack event data
145+ logger: Logger instance
146+ is_mention: Whether this is an explicit mention (True) or thread reply (False)
147+ """
143148 try :
144149 text = event .get ("text" , "" )
145150 channel = event .get ("channel" )
@@ -162,7 +167,7 @@ async def handle_app_mention( # pyright: ignore[reportUnusedFunction]
162167 assert isinstance (thread_ts , str )
163168
164169 logger .info (
165- f"Received mention from user { user } in channel { channel } : { text } "
170+ f"Received { ' mention' if is_mention else 'thread message' } from user { user } in channel { channel } : { text } "
166171 )
167172
168173 # Get slack thread messages
@@ -187,7 +192,7 @@ async def handle_app_mention( # pyright: ignore[reportUnusedFunction]
187192 self .start_conversation (args )
188193
189194 except Exception as e :
190- logger .error (f"Error handling app mention : { e } " , exc_info = True )
195+ logger .error (f"Error handling message : { e } " , exc_info = True )
191196 channel = event .get ("channel" )
192197 if channel :
193198 await self ._send_reply (
@@ -196,6 +201,57 @@ async def handle_app_mention( # pyright: ignore[reportUnusedFunction]
196201 f"Sorry, I encountered an error: { str (e )} " ,
197202 )
198203
204+ @self .app .event ("app_mention" )
205+ async def handle_app_mention ( # pyright: ignore[reportUnusedFunction]
206+ event : Dict [str , Any ], say : AsyncSay , logger : logging .Logger
207+ ) -> None :
208+ """Handle app mentions by processing through conversation tree."""
209+ await process_message (event , logger , is_mention = True )
210+
211+ @self .app .event ("message" )
212+ async def handle_message ( # pyright: ignore[reportUnusedFunction]
213+ event : Dict [str , Any ], logger : logging .Logger
214+ ) -> None :
215+ """Handle messages in threads where bot is participating."""
216+ logger .debug (f"Received message event: { event } " )
217+
218+ # Ignore messages that are not in threads
219+ if "thread_ts" not in event :
220+ logger .debug ("Ignoring non-thread message" )
221+ return
222+
223+ # Ignore bot's own messages
224+ if event .get ("bot_id" ):
225+ logger .debug ("Ignoring bot's own message" )
226+ return
227+
228+ # Ignore subtypes (like message_changed, message_deleted, etc.)
229+ if event .get ("subtype" ):
230+ logger .debug (f"Ignoring message with subtype: { event .get ('subtype' )} " )
231+ return
232+
233+ channel = event .get ("channel" )
234+ thread_ts = event .get ("thread_ts" )
235+
236+ if not channel or not thread_ts :
237+ logger .debug ("Missing channel or thread_ts" )
238+ return
239+
240+ logger .debug (f"Checking if bot is participating in thread { thread_ts } " )
241+
242+ # Check if bot has participated in this thread
243+ is_participating = await self ._is_bot_in_thread (channel , thread_ts )
244+
245+ logger .debug (f"Bot participating in thread: { is_participating } " )
246+
247+ if is_participating :
248+ logger .info (
249+ f"Processing thread message in channel { channel } , thread { thread_ts } "
250+ )
251+ await process_message (event , logger , is_mention = False )
252+ else :
253+ logger .debug ("Bot not participating in this thread, ignoring message" )
254+
199255 async def _get_thread_messages (self , channel : str , thread_ts : str ) -> List [str ]:
200256 """Get all messages from a thread.
201257
@@ -225,6 +281,54 @@ async def _get_thread_messages(self, channel: str, thread_ts: str) -> List[str]:
225281 logger .error (f"Error getting thread messages: { e } " , exc_info = True )
226282 return []
227283
284+ async def _is_bot_in_thread (self , channel : str , thread_ts : str ) -> bool :
285+ """Check if the bot has participated in a thread.
286+
287+ Args:
288+ channel: Slack channel ID
289+ thread_ts: Thread timestamp
290+
291+ Returns:
292+ True if bot has sent messages in the thread, False otherwise
293+ """
294+ try :
295+ # Get bot's user ID
296+ auth_result = await self .app .client .auth_test ()
297+ bot_user_id = auth_result .get ("user_id" )
298+
299+ if not bot_user_id :
300+ logger .warning ("Could not get bot user ID" )
301+ return False
302+
303+ logger .debug (f"Bot user ID: { bot_user_id } " )
304+
305+ # Get thread messages
306+ result = await self .app .client .conversations_replies (
307+ channel = channel , ts = thread_ts
308+ )
309+
310+ messages = result .get ("messages" , [])
311+ logger .debug (f"Found { len (messages )} messages in thread { thread_ts } " )
312+
313+ # Check if any message is from the bot
314+ for msg in messages :
315+ msg_user = msg .get ("user" )
316+ msg_bot_id = msg .get ("bot_id" )
317+ logger .debug (f"Message from user={ msg_user } , bot_id={ msg_bot_id } " )
318+
319+ if msg_user == bot_user_id or msg_bot_id :
320+ logger .debug (f"Found bot message in thread" )
321+ return True
322+
323+ logger .debug (f"No bot messages found in thread" )
324+ return False
325+
326+ except Exception as e :
327+ logger .error (
328+ f"Error checking bot participation in thread: { e } " , exc_info = True
329+ )
330+ return False
331+
228332 def create_queue_listener (
229333 self ,
230334 queue : janus .Queue [str ],
0 commit comments