From 46a5362bf8d22af354b191b2047b2222721c7b6d Mon Sep 17 00:00:00 2001 From: Ali Alavi Date: Sun, 23 Aug 2026 20:35:28 +0000 Subject: [PATCH] fix(gateway): route insert_speech and speech_insert payloads to Mac clients without drop --- server/relay_server.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/server/relay_server.py b/server/relay_server.py index f0e8c53..b876b30 100644 --- a/server/relay_server.py +++ b/server/relay_server.py @@ -1,10 +1,8 @@ #!/usr/bin/env python3 """ -Soniox Collaborative Bi-Directional Input Synchronization Gateway (v5.2) -- Real-time Google Docs / Figma style collaborative mirroring between Mac and Android. -- Ultra-low latency streaming speech recognition with pre-warmed Soniox pool. -- Monotonic revision counters, echo-loop suppression, and sub-15ms WebSocket routing. -- Process-targeted cursor-aware voice insertion. +Soniox Collaborative Bi-Directional Input Synchronization Gateway (v5.3) +- Handles all message types: sync_state, insert_speech, speech_insert, update_input, paste. +- Sub-15ms WebSocket routing between Android and Mac. """ import asyncio @@ -125,6 +123,10 @@ async def broadcast_state(payload_dict: dict, exclude_ws=None): current_room_state.update(payload_dict) payload_str = json.dumps(payload_dict, ensure_ascii=False) + logger.info("📡 Broadcasting %s (len: %d) to %d Macs, %d Phones", + payload_dict.get("type"), len(payload_dict.get("text", "")), + len(connected_mac_websockets), len(connected_phone_websockets)) + # 1. Send to Phone clients dead_phones = set() for ws in list(connected_phone_websockets): @@ -239,10 +241,10 @@ async def handle_phone_stream_ws(request): msg_type = data.get("type") or data.get("action") sid = data.get("session_id", f"sess_{int(time.time()*1000)}") - if msg_type == "sync_state" or msg_type == "phone_input_edit" or msg_type == "update_input": - # Phone edited text: broadcast to Mac immediately! + # ALL text / sync / insert operations must be broadcast to Mac! + if msg_type in ("sync_state", "insert_speech", "speech_insert", "phone_input_edit", "update_input", "paste"): data["source"] = "android" - data["type"] = "sync_state" + data["type"] = msg_type await broadcast_state(data, exclude_ws=ws) elif msg_type == "start": @@ -265,7 +267,6 @@ async def handle_phone_stream_ws(request): if active_soniox_ws and is_ws_open(active_soniox_ws): await active_soniox_ws.send(json.dumps({"type": "finalize"})) try: - # 350ms fast timeout for finalize await asyncio.wait_for(stop_event.wait(), timeout=0.35) except asyncio.TimeoutError: pass @@ -350,7 +351,7 @@ async def handle_health(request): state_copy = dict(current_room_state) return web.json_response({ "status": "ok", - "service": "Soniox Collaborative Sync Gateway v5.2", + "service": "Soniox Collaborative Sync Gateway v5.3", "connected_macs": len(connected_mac_websockets), "connected_phones": len(connected_phone_websockets), "current_app": state_copy.get("app", ""), @@ -364,7 +365,7 @@ async def handle_paste(request): text = data.get("text", "") cursor = data.get("cursor_pos") or data.get("cursor") data["source"] = "http_post" - data["type"] = "sync_state" + data["type"] = "update_input" await broadcast_state(data) return web.json_response({"status": "synced", "revision": current_room_state["revision"]}) except Exception as e: