diff --git a/.github/workflows/build-tests.yml b/.github/workflows/build-tests.yml index 6dff7ba..1cf35f9 100644 --- a/.github/workflows/build-tests.yml +++ b/.github/workflows/build-tests.yml @@ -11,4 +11,4 @@ jobs: with: python_versions: '["3.10", "3.11", "3.12", "3.13", "3.14"]' install_extras: 'dev' - test_path: 'test' + test_path: 'test/unittests' diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d6cb8d8..2a5e7af 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -11,6 +11,6 @@ jobs: with: python_version: '3.11' coverage_source: 'ovos_utterance_plugin_cancel' - test_path: 'test/' - install_extras: '' + test_path: 'test/unittests' + install_extras: '.[dev]' min_coverage: 0 diff --git a/.github/workflows/opm-check.yml b/.github/workflows/opm-check.yml new file mode 100644 index 0000000..7545741 --- /dev/null +++ b/.github/workflows/opm-check.yml @@ -0,0 +1,14 @@ +name: OPM Plugin Check + +on: + pull_request: + branches: [dev, master, main] + workflow_dispatch: + +jobs: + opm_check: + uses: OpenVoiceOS/gh-automations/.github/workflows/opm-check.yml@dev + secrets: inherit + with: + python_version: '3.11' + plugin_type: 'auto' diff --git a/.github/workflows/ovoscope.yml b/.github/workflows/ovoscope.yml new file mode 100644 index 0000000..e1c8104 --- /dev/null +++ b/.github/workflows/ovoscope.yml @@ -0,0 +1,23 @@ +name: Ovoscope End-to-End Tests + +on: + push: + branches: [dev] + pull_request: + branches: [dev] + workflow_dispatch: + +jobs: + ovoscope: + uses: OpenVoiceOS/gh-automations/.github/workflows/ovoscope.yml@dev + secrets: inherit + with: + runner: "ubuntu-latest" + python_version: "3.11" + install_extras: "test" + test_path: "test/end2end/" + # The cancel transformer fires before intent matching, so the + # ovoscope cases here do not depend on any intent engine, and + # transformers don't emit bus events so bus-coverage tracking + # would be empty anyway. + pr_comment: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 80a7e5c..d0a8fd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,40 +1,28 @@ # Changelog -## [0.2.8a1](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/tree/0.2.8a1) (2026-04-17) +## [0.3.0a2](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/tree/0.3.0a2) (2026-06-16) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/compare/0.2.7a2...0.2.8a1) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/compare/0.3.0a1...0.3.0a2) **Merged pull requests:** -- fix: drop setup.py and requirements.txt [\#30](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/pull/30) ([JarbasAl](https://github.com/JarbasAl)) +- chore: add missing shared CI workflows [\#38](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/pull/38) ([JarbasAl](https://github.com/JarbasAl)) -## [0.2.7a2](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/tree/0.2.7a2) (2026-04-17) +## [0.3.0a1](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/tree/0.3.0a1) (2026-05-25) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/compare/0.2.7a1...0.2.7a2) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/compare/0.2.9a1...0.3.0a1) **Merged pull requests:** -- ci: modernize workflows, add pyproject.toml, type hints and tests [\#28](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/pull/28) ([JarbasAl](https://github.com/JarbasAl)) +- feat: cancel.blacklist veto for utterances \*about\* a cancel word \(partial \#7\) [\#35](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/pull/35) ([JarbasAl](https://github.com/JarbasAl)) -## [0.2.7a1](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/tree/0.2.7a1) (2026-04-03) +## [0.2.9a1](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/tree/0.2.9a1) (2026-05-25) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/compare/0.2.6a1...0.2.7a1) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/compare/0.2.8...0.2.9a1) **Merged pull requests:** -- fix\(i18n\): normalize locale folders to canonical BCP-47 [\#26](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/pull/26) ([JarbasAl](https://github.com/JarbasAl)) - -## [0.2.6a1](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/tree/0.2.6a1) (2025-11-24) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/compare/V0.2.5...0.2.6a1) - -**Merged pull requests:** - -- es-es/translate [\#24](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/pull/24) ([gitlocalize-app[bot]](https://github.com/apps/gitlocalize-app)) - -## [V0.2.5](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/tree/V0.2.5) (2025-07-31) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/compare/0.2.5...V0.2.5) +- fix: cancel transformer not firing — entry-point group, lang lookup, locale typos [\#32](https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel/pull/32) ([JarbasAl](https://github.com/JarbasAl)) diff --git a/ovos_utterance_plugin_cancel/__init__.py b/ovos_utterance_plugin_cancel/__init__.py index 2810dec..6690330 100644 --- a/ovos_utterance_plugin_cancel/__init__.py +++ b/ovos_utterance_plugin_cancel/__init__.py @@ -22,22 +22,56 @@ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import os -from os.path import join, dirname, isfile +from os.path import join, dirname from functools import lru_cache from typing import Dict, List, Optional, Tuple from ovos_plugin_manager.templates.transformers import UtteranceTransformer +from ovos_spec_tools import LocaleResources, standardize_lang from ovos_utils.log import LOG -from ovos_utils.lang import standardize_lang_tag -from ovos_utils.bracket_expansion import expand_template -from langcodes import closest_match + + +def _resolve_lang(context: Optional[Dict[str, object]], + default: str = "en-US") -> str: + """Return the BCP-47 language tag for an UtteranceTransformer call. + + ``context`` is the OVOS-MSG-1 ``message.context`` dict the + transformer service hands to ``transform()``. Two sources may carry + a language: + + 1. ``context["lang"]`` — the top-level convenience key that + ovos-core's IntentService writes before invoking transformers + (``ovos_core/intent_services/service.py::_handle_transformers``). + This is the **current contract** between core and transformer + plugins; trust it when present. + 2. ``context["session"]["lang"]`` — the normative session-carrier + field per OVOS-MSG-1 §4. Falls back here when a caller hands the + transformer a Message without going through ovos-core's + pre-processing (HiveMind relays, tests, alternative bus + clients). + + The convenience top-level key is a known gap in the spec — the + transformer signature will likely grow an explicit ``lang`` kwarg + in a future revision, at which point this helper collapses to a + one-liner. Until then, the dual lookup keeps the plugin robust + against direct callers. + + ``dict.get(key, default)`` returns ``None`` for keys present with a + ``None`` value (only a *missing* key triggers the default), so the + ``or`` chain handles explicit ``None`` correctly. + """ + context = context or {} + session = context.get("session") or {} + lang = (context.get("lang") + or session.get("lang") + or default) + return standardize_lang(lang) class NevermindPlugin(UtteranceTransformer): """Utterance transformer that drops utterances ending with a cancel phrase. - Cancel phrases are loaded from ``locale//cancel.intent`` and + Cancel phrases are loaded from ``locale//cancel.voc`` and matched against the tail of each utterance. On a match the utterance list is cleared and ``{"canceled": True, "cancel_word": }`` is added to the context dict so downstream components can react. @@ -45,39 +79,41 @@ class NevermindPlugin(UtteranceTransformer): def __init__(self, name: str = "ovos-utterance-cancel", priority: int = 15) -> None: super().__init__(name, priority) + # OVOS-INTENT-2 resource loader. One instance serves every language + # the plugin ships; the language is a parameter of each load call. + # The default lang_resolver is `closest_lang` (OVOS-INTENT-2 §2.2 + # smart fallback), gated on distance < 10. + self._resources = LocaleResources( + skill_locale=join(dirname(__file__), "locale")) @lru_cache() def get_cancel_words(self, lang: str = "en-US") -> List[str]: - """Return the list of cancel phrases for *lang*. - - Phrases are read from ``locale//cancel.intent``, expanded - via bracket-expansion, and deduplicated. The result is LRU-cached per - language tag for the lifetime of the process. + """Return the cancel phrases for *lang* (``cancel.voc``).""" + try: + phrases = self._resources.load_vocabulary("cancel", lang) + except FileNotFoundError: + LOG.warning(f"cancel.voc not available for {lang}") + return [] + return list({phrase.strip() for phrase in phrases if phrase.strip()}) - Args: - lang: BCP-47 language tag (e.g. ``"en-US"``). + @lru_cache() + def get_cancel_blacklist(self, lang: str = "en-US") -> List[str]: + """Return the *veto prefixes* for *lang* (``cancel.blacklist``). - Returns: - List of cancel phrases, or an empty list when no locale is close - enough (langcodes distance ≥ 10). + OVOS-INTENT-2 §4.3 defines ``.blacklist`` as a phrase set that + an engine consults to *exclude* matches. Here, utterances that + start with any phrase in ``cancel.blacklist`` bypass the cancel + suffix match — they are *about* a cancel word (define / spell / + pronounce / play / etc.) rather than commands to cancel. + Partial fix for issue #7. A missing ``cancel.blacklist`` is + non-fatal: the plugin falls back to the historic + "always check the suffix" behaviour. """ - locale_dir = join(dirname(__file__), "locale") - langs = [l for l in os.listdir(locale_dir) - if isfile(join(locale_dir, l, "cancel.intent"))] - best_lang, score = closest_match(lang, langs) - # langcodes distance: 0 = same, 1-3 = minor regional, 4-10 = significant regional - if score < 10: - res_path = join(locale_dir, best_lang, "cancel.intent") - lines: List[str] = [] - with open(res_path) as f: - for line in f: - line = line.strip() - if not line or line.startswith("#"): - continue - lines.extend(expand_template(line)) - return list({l.strip() for l in lines if l.strip()}) - LOG.warning(f"cancel.intent not available for {lang}") - return [] + try: + phrases = self._resources.load_blacklist("cancel", lang) + except FileNotFoundError: + return [] + return list({phrase.strip() for phrase in phrases if phrase.strip()}) def transform( self, @@ -86,6 +122,11 @@ def transform( ) -> Tuple[List[str], Dict[str, object]]: """Drop utterances that end with a cancel phrase. + Skipped when the utterance starts with a phrase listed in + ``cancel.blacklist`` for the active language — partial veto for + the edge cases tracked in issue #7 (e.g. ``"say nevermind"``, + ``"what is the opposite of nevermind"``). + Args: utterances: Recognised utterance candidates. context: Session context dict; ``lang`` key is used for locale @@ -97,10 +138,12 @@ def transform( ``{"canceled": True, "cancel_word": }``. Otherwise the original utterances are returned unchanged with an empty dict. """ - context = context or {} - lang = standardize_lang_tag(context.get("lang", "en-US")) + lang = _resolve_lang(context) + blacklist = self.get_cancel_blacklist(lang) for nevermind in self.get_cancel_words(lang): for utterance in utterances: + if any(utterance.startswith(p) for p in blacklist): + continue if utterance.endswith(nevermind): return [], {"canceled": True, "cancel_word": nevermind} return utterances, {} diff --git a/ovos_utterance_plugin_cancel/locale/ca-ES/cancel.blacklist b/ovos_utterance_plugin_cancel/locale/ca-ES/cancel.blacklist new file mode 100644 index 0000000..32a44ce --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/ca-ES/cancel.blacklist @@ -0,0 +1,18 @@ +# OVOS-INTENT-2 §4.3 blacklist for the cancel transformer. +# +# Utterance prefixes that veto the cancel suffix match. +# +# When an utterance starts with one of these phrases, the cancel +# suffix match is skipped — even if the utterance happens to end with +# a word from cancel.voc. The utterance is *about* the cancel word +# (define it, spell it, pronounce it, ask after it) rather than a +# command to cancel. +(reprodueix|digues|lletreja|pronuncia|llegeix|repeteix|escriu) +(defineix|explica|tradueix) +digues-me (el|la|sobre|què|allò) +ensenya'm +com (ho fas|es|és|funciona) +què (és|són|fa|fan|vol dir) +quin és (el significat|el contrari|la traducció) +quina és (l'antònim|la traducció|la pronunciació) +per què (no|fas|fa|és) diff --git a/ovos_utterance_plugin_cancel/locale/ca-ES/cancel.intent b/ovos_utterance_plugin_cancel/locale/ca-ES/cancel.intent index 8fce571..af5dff0 100644 --- a/ovos_utterance_plugin_cancel/locale/ca-ES/cancel.intent +++ b/ovos_utterance_plugin_cancel/locale/ca-ES/cancel.intent @@ -1,15 +1,8 @@ -cancel·la això -cancel·la aquesta ordre -cancel·la esta ordre -cancel·la l'ordre -cancel·la s'ordre -cancel·la-ho -no importa això -no importa aquesta ordre -no importa esta ordre -oblida això -oblida aquesta ordre -oblida esta ordre -oblida l'ordre -oblida s'ordre -oblida-ho \ No newline at end of file +# Documents the matching pattern: an utterance ending with a cancel +# phrase from cancel.voc, optionally with no leading utterance at all +# (a standalone cancel). +# +# The plugin currently does suffix-matching with the cancel.voc +# vocabulary; this .intent is informational, not consumed at runtime, +# and exists so the resource role is unambiguous in the locale folder. +[{utterance}] diff --git a/ovos_utterance_plugin_cancel/locale/ca-ES/cancel.voc b/ovos_utterance_plugin_cancel/locale/ca-ES/cancel.voc new file mode 100644 index 0000000..8fce571 --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/ca-ES/cancel.voc @@ -0,0 +1,15 @@ +cancel·la això +cancel·la aquesta ordre +cancel·la esta ordre +cancel·la l'ordre +cancel·la s'ordre +cancel·la-ho +no importa això +no importa aquesta ordre +no importa esta ordre +oblida això +oblida aquesta ordre +oblida esta ordre +oblida l'ordre +oblida s'ordre +oblida-ho \ No newline at end of file diff --git a/ovos_utterance_plugin_cancel/locale/da-DK/cancel.blacklist b/ovos_utterance_plugin_cancel/locale/da-DK/cancel.blacklist new file mode 100644 index 0000000..2d63170 --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/da-DK/cancel.blacklist @@ -0,0 +1,17 @@ +# OVOS-INTENT-2 §4.3 blacklist for the cancel transformer. +# +# Utterance prefixes that veto the cancel suffix match. +# +# When an utterance starts with one of these phrases, the cancel +# suffix match is skipped — even if the utterance happens to end with +# a word from cancel.voc. The utterance is *about* the cancel word +# (define it, spell it, pronounce it, ask after it) rather than a +# command to cancel. +(afspil|sig|stav|udtal|læs|gentag|skriv) +(definer|forklar|oversæt) +fortæl mig (om|hvad|hvordan) +lær mig +hvordan (gør du|skal man|er|udtaler du|staves) +hvad (er|betyder|hedder) +hvad er (betydningen|det modsatte|udtalen|oversættelsen) +hvorfor (gør du|gør du ikke|virker) diff --git a/ovos_utterance_plugin_cancel/locale/da-DK/cancel.intent b/ovos_utterance_plugin_cancel/locale/da-DK/cancel.intent index 6849635..af5dff0 100644 --- a/ovos_utterance_plugin_cancel/locale/da-DK/cancel.intent +++ b/ovos_utterance_plugin_cancel/locale/da-DK/cancel.intent @@ -1,14 +1,8 @@ -afbryd det -annulle kommando -annuller den kommando -annuller den ordre -annuller det -annuller ordre -glem den kommando -glem den ordre -glem det -glem kommando -glem ordre -ligeglad med den kommando -pyt med den rækkefølge -pyt med det \ No newline at end of file +# Documents the matching pattern: an utterance ending with a cancel +# phrase from cancel.voc, optionally with no leading utterance at all +# (a standalone cancel). +# +# The plugin currently does suffix-matching with the cancel.voc +# vocabulary; this .intent is informational, not consumed at runtime, +# and exists so the resource role is unambiguous in the locale folder. +[{utterance}] diff --git a/ovos_utterance_plugin_cancel/locale/da-DK/cancel.voc b/ovos_utterance_plugin_cancel/locale/da-DK/cancel.voc new file mode 100644 index 0000000..fe78a24 --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/da-DK/cancel.voc @@ -0,0 +1,14 @@ +afbryd det +annuller kommando +annuller den kommando +annuller den ordre +annuller det +annuller ordre +glem den kommando +glem den ordre +glem det +glem kommando +glem ordre +ligeglad med den kommando +pyt med den rækkefølge +pyt med det \ No newline at end of file diff --git a/ovos_utterance_plugin_cancel/locale/de-DE/cancel.blacklist b/ovos_utterance_plugin_cancel/locale/de-DE/cancel.blacklist new file mode 100644 index 0000000..dd4e39c --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/de-DE/cancel.blacklist @@ -0,0 +1,17 @@ +# OVOS-INTENT-2 §4.3 blacklist for the cancel transformer. +# +# Utterance prefixes that veto the cancel suffix match. +# +# When an utterance starts with one of these phrases, the cancel +# suffix match is skipped — even if the utterance happens to end with +# a word from cancel.voc. The utterance is *about* the cancel word +# (define it, spell it, pronounce it, ask after it) rather than a +# command to cancel. +(spiele|sage|sag|buchstabiere|sprich|lies|wiederhole|schreib|tippe) +(definiere|erkläre|übersetze) +(sag|erzähl) mir (was|wie|über|das|die|den) +(bring mir bei|lehre mich) +wie (sagt man|spricht man|schreibt man|buchstabiert man|funktioniert) +was (ist|sind|bedeutet|heißt) +was ist (die bedeutung|das gegenteil|die übersetzung|die aussprache) +warum (machst du|tust du nicht|funktioniert) diff --git a/ovos_utterance_plugin_cancel/locale/de-DE/cancel.intent b/ovos_utterance_plugin_cancel/locale/de-DE/cancel.intent index 43c8ec6..af5dff0 100644 --- a/ovos_utterance_plugin_cancel/locale/de-DE/cancel.intent +++ b/ovos_utterance_plugin_cancel/locale/de-DE/cancel.intent @@ -1,11 +1,8 @@ -(befehl|kommando) (vergessen|zurücknehmen|aufheben|abbrechen|zurück|löschen) -(breche|brech|brich) ((den|diesen|) befehl|(dieses|das|) kommando) ab -(hebe|heb) ((den|diesen|) befehl|(dieses|das|) kommando) auf -(nehme|nehm|nimm) (den|diesen|) (befehl|kommando) zurück -(vergess|vergiss|vergesse|lösche) (es|(den|diesen) befehl|(dieses|das|) kommando) -Befehl Abbrechen -Vergiss es -vergiss Befehl -vergiss Kommando -vergiss das -vergiss diesen Befehl \ No newline at end of file +# Documents the matching pattern: an utterance ending with a cancel +# phrase from cancel.voc, optionally with no leading utterance at all +# (a standalone cancel). +# +# The plugin currently does suffix-matching with the cancel.voc +# vocabulary; this .intent is informational, not consumed at runtime, +# and exists so the resource role is unambiguous in the locale folder. +[{utterance}] diff --git a/ovos_utterance_plugin_cancel/locale/de-DE/cancel.voc b/ovos_utterance_plugin_cancel/locale/de-DE/cancel.voc new file mode 100644 index 0000000..43c8ec6 --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/de-DE/cancel.voc @@ -0,0 +1,11 @@ +(befehl|kommando) (vergessen|zurücknehmen|aufheben|abbrechen|zurück|löschen) +(breche|brech|brich) ((den|diesen|) befehl|(dieses|das|) kommando) ab +(hebe|heb) ((den|diesen|) befehl|(dieses|das|) kommando) auf +(nehme|nehm|nimm) (den|diesen|) (befehl|kommando) zurück +(vergess|vergiss|vergesse|lösche) (es|(den|diesen) befehl|(dieses|das|) kommando) +Befehl Abbrechen +Vergiss es +vergiss Befehl +vergiss Kommando +vergiss das +vergiss diesen Befehl \ No newline at end of file diff --git a/ovos_utterance_plugin_cancel/locale/en-US/cancel.blacklist b/ovos_utterance_plugin_cancel/locale/en-US/cancel.blacklist new file mode 100644 index 0000000..14a0c35 --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/en-US/cancel.blacklist @@ -0,0 +1,18 @@ +# OVOS-INTENT-2 §4.3 blacklist for the cancel transformer. +# +# Utterance prefixes that veto the cancel suffix match. +# +# When an utterance starts with one of these phrases, the cancel +# suffix match is skipped — even if the utterance happens to end with +# a word from cancel.voc. The utterance is *about* the cancel word +# (define it, spell it, pronounce it, ask after it) rather than a +# command to cancel. +(play|say|spell|pronounce|read|repeat|write|type) +(define|explain|translate) +tell me (the|about|what) +teach me +how (do you|to|does|is) +what (is|are|does|do) +what's the (meaning|opposite|phonemes|translation|french|german|spanish|portuguese|italian|catalan) +what is (another|the meaning|the opposite|the phonemes|the french|the german|the spanish|the portuguese|the italian|the catalan|the translation) +why (do you|don't you|does not|doesn't) diff --git a/ovos_utterance_plugin_cancel/locale/en-US/cancel.intent b/ovos_utterance_plugin_cancel/locale/en-US/cancel.intent index dde9ce7..af5dff0 100644 --- a/ovos_utterance_plugin_cancel/locale/en-US/cancel.intent +++ b/ovos_utterance_plugin_cancel/locale/en-US/cancel.intent @@ -1,15 +1,8 @@ -cancel command -cancel it -cancel order -cancel that -cancel that command -cancel that order -forget command -forget it -forget order -forget that -forget that command -forget that order -nevermind that -nevermind that command -nevermind that order \ No newline at end of file +# Documents the matching pattern: an utterance ending with a cancel +# phrase from cancel.voc, optionally with no leading utterance at all +# (a standalone cancel). +# +# The plugin currently does suffix-matching with the cancel.voc +# vocabulary; this .intent is informational, not consumed at runtime, +# and exists so the resource role is unambiguous in the locale folder. +[{utterance}] diff --git a/ovos_utterance_plugin_cancel/locale/en-US/cancel.voc b/ovos_utterance_plugin_cancel/locale/en-US/cancel.voc new file mode 100644 index 0000000..dde9ce7 --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/en-US/cancel.voc @@ -0,0 +1,15 @@ +cancel command +cancel it +cancel order +cancel that +cancel that command +cancel that order +forget command +forget it +forget order +forget that +forget that command +forget that order +nevermind that +nevermind that command +nevermind that order \ No newline at end of file diff --git a/ovos_utterance_plugin_cancel/locale/es-ES/cancel.blacklist b/ovos_utterance_plugin_cancel/locale/es-ES/cancel.blacklist new file mode 100644 index 0000000..11fb003 --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/es-ES/cancel.blacklist @@ -0,0 +1,17 @@ +# OVOS-INTENT-2 §4.3 blacklist for the cancel transformer. +# +# Utterance prefixes that veto the cancel suffix match. +# +# When an utterance starts with one of these phrases, the cancel +# suffix match is skipped — even if the utterance happens to end with +# a word from cancel.voc. The utterance is *about* the cancel word +# (define it, spell it, pronounce it, ask after it) rather than a +# command to cancel. +(reproduce|di|deletrea|pronuncia|lee|repite|escribe) +(define|explica|traduce) +dime (el|la|sobre|qué|lo) +enséñame +cómo (se|haces|funciona|se dice|se escribe|se pronuncia|se deletrea) +qué (es|son|hace|hacen|significa|quiere decir) +cuál es (el significado|el opuesto|el contrario|la traducción|la pronunciación) +por qué (no|haces|hace|es|funciona) diff --git a/ovos_utterance_plugin_cancel/locale/es-ES/cancel.intent b/ovos_utterance_plugin_cancel/locale/es-ES/cancel.intent index 4adf344..af5dff0 100644 --- a/ovos_utterance_plugin_cancel/locale/es-ES/cancel.intent +++ b/ovos_utterance_plugin_cancel/locale/es-ES/cancel.intent @@ -1,15 +1,8 @@ -cancela comando -cancela eso -cancelar esa orden -cancelar ese comando -cancelar orden -cancélalo -no importa esa orden -no importa ese comando -no importa eso -olvida esa orden -olvida ese comando -olvida eso -olvidar comando -olvidar orden -olvídalo \ No newline at end of file +# Documents the matching pattern: an utterance ending with a cancel +# phrase from cancel.voc, optionally with no leading utterance at all +# (a standalone cancel). +# +# The plugin currently does suffix-matching with the cancel.voc +# vocabulary; this .intent is informational, not consumed at runtime, +# and exists so the resource role is unambiguous in the locale folder. +[{utterance}] diff --git a/ovos_utterance_plugin_cancel/locale/es-ES/cancel.voc b/ovos_utterance_plugin_cancel/locale/es-ES/cancel.voc new file mode 100644 index 0000000..4adf344 --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/es-ES/cancel.voc @@ -0,0 +1,15 @@ +cancela comando +cancela eso +cancelar esa orden +cancelar ese comando +cancelar orden +cancélalo +no importa esa orden +no importa ese comando +no importa eso +olvida esa orden +olvida ese comando +olvida eso +olvidar comando +olvidar orden +olvídalo \ No newline at end of file diff --git a/ovos_utterance_plugin_cancel/locale/fr-FR/cancel.blacklist b/ovos_utterance_plugin_cancel/locale/fr-FR/cancel.blacklist new file mode 100644 index 0000000..7d2db57 --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/fr-FR/cancel.blacklist @@ -0,0 +1,18 @@ +# OVOS-INTENT-2 §4.3 blacklist for the cancel transformer. +# +# Utterance prefixes that veto the cancel suffix match. +# +# When an utterance starts with one of these phrases, the cancel +# suffix match is skipped — even if the utterance happens to end with +# a word from cancel.voc. The utterance is *about* the cancel word +# (define it, spell it, pronounce it, ask after it) rather than a +# command to cancel. +(joue|dis|épelle|prononce|lis|répète|écris|tape) +(définis|explique|traduis) +(dis-moi|raconte-moi) (le|la|ce|sur|quoi|comment) +apprends-moi +comment (dit-on|écrit-on|prononce-t-on|épelle-t-on|fais-tu|fonctionne) +(qu'est-ce que|quel est|quelle est) +quel est (le sens|le contraire|l'opposé|la traduction|la prononciation) +quelle est (la signification|la traduction|la prononciation) +pourquoi (tu|ne|ça ne|ça) diff --git a/ovos_utterance_plugin_cancel/locale/fr-FR/cancel.intent b/ovos_utterance_plugin_cancel/locale/fr-FR/cancel.intent index 22b8bcd..af5dff0 100644 --- a/ovos_utterance_plugin_cancel/locale/fr-FR/cancel.intent +++ b/ovos_utterance_plugin_cancel/locale/fr-FR/cancel.intent @@ -1,12 +1,8 @@ -annuler cette commande -annuler la commande -annuler que -l'annuler -l'oublier -ne pas tenir compte de cette commande -oublier ce commandement -oublier cet ordre -oublier l'ordre -oublier la commande -oublier que -sans tenir compte de cela \ No newline at end of file +# Documents the matching pattern: an utterance ending with a cancel +# phrase from cancel.voc, optionally with no leading utterance at all +# (a standalone cancel). +# +# The plugin currently does suffix-matching with the cancel.voc +# vocabulary; this .intent is informational, not consumed at runtime, +# and exists so the resource role is unambiguous in the locale folder. +[{utterance}] diff --git a/ovos_utterance_plugin_cancel/locale/fr-FR/cancel.voc b/ovos_utterance_plugin_cancel/locale/fr-FR/cancel.voc new file mode 100644 index 0000000..22b8bcd --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/fr-FR/cancel.voc @@ -0,0 +1,12 @@ +annuler cette commande +annuler la commande +annuler que +l'annuler +l'oublier +ne pas tenir compte de cette commande +oublier ce commandement +oublier cet ordre +oublier l'ordre +oublier la commande +oublier que +sans tenir compte de cela \ No newline at end of file diff --git a/ovos_utterance_plugin_cancel/locale/gl-ES/cancel.blacklist b/ovos_utterance_plugin_cancel/locale/gl-ES/cancel.blacklist new file mode 100644 index 0000000..551fe0c --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/gl-ES/cancel.blacklist @@ -0,0 +1,17 @@ +# OVOS-INTENT-2 §4.3 blacklist for the cancel transformer. +# +# Utterance prefixes that veto the cancel suffix match. +# +# When an utterance starts with one of these phrases, the cancel +# suffix match is skipped — even if the utterance happens to end with +# a word from cancel.voc. The utterance is *about* the cancel word +# (define it, spell it, pronounce it, ask after it) rather than a +# command to cancel. +(reproduce|di|soletra|pronuncia|le|repite|escribe) +(define|explica|traduce) +dime (o|a|sobre|que|iso) +ensíname +como (se|fas|funciona|se di|se escribe|se pronuncia|se soletra) +que (é|son|fai|fan|significa|quere dicir) +cal é (o significado|o oposto|o contrario|a tradución|a pronuncia) +por que (non|fas|fai|é|funciona) diff --git a/ovos_utterance_plugin_cancel/locale/gl-ES/cancel.intent b/ovos_utterance_plugin_cancel/locale/gl-ES/cancel.intent index 71d8be9..af5dff0 100644 --- a/ovos_utterance_plugin_cancel/locale/gl-ES/cancel.intent +++ b/ovos_utterance_plugin_cancel/locale/gl-ES/cancel.intent @@ -1,15 +1,8 @@ -(cancela|cancelar) comando -(cancela|cancelar) ese comando -(cancela|cancelar) iso -(cancela|cancelar) orde -(cancélao|cancelalo) -(deixa estar|nada, esquece) -(esquece iso|esquecer iso) -(esquecer a orde|esquece a orde) -(esquecer esa orde|esquece esa orde) -(esquecer ese comando|esquece ese comando) -(esquecer o comando|esquece o comando) -(esquéceo|esquecelo) -(ignora esa orde|non fagas caso desa orde) -(ignora ese comando|non fagas caso dese comando) -cancelar esa orde \ No newline at end of file +# Documents the matching pattern: an utterance ending with a cancel +# phrase from cancel.voc, optionally with no leading utterance at all +# (a standalone cancel). +# +# The plugin currently does suffix-matching with the cancel.voc +# vocabulary; this .intent is informational, not consumed at runtime, +# and exists so the resource role is unambiguous in the locale folder. +[{utterance}] diff --git a/ovos_utterance_plugin_cancel/locale/gl-ES/cancel.voc b/ovos_utterance_plugin_cancel/locale/gl-ES/cancel.voc new file mode 100644 index 0000000..71d8be9 --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/gl-ES/cancel.voc @@ -0,0 +1,15 @@ +(cancela|cancelar) comando +(cancela|cancelar) ese comando +(cancela|cancelar) iso +(cancela|cancelar) orde +(cancélao|cancelalo) +(deixa estar|nada, esquece) +(esquece iso|esquecer iso) +(esquecer a orde|esquece a orde) +(esquecer esa orde|esquece esa orde) +(esquecer ese comando|esquece ese comando) +(esquecer o comando|esquece o comando) +(esquéceo|esquecelo) +(ignora esa orde|non fagas caso desa orde) +(ignora ese comando|non fagas caso dese comando) +cancelar esa orde \ No newline at end of file diff --git a/ovos_utterance_plugin_cancel/locale/it-IT/cancel.blacklist b/ovos_utterance_plugin_cancel/locale/it-IT/cancel.blacklist new file mode 100644 index 0000000..7c2f92a --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/it-IT/cancel.blacklist @@ -0,0 +1,17 @@ +# OVOS-INTENT-2 §4.3 blacklist for the cancel transformer. +# +# Utterance prefixes that veto the cancel suffix match. +# +# When an utterance starts with one of these phrases, the cancel +# suffix match is skipped — even if the utterance happens to end with +# a word from cancel.voc. The utterance is *about* the cancel word +# (define it, spell it, pronounce it, ask after it) rather than a +# command to cancel. +(riproduci|suona|di'|compita|pronuncia|leggi|ripeti|scrivi|digita) +(definisci|spiega|traduci) +dimmi (il|la|cosa|che cosa|su|riguardo) +insegnami +come (si|fai|funziona|si dice|si scrive|si pronuncia|si compita) +(cosa|che cosa) (è|sono|fa|fanno|significa|vuol dire) +qual è (il significato|l'opposto|il contrario|la traduzione|la pronuncia) +perché (non|fai|fa|è|funziona) diff --git a/ovos_utterance_plugin_cancel/locale/it-IT/cancel.intent b/ovos_utterance_plugin_cancel/locale/it-IT/cancel.intent index 4506111..af5dff0 100644 --- a/ovos_utterance_plugin_cancel/locale/it-IT/cancel.intent +++ b/ovos_utterance_plugin_cancel/locale/it-IT/cancel.intent @@ -1,15 +1,8 @@ -(dimentica|lascia perdere) il comando -(dimentica|lascia perdere) l'ordine -(dimentica|lascia perdere) quel comando -(dimentica|lascia perdere) quell'ordine -(lascia perdere|lascialo perdere|lascia perdere quello|lascia perdere l'ultima cosa) -(|dimentica|dimenticalo|dimentica quello|dimentica l'ultima cosa) -annulla il comando -annulla l'ordine -annulla quel comando -annulla quell'ordine -annulla(lo|) (quello| l'ultima cosa|) -dimentica(lo|) (quello| l'ultima cosa|) -non importa (di quello| dell'ultima cosa|) -non importa di quel comando -non importa di quell'ordine \ No newline at end of file +# Documents the matching pattern: an utterance ending with a cancel +# phrase from cancel.voc, optionally with no leading utterance at all +# (a standalone cancel). +# +# The plugin currently does suffix-matching with the cancel.voc +# vocabulary; this .intent is informational, not consumed at runtime, +# and exists so the resource role is unambiguous in the locale folder. +[{utterance}] diff --git a/ovos_utterance_plugin_cancel/locale/it-IT/cancel.voc b/ovos_utterance_plugin_cancel/locale/it-IT/cancel.voc new file mode 100644 index 0000000..b57ec52 --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/it-IT/cancel.voc @@ -0,0 +1,15 @@ +(dimentica|lascia perdere) il comando +(dimentica|lascia perdere) l'ordine +(dimentica|lascia perdere) quel comando +(dimentica|lascia perdere) quell'ordine +(lascia perdere|lascialo perdere|lascia perdere quello|lascia perdere l'ultima cosa) +(dimentica|dimenticalo|dimentica quello|dimentica l'ultima cosa) +annulla il comando +annulla l'ordine +annulla quel comando +annulla quell'ordine +annulla(lo|) (quello| l'ultima cosa|) +dimentica(lo|) (quello| l'ultima cosa|) +non importa (di quello| dell'ultima cosa|) +non importa di quel comando +non importa di quell'ordine \ No newline at end of file diff --git a/ovos_utterance_plugin_cancel/locale/nl-NL/cancel.blacklist b/ovos_utterance_plugin_cancel/locale/nl-NL/cancel.blacklist new file mode 100644 index 0000000..8dfe53e --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/nl-NL/cancel.blacklist @@ -0,0 +1,17 @@ +# OVOS-INTENT-2 §4.3 blacklist for the cancel transformer. +# +# Utterance prefixes that veto the cancel suffix match. +# +# When an utterance starts with one of these phrases, the cancel +# suffix match is skipped — even if the utterance happens to end with +# a word from cancel.voc. The utterance is *about* the cancel word +# (define it, spell it, pronounce it, ask after it) rather than a +# command to cancel. +(speel|zeg|spel|spreek|lees|herhaal|schrijf|type|typ) +(definieer|leg uit|vertaal) +vertel me (over|wat|hoe|de|het) +leer me +hoe (zeg je|schrijf je|spreek je|spel je|werkt) +wat (is|zijn|betekent|doet) +wat is (de betekenis|het tegenovergestelde|de vertaling|de uitspraak) +waarom (doe je|doe je niet|werkt) diff --git a/ovos_utterance_plugin_cancel/locale/nl-NL/cancel.intent b/ovos_utterance_plugin_cancel/locale/nl-NL/cancel.intent index 496976c..af5dff0 100644 --- a/ovos_utterance_plugin_cancel/locale/nl-NL/cancel.intent +++ b/ovos_utterance_plugin_cancel/locale/nl-NL/cancel.intent @@ -1,15 +1,8 @@ -annuleren -annuleren dat -bestelling annuleren -bestelling vergeten -commando annuleren -commando vergeten -die bestelling annuleren -die opdracht annuleren -laat dat commando maar zitten -laat die volgorde maar zitten -laat maar zitten -vergeet dat commando -vergeet die bestelling -vergeet het -vergeten dat \ No newline at end of file +# Documents the matching pattern: an utterance ending with a cancel +# phrase from cancel.voc, optionally with no leading utterance at all +# (a standalone cancel). +# +# The plugin currently does suffix-matching with the cancel.voc +# vocabulary; this .intent is informational, not consumed at runtime, +# and exists so the resource role is unambiguous in the locale folder. +[{utterance}] diff --git a/ovos_utterance_plugin_cancel/locale/nl-NL/cancel.voc b/ovos_utterance_plugin_cancel/locale/nl-NL/cancel.voc new file mode 100644 index 0000000..496976c --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/nl-NL/cancel.voc @@ -0,0 +1,15 @@ +annuleren +annuleren dat +bestelling annuleren +bestelling vergeten +commando annuleren +commando vergeten +die bestelling annuleren +die opdracht annuleren +laat dat commando maar zitten +laat die volgorde maar zitten +laat maar zitten +vergeet dat commando +vergeet die bestelling +vergeet het +vergeten dat \ No newline at end of file diff --git a/ovos_utterance_plugin_cancel/locale/pt-BR/cancel.blacklist b/ovos_utterance_plugin_cancel/locale/pt-BR/cancel.blacklist new file mode 100644 index 0000000..1e1b37d --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/pt-BR/cancel.blacklist @@ -0,0 +1,17 @@ +# OVOS-INTENT-2 §4.3 blacklist for the cancel transformer. +# +# Utterance prefixes that veto the cancel suffix match. +# +# When an utterance starts with one of these phrases, the cancel +# suffix match is skipped — even if the utterance happens to end with +# a word from cancel.voc. The utterance is *about* the cancel word +# (define it, spell it, pronounce it, ask after it) rather than a +# command to cancel. +(toque|reproduza|diga|soletre|pronuncie|leia|repita|escreva|digite) +(defina|explique|traduza) +(me diga|diga-me) (o|a|sobre|o que|qual) +me ensine +como (se diz|se escreve|se pronuncia|se soletra|você faz|funciona) +(o que|qual) (é|são|faz|fazem|significa|quer dizer) +qual é (o significado|o oposto|o contrário|a tradução|a pronúncia) +por que (você|você não|não|é|funciona) diff --git a/ovos_utterance_plugin_cancel/locale/pt-BR/cancel.intent b/ovos_utterance_plugin_cancel/locale/pt-BR/cancel.intent index 2f94fa6..af5dff0 100644 --- a/ovos_utterance_plugin_cancel/locale/pt-BR/cancel.intent +++ b/ovos_utterance_plugin_cancel/locale/pt-BR/cancel.intent @@ -1,12 +1,8 @@ -cancele -cancele a ordem -cancele esse comando -cancele isso -cancele o comando -deixa pra lá -esqueça -esqueça a ordem -esqueça essa ordem -esqueça esse comando -esqueça isso -esqueça o comando \ No newline at end of file +# Documents the matching pattern: an utterance ending with a cancel +# phrase from cancel.voc, optionally with no leading utterance at all +# (a standalone cancel). +# +# The plugin currently does suffix-matching with the cancel.voc +# vocabulary; this .intent is informational, not consumed at runtime, +# and exists so the resource role is unambiguous in the locale folder. +[{utterance}] diff --git a/ovos_utterance_plugin_cancel/locale/pt-BR/cancel.voc b/ovos_utterance_plugin_cancel/locale/pt-BR/cancel.voc new file mode 100644 index 0000000..2f94fa6 --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/pt-BR/cancel.voc @@ -0,0 +1,12 @@ +cancele +cancele a ordem +cancele esse comando +cancele isso +cancele o comando +deixa pra lá +esqueça +esqueça a ordem +esqueça essa ordem +esqueça esse comando +esqueça isso +esqueça o comando \ No newline at end of file diff --git a/ovos_utterance_plugin_cancel/locale/pt-PT/cancel.blacklist b/ovos_utterance_plugin_cancel/locale/pt-PT/cancel.blacklist new file mode 100644 index 0000000..ffcdccd --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/pt-PT/cancel.blacklist @@ -0,0 +1,17 @@ +# OVOS-INTENT-2 §4.3 blacklist for the cancel transformer. +# +# Utterance prefixes that veto the cancel suffix match. +# +# When an utterance starts with one of these phrases, the cancel +# suffix match is skipped — even if the utterance happens to end with +# a word from cancel.voc. The utterance is *about* the cancel word +# (define it, spell it, pronounce it, ask after it) rather than a +# command to cancel. +(toca|reproduz|diz|soletra|pronuncia|lê|repete|escreve) +(define|explica|traduz) +diz-me (o|a|sobre|o que|qual) +ensina-me +como (se diz|se escreve|se pronuncia|se soletra|fazes|funciona) +(o que|qual) (é|são|faz|fazem|significa|quer dizer) +qual é (o significado|o oposto|o contrário|a tradução|a pronúncia) +porque (não|fazes|faz|é|funciona) diff --git a/ovos_utterance_plugin_cancel/locale/pt-PT/cancel.intent b/ovos_utterance_plugin_cancel/locale/pt-PT/cancel.intent index ac69458..af5dff0 100644 --- a/ovos_utterance_plugin_cancel/locale/pt-PT/cancel.intent +++ b/ovos_utterance_plugin_cancel/locale/pt-PT/cancel.intent @@ -1,9 +1,8 @@ -(cancela|cancelar|esquecer|anular) (ordem|comado|isso) -(cancela|cancelar|esquece|esquecer|anular) (a|o) (ultima|ultimo) (ordem|comado) -(cancela|cancelar|esquece|esquecer|anular) (ultima|ultimo|esse|este|esta) (ordem|comado) -(esquece|cancela|cancelar) -(ignora|ignorar) (o|a) (ultima|ultimo) (ordem|comado) -(ignora|ignorar) (ultima|ultimo|esse|este|esta) (ordem|comado) -não (importa|executar) -não (importa|executar|executes) (a|o) (ultima|ultimo) (ordem|comado) -não (importa|executar|executes) (ultima|ultimo|esse|este|esta) (ordem|comado) \ No newline at end of file +# Documents the matching pattern: an utterance ending with a cancel +# phrase from cancel.voc, optionally with no leading utterance at all +# (a standalone cancel). +# +# The plugin currently does suffix-matching with the cancel.voc +# vocabulary; this .intent is informational, not consumed at runtime, +# and exists so the resource role is unambiguous in the locale folder. +[{utterance}] diff --git a/ovos_utterance_plugin_cancel/locale/pt-PT/cancel.voc b/ovos_utterance_plugin_cancel/locale/pt-PT/cancel.voc new file mode 100644 index 0000000..34a180d --- /dev/null +++ b/ovos_utterance_plugin_cancel/locale/pt-PT/cancel.voc @@ -0,0 +1,9 @@ +(cancela|cancelar|esquecer|anular) (ordem|comando|isso) +(cancela|cancelar|esquece|esquecer|anular) (a|o) (ultima|ultimo) (ordem|comando) +(cancela|cancelar|esquece|esquecer|anular) (ultima|ultimo|esse|este|esta) (ordem|comando) +(esquece|cancela|cancelar) +(ignora|ignorar) (o|a) (ultima|ultimo) (ordem|comando) +(ignora|ignorar) (ultima|ultimo|esse|este|esta) (ordem|comando) +não (importa|executar) +não (importa|executar|executes) (a|o) (ultima|ultimo) (ordem|comando) +não (importa|executar|executes) (ultima|ultimo|esse|este|esta) (ordem|comando) \ No newline at end of file diff --git a/ovos_utterance_plugin_cancel/version.py b/ovos_utterance_plugin_cancel/version.py index bfbefd0..af8c8b5 100644 --- a/ovos_utterance_plugin_cancel/version.py +++ b/ovos_utterance_plugin_cancel/version.py @@ -1,8 +1,8 @@ # START_VERSION_BLOCK VERSION_MAJOR = 0 -VERSION_MINOR = 2 -VERSION_BUILD = 8 -VERSION_ALPHA = 1 +VERSION_MINOR = 3 +VERSION_BUILD = 0 +VERSION_ALPHA = 2 # END_VERSION_BLOCK __version__ = f"{VERSION_MAJOR}.{VERSION_MINOR}.{VERSION_BUILD}" + (f"a{VERSION_ALPHA}" if VERSION_ALPHA else "") diff --git a/pyproject.toml b/pyproject.toml index fc524ed..45acd72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,9 +23,9 @@ classifiers = [ ] requires-python = ">=3.10" dependencies = [ - "ovos-utils>=0.6.0,<1.0.0", + "ovos-utils>=0.11.1a1,<1.0.0", "ovos-plugin-manager>=0.0.1", - "langcodes>=3.0.0", + "ovos-spec-tools[langcodes]>=0.5.1a1,<1.0.0", ] [project.optional-dependencies] @@ -33,11 +33,15 @@ dev = [ "pytest", "pytest-cov", ] +test = [ + "ovoscope", + "ovos-skill-hello-world", +] [project.urls] Homepage = "https://github.com/OpenVoiceOS/ovos-utterance-plugin-cancel" -[project.entry-points."ovos.utterance.transformer"] +[project.entry-points."opm.transformer.text"] ovos-utterance-cancel-plugin = "ovos_utterance_plugin_cancel:NevermindPlugin" [tool.setuptools] diff --git a/test/end2end/__init__.py b/test/end2end/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/end2end/test_cancel_plugin.py b/test/end2end/test_cancel_plugin.py new file mode 100644 index 0000000..18e2c25 --- /dev/null +++ b/test/end2end/test_cancel_plugin.py @@ -0,0 +1,221 @@ +"""End-to-end ovoscope tests for ``ovos-utterance-plugin-cancel``. + +Three scenarios: + +* the cancel transformer fires on a suffix-matched utterance + (positive path); +* the cancel transformer is **not** invoked when the plugin is + disabled in config — the utterance falls through to intent failure + (negative gate: proves the positive test is observing the plugin + itself, not some unrelated component); +* a plain utterance with no cancel suffix passes through to the skill + unchanged (smoke). +""" +import json +import os +import tempfile +from unittest import TestCase + +from ovos_bus_client.message import Message +from ovos_bus_client.session import Session +from ovos_config.config import Configuration +from ovos_config.models import LocalConf +from ovos_utils.log import LOG +from ovoscope import End2EndTest, get_minicroft + + +# The entry-point name under which this plugin is registered. The +# OVOS UtteranceTransformersService loads a plugin only when this name +# appears (with ``active: True``) in ``Configuration().utterance_transformers`` +# — ``ovos_core/transformers.py:29``. +PLUGIN_NAME = "ovos-utterance-cancel-plugin" + + +class _CancelPluginTestBase(TestCase): + """Common boot / teardown. + + MiniCroft's ``isolate_config=True`` clears the user's XDG configs + **and** calls ``Configuration.reload()``, which would wipe any + in-memory override done in ``setUp``. Each subclass writes its + desired config to a temp file and prepends it to + ``Configuration.xdg_configs`` before booting MiniCroft with + ``isolate_config=False`` — the highest-precedence source wins on + reload.""" + + skill_id = "ovos-skill-hello-world.openvoiceos" + plugin_active: bool = True + + def setUp(self): + LOG.set_level("DEBUG") + self._tmp_conf = self._write_config( + {"utterance_transformers": { + PLUGIN_NAME: {"active": self.plugin_active}}}) + self._orig_xdg = Configuration.xdg_configs[:] + Configuration.xdg_configs = ( + [LocalConf(self._tmp_conf)] + Configuration.xdg_configs) + Configuration.reload() + + self.minicroft = get_minicroft( + [self.skill_id], isolate_config=False) + + def tearDown(self): + if self.minicroft: + self.minicroft.stop() + Configuration.xdg_configs = self._orig_xdg + Configuration.reload() + os.unlink(self._tmp_conf) + LOG.set_level("CRITICAL") + + @staticmethod + def _write_config(data: dict) -> str: + fd, path = tempfile.mkstemp(prefix="cancel-plugin-test-", + suffix=".json") + with os.fdopen(fd, "w") as fh: + json.dump(data, fh) + return path + + @staticmethod + def _utterance(text: str, session: Session) -> Message: + return Message( + "recognizer_loop:utterance", + {"utterances": [text], "lang": session.lang}, + {"session": session.serialize(), + "source": "A", "destination": "B"}) + + +class TestCancelPluginEnabled(_CancelPluginTestBase): + """The OVOS default config ships this plugin enabled; assert that + explicitly here as well to keep the test future-proof against + upstream config changes.""" + + plugin_active = True + + # --- positive: cancel fires -------------------------------------------- + + def test_cancel_mid_sentence(self): + """An utterance ending with a cancel phrase is dropped before + intent matching — the cancel sequence is emitted instead.""" + session = Session("123") + session.lang = "en-US" + message = self._utterance( + "can you tell me the...ummm...oh, nevermind that", session) + + End2EndTest( + minicroft=self.minicroft, + skill_ids=[self.skill_id], + source_message=message, + final_session=session, + expected_messages=[ + message, + Message("mycroft.audio.play_sound", {"uri": "snd/cancel.mp3"}), + Message("ovos.utterance.cancelled", {}), + Message("ovos.utterance.handled", {}), + ], + ).execute(timeout=10) + + def test_cancel_suffix_on_arbitrary_utterance(self): + """The suffix fires regardless of whether the leading utterance + would have matched a skill.""" + session = Session("123") + session.lang = "en-US" + message = self._utterance("hello world cancel command", session) + + End2EndTest( + minicroft=self.minicroft, + skill_ids=[self.skill_id], + source_message=message, + expected_messages=[ + message, + Message("mycroft.audio.play_sound", {"uri": "snd/cancel.mp3"}), + Message("ovos.utterance.cancelled", {}), + Message("ovos.utterance.handled", {}), + ], + ).execute(timeout=10) + + # --- skip-prefix veto (issue #7 partial fix) --------------------------- + + def test_blacklist_prefix_vetoes_cancel(self): + """An utterance starting with a phrase from ``cancel.blacklist`` + bypasses the cancel suffix match even when it ends in a cancel + word (OVOS-INTENT-2 §4.3 blacklist role). + + Partial fix for issue #7: utterances *about* the cancel word + (define / spell / pronounce / what-is / how-do-you / ...) are + not commands to cancel.""" + session = Session("123") + session.lang = "en-US" + # ``nevermind that`` IS in en-US/cancel.voc — without the + # blacklist prefix, ``hello world nevermind that`` fires the + # cancel sequence (see test_cancel_suffix_on_arbitrary_utterance). + # ``spell`` as a blacklist prefix vetoes it. + message = self._utterance("spell nevermind that", session) + + End2EndTest( + minicroft=self.minicroft, + skill_ids=[self.skill_id], + source_message=message, + expected_messages=[ + message, + # No cancel sequence — intent failure (hello-world + # doesn't register a "spell" intent in this rig). + Message("mycroft.audio.play_sound", {"uri": "snd/error.mp3"}), + Message("complete_intent_failure", {}), + Message("ovos.utterance.handled", {}), + ], + ).execute(timeout=10) + + # --- smoke: passthrough -------------------------------------------------- + + def test_passthrough_without_cancel_word(self): + """An utterance with no cancel suffix is not intercepted. + + ``hello world`` doesn't match any registered intent in this + minimal test rig, so the expected outcome is intent-failure + (``snd/error.mp3``) — the point being that the cancel + sequence (``ovos.utterance.cancelled``) is **absent**, which + the strict ovoscope message-type + count checks enforce.""" + session = Session("123") + session.lang = "en-US" + message = self._utterance("hello world", session) + + End2EndTest( + minicroft=self.minicroft, + skill_ids=[self.skill_id], + source_message=message, + expected_messages=[ + message, + Message("mycroft.audio.play_sound", {"uri": "snd/error.mp3"}), + Message("complete_intent_failure", {}), + Message("ovos.utterance.handled", {}), + ], + ).execute(timeout=10) + + +class TestCancelPluginDisabled(_CancelPluginTestBase): + """Negative gate. With the plugin explicitly disabled, the same + cancel-suffix utterance is **not** intercepted: it falls through + to intent matching and lands on the intent-failure error sound. + This proves the positive test above is observing the plugin + itself, not some unrelated component.""" + + plugin_active = False + + def test_cancel_suffix_falls_through_when_disabled(self): + session = Session("123") + session.lang = "en-US" + message = self._utterance( + "can you tell me the...ummm...oh, nevermind that", session) + + End2EndTest( + minicroft=self.minicroft, + skill_ids=[self.skill_id], + source_message=message, + expected_messages=[ + message, + # No cancel sequence — intent failure plays the error + # sound and emits handled. + Message("mycroft.audio.play_sound", {"uri": "snd/error.mp3"}), + Message("complete_intent_failure", {}), + Message("ovos.utterance.handled", {}), + ], + ).execute(timeout=10)