diff --git a/pyproject.toml b/pyproject.toml index 9ebe6254..a08cfcae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,10 +11,10 @@ license = "apache-2.0" authors = [{name = "jarbasAi", email = "jarbasai@mailfence.com"}] requires-python = ">=3.9" dependencies = [ - "ovos-utils>= 0.7.0,<1.0.0", - "ovos_bus_client>=2.6.0a1,<3.0.0", # HandlerLifecycle done-signal util (#246) + "ovos-utils>= 0.13.3a2,<1.0.0", # FakeBus folds before handlers (ovos-utils#396) — keeps in-place add_context mutations alive so intent-layer gating works + "ovos_bus_client>=2.6.2a2,<3.0.0", # HandlerLifecycle done-signal util (#246) + singleton-registry SessionManager (2.6.2a2) "ovos-config>=0.0.12,<3.0.0", - "ovos-spec-tools>=0.16.1a2", # carries the adapt-free intent-definition primitives (OVOS-INTENT-4) + "ovos-spec-tools>=1.2.3a1", # canonical SessionManager registry + OVOS-INTENT-4 intent-definition primitives "ovos-yes-no-plugin>=0.3.0,<1.0.0", "ovos-option-matcher-fuzzy-plugin>=0.0.1,<1.0.0", "ovos-number-parser>=0.0.1,<1.0.0", @@ -26,9 +26,9 @@ dependencies = [ [project.optional-dependencies] test = [ - "ovos-core>=0.0.8a50", + "ovos-core>=2.4.0a1", # orchestrator owns ovos.utterance.handled on the matched path (PIPELINE-1 §9.5) "ovoscope>=0.1.0", - "ovos-adapt-parser>=1.3.1a1", + "ovos-adapt-parser>=1.4.2a1", "pytest", "pytest-cov", "ovos-translate-server-plugin", diff --git a/test/unittests/skills/test_base.py b/test/unittests/skills/test_base.py index b4ec9dbf..081800d4 100644 --- a/test/unittests/skills/test_base.py +++ b/test/unittests/skills/test_base.py @@ -355,16 +355,41 @@ def test_on_event_end(self): self.assertEqual(context["skill_id"], self.skill_id) self.assertEqual(context["session"]["session_id"], "sess1") - def test_on_event_end_is_intent_still_emits_utterance_handled(self): - # the ovos.utterance.handled emission must be PRESERVED alongside the - # delegated .complete done-signal when is_intent=True + def test_on_event_end_intent_defers_utterance_handled_to_core(self): + # PIPELINE-1 §9.5: with a modern ovos-core (>=2.3.0a1) the orchestrator + # owns the universal `ovos.utterance.handled` end-marker on every + # terminal path — the matched path included. Workshop must NOT also + # emit it (consumers would see the marker twice); only the delegated + # `mycroft.skill.handler.complete` done-signal is emitted here. + from unittest.mock import patch from ovos_bus_client.message import Message from ovos_spec_tools import SpecMessage msg = Message("trigger", {}, {}) skill_data = {"name": "TestSkill.handle_test"} - captured = self._capture(OVOSSkill._on_event_end, msg, - "mycroft.skill.handler", dict(skill_data), - True) + with patch("ovos_workshop.skills.ovos._core_owns_utterance_handled", + return_value=True): + captured = self._capture(OVOSSkill._on_event_end, msg, + "mycroft.skill.handler", + dict(skill_data), True) + types = [c[0] for c in captured] + self.assertIn("mycroft.skill.handler.complete", types) + self.assertNotIn(SpecMessage.UTTERANCE_HANDLED.value, types) + + def test_on_event_end_intent_emits_utterance_handled_legacy(self): + # Migration-window back-compat: with an absent/old ovos-core that does + # not yet emit `ovos.utterance.handled` on the matched path, workshop + # must still emit it alongside the delegated `.complete` done-signal so + # spec consumers always observe exactly one end-marker. + from unittest.mock import patch + from ovos_bus_client.message import Message + from ovos_spec_tools import SpecMessage + msg = Message("trigger", {}, {}) + skill_data = {"name": "TestSkill.handle_test"} + with patch("ovos_workshop.skills.ovos._core_owns_utterance_handled", + return_value=False): + captured = self._capture(OVOSSkill._on_event_end, msg, + "mycroft.skill.handler", + dict(skill_data), True) types = [c[0] for c in captured] self.assertIn("mycroft.skill.handler.complete", types) self.assertIn(SpecMessage.UTTERANCE_HANDLED.value, types)