Skip to content

Commit a04523f

Browse files
JarbasAlclaude
andauthored
feat: yesno/selection agent plugins (#390)
* feat: yesno/selection agent plugins * fix: default plugins * fix: dependencies * chore: drop requirements.txt, deps now in pyproject.toml Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: correct test patch paths and expected fallback types - patch load_yesno_plugin/load_option_matcher_plugin at ovos_workshop.skills.ovos (not ovos_plugin_manager.agents) since they are imported into the module - _get_yesno_engine falls back to HeuristicYesNoEngine, not None - _get_selection_engine falls back to FuzzyOptionMatcherPlugin, not None - replace YesNoSolver references with patch.object on _get_yesno_engine Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: drop duplicate unit_tests workflow build_tests already runs the same test path; unit_tests only differed by adding audio system deps not needed for these tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: add ask_yesno and ask_selection tests - test_base.py: fill in TODO stubs with mock-based tests on real OVOSSkill (yes/no/unmatched/timeout for ask_yesno; empty/single/invalid/fuzzy/timeout for ask_selection) - test_ask_e2e.py: ovoscope end-to-end tests — inline AskYesNoSkill and AskSelectionSkill loaded via get_minicroft; user response injected via {skill_id}.converse.get_response after hearing speak; skipped when ovoscope/ovos-core are not installed Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: require ovoscope for e2e tests, add [test] extras - remove pytest.importorskip — e2e tests are mandatory, not optional - add [project.optional-dependencies] test group to pyproject.toml (ovos-core, ovoscope, pytest, pytest-cov, ovos-translate-server-plugin) - build_tests.yml: pass install_extras=test so CI installs test deps - keep requirements/test.txt in sync for local pip install -r workflows Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: remove unsupported message kwarg from ask_yesno/ask_selection calls Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * . * fix: forward min_conf to OptionMatcher engine via config dict The ask_selection min_conf parameter was documented but silently ignored. FuzzyOptionMatcherPlugin reads threshold from self.config["min_conf"], so set it on the cached engine instance before each match_option call. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: sync documentation after yesno/selection plugin integration AI-Generated Change: - Model: claude-sonnet-4-6 - Intent: keep docs accurate after feat: yesno/selection agent plugins - Impact: updated README.md, docs/index.md, docs/ovos-skill.md, docs/skill-interaction.md - Changes: - README.md: rewritten with install instructions, config table, and docs links - docs/skill-interaction.md: fixed fallback class name (HeuristicYesNoEngine not YesNoSolver), corrected ask_yesno_plugin default (ovos-solver-yes-no-plugin not None), added line-number citations, corrected failure behaviour table - docs/ovos-skill.md: noted plugin configurability for ask_yesno/ask_selection - docs/index.md: added skill-interaction.md to navigation table - Verified via: manual review against ovos_workshop/skills/ovos.py:1932-2040 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: move ovoscope e2e tests out of build_tests path to prevent hangs get_minicroft blocks up to 60s waiting for READY — too heavy for the standard build_tests matrix. Move test/unittests/test_ask_e2e.py to test/ovoscope/ (not scanned by build_tests) and add a dedicated ovoscope_tests workflow that installs [test] extras and runs that path. Revert install_extras from build_tests.yml. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: unblock speak(wait=True) in e2e tests by emitting audio_output_end ask_selection speaks each option with wait=True, blocking 15s per speak waiting for recognizer_loop:audio_output_end that never arrives in tests. Emit that event immediately on every speak message so the skill proceeds without delay, then inject the user response once speaking settles. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Delete MAINTENANCE_REPORT.md * fix: use bus.on("speak") not on("message") to avoid recursion in e2e tests Listening on "message" caught the audio_output_end we emitted, causing infinite recursion. Listen on "speak" specifically instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: merge ovoscope e2e tests back into build_tests No need for a separate workflow. Move test_ask_e2e.py back to test/unittests/ and install [test] extras in build_tests so ovoscope and ovos-core are available. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: delay audio_output_end emission past sess.is_speaking=True FakeBus is synchronous — on_speak fires inline during bus.emit(speak), before the caller sets sess.is_speaking=True. Emitting audio_output_end in the same stack means wait_while_speaking misses it and hangs 15s. Use a daemon thread with a 20ms delay so is_speaking is set first. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: remove fragile ovoscope e2e tests for ask_yesno/ask_selection The tests fought TTS wait machinery (speak(wait=True) blocks on recognizer_loop:audio_output_end) without adding coverage beyond what test_skill_interaction.py already provides via mocked get_response. Unit tests are sufficient here. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: drop ovoscope from test deps, revert install_extras No ovoscope e2e tests remain, no need for the heavy dependency. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: restore ovoscope e2e tests with proper TTS wait fix Patch SessionManager.wait_while_speaking to a no-op so speak(wait=True) doesn't block on TTS completion. Inject user response via skill.converse.get_response.enable event instead of timing hacks. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: restore SessionManager.bus after e2e tests to prevent state leak get_minicroft sets SessionManager.bus globally. Without restoring it, subsequent tests get real wait_while_speaking behaviour instead of the early-return they expect (bus=None). Also scope wait_while_speaking patch to setUp/tearDown so it can't leak if tearDownClass throws. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 1c73d1e commit a04523f

14 files changed

Lines changed: 979 additions & 87 deletions

File tree

.github/workflows/build_tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ jobs:
1212
secrets: inherit
1313
with:
1414
test_path: 'test/unittests'
15+
install_extras: 'test'

.github/workflows/test.yml

Lines changed: 0 additions & 15 deletions
This file was deleted.

MAINTENANCE_REPORT.md

Lines changed: 0 additions & 35 deletions
This file was deleted.

README.md

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,62 @@
11
# OVOS Workshop
2-
OVOS Workshop contains skill base classes and supporting tools to build skills
3-
and applications for OpenVoiceOS systems.
2+
3+
Base classes, decorators, and helpers for building skills and applications for OpenVoiceOS.
4+
5+
## Install
6+
7+
```bash
8+
pip install ovos-workshop
9+
```
10+
11+
Runtime dependencies include `ovos-yes-no-plugin` and `ovos-option-matcher-fuzzy-plugin`, which back the `ask_yesno` and `ask_selection` skill methods.
12+
13+
## Quick Start
14+
15+
```python
16+
from ovos_workshop.skills.ovos import OVOSSkill
17+
from ovos_workshop.decorators import intent_handler
18+
19+
20+
class HelloWorldSkill(OVOSSkill):
21+
22+
@intent_handler("hello.intent")
23+
def handle_hello(self, message):
24+
self.speak_dialog("hello.response")
25+
26+
27+
def create_skill():
28+
return HelloWorldSkill()
29+
```
30+
31+
Register in `pyproject.toml`:
32+
33+
```toml
34+
[project.entry-points."opm.skills"]
35+
hello-world-skill = "hello_world_skill:HelloWorldSkill"
36+
```
37+
38+
## Configuration
39+
40+
Key settings a skill can accept in its `settings.json`:
41+
42+
| Key | Default | Description |
43+
|-----|---------|-------------|
44+
| `ask_yesno_plugin` | `ovos-solver-yes-no-plugin` | YesNoEngine plugin used by `ask_yesno()` |
45+
| `ask_selection_plugin` | `ovos-option-matcher-fuzzy-plugin` | OptionMatcherEngine plugin used by `ask_selection()` |
46+
47+
Both keys can also be set system-wide under the `skills` block in `mycroft.conf`.
48+
49+
## Documentation
50+
51+
Full reference is in [`docs/`](docs/index.md):
52+
53+
- [Skill classes](docs/skill-classes.md)
54+
- [OVOSSkill base class](docs/ovos-skill.md)
55+
- [ask_yesno / ask_selection plugin system](docs/skill-interaction.md)
56+
- [Decorators](docs/decorators.md)
57+
- [Settings](docs/settings.md)
58+
- [Resource files](docs/resource-files.md)
59+
60+
## License
61+
62+
Apache 2.0

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ OVOSSkill ovos_workshop/skills/ovos.py
6868
| [app.md](app.md) | `OVOSAbstractApplication` | Skill-like app that runs without the intent service |
6969
| [game-skill.md](game-skill.md) | `OVOSGameSkill`, `ConversationalGameSkill` | OCP-integrated game loop with converse and auto-save |
7070
| [auto-translatable.md](auto-translatable.md) | `UniversalSkill`, `UniversalFallback` | Auto-translate input/output for any language |
71+
| [skill-interaction.md](skill-interaction.md) | `OVOSSkill.ask_yesno`, `OVOSSkill.ask_selection` | Pluggable yes/no and option-selection engines |
7172
| [skill-api.md](skill-api.md) | `SkillApi`, `skill_api_method` | Inter-skill RPC over the MessageBus |
7273
| [filesystem.md](filesystem.md) | `FileSystemAccess` | Sandboxed, XDG-compliant file storage for skills |
7374
| [resource-files.md](resource-files.md) | `SkillResources` | Locale, dialog, vocab, regex, and other resource files |

docs/ovos-skill.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ choice = self.ask_selection(["A", "B", "C"], "Pick one")
120120

121121
`get_response` suspends the converse channel for this skill until the user responds or a timeout is hit. Raise `AbortQuestion` to cancel gracefully.
122122

123+
`ask_yesno` and `ask_selection` are backed by pluggable engine plugins. The active plugin can be set per-skill via `settings.json` (`ask_yesno_plugin`, `ask_selection_plugin`) or system-wide in `mycroft.conf` under the `skills` block. Defaults are `ovos-solver-yes-no-plugin` and `ovos-option-matcher-fuzzy-plugin`, both installed as runtime dependencies. See [skill-interaction.md](skill-interaction.md) for full configuration reference.
124+
123125
## Intent Registration
124126

125127
```python

0 commit comments

Comments
 (0)