feat: integrate learned peripherals into mode creation
Expose safe NFC-bound infrared actions to semantic scene composition, require flashing-light actions for romantic and Mid-Autumn modes, and preserve mixed CAN/IR mode flows. Fix NFC device reassignment and retry extension/toy MQTT publication when an active card's binding catalog changes.
This commit is contained in:
@@ -91,15 +91,23 @@ def test_scene_context_contains_only_callable_reusable_atomic_capabilities(
|
||||
assert all(item.get("target_scopes") for item in capabilities)
|
||||
assert all(isinstance(item.get("parameters"), list) for item in capabilities)
|
||||
|
||||
# The scene planner needs contracts, not mutable runtime surfaces or saved
|
||||
# modes. Omitting them also prevents catalog text from becoming prompt
|
||||
# injection material and keeps the cold-path prompt small.
|
||||
# The scene planner gets a bounded inventory, not mutable runtime surfaces,
|
||||
# saved modes, NFC identifiers, slots, or transport payloads.
|
||||
assert "modes" not in context
|
||||
assert "learned_peripherals" not in context
|
||||
assert "service_surfaces" not in context
|
||||
assert "device_groups" not in context
|
||||
assert "execution_context" not in context
|
||||
assert "忽略此前规则" not in json.dumps(context, ensure_ascii=False)
|
||||
assert context["peripheral_actions"] == []
|
||||
assert context["device_inventory"][-1]["availability"] == "unavailable"
|
||||
door_inventory = next(
|
||||
item for item in context["device_inventory"] if item.get("id") == "door"
|
||||
)
|
||||
assert door_inventory["availability"] == "unavailable"
|
||||
assert door_inventory["capability_ids"] == ["door.set_rear_lock"]
|
||||
encoded_context = json.dumps(context, ensure_ascii=False)
|
||||
assert "card_uid" not in encoded_context
|
||||
assert '"slot"' not in encoded_context
|
||||
|
||||
forbidden_capability_fields = {
|
||||
"bindings",
|
||||
@@ -154,7 +162,6 @@ def test_scene_context_exposes_only_absolute_parameters_and_native_scene_bounds(
|
||||
(
|
||||
lambda snapshot: snapshot.__setitem__("catalog_revision", "runtime-only-change"),
|
||||
lambda snapshot: snapshot.__setitem__("modes", []),
|
||||
lambda snapshot: snapshot.__setitem__("learned_peripherals", []),
|
||||
lambda snapshot: snapshot.__setitem__(
|
||||
"execution_context", {"current_physical_availability": "preflight_required"}
|
||||
),
|
||||
@@ -175,6 +182,69 @@ def test_composition_revision_ignores_dynamic_non_planning_state(
|
||||
assert _revision(_scene_context(capability_catalog, changed)) == expected
|
||||
|
||||
|
||||
def test_scene_context_exposes_only_authorized_safe_peripheral_refs() -> None:
|
||||
catalog = AssistantCapabilityCatalog(
|
||||
SeatDemoService(),
|
||||
peripheral_provider=lambda: [
|
||||
{
|
||||
"id": "ir-slot:romance-light:7",
|
||||
"status": "learned",
|
||||
"enabled": True,
|
||||
"device_id": "romance-light",
|
||||
"device_name": "氛围灯带",
|
||||
"object_names": ["氛围灯带", "灯带"],
|
||||
"device_type": "light",
|
||||
"card_uid": "2CC8B103",
|
||||
"action": "设置为红色",
|
||||
"action_names": ["设置为红色", "红色"],
|
||||
"risk": "low",
|
||||
"slot": 7,
|
||||
},
|
||||
{
|
||||
"id": "ir-slot:unbound-light:8",
|
||||
"status": "learned",
|
||||
"enabled": False,
|
||||
"device_id": "unbound-light",
|
||||
"device_name": "未授权灯带",
|
||||
"object_names": ["未授权灯带"],
|
||||
"action": "打开",
|
||||
"action_names": ["打开"],
|
||||
"slot": 8,
|
||||
},
|
||||
],
|
||||
)
|
||||
context = catalog.scene_composition_context(catalog.snapshot())
|
||||
|
||||
assert len(context["peripheral_actions"]) == 1
|
||||
action = context["peripheral_actions"][0]
|
||||
assert action["ref"].startswith("ir:")
|
||||
assert action["device_label"] == "氛围灯带"
|
||||
assert action["action_label"] == "设置为红色"
|
||||
inventory = [
|
||||
item
|
||||
for item in context["device_inventory"]
|
||||
if item["kind"] == "learned_infrared"
|
||||
]
|
||||
assert [item["availability"] for item in inventory] == [
|
||||
"available",
|
||||
"unavailable",
|
||||
]
|
||||
encoded = json.dumps(context, ensure_ascii=False)
|
||||
assert "2CC8B103" not in encoded
|
||||
assert '"slot":7' not in encoded
|
||||
|
||||
|
||||
def test_composition_revision_changes_with_peripheral_planning_contract(
|
||||
capability_catalog: AssistantCapabilityCatalog,
|
||||
) -> None:
|
||||
snapshot = capability_catalog.snapshot()
|
||||
expected = _revision(_scene_context(capability_catalog, snapshot))
|
||||
changed = copy.deepcopy(snapshot)
|
||||
changed["learned_peripherals"] = []
|
||||
|
||||
assert _revision(_scene_context(capability_catalog, changed)) != expected
|
||||
|
||||
|
||||
def test_composition_revision_is_order_independent(
|
||||
capability_catalog: AssistantCapabilityCatalog,
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user