From 7905c1b07a4752c76c8194de07d07243d452e5f8 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Sat, 27 Jun 2026 01:28:43 +0100 Subject: [PATCH 1/3] refactor: re-export intent-definition primitives from ovos-spec-tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Intent, IntentBuilder and open_intent_envelope are now re-exported from ovos-spec-tools (OVOS-INTENT-4 adapt-free keyword-intent definition model) instead of being defined locally as adapt metaclass wrappers. Skills keep their `from ovos_workshop.intents import IntentBuilder` import; the canonical implementation lives in the spec. IntentServiceInterface and the munge_* helpers are unchanged — they consume the re-exported definitions. The spec-tools IntentBuilder is API-compatible (.require/.optionally/.one_of/.exclude/.build/.name) and Intent keeps the same attribute surface (name/requires/at_least_one/optional/excludes). The local matching logic (Intent.validate / _resolve_one_of) is dropped: per OVOS-PIPELINE-1 matching is an engine-plugin concern, not part of the definition primitive. test_intent.py (which exercised that matching logic) is removed accordingly. pyproject: bump ovos-spec-tools floor to >=0.14.0a1 (the version carrying the intent primitives). CI pre-installs the unpublished spec-tools branch. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/build_tests.yml | 3 + .github/workflows/coverage.yml | 2 + ovos_workshop/intents.py | 323 +----------------------------- pyproject.toml | 2 +- test/unittests/test_intent.py | 271 ------------------------- 5 files changed, 13 insertions(+), 588 deletions(-) delete mode 100644 test/unittests/test_intent.py diff --git a/.github/workflows/build_tests.yml b/.github/workflows/build_tests.yml index 237096e4..6c39e8e0 100644 --- a/.github/workflows/build_tests.yml +++ b/.github/workflows/build_tests.yml @@ -13,3 +13,6 @@ jobs: with: test_path: 'test/unittests' install_extras: 'test' + # intent-definition primitives now live in ovos-spec-tools (unpublished branch); + # plus the in-flight cap cascade: cap-lifted ovos-adapt-parser + ovos-core allowing ovos-workshop 9.x (unpublished) + pre_install_pip: 'git+https://github.com/OpenVoiceOS/ovos-spec-tools@feat/intent-primitives git+https://github.com/OpenVoiceOS/ovos-adapt-pipeline-plugin@fix/allow-ovos-workshop-9 git+https://github.com/OpenVoiceOS/ovos-core@fix/allow-ovos-workshop-9' diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index ba4fdddc..20d7525b 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -23,6 +23,8 @@ jobs: python -m pip install build wheel - name: Install repo run: | + # intent-definition primitives now live in ovos-spec-tools (unpublished branch) + pip install git+https://github.com/OpenVoiceOS/ovos-spec-tools@feat/intent-primitives pip install -e . - name: Install test dependencies run: | diff --git a/ovos_workshop/intents.py b/ovos_workshop/intents.py index d5338a77..ebfefc85 100644 --- a/ovos_workshop/intents.py +++ b/ovos_workshop/intents.py @@ -1,314 +1,17 @@ -import abc -import itertools from os.path import exists from threading import RLock -from typing import List, Tuple, Optional +from typing import List, Optional import warnings from ovos_bus_client.message import Message, dig_for_message from ovos_bus_client.util import get_mycroft_bus from ovos_utils.log import LOG, log_deprecation - -class _IntentMeta(abc.ABCMeta): - def __instancecheck__(self, instance): - check = super().__instancecheck__(instance) - if not check: - try: - # backwards compat isinstancechecks - from adapt.intent import Intent as _I - check = isinstance(instance, _I) - except ImportError: - pass - return check - - -class Intent(metaclass=_IntentMeta): - def __init__(self, name="", requires=None, at_least_one=None, optional=None, excludes=None): - """Create Intent object - - Args: - name(str): Name for Intent - requires(list): Entities that are required - at_least_one(list): One of these Entities are required - optional(list): Optional Entities used by the intent - """ - self.name = name - self.requires = requires or [] - self.at_least_one = at_least_one or [] - self.optional = optional or [] - self.excludes = excludes or [] - - def validate(self, tags, confidence): - """Using this method removes tags from the result of validate_with_tags - - Returns: - intent(intent): Results from validate_with_tags - """ - intent, tags = self.validate_with_tags(tags, confidence) - return intent - - def validate_with_tags(self, tags, confidence): - """Validate whether tags has required entites for this intent to fire - - Args: - tags(list): Tags and Entities used for validation - confidence(float): The weight associate to the parse result, - as indicated by the parser. This is influenced by a parser - that uses edit distance or context. - - Returns: - intent, tags: Returns intent and tags used by the intent on - failure to meat required entities then returns intent with - confidence - of 0.0 and an empty list for tags. - """ - result = {'intent_type': self.name} - intent_confidence = 0.0 - local_tags = tags[:] - used_tags = [] - - # Check excludes first - for exclude_type in self.excludes: - exclude_tag, _canonical_form, _tag_confidence = \ - self._find_first_tag(local_tags, exclude_type) - if exclude_tag: - result['confidence'] = 0.0 - return result, [] - - for require_type, attribute_name in self.requires: - required_tag, canonical_form, tag_confidence = \ - self._find_first_tag(local_tags, require_type) - if not required_tag: - result['confidence'] = 0.0 - return result, [] - - result[attribute_name] = canonical_form - if required_tag in local_tags: - local_tags.remove(required_tag) - used_tags.append(required_tag) - intent_confidence += tag_confidence - - if len(self.at_least_one) > 0: - best_resolution = self._resolve_one_of(local_tags, self.at_least_one) - if not best_resolution: - result['confidence'] = 0.0 - return result, [] - else: - for key in best_resolution: - # TODO: at least one should support aliases - result[key] = best_resolution[key][0].get('key') - intent_confidence += 1.0 * best_resolution[key][0]['entities'][0].get('confidence', 1.0) - used_tags.append(best_resolution[key][0]) - if best_resolution in local_tags: - local_tags.remove(best_resolution[key][0]) - - for optional_type, attribute_name in self.optional: - optional_tag, canonical_form, tag_confidence = \ - self._find_first_tag(local_tags, optional_type) - if not optional_tag or attribute_name in result: - continue - result[attribute_name] = canonical_form - if optional_tag in local_tags: - local_tags.remove(optional_tag) - used_tags.append(optional_tag) - intent_confidence += tag_confidence - - total_confidence = (intent_confidence / len(tags) * confidence) \ - if tags else 0.0 - - CLIENT_ENTITY_NAME = 'Client' # TODO - ??? what is this magic string - - target_client, canonical_form, confidence = \ - self._find_first_tag(local_tags, CLIENT_ENTITY_NAME) - - result['target'] = target_client.get('key') if target_client else None - result['confidence'] = total_confidence - - return result, used_tags - - @classmethod - def _resolve_one_of(cls, tags, at_least_one): - """Search through all combinations of at_least_one rules to find a - combination that is covered by tags - - Args: - tags(list): List of tags with Entities to search for Entities - at_least_one(list): List of Entities to find in tags - - Returns: - object: - returns None if no match is found but returns any match as an object - """ - for possible_resolution in itertools.product(*at_least_one): - resolution = {} - pr = possible_resolution[:] - for entity_type in pr: - last_end_index = -1 - if entity_type in resolution: - last_end_index = resolution[entity_type][-1].get('end_token') - tag, value, c = cls._find_first_tag(tags, entity_type, - after_index=last_end_index) - if not tag: - break - else: - if entity_type not in resolution: - resolution[entity_type] = [] - resolution[entity_type].append(tag) - # Check if this is a valid resolution (all one_of rules matched) - if len(resolution) == len(possible_resolution): - return resolution - - return None - - @staticmethod - def _find_first_tag(tags, entity_type, after_index=-1): - """Searches tags for entity type after given index - - Args: - tags(list): a list of tags with entity types to be compared to - entity_type - entity_type(str): This is he entity type to be looking for in tags - after_index(int): the start token must be greater than this. - - Returns: - ( tag, v, confidence ): - tag(str): is the tag that matched - v(str): ? the word that matched? - confidence(float): is a measure of accuracy. 1 is full confidence - and 0 is none. - """ - for tag in tags: - for entity in tag.get('entities'): - for v, t in entity.get('data'): - if t.lower() == entity_type.lower() and \ - (tag.get('start_token', 0) > after_index or \ - tag.get('from_context', False)): - return tag, v, entity.get('confidence') - - return None, None, None - - -class _IntentBuilderMeta(abc.ABCMeta): - def __instancecheck__(self, instance): - check = super().__instancecheck__(instance) - if not check: - try: - # backwards compat isinstancechecks - from adapt.intent import IntentBuilder as _IB - check = isinstance(instance, _IB) - except ImportError: - pass - return check - - -class IntentBuilder(metaclass=_IntentBuilderMeta): - """ - IntentBuilder, used to construct intent parsers. - - Attributes: - at_least_one(list): A list of Entities where one is required. - These are separated into lists so you can have one of (A or B) and - then require one of (D or F). - requires(list): A list of Required Entities - optional(list): A list of optional Entities - excludes(list): A list of forbidden Entities - name(str): Name of intent - - Notes: - This is designed to allow construction of intents in one line. - - Example: - IntentBuilder("Intent")\ - .requires("A")\ - .one_of("C","D")\ - .optional("G").build() - """ - - def __init__(self, intent_name): - """ - Constructor - - Args: - intent_name(str): the name of the intents that this parser - parses/validates - """ - self.at_least_one = [] - self.requires = [] - self.excludes = [] - self.optional = [] - self.name = intent_name - - def one_of(self, *args): - """ - The intent parser should require one of the provided entity types to - validate this clause. - - Args: - args(args): *args notation list of entity names - - Returns: - self: to continue modifications. - """ - self.at_least_one.append(args) - return self - - def require(self, entity_type, attribute_name=None): - """ - The intent parser should require an entity of the provided type. - - Args: - entity_type(str): an entity type - attribute_name(str): the name of the attribute on the parsed intent. - Defaults to match entity_type. - - Returns: - self: to continue modifications. - """ - if not attribute_name: - attribute_name = entity_type - self.requires += [(entity_type, attribute_name)] - return self - - def exclude(self, entity_type): - """ - The intent parser must not contain an entity of the provided type. - - Args: - entity_type(str): an entity type - - Returns: - self: to continue modifications. - """ - self.excludes.append(entity_type) - return self - - def optionally(self, entity_type, attribute_name=None): - """ - Parsed intents from this parser can optionally include an entity of the - provided type. - - Args: - entity_type(str): an entity type - attribute_name(str): the name of the attribute on the parsed intent. - Defaults to match entity_type. - - Returns: - self: to continue modifications. - """ - if not attribute_name: - attribute_name = entity_type - self.optional += [(entity_type, attribute_name)] - return self - - def build(self): - """ - Constructs an intent from the builder's specifications. - - :return: an Intent instance. - """ - return Intent(self.name, self.requires, - self.at_least_one, self.optional, - self.excludes) +# OVOS-INTENT-4 keyword-intent *definition* primitives. The canonical, adapt-free +# implementations live in ovos-spec-tools; they are re-exported here so skills keep +# their long-standing `from ovos_workshop.intents import IntentBuilder` import while +# the single source of truth is the spec. The `IntentServiceInterface` producer +# (and the munge_* helpers) below consume these definitions. +from ovos_spec_tools import Intent, IntentBuilder, open_intent_envelope def to_alnum(skill_id: str) -> str: @@ -673,15 +376,3 @@ def __contains__(self, val): Checks if an intent name has been registered. """ return val in [i[0] for i in self.registered_intents] - - -def open_intent_envelope(message): - """ - Convert dictionary received over messagebus to Intent. - """ - intent_dict = message.data - return Intent(intent_dict.get('name'), - intent_dict.get('requires'), - intent_dict.get('at_least_one'), - intent_dict.get('optional'), - intent_dict.get('excludes')) diff --git a/pyproject.toml b/pyproject.toml index 5a4bdcd2..242509d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ dependencies = [ "ovos-utils>= 0.7.0,<1.0.0", "ovos_bus_client>=2.2.0a1,<3.0.0", "ovos-config>=0.0.12,<3.0.0", - "ovos-spec-tools>=0.9.0a1", + "ovos-spec-tools>=0.14.0a1", # carries the adapt-free intent-definition primitives (OVOS-INTENT-4) "ovos-yes-no-plugin>=0.3.0,<1.0.0", "ovos-option-matcher-fuzzy-plugin>=0.0.1,<1.0.0", "ovos-number-parser>=0.0.1,<1.0.0", diff --git a/test/unittests/test_intent.py b/test/unittests/test_intent.py deleted file mode 100644 index 8e2cf97f..00000000 --- a/test/unittests/test_intent.py +++ /dev/null @@ -1,271 +0,0 @@ -import unittest - -from ovos_workshop.intents import Intent, IntentBuilder - - -class IntentTest(unittest.TestCase): - - def test_basic_intent(self): - intent = IntentBuilder("play television intent") \ - .require("PlayVerb") \ - .require("Television Show") \ - .build() - tags = [{'match': 'play', 'key': 'play', 'start_token': 0, - 'entities': [{'key': 'play', 'match': 'play', 'data': [('play', 'PlayVerb')], 'confidence': 1.0}], - 'end_token': 0, 'from_context': False}, {'start_token': 1, 'entities': [ - {'key': 'the big bang theory', 'match': 'the big bang theory', - 'data': [('the big bang theory', 'Television Show')], 'confidence': 1.0}], 'confidence': 1.0, - 'end_token': 4, 'match': 'the big bang theory', - 'key': 'the big bang theory', 'from_context': False}] - result_intent = intent.validate(tags, 0.95) - assert result_intent.get('confidence') > 0.0 - assert result_intent.get('PlayVerb') == 'play' - assert result_intent.get('Television Show') == "the big bang theory" - - def test_at_least_one(self): - intent = IntentBuilder("play intent") \ - .require("PlayVerb") \ - .one_of("Television Show", "Radio Station") \ - .build() - tags = [{'match': 'play', 'key': 'play', 'start_token': 0, - 'entities': [{'key': 'play', 'match': 'play', 'data': [('play', 'PlayVerb')], 'confidence': 1.0}], - 'end_token': 0, 'from_context': False}, {'start_token': 1, 'entities': [ - {'key': 'the big bang theory', 'match': 'the big bang theory', - 'data': [('the big bang theory', 'Television Show')], 'confidence': 1.0}], 'confidence': 1.0, - 'end_token': 4, 'match': 'the big bang theory', - 'key': 'the big bang theory', 'from_context': False}] - - result_intent = intent.validate(tags, 0.95) - assert result_intent.get('confidence') > 0.0 - assert result_intent.get('PlayVerb') == 'play' - assert result_intent.get('Television Show') == "the big bang theory" - - tags = [{'match': 'play', 'key': 'play', 'start_token': 0, - 'entities': [{'key': 'play', 'match': 'play', 'data': [('play', 'PlayVerb')], 'confidence': 1.0}], - 'end_token': 0, 'from_context': False}, - {'match': 'barenaked ladies', 'key': 'barenaked ladies', 'start_token': 2, 'entities': [ - {'key': 'barenaked ladies', 'match': 'barenaked ladies', - 'data': [('barenaked ladies', 'Radio Station')], 'confidence': 1.0}], 'end_token': 3, - 'from_context': False}] - - result_intent = intent.validate(tags, 0.8) - assert result_intent.get('confidence') > 0.0 - assert result_intent.get('PlayVerb') == 'play' - assert result_intent.get('Radio Station') == "barenaked ladies" - - def test_at_least_on_no_required(self): - intent = IntentBuilder("play intent") \ - .one_of("Television Show", "Radio Station") \ - .build() - tags = [{'match': 'play', 'key': 'play', 'start_token': 0, - 'entities': [{'key': 'play', 'match': 'play', 'data': [('play', 'PlayVerb')], 'confidence': 1.0}], - 'end_token': 0, 'from_context': False}, {'start_token': 1, 'entities': [ - {'key': 'the big bang theory', 'match': 'the big bang theory', - 'data': [('the big bang theory', 'Television Show')], 'confidence': 1.0}], 'confidence': 1.0, - 'end_token': 4, 'match': 'the big bang theory', - 'key': 'the big bang theory', 'from_context': False}] - result_intent = intent.validate(tags, 0.9) - assert result_intent.get('confidence') > 0.0 - assert result_intent.get('Television Show') == "the big bang theory" - - tags = [{'match': 'play', 'key': 'play', 'start_token': 0, - 'entities': [{'key': 'play', 'match': 'play', 'data': [('play', 'PlayVerb')], 'confidence': 1.0}], - 'end_token': 0, 'from_context': False}, - {'match': 'barenaked ladies', 'key': 'barenaked ladies', 'start_token': 2, 'entities': [ - {'key': 'barenaked ladies', 'match': 'barenaked ladies', - 'data': [('barenaked ladies', 'Radio Station')], 'confidence': 1.0}], 'end_token': 3, - 'from_context': False}] - - result_intent = intent.validate(tags, 0.8) - assert result_intent.get('confidence') > 0.0 - assert result_intent.get('Radio Station') == "barenaked ladies" - - def test_at_least_one_alone(self): - intent = IntentBuilder("OptionsForLunch") \ - .one_of("Question", "Command") \ - .build() - tags = [{'match': 'show', 'key': 'show', 'start_token': 0, - 'entities': [{'key': 'show', 'match': 'show', 'data': [('show', 'Command')], 'confidence': 1.0}], - 'end_token': 0, 'from_context': False}] - - result_intent = intent.validate(tags, 1.0) - assert result_intent.get('confidence') > 0.0 - assert result_intent.get('Command') == "show" - - def test_basic_intent_with_alternate_names(self): - intent = IntentBuilder("play television intent") \ - .require("PlayVerb", "Play Verb") \ - .require("Television Show", "series") \ - .build() - tags = [{'match': 'play', 'key': 'play', 'start_token': 0, - 'entities': [{'key': 'play', 'match': 'play', 'data': [('play', 'PlayVerb')], 'confidence': 1.0}], - 'end_token': 0, 'from_context': False}, {'start_token': 1, 'entities': [ - {'key': 'the big bang theory', 'match': 'the big bang theory', - 'data': [('the big bang theory', 'Television Show')], 'confidence': 1.0}], 'confidence': 1.0, - 'end_token': 4, 'match': 'the big bang theory', - 'key': 'the big bang theory', 'from_context': False}] - - result_intent = intent.validate(tags, 0.95) - assert result_intent.get('confidence') > 0.0 - assert result_intent.get('Play Verb') == 'play' - assert result_intent.get('series') == "the big bang theory" - - def test_resolve_one_of(self): - tags = [ - { - "confidence": 1.0, - "end_token": 1, - "entities": [ - { - "confidence": 1.0, - "data": [ - [ - "what is", - "skill_iot_controlINFORMATION_QUERY" - ] - ], - "key": "what is", - "match": "what is" - } - ], - "from_context": False, - "key": "what is", - "match": "what is", - "start_token": 0 - }, - { - "end_token": 3, - "entities": [ - { - "confidence": 1.0, - "data": [ - [ - "temperature", - "skill_weatherTemperature" - ], - [ - "temperature", - "skill_iot_controlTEMPERATURE" - ] - ], - "key": "temperature", - "match": "temperature" - } - ], - "from_context": False, - "key": "temperature", - "match": "temperature", - "start_token": 3 - }, - { - "confidence": 1.0, - "end_token": 7, - "entities": [ - { - "confidence": 1.0, - "data": [ - [ - "living room", - "skill_iot_controlENTITY" - ] - ], - "key": "living room", - "match": "living room" - } - ], - "from_context": False, - "key": "living room", - "match": "living room", - "start_token": 6 - } - ] - - at_least_one = [ - [ - "skill_iot_controlINFORMATION_QUERY" - ], - [ - "skill_iot_controlTEMPERATURE", - "skill_iot_controlENTITY" - ], - [ - "skill_iot_controlTEMPERATURE" - ] - ] - - result = { - "skill_iot_controlENTITY": [ - { - "confidence": 1.0, - "end_token": 7, - "entities": [ - { - "confidence": 1.0, - "data": [ - [ - "living room", - "skill_iot_controlENTITY" - ] - ], - "key": "living room", - "match": "living room" - } - ], - "from_context": False, - "key": "living room", - "match": "living room", - "start_token": 6 - } - ], - "skill_iot_controlINFORMATION_QUERY": [ - { - "confidence": 1.0, - "end_token": 1, - "entities": [ - { - "confidence": 1.0, - "data": [ - [ - "what is", - "skill_iot_controlINFORMATION_QUERY" - ] - ], - "key": "what is", - "match": "what is" - } - ], - "from_context": False, - "key": "what is", - "match": "what is", - "start_token": 0 - } - ], - "skill_iot_controlTEMPERATURE": [ - { - "end_token": 3, - "entities": [ - { - "confidence": 1.0, - "data": [ - [ - "temperature", - "skill_weatherTemperature" - ], - [ - "temperature", - "skill_iot_controlTEMPERATURE" - ] - ], - "key": "temperature", - "match": "temperature" - } - ], - "from_context": False, - "key": "temperature", - "match": "temperature", - "start_token": 3 - } - ] - } - - assert Intent._resolve_one_of(tags, at_least_one) == result From 5f35d5f242193e5d636cccd1963380e6700e5b5b Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Sat, 27 Jun 2026 03:59:18 +0100 Subject: [PATCH 2/3] fix: pin published ovos-spec-tools>=0.16.1a2, drop git-dep Co-Authored-By: Claude Opus 4.8 --- .github/workflows/build_tests.yml | 5 ++--- .github/workflows/coverage.yml | 2 -- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build_tests.yml b/.github/workflows/build_tests.yml index 6c39e8e0..fed911f6 100644 --- a/.github/workflows/build_tests.yml +++ b/.github/workflows/build_tests.yml @@ -13,6 +13,5 @@ jobs: with: test_path: 'test/unittests' install_extras: 'test' - # intent-definition primitives now live in ovos-spec-tools (unpublished branch); - # plus the in-flight cap cascade: cap-lifted ovos-adapt-parser + ovos-core allowing ovos-workshop 9.x (unpublished) - pre_install_pip: 'git+https://github.com/OpenVoiceOS/ovos-spec-tools@feat/intent-primitives git+https://github.com/OpenVoiceOS/ovos-adapt-pipeline-plugin@fix/allow-ovos-workshop-9 git+https://github.com/OpenVoiceOS/ovos-core@fix/allow-ovos-workshop-9' + # in-flight cap cascade: cap-lifted ovos-adapt-parser + ovos-core allowing ovos-workshop 9.x (unpublished) + pre_install_pip: 'git+https://github.com/OpenVoiceOS/ovos-adapt-pipeline-plugin@fix/allow-ovos-workshop-9 git+https://github.com/OpenVoiceOS/ovos-core@fix/allow-ovos-workshop-9' diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 20d7525b..ba4fdddc 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -23,8 +23,6 @@ jobs: python -m pip install build wheel - name: Install repo run: | - # intent-definition primitives now live in ovos-spec-tools (unpublished branch) - pip install git+https://github.com/OpenVoiceOS/ovos-spec-tools@feat/intent-primitives pip install -e . - name: Install test dependencies run: | diff --git a/pyproject.toml b/pyproject.toml index 242509d9..9abc70e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ dependencies = [ "ovos-utils>= 0.7.0,<1.0.0", "ovos_bus_client>=2.2.0a1,<3.0.0", "ovos-config>=0.0.12,<3.0.0", - "ovos-spec-tools>=0.14.0a1", # carries the adapt-free intent-definition primitives (OVOS-INTENT-4) + "ovos-spec-tools>=0.16.1a2", # carries the adapt-free intent-definition primitives (OVOS-INTENT-4) "ovos-yes-no-plugin>=0.3.0,<1.0.0", "ovos-option-matcher-fuzzy-plugin>=0.0.1,<1.0.0", "ovos-number-parser>=0.0.1,<1.0.0", From 32e3dcb753f92d5c41e9d8e40a714954c2885280 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Sat, 27 Jun 2026 16:27:43 +0100 Subject: [PATCH 3/3] Update build_tests.yml --- .github/workflows/build_tests.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build_tests.yml b/.github/workflows/build_tests.yml index fed911f6..237096e4 100644 --- a/.github/workflows/build_tests.yml +++ b/.github/workflows/build_tests.yml @@ -13,5 +13,3 @@ jobs: with: test_path: 'test/unittests' install_extras: 'test' - # in-flight cap cascade: cap-lifted ovos-adapt-parser + ovos-core allowing ovos-workshop 9.x (unpublished) - pre_install_pip: 'git+https://github.com/OpenVoiceOS/ovos-adapt-pipeline-plugin@fix/allow-ovos-workshop-9 git+https://github.com/OpenVoiceOS/ovos-core@fix/allow-ovos-workshop-9'