@@ -40,12 +40,16 @@ def __init__(self, agent=None):
4040 self .ctx = None
4141 self .agent = agent
4242 self .current_emotion = "waiting" # Track current emotional state
43+ self .previous_emotion = "" # Track previous emotion for smooth transitions
4344 self .emotion_history = [] # Log emotional journey
4445 self .ending_conversation = False # Flag to prevent timer conflicts during goodbye
4546
4647 # Text tracking for TTS events
4748 self .current_speech_preview = "" # Preview text for "started" events
4849 self .current_speech_full_text = "" # Accumulated full text for "finished" events
50+
51+ # Tool tracking
52+ self .last_tool_used = "" # Last function tool called
4953 self .virtual_request_queue = [] # Queue for virtual coffee requests
5054 self .announcing_virtual_request = False # Flag to prevent conflicts during announcements
5155 self .recent_greetings = [] # Track recent greetings to avoid repetition
@@ -67,6 +71,23 @@ async def transition_to_state(self, new_state: AgentState):
6771 await self ._exit_current_state ()
6872 self .current_state = new_state
6973 await self ._enter_new_state ()
74+
75+ # Send agent status update for behavioral mode changes
76+ behavioral_mode_map = {
77+ AgentState .DORMANT : "dormant" ,
78+ AgentState .CONNECTING : "connecting" ,
79+ AgentState .ACTIVE : "active" ,
80+ AgentState .DISCONNECTING : "disconnecting"
81+ }
82+
83+ behavioral_mode = behavioral_mode_map .get (new_state , "dormant" )
84+
85+ # Determine conversation phase for greetings
86+ conversation_phase = ""
87+ if new_state == AgentState .ACTIVE :
88+ conversation_phase = "greeting"
89+
90+ await self ._send_agent_status (behavioral_mode , "idle" , conversation_phase )
7091
7192 async def _exit_current_state (self ):
7293 """Clean up current state"""
@@ -262,23 +283,26 @@ def on_agent_state_changed(event):
262283 """Handle agent state changes (initializing/listening/thinking/speaking)"""
263284 logger .info (f"🔍 DEBUG: agent_state_changed: { event .old_state } → { event .new_state } " )
264285
265- # Send TTS events based on state transitions
286+ # Send unified agent status based on state transitions
266287 async def handle_state_change ():
267288 try :
289+ # Map LiveKit agent states to our behavioral modes
290+ current_behavioral_mode = "dormant" # Default
291+ if self .current_state == AgentState .ACTIVE :
292+ current_behavioral_mode = "active"
293+ elif self .current_state == AgentState .CONNECTING :
294+ current_behavioral_mode = "connecting"
295+ elif self .current_state == AgentState .DISCONNECTING :
296+ current_behavioral_mode = "disconnecting"
297+
268298 if event .new_state == "speaking" :
269- logger .info ("🔍 DEBUG: Agent started speaking - sending TTS started event" )
270- current_emotion = self .current_emotion
271- # Use preview text for started event
272- text_to_send = self .current_speech_preview or "Agent Response"
273- await self ._send_tts_event ("started" , text_to_send , current_emotion , "session" )
299+ logger .info ("🔍 DEBUG: Agent started speaking - sending agent status" )
300+ await self ._send_agent_status (current_behavioral_mode , "speaking" )
274301 elif event .old_state == "speaking" and event .new_state != "speaking" :
275- logger .info ("🔍 DEBUG: Agent stopped speaking - sending TTS finished event" )
276- current_emotion = self .current_emotion
277- # Use full accumulated text for finished event
278- text_to_send = self .current_speech_full_text or "Agent Response"
279- await self ._send_tts_event ("finished" , text_to_send , current_emotion , "session" )
302+ logger .info ("🔍 DEBUG: Agent stopped speaking - sending agent status" )
303+ await self ._send_agent_status (current_behavioral_mode , "idle" )
280304 except Exception as e :
281- logger .error (f"Error handling agent state change TTS events: { e } " )
305+ logger .error (f"Error handling agent state change status events: { e } " )
282306
283307 asyncio .create_task (handle_state_change ())
284308
@@ -482,6 +506,7 @@ def process_emotional_response(self, llm_response: str) -> tuple[str, str]:
482506 if emotion != self .current_emotion :
483507 logger .info (f"🎭 Emotion transition: { self .current_emotion } → { emotion } " )
484508 self .log_animated_eyes (emotion )
509+ self .previous_emotion = self .current_emotion # Store previous before updating
485510 self .current_emotion = emotion
486511
487512 # Store in emotion history
@@ -561,4 +586,54 @@ async def _send_tts_event(self, event: str, text: str, emotion: str, source: str
561586 }
562587 await self .agent ._send_websocket_event ("TTS_EVENT" , event_data )
563588 else :
564- logger .debug (f"Cannot send TTS { event } event - no agent WebSocket connection" )
589+ logger .debug (f"Cannot send TTS { event } event - no agent WebSocket connection" )
590+
591+ async def _send_agent_status (self , behavioral_mode : str , speech_status : str , conversation_phase : str = "" ):
592+ """Send unified agent status through agent's WebSocket connection"""
593+ if self .agent and hasattr (self .agent , '_send_websocket_event' ):
594+ # Determine conversation phase if not provided
595+ if not conversation_phase :
596+ if self .announcing_virtual_request :
597+ conversation_phase = "announcement"
598+ elif behavioral_mode == "active" :
599+ conversation_phase = "discussion"
600+ # else conversation_phase remains empty for dormant
601+
602+ # Get current speech text based on speech status
603+ speech_text = ""
604+ if speech_status == "speaking" :
605+ speech_text = self .current_speech_preview or ""
606+ elif speech_status == "idle" and self .current_speech_full_text :
607+ speech_text = self .current_speech_full_text
608+
609+ status_data = {
610+ "behavioral_mode" : behavioral_mode ,
611+ "speech_status" : speech_status ,
612+ "emotion" : self .current_emotion ,
613+ "speech_text" : speech_text ,
614+ "previous_emotion" : getattr (self , 'previous_emotion' , '' ),
615+ "conversation_phase" : conversation_phase ,
616+ "last_tool_used" : self .last_tool_used ,
617+ "timestamp" : datetime .now ().isoformat ()
618+ }
619+ await self .agent ._send_websocket_event ("AGENT_STATUS" , status_data )
620+ else :
621+ logger .debug (f"Cannot send agent status - no agent WebSocket connection" )
622+
623+ async def _send_tool_event (self , tool_name : str , status : str , parameters : list = None , result : str = "" ):
624+ """Send tool event through agent's WebSocket connection"""
625+ if self .agent and hasattr (self .agent , '_send_websocket_event' ):
626+ # Update last tool used when tool starts
627+ if status == "started" :
628+ self .last_tool_used = tool_name
629+
630+ tool_data = {
631+ "tool_name" : tool_name ,
632+ "status" : status ,
633+ "parameters" : parameters or [],
634+ "result" : result ,
635+ "timestamp" : datetime .now ().isoformat ()
636+ }
637+ await self .agent ._send_websocket_event ("TOOL_EVENT" , tool_data )
638+ else :
639+ logger .debug (f"Cannot send tool event - no agent WebSocket connection" )
0 commit comments