diff --git a/ovos_core/intent_services/__init__.py b/ovos_core/intent_services/__init__.py index e812a94009d..009935a668d 100644 --- a/ovos_core/intent_services/__init__.py +++ b/ovos_core/intent_services/__init__.py @@ -268,12 +268,31 @@ def _handle_deactivate(self, message): self._deactivations[sess.session_id].append(skill_id) def _emit_match_message(self, match: Union[IntentHandlerMatch, PipelineMatch], message: Message): - """Update the message data with the matched utterance information and - activate the corresponding skill if available. - + """ + Emit a reply message for a matched intent, updating session and skill activation. + + This method processes matched intents from either a pipeline matcher or an intent handler, + creating a reply message with matched intent details and managing skill activation. + Args: - match (IntentHandlerMatch): The matched utterance object. - message (Message): The messagebus data. + match (Union[IntentHandlerMatch, PipelineMatch]): The matched intent object containing + utterance and matching information. + message (Message): The original messagebus message that triggered the intent match. + + Details: + - Handles two types of matches: PipelineMatch and IntentHandlerMatch + - Creates a reply message with matched intent data + - Activates the corresponding skill if not previously deactivated + - Updates session information + - Emits the reply message on the messagebus + + Side Effects: + - Modifies session state + - Emits a messagebus event + - Can trigger skill activation events + + Returns: + None """ reply = None sess = match.updated_session or SessionManager.get(message) @@ -313,6 +332,24 @@ def _emit_match_message(self, match: Union[IntentHandlerMatch, PipelineMatch], m self.bus.emit(reply) def send_cancel_event(self, message): + """ + Emit events and play a sound when an utterance is canceled. + + Logs the cancellation with the specific cancel word, plays a predefined cancel sound, + and emits multiple events to signal the utterance cancellation. + + Parameters: + message (Message): The original message that triggered the cancellation. + + Events Emitted: + - 'mycroft.audio.play_sound': Plays a cancel sound from configuration + - 'ovos.utterance.cancelled': Signals that the utterance was canceled + - 'ovos.utterance.handled': Indicates the utterance processing is complete + + Notes: + - Uses the default cancel sound path 'snd/cancel.mp3' if not specified in configuration + - Ensures events are sent as replies to the original message + """ LOG.info("utterance canceled, cancel_word:" + message.context.get("cancel_word")) # play dedicated cancel sound sound = Configuration().get('sounds', {}).get('cancel', "snd/cancel.mp3") diff --git a/ovos_core/intent_services/converse_service.py b/ovos_core/intent_services/converse_service.py index dfda3e88ecc..4d6421f97ed 100644 --- a/ovos_core/intent_services/converse_service.py +++ b/ovos_core/intent_services/converse_service.py @@ -313,15 +313,29 @@ def converse(self, utterances: List[str], skill_id: str, lang: str, message: Mes return False def converse_with_skills(self, utterances: List[str], lang: str, message: Message) -> Optional[PipelineMatch]: - """Give active skills a chance at the utterance - + """ + Attempt to converse with active skills for a given set of utterances. + + Iterates through active skills to find one that can handle the utterance. Filters skills based on timeout and blacklist status. + Args: - utterances (list): list of utterances - lang (string): 4 letter ISO language code - message (Message): message to use to generate reply - + utterances (List[str]): List of utterance strings to process + lang (str): 4-letter ISO language code for the utterances + message (Message): Message context for generating a reply + Returns: - IntentMatch if handled otherwise None. + PipelineMatch: Match details if a skill successfully handles the utterance, otherwise None + - handled (bool): Whether the utterance was fully handled + - match_data (dict): Additional match metadata + - skill_id (str): ID of the skill that handled the utterance + - updated_session (Session): Current session state after skill interaction + - utterance (str): The original utterance processed + + Notes: + - Standardizes language tag + - Filters out blacklisted skills + - Checks for skill conversation timeouts + - Attempts conversation with each eligible skill """ lang = standardize_lang_tag(lang) session = SessionManager.get(message) diff --git a/ovos_core/intent_services/stop_service.py b/ovos_core/intent_services/stop_service.py index 5590b8a83a1..3d0c82651ff 100644 --- a/ovos_core/intent_services/stop_service.py +++ b/ovos_core/intent_services/stop_service.py @@ -50,8 +50,25 @@ def get_active_skills(message: Optional[Message] = None) -> List[str]: return [skill[0] for skill in session.active_skills] def _collect_stop_skills(self, message: Message) -> List[str]: - """use the messagebus api to determine which skills can stop - This includes all skills and external applications""" + """ + Collect skills that can be stopped based on a ping-pong mechanism. + + This method determines which active skills can handle a stop request by sending + a stop ping to each active skill and waiting for their acknowledgment. + + Parameters: + message (Message): The original message triggering the stop request. + + Returns: + List[str]: A list of skill IDs that can be stopped. If no skills explicitly + indicate they can stop, returns all active skills. + + Notes: + - Excludes skills that are blacklisted in the current session + - Uses a non-blocking event mechanism to collect skill responses + - Waits up to 0.5 seconds for skills to respond + - Falls back to all active skills if no explicit stop confirmation is received + """ sess = SessionManager.get(message) want_stop = [] @@ -66,6 +83,23 @@ def _collect_stop_skills(self, message: Message) -> List[str]: event = Event() def handle_ack(msg): + """ + Handle acknowledgment from skills during the stop process. + + This method is a nested function used in skill stopping negotiation. It validates and tracks skill responses to a stop request. + + Parameters: + msg (Message): Message containing skill acknowledgment details. + + Side Effects: + - Modifies the `want_stop` list with skills that can handle stopping + - Updates the `skill_ids` list to track which skills have responded + - Sets the threading event when all active skills have responded + + Notes: + - Checks if a skill can handle stopping based on multiple conditions + - Ensures all active skills provide a response before proceeding + """ nonlocal event, skill_ids skill_id = msg.data["skill_id"] @@ -96,15 +130,28 @@ def handle_ack(msg): return want_stop or active_skills def stop_skill(self, skill_id: str, message: Message) -> bool: - """Tell a skill to stop anything it's doing, - taking into account the message Session - + """ + Stop a skill's ongoing activities and manage its session state. + + Sends a stop command to a specific skill and handles its response, ensuring + that any active interactions or processes are terminated. The method checks + for errors, verifies the skill's stopped status, and emits additional signals + to forcibly abort ongoing actions like conversations, questions, or speech. + Args: - skill_id: skill to query. - message (Message): message containing interaction info. - + skill_id (str): Unique identifier of the skill to be stopped. + message (Message): The original message context containing interaction details. + Returns: - handled (bool): True if handled otherwise False. + bool: True if the skill was successfully stopped, False otherwise. + + Raises: + Logs error if skill stop request encounters an issue. + + Notes: + - Emits multiple bus messages to ensure complete skill termination + - Checks and handles different skill interaction states + - Supports force-stopping of conversations, questions, and speech """ stop_msg = message.reply(f"{skill_id}.stop") result = self.bus.wait_for_response(stop_msg, f"{skill_id}.stop.response") @@ -133,15 +180,28 @@ def stop_skill(self, skill_id: str, message: Message) -> bool: return stopped def match_stop_high(self, utterances: List[str], lang: str, message: Message) -> Optional[PipelineMatch]: - """If utterance is an exact match for "stop" , run before intent stage - - Args: - utterances (list): list of utterances - lang (string): 4 letter ISO language code - message (Message): message to use to generate reply - + """ + Handles high-confidence stop requests by matching exact stop vocabulary and managing skill stopping. + + Attempts to stop skills when an exact "stop" or "global_stop" command is detected. Performs the following actions: + - Identifies the closest language match for vocabulary + - Checks for global stop command when no active skills exist + - Emits a global stop message if applicable + - Attempts to stop individual skills if a stop command is detected + - Disables response mode for stopped skills + + Parameters: + utterances (List[str]): List of user utterances to match against stop vocabulary + lang (str): Four-letter ISO language code for language-specific matching + message (Message): Message context for generating appropriate responses + Returns: - PipelineMatch if handled otherwise None. + Optional[PipelineMatch]: Match result indicating whether stop was handled, with optional skill and session information + - Returns None if no stop action could be performed + - Returns PipelineMatch with handled=True for successful global or skill-specific stop + + Raises: + No explicit exceptions raised, but may log debug/info messages during processing """ lang = self._get_closest_lang(lang) if lang is None: # no vocs registered for this lang @@ -182,16 +242,26 @@ def match_stop_high(self, utterances: List[str], lang: str, message: Message) -> return None def match_stop_medium(self, utterances: List[str], lang: str, message: Message) -> Optional[PipelineMatch]: - """ if "stop" intent is in the utterance, - but it contains additional words not in .intent files - - Args: - utterances (list): list of utterances - lang (string): 4 letter ISO language code - message (Message): message to use to generate reply - + """ + Handle stop intent with additional context beyond simple stop commands. + + This method processes utterances that contain "stop" or global stop vocabulary but may include + additional words not explicitly defined in intent files. It performs a medium-confidence + intent matching for stop requests. + + Parameters: + utterances (List[str]): List of input utterances to analyze + lang (str): Four-letter ISO language code for localization + message (Message): Message context for generating appropriate responses + Returns: - PipelineMatch if handled otherwise None. + Optional[PipelineMatch]: A pipeline match if the stop intent is successfully processed, + otherwise None if no stop intent is detected + + Notes: + - Attempts to match stop vocabulary with fuzzy matching + - Falls back to low-confidence matching if medium-confidence match is inconclusive + - Handles global stop scenarios when no active skills are present """ lang = self._get_closest_lang(lang) if lang is None: # no vocs registered for this lang @@ -222,15 +292,24 @@ def _get_closest_lang(self, lang: str) -> Optional[str]: return None def match_stop_low(self, utterances: List[str], lang: str, message: Message) -> Optional[PipelineMatch]: - """ before fallback_low , fuzzy match stop intent - - Args: - utterances (list): list of utterances - lang (string): 4 letter ISO language code - message (Message): message to use to generate reply - + """ + Perform a low-confidence fuzzy match for stop intent before fallback processing. + + This method attempts to match stop-related vocabulary with low confidence and handle stopping of active skills. + + Parameters: + utterances (List[str]): List of input utterances to match against stop vocabulary + lang (str): Four-letter ISO language code for vocabulary matching + message (Message): Message context used for generating replies and managing session + Returns: - PipelineMatch if handled otherwise None. + Optional[PipelineMatch]: A pipeline match object if a stop action is handled, otherwise None + + Notes: + - Increases confidence if active skills are present + - Attempts to stop individual skills before emitting a global stop signal + - Handles language-specific vocabulary matching + - Configurable minimum confidence threshold for stop intent """ lang = self._get_closest_lang(lang) if lang is None: # no vocs registered for this lang