Release 3.5.2a1 - #971
Open
github-actions[bot] wants to merge 291 commits into
Open
Conversation
padacioso 2.0 (OVOS-INTENT-1 grammar) validated end-to-end in the OVOS spec-compliance harness against the workshop-9 stack.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
spec-tools crossed to 1.x; bus-client 2.6.0a1 and the rest of the stack now require ovos-spec-tools>=1.1.0a1. Drop the <1.0.0 cap (keep the floor) so the stack resolves. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…792) ovos-skill-fallback-unknown's 'unknown' dialog speaks with meta {dialog, data, skill}; the e2e expected only {skill}, so test_fallback_match fails on dev independently of any PR. Match the real skill output. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
ovos-core ships three opm.pipeline plugins (converse/fallback/stop) but had no opm_check workflow. Add the shared gh-automations opm-check caller @dev so entry-point registration is validated on PRs. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…l events (#788) * feat: orchestrator emits the PIPELINE-1 §8 handler-lifecycle trio Make ovos-core (the orchestrator) the authoritative emitter of the OVOS-PIPELINE-1 §8 handler-lifecycle trio (ovos.intent.handler.{start,complete,error}), wrapping every dispatch: start immediately before the <skill_id>:<intent_name> dispatch (§7), then exactly one of complete (on the framework done-signal) / error (on the framework error signal or the §8.3 timeout). Each trio Message is forward-derived from the dispatch so context (incl. session) is preserved unchanged (§8, MSG-1 §5.1). New IntentDispatcher (ovos_core/intent_services/dispatcher.py) owns the §7 dispatch + §8 trio. Completion is observed across the distributed bus via the skill framework's long-standing legacy done-signals (mycroft.skill.handler.complete/.error) — framework infrastructure, not the user handler (which emits nothing per §8/§11). A per-dispatch §8.3 timeout guarantees exactly one terminal even if the handler never reports; on that path the orchestrator also owns ovos.utterance.handled. Reserved-name dispatches get the trio identically (§7.0/§7.3); the resolved-guard keeps the terminal count at one regardless of the bus namespace bridge. This is additive: the §9.5 end-marker on the ordinary matched path and the §9.2 ovos.intent.matched notification are left to ovos-workshop / follow-up changes and are out of scope here. Dep floors: ovos-bus-client>=2.5.1a1, ovos-spec-tools>=0.17.3a1 (SpecMessage.INTENT_HANDLER_* members). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat: orchestrator owns the PIPELINE-1 §9 utterance-terminal events Complete the orchestrator's ownership of the OVOS-PIPELINE-1 §6.1 per-utterance terminal sequence, on top of the §8 handler-lifecycle trio: - §9.2 ovos.intent.matched — emitted by _dispatch_match on every accepted match, before the dispatch goes out (notification, not a dispatch). Carries skill_id, intent_name (the full <skill_id>:<intent_name> match_type), lang, utterance, slots, pipeline_id. - §9.3 ovos.intent.unmatched — the no-match / all-filtered terminal, replacing the legacy complete_intent_failure (the two are bridged by ovos-spec-tools' MIGRATION_MAP, so emitting the spec topic re-delivers the legacy one to consumers still on it). - §6.4 cancellation now emits the spec ovos.utterance.cancelled. Each utterance terminates with exactly one ovos.utterance.handled (§9.5): core owns it on the no-match, cancel and §8.3-timeout paths; on the ordinary matched path the skill framework still emits it (moving that fully into core is gated on the ovos-workshop reduction). Rename _emit_match_message -> _dispatch_match (it orchestrates the §6.1 post-match steps then dispatches) and correct the IntentDispatcher docstring to scope it to §7 dispatch + §8 trio (the §9.2 notification lives in the service). Verified on a real minicroft: matched path emits matched/start/ complete/handled exactly once each; no-match path emits ovos.intent.unmatched + ovos.utterance.handled (no complete_intent_ failure). test_no_skills / test_lang_detect conformance suites green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test: account for §9.2 matched + §8.1 start in activate/fallback e2e The converse-deactivate (test_activate) and fallback (test_fallback) ovoscope scenarios each gain two captured messages now that the orchestrator emits ovos.intent.matched (§9.2) and ovos.intent.handler. start (§8.1) natively before every dispatch — previously the spec trio existed only as uncounted bridge-mirrors of workshop's legacy emit. Verified on a real minicroft: the two extra messages per scenario are exactly ovos.intent.matched + ovos.intent.handler.start (the reserved-name converse:skill / fallback .request dispatches carry no mycroft.skill.handler.* done-signal, so their §8 terminal resolves via the §8.3 timeout after the end-marker, not captured). No spec-topic double-emit: ovos.intent.matched, ovos.intent.handler.start and ovos.utterance.handled each appear exactly once per utterance. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat: core emits ovos.utterance.handled on the matched path too (§9.5) The orchestrator owns the universal end-marker on EVERY terminal path. The dispatcher already emitted ovos.utterance.handled on the timeout path; emit it after the complete/error terminals as well, so a matched dispatch also ends with exactly one core-emitted handled (the _pop/resolved guard fires one terminal per dispatch -> one handled). Unmatched/cancel keep their service.py handled. Workshop may still emit its own matched-path handled during the migration window; that core-vs-workshop duplicate is expected and removed later workshop-side. * chore: bump ovos-workshop floor to >=9.0.1a5 (HandlerLifecycle delegation merged) * chore: bump ovos-workshop floor to >=9.0.2a1 (matched-path handled guard) ovos-workshop 9.0.2a1 (#442) guards its matched-path ovos.utterance.handled emission behind a version check on the installed ovos-core, so once core ships the §9.5 matched-path emission the framework stops double-emitting. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore: stage version 2.3.0a1 so CI exercises the workshop §9.5 guard This feat bumps core to the 2.3.x line. ovos-workshop 9.0.2a1 suppresses its matched-path ovos.utterance.handled only when the installed ovos-core is >=2.3.0a1; staging the version here lets PR CI install a core that trips that guard, so the e2e exercises core as the single end-marker emitter rather than relying on the published 2.2.x. Release automation re-derives the final version on merge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(deps): floor-pin ovos-m2v-pipeline>=0.3.1a1 (workshop 9.x compatible) The published m2v 0.0.10a1 caps ovos-workshop<9.0.0; with core's workshop floor at 9.0.2a1 pip backtracked m2v down to 0.0.10a1 and hit that cap -> ResolutionImpossible. m2v 0.3.1a1 drops the workshop dependency entirely, so floor-pinning it (the prerelease-floor-pin pattern) forbids the backtrack and the closure resolves. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(deps): floor-pin downstream stack to spec-tools-1.x-ready prereleases core's bus-client>=2.5.1a1 pulls ovos-bus-client 2.5.1a3/2.6.0a1, which require ovos-spec-tools>=1.1.0a1. pip backtracked the mycroft/plugins/skills-essential extras down to stale releases that cap ovos-spec-tools<1.0.0 (e.g. ovos-audio 2.0.1a1) -> ResolutionImpossible. Floor-pin each to its latest prerelease (all spec-tools-1.x-ready) so the resolver can't backtrack into the capped ones, and make core's own ovos-spec-tools floor explicit at >=1.1.0a1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(deps): floor-pin ovos-adapt-parser>=1.4.2a1 (spec-tools-1.x ready) Every ovos-adapt-parser release up to 1.4.1a1 caps ovos-spec-tools<1.0.0; the uncap landed in 1.4.2a1. Floor-pin it so pip can't backtrack into the capped versions while core requires ovos-spec-tools>=1.1.0a1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test: align e2e expectations with §9.5 end-marker payload - fallback speak meta carries the skill's dialog/data keys, not just skill. - ovos.utterance.handled (§9.5) is the orchestrator end-marker with EMPTY data; the stop count-to-infinity / ping-pong expectations wrongly carried the handler name on it (KeyError 'name'). The handler name stays on the framework mycroft.skill.handler.complete signal, where it belongs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: orchestrator owns ovos.utterance.handled, dispatcher only signals done PIPELINE-1 §9.5: ovos.utterance.handled is the orchestrator's universal end-marker, not the dispatcher's. The IntentDispatcher owned only the §8 handler-lifecycle trio but was also emitting the §9.5 end-marker on its terminal paths — wrong layer. - IntentDispatcher no longer emits ovos.utterance.handled. Each in-flight dispatch carries a 'done' Event set on its §8 terminal (complete/error/timeout); dispatch() returns the entry. - IntentService._dispatch_match blocks on entry.done (the §8.3 timeout guarantees it fires) then emits the single ovos.utterance.handled, uniformly with the no-match (send_complete_intent_failure) and cancel (send_cancel_event) paths it already owns. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: dispatch entry is a context manager that blocks until the §8 terminal Reads cleaner at the call site — the event-waiting is hidden behind the context manager: with self.intent_dispatcher.dispatch(reply, skill_id, intent_name): pass self.bus.emit(reply.forward(SpecMessage.UTTERANCE_HANDLED, {})) _InFlightDispatch gains __enter__/__exit__ (exit blocks on its done event). Callers that don't want to block can still wait on entry.done directly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: keep explicit entry.done.wait() call site (drop context manager) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: guarantee dispatch waiters are always released (review) - terminal handlers wrap the §8 emission in try/finally so entry.done is set even if the bus emission raises (the §8.3 timer is already cancelled by _pop, so nothing else would release a blocked orchestrator). - IntentDispatcher.shutdown() sets each in-flight entry's done before clearing, so a _dispatch_match caller is never left blocked on entry.done.wait() forever. - test_timeout_emits_error_and_releases waits on entry.done instead of a fixed sleep (deterministic); test_orchestrator_emits_handled_after_terminal now asserts ovos.intent.handler.complete precedes ovos.utterance.handled. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(e2e): pin §8.3 handler-timeout to 10s in the end2end suite The orchestrator blocks on each handler's §8 terminal before emitting §9.5 ovos.utterance.handled; the production backstop is 5min. e2e handlers report in <1s (or are explicitly stopped), so pin the backstop to 10s — a dropped done-signal then fails the suite in seconds instead of stalling for minutes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: orchestrator emits utterance.handled by reacting to the §8 terminal (non-blocking) Blocking handle_utterance on entry.done.wait() deadlocked the synchronous bus: handle_utterance is itself a bus handler, so blocking it stalled the same path that must deliver the handler's done-signal — the §8.3 timer then fired ovos.intent.handler.error instead of the handler completing (8 e2e tests). Keep the orchestrator as the §9.5 owner, but non-blocking: IntentService now subscribes to the dispatcher's §8 terminal (ovos.intent.handler.complete/error) and emits ovos.utterance.handled in reaction, uniformly with the no-match and cancel paths. The dispatcher reverts to trio-only (no done event, no blocking); the §8.3 timeout still backstops via the error terminal. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(e2e): expect the active skill's §8 complete terminal during stop The count/ping-pong stop tests inject a long-running intent (daemon) then stop it. When that daemon intent completes (on stop), its dispatch now emits the §8 spec terminal ovos.intent.handler.complete before the §9.5 ovos.utterance.handled, so add it to the expected sequence (got 10 messages, expected 9). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: emit utterance.handled via dispatcher on_terminal callback (deterministic) The reactive subscription (IntentService listening to ovos.intent.handler.complete) raced the ovoscope capture: FakeBus dispatches the terminal to both the capture recorder and the subscription, and when the subscription fired first it emitted the EOF ovos.utterance.handled before the terminal was recorded — so the capture stopped early and dropped the §8 complete (flaky 9-vs-10 message counts). Make it deterministic: the dispatcher invokes an on_terminal callback right AFTER the §8 terminal is on the bus; the orchestrator's _emit_utterance_handled emits the §9.5 end-marker then. Same call stack, so the terminal is always observed before the end-marker. Orchestrator still owns the emission; dispatcher stays non-blocking. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Update ovos-utils version in pyproject.toml * StopService: wrap handlers in HandlerLifecycle instead of ad-hoc emission (#796) * Update Changelog * StopService: wrap handlers in HandlerLifecycle Replace rudimentary manual emission of both UTTERANCE_HANDLED (which the orchestrator now owns via IntentDispatcher._notify_terminal) and mycroft.skill.handler.complete with the canonical HandlerLifecycle context manager from ovos-bus-client. HandlerLifecycle consistently emits the full handler-lifecycle trio (start/complete/error) so that the dispatcher can properly track in-flight entries and fire §9.5 UTTERANCE_HANDLED at the right moment. Co-Authored-By: Claude * ConverseService: wrap handle_converse in HandlerLifecycle Same pattern as StopService — handle_converse is called via bus event dispatch after the converse pipeline stage matches, but had no lifecycle signalling. Without it the dispatcher's in-flight entry for converse dispatches would only ever resolve on timeout (10s). Co-Authored-By: Claude * Update stop_service unit tests for HandlerLifecycle test_handle_global_stop_emits_mycroft_stop: check for mycroft.skill.handler.start/complete instead of ovos.utterance.handled. test_handle_skill_stop_forwards_to_skill: HandlerLifecycle emits 3 messages (start, forward, complete), not 1. Co-Authored-By: Claude * Update stop service tests to include additional assertions Added assertions to check for 'mycroft.stop' and 'ovos.utterance.handled' messages in stop service tests. * feat: emit handler done-signal for converse + fallback dispatches (PIPELINE-1 §8) (#789) * feat: emit handler done-signal for converse + fallback dispatches The orchestrator's converse and fallback dispatches (PIPELINE-1 §7.3 reserved-name/polymorphic dispatches) run in skills WITHOUT ovos-workshop's handler_info wrapper, so they never produce the framework done-signal (mycroft.skill.handler.{start,complete,error}). A dispatcher observing that signal (PIPELINE-1 §8, the IntentDispatcher) therefore never sees a completion for these dispatches and falls back to its 5-minute handler timeout. Adopt the shared HandlerLifecycle util (ovos_bus_client.handler, bus-client 2.6.0a1) so core reports the dispatch->outcome span it orchestrates: - converse: handle_converse emits handler.start at the converse.request dispatch, registers a one-shot skill.converse.response listener (filtered by the targeted skill_id) -> handler.complete, with a generous timeout backstop -> handler.error. The done-signal is stamped with the targeted skill_id so a dispatcher correlates it by (session_id, skill_id). - fallback: the fallback dispatch is owned by the orchestrator; core translates each registered skill's own lifecycle markers (ovos.skills.fallback.<skill_id>.start/.response) into handler.start/handler.complete, stamped with that skill_id. Wired on register, removed on deregister/shutdown. stop_service is intentionally NOT touched here (owned by PR #777 / STOP-1). Floor ovos-bus-client>=2.6.0a1 (first release shipping ovos_bus_client.handler) and the coupled ovos-spec-tools>=1.1.0a1 it requires. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: drop ovos-spec-tools upper version cap Keep the >=1.1.0a1 floor (bus-client 2.6.0a1 requires it); remove the <2.0.0 max cap so spec-tools is free to float forward. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Update Changelog --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * test(e2e): ignore TTS mock audio signals in all end2end tests * fix(test): remove ovos.utterance.handled assertion from TestBusHandlers (orchestrator owns it now) * fix: correlate fallback dispatch to its §8 terminal via match_data skill_id The fallback pipeline's match_type (ovos.skills.fallback.<id>.request) carries no ':', so the orchestrator derived the dispatcher correlation key as the whole topic. The framework done-signal (re-emitted mycroft.skill.handler.complete) is stamped with the real skill_id, so IntentDispatcher._pop never matched it and the §8 ovos.intent.handler.complete terminal — and with it the §9.5 ovos.utterance.handled end-marker — only fired on the 5-minute §8.3 handler timeout. Every fallback-handled utterance was affected in production. Derive the correlation key from match_data['skill_id'] when the topic has no ':', so it equals the done-signal's skill_id. Activation is unchanged (match.skill_id stays None, no spurious {skill_id}.activate). test_fallback now asserts the §8 terminal, which can only be captured when correlation succeeds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(e2e): align stop suite with §8 trio + live-session resend StopService wraps its global/skill stop handlers in HandlerLifecycle (#796), so the legacy mycroft.skill.handler.{start,complete} done-signal now brackets mycroft.stop / {skill_id}.stop. Update the stop expectations to assert the trio. The ping-pong tests built the stop message from a stale, test-local Session that never saw the count skill's server-side self-activation (the count message, serialized before activation, folds an empty active_skills back into the singleton — correct SESSION-1 last-write-wins). Resend the live singleton session for the stop turn, as a real client tracking responses would, instead of manually activating — so the running skill is in active_skills and the ping-pong path runs without a manual activate crutch. Also drop the §8 ovos.intent.handler.complete from the expected lists where it is filtered via ignore_messages. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: make converse dispatch terminal resolution atomic + session-scoped handle_converse resolved the §8 lifecycle from two threads (the skill.converse.response handler and the timeout timer) with a non-atomic check-then-set on an Event, so both could pass the guard and emit two framework done-signals (complete + error). Claim the resolution under a Lock so exactly one terminal fires. Also ignore acks carrying a different session_id, so a concurrent converse dispatch to the same skill in another session cannot cross-resolve. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(e2e): converse §8 trio in deactivate + ignore racy stop-cleanup artifacts - test_deactivate_inside_converse: ConverseService now reports the dispatch via the mycroft.skill.handler.* done-signal which the orchestrator translates to the §8 ovos.intent.handler.complete terminal; assert the trio (+3 messages). - The ping-pong stop tests interrupt a running skill; the async stop-pipeline cleanup (abort_question / converse.force_timeout / audio.speech.stop) fires or not depending on exactly where the stop lands, so it raced the message count in CI. Ignore those artifacts (they are not what the tests assert) and drop the flaky force_timeout async_messages assertion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(e2e): stop ping-pong tests assert only through the stop terminal The count-to-infinity ping-pong scenarios interrupt a running skill. After the stop turn's ovos.utterance.handled, the interrupted count daemon exits and races in its own CountSkill complete + a second ovos.utterance.handled — a tail whose exact contents/timing depend on where the stop lands relative to the 1s count loop (it produced a non-reproducible +1 message in CI's parallel workers). Capture only through the deterministic stop turn (eof_msgs=[ovos.utterance.handled]) and drop the racy daemon-completion tail from the expected sequence. The stop routing — ping/pong, activate, stop:skill, the StopService HandlerLifecycle trio, {skill}.stop(.response), and the stop turn's end-marker — is fully asserted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: StopService is a pipeline plugin, not an ovos-workshop skill StopService subclassed OVOSAbstractApplication purely for voc_match/voc_list/locale loading. That base class also registered it as a skill (skill_id=stop.openvoiceos), so it answered the mycroft.stop broadcast with stop.openvoiceos.stop.response — StopService 'stopping itself', a leak that polluted the stop lifecycle. Drop OVOSAbstractApplication and load the stop/global_stop .voc files via ovos-spec-tools LocaleResources (the plugin-agnostic voc matcher, same role common-query/OCP use). self.bus and self.config come from ConfidenceMatcherPipeline. No more skill machinery — no stop.openvoiceos.stop.response. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(e2e): assert stop dispatch lifecycle via ovoscope skill_id filter Stopping a running skill produces two concurrent dispatch lifecycles (the stop dispatch + the interrupted skill's own §8 trio/§9.5 terminal) whose messages interleave non-deterministically under load — the source of the persistent count-mismatch flakiness. Assert the stop dispatch lifecycle in isolation via the new ovoscope End2EndTest skill_id filter (skill_id=stop.openvoiceos) with eof_count=2 so capture spans both utterances' ovos.utterance.handled. The full stop §8 trio + §9 terminals are now modelled deterministically; the interrupted skill's §8 trio is covered (uninterrupted) by test_count. Also: TestStopServiceAsSkill -> TestStopServiceNotASkill (regression guard that StopService no longer emits stop.openvoiceos.stop.response), drop the now-dead stop-response ignores, and floor-pin ovoscope>=1.4.0a1 for the new features. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(unit): update test_stop_service for the pipeline-plugin refactor StopService no longer subclasses OVOSAbstractApplication; vocabulary matching is delegated to self._locale (ovos-spec-tools LocaleResources). Drop the removed OVOSAbstractApplication.__init__ patch from the service factory and redirect the voc_match/voc_list patches to svc._locale. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: orchestrator survives a pipeline matcher raising; fix malformed stop .voc Two issues surfaced by the StopService spec-tools refactor (LocaleResources.voc_match is strict where OVOSAbstractApplication.voc_match was lenient): 1. A pipeline matcher raising (here: a malformed .voc) propagated out of the handle_utterance loop and aborted the WHOLE utterance — no match was tried and no §9.3/§9.5 terminal fired. Wrap the match_func call in try/except: log and treat as no-match so iteration continues. Any pipeline plugin can misbehave; one bad matcher must not break the utterance. 2. ca-es/stop.voc, ca-es/global_stop.voc and de-de/global_stop.voc had single-branch groups '(x)' which ovos-spec-tools rejects (a group needs >=2 branches). The old lenient parser treated them as the mandatory token x; drop the parens to preserve that matching with a valid template. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(e2e): stop ping-pong tests assert only the deterministic stop messages The §8 SPEC trio (ovos.intent.matched / ovos.intent.handler.start / .complete) is not reliably observed in these concurrent-lifecycle stop scenarios under heavy parallel CI load (the orchestrator's spec-namespace messages drop relative to the legacy done-signal — reproduced only at full-suite xdist scale, never in isolation). Scope the assertion to the deterministic, always-present messages: the stop activation, the stop:skill/stop:global dispatch, the StopService HandlerLifecycle done-signal trio (mycroft.skill.handler.start/complete — which the orchestrator translates into the §8 terminal), and the §9.5 ovos.utterance.handled end-marker. The §8 spec trio is filtered via ignore_messages here and asserted deterministically in the single-lifecycle adapt/padatious suites. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: CR2/CR4/CR5/CR7 bugs, meta-commentary cleanup, SpecMessage migration - dispatcher: fix stale timer on shutdown (mark unresolved resolved before clear) - converse_service: fix msg guard ordering before SessionManager.get - converse_service: fix skill_max=0 treated as disabled - fallback_service: fix priority=0 being treated as falsy - service: skip pipeline matchers with empty match_type (CR5) - service: resolve skill_id consistently in INTENT_MATCHED (CR7) - all tests: replace hardcoded spec topics with SpecMessage.X - all tests: add try/finally for MiniCroft cleanup - pyproject.toml: add <2.0.0 upper bound for ovos-spec-tools * fix: replace sleep(2) with deterministic skill-activation poll in stop e2e tests Under parallel CI load (xdist 4 workers) the fixed sleep was too short, causing test_count_infinity_stop_low to get 4 messages instead of 6 (the session hadn't been updated yet, so a global stop fired instead of a skill-specific stop). Replace with _wait_for_active_skill that polls SessionManager.active_skills with a 10s timeout. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat: INTENT-4 §10 orchestrator manifest (IntentManifest) Adds IntentManifest helper that indexes ovos.intent.register.* broadcasts into a (session_id, skill_id, intent_name, lang, method) keyed dict and serves ovos.intent.list / ovos.intent.describe pull queries (§10.1 / §10.2). Handles deregister, enable/disable, and skill teardown (§§8.2–8.5). Session-scoped inheritance follows §11.2: satellite sessions merge the default pool. IntentService now holds self.intent_manifest = IntentManifest(bus). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: IntentManifest.shutdown() unregisters bus listeners Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: add intent_manifest to test _make_service to prevent shutdown crash The _make_service helper in test_intent_service_extended.py bypasses IntentService.__init__, so the intent_manifest attribute was never set. When shutdown() was called, it crashed with AttributeError because self.intent_manifest didn't exist. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…me suppression (#778) * feat: OVOS-PIPELINE-1 §6.2 required_slots backstop + §7.3 reserved-name suppression Reworked onto dev's IntentDispatcher (#788) architecture. The matched/unmatched/ handled emission this PR originally carried now lives in #788; the two deltas that remain unique are re-applied here: - §7.3: suppress the §7.1 `active_handlers` push for reserved-name dispatches (converse/response/stop/fallback/common_query) — a reserved name is a continuation/termination of an already-active skill, not a fresh activation. `activate_skill` is already a §7.1 shim over `add_active_handler`, so only the suppression gate was missing. - §6.2: orchestrator backstop that drops a match missing any `required_slots` (surfaced by the plugin in `match_data['__required_slots__']`) and continues iteration — a second line of defense behind engine-side enforcement. No-op and fully backward compatible when a plugin does not surface the constraint. Adds unit coverage for both the backstop and reserved-name suppression. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: source §6.2 required_slots from the INTENT-4 §10 manifest Replace the non-canonical `match_data['__required_slots__']` key with a lookup against the orchestrator's IntentManifest: `get_required_slots` returns the `required_slots` an intent declared in its `ovos.intent.register.*` payload, merged across the session's effective pool. Intents absent from the manifest yield no required slots, so the backstop stays a no-op there. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the real ggwave audio transformer plugin and SkillsStore onto a single bus and feed a genuine ggwave waveform carrying a GHS: payload; assert the decoded payload drives ovos.skills.install through to a pip install request (pip and GitHub validation mocked). Also assert the allow_pip=false path refuses the install. Adds ovos-dinkum-listener and the ggwave plugin to the test extra and bumps the ovoscope floor to >=0.20.0 for feed_audio_stream. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ovos_core.transformers now subclasses the canonical runner services in ovos_plugin_manager.transformer_services instead of maintaining its own copies. Legacy descending priority order (priority 1 runs last and has the final say) is preserved via sort_ascending=False. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BREAKING CHANGE: transformer chains now run in ascending priority order per OVOS-TRANSFORM §4 — a plugin with priority 1 runs first. The previous descending traversal (priority 1 ran last and had the final say) was non-spec. Deployments relying on relative transformer ordering should review their configured priorities or set an explicit 'order' list in each transformer config section. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sformers Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ManualCPS namespace bridging was removed from MiniCroft, so converse is no longer unconditionally injected into active_skills. Update the test fixture to match the default (empty list).
…ra (#950) ovos-core ships the default plugin set through the `plugins` extra in pyproject.toml. Add ovos-typed-slots-transformer as a floor pin alongside the other utterance transformers so `pip install ovos-core[plugins]` installs it. Enabling the transformer remains a config decision via the `typed_slots_transformers` block and stays off by default. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
The comment on lines 687-688 suggested `drop_unregistered_typed_slots` logs each dropped key individually with its reason. The live gate showed the function logs them once in a single warning naming all keys; update the comment to match the actual behavior. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…VOS-INTENT-4 §3.2) (#956) _on_register, _on_deregister, and _on_skill_deregister in IntentManifest read skill_id via `message.data.get("skill_id") or message.context.get("skill_id")`, preferring the payload over the context. OVOS-INTENT-4 §3.2 makes context["skill_id"] authoritative for these topics and requires dropping any message whose payload skill_id differs from it — a skill could otherwise register or deregister another skill's intents, and ovos.skill.deregister would amount to a remote uninstall. Each handler now reads skill_id from context only, drops the message with a WARN log when context lacks it, and drops with a WARN log when a present payload skill_id differs from the context value. _on_enable_disable is untouched: §3.2 exempts ovos.intent.enable/ disable as control messages where payload skill_id names the target and context skill_id names the source, and the two MAY differ. Fail-before: reverting only manifest.py made the 5 new mismatch/ missing-context tests fail (register, deregister, skill_deregister), confirming they exercise the fix. Existing tests elsewhere that built register messages with an empty context were updated to carry the producing skill_id in context, matching how the orchestrator actually stamps dispatch messages (§3.1). Full suite: 594 passed on dev baseline; 602 passed on this branch (+8 new regression tests, 0 regressions). Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…-owned policy field is re-imposed (OVOS-PIPELINE-1 §9.1.1, §5.5) (#957) §9.1.1: `_stamp_utterance_id` kept a caller-supplied `utterance_id` instead of replacing it at lifecycle entry, letting an entry Message dictate its own lifecycle id and letting a stale poll pong survive under it. It now always assigns a fresh id at entry and logs at debug when one was replaced; the no-overwrite behaviour still applies downstream (the transformer-chain re-assertion in `handle_utterance`). §5.5: `_dispatch_match` accepted a plugin's `updated_session` wholesale, subject only to a `resolved_session_id()` equality guard, without re-imposing any deployment-owned per-component override field from the session the orchestrator held before the plugin ran. It now snapshots and restores the full class SESSION-1 §3 registers for this role: `pipeline`, the six OVOS-TRANSFORM-1 §5 transformer-chain lists, the three blacklist denylists (`blacklisted_skills`/`_intents`/`_pipelines`), and `site_id`. Fail-before: reverting only the source change (test unchanged) fails all 3 new tests - `test_entry_stamp_replaces_any_supplied_identifier`, `test_stale_pong_carrying_the_replaced_id_is_discarded`, and `test_updated_session_cannot_relax_deployment_owned_policy` - each on the exact assertion the fix now satisfies. Full suite: 596 unittests + 42 end2end passed after the fix. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
OVOS-PIPELINE-1 §7.3 reserves converse, response, stop, fallback and common_query: skills and pipelines MUST NOT register under these names, and OVOS-INTENT-4 §5.3/§6.3 requires the malformed registration be logged at WARN and not indexed. The manifest's `_on_register` only special-cased the literal name `stop`, leaving the other four reserved names free to shadow the targeted-dispatch topics their reserving specs define. Generalises the check into a module-level `RESERVED_INTENT_NAMES` frozenset carrying the §7.3 citation, applies it to every `ovos.intent.register.*` topic the manifest indexes, and extends the same refusal to `_on_deregister` (a reserved name was never indexed, so deregistering it is a no-op, now logged rather than silent). Fail-before: reverting the source change makes `test_intent_manifest.py` fail collection with `ImportError: cannot import name 'RESERVED_INTENT_NAMES'` (the test module now imports the constant); restoring the source makes all 62 tests (5 subtests) pass, and the full unit suite (605 tests) is green. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
The manifest's register, deregister and skill-deregister handlers took the acting identity from message.context["skill_id"], dropped any message whose context lacked one, and dropped again when the payload skill_id differed from the context. OVOS-INTENT-4 §3.2 makes the payload skill_id the target and context.skill_id provenance only: a consumer must act on the payload value, must not treat a difference as grounds for rejection, and must not treat an absent context skill_id as malformed. All three handlers now resolve the target through _target_skill_id — payload first, context as fallback — logging source and target at DEBUG when they differ. The malformed-field and reserved-intent_name checks are unchanged; _on_enable_disable already had the conformant shape. Six regression tests cover payload/context mismatch and empty context for each of the three handlers; all six fail against the unfixed handlers (6 failed, 56 passed) and pass after (62 passed). Full unit suite: 605 passed. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Route the toggle handler through _target_skill_id so all four INTENT-4 §§5-8 handlers resolve identity the same way, and warn inside the helper when an absent payload target is filled from the context. The substitution stays: ovos-spec-tools bridges the legacy toggles onto the spec topics and the legacy payload has no skill_id to carry, so payload-only resolution would make every bridged toggle a silent no-op.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Human review requested! This supersedes any earlier open release proposal for this repository.