diff --git a/CHANGELOG.md b/CHANGELOG.md index b4a23ba965e1..58f62209d56f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,36 @@ # Changelog -## [2.1.1a1](https://github.com/OpenVoiceOS/ovos-core/tree/2.1.1a1) (2025-11-05) +## [2.1.3a2](https://github.com/OpenVoiceOS/ovos-core/tree/2.1.3a2) (2026-03-07) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-core/compare/2.1.0...2.1.1a1) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-core/compare/2.1.3a1...2.1.3a2) **Merged pull requests:** -- Update ovos-plugin-manager version range [\#734](https://github.com/OpenVoiceOS/ovos-core/pull/734) ([JarbasAl](https://github.com/JarbasAl)) +- Prevent duplicate skill loads during overlapping rescans [\#744](https://github.com/OpenVoiceOS/ovos-core/pull/744) ([goldyfruit](https://github.com/goldyfruit)) + +## [2.1.3a1](https://github.com/OpenVoiceOS/ovos-core/tree/2.1.3a1) (2026-03-04) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-core/compare/2.1.2a2...2.1.3a1) + +**Merged pull requests:** + +- fix: skill dependencies [\#742](https://github.com/OpenVoiceOS/ovos-core/pull/742) ([JarbasAl](https://github.com/JarbasAl)) + +## [2.1.2a2](https://github.com/OpenVoiceOS/ovos-core/tree/2.1.2a2) (2026-01-19) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-core/compare/2.1.2a1...2.1.2a2) + +**Merged pull requests:** + +- gl-es/translate [\#739](https://github.com/OpenVoiceOS/ovos-core/pull/739) ([gitlocalize-app[bot]](https://github.com/apps/gitlocalize-app)) + +## [2.1.2a1](https://github.com/OpenVoiceOS/ovos-core/tree/2.1.2a1) (2025-11-10) + +[Full Changelog](https://github.com/OpenVoiceOS/ovos-core/compare/2.1.1...2.1.2a1) + +**Merged pull requests:** + +- Update ovos-workshop requirement from \<8.0.0,\>=7.0.6 to \>=7.0.6,\<9.0.0 in /requirements [\#736](https://github.com/OpenVoiceOS/ovos-core/pull/736) ([dependabot[bot]](https://github.com/apps/dependabot)) diff --git a/ovos_core/intent_services/locale/gl-es/global_stop.intent b/ovos_core/intent_services/locale/gl-es/global_stop.intent index 848a88156062..dafb2b14dd67 100644 --- a/ovos_core/intent_services/locale/gl-es/global_stop.intent +++ b/ovos_core/intent_services/locale/gl-es/global_stop.intent @@ -8,8 +8,8 @@ Parar todas as accións Parar todas as actividades activas Parar todas as tarefas actuais Parar todo agora +Rematar todas as accións en marcha Rematar todas as actividades -Rematar todas as actividades en execución Rematar todas as operacións Rematar todas as tarefas abertas Rematar todos os procesos @@ -18,6 +18,7 @@ acabar todo cancelalo todo cancelar todo detelo todo +detelo todo deter todo finalizalo todo finalizar todo @@ -27,5 +28,4 @@ paralo todo paralo todo parar todo parar todo -rematalo todo rematar todo \ No newline at end of file diff --git a/ovos_core/intent_services/locale/gl-es/stop.intent b/ovos_core/intent_services/locale/gl-es/stop.intent index ffab1c5400bd..a11980deebac 100644 --- a/ovos_core/intent_services/locale/gl-es/stop.intent +++ b/ovos_core/intent_services/locale/gl-es/stop.intent @@ -1,17 +1,17 @@ -Acaba isto -Cancela a tarefa actual -Interrompe a acción actual +Acaba iso +Cancela a tarefa en curso +Detén a tarefa en curso Para isto Parar a acción actual +Parar a acción en curso Parar a actividade actual -Parar a operación actual Parar de executar esta tarefa Parar de executar o comando actual Parar de traballar niso Parar o proceso en curso +Parar o proceso en curso Parar o que estás a facer Podes parar agora? -Remata a tarefa actual parar parar de facer iso parar iso \ No newline at end of file diff --git a/ovos_core/intent_services/locale/nl-nl/global_stop.intent b/ovos_core/intent_services/locale/nl-nl/global_stop.intent index fbe3afc8b570..d030bdac5c75 100644 --- a/ovos_core/intent_services/locale/nl-nl/global_stop.intent +++ b/ovos_core/intent_services/locale/nl-nl/global_stop.intent @@ -1,31 +1,31 @@ -Annuleer alle lopende acties -Annuleer alle taken -Beëindig alle acties -Beëindig alle lopende acties +Alle lopende processen afbreken +Alle taken annuleren +Beëindig alle bewerkingen Beëindig alle processen -Rond alle activiteiten af -Stop alle activiteiten +Stop alle acties Stop alle huidige taken -Stop alle lopende acties -Stop alle lopende processen -Stop alle lopende processen -Stop met alle acties -Stop nu met alles -Stop onmiddellijk alle acties -Voltooi alle openstaande taken +Stop nu alles +Stop onmiddellijk alle activiteiten +Voltooi alle activiteiten +alles afbreken +alles afbreken +alles afmaken +alles afmaken +alles annuleren alles annuleren alles beëindigen alles beëindigen alles stoppen -alles stoppen -alles stoppen -alles stoppen -alles stoppen -alles stopzetten -annuleer alles -beëindig alles beëindig alles +genoeg +hou op +hou op met praten +kappen +kappen nu +niet meer praten +nu ophouden +stop alles stop alles stop alles stop alles -stop met alles \ No newline at end of file +stop met praten \ No newline at end of file diff --git a/ovos_core/intent_services/locale/nl-nl/stop.intent b/ovos_core/intent_services/locale/nl-nl/stop.intent index 15e78b7c9799..a3c1ff8dd4ef 100644 --- a/ovos_core/intent_services/locale/nl-nl/stop.intent +++ b/ovos_core/intent_services/locale/nl-nl/stop.intent @@ -1,17 +1,17 @@ -(kun|kan) je nu stoppen Annuleer de huidige taak -Beëindig de huidige actie Beëindig de huidige taak -Beëindig the current action -Ga niet verder -Hou daar alsjeblieft mee op -Maak er een einde aan -Stop de huidige actie +Kun je nu stoppen? +Maak er alsjeblieft een einde aan +Stop a.u.b. +Stop alstublieft met de huidige actie +Stop daar alsjeblieft mee Stop de huidige actie +Stop de huidige activiteit Stop het lopende proces Stop met het uitvoeren van de huidige opdracht Stop met het uitvoeren van die taak -Stop waar je mee bezig bent +Stop wat je doet +Stoppen maar stop -stop dit +stop daarmee stop ermee \ No newline at end of file diff --git a/ovos_core/skill_manager.py b/ovos_core/skill_manager.py index a1259997178f..1aa96806e70a 100644 --- a/ovos_core/skill_manager.py +++ b/ovos_core/skill_manager.py @@ -109,6 +109,8 @@ def __init__(self, bus, watchdog=None, alive_hook=on_alive, started_hook=on_star self.config = Configuration() self.plugin_skills = {} + self._plugin_skills_lock = threading.RLock() + self._loading_plugin_skills = set() self.enclosure = EnclosureAPI(bus) self.num_install_retries = 0 self.empty_skill_dirs = set() # Save a record of empty skill dirs. @@ -209,6 +211,25 @@ def skills_config(self): """ return self.config['skills'] + def _is_plugin_skill_tracked(self, skill_id): + """Check whether a skill is loaded or currently being loaded.""" + with self._plugin_skills_lock: + return (skill_id in self.plugin_skills or + skill_id in self._loading_plugin_skills) + + def _reserve_plugin_skill_load(self, skill_id): + """Mark a skill as loading so overlapping scans skip it.""" + with self._plugin_skills_lock: + if skill_id in self.plugin_skills or skill_id in self._loading_plugin_skills: + return False + self._loading_plugin_skills.add(skill_id) + return True + + def _release_plugin_skill_load(self, skill_id): + """Clear the in-progress marker for a skill load attempt.""" + with self._plugin_skills_lock: + self._loading_plugin_skills.discard(skill_id) + def handle_gui_connected(self, message): """Handle GUI connection event. @@ -295,16 +316,19 @@ def load_plugin_skills(self, network=None, internet=None): LOG.warning(f"{skill_id} is blacklisted, it will NOT be loaded") LOG.info(f"Consider uninstalling {skill_id} instead of blacklisting it") continue - if skill_id not in self.plugin_skills: - skill_loader = self._get_plugin_skill_loader(skill_id, init_bus=False, - skill_class=plug) - requirements = skill_loader.runtime_requirements - if not network and requirements.network_before_load: - continue - if not internet and requirements.internet_before_load: - continue - self._load_plugin_skill(skill_id, plug) - loaded_new = True + if self._is_plugin_skill_tracked(skill_id): + continue + skill_loader = self._get_plugin_skill_loader(skill_id, init_bus=False, + skill_class=plug) + requirements = skill_loader.runtime_requirements + if not network and requirements.network_before_load: + continue + if not internet and requirements.internet_before_load: + continue + if not self._reserve_plugin_skill_load(skill_id): + continue + self._load_plugin_skill(skill_id, plug, reserved=True) + loaded_new = True return loaded_new def _get_internal_skill_bus(self): @@ -342,18 +366,24 @@ def _get_plugin_skill_loader(self, skill_id, init_bus=True, skill_class=None): loader.skill_class = skill_class return loader - def _load_plugin_skill(self, skill_id, skill_plugin): + def _load_plugin_skill(self, skill_id, skill_plugin, reserved=False): """Load a plugin skill. Args: skill_id (str): ID of the skill. skill_plugin: Plugin skill instance. + reserved (bool): True if the caller already marked the skill as loading. Returns: PluginSkillLoader: Loaded plugin skill loader instance if successful, None otherwise. """ - skill_loader = self._get_plugin_skill_loader(skill_id, skill_class=skill_plugin) + if not reserved and not self._reserve_plugin_skill_load(skill_id): + LOG.debug(f"Skipping duplicate load attempt for {skill_id}; load already in progress") + return None + + skill_loader = None try: + skill_loader = self._get_plugin_skill_loader(skill_id, skill_class=skill_plugin) load_status = skill_loader.load(skill_plugin) if load_status: self.bus.emit(Message("mycroft.skill.loaded", {"skill_id": skill_id})) @@ -361,7 +391,10 @@ def _load_plugin_skill(self, skill_id, skill_plugin): LOG.exception(f'Load of skill {skill_id} failed!') load_status = False finally: - self.plugin_skills[skill_id] = skill_loader + if skill_loader is not None: + with self._plugin_skills_lock: + self.plugin_skills[skill_id] = skill_loader + self._release_plugin_skill_load(skill_id) return skill_loader if load_status else None diff --git a/ovos_core/version.py b/ovos_core/version.py index 83e43b9282a5..0a3432200de6 100644 --- a/ovos_core/version.py +++ b/ovos_core/version.py @@ -1,8 +1,8 @@ # START_VERSION_BLOCK VERSION_MAJOR = 2 VERSION_MINOR = 1 -VERSION_BUILD = 1 -VERSION_ALPHA = 0 +VERSION_BUILD = 3 +VERSION_ALPHA = 2 # END_VERSION_BLOCK # for compat with old imports diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 30545ce6ad25..62d6bdea238d 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -7,7 +7,7 @@ ovos-utils>=0.8.2a1,<1.0.0 ovos_bus_client>=1.3.6a1,<2.0.0 ovos-plugin-manager>=1.0.3,<3.0.0 ovos-config>=0.0.13,<3.0.0 -ovos-workshop>=7.0.6,<8.0.0 +ovos-workshop>=7.0.6,<9.0.0 rapidfuzz>=3.6,<4.0 langcodes diff --git a/requirements/skills-audio.txt b/requirements/skills-audio.txt index 8fe86baf32a2..ba22324f94fc 100644 --- a/requirements/skills-audio.txt +++ b/requirements/skills-audio.txt @@ -1,6 +1,7 @@ # skills that run in audio enabled devices (require mic/speaker) -ovos-skill-boot-finished>=0.4.8,<1.0.0 -ovos-skill-audio-recording>=0.2.4,<1.0.0 -ovos-skill-dictation>=0.2.5,<1.0.0 -ovos-skill-volume>=0.1.16,<1.0.0 -ovos-skill-naptime>=0.3.15,<1.0.0 +# NOTE: we do not set upper range on purpose, it is up to release channel being used to do that +ovos-skill-boot-finished>=0.4.8 +ovos-skill-audio-recording>=0.2.4 +ovos-skill-dictation>=0.2.5 +ovos-skill-volume>=0.1.16 +ovos-skill-naptime>=0.3.15 diff --git a/requirements/skills-ca.txt b/requirements/skills-ca.txt index b30aaca81e22..7708312781cc 100644 --- a/requirements/skills-ca.txt +++ b/requirements/skills-ca.txt @@ -1,3 +1,4 @@ # skills providing catalan specific functionality +# NOTE: we do not set upper range on purpose, it is up to release channel being used to do that ovos-skill-fuster-quotes ovos-skill-word-of-the-day diff --git a/requirements/skills-desktop.txt b/requirements/skills-desktop.txt index 35c09b68cf12..0d2376a129fa 100644 --- a/requirements/skills-desktop.txt +++ b/requirements/skills-desktop.txt @@ -1,4 +1,5 @@ # skills that require a linux desktop environment -ovos-skill-application-launcher>=0.5.14,<1.0.0 -ovos-skill-wallpapers>=1.0.2,<3.0.0 -ovos-skill-screenshot>=0.0.2,<1.0.0 +# NOTE: we do not set upper range on purpose, it is up to release channel being used to do that +ovos-skill-application-launcher>=0.5.14 +ovos-skill-wallpapers>=1.0.2 +ovos-skill-screenshot>=0.0.2 diff --git a/requirements/skills-en.txt b/requirements/skills-en.txt index 35507b62e2a9..039db8fc9abb 100644 --- a/requirements/skills-en.txt +++ b/requirements/skills-en.txt @@ -1,4 +1,5 @@ # skills providing english specific functionality +# NOTE: we do not set upper range on purpose, it is up to release channel being used to do that ovos-skill-word-of-the-day # skills below need translation before they are moved to skill-extras.txt -ovos-skill-days-in-history>=0.3.11,<1.0.0 +ovos-skill-days-in-history>=0.3.11 diff --git a/requirements/skills-essential.txt b/requirements/skills-essential.txt index 2ffaab767963..6c70aa4090de 100644 --- a/requirements/skills-essential.txt +++ b/requirements/skills-essential.txt @@ -1,11 +1,12 @@ # skills providing core functionality (offline) -ovos-skill-fallback-unknown>=0.1.9,<1.0.0 -ovos-skill-alerts>=0.1.10,<1.0.0 -ovos-skill-personal>=0.1.19,<1.0.0 +# NOTE: we do not set upper range on purpose, it is up to release channel being used to do that +ovos-skill-fallback-unknown>=0.1.9 +ovos-skill-alerts>=0.1.10 +ovos-skill-personal>=0.1.19 ovos-skill-date-time>=1.1.3,<2.0.0 -ovos-skill-hello-world>=0.1.10,<1.0.0 -ovos-skill-spelling>=0.2.5,<1.0.0 -ovos-skill-diagnostics>=0.0.2,<1.0.0 -ovos-skill-parrot>=0.1.25,<1.0.0 -ovos-skill-count>=0.0.1,<1.0.0 -ovos-skill-randomness>=0.1.2,<1.0.0; python_version >= "3.10" +ovos-skill-hello-world>=0.1.10 +ovos-skill-spelling>=0.2.5 +ovos-skill-diagnostics>=0.0.2 +ovos-skill-parrot>=0.1.25 +ovos-skill-count>=0.0.1 +ovos-skill-randomness>=0.1.2; python_version >= "3.10" diff --git a/requirements/skills-extra.txt b/requirements/skills-extra.txt index 0dedcdc5f106..2cc450fe0076 100644 --- a/requirements/skills-extra.txt +++ b/requirements/skills-extra.txt @@ -1,10 +1,11 @@ # skills providing non essential functionality -ovos-skill-wordnet>=0.2.5,<1.0.0 -ovos-skill-laugh>=0.1.1,<1.0.0 -ovos-skill-number-facts>=0.1.12,<1.0.0 -ovos-skill-iss-location>=0.2.16,<1.0.0 -ovos-skill-cmd>=0.2.11,<1.0.0 -ovos-skill-moviemaster>=0.0.12,<1.0.0 -ovos-skill-confucius-quotes>=0.1.13,<1.0.0 -ovos-skill-icanhazdadjokes>=0.3.7,<1.0.0 +# NOTE: we do not set upper range on purpose, it is up to release channel being used to do that +ovos-skill-wordnet>=0.2.5 +ovos-skill-laugh>=0.1.1 +ovos-skill-number-facts>=0.1.12 +ovos-skill-iss-location>=0.2.16 +ovos-skill-cmd>=0.2.11 +ovos-skill-moviemaster>=0.0.12 +ovos-skill-confucius-quotes>=0.1.13 +ovos-skill-icanhazdadjokes>=0.3.7 ovos-skill-camera diff --git a/requirements/skills-gl.txt b/requirements/skills-gl.txt index f9ec9d061f92..cdf0d55b3e1f 100644 --- a/requirements/skills-gl.txt +++ b/requirements/skills-gl.txt @@ -1,2 +1,3 @@ # skills providing galician specific functionality +# NOTE: we do not set upper range on purpose, it is up to release channel being used to do that ovos-skill-word-of-the-day>=0.2.0 diff --git a/requirements/skills-gui.txt b/requirements/skills-gui.txt index e6544b7d6c77..c0ce5d566377 100644 --- a/requirements/skills-gui.txt +++ b/requirements/skills-gui.txt @@ -1,3 +1,4 @@ -ovos-skill-homescreen>=3.0.3,<4.0.0 -ovos-skill-screenshot>=0.0.2,<1.0.0 -ovos-skill-color-picker>=0.0.2,<1.0.0 \ No newline at end of file +# NOTE: we do not set upper rage on purpose, it is up to release channel being used to do that +ovos-skill-homescreen>=3.0.3 +ovos-skill-screenshot>=0.0.2 +ovos-skill-color-picker>=0.0.2 \ No newline at end of file diff --git a/requirements/skills-internet.txt b/requirements/skills-internet.txt index 4ff3ee3e5c64..0218d31659ed 100644 --- a/requirements/skills-internet.txt +++ b/requirements/skills-internet.txt @@ -1,8 +1,9 @@ # skills that require internet connectivity, should not be installed in offline devices -ovos-skill-weather>=1.0.3,<2.0.0 -ovos-skill-ddg>=0.3.5,<1.0.0 -ovos-skill-wolfie>=0.5.8,<1.0.0 -ovos-skill-wikipedia>=0.8.13,<1.0.0 -ovos-skill-wikihow>=0.3.3,<1.0.0 -ovos-skill-speedtest>=0.3.6,<1.0.0 -ovos-skill-ip>=0.2.5,<1.0.0 +# NOTE: we do not set upper range on purpose, it is up to release channel being used to do that +ovos-skill-weather>=1.0.3 +ovos-skill-ddg>=0.3.5 +ovos-skill-wolfie>=0.5.8 +ovos-skill-wikipedia>=0.8.13 +ovos-skill-wikihow>=0.3.3 +ovos-skill-speedtest>=0.3.6 +ovos-skill-ip>=0.2.5 diff --git a/requirements/skills-media.txt b/requirements/skills-media.txt index 5a804f2a057f..d751f7b19b8d 100644 --- a/requirements/skills-media.txt +++ b/requirements/skills-media.txt @@ -1,6 +1,7 @@ # skills for OCP, require audio playback plugins (usually mpv) -ovos-skill-somafm>=0.1.3,<1.0.0 -ovos-skill-news>=0.4.6a1,<1.0.0 -ovos-skill-pyradios>=0.1.5,<1.0.0 -ovos-skill-local-media>=0.2.12,<1.0.0 -ovos-skill-youtube-music>=0.1.7,<1.0.0 +# NOTE: we do not set upper range on purpose, it is up to release channel being used to do that +ovos-skill-somafm>=0.1.3 +ovos-skill-news>=0.4.6a1 +ovos-skill-pyradios>=0.1.5 +ovos-skill-local-media>=0.2.12 +ovos-skill-youtube-music>=0.1.7 diff --git a/requirements/skills-pt.txt b/requirements/skills-pt.txt index b9409c94fca0..524c78d8c40b 100644 --- a/requirements/skills-pt.txt +++ b/requirements/skills-pt.txt @@ -1,2 +1,3 @@ # skills providing portuguese specific functionality +# NOTE: we do not set upper range on purpose, it is up to release channel being used to do that ovos-skill-word-of-the-day diff --git a/test/unittests/test_skill_manager.py b/test/unittests/test_skill_manager.py index ef4b9a2f5548..663e5000fcb3 100644 --- a/test/unittests/test_skill_manager.py +++ b/test/unittests/test_skill_manager.py @@ -16,6 +16,7 @@ from copy import deepcopy from pathlib import Path from shutil import rmtree +from threading import Event, Thread from unittest import TestCase from unittest.mock import Mock, patch @@ -215,6 +216,84 @@ def test_load_plugin_skill_success(self): # Verify return value self.assertEqual(result, mock_loader) + @patch('ovos_core.skill_manager.find_skill_plugins') + def test_load_plugin_skills_skips_skill_already_loading(self, mock_find_skill_plugins): + """Test plugin discovery skips a skill that is already being loaded.""" + skill_id = 'test.loading.skill' + mock_find_skill_plugins.return_value = {skill_id: Mock()} + self.skill_manager.plugin_skills = {} + self.skill_manager._loading_plugin_skills.add(skill_id) + self.skill_manager._get_plugin_skill_loader = Mock() + self.skill_manager._load_plugin_skill = Mock() + + loaded_new = self.skill_manager.load_plugin_skills(network=True, internet=True) + + self.assertFalse(loaded_new) + self.skill_manager._get_plugin_skill_loader.assert_not_called() + self.skill_manager._load_plugin_skill.assert_not_called() + + def test_load_plugin_skill_tracks_loading_state(self): + """Test a skill is marked loading before PluginSkillLoader.load runs.""" + skill_id = 'test.tracked.skill' + mock_plugin = Mock() + mock_loader = Mock(spec=SkillLoader) + mock_loader.skill_id = skill_id + + def load_side_effect(plugin): + self.assertEqual(plugin, mock_plugin) + self.assertIn(skill_id, self.skill_manager._loading_plugin_skills) + return True + + mock_loader.load.side_effect = load_side_effect + self.skill_manager._get_plugin_skill_loader = Mock(return_value=mock_loader) + self.skill_manager.plugin_skills = {} + + result = self.skill_manager._load_plugin_skill(skill_id, mock_plugin) + + self.assertEqual(result, mock_loader) + self.assertNotIn(skill_id, self.skill_manager._loading_plugin_skills) + self.assertEqual(mock_loader, self.skill_manager.plugin_skills[skill_id]) + + def test_load_plugin_skill_skips_concurrent_duplicate_attempt(self): + """Test concurrent loads for the same skill only execute once.""" + skill_id = 'test.concurrent.skill' + mock_plugin = Mock() + mock_loader = Mock(spec=SkillLoader) + mock_loader.skill_id = skill_id + load_started = Event() + allow_finish = Event() + results = {} + + def load_side_effect(plugin): + self.assertEqual(plugin, mock_plugin) + load_started.set() + self.assertTrue(allow_finish.wait(2)) + return True + + mock_loader.load.side_effect = load_side_effect + self.skill_manager._get_plugin_skill_loader = Mock(return_value=mock_loader) + self.skill_manager.plugin_skills = {} + + def first_load(): + results['first'] = self.skill_manager._load_plugin_skill(skill_id, mock_plugin) + + thread = Thread(target=first_load) + thread.start() + self.assertTrue(load_started.wait(1)) + + results['second'] = self.skill_manager._load_plugin_skill(skill_id, mock_plugin) + + allow_finish.set() + thread.join(timeout=2) + + self.assertFalse(thread.is_alive()) + self.assertEqual(results['first'], mock_loader) + self.assertIsNone(results['second']) + self.assertEqual(1, self.skill_manager._get_plugin_skill_loader.call_count) + mock_loader.load.assert_called_once_with(mock_plugin) + self.assertNotIn(skill_id, self.skill_manager._loading_plugin_skills) + self.assertEqual(mock_loader, self.skill_manager.plugin_skills[skill_id]) + def test_load_plugin_skill_failure(self): """Test failed plugin skill loading is handled gracefully.""" skill_id = 'test.failing.skill' @@ -245,6 +324,7 @@ def test_load_plugin_skill_failure(self): # Verify skill was still added to plugin_skills (even on failure) self.assertIn(skill_id, self.skill_manager.plugin_skills) self.assertEqual(mock_loader, self.skill_manager.plugin_skills[skill_id]) + self.assertNotIn(skill_id, self.skill_manager._loading_plugin_skills) # Verify return value is None on failure self.assertIsNone(result) @@ -274,6 +354,7 @@ def test_load_plugin_skill_returns_false(self): # Verify skill was added to plugin_skills self.assertIn(skill_id, self.skill_manager.plugin_skills) + self.assertNotIn(skill_id, self.skill_manager._loading_plugin_skills) # Verify return value is None when load returns False self.assertIsNone(result) diff --git a/translations/gl-es/intents.json b/translations/gl-es/intents.json index bc73a30e27ca..b8911345cf06 100644 --- a/translations/gl-es/intents.json +++ b/translations/gl-es/intents.json @@ -1,54 +1,54 @@ { - "stop.intent": [ - "Podes parar agora?", - "Parar a acción actual", - "Parar a actividade actual", - "Cancela a tarefa actual", - "Interrompe a acción actual", - "Acaba isto", - "Para isto", - "Remata a tarefa actual", - "Parar de executar o comando actual", - "Parar de executar esta tarefa", - "Parar a operación actual", - "Parar o proceso en curso", - "Parar o que estás a facer", - "Parar de traballar niso", - "parar", - "parar de facer iso", - "parar iso" - ], - "global_stop.intent": [ - "Deter todos os procesos en curso", - "Deter todas as accións en marcha", - "Cancelar todas as operacións pendentes", - "Cancelar todas as tarefas", - "Parar todas as accións", - "Parar todas as actividades activas", - "Rematar todos os procesos", - "Rematar todas as actividades", - "Rematar todas as tarefas abertas", - "Interromper inmediatamente todas as actividades", - "Interromper todos os procesos en curso", - "Parar todas as tarefas actuais", - "Parar todo agora", - "Rematar todas as operacións", - "Rematar todas as actividades en execución", - "deter todo", - "detelo todo", - "cancelar todo", - "cancelalo todo", - "parar todo", - "paralo todo", - "rematar todo", - "rematalo todo", - "acabar todo", - "acabalo todo", - "interromper todo", - "interrompelo todo", - "parar todo", - "paralo todo", - "finalizar todo", - "finalizalo todo" - ] -} \ No newline at end of file + "stop.intent": [ + "parar", + "parar de facer iso", + "parar iso", + "Parar o que estás a facer", + "Para isto", + "Podes parar agora?", + "Parar de executar esta tarefa", + "Parar a acción actual", + "Parar o proceso en curso", + "Parar a actividade actual", + "Acaba iso", + "Parar de traballar niso", + "Parar de executar o comando actual", + "Detén a tarefa en curso", + "Parar o proceso en curso", + "Parar a acción en curso", + "Cancela a tarefa en curso" + ], + "global_stop.intent": [ + "parar todo", + "rematar todo", + "finalizar todo", + "cancelar todo", + "acabar todo", + "interromper todo", + "deter todo", + "parar todo", + "paralo todo", + "detelo todo", + "finalizalo todo", + "cancelalo todo", + "acabalo todo", + "interrompelo todo", + "detelo todo", + "paralo todo", + "Parar todo agora", + "Rematar todos os procesos", + "Rematar todas as operacións", + "Cancelar todas as tarefas", + "Rematar todas as actividades", + "Interromper inmediatamente todas as actividades", + "Deter todos os procesos en curso", + "Parar todas as accións", + "Parar todas as tarefas actuais", + "Rematar todas as accións en marcha", + "Cancelar todas as operacións pendentes", + "Rematar todas as tarefas abertas", + "Interromper todos os procesos en curso", + "Deter todas as accións en marcha", + "Parar todas as actividades activas" + ] +}