Jarvis2345 commited on
Commit
26466a0
Β·
verified Β·
1 Parent(s): 270c887

deploy(S4): Blender headless pipeline + WebAR client + backend fixes

Browse files
backend/routes/mobile_bridge_routes.py CHANGED
@@ -224,20 +224,34 @@ async def command(request: Request) -> dict[str, Any]:
224
  #
225
  # Falls through to record-only when no PC is listening, which is the honest
226
  # answer when the machine is off rather than a failure.
 
 
 
 
 
227
  try:
228
  from backend.ws.agent_ws import ws_manager as _ws_manager
229
- delivered = await _ws_manager.send_to_pcs({
230
- "event": "system:execute",
231
- "payload": {"cmd": cmd, "source": "guardian", "device_name": device_name},
232
- })
233
  except Exception as exc:
234
  log.warning("PC relay unavailable: %s", exc)
235
- delivered = 0
236
 
 
237
  if delivered:
 
 
 
 
 
 
 
 
 
238
  return {
239
- "ok": True,
240
- "spoken": [f"Sent to your PC."],
 
 
241
  "status": "relayed",
242
  "action": "pc_relay",
243
  "pc_count": delivered,
 
224
  #
225
  # Falls through to record-only when no PC is listening, which is the honest
226
  # answer when the machine is off rather than a failure.
227
+ #
228
+ # We wait for the PC's actual answer rather than reporting success on
229
+ # hand-off. The desktop can refuse a command outright β€” an un-activated
230
+ # session hard-blocks every one of them β€” and reporting "Sent to your PC."
231
+ # in that case tells the user their command ran when it did not.
232
  try:
233
  from backend.ws.agent_ws import ws_manager as _ws_manager
234
+ outcome = await _ws_manager.execute_on_pcs(cmd, device_name=device_name)
 
 
 
235
  except Exception as exc:
236
  log.warning("PC relay unavailable: %s", exc)
237
+ outcome = {"delivered": 0, "ok": False, "spoken": [], "timed_out": False}
238
 
239
+ delivered = int(outcome.get("delivered") or 0)
240
  if delivered:
241
+ if outcome.get("timed_out"):
242
+ return {
243
+ "ok": False,
244
+ "spoken": ["Your PC received the command but did not report back."],
245
+ "status": "no_response",
246
+ "action": "pc_relay",
247
+ "pc_count": delivered,
248
+ }
249
+ spoken = outcome.get("spoken") or []
250
  return {
251
+ # The PC's own words when it has any β€” that is where "This PC is
252
+ # locked…" reaches the phone β€” and a plain confirmation otherwise.
253
+ "ok": bool(outcome.get("ok")),
254
+ "spoken": spoken or ["Done on your PC."],
255
  "status": "relayed",
256
  "action": "pc_relay",
257
  "pc_count": delivered,
backend/ws/agent_ws.py CHANGED
@@ -56,6 +56,12 @@ class ConnectionManager:
56
  # idea which socket it was. That is why phone -> cloud -> PC control did
57
  # not work: not a missing channel, an unread introduction.
58
  self.pc_connections: list[WebSocket] = []
 
 
 
 
 
 
59
  from backend.agent.react_agent import Tool
60
  self.tools = []
61
  for name, func in TOOL_REGISTRY.items():
@@ -102,6 +108,49 @@ class ConnectionManager:
102
  self.pc_connections.remove(connection)
103
  return delivered
104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  async def broadcast(self, message: dict):
106
  for connection in self.active_connections:
107
  try:
@@ -134,6 +183,27 @@ class ConnectionManager:
134
  except Exception:
135
  pass
136
  return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  # ── WAKE WORD β†’ PERSONA SWITCH (Cloud + Local path) ──────────────────────
138
  # Mobile app / local PC sends: {"event": "voice:wake_word", "payload": {"agent": "hey jarvis"}}
139
  # The server auto-switches the active persona and broadcasts UI + TTS confirmation.
 
56
  # idea which socket it was. That is why phone -> cloud -> PC control did
57
  # not work: not a missing channel, an unread introduction.
58
  self.pc_connections: list[WebSocket] = []
59
+ # In-flight command_id -> Future, resolved when a PC reports the outcome.
60
+ # Without this the cloud answered the phone the instant it had *handed
61
+ # off* the command, so "Sent to your PC." was printed for commands the
62
+ # desktop then refused outright (locked session) or failed to run. The
63
+ # phone showed success for work that never happened.
64
+ self.pending_results: dict[str, asyncio.Future] = {}
65
  from backend.agent.react_agent import Tool
66
  self.tools = []
67
  for name, func in TOOL_REGISTRY.items():
 
108
  self.pc_connections.remove(connection)
109
  return delivered
110
 
111
+ async def execute_on_pcs(self, cmd: str, device_name: str = "",
112
+ timeout: float = 20.0) -> dict:
113
+ """Send a command to the PCs and wait for the first real outcome.
114
+
115
+ Returns {delivered, ok, spoken, timed_out}. `delivered == 0` means no
116
+ desktop was listening; `timed_out` means one was, but it never answered
117
+ (old relay build, or the command stack hung) β€” three states the caller
118
+ must be able to tell apart, because only one of them is success.
119
+ """
120
+ command_id = str(uuid.uuid4())
121
+ loop = asyncio.get_running_loop()
122
+ future: asyncio.Future = loop.create_future()
123
+ self.pending_results[command_id] = future
124
+ try:
125
+ delivered = await self.send_to_pcs({
126
+ "event": "system:execute",
127
+ "payload": {
128
+ "cmd": cmd,
129
+ "source": "guardian",
130
+ "device_name": device_name,
131
+ "command_id": command_id,
132
+ },
133
+ })
134
+ if not delivered:
135
+ return {"delivered": 0, "ok": False, "spoken": [], "timed_out": False}
136
+ try:
137
+ result = await asyncio.wait_for(future, timeout=timeout)
138
+ except asyncio.TimeoutError:
139
+ logging.warning("PC relay did not report a result for %r", cmd)
140
+ return {"delivered": delivered, "ok": False, "spoken": [],
141
+ "timed_out": True}
142
+ spoken = result.get("spoken") or []
143
+ if isinstance(spoken, str):
144
+ spoken = [spoken]
145
+ return {
146
+ "delivered": delivered,
147
+ "ok": bool(result.get("ok")),
148
+ "spoken": [str(s) for s in spoken],
149
+ "timed_out": False,
150
+ }
151
+ finally:
152
+ self.pending_results.pop(command_id, None)
153
+
154
  async def broadcast(self, message: dict):
155
  for connection in self.active_connections:
156
  try:
 
183
  except Exception:
184
  pass
185
  return
186
+
187
+ # ── WHAT HAPPENED? ──────────────────────────────────────────────────────
188
+ # The PC reports back the outcome of a system:execute it was given, keyed
189
+ # by the command_id the cloud minted. This closes the loop that used to
190
+ # be open-ended: without it the phone was told "Sent to your PC." and
191
+ # never learned that the desktop had refused the command.
192
+ if data.get("event") == "relay:result":
193
+ payload = data.get("payload") or {}
194
+ command_id = str(payload.get("command_id") or "")
195
+ future = self.pending_results.get(command_id)
196
+ if future is not None and not future.done():
197
+ future.set_result({
198
+ "ok": bool(payload.get("ok")),
199
+ "spoken": payload.get("spoken") or [],
200
+ })
201
+ else:
202
+ # Late or duplicate answer β€” the waiter already gave up, or a
203
+ # second PC answered after the first. Never an error.
204
+ logging.debug("relay:result for unknown command_id %r", command_id)
205
+ return
206
+
207
  # ── WAKE WORD β†’ PERSONA SWITCH (Cloud + Local path) ──────────────────────
208
  # Mobile app / local PC sends: {"event": "voice:wake_word", "payload": {"agent": "hey jarvis"}}
209
  # The server auto-switches the active persona and broadcasts UI + TTS confirmation.