@@ -45,6 +45,7 @@ class PersonaManager(LoggingConfigurable):
45
45
root_dir : str
46
46
event_loop : AbstractEventLoop
47
47
_mcp_config_loader : MCPConfigLoader
48
+ last_mentioned_persona : BasePersona | None
48
49
49
50
log : Logger # type: ignore
50
51
"""
@@ -89,6 +90,9 @@ def __init__(
89
90
90
91
# Initialize MCP config loader
91
92
self ._mcp_config_loader = MCPConfigLoader ()
93
+ # Initialize last_mentioned_persona to None
94
+ self .last_mentioned_persona = None
95
+
92
96
self ._init_persona_classes ()
93
97
self .log .info ("Persona classes loaded!" )
94
98
self ._personas = self ._init_personas ()
@@ -281,22 +285,55 @@ def route_message(self, new_message: Message):
281
285
Method that routes an incoming message to the correct persona by calling
282
286
its `process_message()` method.
283
287
284
- - (TODO) If the chat contains only one persona & one user, then this
285
- method routes all new messages to that persona.
288
+ - If the chat has multiple users, then each persona only replies
289
+ when `@`-mentioned.
290
+
291
+ - If there is only one user, the last mentioned persona replies
292
+ unless another persona is `@`-mentioned. If only one persona exists
293
+ as well, then the persona always replies, regardless of whether
294
+ it is `@`-mentioned.
286
295
287
- - Otherwise, this method only routes new messages to personas that are
288
- `@`-mentioned in the message.
289
296
"""
297
+
298
+ # Gather routing context
299
+ human_users = self .get_active_human_users ()
300
+ sender_is_persona = is_persona (new_message .sender )
290
301
mentioned_personas = self .get_mentioned_personas (new_message )
291
- if not len (mentioned_personas ):
302
+
303
+ human_user_count = len (human_users )
304
+ persona_count = len (self .personas )
305
+ mentioned_count = len (mentioned_personas )
306
+
307
+ # Don't route persona replies without mentions
308
+ if sender_is_persona and mentioned_count == 0 :
292
309
return
293
310
294
- mentioned_persona_names = [persona .name for persona in mentioned_personas ]
295
- self .log .info (
296
- f"Received new user message mentioning the following personas: { mentioned_persona_names } ."
297
- )
298
- for persona in mentioned_personas :
311
+ # Multi-user chat: require explicit @-mentions only
312
+ if human_user_count > 1 :
313
+ for persona in mentioned_personas :
314
+ self .event_loop .create_task (persona .process_message (new_message ))
315
+ return
316
+
317
+ # Single user + multiple personas case: route to mentions if they are present,
318
+ # otherwise route to last mentioned
319
+ if persona_count > 1 :
320
+ if mentioned_personas :
321
+ # Update last mentioned persona if sender is human
322
+ if not sender_is_persona :
323
+ self .last_mentioned_persona = mentioned_personas [0 ]
324
+ for persona in mentioned_personas :
325
+ asyncio .create_task (persona .process_message (new_message ))
326
+ elif self .last_mentioned_persona :
327
+ asyncio .create_task (
328
+ self .last_mentioned_persona .process_message (new_message )
329
+ )
330
+ return
331
+
332
+ # Default case (single user, 0/1 personas): persona always replies if present
333
+ for persona in self .personas .values ():
299
334
self .event_loop .create_task (persona .process_message (new_message ))
335
+ break
336
+ return
300
337
301
338
def get_chat_path (self , relative : bool = False ) -> str :
302
339
"""
@@ -345,6 +382,20 @@ def get_mcp_config(self) -> dict[str, Any]:
345
382
else :
346
383
return self ._mcp_config_loader .get_config (jdir )
347
384
385
+ def get_active_human_users (self ):
386
+ """Returns active human users in a chat session"""
387
+ users = []
388
+ for value in [k for k in self .ychat .awareness .states .values ()]:
389
+ if "user" in value .keys () and not is_persona (value ["user" ]["username" ]):
390
+ users .append (value ["user" ])
391
+
392
+ return users
393
+
394
+
395
+ def is_persona (username : str ):
396
+ """Returns true if username belongs to a persona"""
397
+ return username .startswith ("jupyter-ai-personas" )
398
+
348
399
349
400
def load_from_dir (dir : str , log : Logger ) -> list [dict ]:
350
401
"""
0 commit comments