feat(n7web): refine spatial UI and NFC-gated extension routing

This commit is contained in:
Miayui
2026-09-16 16:14:10 +08:00
parent 0e4328f548
commit 75ce391312
16 changed files with 925 additions and 106 deletions
+91 -1
View File
@@ -1331,7 +1331,7 @@ operations:
self.assertIn('canvas.addEventListener("webglcontextlost"', app_script)
self.assertIn('"cognitive-agent:module-change"', app_script)
self.assertIn(
'const CACHE_VERSION = "cognitive-agent-pad-v315-vue-vite"',
'const CACHE_VERSION = "cognitive-agent-pad-v317-nfc-auth-routing"',
service_worker,
)
self.assertIn('/console-v2.css?v=delivery-state-69', service_worker)
@@ -2084,6 +2084,96 @@ operations:
finally:
SeatDemoHandler.extension = original_extension
def test_new_tcp_slot_is_immediately_parsed_but_blocked_until_nfc_binding(
self,
) -> None:
original_extension = SeatDemoHandler.extension
original_nfc = SeatDemoHandler.nfc
with tempfile.TemporaryDirectory() as directory:
extension = SelfExtensionService(
SimulationGateway(),
catalog_db_path=Path(directory, "unbound-cactus.sqlite3"),
)
SeatDemoHandler.extension = extension
SeatDemoHandler.nfc = SimpleNamespace(
snapshot=lambda: {
"revision": 1,
"reader": {"online": True},
"card": None,
}
)
save_request = urllib.request.Request(
f"{self.base_url}/api/extension/gscu/slots/11",
method="PUT",
headers={"Content-Type": "application/json"},
data=json.dumps(
{
"device_id": "toy-cacti-002",
"object_names": ["仙人掌"],
"action_names": ["打开", "开启"],
"source": "tcp",
},
ensure_ascii=False,
).encode("utf-8"),
)
control_request = urllib.request.Request(
f"{self.base_url}/api/requests",
method="POST",
headers={
"Content-Type": "application/json",
"Accept": "application/x-ndjson",
},
data=json.dumps(
{
"utterance": "打开仙人掌",
"provider": "local",
"conversation_id": "conv-unbound-cactus-e2e",
"execution_mode": "hardware",
"client_type": "web_console",
},
ensure_ascii=False,
).encode("utf-8"),
)
try:
with self.opener.open(save_request, timeout=3) as response:
self.assertEqual(response.status, 200)
with patch.object(
extension,
"execute_gateway",
side_effect=AssertionError("unbound IR command must not be emitted"),
):
with self.opener.open(control_request, timeout=3) as response:
events = [
json.loads(line)
for line in response.read().decode("utf-8").splitlines()
if line.strip()
]
finally:
SeatDemoHandler.extension = original_extension
SeatDemoHandler.nfc = original_nfc
self.assertEqual(events[-1]["type"], "result")
result = events[-1]["payload"]
self.assertEqual(result["status"], "blocked")
self.assertLess(result["local_control_elapsed_ms"], 1000)
self.assertEqual(result["parsed_clauses"], ["打开仙人掌"])
self.assertEqual(result["unresolved_clauses"], [])
self.assertEqual(
result["denied_peripheral_actions"][0]["reason"],
"nfc_binding_required",
)
operation = next(
operation
for config in result["function_configs"].values()
for operation in config["spec"]["operations"]
)
self.assertEqual(operation["target"]["device"], "toy-cacti-002")
self.assertEqual(operation["parameters"], {"action": "打开", "slot": 11})
self.assertEqual(result["operation_results"][0]["status"], "blocked")
self.assertFalse(
result["operation_results"][0]["telemetry"]["transport_submitted"]
)
def test_main_request_routes_durable_nfc_peripheral_after_card_removal(self) -> None:
original_extension = SeatDemoHandler.extension
original_nfc = SeatDemoHandler.nfc